execbro 2.3.0 → 2.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/__tests__/helpers/fake-cdp-server.d.ts +0 -12
- package/build/__tests__/helpers/fake-cdp-server.d.ts.map +1 -1
- package/build/__tests__/helpers/fake-cdp-server.js +0 -22
- package/build/__tests__/helpers/fake-cdp-server.js.map +1 -1
- package/build/__tests__/helpers/tap-test-helpers.d.ts +0 -23
- package/build/__tests__/helpers/tap-test-helpers.d.ts.map +1 -1
- package/build/__tests__/helpers/tap-test-helpers.js +2 -29
- package/build/__tests__/helpers/tap-test-helpers.js.map +1 -1
- package/build/__tests__/integration/auto-reconnect.test.js +4 -14
- package/build/__tests__/integration/auto-reconnect.test.js.map +1 -1
- package/build/__tests__/integration/connection-health.test.js +1 -10
- package/build/__tests__/integration/connection-health.test.js.map +1 -1
- package/build/__tests__/integration/execute-in-app.test.js +1 -11
- package/build/__tests__/integration/execute-in-app.test.js.map +1 -1
- package/build/__tests__/integration/native-logs.test.js +0 -5
- package/build/__tests__/integration/native-logs.test.js.map +1 -1
- package/build/__tests__/integration/per-call-timeout.test.js +1 -5
- package/build/__tests__/integration/per-call-timeout.test.js.map +1 -1
- package/build/__tests__/integration/socket-close-midflight.test.js +2 -12
- package/build/__tests__/integration/socket-close-midflight.test.js.map +1 -1
- package/build/__tests__/integration/stale-target-probe.test.js +2 -17
- package/build/__tests__/integration/stale-target-probe.test.js.map +1 -1
- package/build/__tests__/integration/tap-strategies.test.js +2 -36
- package/build/__tests__/integration/tap-strategies.test.js.map +1 -1
- package/build/__tests__/integration/text-input.test.js +0 -39
- package/build/__tests__/integration/text-input.test.js.map +1 -1
- package/build/__tests__/integration/tools.test.js +1 -7
- package/build/__tests__/integration/tools.test.js.map +1 -1
- package/build/__tests__/setup.js +0 -2
- package/build/__tests__/setup.js.map +1 -1
- package/build/__tests__/unit/androidEmulatorAvds.test.js +0 -10
- package/build/__tests__/unit/androidEmulatorAvds.test.js.map +1 -1
- package/build/__tests__/unit/appContext.test.js +0 -2
- package/build/__tests__/unit/appContext.test.js.map +1 -1
- package/build/__tests__/unit/appRequest.test.js +0 -3
- package/build/__tests__/unit/appRequest.test.js.map +1 -1
- package/build/__tests__/unit/buildToken.test.js +0 -6
- package/build/__tests__/unit/buildToken.test.js.map +1 -1
- package/build/__tests__/unit/burst.test.js +0 -1
- package/build/__tests__/unit/burst.test.js.map +1 -1
- package/build/__tests__/unit/clearLogsNative.test.js +0 -8
- package/build/__tests__/unit/clearLogsNative.test.js.map +1 -1
- package/build/__tests__/unit/componentFormatNaming.test.js +0 -13
- package/build/__tests__/unit/componentFormatNaming.test.js.map +1 -1
- package/build/__tests__/unit/componentSource.test.js +0 -4
- package/build/__tests__/unit/componentSource.test.js.map +1 -1
- package/build/__tests__/unit/componentSourceResolve.test.js +0 -1
- package/build/__tests__/unit/componentSourceResolve.test.js.map +1 -1
- package/build/__tests__/unit/contextWiring.test.js +0 -3
- package/build/__tests__/unit/contextWiring.test.js.map +1 -1
- package/build/__tests__/unit/coordinateMiss.test.js +0 -16
- package/build/__tests__/unit/coordinateMiss.test.js.map +1 -1
- package/build/__tests__/unit/deviceDiscovery.test.js +0 -4
- package/build/__tests__/unit/deviceDiscovery.test.js.map +1 -1
- package/build/__tests__/unit/deviceResolution.test.js +0 -2
- package/build/__tests__/unit/deviceResolution.test.js.map +1 -1
- package/build/__tests__/unit/deviceResolver.test.js +0 -1
- package/build/__tests__/unit/deviceResolver.test.js.map +1 -1
- package/build/__tests__/unit/deviceResolverStaleRetry.test.js +0 -8
- package/build/__tests__/unit/deviceResolverStaleRetry.test.js.map +1 -1
- package/build/__tests__/unit/epochRender.test.js +0 -3
- package/build/__tests__/unit/epochRender.test.js.map +1 -1
- package/build/__tests__/unit/errorCategorization.test.js +0 -18
- package/build/__tests__/unit/errorCategorization.test.js.map +1 -1
- package/build/__tests__/unit/exec.test.js +0 -1
- package/build/__tests__/unit/exec.test.js.map +1 -1
- package/build/__tests__/unit/executor.test.js +0 -15
- package/build/__tests__/unit/executor.test.js.map +1 -1
- package/build/__tests__/unit/findElementTreeReuse.test.js +0 -12
- package/build/__tests__/unit/findElementTreeReuse.test.js.map +1 -1
- package/build/__tests__/unit/fingerprint.test.js +0 -4
- package/build/__tests__/unit/fingerprint.test.js.map +1 -1
- package/build/__tests__/unit/focusedInputTools.test.js +0 -4
- package/build/__tests__/unit/focusedInputTools.test.js.map +1 -1
- package/build/__tests__/unit/getLogsEmptyContract.test.js +0 -30
- package/build/__tests__/unit/getLogsEmptyContract.test.js.map +1 -1
- package/build/__tests__/unit/getLogsNativeSource.test.js +0 -22
- package/build/__tests__/unit/getLogsNativeSource.test.js.map +1 -1
- package/build/__tests__/unit/graphqlOperation.test.d.ts +2 -0
- package/build/__tests__/unit/graphqlOperation.test.d.ts.map +1 -0
- package/build/__tests__/unit/graphqlOperation.test.js +107 -0
- package/build/__tests__/unit/graphqlOperation.test.js.map +1 -0
- package/build/__tests__/unit/inputTarget.test.js +0 -16
- package/build/__tests__/unit/inputTarget.test.js.map +1 -1
- package/build/__tests__/unit/inputTextTool.test.js +0 -3
- package/build/__tests__/unit/inputTextTool.test.js.map +1 -1
- package/build/__tests__/unit/jsLogEvents.test.js +0 -3
- package/build/__tests__/unit/jsLogEvents.test.js.map +1 -1
- package/build/__tests__/unit/keyboardMetrics.test.js +0 -2
- package/build/__tests__/unit/keyboardMetrics.test.js.map +1 -1
- package/build/__tests__/unit/keyboardRaise.test.js +0 -2
- package/build/__tests__/unit/keyboardRaise.test.js.map +1 -1
- package/build/__tests__/unit/licenseGrace.test.js +1 -4
- package/build/__tests__/unit/licenseGrace.test.js.map +1 -1
- package/build/__tests__/unit/logEventFormat.test.js +0 -27
- package/build/__tests__/unit/logEventFormat.test.js.map +1 -1
- package/build/__tests__/unit/logEvents.test.js +1 -5
- package/build/__tests__/unit/logEvents.test.js.map +1 -1
- package/build/__tests__/unit/logGrouping.test.js +0 -2
- package/build/__tests__/unit/logGrouping.test.js.map +1 -1
- package/build/__tests__/unit/logOwnership.test.js +0 -11
- package/build/__tests__/unit/logOwnership.test.js.map +1 -1
- package/build/__tests__/unit/logRelevance.test.js +0 -6
- package/build/__tests__/unit/logRelevance.test.js.map +1 -1
- package/build/__tests__/unit/logSourceAndroid.test.js +0 -4
- package/build/__tests__/unit/logSourceAndroid.test.js.map +1 -1
- package/build/__tests__/unit/logSourceIos.test.js +0 -5
- package/build/__tests__/unit/logSourceIos.test.js.map +1 -1
- package/build/__tests__/unit/logs.test.js +0 -1
- package/build/__tests__/unit/logs.test.js.map +1 -1
- package/build/__tests__/unit/measureComponentTools.test.js +0 -1
- package/build/__tests__/unit/measureComponentTools.test.js.map +1 -1
- package/build/__tests__/unit/metro.test.js +0 -2
- package/build/__tests__/unit/metro.test.js.map +1 -1
- package/build/__tests__/unit/nativeInputValue.test.js +0 -4
- package/build/__tests__/unit/nativeInputValue.test.js.map +1 -1
- package/build/__tests__/unit/nativeLogs.test.js +1 -23
- package/build/__tests__/unit/nativeLogs.test.js.map +1 -1
- package/build/__tests__/unit/navigateVerify.test.js +0 -6
- package/build/__tests__/unit/navigateVerify.test.js.map +1 -1
- package/build/__tests__/unit/navigation.test.js +0 -5
- package/build/__tests__/unit/navigation.test.js.map +1 -1
- package/build/__tests__/unit/networkInterceptor.test.js +0 -2
- package/build/__tests__/unit/networkInterceptor.test.js.map +1 -1
- package/build/__tests__/unit/overlayGuard.test.d.ts +2 -0
- package/build/__tests__/unit/overlayGuard.test.d.ts.map +1 -0
- package/build/__tests__/unit/overlayGuard.test.js +123 -0
- package/build/__tests__/unit/overlayGuard.test.js.map +1 -0
- package/build/__tests__/unit/pendingExecutions.test.js +0 -6
- package/build/__tests__/unit/pendingExecutions.test.js.map +1 -1
- package/build/__tests__/unit/probe.test.js +0 -1
- package/build/__tests__/unit/probe.test.js.map +1 -1
- package/build/__tests__/unit/projectMemory.test.js +6 -7
- package/build/__tests__/unit/projectMemory.test.js.map +1 -1
- package/build/__tests__/unit/projectStore.test.js +2 -2
- package/build/__tests__/unit/projectStore.test.js.map +1 -1
- package/build/__tests__/unit/reloadAppAutoConnect.test.js +0 -9
- package/build/__tests__/unit/reloadAppAutoConnect.test.js.map +1 -1
- package/build/__tests__/unit/rnGlobalsBootstrap.test.js +3 -3
- package/build/__tests__/unit/rnGlobalsBootstrap.test.js.map +1 -1
- package/build/__tests__/unit/routeHistory.test.d.ts +2 -0
- package/build/__tests__/unit/routeHistory.test.d.ts.map +1 -0
- package/build/__tests__/unit/routeHistory.test.js +136 -0
- package/build/__tests__/unit/routeHistory.test.js.map +1 -0
- package/build/__tests__/unit/screenSpace.test.js +30 -13
- package/build/__tests__/unit/screenSpace.test.js.map +1 -1
- package/build/__tests__/unit/screenState.test.js +3 -6
- package/build/__tests__/unit/screenState.test.js.map +1 -1
- package/build/__tests__/unit/screenStateInputValue.test.js +0 -7
- package/build/__tests__/unit/screenStateInputValue.test.js.map +1 -1
- package/build/__tests__/unit/screenStateKeyboard.test.js +0 -1
- package/build/__tests__/unit/screenStateKeyboard.test.js.map +1 -1
- package/build/__tests__/unit/screenshot-diff.test.js +0 -6
- package/build/__tests__/unit/screenshot-diff.test.js.map +1 -1
- package/build/__tests__/unit/sdkMirrorPoller.test.js +0 -8
- package/build/__tests__/unit/sdkMirrorPoller.test.js.map +1 -1
- package/build/__tests__/unit/settleAndDiff.test.js +0 -11
- package/build/__tests__/unit/settleAndDiff.test.js.map +1 -1
- package/build/__tests__/unit/signedVerdict.test.js +0 -5
- package/build/__tests__/unit/signedVerdict.test.js.map +1 -1
- package/build/__tests__/unit/swipeDirection.test.js +6 -9
- package/build/__tests__/unit/swipeDirection.test.js.map +1 -1
- package/build/__tests__/unit/symbolicate.test.js +0 -1
- package/build/__tests__/unit/symbolicate.test.js.map +1 -1
- package/build/__tests__/unit/tap.test.js +2 -40
- package/build/__tests__/unit/tap.test.js.map +1 -1
- package/build/__tests__/unit/textEntry.test.js +0 -27
- package/build/__tests__/unit/textEntry.test.js.map +1 -1
- package/build/__tests__/unit/toolDescriptions.test.js +0 -40
- package/build/__tests__/unit/toolDescriptions.test.js.map +1 -1
- package/build/__tests__/unit/toolsJson.test.js +0 -10
- package/build/__tests__/unit/toolsJson.test.js.map +1 -1
- package/build/__tests__/unit/transportClassify.test.js +0 -20
- package/build/__tests__/unit/transportClassify.test.js.map +1 -1
- package/build/__tests__/unit/truncate.test.js +0 -5
- package/build/__tests__/unit/truncate.test.js.map +1 -1
- package/build/__tests__/unit/usageCache.test.js +0 -3
- package/build/__tests__/unit/usageCache.test.js.map +1 -1
- package/build/__tests__/unit/usageGate.test.js +1 -1
- package/build/__tests__/unit/usageGate.test.js.map +1 -1
- package/build/__tests__/unit/visibility.test.js +0 -8
- package/build/__tests__/unit/visibility.test.js.map +1 -1
- package/build/__tests__/unit/waitForMeasurements.test.js +2 -7
- package/build/__tests__/unit/waitForMeasurements.test.js.map +1 -1
- package/build/bin.js +0 -18
- package/build/bin.js.map +1 -1
- package/build/core/android.d.ts +0 -120
- package/build/core/android.d.ts.map +1 -1
- package/build/core/android.js +4 -259
- package/build/core/android.js.map +1 -1
- package/build/core/appContext.d.ts +0 -50
- package/build/core/appContext.d.ts.map +1 -1
- package/build/core/appContext.js +0 -50
- package/build/core/appContext.js.map +1 -1
- package/build/core/appDetection.d.ts +0 -16
- package/build/core/appDetection.d.ts.map +1 -1
- package/build/core/appDetection.js +0 -36
- package/build/core/appDetection.js.map +1 -1
- package/build/core/appRequest.d.ts +0 -12
- package/build/core/appRequest.d.ts.map +1 -1
- package/build/core/appRequest.js +0 -15
- package/build/core/appRequest.js.map +1 -1
- package/build/core/bufferConfig.d.ts +0 -2
- package/build/core/bufferConfig.d.ts.map +1 -1
- package/build/core/bufferConfig.js +0 -2
- package/build/core/bufferConfig.js.map +1 -1
- package/build/core/bundle.js +0 -40
- package/build/core/bundle.js.map +1 -1
- package/build/core/componentSearch.d.ts +0 -19
- package/build/core/componentSearch.d.ts.map +1 -1
- package/build/core/componentSearch.js +1 -22
- package/build/core/componentSearch.js.map +1 -1
- package/build/core/componentSource.d.ts +0 -21
- package/build/core/componentSource.d.ts.map +1 -1
- package/build/core/componentSource.js +0 -27
- package/build/core/componentSource.js.map +1 -1
- package/build/core/componentTree.js +0 -12
- package/build/core/componentTree.js.map +1 -1
- package/build/core/config.d.ts +0 -8
- package/build/core/config.d.ts.map +1 -1
- package/build/core/config.js +0 -10
- package/build/core/config.js.map +1 -1
- package/build/core/connection.d.ts +0 -76
- package/build/core/connection.d.ts.map +1 -1
- package/build/core/connection.js +15 -340
- package/build/core/connection.js.map +1 -1
- package/build/core/connectionState.d.ts +0 -78
- package/build/core/connectionState.d.ts.map +1 -1
- package/build/core/connectionState.js +4 -92
- package/build/core/connectionState.js.map +1 -1
- package/build/core/debugGlobals.js +0 -68
- package/build/core/debugGlobals.js.map +1 -1
- package/build/core/deviceDiscovery.d.ts +0 -5
- package/build/core/deviceDiscovery.d.ts.map +1 -1
- package/build/core/deviceDiscovery.js +0 -12
- package/build/core/deviceDiscovery.js.map +1 -1
- package/build/core/deviceResolver.d.ts +0 -10
- package/build/core/deviceResolver.d.ts.map +1 -1
- package/build/core/deviceResolver.js +0 -54
- package/build/core/deviceResolver.js.map +1 -1
- package/build/core/epochRender.d.ts +0 -12
- package/build/core/epochRender.d.ts.map +1 -1
- package/build/core/epochRender.js +0 -12
- package/build/core/epochRender.js.map +1 -1
- package/build/core/errorScreenParser.d.ts +0 -21
- package/build/core/errorScreenParser.d.ts.map +1 -1
- package/build/core/errorScreenParser.js +0 -50
- package/build/core/errorScreenParser.js.map +1 -1
- package/build/core/errors.d.ts +0 -15
- package/build/core/errors.d.ts.map +1 -1
- package/build/core/errors.js +0 -15
- package/build/core/errors.js.map +1 -1
- package/build/core/escapeNonAscii.d.ts +0 -9
- package/build/core/escapeNonAscii.d.ts.map +1 -1
- package/build/core/escapeNonAscii.js +1 -43
- package/build/core/escapeNonAscii.js.map +1 -1
- package/build/core/exec.d.ts +0 -12
- package/build/core/exec.d.ts.map +1 -1
- package/build/core/exec.js +2 -14
- package/build/core/exec.js.map +1 -1
- package/build/core/executor.js +0 -3
- package/build/core/executor.js.map +1 -1
- package/build/core/fastRefreshRecorder.js +0 -4
- package/build/core/fastRefreshRecorder.js.map +1 -1
- package/build/core/fastRefreshTools.js +0 -3
- package/build/core/fastRefreshTools.js.map +1 -1
- package/build/core/feedback.js +0 -4
- package/build/core/feedback.js.map +1 -1
- package/build/core/graphqlOperation.d.ts +13 -0
- package/build/core/graphqlOperation.d.ts.map +1 -0
- package/build/core/graphqlOperation.js +80 -0
- package/build/core/graphqlOperation.js.map +1 -0
- package/build/core/guides.d.ts +0 -10
- package/build/core/guides.d.ts.map +1 -1
- package/build/core/guides.js +0 -10
- package/build/core/guides.js.map +1 -1
- package/build/core/iconSemantics.d.ts +0 -15
- package/build/core/iconSemantics.d.ts.map +1 -1
- package/build/core/iconSemantics.js +0 -31
- package/build/core/iconSemantics.js.map +1 -1
- package/build/core/index.js +4 -47
- package/build/core/index.js.map +1 -1
- package/build/core/injected/visibility.d.ts +0 -30
- package/build/core/injected/visibility.d.ts.map +1 -1
- package/build/core/injected/visibility.js +0 -33
- package/build/core/injected/visibility.js.map +1 -1
- package/build/core/injectedFilters.d.ts +0 -28
- package/build/core/injectedFilters.d.ts.map +1 -1
- package/build/core/injectedFilters.js +0 -28
- package/build/core/injectedFilters.js.map +1 -1
- package/build/core/inputTarget.d.ts +1 -34
- package/build/core/inputTarget.d.ts.map +1 -1
- package/build/core/inputTarget.js +0 -26
- package/build/core/inputTarget.js.map +1 -1
- package/build/core/inputTargetTools.d.ts +0 -7
- package/build/core/inputTargetTools.d.ts.map +1 -1
- package/build/core/inputTargetTools.js +0 -7
- package/build/core/inputTargetTools.js.map +1 -1
- package/build/core/inspector.d.ts +0 -33
- package/build/core/inspector.d.ts.map +1 -1
- package/build/core/inspector.js +27 -40
- package/build/core/inspector.js.map +1 -1
- package/build/core/ios.d.ts +11 -122
- package/build/core/ios.d.ts.map +1 -1
- package/build/core/ios.js +32 -299
- package/build/core/ios.js.map +1 -1
- package/build/core/jsExecute.d.ts +0 -100
- package/build/core/jsExecute.d.ts.map +1 -1
- package/build/core/jsExecute.js +0 -261
- package/build/core/jsExecute.js.map +1 -1
- package/build/core/jsLogEvents.d.ts +0 -8
- package/build/core/jsLogEvents.d.ts.map +1 -1
- package/build/core/jsLogEvents.js +0 -8
- package/build/core/jsLogEvents.js.map +1 -1
- package/build/core/keyboardMetrics.js +0 -8
- package/build/core/keyboardMetrics.js.map +1 -1
- package/build/core/keyboardRaise.d.ts +0 -8
- package/build/core/keyboardRaise.d.ts.map +1 -1
- package/build/core/keyboardRaise.js +0 -23
- package/build/core/keyboardRaise.js.map +1 -1
- package/build/core/license.d.ts +0 -4
- package/build/core/license.d.ts.map +1 -1
- package/build/core/license.js +2 -55
- package/build/core/license.js.map +1 -1
- package/build/core/logDiagnosis.d.ts +0 -34
- package/build/core/logDiagnosis.d.ts.map +1 -1
- package/build/core/logDiagnosis.js +0 -15
- package/build/core/logDiagnosis.js.map +1 -1
- package/build/core/logEventFormat.d.ts +0 -1
- package/build/core/logEventFormat.d.ts.map +1 -1
- package/build/core/logEventFormat.js +0 -30
- package/build/core/logEventFormat.js.map +1 -1
- package/build/core/logEvents.d.ts +0 -54
- package/build/core/logEvents.d.ts.map +1 -1
- package/build/core/logEvents.js +0 -37
- package/build/core/logEvents.js.map +1 -1
- package/build/core/logGrouping.d.ts +0 -9
- package/build/core/logGrouping.d.ts.map +1 -1
- package/build/core/logGrouping.js +0 -17
- package/build/core/logGrouping.js.map +1 -1
- package/build/core/logOwnership.d.ts +0 -17
- package/build/core/logOwnership.d.ts.map +1 -1
- package/build/core/logOwnership.js +0 -34
- package/build/core/logOwnership.js.map +1 -1
- package/build/core/logRelevance.d.ts +0 -8
- package/build/core/logRelevance.d.ts.map +1 -1
- package/build/core/logRelevance.js +0 -15
- package/build/core/logRelevance.js.map +1 -1
- package/build/core/logSourceAndroid.d.ts +0 -8
- package/build/core/logSourceAndroid.d.ts.map +1 -1
- package/build/core/logSourceAndroid.js +0 -11
- package/build/core/logSourceAndroid.js.map +1 -1
- package/build/core/logSourceIos.d.ts +0 -8
- package/build/core/logSourceIos.d.ts.map +1 -1
- package/build/core/logSourceIos.js +1 -24
- package/build/core/logSourceIos.js.map +1 -1
- package/build/core/logbox.d.ts +0 -30
- package/build/core/logbox.d.ts.map +1 -1
- package/build/core/logbox.js +0 -43
- package/build/core/logbox.js.map +1 -1
- package/build/core/logs.d.ts +0 -6
- package/build/core/logs.d.ts.map +1 -1
- package/build/core/logs.js +0 -19
- package/build/core/logs.js.map +1 -1
- package/build/core/metro.d.ts +0 -14
- package/build/core/metro.d.ts.map +1 -1
- package/build/core/metro.js +1 -32
- package/build/core/metro.js.map +1 -1
- package/build/core/moduleRegistry.d.ts +0 -17
- package/build/core/moduleRegistry.d.ts.map +1 -1
- package/build/core/moduleRegistry.js +0 -17
- package/build/core/moduleRegistry.js.map +1 -1
- package/build/core/nativeInputValue.d.ts +0 -29
- package/build/core/nativeInputValue.d.ts.map +1 -1
- package/build/core/nativeInputValue.js +0 -18
- package/build/core/nativeInputValue.js.map +1 -1
- package/build/core/nativeLogs.d.ts +0 -47
- package/build/core/nativeLogs.d.ts.map +1 -1
- package/build/core/nativeLogs.js +0 -82
- package/build/core/nativeLogs.js.map +1 -1
- package/build/core/nativeOnlyHints.js +0 -4
- package/build/core/nativeOnlyHints.js.map +1 -1
- package/build/core/navigate.d.ts +0 -15
- package/build/core/navigate.d.ts.map +1 -1
- package/build/core/navigate.js +0 -22
- package/build/core/navigate.js.map +1 -1
- package/build/core/navigation.d.ts +0 -60
- package/build/core/navigation.d.ts.map +1 -1
- package/build/core/navigation.js +0 -60
- package/build/core/navigation.js.map +1 -1
- package/build/core/network.d.ts +0 -5
- package/build/core/network.d.ts.map +1 -1
- package/build/core/network.js +10 -31
- package/build/core/network.js.map +1 -1
- package/build/core/networkInterceptor.d.ts +0 -19
- package/build/core/networkInterceptor.d.ts.map +1 -1
- package/build/core/networkInterceptor.js +3 -27
- package/build/core/networkInterceptor.js.map +1 -1
- package/build/core/ocr.d.ts +0 -31
- package/build/core/ocr.d.ts.map +1 -1
- package/build/core/ocr.js +1 -71
- package/build/core/ocr.js.map +1 -1
- package/build/core/paths.js +0 -1
- package/build/core/paths.js.map +1 -1
- package/build/core/posthog.js +0 -7
- package/build/core/posthog.js.map +1 -1
- package/build/core/pressables.d.ts +0 -25
- package/build/core/pressables.d.ts.map +1 -1
- package/build/core/pressables.js +0 -37
- package/build/core/pressables.js.map +1 -1
- package/build/core/probe.d.ts +0 -9
- package/build/core/probe.d.ts.map +1 -1
- package/build/core/probe.js +0 -9
- package/build/core/probe.js.map +1 -1
- package/build/core/projectMemory.d.ts +0 -2
- package/build/core/projectMemory.d.ts.map +1 -1
- package/build/core/projectMemory.js +2 -7
- package/build/core/projectMemory.js.map +1 -1
- package/build/core/projectStore.d.ts +0 -18
- package/build/core/projectStore.d.ts.map +1 -1
- package/build/core/projectStore.js +0 -19
- package/build/core/projectStore.js.map +1 -1
- package/build/core/promiseHandles.d.ts +0 -13
- package/build/core/promiseHandles.d.ts.map +1 -1
- package/build/core/promiseHandles.js +0 -25
- package/build/core/promiseHandles.js.map +1 -1
- package/build/core/register.js +0 -44
- package/build/core/register.js.map +1 -1
- package/build/core/rnGlobalsBootstrap.d.ts +0 -13
- package/build/core/rnGlobalsBootstrap.d.ts.map +1 -1
- package/build/core/rnGlobalsBootstrap.js +0 -22
- package/build/core/rnGlobalsBootstrap.js.map +1 -1
- package/build/core/routeHistory.d.ts +24 -0
- package/build/core/routeHistory.d.ts.map +1 -0
- package/build/core/routeHistory.js +186 -0
- package/build/core/routeHistory.js.map +1 -0
- package/build/core/screenLayout.d.ts +0 -11
- package/build/core/screenLayout.d.ts.map +1 -1
- package/build/core/screenLayout.js +24 -61
- package/build/core/screenLayout.js.map +1 -1
- package/build/core/screenSpace.d.ts +32 -44
- package/build/core/screenSpace.d.ts.map +1 -1
- package/build/core/screenSpace.js +80 -46
- package/build/core/screenSpace.js.map +1 -1
- package/build/core/screenSpaceDevice.d.ts +0 -8
- package/build/core/screenSpaceDevice.d.ts.map +1 -1
- package/build/core/screenSpaceDevice.js +31 -14
- package/build/core/screenSpaceDevice.js.map +1 -1
- package/build/core/screenState.d.ts +2 -74
- package/build/core/screenState.d.ts.map +1 -1
- package/build/core/screenState.js +204 -145
- package/build/core/screenState.js.map +1 -1
- package/build/core/sdkBridge.js +0 -8
- package/build/core/sdkBridge.js.map +1 -1
- package/build/core/sdkMirrorPoller.d.ts +0 -18
- package/build/core/sdkMirrorPoller.d.ts.map +1 -1
- package/build/core/sdkMirrorPoller.js +0 -47
- package/build/core/sdkMirrorPoller.js.map +1 -1
- package/build/core/selectionBuffer.d.ts +0 -6
- package/build/core/selectionBuffer.d.ts.map +1 -1
- package/build/core/selectionBuffer.js +0 -5
- package/build/core/selectionBuffer.js.map +1 -1
- package/build/core/signedVerdict.js +0 -6
- package/build/core/signedVerdict.js.map +1 -1
- package/build/core/state.d.ts +0 -31
- package/build/core/state.d.ts.map +1 -1
- package/build/core/state.js +0 -57
- package/build/core/state.js.map +1 -1
- package/build/core/symbolicate.js +0 -11
- package/build/core/symbolicate.js.map +1 -1
- package/build/core/telemetry.d.ts +0 -47
- package/build/core/telemetry.d.ts.map +1 -1
- package/build/core/telemetry.js +1 -173
- package/build/core/telemetry.js.map +1 -1
- package/build/core/textEntry.d.ts +0 -34
- package/build/core/textEntry.d.ts.map +1 -1
- package/build/core/textEntry.js +0 -73
- package/build/core/textEntry.js.map +1 -1
- package/build/core/toolHelpers.d.ts +0 -12
- package/build/core/toolHelpers.d.ts.map +1 -1
- package/build/core/toolHelpers.js +2 -47
- package/build/core/toolHelpers.js.map +1 -1
- package/build/core/truncate.d.ts +0 -17
- package/build/core/truncate.d.ts.map +1 -1
- package/build/core/truncate.js +0 -32
- package/build/core/truncate.js.map +1 -1
- package/build/core/types.d.ts +0 -6
- package/build/core/types.d.ts.map +1 -1
- package/build/core/usageCache.js +0 -12
- package/build/core/usageCache.js.map +1 -1
- package/build/index.js +0 -18
- package/build/index.js.map +1 -1
- package/build/pro/coordinateMiss.d.ts +0 -30
- package/build/pro/coordinateMiss.d.ts.map +1 -1
- package/build/pro/coordinateMiss.js +0 -17
- package/build/pro/coordinateMiss.js.map +1 -1
- package/build/pro/overlayGuard.d.ts +27 -0
- package/build/pro/overlayGuard.d.ts.map +1 -0
- package/build/pro/overlayGuard.js +103 -0
- package/build/pro/overlayGuard.js.map +1 -0
- package/build/pro/screenshot-diff.js +4 -6
- package/build/pro/screenshot-diff.js.map +1 -1
- package/build/pro/tap.d.ts +0 -55
- package/build/pro/tap.d.ts.map +1 -1
- package/build/pro/tap.js +72 -316
- package/build/pro/tap.js.map +1 -1
- package/build/pro/usageGate.js +0 -9
- package/build/pro/usageGate.js.map +1 -1
- package/build/pro/usageNotifications.js +1 -13
- package/build/pro/usageNotifications.js.map +1 -1
- package/build/pro/verifyAction.d.ts +0 -13
- package/build/pro/verifyAction.d.ts.map +1 -1
- package/build/pro/verifyAction.js +0 -45
- package/build/pro/verifyAction.js.map +1 -1
- package/build/tools/_deviceArg.d.ts +0 -35
- package/build/tools/_deviceArg.d.ts.map +1 -1
- package/build/tools/_deviceArg.js +0 -35
- package/build/tools/_deviceArg.js.map +1 -1
- package/build/tools/accountTools.js +2 -2
- package/build/tools/accountTools.js.map +1 -1
- package/build/tools/bundleTools.js +0 -16
- package/build/tools/bundleTools.js.map +1 -1
- package/build/tools/componentTools.d.ts.map +1 -1
- package/build/tools/componentTools.js +47 -53
- package/build/tools/componentTools.js.map +1 -1
- package/build/tools/connectionTools.js +1 -59
- package/build/tools/connectionTools.js.map +1 -1
- package/build/tools/deviceTools.js +0 -11
- package/build/tools/deviceTools.js.map +1 -1
- package/build/tools/executionTools.js +1 -18
- package/build/tools/executionTools.js.map +1 -1
- package/build/tools/interactionTools.d.ts +0 -10
- package/build/tools/interactionTools.d.ts.map +1 -1
- package/build/tools/interactionTools.js +0 -57
- package/build/tools/interactionTools.js.map +1 -1
- package/build/tools/logTools.d.ts +0 -13
- package/build/tools/logTools.d.ts.map +1 -1
- package/build/tools/logTools.js +2 -126
- package/build/tools/logTools.js.map +1 -1
- package/build/tools/metaTools.js +0 -10
- package/build/tools/metaTools.js.map +1 -1
- package/build/tools/networkTools.js +4 -43
- package/build/tools/networkTools.js.map +1 -1
- package/build/tools/requestTools.js +0 -5
- package/build/tools/requestTools.js.map +1 -1
- package/build/tools/screenshotTools.js +5 -111
- package/build/tools/screenshotTools.js.map +1 -1
- package/package.json +2 -2
- package/tools.json +1 -0
package/build/core/connection.js
CHANGED
|
@@ -11,26 +11,13 @@ import { scheduleAppDetection } from "./appDetection.js";
|
|
|
11
11
|
import { markConnectionEstablished } from "./jsExecute.js";
|
|
12
12
|
import { startSdkMirrorPoller, stopSdkMirrorPoller } from "./sdkMirrorPoller.js";
|
|
13
13
|
import { DEFAULT_RECONNECTION_CONFIG, MIN_STABLE_CONNECTION_MS, initConnectionState, updateConnectionState, getConnectionState, recordConnectionGap, closeConnectionGap, saveConnectionMetadata, clearConnectionMetadata, getConnectionMetadata, saveReconnectionTimer, cancelReconnectionTimer, calculateBackoffDelay, initContextHealth, markContextHealthy, markContextStale, getContextHealth, updateContextHealth, formatDuration, recordConnectionEvent, } from "./connectionState.js";
|
|
14
|
-
// Connection locks to prevent concurrent connection attempts to the same device
|
|
15
14
|
const connectionLocks = new Set();
|
|
16
|
-
// Last CDP target id seen per device name, used to detect app relaunches.
|
|
17
15
|
const lastTargetIdByDevice = new Map();
|
|
18
|
-
// Track Network.enable message IDs to detect CDP network support
|
|
19
16
|
const pendingNetworkEnableIds = new Set();
|
|
20
|
-
|
|
21
|
-
// app.sdkPresent. The probe runs on connect and then periodically because
|
|
22
|
-
// the SDK's init() may execute after we attach.
|
|
23
|
-
const pendingSdkProbeIds = new Map(); // msgId -> appKey
|
|
17
|
+
const pendingSdkProbeIds = new Map();
|
|
24
18
|
const SDK_PROBE_INITIAL_DELAY_MS = 200;
|
|
25
19
|
const SDK_PROBE_INTERVAL_MS = 3000;
|
|
26
|
-
// Consecutive SDK-absent probes required to flip sdkPresent true→false.
|
|
27
|
-
// Hysteresis against the post-reload window where the JS context exists but
|
|
28
|
-
// the SDK's init() hasn't re-run yet (see ConnectedApp.sdkMissCount).
|
|
29
20
|
const SDK_ABSENCE_CONFIRM_COUNT = 2;
|
|
30
|
-
// Fast re-probe schedule after a context is (re)created, so the SDK is
|
|
31
|
-
// re-detected within a few hundred ms instead of waiting up to a full
|
|
32
|
-
// SDK_PROBE_INTERVAL_MS tick. Covers reloads not driven by reload_app
|
|
33
|
-
// (shake-to-reload, Metro "r").
|
|
34
21
|
const SDK_REPROBE_DELAYS_MS = [200, 500, 1000, 2000];
|
|
35
22
|
function sendSdkProbe(ws, appKey) {
|
|
36
23
|
if (ws.readyState !== WebSocket.OPEN)
|
|
@@ -65,16 +52,11 @@ function startSdkProbeLoop(ws, appKey) {
|
|
|
65
52
|
if (app) {
|
|
66
53
|
app.sdkProbeTimer = interval;
|
|
67
54
|
}
|
|
68
|
-
// Best-effort cleanup if the socket closes before the initial probe fires
|
|
69
55
|
ws.once("close", () => {
|
|
70
56
|
clearTimeout(initial);
|
|
71
57
|
clearInterval(interval);
|
|
72
58
|
});
|
|
73
59
|
}
|
|
74
|
-
// Fire a burst of SDK probes on the heels of a (re)created execution context.
|
|
75
|
-
// After a reload the context is recreated but the SDK's init() re-runs slightly
|
|
76
|
-
// later; without this, sdkPresent only recovers on the next 3s interval tick,
|
|
77
|
-
// widening the window where get_network_requests falls off the SDK buffer.
|
|
78
60
|
function scheduleFastSdkReprobe(ws, appKey) {
|
|
79
61
|
for (const delay of SDK_REPROBE_DELAYS_MS) {
|
|
80
62
|
const t = setTimeout(() => {
|
|
@@ -85,48 +67,24 @@ function scheduleFastSdkReprobe(ws, appKey) {
|
|
|
85
67
|
ws.once("close", () => clearTimeout(t));
|
|
86
68
|
}
|
|
87
69
|
}
|
|
88
|
-
// Suppress auto-reconnection for intentionally disconnected devices
|
|
89
70
|
const reconnectionSuppressed = new Set();
|
|
90
|
-
/**
|
|
91
|
-
* Suppress auto-reconnection for all current connections.
|
|
92
|
-
* Used by disconnect_metro to prevent close handlers from re-connecting.
|
|
93
|
-
*/
|
|
94
71
|
export function suppressReconnection() {
|
|
95
72
|
for (const key of connectedApps.keys()) {
|
|
96
73
|
reconnectionSuppressed.add(key);
|
|
97
74
|
}
|
|
98
75
|
}
|
|
99
|
-
/**
|
|
100
|
-
* Suppress auto-reconnection for a specific connection key.
|
|
101
|
-
* Used by disconnect_metro with device targeting.
|
|
102
|
-
*/
|
|
103
76
|
export function suppressReconnectionForKey(appKey) {
|
|
104
77
|
reconnectionSuppressed.add(appKey);
|
|
105
78
|
}
|
|
106
|
-
/**
|
|
107
|
-
* Clear reconnection suppression (called when user explicitly reconnects via scan_metro).
|
|
108
|
-
*/
|
|
109
79
|
export function clearReconnectionSuppression() {
|
|
110
80
|
reconnectionSuppressed.clear();
|
|
111
81
|
}
|
|
112
82
|
const STALE_ACTIVITY_THRESHOLD_MS = 30_000;
|
|
113
|
-
const PING_INTERVAL_MS = 1_000;
|
|
114
|
-
/**
|
|
115
|
-
* Max time (ms) to wait for a Runtime.evaluate("1+1") reply during liveness probing.
|
|
116
|
-
* Tight budget: live targets respond in under 50ms; zombie CDP targets never respond.
|
|
117
|
-
*/
|
|
83
|
+
const PING_INTERVAL_MS = 1_000;
|
|
118
84
|
const PROBE_TIMEOUT_MS = 1500;
|
|
119
85
|
const RECONNECT_SETTLE_MS = 500;
|
|
120
|
-
/**
|
|
121
|
-
* Creates a WebSocket connection with Origin header fallback.
|
|
122
|
-
* RN 0.85+ Metro requires the Origin header, but Expo SDK 55 Bridgeless
|
|
123
|
-
* rejects connections that include one. The rejection can manifest as:
|
|
124
|
-
* - Connection refused before open (error/close before open event)
|
|
125
|
-
* - Immediate close after open (open fires, then close with code 1006)
|
|
126
|
-
* Tries with Origin first, falls back to without on quick failure.
|
|
127
|
-
*/
|
|
128
86
|
export function createWebSocketWithOriginFallback(url, timeoutMs = 5000) {
|
|
129
|
-
const STABILIZE_MS = 500;
|
|
87
|
+
const STABILIZE_MS = 500;
|
|
130
88
|
return new Promise((resolve, reject) => {
|
|
131
89
|
const wsUrl = new URL(url);
|
|
132
90
|
const origin = `http://${wsUrl.hostname}:${wsUrl.port}`;
|
|
@@ -138,7 +96,6 @@ export function createWebSocketWithOriginFallback(url, timeoutMs = 5000) {
|
|
|
138
96
|
ws.on("open", () => {
|
|
139
97
|
opened = true;
|
|
140
98
|
if (withOrigin) {
|
|
141
|
-
// Wait briefly to detect immediate close (Expo SDK 55 pattern)
|
|
142
99
|
setTimeout(() => {
|
|
143
100
|
if (settled)
|
|
144
101
|
return;
|
|
@@ -147,7 +104,6 @@ export function createWebSocketWithOriginFallback(url, timeoutMs = 5000) {
|
|
|
147
104
|
console.error("[execbro] WebSocket connected with Origin header");
|
|
148
105
|
resolve(ws);
|
|
149
106
|
}
|
|
150
|
-
// If not OPEN, the close handler will trigger fallback
|
|
151
107
|
}, STABILIZE_MS);
|
|
152
108
|
}
|
|
153
109
|
else {
|
|
@@ -185,7 +141,6 @@ export function createWebSocketWithOriginFallback(url, timeoutMs = 5000) {
|
|
|
185
141
|
}, timeoutMs);
|
|
186
142
|
});
|
|
187
143
|
}
|
|
188
|
-
// Helper to find appKey from device info by searching connectedApps
|
|
189
144
|
function findAppKeyForDevice(device) {
|
|
190
145
|
for (const [key, app] of connectedApps.entries()) {
|
|
191
146
|
if (app.deviceInfo.id === device.id) {
|
|
@@ -194,7 +149,6 @@ function findAppKeyForDevice(device) {
|
|
|
194
149
|
}
|
|
195
150
|
return null;
|
|
196
151
|
}
|
|
197
|
-
// Helper to convert WebSocket readyState to readable name
|
|
198
152
|
export function getWebSocketStateName(state) {
|
|
199
153
|
switch (state) {
|
|
200
154
|
case WebSocket.CONNECTING: return "CONNECTING";
|
|
@@ -204,24 +158,13 @@ export function getWebSocketStateName(state) {
|
|
|
204
158
|
default: return `UNKNOWN(${state})`;
|
|
205
159
|
}
|
|
206
160
|
}
|
|
207
|
-
/**
|
|
208
|
-
* Purge stale connections for scanned ports.
|
|
209
|
-
* When Metro restarts with a different app, /json returns new device IDs.
|
|
210
|
-
* Old connections keyed by the previous device IDs remain in connectedApps.
|
|
211
|
-
* This function removes connections on scanned ports whose device IDs
|
|
212
|
-
* are no longer present in the fresh device list from Metro.
|
|
213
|
-
*/
|
|
214
161
|
export function purgeStaleConnectionsForPorts(freshDevicesByPort) {
|
|
215
162
|
const purged = [];
|
|
216
163
|
const scannedPorts = new Set(freshDevicesByPort.keys());
|
|
217
164
|
for (const [appKey, app] of connectedApps.entries()) {
|
|
218
|
-
// Only check connections on ports we just scanned
|
|
219
165
|
if (!scannedPorts.has(app.port))
|
|
220
166
|
continue;
|
|
221
167
|
const freshDevices = freshDevicesByPort.get(app.port) || [];
|
|
222
|
-
// Check both device ID and appId — device IDs are per-physical-device
|
|
223
|
-
// (stable across Metro restarts), so a different app on the same device
|
|
224
|
-
// would have the same ID but a different appId
|
|
225
168
|
const matchingDevice = freshDevices.find(d => d.id === app.deviceInfo.id);
|
|
226
169
|
const stillValid = matchingDevice && matchingDevice.appId === app.deviceInfo.appId;
|
|
227
170
|
if (!stillValid) {
|
|
@@ -230,7 +173,6 @@ export function purgeStaleConnectionsForPorts(freshDevicesByPort) {
|
|
|
230
173
|
? "device no longer in Metro /json"
|
|
231
174
|
: `appId changed: ${app.deviceInfo.appId} → ${matchingDevice.appId}`;
|
|
232
175
|
console.error(`[execbro] Purging stale connection: ${name} (port ${app.port}, id ${app.deviceInfo.id}) — ${reason}`);
|
|
233
|
-
// Suppress reconnection so close handler doesn't try to reconnect
|
|
234
176
|
reconnectionSuppressed.add(appKey);
|
|
235
177
|
cancelReconnectionTimer(appKey);
|
|
236
178
|
clearLastCDPMessageTime(appKey);
|
|
@@ -238,14 +180,13 @@ export function purgeStaleConnectionsForPorts(freshDevicesByPort) {
|
|
|
238
180
|
try {
|
|
239
181
|
app.ws.close();
|
|
240
182
|
}
|
|
241
|
-
catch {
|
|
183
|
+
catch { }
|
|
242
184
|
connectedApps.delete(appKey);
|
|
243
185
|
purged.push(name);
|
|
244
186
|
}
|
|
245
187
|
}
|
|
246
188
|
return purged;
|
|
247
189
|
}
|
|
248
|
-
// Format CDP RemoteObject to readable string
|
|
249
190
|
export function formatRemoteObject(result) {
|
|
250
191
|
if (result.type === "undefined") {
|
|
251
192
|
return "undefined";
|
|
@@ -253,44 +194,31 @@ export function formatRemoteObject(result) {
|
|
|
253
194
|
if (result.subtype === "null") {
|
|
254
195
|
return "null";
|
|
255
196
|
}
|
|
256
|
-
// For objects/arrays with a value, stringify it
|
|
257
197
|
if (result.value !== undefined) {
|
|
258
198
|
if (typeof result.value === "object") {
|
|
259
199
|
return JSON.stringify(result.value, null, 2);
|
|
260
200
|
}
|
|
261
201
|
return String(result.value);
|
|
262
202
|
}
|
|
263
|
-
// Use description for complex objects
|
|
264
203
|
if (result.description) {
|
|
265
204
|
return result.description;
|
|
266
205
|
}
|
|
267
|
-
// Handle unserializable values (NaN, Infinity, etc.)
|
|
268
206
|
if (result.unserializableValue) {
|
|
269
207
|
return result.unserializableValue;
|
|
270
208
|
}
|
|
271
209
|
return `[${result.type}${result.subtype ? ` ${result.subtype}` : ""}]`;
|
|
272
210
|
}
|
|
273
|
-
/**
|
|
274
|
-
* Extract a clean, informative error message from CDP exception details
|
|
275
|
-
* Handles various error formats from Hermes and other JS engines
|
|
276
|
-
*/
|
|
277
211
|
function extractExceptionMessage(exceptionDetails) {
|
|
278
212
|
const parts = [];
|
|
279
|
-
// Get the exception object if available
|
|
280
213
|
const exc = exceptionDetails.exception;
|
|
281
214
|
if (exc) {
|
|
282
|
-
// For error objects, className tells us the error type (ReferenceError, TypeError, etc.)
|
|
283
215
|
const errorType = exc.className || (exc.subtype === 'error' ? 'Error' : '');
|
|
284
|
-
// The description usually contains "ErrorType: message" or full stack trace
|
|
285
|
-
// We want to extract just the first line (the actual error message)
|
|
286
216
|
if (exc.description) {
|
|
287
217
|
const firstLine = exc.description.split('\n')[0].trim();
|
|
288
|
-
// If description already includes the error type, use it directly
|
|
289
218
|
if (firstLine.includes(':')) {
|
|
290
219
|
parts.push(firstLine);
|
|
291
220
|
}
|
|
292
221
|
else if (errorType) {
|
|
293
|
-
// Combine error type with description
|
|
294
222
|
parts.push(`${errorType}: ${firstLine}`);
|
|
295
223
|
}
|
|
296
224
|
else {
|
|
@@ -298,7 +226,6 @@ function extractExceptionMessage(exceptionDetails) {
|
|
|
298
226
|
}
|
|
299
227
|
}
|
|
300
228
|
else if (exc.value !== undefined) {
|
|
301
|
-
// For primitive exceptions (throw "string" or throw 123)
|
|
302
229
|
const valueStr = typeof exc.value === 'string' ? exc.value : JSON.stringify(exc.value);
|
|
303
230
|
if (errorType) {
|
|
304
231
|
parts.push(`${errorType}: ${valueStr}`);
|
|
@@ -308,32 +235,25 @@ function extractExceptionMessage(exceptionDetails) {
|
|
|
308
235
|
}
|
|
309
236
|
}
|
|
310
237
|
else if (errorType) {
|
|
311
|
-
// Just the error type, no message
|
|
312
238
|
parts.push(errorType);
|
|
313
239
|
}
|
|
314
240
|
}
|
|
315
|
-
// Fall back to exceptionDetails.text if we couldn't extract from exception object
|
|
316
|
-
// But avoid just "Uncaught" which is not helpful
|
|
317
241
|
if (parts.length === 0) {
|
|
318
242
|
const text = exceptionDetails.text;
|
|
319
243
|
if (text && text.toLowerCase() !== 'uncaught') {
|
|
320
244
|
parts.push(text);
|
|
321
245
|
}
|
|
322
246
|
}
|
|
323
|
-
// Add location info for syntax/compilation errors (helps identify the problem)
|
|
324
247
|
if (exceptionDetails.lineNumber !== undefined && exceptionDetails.columnNumber !== undefined) {
|
|
325
|
-
// Only add location if it's meaningful (not 0:0 which is often just wrapper)
|
|
326
248
|
if (exceptionDetails.lineNumber > 0 || exceptionDetails.columnNumber > 0) {
|
|
327
249
|
parts.push(`at line ${exceptionDetails.lineNumber}:${exceptionDetails.columnNumber}`);
|
|
328
250
|
}
|
|
329
251
|
}
|
|
330
|
-
// If we still have nothing, provide a generic message
|
|
331
252
|
if (parts.length === 0) {
|
|
332
253
|
return 'JavaScript execution failed (no error details available)';
|
|
333
254
|
}
|
|
334
255
|
return parts.join(' ');
|
|
335
256
|
}
|
|
336
|
-
// Format a CDP object preview recursively
|
|
337
257
|
function formatPreview(preview) {
|
|
338
258
|
const isArray = preview.subtype === "array";
|
|
339
259
|
const props = preview.properties || [];
|
|
@@ -358,40 +278,32 @@ function formatPreview(preview) {
|
|
|
358
278
|
? `[${formatted.join(", ")}${overflow}]`
|
|
359
279
|
: `{${formatted.join(", ")}${overflow}}`;
|
|
360
280
|
}
|
|
361
|
-
// Format a single CDP console argument (sync — without object resolution)
|
|
362
281
|
function formatConsoleArg(arg) {
|
|
363
|
-
// Explicit undefined (matches browser: console.log(undefined) → "undefined")
|
|
364
282
|
if (arg.type === "undefined") {
|
|
365
283
|
return "undefined";
|
|
366
284
|
}
|
|
367
|
-
// Primitives
|
|
368
285
|
if (arg.type === "string" || arg.type === "number" || arg.type === "boolean") {
|
|
369
286
|
return String(arg.value);
|
|
370
287
|
}
|
|
371
|
-
// Objects/arrays with preview — expand inline
|
|
372
288
|
if (arg.preview?.properties) {
|
|
373
289
|
return formatPreview(arg.preview);
|
|
374
290
|
}
|
|
375
|
-
// Raw value (e.g. null sent as value)
|
|
376
291
|
if (arg.value !== undefined) {
|
|
377
292
|
return JSON.stringify(arg.value);
|
|
378
293
|
}
|
|
379
|
-
// Description fallback (functions, symbols, errors without preview)
|
|
380
294
|
if (arg.description) {
|
|
381
295
|
return arg.description;
|
|
382
296
|
}
|
|
383
|
-
// Object without any resolved data
|
|
384
297
|
if (arg.type === "object") {
|
|
385
298
|
return "[Object]";
|
|
386
299
|
}
|
|
387
300
|
return `[${arg.type || "unknown"}]`;
|
|
388
301
|
}
|
|
389
|
-
// Fetch object properties via CDP Runtime.getProperties
|
|
390
302
|
function fetchObjectProperties(ws, objectId, depth = 2) {
|
|
391
303
|
return new Promise((resolve) => {
|
|
392
304
|
const msgId = getNextMessageId();
|
|
393
305
|
const timeout = setTimeout(() => {
|
|
394
|
-
resolve("Object");
|
|
306
|
+
resolve("Object");
|
|
395
307
|
}, 3000);
|
|
396
308
|
const handler = (data) => {
|
|
397
309
|
try {
|
|
@@ -408,7 +320,6 @@ function fetchObjectProperties(ws, objectId, depth = 2) {
|
|
|
408
320
|
}
|
|
409
321
|
}
|
|
410
322
|
catch {
|
|
411
|
-
// Ignore parse errors
|
|
412
323
|
}
|
|
413
324
|
};
|
|
414
325
|
ws.on("message", handler);
|
|
@@ -419,11 +330,9 @@ function fetchObjectProperties(ws, objectId, depth = 2) {
|
|
|
419
330
|
}));
|
|
420
331
|
});
|
|
421
332
|
}
|
|
422
|
-
// Format CDP property descriptors into a readable string
|
|
423
333
|
async function formatCDPProperties(ws, properties, depth) {
|
|
424
334
|
const parts = [];
|
|
425
335
|
let isArrayLike = false;
|
|
426
|
-
// Detect arrays by checking for numeric keys and "length" property
|
|
427
336
|
const propNames = properties.filter((p) => !p.isAccessor && p.name !== "__proto__").map((p) => p.name);
|
|
428
337
|
const hasLength = propNames.includes("length");
|
|
429
338
|
const numericKeys = propNames.filter((n) => /^\d+$/.test(n));
|
|
@@ -447,7 +356,6 @@ async function formatCDPProperties(ws, properties, depth) {
|
|
|
447
356
|
? `[${parts.join(", ")}]`
|
|
448
357
|
: `{${parts.join(", ")}}`;
|
|
449
358
|
}
|
|
450
|
-
// Format a single property value
|
|
451
359
|
async function formatPropertyValue(ws, val, remainingDepth) {
|
|
452
360
|
const type = val.type;
|
|
453
361
|
const subtype = val.subtype;
|
|
@@ -461,11 +369,9 @@ async function formatPropertyValue(ws, val, remainingDepth) {
|
|
|
461
369
|
return String(val.value);
|
|
462
370
|
if (type === "function")
|
|
463
371
|
return "[Function]";
|
|
464
|
-
// Nested object — recurse if depth allows
|
|
465
372
|
if (type === "object" && val.objectId && remainingDepth > 0) {
|
|
466
373
|
return fetchObjectProperties(ws, val.objectId, remainingDepth);
|
|
467
374
|
}
|
|
468
|
-
// Object but no depth left — use description
|
|
469
375
|
if (type === "object") {
|
|
470
376
|
return val.description || "[Object]";
|
|
471
377
|
}
|
|
@@ -475,26 +381,20 @@ async function formatPropertyValue(ws, val, remainingDepth) {
|
|
|
475
381
|
return val.description;
|
|
476
382
|
return `[${type}]`;
|
|
477
383
|
}
|
|
478
|
-
// Resolve all object args in a console message, returning formatted text
|
|
479
384
|
async function resolveConsoleArgs(ws, args) {
|
|
480
385
|
const parts = await Promise.all(args.map(async (arg) => {
|
|
481
|
-
// Primitives — format synchronously
|
|
482
386
|
if (arg.type === "string" || arg.type === "number" || arg.type === "boolean") {
|
|
483
387
|
return String(arg.value);
|
|
484
388
|
}
|
|
485
|
-
// Objects/arrays with preview — expand inline
|
|
486
389
|
if (arg.preview?.properties) {
|
|
487
390
|
return formatPreview(arg.preview);
|
|
488
391
|
}
|
|
489
|
-
// Object with objectId — fetch properties via CDP
|
|
490
392
|
if (arg.type === "object" && arg.objectId) {
|
|
491
393
|
return fetchObjectProperties(ws, arg.objectId);
|
|
492
394
|
}
|
|
493
|
-
// Raw value
|
|
494
395
|
if (arg.value !== undefined) {
|
|
495
396
|
return JSON.stringify(arg.value);
|
|
496
397
|
}
|
|
497
|
-
// Description fallback
|
|
498
398
|
if (arg.description) {
|
|
499
399
|
return arg.description;
|
|
500
400
|
}
|
|
@@ -502,24 +402,19 @@ async function resolveConsoleArgs(ws, args) {
|
|
|
502
402
|
}));
|
|
503
403
|
return parts.join(" ");
|
|
504
404
|
}
|
|
505
|
-
// Handle CDP messages
|
|
506
405
|
export function handleCDPMessage(message, device, ws) {
|
|
507
|
-
// Track last CDP activity for connection liveness detection (per-device)
|
|
508
406
|
const cdpAppKey = findAppKeyForDevice(device);
|
|
509
407
|
if (cdpAppKey) {
|
|
510
408
|
updateLastCDPMessageTime(cdpAppKey, new Date());
|
|
511
409
|
}
|
|
512
410
|
const deviceName = device.deviceName || device.title || "unknown";
|
|
513
|
-
// Handle responses to our requests (e.g., Runtime.evaluate)
|
|
514
411
|
if (typeof message.id === "number") {
|
|
515
412
|
const pending = pendingExecutions.get(message.id);
|
|
516
413
|
if (pending) {
|
|
517
414
|
clearTimeout(pending.timeoutId);
|
|
518
415
|
pendingExecutions.delete(message.id);
|
|
519
|
-
// Check for CDP-level error (protocol error, not JS exception)
|
|
520
416
|
if (message.error) {
|
|
521
417
|
const error = message.error;
|
|
522
|
-
// Build comprehensive error message including code and data if available
|
|
523
418
|
const parts = [];
|
|
524
419
|
if (error.message)
|
|
525
420
|
parts.push(error.message);
|
|
@@ -531,21 +426,18 @@ export function handleCDPMessage(message, device, ws) {
|
|
|
531
426
|
pending.resolve({ success: false, error: errorMessage });
|
|
532
427
|
return;
|
|
533
428
|
}
|
|
534
|
-
// Check for JavaScript exception in result
|
|
535
429
|
const result = message.result;
|
|
536
430
|
if (result?.exceptionDetails) {
|
|
537
431
|
const errorMessage = extractExceptionMessage(result.exceptionDetails);
|
|
538
432
|
pending.resolve({ success: false, error: errorMessage });
|
|
539
433
|
return;
|
|
540
434
|
}
|
|
541
|
-
// Success - format the result
|
|
542
435
|
if (result?.result) {
|
|
543
436
|
pending.resolve({ success: true, result: formatRemoteObject(result.result) });
|
|
544
437
|
return;
|
|
545
438
|
}
|
|
546
439
|
pending.resolve({ success: true, result: "undefined" });
|
|
547
440
|
}
|
|
548
|
-
// Track SDK-probe responses
|
|
549
441
|
if (pendingSdkProbeIds.has(message.id)) {
|
|
550
442
|
const appKey = pendingSdkProbeIds.get(message.id);
|
|
551
443
|
pendingSdkProbeIds.delete(message.id);
|
|
@@ -554,15 +446,11 @@ export function handleCDPMessage(message, device, ws) {
|
|
|
554
446
|
const result = message.result?.result;
|
|
555
447
|
const present = result?.value === true;
|
|
556
448
|
const wasPresent = app.sdkPresent === true;
|
|
557
|
-
// Hysteresis on the true→false edge: tolerate transient SDK
|
|
558
|
-
// absence right after a reload (context recreated, init() not
|
|
559
|
-
// re-run yet) so we don't restore duplicate-prone CDP/interceptor
|
|
560
|
-
// writes and bounce get_network_requests off the SDK buffer.
|
|
561
449
|
let nextPresent = present;
|
|
562
450
|
if (!present && wasPresent) {
|
|
563
451
|
app.sdkMissCount = (app.sdkMissCount ?? 0) + 1;
|
|
564
452
|
if (app.sdkMissCount < SDK_ABSENCE_CONFIRM_COUNT) {
|
|
565
|
-
nextPresent = true;
|
|
453
|
+
nextPresent = true;
|
|
566
454
|
}
|
|
567
455
|
else {
|
|
568
456
|
app.sdkMissCount = 0;
|
|
@@ -574,9 +462,6 @@ export function handleCDPMessage(message, device, ws) {
|
|
|
574
462
|
app.sdkPresent = nextPresent;
|
|
575
463
|
if (nextPresent !== wasPresent) {
|
|
576
464
|
console.error(`[execbro] SDK ${present ? "detected" : "no longer detected"} on ${app.deviceInfo.title}; CDP/JS-interceptor buffer writes ${present ? "suppressed" : "restored"}`);
|
|
577
|
-
// Toggle the in-app interceptor's emit flag so it stops
|
|
578
|
-
// (or resumes) producing console.debug lines and CDP
|
|
579
|
-
// traffic. Fire-and-forget — the flag is idempotent.
|
|
580
465
|
if (app.ws.readyState === WebSocket.OPEN) {
|
|
581
466
|
try {
|
|
582
467
|
app.ws.send(JSON.stringify({
|
|
@@ -589,14 +474,12 @@ export function handleCDPMessage(message, device, ws) {
|
|
|
589
474
|
}));
|
|
590
475
|
}
|
|
591
476
|
catch {
|
|
592
|
-
// ignore — next probe will retry
|
|
593
477
|
}
|
|
594
478
|
}
|
|
595
479
|
}
|
|
596
480
|
}
|
|
597
481
|
return;
|
|
598
482
|
}
|
|
599
|
-
// Track Network.enable responses to detect CDP network support
|
|
600
483
|
if (pendingNetworkEnableIds.has(message.id)) {
|
|
601
484
|
pendingNetworkEnableIds.delete(message.id);
|
|
602
485
|
const nAppKey = findAppKeyForDevice(device);
|
|
@@ -617,29 +500,22 @@ export function handleCDPMessage(message, device, ws) {
|
|
|
617
500
|
return;
|
|
618
501
|
}
|
|
619
502
|
const method = message.method;
|
|
620
|
-
// Handle Runtime.consoleAPICalled
|
|
621
503
|
if (method === "Runtime.consoleAPICalled") {
|
|
622
504
|
const params = message.params;
|
|
623
505
|
const type = params.type || "log";
|
|
624
506
|
const level = mapConsoleType(type);
|
|
625
507
|
const args = params.args || [];
|
|
626
|
-
// Check for network interceptor events before processing as logs
|
|
627
508
|
const interceptorJson = isInterceptorEvent(args);
|
|
628
509
|
if (interceptorJson !== null) {
|
|
629
510
|
const iAppKey = findAppKeyForDevice(device);
|
|
630
511
|
const iApp = iAppKey ? connectedApps.get(iAppKey) : null;
|
|
631
|
-
// Skip when CDP Network domain is the source, OR when the in-app
|
|
632
|
-
// SDK is the source — both would otherwise produce duplicate
|
|
633
|
-
// entries for every request.
|
|
634
512
|
if (!iApp?.cdpNetworkSupported && !iApp?.sdkPresent) {
|
|
635
513
|
applyInterceptedEvent(interceptorJson, getNetworkBuffer(deviceName), deviceName);
|
|
636
514
|
}
|
|
637
515
|
return;
|
|
638
516
|
}
|
|
639
|
-
// Check if any args need async object resolution
|
|
640
517
|
const hasObjectArgs = ws && args.some((a) => a.type === "object" && a.objectId && !a.preview?.properties);
|
|
641
518
|
if (hasObjectArgs) {
|
|
642
|
-
// Resolve object args asynchronously via CDP
|
|
643
519
|
resolveConsoleArgs(ws, args).then((messageText) => {
|
|
644
520
|
getLogBuffer(deviceName).add({
|
|
645
521
|
timestamp: new Date(),
|
|
@@ -648,7 +524,6 @@ export function handleCDPMessage(message, device, ws) {
|
|
|
648
524
|
args: args.map((a) => a.value)
|
|
649
525
|
});
|
|
650
526
|
}).catch(() => {
|
|
651
|
-
// Fallback to sync formatting on error
|
|
652
527
|
const messageText = args.map(formatConsoleArg).join(" ");
|
|
653
528
|
getLogBuffer(deviceName).add({
|
|
654
529
|
timestamp: new Date(),
|
|
@@ -670,7 +545,6 @@ export function handleCDPMessage(message, device, ws) {
|
|
|
670
545
|
}
|
|
671
546
|
}
|
|
672
547
|
}
|
|
673
|
-
// Handle Log.entryAdded
|
|
674
548
|
if (method === "Log.entryAdded") {
|
|
675
549
|
const params = message.params;
|
|
676
550
|
if (params.entry) {
|
|
@@ -682,24 +556,12 @@ export function handleCDPMessage(message, device, ws) {
|
|
|
682
556
|
});
|
|
683
557
|
}
|
|
684
558
|
}
|
|
685
|
-
// Handle Network.requestWillBeSent
|
|
686
559
|
if (method === "Network.requestWillBeSent") {
|
|
687
|
-
// Skip CDP capture when the in-app SDK is the source of truth —
|
|
688
|
-
// otherwise every request lands in both buffers under different ids.
|
|
689
|
-
// The follow-up Network.responseReceived/loadingFinished/loadingFailed
|
|
690
|
-
// handlers below only mutate existing entries, so they self-skip.
|
|
691
560
|
const nAppKey = findAppKeyForDevice(device);
|
|
692
561
|
const nApp = nAppKey ? connectedApps.get(nAppKey) : null;
|
|
693
562
|
if (nApp?.sdkPresent) {
|
|
694
563
|
return;
|
|
695
564
|
}
|
|
696
|
-
// An arriving Network.requestWillBeSent is proof the CDP Network domain
|
|
697
|
-
// is active. Some backends (e.g. RN 0.85 Bridgeless/Hermes) enable the
|
|
698
|
-
// domain but never send a response to Network.enable, so the
|
|
699
|
-
// response-based detection above never fires and cdpNetworkSupported
|
|
700
|
-
// stays undefined — leaving the JS interceptor un-suppressed and every
|
|
701
|
-
// request double-captured (CDP + interceptor). Mark support here so the
|
|
702
|
-
// interceptor dedup guard (see Runtime.consoleAPICalled handler) trips.
|
|
703
565
|
if (nApp && !nApp.cdpNetworkSupported) {
|
|
704
566
|
nApp.cdpNetworkSupported = true;
|
|
705
567
|
console.error(`[execbro] CDP Network domain active (detected via requestWillBeSent on ${nApp.deviceInfo.title})`);
|
|
@@ -720,7 +582,6 @@ export function handleCDPMessage(message, device, ws) {
|
|
|
720
582
|
};
|
|
721
583
|
getNetworkBuffer(deviceName).set(params.requestId, request);
|
|
722
584
|
}
|
|
723
|
-
// Handle Network.responseReceived
|
|
724
585
|
if (method === "Network.responseReceived") {
|
|
725
586
|
const params = message.params;
|
|
726
587
|
const existing = getNetworkBuffer(deviceName).get(params.requestId);
|
|
@@ -735,7 +596,6 @@ export function handleCDPMessage(message, device, ws) {
|
|
|
735
596
|
getNetworkBuffer(deviceName).set(params.requestId, existing);
|
|
736
597
|
}
|
|
737
598
|
}
|
|
738
|
-
// Handle Network.loadingFinished
|
|
739
599
|
if (method === "Network.loadingFinished") {
|
|
740
600
|
const params = message.params;
|
|
741
601
|
const existing = getNetworkBuffer(deviceName).get(params.requestId);
|
|
@@ -748,7 +608,6 @@ export function handleCDPMessage(message, device, ws) {
|
|
|
748
608
|
getNetworkBuffer(deviceName).set(params.requestId, existing);
|
|
749
609
|
}
|
|
750
610
|
}
|
|
751
|
-
// Handle Network.loadingFailed
|
|
752
611
|
if (method === "Network.loadingFailed") {
|
|
753
612
|
const params = message.params;
|
|
754
613
|
const existing = getNetworkBuffer(deviceName).get(params.requestId);
|
|
@@ -758,54 +617,39 @@ export function handleCDPMessage(message, device, ws) {
|
|
|
758
617
|
getNetworkBuffer(deviceName).set(params.requestId, existing);
|
|
759
618
|
}
|
|
760
619
|
}
|
|
761
|
-
// Handle Runtime context lifecycle events for health tracking
|
|
762
620
|
const appKey = findAppKeyForDevice(device);
|
|
763
621
|
if (appKey) {
|
|
764
|
-
// Handle Runtime.executionContextCreated
|
|
765
622
|
if (method === "Runtime.executionContextCreated") {
|
|
766
623
|
const params = message.params;
|
|
767
624
|
markContextHealthy(appKey, params.context.id);
|
|
768
625
|
console.error(`[execbro] Context created: ${params.context.id}`);
|
|
769
|
-
// Re-inject network interceptor and re-check CDP Network support
|
|
770
626
|
const ctxApp = connectedApps.get(appKey);
|
|
771
627
|
if (ctxApp?.ws?.readyState === WebSocket.OPEN) {
|
|
772
628
|
injectNetworkInterceptor(ctxApp.ws);
|
|
773
629
|
const nEnableId = sendNetworkEnable(ctxApp.ws);
|
|
774
630
|
pendingNetworkEnableIds.add(nEnableId);
|
|
775
|
-
// The context was just recreated (e.g. reload). Re-probe the SDK
|
|
776
|
-
// quickly so sdkPresent re-establishes within a few hundred ms
|
|
777
|
-
// instead of on the next 3s interval tick.
|
|
778
631
|
scheduleFastSdkReprobe(ctxApp.ws, appKey);
|
|
779
632
|
}
|
|
780
633
|
}
|
|
781
|
-
// Handle Runtime.executionContextDestroyed
|
|
782
634
|
if (method === "Runtime.executionContextDestroyed") {
|
|
783
635
|
markContextStale(appKey);
|
|
784
636
|
console.error(`[execbro] Context destroyed`);
|
|
785
637
|
}
|
|
786
|
-
// Handle Runtime.executionContextsCleared
|
|
787
638
|
if (method === "Runtime.executionContextsCleared") {
|
|
788
639
|
markContextStale(appKey);
|
|
789
|
-
// A replaced JS runtime starts a new app run. Bump so pre-restart
|
|
790
|
-
// entries stay addressable and readers can draw the boundary.
|
|
791
640
|
const clearedDevice = device.deviceName || device.title || "unknown";
|
|
792
641
|
const nextEpoch = bumpEpoch(clearedDevice);
|
|
793
642
|
console.error(`[execbro] All contexts cleared — ${clearedDevice} now epoch ${nextEpoch}`);
|
|
794
643
|
}
|
|
795
644
|
}
|
|
796
645
|
}
|
|
797
|
-
// Connect to a device via CDP WebSocket
|
|
798
646
|
export async function connectToDevice(device, port, options = {}) {
|
|
799
647
|
const { isReconnection = false, reconnectionConfig = DEFAULT_RECONNECTION_CONFIG } = options;
|
|
800
648
|
return new Promise(async (resolve, reject) => {
|
|
801
649
|
const appKey = `${port}-${device.id}`;
|
|
802
|
-
// Check if already connected with a valid WebSocket
|
|
803
650
|
const existingApp = connectedApps.get(appKey);
|
|
804
651
|
if (existingApp) {
|
|
805
652
|
if (existingApp.ws.readyState === WebSocket.OPEN) {
|
|
806
|
-
// Verify WebSocket is actually alive with a ping/pong check.
|
|
807
|
-
// readyState can show OPEN even when the remote end is gone
|
|
808
|
-
// (TCP hasn't timed out yet, especially after Metro restart).
|
|
809
653
|
const isAlive = await new Promise((pongResolve) => {
|
|
810
654
|
const timeout = setTimeout(() => pongResolve(false), 2000);
|
|
811
655
|
existingApp.ws.once("pong", () => {
|
|
@@ -824,26 +668,22 @@ export async function connectToDevice(device, port, options = {}) {
|
|
|
824
668
|
resolve(`Already connected to ${device.title}`);
|
|
825
669
|
return;
|
|
826
670
|
}
|
|
827
|
-
// Ping failed — connection is dead, clean up
|
|
828
671
|
console.error(`[execbro] Existing connection to ${device.title} failed liveness check (no pong), reconnecting`);
|
|
829
672
|
recordConnectionEvent("liveness-failed", appKey, device.title, "no pong within 2000ms");
|
|
830
673
|
reconnectionSuppressed.add(appKey);
|
|
831
674
|
try {
|
|
832
675
|
existingApp.ws.terminate();
|
|
833
676
|
}
|
|
834
|
-
catch {
|
|
677
|
+
catch { }
|
|
835
678
|
connectedApps.delete(appKey);
|
|
836
679
|
clearLastCDPMessageTime(appKey);
|
|
837
680
|
clearActiveSimulatorIfSource(appKey);
|
|
838
681
|
}
|
|
839
682
|
else {
|
|
840
|
-
// WebSocket exists but not OPEN - clean up stale entry
|
|
841
683
|
console.error(`[execbro] Cleaning up stale connection for ${device.title} (state: ${getWebSocketStateName(existingApp.ws.readyState)})`);
|
|
842
684
|
connectedApps.delete(appKey);
|
|
843
685
|
}
|
|
844
686
|
}
|
|
845
|
-
// Skip if this device is already connected on a different port
|
|
846
|
-
// (Metro's /json advertises all devices it can see, even ones built by other Metro servers)
|
|
847
687
|
const deviceName = device.deviceName || device.title;
|
|
848
688
|
for (const [existingKey, existingApp] of connectedApps.entries()) {
|
|
849
689
|
if (existingKey !== appKey && existingApp.ws.readyState === WebSocket.OPEN) {
|
|
@@ -854,15 +694,12 @@ export async function connectToDevice(device, port, options = {}) {
|
|
|
854
694
|
}
|
|
855
695
|
}
|
|
856
696
|
}
|
|
857
|
-
// Prevent concurrent connection attempts to the same device
|
|
858
697
|
if (connectionLocks.has(appKey)) {
|
|
859
698
|
resolve(`Connection already in progress for ${device.title}`);
|
|
860
699
|
return;
|
|
861
700
|
}
|
|
862
701
|
connectionLocks.add(appKey);
|
|
863
|
-
// Cancel any pending reconnection timer for this appKey
|
|
864
702
|
cancelReconnectionTimer(appKey);
|
|
865
|
-
// Save connection metadata for potential reconnection
|
|
866
703
|
saveConnectionMetadata(appKey, {
|
|
867
704
|
port,
|
|
868
705
|
deviceInfo: device,
|
|
@@ -870,53 +707,34 @@ export async function connectToDevice(device, port, options = {}) {
|
|
|
870
707
|
});
|
|
871
708
|
try {
|
|
872
709
|
const ws = await createWebSocketWithOriginFallback(device.webSocketDebuggerUrl);
|
|
873
|
-
// Register close/error handlers IMMEDIATELY — before any async work
|
|
874
|
-
// below (probeCdpAlive, Runtime.enable, simctl/adb lookups). Previously
|
|
875
|
-
// they were attached only at the end of the setup block, so any close
|
|
876
|
-
// event during that window was silently swallowed: the entry stayed
|
|
877
|
-
// in connectedApps, readyState flipped to CLOSED, but no reconnect
|
|
878
|
-
// was scheduled and no `closed` event was recorded.
|
|
879
710
|
let pingInterval = null;
|
|
880
711
|
let staleRejected = false;
|
|
881
712
|
const handleClose = () => {
|
|
882
713
|
if (staleRejected)
|
|
883
|
-
return;
|
|
714
|
+
return;
|
|
884
715
|
if (pingInterval) {
|
|
885
716
|
clearInterval(pingInterval);
|
|
886
717
|
pingInterval = null;
|
|
887
718
|
}
|
|
888
|
-
// Fail anything still in flight on this socket. Otherwise each
|
|
889
|
-
// call parks until its own timeoutMs and then reports a generic
|
|
890
|
-
// "Expression took too long", which classifies as logical and
|
|
891
|
-
// never triggers auto-reconnect.
|
|
892
719
|
const failedCount = failPendingExecutionsForSocket(ws, "WebSocket connection is not open.");
|
|
893
720
|
if (failedCount > 0) {
|
|
894
721
|
console.error(`[execbro] Failed ${failedCount} in-flight call(s) on closed socket for ${device.title}`);
|
|
895
722
|
}
|
|
896
|
-
// Release connection lock if still held
|
|
897
723
|
connectionLocks.delete(appKey);
|
|
898
|
-
// Stop mirroring — the socket is gone, so every tick would just
|
|
899
|
-
// fail. No drain attempt here: there is nothing left to read.
|
|
900
724
|
stopSdkMirrorPoller(device.deviceName || device.title || "unknown");
|
|
901
725
|
connectedApps.delete(appKey);
|
|
902
726
|
clearLastCDPMessageTime(appKey);
|
|
903
|
-
// Clear active simulator UDID if this connection set it
|
|
904
727
|
clearActiveSimulatorIfSource(appKey);
|
|
905
|
-
// Keep log and network buffers across reconnections
|
|
906
|
-
// They are only cleared on demand via clear_logs / clear_network tools
|
|
907
|
-
// Check if connection was stable before resetting attempts
|
|
908
728
|
const state = getConnectionState(appKey);
|
|
909
729
|
let wasStable = false;
|
|
910
730
|
if (state?.lastConnectedTime) {
|
|
911
731
|
const connectionDuration = Date.now() - state.lastConnectedTime.getTime();
|
|
912
732
|
wasStable = connectionDuration >= MIN_STABLE_CONNECTION_MS;
|
|
913
733
|
if (wasStable) {
|
|
914
|
-
// Connection was stable - reset attempts for fresh start
|
|
915
734
|
updateConnectionState(appKey, { reconnectionAttempts: 0 });
|
|
916
735
|
console.error(`[execbro] Connection was stable for ${Math.round(connectionDuration / 1000)}s, resetting reconnection attempts`);
|
|
917
736
|
}
|
|
918
737
|
}
|
|
919
|
-
// Record the gap and trigger reconnection
|
|
920
738
|
recordConnectionGap(appKey, "Connection closed");
|
|
921
739
|
if (state) {
|
|
922
740
|
updateConnectionState(appKey, {
|
|
@@ -928,11 +746,7 @@ export async function connectToDevice(device, port, options = {}) {
|
|
|
928
746
|
const earlyPhase = state ? "" : " (before state init)";
|
|
929
747
|
console.error(`[execbro] Disconnected from ${device.title}${earlyPhase}`);
|
|
930
748
|
recordConnectionEvent("closed", appKey, device.title, `uptime ${formatDuration(uptimeMs)}${wasStable ? " (stable)" : " (unstable)"}${earlyPhase}`);
|
|
931
|
-
// Schedule auto-reconnection if enabled (skip if intentionally disconnected)
|
|
932
749
|
if (reconnectionConfig.enabled && !reconnectionSuppressed.has(appKey)) {
|
|
933
|
-
// If close fired before initConnectionState, scheduleReconnection
|
|
934
|
-
// would early-return (it requires existing state). Init it now
|
|
935
|
-
// so the reconnect loop can still run.
|
|
936
750
|
if (!state)
|
|
937
751
|
initConnectionState(appKey);
|
|
938
752
|
scheduleReconnection(appKey, reconnectionConfig);
|
|
@@ -950,10 +764,6 @@ export async function connectToDevice(device, port, options = {}) {
|
|
|
950
764
|
ws.on("error", (error) => {
|
|
951
765
|
console.error(`[execbro] WebSocket error for ${device.title}: ${error?.message || error}`);
|
|
952
766
|
});
|
|
953
|
-
// Verify the JS context is actually alive. Metro's /json can advertise
|
|
954
|
-
// zombie CDP pages that complete the WS handshake but no longer execute.
|
|
955
|
-
// A stale target here causes every downstream tool to time out or return
|
|
956
|
-
// degenerate state (e.g. __DEV__ === false on a debug bundle).
|
|
957
767
|
const alive = await probeCdpAlive(ws, PROBE_TIMEOUT_MS);
|
|
958
768
|
if (!alive) {
|
|
959
769
|
staleRejected = true;
|
|
@@ -962,41 +772,29 @@ export async function connectToDevice(device, port, options = {}) {
|
|
|
962
772
|
try {
|
|
963
773
|
ws.terminate();
|
|
964
774
|
}
|
|
965
|
-
catch {
|
|
775
|
+
catch { }
|
|
966
776
|
clearConnectionMetadata(appKey);
|
|
967
777
|
console.error(`[execbro] Rejecting stale CDP target for ${device.title} (no probe response)`);
|
|
968
778
|
recordConnectionEvent("stale-target", appKey, device.title, `no probe response within ${PROBE_TIMEOUT_MS}ms${isReconnection ? " (during reconnect)" : ""}`);
|
|
969
779
|
resolve(`Skipped ${device.deviceName || device.title} (stale CDP target — no response from JS context)`);
|
|
970
780
|
return;
|
|
971
781
|
}
|
|
972
|
-
// Connection established — run setup
|
|
973
782
|
connectionLocks.delete(appKey);
|
|
974
783
|
connectedApps.set(appKey, { ws, deviceInfo: device, port, platform: "android" });
|
|
975
784
|
markConnectionEstablished();
|
|
976
|
-
// A new CDP target id under a device name we have buffered before
|
|
977
|
-
// means the app process was replaced. Bump so pre-restart entries
|
|
978
|
-
// stay addressable and readers can draw the boundary.
|
|
979
785
|
const bufferKey = device.deviceName || device.title || "unknown";
|
|
980
786
|
const previousTargetId = lastTargetIdByDevice.get(bufferKey);
|
|
981
787
|
if (previousTargetId && previousTargetId !== device.id) {
|
|
982
788
|
bumpEpoch(bufferKey);
|
|
983
789
|
}
|
|
984
790
|
lastTargetIdByDevice.set(bufferKey, device.id);
|
|
985
|
-
// Mirror the in-app SDK buffers into ours so a hard app restart
|
|
986
|
-
// does not take the only copy with it. No-op when the SDK is absent
|
|
987
|
-
// or EXECBRO_DISABLE_SDK_MIRROR=1.
|
|
988
791
|
startSdkMirrorPoller(device.deviceName);
|
|
989
|
-
// Initialize or update connection state
|
|
990
|
-
// Note: We do NOT reset reconnectionAttempts here - that happens
|
|
991
|
-
// only when connection has been stable for MIN_STABLE_CONNECTION_MS
|
|
992
792
|
if (isReconnection) {
|
|
993
793
|
closeConnectionGap(appKey);
|
|
994
794
|
updateConnectionState(appKey, {
|
|
995
795
|
status: "connected",
|
|
996
796
|
lastConnectedTime: new Date()
|
|
997
|
-
// reconnectionAttempts NOT reset here - see ws.on("close") for stable connection check
|
|
998
797
|
});
|
|
999
|
-
// Reset context health for reconnection
|
|
1000
798
|
initContextHealth(appKey);
|
|
1001
799
|
console.error(`[execbro] Reconnected to ${device.title}`);
|
|
1002
800
|
recordConnectionEvent("reconnect-success", appKey, device.title);
|
|
@@ -1007,26 +805,20 @@ export async function connectToDevice(device, port, options = {}) {
|
|
|
1007
805
|
console.error(`[execbro] Connected to ${device.title}`);
|
|
1008
806
|
recordConnectionEvent("connect-success", appKey, device.title);
|
|
1009
807
|
}
|
|
1010
|
-
// Enable Runtime domain to receive console messages
|
|
1011
808
|
ws.send(JSON.stringify({
|
|
1012
809
|
id: getNextMessageId(),
|
|
1013
810
|
method: "Runtime.enable"
|
|
1014
811
|
}));
|
|
1015
|
-
// Also enable Log domain
|
|
1016
812
|
ws.send(JSON.stringify({
|
|
1017
813
|
id: getNextMessageId(),
|
|
1018
814
|
method: "Log.enable"
|
|
1019
815
|
}));
|
|
1020
|
-
// Inject JS network interceptor (immediate capture, may fail if context not ready)
|
|
1021
816
|
injectNetworkInterceptor(ws);
|
|
1022
|
-
|
|
817
|
+
void import("./routeHistory.js")
|
|
818
|
+
.then((m) => m.installRouteHistory(device.deviceName))
|
|
819
|
+
.catch(() => { });
|
|
1023
820
|
const networkEnableId = sendNetworkEnable(ws);
|
|
1024
821
|
pendingNetworkEnableIds.add(networkEnableId);
|
|
1025
|
-
// Resolve native identifiers from the device name in parallel:
|
|
1026
|
-
// - iOS: simulator UDID via findSimulatorByName
|
|
1027
|
-
// - Android: adb serial via getAdbIdForAvd (maps AVD name → emulator-NNNN)
|
|
1028
|
-
// Both are best-effort; failures are swallowed. Identifiers enable
|
|
1029
|
-
// automatic device scoping and power the registry-first resolver fast path.
|
|
1030
822
|
if (device.deviceName) {
|
|
1031
823
|
const [simulatorUdid, adbSerial] = await Promise.all([
|
|
1032
824
|
findSimulatorByName(device.deviceName).catch(() => null),
|
|
@@ -1047,19 +839,11 @@ export async function connectToDevice(device, port, options = {}) {
|
|
|
1047
839
|
}
|
|
1048
840
|
}
|
|
1049
841
|
}
|
|
1050
|
-
// Fire-and-forget app detection (500ms delayed, non-blocking)
|
|
1051
842
|
const appForDetection = connectedApps.get(appKey);
|
|
1052
843
|
if (appForDetection) {
|
|
1053
844
|
scheduleAppDetection(appForDetection);
|
|
1054
845
|
}
|
|
1055
|
-
// Start periodic SDK probe. When __RN_AI_DEVTOOLS__ is detected,
|
|
1056
|
-
// app.sdkPresent flips to true and CDP/JS-interceptor buffer
|
|
1057
|
-
// writes are suppressed (SDK becomes the single source).
|
|
1058
846
|
startSdkProbeLoop(ws, appKey);
|
|
1059
|
-
// Start WebSocket ping/pong keepalive to detect dead connections
|
|
1060
|
-
// (especially important for physical devices over Wi-Fi).
|
|
1061
|
-
// pingInterval was declared up-top so the early-registered close
|
|
1062
|
-
// handler can clear it; close/error handlers are already attached.
|
|
1063
847
|
let pongReceived = true;
|
|
1064
848
|
pingInterval = setInterval(() => {
|
|
1065
849
|
if (!pongReceived) {
|
|
@@ -1083,13 +867,11 @@ export async function connectToDevice(device, port, options = {}) {
|
|
|
1083
867
|
handleCDPMessage(message, device, ws);
|
|
1084
868
|
}
|
|
1085
869
|
catch {
|
|
1086
|
-
// Ignore non-JSON messages
|
|
1087
870
|
}
|
|
1088
871
|
});
|
|
1089
872
|
resolve(`Connected to ${device.title} (${device.deviceName})`);
|
|
1090
873
|
}
|
|
1091
874
|
catch (error) {
|
|
1092
|
-
// Connection failed (both with and without Origin header)
|
|
1093
875
|
connectionLocks.delete(appKey);
|
|
1094
876
|
cancelReconnectionTimer(appKey);
|
|
1095
877
|
connectedApps.delete(appKey);
|
|
@@ -1105,9 +887,6 @@ export async function connectToDevice(device, port, options = {}) {
|
|
|
1105
887
|
}
|
|
1106
888
|
});
|
|
1107
889
|
}
|
|
1108
|
-
/**
|
|
1109
|
-
* Schedule a reconnection attempt with exponential backoff
|
|
1110
|
-
*/
|
|
1111
890
|
function scheduleReconnection(appKey, config = DEFAULT_RECONNECTION_CONFIG) {
|
|
1112
891
|
const state = getConnectionState(appKey);
|
|
1113
892
|
if (!state)
|
|
@@ -1133,9 +912,6 @@ function scheduleReconnection(appKey, config = DEFAULT_RECONNECTION_CONFIG) {
|
|
|
1133
912
|
}, delay);
|
|
1134
913
|
saveReconnectionTimer(appKey, timer);
|
|
1135
914
|
}
|
|
1136
|
-
/**
|
|
1137
|
-
* Attempt to reconnect to a previously connected device
|
|
1138
|
-
*/
|
|
1139
915
|
async function attemptReconnection(appKey, config = DEFAULT_RECONNECTION_CONFIG) {
|
|
1140
916
|
const metadata = getConnectionMetadata(appKey);
|
|
1141
917
|
if (!metadata) {
|
|
@@ -1145,7 +921,6 @@ async function attemptReconnection(appKey, config = DEFAULT_RECONNECTION_CONFIG)
|
|
|
1145
921
|
}
|
|
1146
922
|
const deviceTitle = metadata.deviceInfo.title;
|
|
1147
923
|
recordConnectionEvent("reconnect-attempt", appKey, deviceTitle);
|
|
1148
|
-
// Quick check: is Metro even running on this port?
|
|
1149
924
|
const { scanMetroPorts } = await import("./metro.js");
|
|
1150
925
|
const openPorts = await scanMetroPorts(metadata.port, metadata.port);
|
|
1151
926
|
if (openPorts.length === 0) {
|
|
@@ -1155,15 +930,12 @@ async function attemptReconnection(appKey, config = DEFAULT_RECONNECTION_CONFIG)
|
|
|
1155
930
|
return false;
|
|
1156
931
|
}
|
|
1157
932
|
try {
|
|
1158
|
-
// Re-fetch devices to get fresh WebSocket URL (may have changed)
|
|
1159
933
|
const devices = await fetchDevices(metadata.port);
|
|
1160
|
-
// Try to find the same device first, otherwise select main device
|
|
1161
934
|
const device = devices.find(d => d.id === metadata.deviceInfo.id)
|
|
1162
935
|
|| selectMainDevice(devices);
|
|
1163
936
|
if (!device) {
|
|
1164
937
|
console.error(`[execbro] Device no longer available for ${appKey}`);
|
|
1165
938
|
recordConnectionEvent("reconnect-failed", appKey, deviceTitle, "device no longer advertised by Metro");
|
|
1166
|
-
// Schedule next attempt
|
|
1167
939
|
scheduleReconnection(appKey, config);
|
|
1168
940
|
return false;
|
|
1169
941
|
}
|
|
@@ -1174,12 +946,10 @@ async function attemptReconnection(appKey, config = DEFAULT_RECONNECTION_CONFIG)
|
|
|
1174
946
|
const msg = error instanceof Error ? error.message : String(error);
|
|
1175
947
|
console.error(`[execbro] Reconnection failed: ${msg}`);
|
|
1176
948
|
recordConnectionEvent("reconnect-failed", appKey, deviceTitle, msg);
|
|
1177
|
-
// Schedule next attempt
|
|
1178
949
|
scheduleReconnection(appKey, config);
|
|
1179
950
|
return false;
|
|
1180
951
|
}
|
|
1181
952
|
}
|
|
1182
|
-
// Get list of connected apps
|
|
1183
953
|
export function getConnectedApps() {
|
|
1184
954
|
return Array.from(connectedApps.entries()).map(([key, app]) => ({
|
|
1185
955
|
key,
|
|
@@ -1187,24 +957,16 @@ export function getConnectedApps() {
|
|
|
1187
957
|
isConnected: app.ws.readyState === WebSocket.OPEN
|
|
1188
958
|
}));
|
|
1189
959
|
}
|
|
1190
|
-
// Get first connected app with an OPEN WebSocket (or null if none)
|
|
1191
960
|
export function getFirstConnectedApp() {
|
|
1192
|
-
// Find first app with OPEN WebSocket, cleaning up stale entries
|
|
1193
961
|
for (const [key, app] of connectedApps.entries()) {
|
|
1194
962
|
if (app.ws.readyState === WebSocket.OPEN) {
|
|
1195
963
|
return app;
|
|
1196
964
|
}
|
|
1197
|
-
// Clean up stale entry
|
|
1198
965
|
console.error(`[execbro] Cleaning up stale connection in getFirstConnectedApp: ${key} (state: ${getWebSocketStateName(app.ws.readyState)})`);
|
|
1199
966
|
connectedApps.delete(key);
|
|
1200
967
|
}
|
|
1201
968
|
return null;
|
|
1202
969
|
}
|
|
1203
|
-
/**
|
|
1204
|
-
* Find the connected RN app running on a specific iOS simulator (by UDID).
|
|
1205
|
-
* Returns null if no connected app matches — caller should skip any CDP-based
|
|
1206
|
-
* enrichment to avoid pulling data from a different simulator's app.
|
|
1207
|
-
*/
|
|
1208
970
|
export function getConnectedAppBySimulatorUdid(udid) {
|
|
1209
971
|
for (const [key, app] of connectedApps.entries()) {
|
|
1210
972
|
if (app.ws.readyState !== WebSocket.OPEN) {
|
|
@@ -1217,13 +979,6 @@ export function getConnectedAppBySimulatorUdid(udid) {
|
|
|
1217
979
|
}
|
|
1218
980
|
return null;
|
|
1219
981
|
}
|
|
1220
|
-
/**
|
|
1221
|
-
* Find the connected RN app running on a specific Android device.
|
|
1222
|
-
* Falls back to the sole connected Android app when `deviceId` is not provided
|
|
1223
|
-
* or cannot be resolved via deviceName substring match — this keeps the common
|
|
1224
|
-
* single-device case working while preventing cross-device data leakage when
|
|
1225
|
-
* multiple Android apps are connected.
|
|
1226
|
-
*/
|
|
1227
982
|
export function getConnectedAppByAndroidDeviceId(deviceId) {
|
|
1228
983
|
const androidApps = [];
|
|
1229
984
|
for (const [key, app] of connectedApps.entries()) {
|
|
@@ -1249,20 +1004,13 @@ export function getConnectedAppByAndroidDeviceId(deviceId) {
|
|
|
1249
1004
|
}
|
|
1250
1005
|
return null;
|
|
1251
1006
|
}
|
|
1252
|
-
/**
|
|
1253
|
-
* Lowercase and strip separators (whitespace, `_`, `-`) so substring matches
|
|
1254
|
-
* survive punctuation drift between caller input and the device's reported
|
|
1255
|
-
* identifier (e.g. "SM_A356N" vs "SM-A356N - 15 - API 35").
|
|
1256
|
-
*/
|
|
1257
1007
|
function normalizeDeviceId(value) {
|
|
1258
1008
|
if (!value)
|
|
1259
1009
|
return "";
|
|
1260
1010
|
return value.toLowerCase().replace(/[\s_\-]+/g, "");
|
|
1261
1011
|
}
|
|
1262
|
-
/** Guess which platform a caller-supplied device string refers to. */
|
|
1263
1012
|
function guessRequestedPlatform(device) {
|
|
1264
1013
|
const lower = device.toLowerCase();
|
|
1265
|
-
// Simulator UDIDs are uppercase UUIDs; adb serials are not.
|
|
1266
1014
|
if (/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/.test(lower))
|
|
1267
1015
|
return "ios";
|
|
1268
1016
|
if (/iphone|ipad|ipod|\bios\b|simulator/.test(lower))
|
|
@@ -1274,10 +1022,6 @@ function guessRequestedPlatform(device) {
|
|
|
1274
1022
|
function deviceLabel(app) {
|
|
1275
1023
|
return app.deviceInfo.deviceName || app.deviceInfo.title || "";
|
|
1276
1024
|
}
|
|
1277
|
-
/**
|
|
1278
|
-
* Human-facing explanation for a `kind: "none"` / `kind: "ambiguous"` resolution.
|
|
1279
|
-
* Kept next to the matcher so the message always reflects the matching rules.
|
|
1280
|
-
*/
|
|
1281
1025
|
export function describeDeviceResolution(resolution) {
|
|
1282
1026
|
if (resolution.kind === "ok")
|
|
1283
1027
|
return "";
|
|
@@ -1293,9 +1037,6 @@ export function describeDeviceResolution(resolution) {
|
|
|
1293
1037
|
return `No connected device matches "${device}". No devices are currently connected — run scan_metro to discover and connect to Metro servers.`;
|
|
1294
1038
|
}
|
|
1295
1039
|
const quoted = connected.map(a => `"${deviceLabel(a)}"`).join(", ");
|
|
1296
|
-
// Cross-platform mismatch is the dominant shape of this failure: the agent
|
|
1297
|
-
// passes a name from list_devices while only the other
|
|
1298
|
-
// platform is attached to Metro. Saying so beats "retry with one of these".
|
|
1299
1040
|
const requested = guessRequestedPlatform(device);
|
|
1300
1041
|
const attachedPlatforms = new Set(connected.map(a => a.platform));
|
|
1301
1042
|
if (requested && !attachedPlatforms.has(requested)) {
|
|
@@ -1311,10 +1052,6 @@ export function describeDeviceResolution(resolution) {
|
|
|
1311
1052
|
}
|
|
1312
1053
|
return `No connected device matches "${device}". Connected devices: ${quoted}. Retry with one of these names (substring match, case-insensitive), or run get_apps to list them.`;
|
|
1313
1054
|
}
|
|
1314
|
-
/**
|
|
1315
|
-
* Non-throwing device matcher. Prefer this in call sites that can retry
|
|
1316
|
-
* (auto-connect, rescan); use getConnectedAppByDevice when a miss is terminal.
|
|
1317
|
-
*/
|
|
1318
1055
|
export function resolveConnectedAppByDevice(device) {
|
|
1319
1056
|
const openApps = [];
|
|
1320
1057
|
for (const [key, app] of connectedApps.entries()) {
|
|
@@ -1332,10 +1069,6 @@ export function resolveConnectedAppByDevice(device) {
|
|
|
1332
1069
|
const normDevice = normalizeDeviceId(device);
|
|
1333
1070
|
const matches = [];
|
|
1334
1071
|
for (const app of openApps) {
|
|
1335
|
-
// Match against deviceName + the underlying hardware identifiers, with
|
|
1336
|
-
// separator-insensitive normalization so "SM_A356N" finds
|
|
1337
|
-
// "SM-A356N - 15 - API 35" and "emulator-5554" finds the Android app
|
|
1338
|
-
// attached to that serial.
|
|
1339
1072
|
const haystacks = [
|
|
1340
1073
|
normalizeDeviceId(deviceLabel(app)),
|
|
1341
1074
|
normalizeDeviceId(app.simulatorUdid),
|
|
@@ -1345,8 +1078,6 @@ export function resolveConnectedAppByDevice(device) {
|
|
|
1345
1078
|
matches.push(app);
|
|
1346
1079
|
}
|
|
1347
1080
|
}
|
|
1348
|
-
// Prefer exact (case-insensitive) match when present — disambiguates
|
|
1349
|
-
// "iPhone 17 Pro" from "iPhone 17 Pro Max" when both are connected.
|
|
1350
1081
|
const exact = matches.find(a => deviceLabel(a).toLowerCase() === lowerDevice);
|
|
1351
1082
|
if (exact)
|
|
1352
1083
|
return { kind: "ok", app: exact };
|
|
@@ -1360,8 +1091,6 @@ export function getConnectedAppByDevice(device) {
|
|
|
1360
1091
|
const resolution = resolveConnectedAppByDevice(device);
|
|
1361
1092
|
if (resolution.kind === "ok")
|
|
1362
1093
|
return resolution.app;
|
|
1363
|
-
// No device argument: a miss just means "nothing connected" — callers
|
|
1364
|
-
// handle null themselves (historically getFirstConnectedApp's contract).
|
|
1365
1094
|
if (!device)
|
|
1366
1095
|
return null;
|
|
1367
1096
|
const context = resolution.kind === "ambiguous"
|
|
@@ -1369,7 +1098,6 @@ export function getConnectedAppByDevice(device) {
|
|
|
1369
1098
|
: resolution.connected.length > 0 ? "device_mismatch" : "no_devices_connected";
|
|
1370
1099
|
throw new UserInputError(describeDeviceResolution(resolution), context);
|
|
1371
1100
|
}
|
|
1372
|
-
// Check if any app is connected with an OPEN WebSocket
|
|
1373
1101
|
export function hasConnectedApp() {
|
|
1374
1102
|
for (const [, app] of connectedApps.entries()) {
|
|
1375
1103
|
if (app.ws.readyState === WebSocket.OPEN) {
|
|
@@ -1378,10 +1106,6 @@ export function hasConnectedApp() {
|
|
|
1378
1106
|
}
|
|
1379
1107
|
return false;
|
|
1380
1108
|
}
|
|
1381
|
-
/**
|
|
1382
|
-
* Run a quick health check to verify the page context is responsive
|
|
1383
|
-
* Returns true if the context can execute code, false otherwise
|
|
1384
|
-
*/
|
|
1385
1109
|
export async function runQuickHealthCheck(app) {
|
|
1386
1110
|
const HEALTH_CHECK_TIMEOUT = 2000;
|
|
1387
1111
|
const messageId = getNextMessageId();
|
|
@@ -1394,7 +1118,6 @@ export async function runQuickHealthCheck(app) {
|
|
|
1394
1118
|
resolve: (result) => {
|
|
1395
1119
|
clearTimeout(timeoutId);
|
|
1396
1120
|
pendingExecutions.delete(messageId);
|
|
1397
|
-
// Update context health tracking
|
|
1398
1121
|
const appKey = findAppKeyForDevice(app.deviceInfo);
|
|
1399
1122
|
if (appKey) {
|
|
1400
1123
|
updateContextHealth(appKey, {
|
|
@@ -1422,10 +1145,6 @@ export async function runQuickHealthCheck(app) {
|
|
|
1422
1145
|
});
|
|
1423
1146
|
}
|
|
1424
1147
|
const LOG_PIPELINE_MARKER_PREFIX = "__rn_devtools_health_";
|
|
1425
|
-
/**
|
|
1426
|
-
* Inject a sentinel console.log via CDP and wait for it to appear in the log buffer.
|
|
1427
|
-
* Returns true if the marker arrives within TIMEOUT_MS.
|
|
1428
|
-
*/
|
|
1429
1148
|
async function sendAndWaitForMarker(app, buffer) {
|
|
1430
1149
|
const marker = `${LOG_PIPELINE_MARKER_PREFIX}${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
|
|
1431
1150
|
const TIMEOUT_MS = 3000;
|
|
@@ -1439,7 +1158,6 @@ async function sendAndWaitForMarker(app, buffer) {
|
|
|
1439
1158
|
catch {
|
|
1440
1159
|
return false;
|
|
1441
1160
|
}
|
|
1442
|
-
// Poll the buffer for the marker (check every 50ms, up to TIMEOUT_MS)
|
|
1443
1161
|
const startTime = Date.now();
|
|
1444
1162
|
while (Date.now() - startTime < TIMEOUT_MS) {
|
|
1445
1163
|
if (buffer.getAll().some(entry => entry.message.includes(marker))) {
|
|
@@ -1450,45 +1168,29 @@ async function sendAndWaitForMarker(app, buffer) {
|
|
|
1450
1168
|
}
|
|
1451
1169
|
return false;
|
|
1452
1170
|
}
|
|
1453
|
-
/**
|
|
1454
|
-
* End-to-end log pipeline verification with automatic recovery.
|
|
1455
|
-
*
|
|
1456
|
-
* 1. Injects a sentinel console.log via CDP and verifies it appears in the buffer.
|
|
1457
|
-
* 2. If the marker doesn't arrive, attempts recovery:
|
|
1458
|
-
* a. Re-sends Runtime.enable + Log.enable (fixes missing domain subscription)
|
|
1459
|
-
* b. Retries the marker once
|
|
1460
|
-
* 3. If still broken, force-reconnects via ensureConnection and retries.
|
|
1461
|
-
*
|
|
1462
|
-
* Returns { ok, recovered, message } — the caller decides what to show.
|
|
1463
|
-
*/
|
|
1464
1171
|
export async function verifyLogPipeline(app) {
|
|
1465
1172
|
const deviceName = app.deviceInfo.deviceName || app.deviceInfo.title || "unknown";
|
|
1466
1173
|
const buffer = getLogBuffer(deviceName);
|
|
1467
|
-
// --- Attempt 1: quick check ---
|
|
1468
1174
|
if (await sendAndWaitForMarker(app, buffer)) {
|
|
1469
1175
|
return { ok: true, recovered: false, message: null };
|
|
1470
1176
|
}
|
|
1471
|
-
// --- Attempt 2: re-enable Runtime/Log domains and retry ---
|
|
1472
1177
|
try {
|
|
1473
1178
|
app.ws.send(JSON.stringify({ id: getNextMessageId(), method: "Runtime.enable" }));
|
|
1474
1179
|
app.ws.send(JSON.stringify({ id: getNextMessageId(), method: "Log.enable" }));
|
|
1475
1180
|
}
|
|
1476
1181
|
catch {
|
|
1477
|
-
// WS send failed — fall through to reconnect
|
|
1478
1182
|
}
|
|
1479
|
-
// Brief pause for domain activation
|
|
1480
1183
|
await new Promise(resolve => setTimeout(resolve, 300));
|
|
1481
1184
|
if (await sendAndWaitForMarker(app, buffer)) {
|
|
1482
1185
|
return { ok: true, recovered: true, message: "[CONNECTION] Log pipeline was stale — re-enabled CDP domains. Logs are flowing again." };
|
|
1483
1186
|
}
|
|
1484
|
-
// --- Attempt 3: force reconnect and retry ---
|
|
1485
1187
|
const appKey = `${app.port}-${app.deviceInfo.id}`;
|
|
1486
1188
|
const port = app.port;
|
|
1487
1189
|
cancelReconnectionTimer(appKey);
|
|
1488
1190
|
try {
|
|
1489
1191
|
app.ws.close();
|
|
1490
1192
|
}
|
|
1491
|
-
catch {
|
|
1193
|
+
catch { }
|
|
1492
1194
|
connectedApps.delete(appKey);
|
|
1493
1195
|
const result = await ensureConnection({ port, forceRefresh: false, healthCheck: true });
|
|
1494
1196
|
if (!result.connected || !result.healthCheckPassed) {
|
|
@@ -1498,9 +1200,7 @@ export async function verifyLogPipeline(app) {
|
|
|
1498
1200
|
message: "[CONNECTION] Log pipeline broken and reconnection failed. Ensure the app is running, then call scan_metro.",
|
|
1499
1201
|
};
|
|
1500
1202
|
}
|
|
1501
|
-
// Wait for the new connection to settle
|
|
1502
1203
|
await new Promise(resolve => setTimeout(resolve, 500));
|
|
1503
|
-
// Get the fresh app reference after reconnection
|
|
1504
1204
|
const freshApp = getFirstConnectedApp();
|
|
1505
1205
|
if (!freshApp) {
|
|
1506
1206
|
return {
|
|
@@ -1524,43 +1224,29 @@ export async function verifyLogPipeline(app) {
|
|
|
1524
1224
|
message: "[WARNING] Log pipeline verification failed after reconnection. Console events are not reaching the buffer. Try reload_app or scan_metro.",
|
|
1525
1225
|
};
|
|
1526
1226
|
}
|
|
1527
|
-
/**
|
|
1528
|
-
* Check if a log message is a health check marker (for filtering from output).
|
|
1529
|
-
*/
|
|
1530
1227
|
export function isHealthCheckMarker(message) {
|
|
1531
1228
|
return message.includes(LOG_PIPELINE_MARKER_PREFIX);
|
|
1532
1229
|
}
|
|
1533
|
-
/**
|
|
1534
|
-
* Find the first available Metro port
|
|
1535
|
-
*/
|
|
1536
1230
|
async function findFirstMetroPort() {
|
|
1537
1231
|
const ports = await scanMetroPorts();
|
|
1538
1232
|
return ports.length > 0 ? ports[0] : null;
|
|
1539
1233
|
}
|
|
1540
|
-
/**
|
|
1541
|
-
* Ensure a healthy connection to a React Native app
|
|
1542
|
-
* This will verify or establish a connection, optionally running a health check
|
|
1543
|
-
*/
|
|
1544
1234
|
export async function ensureConnection(options = {}) {
|
|
1545
1235
|
const { port, healthCheck = true, forceRefresh = false } = options;
|
|
1546
1236
|
let wasReconnected = false;
|
|
1547
|
-
// forceRefresh: close every currently-connected app, then fall through to
|
|
1548
|
-
// single-device fresh connect. Full multi-device re-discovery is scan_metro's
|
|
1549
|
-
// job — ensureConnection only fresh-connects one when nothing is open.
|
|
1550
1237
|
if (forceRefresh) {
|
|
1551
1238
|
for (const { key, app } of getConnectedApps()) {
|
|
1552
1239
|
cancelReconnectionTimer(key);
|
|
1553
1240
|
try {
|
|
1554
1241
|
app.ws.close();
|
|
1555
1242
|
}
|
|
1556
|
-
catch {
|
|
1243
|
+
catch { }
|
|
1557
1244
|
connectedApps.delete(key);
|
|
1558
1245
|
}
|
|
1559
1246
|
}
|
|
1560
1247
|
let openApps = getConnectedApps()
|
|
1561
1248
|
.filter(a => a.isConnected)
|
|
1562
1249
|
.map(a => a.app);
|
|
1563
|
-
// Nothing connected — establish a single-device connection from scratch.
|
|
1564
1250
|
if (openApps.length === 0) {
|
|
1565
1251
|
const targetPort = port ?? await findFirstMetroPort();
|
|
1566
1252
|
if (!targetPort) {
|
|
@@ -1584,10 +1270,6 @@ export async function ensureConnection(options = {}) {
|
|
|
1584
1270
|
};
|
|
1585
1271
|
}
|
|
1586
1272
|
try {
|
|
1587
|
-
// Clear any prior reconnection suppression — without this a stale
|
|
1588
|
-
// suppression entry from a previous disconnect_metro keeps the
|
|
1589
|
-
// close-handler from auto-recovering if Metro drops the WS just
|
|
1590
|
-
// after probe.
|
|
1591
1273
|
clearReconnectionSuppression();
|
|
1592
1274
|
const connectResult = await connectToDevice(mainDevice, targetPort);
|
|
1593
1275
|
wasReconnected = true;
|
|
@@ -1600,7 +1282,6 @@ export async function ensureConnection(options = {}) {
|
|
|
1600
1282
|
error: `${connectResult}. The CDP page advertised by Metro is no longer responsive — restart the React Native app, then retry.`,
|
|
1601
1283
|
};
|
|
1602
1284
|
}
|
|
1603
|
-
// Race window: settle briefly and retry getting the open app list.
|
|
1604
1285
|
for (let i = 0; i < 5 && openApps.length === 0; i++) {
|
|
1605
1286
|
if (i > 0)
|
|
1606
1287
|
await new Promise(resolve => setTimeout(resolve, 100));
|
|
@@ -1636,8 +1317,6 @@ export async function ensureConnection(options = {}) {
|
|
|
1636
1317
|
error: "Connection succeeded but app is not available",
|
|
1637
1318
|
};
|
|
1638
1319
|
}
|
|
1639
|
-
// Per-device health-check; reconnect failed devices individually so a dead
|
|
1640
|
-
// Android doesn't take down a healthy iOS report and vice versa.
|
|
1641
1320
|
const perApp = [];
|
|
1642
1321
|
for (const candidate of openApps) {
|
|
1643
1322
|
let app = candidate;
|
|
@@ -1653,7 +1332,7 @@ export async function ensureConnection(options = {}) {
|
|
|
1653
1332
|
try {
|
|
1654
1333
|
app.ws.close();
|
|
1655
1334
|
}
|
|
1656
|
-
catch {
|
|
1335
|
+
catch { }
|
|
1657
1336
|
connectedApps.delete(appKey);
|
|
1658
1337
|
const devices = await fetchDevices(appPort);
|
|
1659
1338
|
const mainDevice = selectMainDevice(devices);
|
|
@@ -1716,7 +1395,6 @@ export async function ensureConnection(options = {}) {
|
|
|
1716
1395
|
}
|
|
1717
1396
|
export function getPassiveConnectionStatus(targetAppKey) {
|
|
1718
1397
|
if (targetAppKey) {
|
|
1719
|
-
// Check a specific device's connection
|
|
1720
1398
|
const app = connectedApps.get(targetAppKey);
|
|
1721
1399
|
if (!app || app.ws.readyState !== WebSocket.OPEN) {
|
|
1722
1400
|
return { connected: false, needsPing: false, reason: "no_connection" };
|
|
@@ -1735,7 +1413,6 @@ export function getPassiveConnectionStatus(targetAppKey) {
|
|
|
1735
1413
|
}
|
|
1736
1414
|
return { connected: true, needsPing: false, reason: "ok" };
|
|
1737
1415
|
}
|
|
1738
|
-
// Default: check first connected app (backwards compatible)
|
|
1739
1416
|
if (!hasConnectedApp()) {
|
|
1740
1417
|
return { connected: false, needsPing: false, reason: "no_connection" };
|
|
1741
1418
|
}
|
|
@@ -1758,7 +1435,6 @@ export function getPassiveConnectionStatus(targetAppKey) {
|
|
|
1758
1435
|
return { connected: true, needsPing: false, reason: "ok" };
|
|
1759
1436
|
}
|
|
1760
1437
|
export async function checkAndEnsureConnection(device) {
|
|
1761
|
-
// Resolve targeted device to appKey if specified
|
|
1762
1438
|
let targetAppKey;
|
|
1763
1439
|
let targetApp = null;
|
|
1764
1440
|
if (device) {
|
|
@@ -1769,7 +1445,6 @@ export async function checkAndEnsureConnection(device) {
|
|
|
1769
1445
|
}
|
|
1770
1446
|
}
|
|
1771
1447
|
catch {
|
|
1772
|
-
// Device not found — fall through to reconnection
|
|
1773
1448
|
}
|
|
1774
1449
|
}
|
|
1775
1450
|
const passive = getPassiveConnectionStatus(targetAppKey);
|