126ad19fcSEvan Bacon/**
226ad19fcSEvan Bacon * Copyright (c) 650 Industries.
326ad19fcSEvan Bacon * Copyright (c) Meta Platforms, Inc. and affiliates.
426ad19fcSEvan Bacon *
526ad19fcSEvan Bacon * This source code is licensed under the MIT license found in the
626ad19fcSEvan Bacon * LICENSE file in the root directory of this source tree.
726ad19fcSEvan Bacon */
826ad19fcSEvan Bacon
926ad19fcSEvan Baconimport * as React from 'react';
1026ad19fcSEvan Bacon
1126ad19fcSEvan Baconimport { LogBoxLog, StackType } from './LogBoxLog';
1226ad19fcSEvan Baconimport type { LogLevel } from './LogBoxLog';
1326ad19fcSEvan Baconimport { LogContext } from './LogContext';
1426ad19fcSEvan Baconimport { parseLogBoxException } from './parseLogBoxLog';
1526ad19fcSEvan Baconimport type { Message, Category, ComponentStack, ExtendedExceptionData } from './parseLogBoxLog';
16*8a424bebSJames Ideimport NativeLogBox from '../modules/NativeLogBox';
17*8a424bebSJames Ideimport parseErrorStack from '../modules/parseErrorStack';
1826ad19fcSEvan Bacon
1926ad19fcSEvan Baconexport type LogBoxLogs = Set<LogBoxLog>;
2026ad19fcSEvan Bacon
2126ad19fcSEvan Baconexport type LogData = {
2226ad19fcSEvan Bacon  level: LogLevel;
2326ad19fcSEvan Bacon  message: Message;
2426ad19fcSEvan Bacon  category: Category;
2526ad19fcSEvan Bacon  componentStack: ComponentStack;
2626ad19fcSEvan Bacon};
2726ad19fcSEvan Bacon
2826ad19fcSEvan Bacontype ExtendedError = any;
2926ad19fcSEvan Bacon
3026ad19fcSEvan Baconexport type Observer = (options: {
3126ad19fcSEvan Bacon  logs: LogBoxLogs;
3226ad19fcSEvan Bacon  isDisabled: boolean;
3326ad19fcSEvan Bacon  selectedLogIndex: number;
3426ad19fcSEvan Bacon}) => void;
3526ad19fcSEvan Bacon
3626ad19fcSEvan Baconexport type IgnorePattern = string | RegExp;
3726ad19fcSEvan Bacon
3826ad19fcSEvan Baconexport type Subscription = {
3926ad19fcSEvan Bacon  unsubscribe: () => void;
4026ad19fcSEvan Bacon};
4126ad19fcSEvan Bacon
4226ad19fcSEvan Baconexport type WarningInfo = {
4326ad19fcSEvan Bacon  finalFormat: string;
4426ad19fcSEvan Bacon  forceDialogImmediately: boolean;
4526ad19fcSEvan Bacon  suppressDialog_LEGACY: boolean;
4626ad19fcSEvan Bacon  suppressCompletely: boolean;
4726ad19fcSEvan Bacon  monitorEvent: string | null;
4826ad19fcSEvan Bacon  monitorListVersion: number;
4926ad19fcSEvan Bacon  monitorSampleRate: number;
5026ad19fcSEvan Bacon};
5126ad19fcSEvan Bacon
5226ad19fcSEvan Baconexport type WarningFilter = (format: string) => WarningInfo;
5326ad19fcSEvan Bacon
5426ad19fcSEvan Bacontype Props = object;
5526ad19fcSEvan Bacon
5626ad19fcSEvan Bacontype State = {
5726ad19fcSEvan Bacon  logs: LogBoxLogs;
5826ad19fcSEvan Bacon  isDisabled: boolean;
5926ad19fcSEvan Bacon  hasError: boolean;
6026ad19fcSEvan Bacon  selectedLogIndex: number;
6126ad19fcSEvan Bacon};
6226ad19fcSEvan Bacon
6326ad19fcSEvan Baconconst observers: Set<{ observer: Observer } & any> = new Set();
6426ad19fcSEvan Baconconst ignorePatterns: Set<IgnorePattern> = new Set();
6526ad19fcSEvan Baconlet logs: LogBoxLogs = new Set();
6626ad19fcSEvan Baconlet updateTimeout: null | ReturnType<typeof setImmediate> | ReturnType<typeof setTimeout> = null;
6726ad19fcSEvan Baconlet _isDisabled = false;
6826ad19fcSEvan Baconlet _selectedIndex = -1;
6926ad19fcSEvan Bacon
7026ad19fcSEvan Baconconst LOGBOX_ERROR_MESSAGE =
7126ad19fcSEvan Bacon  'An error was thrown when attempting to render log messages via LogBox.';
7226ad19fcSEvan Bacon
7326ad19fcSEvan Baconfunction getNextState() {
7426ad19fcSEvan Bacon  return {
7526ad19fcSEvan Bacon    logs,
7626ad19fcSEvan Bacon    isDisabled: _isDisabled,
7726ad19fcSEvan Bacon    selectedLogIndex: _selectedIndex,
7826ad19fcSEvan Bacon  };
7926ad19fcSEvan Bacon}
8026ad19fcSEvan Bacon
8126ad19fcSEvan Baconexport function reportLogBoxError(error: ExtendedError, componentStack?: string): void {
8226ad19fcSEvan Bacon  const ExceptionsManager = require('../modules/ExceptionsManager').default;
8326ad19fcSEvan Bacon
8426ad19fcSEvan Bacon  if (componentStack != null) {
8526ad19fcSEvan Bacon    error.componentStack = componentStack;
8626ad19fcSEvan Bacon  }
8726ad19fcSEvan Bacon  ExceptionsManager.handleException(error);
8826ad19fcSEvan Bacon}
8926ad19fcSEvan Bacon
9026ad19fcSEvan Baconexport function reportUnexpectedLogBoxError(error: ExtendedError, componentStack?: string): void {
9126ad19fcSEvan Bacon  error.message = `${LOGBOX_ERROR_MESSAGE}\n\n${error.message}`;
9226ad19fcSEvan Bacon  return reportLogBoxError(error, componentStack);
9326ad19fcSEvan Bacon}
9426ad19fcSEvan Bacon
9526ad19fcSEvan Baconexport function isLogBoxErrorMessage(message: string): boolean {
9626ad19fcSEvan Bacon  return typeof message === 'string' && message.includes(LOGBOX_ERROR_MESSAGE);
9726ad19fcSEvan Bacon}
9826ad19fcSEvan Bacon
9926ad19fcSEvan Baconexport function isMessageIgnored(message: string): boolean {
10026ad19fcSEvan Bacon  for (const pattern of ignorePatterns) {
10126ad19fcSEvan Bacon    if (
10226ad19fcSEvan Bacon      (pattern instanceof RegExp && pattern.test(message)) ||
10326ad19fcSEvan Bacon      (typeof pattern === 'string' && message.includes(pattern))
10426ad19fcSEvan Bacon    ) {
10526ad19fcSEvan Bacon      return true;
10626ad19fcSEvan Bacon    }
10726ad19fcSEvan Bacon  }
10826ad19fcSEvan Bacon  return false;
10926ad19fcSEvan Bacon}
11026ad19fcSEvan Bacon
11126ad19fcSEvan Baconfunction setImmediateShim(callback: () => void) {
11226ad19fcSEvan Bacon  if (!global.setImmediate) {
11326ad19fcSEvan Bacon    return setTimeout(callback, 0);
11426ad19fcSEvan Bacon  }
11526ad19fcSEvan Bacon  return global.setImmediate(callback);
11626ad19fcSEvan Bacon}
11726ad19fcSEvan Bacon
11826ad19fcSEvan Baconfunction handleUpdate(): void {
11926ad19fcSEvan Bacon  if (updateTimeout == null) {
12026ad19fcSEvan Bacon    updateTimeout = setImmediateShim(() => {
12126ad19fcSEvan Bacon      updateTimeout = null;
12226ad19fcSEvan Bacon      const nextState = getNextState();
12326ad19fcSEvan Bacon      observers.forEach(({ observer }) => observer(nextState));
12426ad19fcSEvan Bacon    });
12526ad19fcSEvan Bacon  }
12626ad19fcSEvan Bacon}
12726ad19fcSEvan Bacon
12826ad19fcSEvan Baconfunction appendNewLog(newLog: LogBoxLog): void {
12926ad19fcSEvan Bacon  // Don't want store these logs because they trigger a
13026ad19fcSEvan Bacon  // state update when we add them to the store.
13126ad19fcSEvan Bacon  if (isMessageIgnored(newLog.message.content)) {
13226ad19fcSEvan Bacon    return;
13326ad19fcSEvan Bacon  }
13426ad19fcSEvan Bacon
13526ad19fcSEvan Bacon  // If the next log has the same category as the previous one
13626ad19fcSEvan Bacon  // then roll it up into the last log in the list by incrementing
13726ad19fcSEvan Bacon  // the count (similar to how Chrome does it).
13826ad19fcSEvan Bacon  const lastLog = Array.from(logs).pop();
13926ad19fcSEvan Bacon  if (lastLog && lastLog.category === newLog.category) {
14026ad19fcSEvan Bacon    lastLog.incrementCount();
14126ad19fcSEvan Bacon    handleUpdate();
14226ad19fcSEvan Bacon    return;
14326ad19fcSEvan Bacon  }
14426ad19fcSEvan Bacon
14526ad19fcSEvan Bacon  if (newLog.level === 'fatal') {
14626ad19fcSEvan Bacon    // If possible, to avoid jank, we don't want to open the error before
14726ad19fcSEvan Bacon    // it's symbolicated. To do that, we optimistically wait for
14826ad19fcSEvan Bacon    // symbolication for up to a second before adding the log.
14926ad19fcSEvan Bacon    const OPTIMISTIC_WAIT_TIME = 1000;
15026ad19fcSEvan Bacon
15126ad19fcSEvan Bacon    let addPendingLog: null | (() => void) = () => {
15226ad19fcSEvan Bacon      logs.add(newLog);
15326ad19fcSEvan Bacon      if (_selectedIndex < 0) {
15426ad19fcSEvan Bacon        setSelectedLog(logs.size - 1);
15526ad19fcSEvan Bacon      } else {
15626ad19fcSEvan Bacon        handleUpdate();
15726ad19fcSEvan Bacon      }
15826ad19fcSEvan Bacon      addPendingLog = null;
15926ad19fcSEvan Bacon    };
16026ad19fcSEvan Bacon
16126ad19fcSEvan Bacon    const optimisticTimeout = setTimeout(() => {
16226ad19fcSEvan Bacon      if (addPendingLog) {
16326ad19fcSEvan Bacon        addPendingLog();
16426ad19fcSEvan Bacon      }
16526ad19fcSEvan Bacon    }, OPTIMISTIC_WAIT_TIME);
16626ad19fcSEvan Bacon
16726ad19fcSEvan Bacon    // TODO: HANDLE THIS
16826ad19fcSEvan Bacon    newLog.symbolicate('component');
16926ad19fcSEvan Bacon
17026ad19fcSEvan Bacon    newLog.symbolicate('stack', (status) => {
17126ad19fcSEvan Bacon      if (addPendingLog && status !== 'PENDING') {
17226ad19fcSEvan Bacon        addPendingLog();
17326ad19fcSEvan Bacon        clearTimeout(optimisticTimeout);
17426ad19fcSEvan Bacon      } else if (status !== 'PENDING') {
17526ad19fcSEvan Bacon        // The log has already been added but we need to trigger a render.
17626ad19fcSEvan Bacon        handleUpdate();
17726ad19fcSEvan Bacon      }
17826ad19fcSEvan Bacon    });
17926ad19fcSEvan Bacon  } else if (newLog.level === 'syntax') {
18026ad19fcSEvan Bacon    logs.add(newLog);
18126ad19fcSEvan Bacon    setSelectedLog(logs.size - 1);
18226ad19fcSEvan Bacon  } else {
18326ad19fcSEvan Bacon    logs.add(newLog);
18426ad19fcSEvan Bacon    handleUpdate();
18526ad19fcSEvan Bacon  }
18626ad19fcSEvan Bacon}
18726ad19fcSEvan Bacon
18826ad19fcSEvan Baconexport function addLog(log: LogData): void {
18926ad19fcSEvan Bacon  const errorForStackTrace = new Error();
19026ad19fcSEvan Bacon
19126ad19fcSEvan Bacon  // Parsing logs are expensive so we schedule this
19226ad19fcSEvan Bacon  // otherwise spammy logs would pause rendering.
19326ad19fcSEvan Bacon  setImmediate(() => {
19426ad19fcSEvan Bacon    try {
19526ad19fcSEvan Bacon      const stack = parseErrorStack(errorForStackTrace?.stack);
19626ad19fcSEvan Bacon
19726ad19fcSEvan Bacon      appendNewLog(
19826ad19fcSEvan Bacon        new LogBoxLog({
19926ad19fcSEvan Bacon          level: log.level,
20026ad19fcSEvan Bacon          message: log.message,
20126ad19fcSEvan Bacon          isComponentError: false,
20226ad19fcSEvan Bacon          stack,
20326ad19fcSEvan Bacon          category: log.category,
20426ad19fcSEvan Bacon          componentStack: log.componentStack,
20526ad19fcSEvan Bacon        })
20626ad19fcSEvan Bacon      );
20726ad19fcSEvan Bacon    } catch (error) {
20826ad19fcSEvan Bacon      reportUnexpectedLogBoxError(error);
20926ad19fcSEvan Bacon    }
21026ad19fcSEvan Bacon  });
21126ad19fcSEvan Bacon}
21226ad19fcSEvan Bacon
21326ad19fcSEvan Baconexport function addException(error: ExtendedExceptionData): void {
21426ad19fcSEvan Bacon  // Parsing logs are expensive so we schedule this
21526ad19fcSEvan Bacon  // otherwise spammy logs would pause rendering.
21626ad19fcSEvan Bacon  setImmediate(() => {
21726ad19fcSEvan Bacon    try {
21826ad19fcSEvan Bacon      appendNewLog(new LogBoxLog(parseLogBoxException(error)));
21926ad19fcSEvan Bacon    } catch (loggingError) {
22026ad19fcSEvan Bacon      reportUnexpectedLogBoxError(loggingError);
22126ad19fcSEvan Bacon    }
22226ad19fcSEvan Bacon  });
22326ad19fcSEvan Bacon}
22426ad19fcSEvan Bacon
22526ad19fcSEvan Baconexport function symbolicateLogNow(type: StackType, log: LogBoxLog) {
22626ad19fcSEvan Bacon  log.symbolicate(type, () => {
22726ad19fcSEvan Bacon    handleUpdate();
22826ad19fcSEvan Bacon  });
22926ad19fcSEvan Bacon}
23026ad19fcSEvan Bacon
23126ad19fcSEvan Baconexport function retrySymbolicateLogNow(type: StackType, log: LogBoxLog) {
23226ad19fcSEvan Bacon  log.retrySymbolicate(type, () => {
23326ad19fcSEvan Bacon    handleUpdate();
23426ad19fcSEvan Bacon  });
23526ad19fcSEvan Bacon}
23626ad19fcSEvan Bacon
23726ad19fcSEvan Baconexport function symbolicateLogLazy(type: StackType, log: LogBoxLog) {
23826ad19fcSEvan Bacon  log.symbolicate(type);
23926ad19fcSEvan Bacon}
24026ad19fcSEvan Bacon
24126ad19fcSEvan Baconexport function clear(): void {
24226ad19fcSEvan Bacon  if (logs.size > 0) {
24326ad19fcSEvan Bacon    logs = new Set();
24426ad19fcSEvan Bacon    setSelectedLog(-1);
24526ad19fcSEvan Bacon  }
24626ad19fcSEvan Bacon}
24726ad19fcSEvan Bacon
24826ad19fcSEvan Baconexport function setSelectedLog(proposedNewIndex: number): void {
24926ad19fcSEvan Bacon  const oldIndex = _selectedIndex;
25026ad19fcSEvan Bacon  let newIndex = proposedNewIndex;
25126ad19fcSEvan Bacon
25226ad19fcSEvan Bacon  const logArray = Array.from(logs);
25326ad19fcSEvan Bacon  let index = logArray.length - 1;
25426ad19fcSEvan Bacon  while (index >= 0) {
25526ad19fcSEvan Bacon    // The latest syntax error is selected and displayed before all other logs.
25626ad19fcSEvan Bacon    if (logArray[index].level === 'syntax') {
25726ad19fcSEvan Bacon      newIndex = index;
25826ad19fcSEvan Bacon      break;
25926ad19fcSEvan Bacon    }
26026ad19fcSEvan Bacon    index -= 1;
26126ad19fcSEvan Bacon  }
26226ad19fcSEvan Bacon  _selectedIndex = newIndex;
26326ad19fcSEvan Bacon  handleUpdate();
26426ad19fcSEvan Bacon  if (NativeLogBox) {
26526ad19fcSEvan Bacon    setTimeout(() => {
26626ad19fcSEvan Bacon      if (oldIndex < 0 && newIndex >= 0) {
26726ad19fcSEvan Bacon        NativeLogBox.show();
26826ad19fcSEvan Bacon      } else if (oldIndex >= 0 && newIndex < 0) {
26926ad19fcSEvan Bacon        NativeLogBox.hide();
27026ad19fcSEvan Bacon      }
27126ad19fcSEvan Bacon    }, 0);
27226ad19fcSEvan Bacon  }
27326ad19fcSEvan Bacon}
27426ad19fcSEvan Bacon
27526ad19fcSEvan Baconexport function clearWarnings(): void {
27626ad19fcSEvan Bacon  const newLogs = Array.from(logs).filter((log) => log.level !== 'warn');
27726ad19fcSEvan Bacon  if (newLogs.length !== logs.size) {
27826ad19fcSEvan Bacon    logs = new Set(newLogs);
27926ad19fcSEvan Bacon    setSelectedLog(-1);
28026ad19fcSEvan Bacon    handleUpdate();
28126ad19fcSEvan Bacon  }
28226ad19fcSEvan Bacon}
28326ad19fcSEvan Bacon
28426ad19fcSEvan Baconexport function clearErrors(): void {
28526ad19fcSEvan Bacon  const newLogs = Array.from(logs).filter((log) => log.level !== 'error' && log.level !== 'fatal');
28626ad19fcSEvan Bacon  if (newLogs.length !== logs.size) {
28726ad19fcSEvan Bacon    logs = new Set(newLogs);
28826ad19fcSEvan Bacon    setSelectedLog(-1);
28926ad19fcSEvan Bacon  }
29026ad19fcSEvan Bacon}
29126ad19fcSEvan Bacon
29226ad19fcSEvan Baconexport function dismiss(log: LogBoxLog): void {
29326ad19fcSEvan Bacon  if (logs.has(log)) {
29426ad19fcSEvan Bacon    logs.delete(log);
29526ad19fcSEvan Bacon    handleUpdate();
29626ad19fcSEvan Bacon  }
29726ad19fcSEvan Bacon}
29826ad19fcSEvan Bacon
29926ad19fcSEvan Baconexport function getIgnorePatterns(): IgnorePattern[] {
30026ad19fcSEvan Bacon  return Array.from(ignorePatterns);
30126ad19fcSEvan Bacon}
30226ad19fcSEvan Bacon
30326ad19fcSEvan Baconexport function addIgnorePatterns(patterns: IgnorePattern[]): void {
30426ad19fcSEvan Bacon  const existingSize = ignorePatterns.size;
30526ad19fcSEvan Bacon  // The same pattern may be added multiple times, but adding a new pattern
30626ad19fcSEvan Bacon  // can be expensive so let's find only the ones that are new.
30726ad19fcSEvan Bacon  patterns.forEach((pattern: IgnorePattern) => {
30826ad19fcSEvan Bacon    if (pattern instanceof RegExp) {
30926ad19fcSEvan Bacon      for (const existingPattern of ignorePatterns) {
31026ad19fcSEvan Bacon        if (
31126ad19fcSEvan Bacon          existingPattern instanceof RegExp &&
31226ad19fcSEvan Bacon          existingPattern.toString() === pattern.toString()
31326ad19fcSEvan Bacon        ) {
31426ad19fcSEvan Bacon          return;
31526ad19fcSEvan Bacon        }
31626ad19fcSEvan Bacon      }
31726ad19fcSEvan Bacon      ignorePatterns.add(pattern);
31826ad19fcSEvan Bacon    }
31926ad19fcSEvan Bacon    ignorePatterns.add(pattern);
32026ad19fcSEvan Bacon  });
32126ad19fcSEvan Bacon  if (ignorePatterns.size === existingSize) {
32226ad19fcSEvan Bacon    return;
32326ad19fcSEvan Bacon  }
32426ad19fcSEvan Bacon  // We need to recheck all of the existing logs.
32526ad19fcSEvan Bacon  // This allows adding an ignore pattern anywhere in the codebase.
32626ad19fcSEvan Bacon  // Without this, if you ignore a pattern after the a log is created,
32726ad19fcSEvan Bacon  // then we would keep showing the log.
32826ad19fcSEvan Bacon  logs = new Set(Array.from(logs).filter((log) => !isMessageIgnored(log.message.content)));
32926ad19fcSEvan Bacon  handleUpdate();
33026ad19fcSEvan Bacon}
33126ad19fcSEvan Bacon
33226ad19fcSEvan Baconexport function setDisabled(value: boolean): void {
33326ad19fcSEvan Bacon  if (value === _isDisabled) {
33426ad19fcSEvan Bacon    return;
33526ad19fcSEvan Bacon  }
33626ad19fcSEvan Bacon  _isDisabled = value;
33726ad19fcSEvan Bacon  handleUpdate();
33826ad19fcSEvan Bacon}
33926ad19fcSEvan Bacon
34026ad19fcSEvan Baconexport function isDisabled(): boolean {
34126ad19fcSEvan Bacon  return _isDisabled;
34226ad19fcSEvan Bacon}
34326ad19fcSEvan Bacon
34426ad19fcSEvan Baconexport function observe(observer: Observer): Subscription {
34526ad19fcSEvan Bacon  const subscription = { observer };
34626ad19fcSEvan Bacon  observers.add(subscription);
34726ad19fcSEvan Bacon
34826ad19fcSEvan Bacon  observer(getNextState());
34926ad19fcSEvan Bacon
35026ad19fcSEvan Bacon  return {
35126ad19fcSEvan Bacon    unsubscribe(): void {
35226ad19fcSEvan Bacon      observers.delete(subscription);
35326ad19fcSEvan Bacon    },
35426ad19fcSEvan Bacon  };
35526ad19fcSEvan Bacon}
35626ad19fcSEvan Bacon
35726ad19fcSEvan Baconexport function withSubscription(WrappedComponent: React.FC<object>): React.Component<object> {
35826ad19fcSEvan Bacon  class LogBoxStateSubscription extends React.Component<React.PropsWithChildren<Props>, State> {
35926ad19fcSEvan Bacon    static getDerivedStateFromError() {
36026ad19fcSEvan Bacon      return { hasError: true };
36126ad19fcSEvan Bacon    }
36226ad19fcSEvan Bacon
36326ad19fcSEvan Bacon    componentDidCatch(err: Error, errorInfo: { componentStack: string } & any) {
36426ad19fcSEvan Bacon      /* $FlowFixMe[class-object-subtyping] added when improving typing for
36526ad19fcSEvan Bacon       * this parameters */
36626ad19fcSEvan Bacon      reportLogBoxError(err, errorInfo.componentStack);
36726ad19fcSEvan Bacon    }
36826ad19fcSEvan Bacon
36926ad19fcSEvan Bacon    _subscription?: Subscription;
37026ad19fcSEvan Bacon
37126ad19fcSEvan Bacon    state = {
37226ad19fcSEvan Bacon      logs: new Set<LogBoxLog>(),
37326ad19fcSEvan Bacon      isDisabled: false,
37426ad19fcSEvan Bacon      hasError: false,
37526ad19fcSEvan Bacon      selectedLogIndex: -1,
37626ad19fcSEvan Bacon    };
37726ad19fcSEvan Bacon
37826ad19fcSEvan Bacon    render() {
37926ad19fcSEvan Bacon      if (this.state.hasError) {
38026ad19fcSEvan Bacon        // This happens when the component failed to render, in which case we delegate to the native redbox.
38126ad19fcSEvan Bacon        // We can't show any fallback UI here, because the error may be with <View> or <Text>.
38226ad19fcSEvan Bacon        return null;
38326ad19fcSEvan Bacon      }
38426ad19fcSEvan Bacon
38526ad19fcSEvan Bacon      return (
38626ad19fcSEvan Bacon        <LogContext.Provider
38726ad19fcSEvan Bacon          value={{
38826ad19fcSEvan Bacon            selectedLogIndex: this.state.selectedLogIndex,
38926ad19fcSEvan Bacon            isDisabled: this.state.isDisabled,
39026ad19fcSEvan Bacon            logs: Array.from(this.state.logs),
39126ad19fcSEvan Bacon          }}>
39226ad19fcSEvan Bacon          {this.props.children}
39326ad19fcSEvan Bacon          <WrappedComponent />
39426ad19fcSEvan Bacon        </LogContext.Provider>
39526ad19fcSEvan Bacon      );
39626ad19fcSEvan Bacon    }
39726ad19fcSEvan Bacon
39826ad19fcSEvan Bacon    componentDidMount(): void {
39926ad19fcSEvan Bacon      this._subscription = observe((data) => {
40026ad19fcSEvan Bacon        this.setState(data);
40126ad19fcSEvan Bacon      });
40226ad19fcSEvan Bacon    }
40326ad19fcSEvan Bacon
40426ad19fcSEvan Bacon    componentWillUnmount(): void {
40526ad19fcSEvan Bacon      if (this._subscription != null) {
40626ad19fcSEvan Bacon        this._subscription.unsubscribe();
40726ad19fcSEvan Bacon      }
40826ad19fcSEvan Bacon    }
40926ad19fcSEvan Bacon
41026ad19fcSEvan Bacon    _handleDismiss = (): void => {
41126ad19fcSEvan Bacon      // Here we handle the cases when the log is dismissed and it
41226ad19fcSEvan Bacon      // was either the last log, or when the current index
41326ad19fcSEvan Bacon      // is now outside the bounds of the log array.
41426ad19fcSEvan Bacon      const { selectedLogIndex, logs: stateLogs } = this.state;
41526ad19fcSEvan Bacon      const logsArray = Array.from(stateLogs);
41626ad19fcSEvan Bacon      if (selectedLogIndex != null) {
41726ad19fcSEvan Bacon        if (logsArray.length - 1 <= 0) {
41826ad19fcSEvan Bacon          setSelectedLog(-1);
41926ad19fcSEvan Bacon        } else if (selectedLogIndex >= logsArray.length - 1) {
42026ad19fcSEvan Bacon          setSelectedLog(selectedLogIndex - 1);
42126ad19fcSEvan Bacon        }
42226ad19fcSEvan Bacon
42326ad19fcSEvan Bacon        dismiss(logsArray[selectedLogIndex]);
42426ad19fcSEvan Bacon      }
42526ad19fcSEvan Bacon    };
42626ad19fcSEvan Bacon
42726ad19fcSEvan Bacon    _handleMinimize = (): void => {
42826ad19fcSEvan Bacon      setSelectedLog(-1);
42926ad19fcSEvan Bacon    };
43026ad19fcSEvan Bacon
43126ad19fcSEvan Bacon    _handleSetSelectedLog = (index: number): void => {
43226ad19fcSEvan Bacon      setSelectedLog(index);
43326ad19fcSEvan Bacon    };
43426ad19fcSEvan Bacon  }
43526ad19fcSEvan Bacon
43626ad19fcSEvan Bacon  // @ts-expect-error
43726ad19fcSEvan Bacon  return LogBoxStateSubscription;
43826ad19fcSEvan Bacon}
439