import type { NavigationAction , NavigationContainerRef , NavigationState , } from '@react-navigation/core' ; import deepEqual from 'deep-equal' ; import * as React from 'react' ; import parseErrorStack from './parseErrorStack' ; type StackFrame = { lineNumber : number | null ; column : number | null ; file : string | null ; methodName : string ; } ; type StackFrameResult = StackFrame & { collapse : boolean ; } ; type StackResult = { stack : StackFrameResult [ ] ; } ; type InitData = { type : 'init' ; state : NavigationState | undefined ; } ; type ActionData = { type : 'action' ; action : NavigationAction ; state : NavigationState | undefined ; stack : string | undefined ; } ; export default function useDevToolsBase ( ref , callback ) { const lastStateRef = React . useRef < NavigationState | undefined > ( ) ; const lastActionRef = React . useRef < { action : NavigationAction ; stack : string | undefined ; } | undefined > ( ) ; const callbackRef = React . useRef ( callback ) ; const lastResetRef = React . useRef < NavigationState | undefined > ( undefined ) ; React . useEffect ( ( ) => { callbackRef . current = callback ; } ) ; const symbolicate = async ( stack ) => { if ( stack == null ) { return undefined ; } const frames = parseErrorStack ( stack ) . slice ( 2 ) . filter ( ( frame ) => frame . file !== '[native code]' ) ; const urlMatch = frames [ 0 ] ?. file ?. match ( /^https?:\/\/.+(:\d+)?\// ) ; if ( ! urlMatch ) { return stack ; } try { const result = await fetch ( `${ urlMatch [ 0 ] }symbolicate` , { method : 'POST' , body : JSON . stringify ( { stack : frames } ) , } ) . then ( ( res ) => res . json ( ) ) ; return result . stack . filter ( ( it ) => ! it . collapse ) . map ( ( { methodName , file , lineNumber , column } ) => `${ methodName }@${ file }:${ lineNumber }:${ column }` ) . join ( '\n' ) ; } catch ( err ) { return stack ; } } ; const pendingPromiseRef = React . useRef < Promise < void > > ( Promise . resolve ( ) ) ; const send = React . useCallback ( ( data ) => { pendingPromiseRef . current = pendingPromiseRef . current . catch ( ( ) => { } ) . then ( async ( ) => { if ( data . stack ) { let stack ; try { stack = await symbolicate ( data . stack ) ; } catch ( err ) { } callbackRef . current ( { ... data , stack } ) ; } else { callbackRef . current ( data ) ; } } ) ; } , [ ] ) ; React . useEffect ( ( ) => { let timer ; let unsubscribeAction ; let unsubscribeState ; const initialize = async ( ) => { if ( ! ref . current ) { await new Promise < void > ( ( resolve ) => { timer = setInterval ( ( ) => { if ( ref . current ) { resolve ( ) ; clearTimeout ( timer ) ; const state = ref . current . getRootState ( ) ; lastStateRef . current = state ; callbackRef . current ( { type : 'init' , state } ) ; } } , 100 ) ; } ) ; } const navigation = ref . current ! ; unsubscribeAction = navigation . addListener ( '__unsafe_action__' , ( e ) => { const action = e . data . action ; if ( e . data . noop ) { send ( { type : 'action' , action , state : lastStateRef . current , stack : e . data . stack , } ) ; } else { lastActionRef . current = e . data ; } } ) ; unsubscribeState = navigation . addListener ( 'state' , ( e ) => { if ( lastResetRef . current && deepEqual ( lastResetRef . current , e . data . state ) ) { lastStateRef . current = undefined ; return ; } const state = navigation . getRootState ( ) ; const lastState = lastStateRef . current ; const lastChange = lastActionRef . current ; lastActionRef . current = undefined ; lastStateRef . current = state ; if ( lastChange === undefined && deepEqual ( state , lastState ) ) { return ; } send ( { type : 'action' , action : lastChange ? lastChange . action : { type : '@@UNKNOWN' } , state , stack : lastChange ?. stack , } ) ; } ) ; } ; initialize ( ) ; return ( ) => { unsubscribeAction ?. ( ) ; unsubscribeState ?. ( ) ; clearTimeout ( timer ) ; } ; } , [ ref , send ] ) ; const resetRoot = React . useCallback ( ( state ) => { if ( ref . current ) { lastResetRef . current = state ; ref . current . resetRoot ( state ) ; } } , [ ref ] ) ; return { resetRoot } ; }