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