/** * Copyright (c) 650 Industries. * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ import React, { useCallback, useEffect, useState } from 'react'; import { Keyboard, ScrollView, View, StyleSheet } from 'react-native'; import * as LogBoxData from './Data/LogBoxData'; import { LogBoxLog, StackType } from './Data/LogBoxLog'; import { useLogs, useSelectedLog } from './Data/LogContext'; import * as LogBoxStyle from './UI/LogBoxStyle'; import { LogBoxInspectorCodeFrame } from './overlay/LogBoxInspectorCodeFrame'; import { LogBoxInspectorFooter as ErrorOverlayFooter } from './overlay/LogBoxInspectorFooter'; import { LogBoxInspectorHeader as ErrorOverlayHeader } from './overlay/LogBoxInspectorHeader'; import { LogBoxInspectorMessageHeader } from './overlay/LogBoxInspectorMessageHeader'; import { LogBoxInspectorStackFrames } from './overlay/LogBoxInspectorStackFrames'; const HEADER_TITLE_MAP = { warn: 'Console Warning', error: 'Console Error', fatal: 'Uncaught Error', syntax: 'Syntax Error', static: 'Static Rendering Error (Node.js)', component: 'Render Error', }; export function LogBoxInspectorContainer() { const { selectedLogIndex, logs } = useLogs(); const log = logs[selectedLogIndex]; if (log == null) { return null; } return ; } export function LogBoxInspector({ log, selectedLogIndex, logs, }: { log: LogBoxLog; selectedLogIndex: number; logs: LogBoxLog[]; }) { const onDismiss = useCallback((): void => { // Here we handle the cases when the log is dismissed and it // was either the last log, or when the current index // is now outside the bounds of the log array. const logsArray = Array.from(logs); if (selectedLogIndex != null) { if (logsArray.length - 1 <= 0) { LogBoxData.setSelectedLog(-1); } else if (selectedLogIndex >= logsArray.length - 1) { LogBoxData.setSelectedLog(selectedLogIndex - 1); } LogBoxData.dismiss(logsArray[selectedLogIndex]); } }, [selectedLogIndex]); const onMinimize = useCallback((): void => { LogBoxData.setSelectedLog(-1); }, []); const onChangeSelectedIndex = useCallback((index: number): void => { LogBoxData.setSelectedLog(index); }, []); useEffect(() => { if (log) { LogBoxData.symbolicateLogNow('stack', log); LogBoxData.symbolicateLogNow('component', log); } }, [log]); useEffect(() => { // Optimistically symbolicate the last and next logs. if (logs.length > 1) { const selected = selectedLogIndex; const lastIndex = logs.length - 1; const prevIndex = selected - 1 < 0 ? lastIndex : selected - 1; const nextIndex = selected + 1 > lastIndex ? 0 : selected + 1; for (const type of ['component', 'stack'] as const) { LogBoxData.symbolicateLogLazy(type, logs[prevIndex]); LogBoxData.symbolicateLogLazy(type, logs[nextIndex]); } } }, [logs, selectedLogIndex]); useEffect(() => { Keyboard.dismiss(); }, []); const _handleRetry = useCallback( (type: StackType) => { LogBoxData.retrySymbolicateLogNow(type, log); }, [log] ); return ( ); } export function ErrorOverlayBody({ onRetry }: { onRetry: (type: StackType) => void }) { const log = useSelectedLog(); return ; } export function ErrorOverlayBodyContents({ log, onRetry, }: { log: LogBoxLog; onRetry: (type: StackType) => void; }) { const [collapsed, setCollapsed] = useState(true); useEffect(() => { setCollapsed(true); }, [log]); const headerTitle = HEADER_TITLE_MAP[log.isComponentError ? 'component' : log.level] ?? log.type; const header = ( setCollapsed(!collapsed)} message={log.message} level={log.level} title={headerTitle} /> ); // Hide useless React stack. const needsStack = !log.message.content.match( /(Expected server HTML to contain a matching|Text content did not match\.)/ ); return ( <> {collapsed && header} {!collapsed && header} {needsStack && ( )} {!!log?.componentStack?.length && ( )} ); } const styles = StyleSheet.create({ scrollBody: { backgroundColor: LogBoxStyle.getBackgroundColor(1), flex: 1, }, container: { top: 0, left: 0, bottom: 0, right: 0, zIndex: 999, flex: 1, // @ts-expect-error: fixed is not in the RN types but it works on web position: 'fixed', }, }); export default LogBoxData.withSubscription(LogBoxInspectorContainer);