@scalebun-release/react-native 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/LICENSE +21 -0
- package/README.md +154 -0
- package/android/build.gradle +94 -0
- package/android/consumer-rules.pro +28 -0
- package/android/src/androidTest/java/com/scalebun/rn/ota/ScaleBunOtaVerifierInstrumentedTest.kt +112 -0
- package/android/src/main/AndroidManifest.xml +14 -0
- package/android/src/main/java/com/scalebun/core/crash/CrashStorage.kt +155 -0
- package/android/src/main/java/com/scalebun/core/crash/NativeCrashHandler.kt +93 -0
- package/android/src/main/java/com/scalebun/core/performance/ANRWatchdog.kt +100 -0
- package/android/src/main/java/com/scalebun/core/performance/AppStartCollector.kt +180 -0
- package/android/src/main/java/com/scalebun/core/performance/FrameMetricsCollector.kt +230 -0
- package/android/src/main/java/com/scalebun/core/performance/PerformanceCore.kt +165 -0
- package/android/src/main/java/com/scalebun/profiler/ProfilerModule.kt +398 -0
- package/android/src/main/java/com/scalebun/profiler/ProfilerPackage.kt +24 -0
- package/android/src/main/java/com/scalebun/profiler/collectors/CpuCollector.kt +161 -0
- package/android/src/main/java/com/scalebun/profiler/collectors/IncidentDetector.kt +223 -0
- package/android/src/main/java/com/scalebun/profiler/collectors/MemoryCollector.kt +157 -0
- package/android/src/main/java/com/scalebun/profiler/collectors/RenderCollector.kt +194 -0
- package/android/src/main/java/com/scalebun/profiler/collectors/SamplerScheduler.kt +218 -0
- package/android/src/main/java/com/scalebun/profiler/collectors/ThreadCollector.kt +142 -0
- package/android/src/main/java/com/scalebun/replaysdk/ReplaySdkModule.kt +1117 -0
- package/android/src/main/java/com/scalebun/replaysdk/ReplaySdkPackage.kt +24 -0
- package/android/src/main/java/com/scalebun/replaysdk/capture/CaptureReason.kt +47 -0
- package/android/src/main/java/com/scalebun/replaysdk/capture/FrameCaptureManager.kt +574 -0
- package/android/src/main/java/com/scalebun/replaysdk/capture/FrameCaptureScheduler.kt +187 -0
- package/android/src/main/java/com/scalebun/replaysdk/capture/UiStabilityDetector.kt +214 -0
- package/android/src/main/java/com/scalebun/replaysdk/core/ReplayConfig.kt +144 -0
- package/android/src/main/java/com/scalebun/replaysdk/core/ReplayLogger.kt +31 -0
- package/android/src/main/java/com/scalebun/replaysdk/core/SessionIdGenerator.kt +15 -0
- package/android/src/main/java/com/scalebun/replaysdk/models/ReplayModels.kt +94 -0
- package/android/src/main/java/com/scalebun/replaysdk/outbox/NativeOutbox.kt +574 -0
- package/android/src/main/java/com/scalebun/replaysdk/outbox/OutboxUploader.kt +559 -0
- package/android/src/main/java/com/scalebun/replaysdk/privacy/PrivacyMaskProcessor.kt +126 -0
- package/android/src/main/java/com/scalebun/replaysdk/session/SessionManager.kt +322 -0
- package/android/src/main/java/com/scalebun/replaysdk/storage/ReplayStorage.kt +136 -0
- package/android/src/main/java/com/scalebun/replaysdk/timeline/TimelineCollector.kt +28 -0
- package/android/src/main/java/com/scalebun/replaysdk/tracking/InteractionTracker.kt +470 -0
- package/android/src/main/java/com/scalebun/replaysdk/tracking/ScreenTracker.kt +20 -0
- package/android/src/main/java/com/scalebun/replaysdk/tracking/ScrollContextProbe.kt +188 -0
- package/android/src/main/java/com/scalebun/replaysdk/transport/ReplayTransport.kt +99 -0
- package/android/src/main/java/com/scalebun/replaysdk/voice/VoiceRecorder.kt +121 -0
- package/android/src/main/java/com/scalebun/replaysdk/voice/VoiceTranscriber.kt +170 -0
- package/android/src/main/java/com/scalebun/rn/ScaleBunPackage.kt +113 -0
- package/android/src/main/java/com/scalebun/rn/crash/ScaleBunCrashModule.kt +188 -0
- package/android/src/main/java/com/scalebun/rn/crash/ScaleBunCrashPackage.kt +22 -0
- package/android/src/main/java/com/scalebun/rn/engage/ScaleBunEngageModule.kt +579 -0
- package/android/src/main/java/com/scalebun/rn/engage/ScaleBunEngagePackage.kt +22 -0
- package/android/src/main/java/com/scalebun/rn/engage/ScaleBunNotificationPresenter.kt +110 -0
- package/android/src/main/java/com/scalebun/rn/ota/BsPatch.kt +240 -0
- package/android/src/main/java/com/scalebun/rn/ota/BundleDownloader.kt +195 -0
- package/android/src/main/java/com/scalebun/rn/ota/DeviceIntegrity.kt +81 -0
- package/android/src/main/java/com/scalebun/rn/ota/DownloadFailure.kt +55 -0
- package/android/src/main/java/com/scalebun/rn/ota/OtaProtocol.kt +244 -0
- package/android/src/main/java/com/scalebun/rn/ota/ScaleBunOtaKeyRegistry.kt +100 -0
- package/android/src/main/java/com/scalebun/rn/ota/ScaleBunOtaModule.kt +683 -0
- package/android/src/main/java/com/scalebun/rn/ota/ScaleBunOtaReleaseVerifier.kt +136 -0
- package/android/src/main/java/com/scalebun/rn/ota/SlotManager.kt +578 -0
- package/android/src/main/java/com/scalebun/rn/ota/TlsPinning.kt +118 -0
- package/android/src/main/java/com/scalebun/rn/performance/PerformanceModule.kt +217 -0
- package/android/src/main/java/com/scalebun/rn/referrer/ScaleBunInstallReferrerModule.kt +77 -0
- package/android/src/main/java/com/scalebun/rn/storage/ScaleBunStorageModule.kt +68 -0
- package/android/src/main/java/com/scalebun/storage/KvFileStore.kt +174 -0
- package/android/src/newarch/java/com/scalebun/profiler/ProfilerSpec.kt +12 -0
- package/android/src/newarch/java/com/scalebun/replaysdk/ReplaySdkSpec.kt +12 -0
- package/android/src/newarch/java/com/scalebun/rn/crash/ScaleBunCrashSpec.kt +15 -0
- package/android/src/newarch/java/com/scalebun/rn/ota/ScaleBunOtaSpec.kt +16 -0
- package/android/src/newarch/java/com/scalebun/rn/performance/PerformanceSpec.kt +12 -0
- package/android/src/newarch/java/com/scalebun/rn/referrer/ScaleBunInstallReferrerSpec.kt +14 -0
- package/android/src/newarch/java/com/scalebun/rn/storage/ScaleBunStorageSpec.kt +16 -0
- package/android/src/oldarch/java/com/scalebun/profiler/ProfilerSpec.kt +34 -0
- package/android/src/oldarch/java/com/scalebun/replaysdk/ReplaySdkSpec.kt +77 -0
- package/android/src/oldarch/java/com/scalebun/rn/crash/ScaleBunCrashSpec.kt +40 -0
- package/android/src/oldarch/java/com/scalebun/rn/ota/ScaleBunOtaSpec.kt +42 -0
- package/android/src/oldarch/java/com/scalebun/rn/performance/PerformanceSpec.kt +27 -0
- package/android/src/oldarch/java/com/scalebun/rn/referrer/ScaleBunInstallReferrerSpec.kt +18 -0
- package/android/src/oldarch/java/com/scalebun/rn/storage/ScaleBunStorageSpec.kt +26 -0
- package/bin/lib/androidCodemod.js +370 -0
- package/bin/lib/iosCodemod.js +343 -0
- package/bin/scalebun.js +1004 -0
- package/dist/scalebun.full.js +37348 -0
- package/dist/scalebun.slim.js +34137 -0
- package/ios/Capture/InteractionTracker.swift +237 -0
- package/ios/Capture/PrivacyMaskProcessor.swift +158 -0
- package/ios/Capture/ScreenshotCaptureManager.swift +398 -0
- package/ios/Capture/WindowResolver.swift +63 -0
- package/ios/Core/DeviceIdentity.swift +50 -0
- package/ios/Core/ReplayConfig.swift +100 -0
- package/ios/Core/ReplayLogger.swift +22 -0
- package/ios/Core/SessionIdGenerator.swift +18 -0
- package/ios/Crash/ScaleBunCrashBridge.mm +74 -0
- package/ios/Crash/ScaleBunCrashHandler.swift +84 -0
- package/ios/Crash/ScaleBunCrashModule.swift +98 -0
- package/ios/Crash/ScaleBunCrashSignalBridge.h +49 -0
- package/ios/Crash/ScaleBunCrashSignalBridge.m +296 -0
- package/ios/Crash/ScaleBunCrashSignalStress.c +137 -0
- package/ios/Crash/ScaleBunCrashStorage.swift +121 -0
- package/ios/Crash/ScaleBunExceptionChain.swift +71 -0
- package/ios/CrashTests/ScaleBunExceptionChainTests.swift +144 -0
- package/ios/Engage/ScaleBunEngageBridge.m +32 -0
- package/ios/Engage/ScaleBunEngageModule.swift +250 -0
- package/ios/Engage/ScaleBunPushNotificationCenter.swift +185 -0
- package/ios/Ota/BsPatch.swift +203 -0
- package/ios/Ota/BundleDownloader.swift +249 -0
- package/ios/Ota/DeviceIntegrity.swift +112 -0
- package/ios/Ota/DownloadFailure.swift +56 -0
- package/ios/Ota/OtaProtocol.swift +246 -0
- package/ios/Ota/OtaSlotManager.swift +613 -0
- package/ios/Ota/ScaleBunOtaBridge.mm +92 -0
- package/ios/Ota/ScaleBunOtaEventsModule.swift +61 -0
- package/ios/Ota/ScaleBunOtaKeyRegistry.swift +147 -0
- package/ios/Ota/ScaleBunOtaModule.swift +587 -0
- package/ios/Ota/ScaleBunOtaReleaseVerifier.swift +119 -0
- package/ios/Ota/TlsPinning.swift +222 -0
- package/ios/OtaTests/DownloadFailureTests.swift +78 -0
- package/ios/OtaTests/ScaleBunOtaVerifierTests.swift +548 -0
- package/ios/Outbox/NativeOutbox.swift +632 -0
- package/ios/Outbox/OutboxUploader.swift +578 -0
- package/ios/Performance/ScaleBunPerformanceBridge.m +32 -0
- package/ios/Performance/ScaleBunPerformanceModule.swift +290 -0
- package/ios/PrivacyInfo.xcprivacy +97 -0
- package/ios/Profiler/ScaleBunProfilerBridge.m +55 -0
- package/ios/Profiler/ScaleBunProfilerModule.swift +966 -0
- package/ios/ReplaySdk-Bridging-Header.h +2 -0
- package/ios/ReplaySdk.swift +793 -0
- package/ios/ReplaySdkBridge.m +97 -0
- package/ios/Session/SessionManager.swift +273 -0
- package/ios/Skan/ScaleBunSkanBridge.m +14 -0
- package/ios/Skan/ScaleBunSkanModule.swift +61 -0
- package/ios/Storage/ReplayStorage.swift +61 -0
- package/ios/Storage/ScaleBunKvFileStore.swift +125 -0
- package/ios/Storage/ScaleBunStorageBridge.mm +72 -0
- package/ios/Storage/ScaleBunStorageModule.swift +57 -0
- package/ios/Transport/ReplayTransport.swift +70 -0
- package/ios/VoiceRecorder.swift +145 -0
- package/lib/commonjs/analytics/EventTracker.js +598 -0
- package/lib/commonjs/analytics/automaticEvents.js +71 -0
- package/lib/commonjs/analytics/batching.js +64 -0
- package/lib/commonjs/analytics/eventLane.js +55 -0
- package/lib/commonjs/analytics/revenue.js +125 -0
- package/lib/commonjs/analytics/subscription.js +94 -0
- package/lib/commonjs/bootstrap/FeatureRegistry.js +86 -0
- package/lib/commonjs/bootstrap/SDKBootstrapper.js +748 -0
- package/lib/commonjs/bucketing/fnv1a32.js +45 -0
- package/lib/commonjs/compat/codepush.js +153 -0
- package/lib/commonjs/config/ConfigManager.js +238 -0
- package/lib/commonjs/config/configTypes.js +16 -0
- package/lib/commonjs/config/otaPolicyState.js +85 -0
- package/lib/commonjs/config/quotaState.js +435 -0
- package/lib/commonjs/core/architecture.js +45 -0
- package/lib/commonjs/core/clock/now.js +13 -0
- package/lib/commonjs/core/config/defaults.js +2 -0
- package/lib/commonjs/core/config/schema.js +512 -0
- package/lib/commonjs/core/constants/endpoints.js +19 -0
- package/lib/commonjs/core/constants/protocol.js +31 -0
- package/lib/commonjs/core/constants/timings.js +39 -0
- package/lib/commonjs/core/constants/version.js +18 -0
- package/lib/commonjs/core/context/app.js +2 -0
- package/lib/commonjs/core/context/device.js +155 -0
- package/lib/commonjs/core/context/session.js +2 -0
- package/lib/commonjs/core/context/user.js +2 -0
- package/lib/commonjs/core/contracts/IFeature.js +2 -0
- package/lib/commonjs/core/di/container.js +29 -0
- package/lib/commonjs/core/encoding/base64.js +50 -0
- package/lib/commonjs/core/id/deviceId.js +52 -0
- package/lib/commonjs/core/id/installationId.js +55 -0
- package/lib/commonjs/core/id/sessionId.js +2 -0
- package/lib/commonjs/core/lifecycle/appLifecycle.js +31 -0
- package/lib/commonjs/core/lifecycle/crashSafe.js +33 -0
- package/lib/commonjs/core/logger/errorClassification.js +107 -0
- package/lib/commonjs/core/logger/internalLogger.js +63 -0
- package/lib/commonjs/core/logger/levels.js +2 -0
- package/lib/commonjs/crypto/hmacSha256.js +132 -0
- package/lib/commonjs/debug/bootstrap.js +429 -0
- package/lib/commonjs/debug/commands.js +732 -0
- package/lib/commonjs/debug/configBuilder.js +70 -0
- package/lib/commonjs/debug/exportBridge.js +155 -0
- package/lib/commonjs/debug/hostResolver.js +42 -0
- package/lib/commonjs/debug/index.js +141 -0
- package/lib/commonjs/debug/metrics.js +39 -0
- package/lib/commonjs/debug/perf.js +186 -0
- package/lib/commonjs/debug/perfWiring.js +109 -0
- package/lib/commonjs/debug/protocol.js +121 -0
- package/lib/commonjs/debug/redaction.js +422 -0
- package/lib/commonjs/debug/replayWiring.js +149 -0
- package/lib/commonjs/debug/screenTracking.js +60 -0
- package/lib/commonjs/debug/sessionWiring.js +48 -0
- package/lib/commonjs/debug/stream.js +542 -0
- package/lib/commonjs/debug/transport.js +368 -0
- package/lib/commonjs/features/bugreport/BugReportFeature.js +112 -0
- package/lib/commonjs/features/bugreport/buildPayload.js +97 -0
- package/lib/commonjs/features/bugreport/index.js +2 -0
- package/lib/commonjs/features/crash/CrashFeature.js +86 -0
- package/lib/commonjs/features/crash/CrashReporter.js +160 -0
- package/lib/commonjs/features/crash/NativeCrashFeature.js +42 -0
- package/lib/commonjs/features/crash/capture.js +48 -0
- package/lib/commonjs/features/crash/globalHandler.js +41 -0
- package/lib/commonjs/features/crash/index.js +14 -0
- package/lib/commonjs/features/crash/nativeCrashBridge.js +361 -0
- package/lib/commonjs/features/crash/rejectionHandler.js +180 -0
- package/lib/commonjs/features/crash/scrubCrash.js +158 -0
- package/lib/commonjs/features/engage/EngageAnchor.js +132 -0
- package/lib/commonjs/features/engage/EngageArchetypeRenderers.js +4292 -0
- package/lib/commonjs/features/engage/EngageInAppView.js +2670 -0
- package/lib/commonjs/features/engage/EngageInlinePlacement.js +133 -0
- package/lib/commonjs/features/engage/EngagePromptProvider.js +1063 -0
- package/lib/commonjs/features/engage/EngagePromptView.js +1252 -0
- package/lib/commonjs/features/engage/EngageTransport.js +608 -0
- package/lib/commonjs/features/engage/EngageVariantContent.js +1778 -0
- package/lib/commonjs/features/engage/__test-support__/reactNativeStub.js +54 -0
- package/lib/commonjs/features/engage/attachmentCapture.js +48 -0
- package/lib/commonjs/features/engage/captureVoiceAttachment.js +88 -0
- package/lib/commonjs/features/engage/engageCoachmarkTour.js +106 -0
- package/lib/commonjs/features/engage/engageGameLogic.js +308 -0
- package/lib/commonjs/features/engage/engageMediaSizing.js +60 -0
- package/lib/commonjs/features/engage/engageMultiStepSheet.js +43 -0
- package/lib/commonjs/features/engage/engageOutcome.js +118 -0
- package/lib/commonjs/features/engage/engagePushTokenBridge.js +72 -0
- package/lib/commonjs/features/engage/engageShakeBridge.js +62 -0
- package/lib/commonjs/features/engage/engageSignals.js +39 -0
- package/lib/commonjs/features/engage/engageStoreReviewBridge.js +49 -0
- package/lib/commonjs/features/engage/engageTestSeenStore.js +99 -0
- package/lib/commonjs/features/engage/engageThrottle.js +404 -0
- package/lib/commonjs/features/engage/engageTriggerEngine.js +365 -0
- package/lib/commonjs/features/engage/engageTypes.js +116 -0
- package/lib/commonjs/features/engage/engageVariantResolver.js +143 -0
- package/lib/commonjs/features/engage/engageWindowInsets.js +98 -0
- package/lib/commonjs/features/engage/gestureTriggerDetector.js +85 -0
- package/lib/commonjs/features/engage/transcription/TranscriptionProvider.js +55 -0
- package/lib/commonjs/features/install-referrer/installReferrer.js +113 -0
- package/lib/commonjs/features/journey/ScaleBunDebugRoot.js +1038 -0
- package/lib/commonjs/features/journey/ScaleBunFlatList.js +39 -0
- package/lib/commonjs/features/journey/ScaleBunImpression.js +113 -0
- package/lib/commonjs/features/journey/ScaleBunScrollView.js +102 -0
- package/lib/commonjs/features/journey/ScaleBunSectionList.js +39 -0
- package/lib/commonjs/features/journey/autoInstrumentScroll.js +161 -0
- package/lib/commonjs/features/journey/calibrationContext.js +42 -0
- package/lib/commonjs/features/journey/gestureBuffer.js +63 -0
- package/lib/commonjs/features/journey/gestureDetector.js +176 -0
- package/lib/commonjs/features/journey/impression.js +93 -0
- package/lib/commonjs/features/journey/interactionProtocol.js +168 -0
- package/lib/commonjs/features/journey/journeyManager.js +466 -0
- package/lib/commonjs/features/journey/journeyTypes.js +55 -0
- package/lib/commonjs/features/journey/nativeScroll.js +85 -0
- package/lib/commonjs/features/journey/navDetector.js +161 -0
- package/lib/commonjs/features/journey/screenshotHelper.js +136 -0
- package/lib/commonjs/features/journey/scrollContext.js +154 -0
- package/lib/commonjs/features/journey/targetGeometry.js +185 -0
- package/lib/commonjs/features/journey/targetRegistry.js +53 -0
- package/lib/commonjs/features/journey/touchTarget.js +249 -0
- package/lib/commonjs/features/journey/uiState.js +188 -0
- package/lib/commonjs/features/navigation/AutoScreenDetector.js +607 -0
- package/lib/commonjs/features/network/NetworkFeature.js +216 -0
- package/lib/commonjs/features/network/index.js +23 -0
- package/lib/commonjs/features/network/thirdParty.js +151 -0
- package/lib/commonjs/features/ota/OtaEventEmitter.js +129 -0
- package/lib/commonjs/features/ota/OtaOrchestrator.js +1309 -0
- package/lib/commonjs/features/ota/OtaTypes.js +6 -0
- package/lib/commonjs/features/ota/deviceAttributes.js +48 -0
- package/lib/commonjs/features/ota/environment.js +172 -0
- package/lib/commonjs/features/ota/geoCountry.js +93 -0
- package/lib/commonjs/features/ota/retry.js +96 -0
- package/lib/commonjs/features/ota/signedDownload.js +187 -0
- package/lib/commonjs/features/ota/useOtaUpdate.js +106 -0
- package/lib/commonjs/features/performance/PerformanceFeature.js +649 -0
- package/lib/commonjs/features/performance/collectors/AppLaunchCollector.js +122 -0
- package/lib/commonjs/features/performance/collectors/AutoScreenLoadCollector.js +232 -0
- package/lib/commonjs/features/performance/collectors/CustomTraceCollector.js +99 -0
- package/lib/commonjs/features/performance/collectors/FrameMetricsCollector.js +184 -0
- package/lib/commonjs/features/performance/collectors/JsStallCollector.js +165 -0
- package/lib/commonjs/features/performance/collectors/NetworkPerfCollector.js +59 -0
- package/lib/commonjs/features/performance/collectors/ScreenLoadCollector.js +78 -0
- package/lib/commonjs/features/performance/collectors/UiHangCollector.js +78 -0
- package/lib/commonjs/features/performance/config.js +58 -0
- package/lib/commonjs/features/performance/getNativePerformanceModule.js +44 -0
- package/lib/commonjs/features/performance/index.js +58 -0
- package/lib/commonjs/features/performance/metrics/MetricsEngine.js +148 -0
- package/lib/commonjs/features/performance/models.js +48 -0
- package/lib/commonjs/features/performance/transport/PerformanceTransport.js +168 -0
- package/lib/commonjs/features/profiler/ProfilerFeature.js +600 -0
- package/lib/commonjs/features/profiler/config.js +151 -0
- package/lib/commonjs/features/profiler/contracts.js +62 -0
- package/lib/commonjs/features/profiler/index.js +40 -0
- package/lib/commonjs/features/profiler/models.js +112 -0
- package/lib/commonjs/features/profiler/transport/ProfilerTransport.js +94 -0
- package/lib/commonjs/features/replay/bridge/adapters/bridgeAdapter.js +203 -0
- package/lib/commonjs/features/replay/bridge/eventEmitter.js +128 -0
- package/lib/commonjs/features/replay/bridge/nativeModule.js +95 -0
- package/lib/commonjs/features/replay/bridge/nativeQuota.js +121 -0
- package/lib/commonjs/features/replay/core/clock/clock.js +47 -0
- package/lib/commonjs/features/replay/core/config/defaults.js +88 -0
- package/lib/commonjs/features/replay/core/config/validator.js +109 -0
- package/lib/commonjs/features/replay/core/env/environmentDetector.js +28 -0
- package/lib/commonjs/features/replay/core/errors/safeCall.js +42 -0
- package/lib/commonjs/features/replay/core/ids/sessionId.js +53 -0
- package/lib/commonjs/features/replay/core/logger/logger.js +36 -0
- package/lib/commonjs/features/replay/core/privacy/privacyTypes.js +34 -0
- package/lib/commonjs/features/replay/core/queue/boundedQueue.js +71 -0
- package/lib/commonjs/features/replay/integrations/alert/alertInstrumentation.js +259 -0
- package/lib/commonjs/features/replay/integrations/logs/consoleIntegration.js +139 -0
- package/lib/commonjs/features/replay/integrations/network/networkAdapter.js +77 -0
- package/lib/commonjs/features/replay/integrations/react-navigation/navigationIntegration.js +88 -0
- package/lib/commonjs/features/replay/integrations/touch/ReplayRoot.js +103 -0
- package/lib/commonjs/features/replay/pipeline/batching/batchManager.js +119 -0
- package/lib/commonjs/features/replay/pipeline/envelope/envelopeBuilder.js +52 -0
- package/lib/commonjs/features/replay/pipeline/retry/retryPolicy.js +53 -0
- package/lib/commonjs/features/replay/pipeline/transport/transportTypes.js +67 -0
- package/lib/commonjs/features/replay/public/api.js +336 -0
- package/lib/commonjs/features/replay/public/enums.js +85 -0
- package/lib/commonjs/features/replay/public/types.js +6 -0
- package/lib/commonjs/features/replay/replay/breadcrumbs/breadcrumbCollector.js +119 -0
- package/lib/commonjs/features/replay/replay/frames/frameModels.js +40 -0
- package/lib/commonjs/features/replay/replay/session/sessionOrchestrator.js +279 -0
- package/lib/commonjs/features/replay/replay/timeline/timelineModels.js +120 -0
- package/lib/commonjs/features/replay/transport/BackendReplayTransport.js +175 -0
- package/lib/commonjs/features/replay/transport/CompositeReplayTransport.js +75 -0
- package/lib/commonjs/features/replay/transport/DesktopReplayTransport.js +124 -0
- package/lib/commonjs/features/replay/transport/ReplayTransport.js +2 -0
- package/lib/commonjs/features/session/BackendSessionAdapter.js +1915 -0
- package/lib/commonjs/features/session/DesktopSessionTransport.js +124 -0
- package/lib/commonjs/features/session/JourneyEventPipeline.js +244 -0
- package/lib/commonjs/features/session/ReplayCaptureManager.js +1253 -0
- package/lib/commonjs/features/session/ScrollTracker.js +147 -0
- package/lib/commonjs/features/session/SessionManager.js +1448 -0
- package/lib/commonjs/features/session/SyncDecisionEngine.js +95 -0
- package/lib/commonjs/features/session/frameLink.js +36 -0
- package/lib/commonjs/features/session/frameScrollState.js +41 -0
- package/lib/commonjs/features/session/index.js +84 -0
- package/lib/commonjs/features/session/nativeCapture.js +53 -0
- package/lib/commonjs/features/session/outboxFrameCapture.js +83 -0
- package/lib/commonjs/features/session/sessionTypes.js +230 -0
- package/lib/commonjs/features/session/viewportPlausibility.js +143 -0
- package/lib/commonjs/features/skan/skanBridge.js +54 -0
- package/lib/commonjs/features/skan/skanConversionManager.js +134 -0
- package/lib/commonjs/index.js +267 -0
- package/lib/commonjs/integrations/fetch/fetchInterceptor.js +174 -0
- package/lib/commonjs/integrations/navigation/screenTracker.js +2 -0
- package/lib/commonjs/integrations/network/bodyCapture.js +336 -0
- package/lib/commonjs/integrations/xhr/xhrInterceptor.js +224 -0
- package/lib/commonjs/metro/composeSourceMap.js +105 -0
- package/lib/commonjs/metro/featureModules.js +131 -0
- package/lib/commonjs/metro/index.js +230 -0
- package/lib/commonjs/metro/optionalDependencyStub.js +6 -0
- package/lib/commonjs/metro/optionalModules.js +31 -0
- package/lib/commonjs/metro/serializerCompose.js +161 -0
- package/lib/commonjs/pipeline/dispatcher/dispatcher.js +28 -0
- package/lib/commonjs/pipeline/envelope/envelope.js +17 -0
- package/lib/commonjs/pipeline/envelope/serializer.js +14 -0
- package/lib/commonjs/pipeline/processors/dedupe.js +2 -0
- package/lib/commonjs/pipeline/processors/enrich.js +16 -0
- package/lib/commonjs/pipeline/processors/sampling.js +2 -0
- package/lib/commonjs/pipeline/processors/sanitize.js +28 -0
- package/lib/commonjs/pipeline/queue/flushQueue.js +107 -0
- package/lib/commonjs/pipeline/queue/memoryQueue.js +31 -0
- package/lib/commonjs/pipeline/queue/persistentQueue.js +342 -0
- package/lib/commonjs/pipeline/queue/queuePolicy.js +2 -0
- package/lib/commonjs/pipeline/scheduler/flushScheduler.js +45 -0
- package/lib/commonjs/pipeline/scheduler/retryPolicy.js +15 -0
- package/lib/commonjs/public/ScaleBunErrorBoundary.js +91 -0
- package/lib/commonjs/public/ScaleBunFacade.js +2044 -0
- package/lib/commonjs/public/ScaleBunProvider.js +62 -0
- package/lib/commonjs/public/typedTracker.js +79 -0
- package/lib/commonjs/public/types.js +6 -0
- package/lib/commonjs/push/PushManager.js +270 -0
- package/lib/commonjs/push/adapters/ManualAdapter.js +86 -0
- package/lib/commonjs/push/adapters/NativeBridgeAdapter.js +131 -0
- package/lib/commonjs/push/adapters/NoopAdapter.js +45 -0
- package/lib/commonjs/push/adapters/RNFirebaseMessagingAdapter.js +248 -0
- package/lib/commonjs/push/adapters/loadMessaging.js +29 -0
- package/lib/commonjs/push/adapters/loadNotifee.js +36 -0
- package/lib/commonjs/push/adapters/selectAdapter.js +153 -0
- package/lib/commonjs/push/detect/capabilities.js +136 -0
- package/lib/commonjs/push/index.js +33 -0
- package/lib/commonjs/push/native/nativeNotifications.js +168 -0
- package/lib/commonjs/push/types.js +2 -0
- package/lib/commonjs/specs/NativeReplaySdk.js +28 -0
- package/lib/commonjs/specs/NativeScaleBunCrash.js +30 -0
- package/lib/commonjs/specs/NativeScaleBunInstallReferrer.js +24 -0
- package/lib/commonjs/specs/NativeScaleBunOta.js +32 -0
- package/lib/commonjs/specs/NativeScaleBunPerformance.js +25 -0
- package/lib/commonjs/specs/NativeScaleBunProfiler.js +26 -0
- package/lib/commonjs/specs/NativeScaleBunStorage.js +40 -0
- package/lib/commonjs/storage/StorageBackend.js +240 -0
- package/lib/commonjs/storage/db/sqlite.js +2 -0
- package/lib/commonjs/storage/files/fileStore.js +2 -0
- package/lib/commonjs/transport/auth/sdkSession.js +28 -0
- package/lib/commonjs/transport/backoff/exponentialJitter.js +16 -0
- package/lib/commonjs/transport/http/endpoints.js +46 -0
- package/lib/commonjs/transport/http/httpClient.js +72 -0
- package/lib/commonjs/transport/rateLimit/retryAfter.js +2 -0
- package/lib/commonjs/transport/signing/requestSigner.js +2 -0
- package/lib/commonjs/vendor/protocol/index.js +327 -0
- package/lib/commonjs/vendor/protocol/internal/validation.js +429 -0
- package/lib/commonjs/vendor/sdk-contracts/bridge.js +2 -0
- package/lib/commonjs/vendor/sdk-contracts/config.js +2 -0
- package/lib/commonjs/vendor/sdk-contracts/events.js +2 -0
- package/lib/commonjs/vendor/sdk-contracts/features.js +2 -0
- package/lib/commonjs/vendor/sdk-contracts/index.js +2 -0
- package/lib/commonjs/vendor/sdk-contracts/platform.js +2 -0
- package/lib/commonjs/vendor/sdk-contracts/session-boundary.js +6 -0
- package/lib/commonjs/vendor/sdk-contracts/session.js +2 -0
- package/lib/commonjs/vendor/vendor.manifest +10 -0
- package/lib/module/analytics/EventTracker.js +592 -0
- package/lib/module/analytics/automaticEvents.js +63 -0
- package/lib/module/analytics/batching.js +55 -0
- package/lib/module/analytics/eventLane.js +47 -0
- package/lib/module/analytics/revenue.js +117 -0
- package/lib/module/analytics/subscription.js +86 -0
- package/lib/module/bootstrap/FeatureRegistry.js +79 -0
- package/lib/module/bootstrap/SDKBootstrapper.js +742 -0
- package/lib/module/bucketing/fnv1a32.js +37 -0
- package/lib/module/compat/codepush.js +147 -0
- package/lib/module/config/ConfigManager.js +230 -0
- package/lib/module/config/configTypes.js +10 -0
- package/lib/module/config/otaPolicyState.js +76 -0
- package/lib/module/config/quotaState.js +423 -0
- package/lib/module/core/architecture.js +38 -0
- package/lib/module/core/clock/now.js +7 -0
- package/lib/module/core/config/defaults.js +2 -0
- package/lib/module/core/config/schema.js +506 -0
- package/lib/module/core/constants/endpoints.js +13 -0
- package/lib/module/core/constants/protocol.js +25 -0
- package/lib/module/core/constants/timings.js +26 -0
- package/lib/module/core/constants/version.js +12 -0
- package/lib/module/core/context/app.js +2 -0
- package/lib/module/core/context/device.js +143 -0
- package/lib/module/core/context/session.js +2 -0
- package/lib/module/core/context/user.js +2 -0
- package/lib/module/core/contracts/IFeature.js +2 -0
- package/lib/module/core/di/container.js +22 -0
- package/lib/module/core/encoding/base64.js +44 -0
- package/lib/module/core/id/deviceId.js +45 -0
- package/lib/module/core/id/installationId.js +50 -0
- package/lib/module/core/id/sessionId.js +2 -0
- package/lib/module/core/lifecycle/appLifecycle.js +25 -0
- package/lib/module/core/lifecycle/crashSafe.js +26 -0
- package/lib/module/core/logger/errorClassification.js +96 -0
- package/lib/module/core/logger/internalLogger.js +59 -0
- package/lib/module/core/logger/levels.js +2 -0
- package/lib/module/crypto/hmacSha256.js +126 -0
- package/lib/module/debug/bootstrap.js +393 -0
- package/lib/module/debug/commands.js +726 -0
- package/lib/module/debug/configBuilder.js +63 -0
- package/lib/module/debug/exportBridge.js +148 -0
- package/lib/module/debug/hostResolver.js +37 -0
- package/lib/module/debug/index.js +13 -0
- package/lib/module/debug/metrics.js +33 -0
- package/lib/module/debug/perf.js +180 -0
- package/lib/module/debug/perfWiring.js +102 -0
- package/lib/module/debug/protocol.js +19 -0
- package/lib/module/debug/redaction.js +411 -0
- package/lib/module/debug/replayWiring.js +139 -0
- package/lib/module/debug/screenTracking.js +53 -0
- package/lib/module/debug/sessionWiring.js +43 -0
- package/lib/module/debug/stream.js +536 -0
- package/lib/module/debug/transport.js +362 -0
- package/lib/module/features/bugreport/BugReportFeature.js +105 -0
- package/lib/module/features/bugreport/buildPayload.js +91 -0
- package/lib/module/features/bugreport/index.js +2 -0
- package/lib/module/features/crash/CrashFeature.js +79 -0
- package/lib/module/features/crash/CrashReporter.js +151 -0
- package/lib/module/features/crash/NativeCrashFeature.js +36 -0
- package/lib/module/features/crash/capture.js +42 -0
- package/lib/module/features/crash/globalHandler.js +36 -0
- package/lib/module/features/crash/index.js +8 -0
- package/lib/module/features/crash/nativeCrashBridge.js +350 -0
- package/lib/module/features/crash/rejectionHandler.js +171 -0
- package/lib/module/features/crash/scrubCrash.js +149 -0
- package/lib/module/features/engage/EngageAnchor.js +121 -0
- package/lib/module/features/engage/EngageArchetypeRenderers.js +4264 -0
- package/lib/module/features/engage/EngageInAppView.js +2656 -0
- package/lib/module/features/engage/EngageInlinePlacement.js +123 -0
- package/lib/module/features/engage/EngagePromptProvider.js +1054 -0
- package/lib/module/features/engage/EngagePromptView.js +1243 -0
- package/lib/module/features/engage/EngageTransport.js +602 -0
- package/lib/module/features/engage/EngageVariantContent.js +1771 -0
- package/lib/module/features/engage/__test-support__/reactNativeStub.js +52 -0
- package/lib/module/features/engage/attachmentCapture.js +42 -0
- package/lib/module/features/engage/captureVoiceAttachment.js +80 -0
- package/lib/module/features/engage/engageCoachmarkTour.js +94 -0
- package/lib/module/features/engage/engageGameLogic.js +287 -0
- package/lib/module/features/engage/engageMediaSizing.js +51 -0
- package/lib/module/features/engage/engageMultiStepSheet.js +35 -0
- package/lib/module/features/engage/engageOutcome.js +108 -0
- package/lib/module/features/engage/engagePushTokenBridge.js +65 -0
- package/lib/module/features/engage/engageShakeBridge.js +56 -0
- package/lib/module/features/engage/engageSignals.js +34 -0
- package/lib/module/features/engage/engageStoreReviewBridge.js +43 -0
- package/lib/module/features/engage/engageTestSeenStore.js +92 -0
- package/lib/module/features/engage/engageThrottle.js +390 -0
- package/lib/module/features/engage/engageTriggerEngine.js +360 -0
- package/lib/module/features/engage/engageTypes.js +110 -0
- package/lib/module/features/engage/engageVariantResolver.js +135 -0
- package/lib/module/features/engage/engageWindowInsets.js +89 -0
- package/lib/module/features/engage/gestureTriggerDetector.js +80 -0
- package/lib/module/features/engage/transcription/TranscriptionProvider.js +46 -0
- package/lib/module/features/install-referrer/installReferrer.js +104 -0
- package/lib/module/features/journey/ScaleBunDebugRoot.js +1030 -0
- package/lib/module/features/journey/ScaleBunFlatList.js +32 -0
- package/lib/module/features/journey/ScaleBunImpression.js +105 -0
- package/lib/module/features/journey/ScaleBunScrollView.js +95 -0
- package/lib/module/features/journey/ScaleBunSectionList.js +32 -0
- package/lib/module/features/journey/autoInstrumentScroll.js +155 -0
- package/lib/module/features/journey/calibrationContext.js +35 -0
- package/lib/module/features/journey/gestureBuffer.js +56 -0
- package/lib/module/features/journey/gestureDetector.js +169 -0
- package/lib/module/features/journey/impression.js +84 -0
- package/lib/module/features/journey/interactionProtocol.js +158 -0
- package/lib/module/features/journey/journeyManager.js +459 -0
- package/lib/module/features/journey/journeyTypes.js +49 -0
- package/lib/module/features/journey/nativeScroll.js +77 -0
- package/lib/module/features/journey/navDetector.js +154 -0
- package/lib/module/features/journey/screenshotHelper.js +129 -0
- package/lib/module/features/journey/scrollContext.js +142 -0
- package/lib/module/features/journey/targetGeometry.js +175 -0
- package/lib/module/features/journey/targetRegistry.js +44 -0
- package/lib/module/features/journey/touchTarget.js +241 -0
- package/lib/module/features/journey/uiState.js +176 -0
- package/lib/module/features/navigation/AutoScreenDetector.js +601 -0
- package/lib/module/features/network/NetworkFeature.js +209 -0
- package/lib/module/features/network/index.js +17 -0
- package/lib/module/features/network/thirdParty.js +142 -0
- package/lib/module/features/ota/OtaEventEmitter.js +123 -0
- package/lib/module/features/ota/OtaOrchestrator.js +1302 -0
- package/lib/module/features/ota/OtaTypes.js +2 -0
- package/lib/module/features/ota/deviceAttributes.js +43 -0
- package/lib/module/features/ota/environment.js +165 -0
- package/lib/module/features/ota/geoCountry.js +86 -0
- package/lib/module/features/ota/retry.js +87 -0
- package/lib/module/features/ota/signedDownload.js +174 -0
- package/lib/module/features/ota/useOtaUpdate.js +100 -0
- package/lib/module/features/performance/PerformanceFeature.js +643 -0
- package/lib/module/features/performance/collectors/AppLaunchCollector.js +115 -0
- package/lib/module/features/performance/collectors/AutoScreenLoadCollector.js +225 -0
- package/lib/module/features/performance/collectors/CustomTraceCollector.js +92 -0
- package/lib/module/features/performance/collectors/FrameMetricsCollector.js +177 -0
- package/lib/module/features/performance/collectors/JsStallCollector.js +158 -0
- package/lib/module/features/performance/collectors/NetworkPerfCollector.js +52 -0
- package/lib/module/features/performance/collectors/ScreenLoadCollector.js +71 -0
- package/lib/module/features/performance/collectors/UiHangCollector.js +71 -0
- package/lib/module/features/performance/config.js +51 -0
- package/lib/module/features/performance/getNativePerformanceModule.js +39 -0
- package/lib/module/features/performance/index.js +9 -0
- package/lib/module/features/performance/metrics/MetricsEngine.js +139 -0
- package/lib/module/features/performance/models.js +42 -0
- package/lib/module/features/performance/transport/PerformanceTransport.js +162 -0
- package/lib/module/features/profiler/ProfilerFeature.js +594 -0
- package/lib/module/features/profiler/config.js +143 -0
- package/lib/module/features/profiler/contracts.js +56 -0
- package/lib/module/features/profiler/index.js +11 -0
- package/lib/module/features/profiler/models.js +106 -0
- package/lib/module/features/profiler/transport/ProfilerTransport.js +87 -0
- package/lib/module/features/replay/bridge/adapters/bridgeAdapter.js +198 -0
- package/lib/module/features/replay/bridge/eventEmitter.js +122 -0
- package/lib/module/features/replay/bridge/nativeModule.js +89 -0
- package/lib/module/features/replay/bridge/nativeQuota.js +109 -0
- package/lib/module/features/replay/core/clock/clock.js +37 -0
- package/lib/module/features/replay/core/config/defaults.js +82 -0
- package/lib/module/features/replay/core/config/validator.js +103 -0
- package/lib/module/features/replay/core/env/environmentDetector.js +22 -0
- package/lib/module/features/replay/core/errors/safeCall.js +35 -0
- package/lib/module/features/replay/core/ids/sessionId.js +44 -0
- package/lib/module/features/replay/core/logger/logger.js +29 -0
- package/lib/module/features/replay/core/privacy/privacyTypes.js +27 -0
- package/lib/module/features/replay/core/queue/boundedQueue.js +64 -0
- package/lib/module/features/replay/integrations/alert/alertInstrumentation.js +252 -0
- package/lib/module/features/replay/integrations/logs/consoleIntegration.js +131 -0
- package/lib/module/features/replay/integrations/network/networkAdapter.js +70 -0
- package/lib/module/features/replay/integrations/react-navigation/navigationIntegration.js +82 -0
- package/lib/module/features/replay/integrations/touch/ReplayRoot.js +96 -0
- package/lib/module/features/replay/pipeline/batching/batchManager.js +113 -0
- package/lib/module/features/replay/pipeline/envelope/envelopeBuilder.js +43 -0
- package/lib/module/features/replay/pipeline/retry/retryPolicy.js +45 -0
- package/lib/module/features/replay/pipeline/transport/transportTypes.js +60 -0
- package/lib/module/features/replay/public/api.js +332 -0
- package/lib/module/features/replay/public/enums.js +85 -0
- package/lib/module/features/replay/public/types.js +2 -0
- package/lib/module/features/replay/replay/breadcrumbs/breadcrumbCollector.js +113 -0
- package/lib/module/features/replay/replay/frames/frameModels.js +33 -0
- package/lib/module/features/replay/replay/session/sessionOrchestrator.js +272 -0
- package/lib/module/features/replay/replay/timeline/timelineModels.js +108 -0
- package/lib/module/features/replay/transport/BackendReplayTransport.js +169 -0
- package/lib/module/features/replay/transport/CompositeReplayTransport.js +68 -0
- package/lib/module/features/replay/transport/DesktopReplayTransport.js +119 -0
- package/lib/module/features/replay/transport/ReplayTransport.js +2 -0
- package/lib/module/features/session/BackendSessionAdapter.js +1908 -0
- package/lib/module/features/session/DesktopSessionTransport.js +118 -0
- package/lib/module/features/session/JourneyEventPipeline.js +239 -0
- package/lib/module/features/session/ReplayCaptureManager.js +1248 -0
- package/lib/module/features/session/ScrollTracker.js +141 -0
- package/lib/module/features/session/SessionManager.js +1443 -0
- package/lib/module/features/session/SyncDecisionEngine.js +88 -0
- package/lib/module/features/session/frameLink.js +29 -0
- package/lib/module/features/session/frameScrollState.js +34 -0
- package/lib/module/features/session/index.js +35 -0
- package/lib/module/features/session/nativeCapture.js +45 -0
- package/lib/module/features/session/outboxFrameCapture.js +77 -0
- package/lib/module/features/session/sessionTypes.js +226 -0
- package/lib/module/features/session/viewportPlausibility.js +137 -0
- package/lib/module/features/skan/skanBridge.js +48 -0
- package/lib/module/features/skan/skanConversionManager.js +128 -0
- package/lib/module/index.js +80 -0
- package/lib/module/integrations/fetch/fetchInterceptor.js +169 -0
- package/lib/module/integrations/navigation/screenTracker.js +2 -0
- package/lib/module/integrations/network/bodyCapture.js +320 -0
- package/lib/module/integrations/xhr/xhrInterceptor.js +219 -0
- package/lib/module/metro/composeSourceMap.js +97 -0
- package/lib/module/metro/featureModules.js +123 -0
- package/lib/module/metro/index.js +218 -0
- package/lib/module/metro/optionalDependencyStub.js +14 -0
- package/lib/module/metro/optionalModules.js +25 -0
- package/lib/module/metro/serializerCompose.js +152 -0
- package/lib/module/pipeline/dispatcher/dispatcher.js +21 -0
- package/lib/module/pipeline/envelope/envelope.js +11 -0
- package/lib/module/pipeline/envelope/serializer.js +8 -0
- package/lib/module/pipeline/processors/dedupe.js +2 -0
- package/lib/module/pipeline/processors/enrich.js +10 -0
- package/lib/module/pipeline/processors/sampling.js +2 -0
- package/lib/module/pipeline/processors/sanitize.js +22 -0
- package/lib/module/pipeline/queue/flushQueue.js +102 -0
- package/lib/module/pipeline/queue/memoryQueue.js +24 -0
- package/lib/module/pipeline/queue/persistentQueue.js +336 -0
- package/lib/module/pipeline/queue/queuePolicy.js +2 -0
- package/lib/module/pipeline/scheduler/flushScheduler.js +38 -0
- package/lib/module/pipeline/scheduler/retryPolicy.js +9 -0
- package/lib/module/public/ScaleBunErrorBoundary.js +82 -0
- package/lib/module/public/ScaleBunFacade.js +2038 -0
- package/lib/module/public/ScaleBunProvider.js +54 -0
- package/lib/module/public/typedTracker.js +72 -0
- package/lib/module/public/types.js +2 -0
- package/lib/module/push/PushManager.js +263 -0
- package/lib/module/push/adapters/ManualAdapter.js +79 -0
- package/lib/module/push/adapters/NativeBridgeAdapter.js +124 -0
- package/lib/module/push/adapters/NoopAdapter.js +38 -0
- package/lib/module/push/adapters/RNFirebaseMessagingAdapter.js +241 -0
- package/lib/module/push/adapters/loadMessaging.js +23 -0
- package/lib/module/push/adapters/loadNotifee.js +30 -0
- package/lib/module/push/adapters/selectAdapter.js +147 -0
- package/lib/module/push/detect/capabilities.js +130 -0
- package/lib/module/push/index.js +9 -0
- package/lib/module/push/native/nativeNotifications.js +155 -0
- package/lib/module/push/types.js +2 -0
- package/lib/module/specs/NativeReplaySdk.js +24 -0
- package/lib/module/specs/NativeScaleBunCrash.js +26 -0
- package/lib/module/specs/NativeScaleBunInstallReferrer.js +20 -0
- package/lib/module/specs/NativeScaleBunOta.js +28 -0
- package/lib/module/specs/NativeScaleBunPerformance.js +21 -0
- package/lib/module/specs/NativeScaleBunProfiler.js +22 -0
- package/lib/module/specs/NativeScaleBunStorage.js +36 -0
- package/lib/module/storage/StorageBackend.js +231 -0
- package/lib/module/storage/db/sqlite.js +2 -0
- package/lib/module/storage/files/fileStore.js +2 -0
- package/lib/module/transport/auth/sdkSession.js +21 -0
- package/lib/module/transport/backoff/exponentialJitter.js +10 -0
- package/lib/module/transport/http/endpoints.js +40 -0
- package/lib/module/transport/http/httpClient.js +64 -0
- package/lib/module/transport/rateLimit/retryAfter.js +2 -0
- package/lib/module/transport/signing/requestSigner.js +2 -0
- package/lib/module/vendor/protocol/index.js +312 -0
- package/lib/module/vendor/protocol/internal/validation.js +423 -0
- package/lib/module/vendor/sdk-contracts/bridge.js +2 -0
- package/lib/module/vendor/sdk-contracts/config.js +2 -0
- package/lib/module/vendor/sdk-contracts/events.js +2 -0
- package/lib/module/vendor/sdk-contracts/features.js +2 -0
- package/lib/module/vendor/sdk-contracts/index.js +2 -0
- package/lib/module/vendor/sdk-contracts/platform.js +2 -0
- package/lib/module/vendor/sdk-contracts/session-boundary.js +2 -0
- package/lib/module/vendor/sdk-contracts/session.js +2 -0
- package/lib/module/vendor/vendor.manifest +10 -0
- package/lib/typescript/analytics/EventTracker.d.ts +197 -0
- package/lib/typescript/analytics/automaticEvents.d.ts +30 -0
- package/lib/typescript/analytics/batching.d.ts +33 -0
- package/lib/typescript/analytics/eventLane.d.ts +22 -0
- package/lib/typescript/analytics/revenue.d.ts +94 -0
- package/lib/typescript/analytics/subscription.d.ts +35 -0
- package/lib/typescript/bootstrap/FeatureRegistry.d.ts +36 -0
- package/lib/typescript/bootstrap/SDKBootstrapper.d.ts +82 -0
- package/lib/typescript/bucketing/fnv1a32.d.ts +20 -0
- package/lib/typescript/compat/codepush.d.ts +76 -0
- package/lib/typescript/config/ConfigManager.d.ts +82 -0
- package/lib/typescript/config/configTypes.d.ts +73 -0
- package/lib/typescript/config/otaPolicyState.d.ts +55 -0
- package/lib/typescript/config/quotaState.d.ts +155 -0
- package/lib/typescript/core/architecture.d.ts +29 -0
- package/lib/typescript/core/clock/now.d.ts +5 -0
- package/lib/typescript/core/config/defaults.d.ts +1 -0
- package/lib/typescript/core/config/schema.d.ts +107 -0
- package/lib/typescript/core/constants/endpoints.d.ts +13 -0
- package/lib/typescript/core/constants/protocol.d.ts +23 -0
- package/lib/typescript/core/constants/timings.d.ts +22 -0
- package/lib/typescript/core/constants/version.d.ts +12 -0
- package/lib/typescript/core/context/app.d.ts +1 -0
- package/lib/typescript/core/context/device.d.ts +61 -0
- package/lib/typescript/core/context/session.d.ts +1 -0
- package/lib/typescript/core/context/user.d.ts +24 -0
- package/lib/typescript/core/contracts/IFeature.d.ts +44 -0
- package/lib/typescript/core/di/container.d.ts +9 -0
- package/lib/typescript/core/encoding/base64.d.ts +13 -0
- package/lib/typescript/core/id/deviceId.d.ts +9 -0
- package/lib/typescript/core/id/installationId.d.ts +20 -0
- package/lib/typescript/core/id/sessionId.d.ts +1 -0
- package/lib/typescript/core/lifecycle/appLifecycle.d.ts +14 -0
- package/lib/typescript/core/lifecycle/crashSafe.d.ts +7 -0
- package/lib/typescript/core/logger/errorClassification.d.ts +46 -0
- package/lib/typescript/core/logger/internalLogger.d.ts +24 -0
- package/lib/typescript/core/logger/levels.d.ts +1 -0
- package/lib/typescript/crypto/hmacSha256.d.ts +10 -0
- package/lib/typescript/debug/bootstrap.d.ts +148 -0
- package/lib/typescript/debug/commands.d.ts +77 -0
- package/lib/typescript/debug/configBuilder.d.ts +13 -0
- package/lib/typescript/debug/exportBridge.d.ts +27 -0
- package/lib/typescript/debug/hostResolver.d.ts +14 -0
- package/lib/typescript/debug/index.d.ts +12 -0
- package/lib/typescript/debug/metrics.d.ts +2 -0
- package/lib/typescript/debug/perf.d.ts +43 -0
- package/lib/typescript/debug/perfWiring.d.ts +33 -0
- package/lib/typescript/debug/protocol.d.ts +15 -0
- package/lib/typescript/debug/redaction.d.ts +69 -0
- package/lib/typescript/debug/replayWiring.d.ts +25 -0
- package/lib/typescript/debug/screenTracking.d.ts +16 -0
- package/lib/typescript/debug/sessionWiring.d.ts +16 -0
- package/lib/typescript/debug/stream.d.ts +77 -0
- package/lib/typescript/debug/transport.d.ts +110 -0
- package/lib/typescript/features/bugreport/BugReportFeature.d.ts +59 -0
- package/lib/typescript/features/bugreport/buildPayload.d.ts +51 -0
- package/lib/typescript/features/bugreport/index.d.ts +1 -0
- package/lib/typescript/features/crash/CrashFeature.d.ts +21 -0
- package/lib/typescript/features/crash/CrashReporter.d.ts +107 -0
- package/lib/typescript/features/crash/NativeCrashFeature.d.ts +17 -0
- package/lib/typescript/features/crash/capture.d.ts +22 -0
- package/lib/typescript/features/crash/globalHandler.d.ts +14 -0
- package/lib/typescript/features/crash/index.d.ts +2 -0
- package/lib/typescript/features/crash/nativeCrashBridge.d.ts +43 -0
- package/lib/typescript/features/crash/rejectionHandler.d.ts +65 -0
- package/lib/typescript/features/crash/scrubCrash.d.ts +35 -0
- package/lib/typescript/features/engage/EngageAnchor.d.ts +36 -0
- package/lib/typescript/features/engage/EngageArchetypeRenderers.d.ts +66 -0
- package/lib/typescript/features/engage/EngageInAppView.d.ts +164 -0
- package/lib/typescript/features/engage/EngageInlinePlacement.d.ts +42 -0
- package/lib/typescript/features/engage/EngagePromptProvider.d.ts +233 -0
- package/lib/typescript/features/engage/EngagePromptView.d.ts +97 -0
- package/lib/typescript/features/engage/EngageTransport.d.ts +158 -0
- package/lib/typescript/features/engage/EngageVariantContent.d.ts +12 -0
- package/lib/typescript/features/engage/attachmentCapture.d.ts +17 -0
- package/lib/typescript/features/engage/captureVoiceAttachment.d.ts +31 -0
- package/lib/typescript/features/engage/engageCoachmarkTour.d.ts +18 -0
- package/lib/typescript/features/engage/engageGameLogic.d.ts +34 -0
- package/lib/typescript/features/engage/engageMediaSizing.d.ts +23 -0
- package/lib/typescript/features/engage/engageMultiStepSheet.d.ts +25 -0
- package/lib/typescript/features/engage/engageOutcome.d.ts +38 -0
- package/lib/typescript/features/engage/engagePushTokenBridge.d.ts +30 -0
- package/lib/typescript/features/engage/engageShakeBridge.d.ts +20 -0
- package/lib/typescript/features/engage/engageSignals.d.ts +13 -0
- package/lib/typescript/features/engage/engageStoreReviewBridge.d.ts +23 -0
- package/lib/typescript/features/engage/engageTestSeenStore.d.ts +46 -0
- package/lib/typescript/features/engage/engageThrottle.d.ts +180 -0
- package/lib/typescript/features/engage/engageTriggerEngine.d.ts +16 -0
- package/lib/typescript/features/engage/engageTypes.d.ts +576 -0
- package/lib/typescript/features/engage/engageVariantResolver.d.ts +46 -0
- package/lib/typescript/features/engage/engageWindowInsets.d.ts +25 -0
- package/lib/typescript/features/engage/gestureTriggerDetector.d.ts +20 -0
- package/lib/typescript/features/engage/transcription/TranscriptionProvider.d.ts +51 -0
- package/lib/typescript/features/install-referrer/installReferrer.d.ts +39 -0
- package/lib/typescript/features/journey/ScaleBunDebugRoot.d.ts +49 -0
- package/lib/typescript/features/journey/ScaleBunFlatList.d.ts +20 -0
- package/lib/typescript/features/journey/ScaleBunImpression.d.ts +30 -0
- package/lib/typescript/features/journey/ScaleBunScrollView.d.ts +25 -0
- package/lib/typescript/features/journey/ScaleBunSectionList.d.ts +20 -0
- package/lib/typescript/features/journey/autoInstrumentScroll.d.ts +46 -0
- package/lib/typescript/features/journey/calibrationContext.d.ts +26 -0
- package/lib/typescript/features/journey/gestureBuffer.d.ts +27 -0
- package/lib/typescript/features/journey/gestureDetector.d.ts +40 -0
- package/lib/typescript/features/journey/impression.d.ts +48 -0
- package/lib/typescript/features/journey/interactionProtocol.d.ts +159 -0
- package/lib/typescript/features/journey/journeyManager.d.ts +62 -0
- package/lib/typescript/features/journey/journeyTypes.d.ts +161 -0
- package/lib/typescript/features/journey/nativeScroll.d.ts +47 -0
- package/lib/typescript/features/journey/navDetector.d.ts +42 -0
- package/lib/typescript/features/journey/screenshotHelper.d.ts +33 -0
- package/lib/typescript/features/journey/scrollContext.d.ts +49 -0
- package/lib/typescript/features/journey/targetGeometry.d.ts +126 -0
- package/lib/typescript/features/journey/targetRegistry.d.ts +28 -0
- package/lib/typescript/features/journey/touchTarget.d.ts +174 -0
- package/lib/typescript/features/journey/uiState.d.ts +93 -0
- package/lib/typescript/features/navigation/AutoScreenDetector.d.ts +196 -0
- package/lib/typescript/features/network/NetworkFeature.d.ts +43 -0
- package/lib/typescript/features/network/index.d.ts +46 -0
- package/lib/typescript/features/network/thirdParty.d.ts +95 -0
- package/lib/typescript/features/ota/OtaEventEmitter.d.ts +93 -0
- package/lib/typescript/features/ota/OtaOrchestrator.d.ts +287 -0
- package/lib/typescript/features/ota/OtaTypes.d.ts +193 -0
- package/lib/typescript/features/ota/deviceAttributes.d.ts +18 -0
- package/lib/typescript/features/ota/environment.d.ts +99 -0
- package/lib/typescript/features/ota/geoCountry.d.ts +14 -0
- package/lib/typescript/features/ota/retry.d.ts +53 -0
- package/lib/typescript/features/ota/signedDownload.d.ts +81 -0
- package/lib/typescript/features/ota/useOtaUpdate.d.ts +72 -0
- package/lib/typescript/features/performance/PerformanceFeature.d.ts +132 -0
- package/lib/typescript/features/performance/collectors/AppLaunchCollector.d.ts +27 -0
- package/lib/typescript/features/performance/collectors/AutoScreenLoadCollector.d.ts +70 -0
- package/lib/typescript/features/performance/collectors/CustomTraceCollector.d.ts +35 -0
- package/lib/typescript/features/performance/collectors/FrameMetricsCollector.d.ts +74 -0
- package/lib/typescript/features/performance/collectors/JsStallCollector.d.ts +45 -0
- package/lib/typescript/features/performance/collectors/NetworkPerfCollector.d.ts +23 -0
- package/lib/typescript/features/performance/collectors/ScreenLoadCollector.d.ts +30 -0
- package/lib/typescript/features/performance/collectors/UiHangCollector.d.ts +21 -0
- package/lib/typescript/features/performance/config.d.ts +73 -0
- package/lib/typescript/features/performance/getNativePerformanceModule.d.ts +2 -0
- package/lib/typescript/features/performance/index.d.ts +10 -0
- package/lib/typescript/features/performance/metrics/MetricsEngine.d.ts +69 -0
- package/lib/typescript/features/performance/models.d.ts +169 -0
- package/lib/typescript/features/performance/transport/PerformanceTransport.d.ts +40 -0
- package/lib/typescript/features/profiler/ProfilerFeature.d.ts +92 -0
- package/lib/typescript/features/profiler/config.d.ts +85 -0
- package/lib/typescript/features/profiler/contracts.d.ts +142 -0
- package/lib/typescript/features/profiler/index.d.ts +13 -0
- package/lib/typescript/features/profiler/models.d.ts +332 -0
- package/lib/typescript/features/profiler/transport/ProfilerTransport.d.ts +30 -0
- package/lib/typescript/features/replay/bridge/adapters/bridgeAdapter.d.ts +52 -0
- package/lib/typescript/features/replay/bridge/eventEmitter.d.ts +100 -0
- package/lib/typescript/features/replay/bridge/nativeModule.d.ts +166 -0
- package/lib/typescript/features/replay/bridge/nativeQuota.d.ts +26 -0
- package/lib/typescript/features/replay/core/clock/clock.d.ts +16 -0
- package/lib/typescript/features/replay/core/config/defaults.d.ts +28 -0
- package/lib/typescript/features/replay/core/config/validator.d.ts +13 -0
- package/lib/typescript/features/replay/core/env/environmentDetector.d.ts +14 -0
- package/lib/typescript/features/replay/core/errors/safeCall.d.ts +12 -0
- package/lib/typescript/features/replay/core/ids/sessionId.d.ts +15 -0
- package/lib/typescript/features/replay/core/logger/logger.d.ts +15 -0
- package/lib/typescript/features/replay/core/privacy/privacyTypes.d.ts +24 -0
- package/lib/typescript/features/replay/core/queue/boundedQueue.d.ts +29 -0
- package/lib/typescript/features/replay/integrations/alert/alertInstrumentation.d.ts +48 -0
- package/lib/typescript/features/replay/integrations/logs/consoleIntegration.d.ts +18 -0
- package/lib/typescript/features/replay/integrations/network/networkAdapter.d.ts +31 -0
- package/lib/typescript/features/replay/integrations/react-navigation/navigationIntegration.d.ts +25 -0
- package/lib/typescript/features/replay/integrations/touch/ReplayRoot.d.ts +26 -0
- package/lib/typescript/features/replay/pipeline/batching/batchManager.d.ts +47 -0
- package/lib/typescript/features/replay/pipeline/envelope/envelopeBuilder.d.ts +12 -0
- package/lib/typescript/features/replay/pipeline/retry/retryPolicy.d.ts +17 -0
- package/lib/typescript/features/replay/pipeline/transport/transportTypes.d.ts +40 -0
- package/lib/typescript/features/replay/public/api.d.ts +83 -0
- package/lib/typescript/features/replay/public/enums.d.ts +73 -0
- package/lib/typescript/features/replay/public/types.d.ts +249 -0
- package/lib/typescript/features/replay/replay/breadcrumbs/breadcrumbCollector.d.ts +26 -0
- package/lib/typescript/features/replay/replay/frames/frameModels.d.ts +23 -0
- package/lib/typescript/features/replay/replay/session/sessionOrchestrator.d.ts +53 -0
- package/lib/typescript/features/replay/replay/timeline/timelineModels.d.ts +29 -0
- package/lib/typescript/features/replay/transport/BackendReplayTransport.d.ts +45 -0
- package/lib/typescript/features/replay/transport/CompositeReplayTransport.d.ts +22 -0
- package/lib/typescript/features/replay/transport/DesktopReplayTransport.d.ts +35 -0
- package/lib/typescript/features/replay/transport/ReplayTransport.d.ts +72 -0
- package/lib/typescript/features/session/BackendSessionAdapter.d.ts +528 -0
- package/lib/typescript/features/session/DesktopSessionTransport.d.ts +28 -0
- package/lib/typescript/features/session/JourneyEventPipeline.d.ts +74 -0
- package/lib/typescript/features/session/ReplayCaptureManager.d.ts +391 -0
- package/lib/typescript/features/session/ScrollTracker.d.ts +48 -0
- package/lib/typescript/features/session/SessionManager.d.ts +414 -0
- package/lib/typescript/features/session/SyncDecisionEngine.d.ts +33 -0
- package/lib/typescript/features/session/frameLink.d.ts +27 -0
- package/lib/typescript/features/session/frameScrollState.d.ts +27 -0
- package/lib/typescript/features/session/index.d.ts +23 -0
- package/lib/typescript/features/session/nativeCapture.d.ts +18 -0
- package/lib/typescript/features/session/outboxFrameCapture.d.ts +20 -0
- package/lib/typescript/features/session/sessionTypes.d.ts +362 -0
- package/lib/typescript/features/session/viewportPlausibility.d.ts +72 -0
- package/lib/typescript/features/skan/skanBridge.d.ts +18 -0
- package/lib/typescript/features/skan/skanConversionManager.d.ts +69 -0
- package/lib/typescript/index.d.ts +59 -0
- package/lib/typescript/integrations/fetch/fetchInterceptor.d.ts +41 -0
- package/lib/typescript/integrations/navigation/screenTracker.d.ts +1 -0
- package/lib/typescript/integrations/network/bodyCapture.d.ts +101 -0
- package/lib/typescript/integrations/xhr/xhrInterceptor.d.ts +46 -0
- package/lib/typescript/metro/composeSourceMap.d.ts +26 -0
- package/lib/typescript/metro/featureModules.d.ts +49 -0
- package/lib/typescript/metro/index.d.ts +99 -0
- package/lib/typescript/metro/optionalDependencyStub.d.ts +14 -0
- package/lib/typescript/metro/optionalModules.d.ts +20 -0
- package/lib/typescript/metro/serializerCompose.d.ts +46 -0
- package/lib/typescript/pipeline/dispatcher/dispatcher.d.ts +7 -0
- package/lib/typescript/pipeline/envelope/envelope.d.ts +16 -0
- package/lib/typescript/pipeline/envelope/serializer.d.ts +7 -0
- package/lib/typescript/pipeline/processors/dedupe.d.ts +1 -0
- package/lib/typescript/pipeline/processors/enrich.d.ts +3 -0
- package/lib/typescript/pipeline/processors/sampling.d.ts +1 -0
- package/lib/typescript/pipeline/processors/sanitize.d.ts +3 -0
- package/lib/typescript/pipeline/queue/flushQueue.d.ts +12 -0
- package/lib/typescript/pipeline/queue/memoryQueue.d.ts +12 -0
- package/lib/typescript/pipeline/queue/persistentQueue.d.ts +120 -0
- package/lib/typescript/pipeline/queue/queuePolicy.d.ts +1 -0
- package/lib/typescript/pipeline/scheduler/flushScheduler.d.ts +9 -0
- package/lib/typescript/pipeline/scheduler/retryPolicy.d.ts +5 -0
- package/lib/typescript/public/ScaleBunErrorBoundary.d.ts +44 -0
- package/lib/typescript/public/ScaleBunFacade.d.ts +800 -0
- package/lib/typescript/public/ScaleBunProvider.d.ts +23 -0
- package/lib/typescript/public/typedTracker.d.ts +88 -0
- package/lib/typescript/public/types.d.ts +316 -0
- package/lib/typescript/push/PushManager.d.ts +69 -0
- package/lib/typescript/push/adapters/ManualAdapter.d.ts +40 -0
- package/lib/typescript/push/adapters/NativeBridgeAdapter.d.ts +37 -0
- package/lib/typescript/push/adapters/NoopAdapter.d.ts +21 -0
- package/lib/typescript/push/adapters/RNFirebaseMessagingAdapter.d.ts +39 -0
- package/lib/typescript/push/adapters/loadMessaging.d.ts +17 -0
- package/lib/typescript/push/adapters/loadNotifee.d.ts +15 -0
- package/lib/typescript/push/adapters/selectAdapter.d.ts +20 -0
- package/lib/typescript/push/detect/capabilities.d.ts +15 -0
- package/lib/typescript/push/index.d.ts +10 -0
- package/lib/typescript/push/native/nativeNotifications.d.ts +65 -0
- package/lib/typescript/push/types.d.ts +143 -0
- package/lib/typescript/specs/NativeReplaySdk.d.ts +69 -0
- package/lib/typescript/specs/NativeScaleBunCrash.d.ts +58 -0
- package/lib/typescript/specs/NativeScaleBunInstallReferrer.d.ts +28 -0
- package/lib/typescript/specs/NativeScaleBunOta.d.ts +141 -0
- package/lib/typescript/specs/NativeScaleBunPerformance.d.ts +31 -0
- package/lib/typescript/specs/NativeScaleBunProfiler.d.ts +38 -0
- package/lib/typescript/specs/NativeScaleBunStorage.d.ts +55 -0
- package/lib/typescript/storage/StorageBackend.d.ts +131 -0
- package/lib/typescript/storage/db/sqlite.d.ts +1 -0
- package/lib/typescript/storage/files/fileStore.d.ts +1 -0
- package/lib/typescript/transport/auth/sdkSession.d.ts +10 -0
- package/lib/typescript/transport/backoff/exponentialJitter.d.ts +5 -0
- package/lib/typescript/transport/http/endpoints.d.ts +26 -0
- package/lib/typescript/transport/http/httpClient.d.ts +23 -0
- package/lib/typescript/transport/rateLimit/retryAfter.d.ts +1 -0
- package/lib/typescript/transport/signing/requestSigner.d.ts +1 -0
- package/lib/typescript/vendor/protocol/index.d.ts +345 -0
- package/lib/typescript/vendor/protocol/internal/validation.d.ts +114 -0
- package/lib/typescript/vendor/sdk-contracts/bridge.d.ts +104 -0
- package/lib/typescript/vendor/sdk-contracts/config.d.ts +156 -0
- package/lib/typescript/vendor/sdk-contracts/events.d.ts +42 -0
- package/lib/typescript/vendor/sdk-contracts/features.d.ts +38 -0
- package/lib/typescript/vendor/sdk-contracts/index.d.ts +15 -0
- package/lib/typescript/vendor/sdk-contracts/platform.d.ts +35 -0
- package/lib/typescript/vendor/sdk-contracts/session-boundary.d.ts +42 -0
- package/lib/typescript/vendor/sdk-contracts/session.d.ts +157 -0
- package/package.json +153 -0
- package/react-native.config.js +24 -0
- package/scalebun-react-native.podspec +44 -0
- package/src/analytics/EventTracker.ts +654 -0
- package/src/analytics/automaticEvents.ts +109 -0
- package/src/analytics/batching.ts +54 -0
- package/src/analytics/eventLane.ts +48 -0
- package/src/analytics/revenue.ts +143 -0
- package/src/analytics/subscription.ts +145 -0
- package/src/bootstrap/FeatureRegistry.ts +81 -0
- package/src/bootstrap/SDKBootstrapper.ts +769 -0
- package/src/bucketing/fnv1a32.ts +40 -0
- package/src/compat/codepush.ts +189 -0
- package/src/config/ConfigManager.ts +233 -0
- package/src/config/configTypes.ts +62 -0
- package/src/config/otaPolicyState.ts +79 -0
- package/src/config/quotaState.ts +484 -0
- package/src/core/architecture.ts +39 -0
- package/src/core/clock/now.ts +6 -0
- package/src/core/config/defaults.ts +0 -0
- package/src/core/config/schema.ts +455 -0
- package/src/core/constants/endpoints.ts +12 -0
- package/src/core/constants/protocol.ts +24 -0
- package/src/core/constants/timings.ts +25 -0
- package/src/core/constants/version.ts +11 -0
- package/src/core/context/app.ts +0 -0
- package/src/core/context/device.ts +169 -0
- package/src/core/context/session.ts +0 -0
- package/src/core/context/user.ts +26 -0
- package/src/core/contracts/IFeature.ts +53 -0
- package/src/core/di/container.ts +26 -0
- package/src/core/encoding/base64.ts +46 -0
- package/src/core/id/deviceId.ts +51 -0
- package/src/core/id/installationId.ts +52 -0
- package/src/core/id/sessionId.ts +0 -0
- package/src/core/lifecycle/appLifecycle.ts +33 -0
- package/src/core/lifecycle/crashSafe.ts +29 -0
- package/src/core/logger/errorClassification.ts +108 -0
- package/src/core/logger/internalLogger.ts +65 -0
- package/src/core/logger/levels.ts +0 -0
- package/src/crypto/hmacSha256.ts +106 -0
- package/src/debug/bootstrap.ts +436 -0
- package/src/debug/commands.ts +597 -0
- package/src/debug/configBuilder.ts +56 -0
- package/src/debug/exportBridge.ts +155 -0
- package/src/debug/hostResolver.ts +34 -0
- package/src/debug/index.ts +25 -0
- package/src/debug/metrics.ts +29 -0
- package/src/debug/perf.ts +212 -0
- package/src/debug/perfWiring.ts +119 -0
- package/src/debug/protocol.ts +58 -0
- package/src/debug/redaction.ts +483 -0
- package/src/debug/replayWiring.ts +148 -0
- package/src/debug/screenTracking.ts +55 -0
- package/src/debug/sessionWiring.ts +59 -0
- package/src/debug/stream.ts +561 -0
- package/src/debug/transport.ts +440 -0
- package/src/features/bugreport/BugReportFeature.ts +128 -0
- package/src/features/bugreport/buildPayload.ts +116 -0
- package/src/features/bugreport/index.ts +0 -0
- package/src/features/crash/CrashFeature.ts +87 -0
- package/src/features/crash/CrashReporter.ts +189 -0
- package/src/features/crash/NativeCrashFeature.ts +42 -0
- package/src/features/crash/capture.ts +48 -0
- package/src/features/crash/globalHandler.ts +39 -0
- package/src/features/crash/index.ts +8 -0
- package/src/features/crash/nativeCrashBridge.ts +352 -0
- package/src/features/crash/rejectionHandler.ts +196 -0
- package/src/features/crash/scrubCrash.ts +175 -0
- package/src/features/engage/EngageAnchor.tsx +159 -0
- package/src/features/engage/EngageArchetypeRenderers.tsx +3705 -0
- package/src/features/engage/EngageInAppView.tsx +2678 -0
- package/src/features/engage/EngageInlinePlacement.tsx +169 -0
- package/src/features/engage/EngagePromptProvider.tsx +1318 -0
- package/src/features/engage/EngagePromptView.tsx +1400 -0
- package/src/features/engage/EngageTransport.ts +668 -0
- package/src/features/engage/EngageVariantContent.tsx +1483 -0
- package/src/features/engage/__test-support__/reactNativeStub.js +40 -0
- package/src/features/engage/attachmentCapture.ts +45 -0
- package/src/features/engage/captureVoiceAttachment.ts +81 -0
- package/src/features/engage/engageCoachmarkTour.ts +120 -0
- package/src/features/engage/engageGameLogic.ts +347 -0
- package/src/features/engage/engageMediaSizing.ts +76 -0
- package/src/features/engage/engageMultiStepSheet.ts +53 -0
- package/src/features/engage/engageOutcome.ts +157 -0
- package/src/features/engage/engagePushTokenBridge.ts +84 -0
- package/src/features/engage/engageShakeBridge.ts +67 -0
- package/src/features/engage/engageSignals.ts +40 -0
- package/src/features/engage/engageStoreReviewBridge.ts +48 -0
- package/src/features/engage/engageTestSeenStore.ts +95 -0
- package/src/features/engage/engageThrottle.ts +478 -0
- package/src/features/engage/engageTriggerEngine.ts +350 -0
- package/src/features/engage/engageTypes.ts +712 -0
- package/src/features/engage/engageVariantResolver.ts +364 -0
- package/src/features/engage/engageWindowInsets.ts +108 -0
- package/src/features/engage/gestureTriggerDetector.ts +83 -0
- package/src/features/engage/transcription/TranscriptionProvider.ts +68 -0
- package/src/features/install-referrer/installReferrer.ts +112 -0
- package/src/features/journey/ScaleBunDebugRoot.tsx +1045 -0
- package/src/features/journey/ScaleBunFlatList.tsx +39 -0
- package/src/features/journey/ScaleBunImpression.tsx +109 -0
- package/src/features/journey/ScaleBunScrollView.tsx +101 -0
- package/src/features/journey/ScaleBunSectionList.tsx +39 -0
- package/src/features/journey/autoInstrumentScroll.ts +155 -0
- package/src/features/journey/calibrationContext.ts +44 -0
- package/src/features/journey/gestureBuffer.ts +59 -0
- package/src/features/journey/gestureDetector.ts +162 -0
- package/src/features/journey/impression.ts +92 -0
- package/src/features/journey/interactionProtocol.ts +243 -0
- package/src/features/journey/journeyManager.ts +469 -0
- package/src/features/journey/journeyTypes.ts +215 -0
- package/src/features/journey/nativeScroll.ts +94 -0
- package/src/features/journey/navDetector.ts +149 -0
- package/src/features/journey/screenshotHelper.ts +137 -0
- package/src/features/journey/scrollContext.ts +146 -0
- package/src/features/journey/targetGeometry.ts +197 -0
- package/src/features/journey/targetRegistry.ts +43 -0
- package/src/features/journey/touchTarget.ts +339 -0
- package/src/features/journey/uiState.ts +182 -0
- package/src/features/navigation/AutoScreenDetector.ts +617 -0
- package/src/features/network/NetworkFeature.ts +232 -0
- package/src/features/network/index.ts +59 -0
- package/src/features/network/thirdParty.ts +160 -0
- package/src/features/ota/OtaEventEmitter.ts +187 -0
- package/src/features/ota/OtaOrchestrator.ts +1513 -0
- package/src/features/ota/OtaTypes.ts +230 -0
- package/src/features/ota/deviceAttributes.ts +46 -0
- package/src/features/ota/environment.ts +199 -0
- package/src/features/ota/geoCountry.ts +85 -0
- package/src/features/ota/retry.ts +123 -0
- package/src/features/ota/signedDownload.ts +202 -0
- package/src/features/ota/useOtaUpdate.ts +161 -0
- package/src/features/performance/PerformanceFeature.ts +729 -0
- package/src/features/performance/collectors/AppLaunchCollector.ts +131 -0
- package/src/features/performance/collectors/AutoScreenLoadCollector.ts +258 -0
- package/src/features/performance/collectors/CustomTraceCollector.ts +116 -0
- package/src/features/performance/collectors/FrameMetricsCollector.ts +210 -0
- package/src/features/performance/collectors/JsStallCollector.ts +184 -0
- package/src/features/performance/collectors/NetworkPerfCollector.ts +69 -0
- package/src/features/performance/collectors/ScreenLoadCollector.ts +95 -0
- package/src/features/performance/collectors/UiHangCollector.ts +98 -0
- package/src/features/performance/config.ts +136 -0
- package/src/features/performance/getNativePerformanceModule.ts +38 -0
- package/src/features/performance/index.ts +28 -0
- package/src/features/performance/metrics/MetricsEngine.ts +152 -0
- package/src/features/performance/models.ts +235 -0
- package/src/features/performance/transport/PerformanceTransport.ts +161 -0
- package/src/features/profiler/ProfilerFeature.ts +661 -0
- package/src/features/profiler/config.ts +230 -0
- package/src/features/profiler/contracts.ts +176 -0
- package/src/features/profiler/index.ts +45 -0
- package/src/features/profiler/models.ts +430 -0
- package/src/features/profiler/transport/ProfilerTransport.ts +93 -0
- package/src/features/replay/bridge/adapters/bridgeAdapter.ts +235 -0
- package/src/features/replay/bridge/eventEmitter.ts +176 -0
- package/src/features/replay/bridge/nativeModule.ts +252 -0
- package/src/features/replay/bridge/nativeQuota.ts +111 -0
- package/src/features/replay/core/clock/clock.ts +38 -0
- package/src/features/replay/core/config/defaults.ts +66 -0
- package/src/features/replay/core/config/validator.ts +113 -0
- package/src/features/replay/core/env/environmentDetector.ts +21 -0
- package/src/features/replay/core/errors/safeCall.ts +34 -0
- package/src/features/replay/core/ids/sessionId.ts +44 -0
- package/src/features/replay/core/logger/logger.ts +36 -0
- package/src/features/replay/core/privacy/privacyTypes.ts +41 -0
- package/src/features/replay/core/queue/boundedQueue.ts +71 -0
- package/src/features/replay/integrations/alert/alertInstrumentation.ts +277 -0
- package/src/features/replay/integrations/logs/consoleIntegration.ts +136 -0
- package/src/features/replay/integrations/network/networkAdapter.ts +73 -0
- package/src/features/replay/integrations/react-navigation/navigationIntegration.ts +89 -0
- package/src/features/replay/integrations/touch/ReplayRoot.tsx +114 -0
- package/src/features/replay/pipeline/batching/batchManager.ts +133 -0
- package/src/features/replay/pipeline/envelope/envelopeBuilder.ts +66 -0
- package/src/features/replay/pipeline/retry/retryPolicy.ts +62 -0
- package/src/features/replay/pipeline/transport/transportTypes.ts +85 -0
- package/src/features/replay/public/api.ts +364 -0
- package/src/features/replay/public/enums.ts +78 -0
- package/src/features/replay/public/types.ts +297 -0
- package/src/features/replay/replay/breadcrumbs/breadcrumbCollector.ts +115 -0
- package/src/features/replay/replay/frames/frameModels.ts +45 -0
- package/src/features/replay/replay/session/sessionOrchestrator.ts +286 -0
- package/src/features/replay/replay/timeline/timelineModels.ts +129 -0
- package/src/features/replay/transport/BackendReplayTransport.ts +215 -0
- package/src/features/replay/transport/CompositeReplayTransport.ts +82 -0
- package/src/features/replay/transport/DesktopReplayTransport.ts +140 -0
- package/src/features/replay/transport/ReplayTransport.ts +84 -0
- package/src/features/session/BackendSessionAdapter.ts +1977 -0
- package/src/features/session/DesktopSessionTransport.ts +130 -0
- package/src/features/session/JourneyEventPipeline.ts +278 -0
- package/src/features/session/ReplayCaptureManager.ts +1401 -0
- package/src/features/session/ScrollTracker.ts +166 -0
- package/src/features/session/SessionManager.ts +1687 -0
- package/src/features/session/SyncDecisionEngine.ts +117 -0
- package/src/features/session/frameLink.ts +41 -0
- package/src/features/session/frameScrollState.ts +36 -0
- package/src/features/session/index.ts +88 -0
- package/src/features/session/nativeCapture.ts +43 -0
- package/src/features/session/outboxFrameCapture.ts +79 -0
- package/src/features/session/sessionTypes.ts +528 -0
- package/src/features/session/viewportPlausibility.ts +134 -0
- package/src/features/skan/skanBridge.ts +53 -0
- package/src/features/skan/skanConversionManager.ts +173 -0
- package/src/index.ts +206 -0
- package/src/integrations/fetch/fetchInterceptor.ts +201 -0
- package/src/integrations/navigation/screenTracker.ts +0 -0
- package/src/integrations/network/bodyCapture.ts +328 -0
- package/src/integrations/xhr/xhrInterceptor.ts +259 -0
- package/src/metro/composeSourceMap.ts +104 -0
- package/src/metro/featureModules.ts +143 -0
- package/src/metro/index.ts +336 -0
- package/src/metro/optionalDependencyStub.ts +13 -0
- package/src/metro/optionalModules.ts +27 -0
- package/src/metro/serializerCompose.ts +193 -0
- package/src/pipeline/dispatcher/dispatcher.ts +22 -0
- package/src/pipeline/envelope/envelope.ts +31 -0
- package/src/pipeline/envelope/serializer.ts +14 -0
- package/src/pipeline/processors/dedupe.ts +0 -0
- package/src/pipeline/processors/enrich.ts +11 -0
- package/src/pipeline/processors/sampling.ts +0 -0
- package/src/pipeline/processors/sanitize.ts +23 -0
- package/src/pipeline/queue/flushQueue.ts +144 -0
- package/src/pipeline/queue/memoryQueue.ts +31 -0
- package/src/pipeline/queue/persistentQueue.ts +385 -0
- package/src/pipeline/queue/queuePolicy.ts +0 -0
- package/src/pipeline/scheduler/flushScheduler.ts +47 -0
- package/src/pipeline/scheduler/retryPolicy.ts +8 -0
- package/src/public/ScaleBunErrorBoundary.tsx +91 -0
- package/src/public/ScaleBunFacade.ts +2129 -0
- package/src/public/ScaleBunProvider.tsx +91 -0
- package/src/public/typedTracker.ts +123 -0
- package/src/public/types.ts +362 -0
- package/src/push/PushManager.ts +318 -0
- package/src/push/adapters/ManualAdapter.ts +100 -0
- package/src/push/adapters/NativeBridgeAdapter.ts +142 -0
- package/src/push/adapters/NoopAdapter.ts +46 -0
- package/src/push/adapters/RNFirebaseMessagingAdapter.ts +271 -0
- package/src/push/adapters/loadMessaging.ts +24 -0
- package/src/push/adapters/loadNotifee.ts +29 -0
- package/src/push/adapters/selectAdapter.ts +176 -0
- package/src/push/detect/capabilities.ts +124 -0
- package/src/push/index.ts +22 -0
- package/src/push/native/nativeNotifications.ts +207 -0
- package/src/push/types.ts +159 -0
- package/src/specs/NativeReplaySdk.ts +101 -0
- package/src/specs/NativeScaleBunCrash.ts +64 -0
- package/src/specs/NativeScaleBunInstallReferrer.ts +29 -0
- package/src/specs/NativeScaleBunOta.ts +154 -0
- package/src/specs/NativeScaleBunPerformance.ts +34 -0
- package/src/specs/NativeScaleBunProfiler.ts +41 -0
- package/src/specs/NativeScaleBunStorage.ts +59 -0
- package/src/storage/StorageBackend.ts +272 -0
- package/src/storage/db/sqlite.ts +0 -0
- package/src/storage/files/fileStore.ts +0 -0
- package/src/transport/auth/sdkSession.ts +27 -0
- package/src/transport/backoff/exponentialJitter.ts +9 -0
- package/src/transport/http/endpoints.ts +33 -0
- package/src/transport/http/httpClient.ts +73 -0
- package/src/transport/rateLimit/retryAfter.ts +0 -0
- package/src/transport/signing/requestSigner.ts +0 -0
- package/src/vendor/protocol/index.ts +391 -0
- package/src/vendor/protocol/internal/validation.ts +497 -0
- package/src/vendor/sdk-contracts/bridge.ts +162 -0
- package/src/vendor/sdk-contracts/config.ts +192 -0
- package/src/vendor/sdk-contracts/events.ts +50 -0
- package/src/vendor/sdk-contracts/features.ts +42 -0
- package/src/vendor/sdk-contracts/index.ts +98 -0
- package/src/vendor/sdk-contracts/platform.ts +42 -0
- package/src/vendor/sdk-contracts/session-boundary.ts +55 -0
- package/src/vendor/sdk-contracts/session.ts +225 -0
- package/src/vendor/vendor.manifest +10 -0
|
@@ -0,0 +1,1513 @@
|
|
|
1
|
+
import {
|
|
2
|
+
AppState,
|
|
3
|
+
DeviceEventEmitter,
|
|
4
|
+
DevSettings,
|
|
5
|
+
NativeEventEmitter,
|
|
6
|
+
NativeModules,
|
|
7
|
+
Platform,
|
|
8
|
+
} from 'react-native';
|
|
9
|
+
import { logger } from '../../core/logger/internalLogger';
|
|
10
|
+
import { noThrow } from '../../core/lifecycle/crashSafe';
|
|
11
|
+
import NativeScaleBunOta from '../../specs/NativeScaleBunOta';
|
|
12
|
+
import type { OtaCheckRequest, OtaCheckResponse, OtaBundlePayload } from './OtaTypes';
|
|
13
|
+
import { otaEventEmitter } from './OtaEventEmitter';
|
|
14
|
+
import { getDeviceCountry, prefetchDeviceCountry } from './geoCountry';
|
|
15
|
+
import { deviceTargetingAttributes } from './deviceAttributes';
|
|
16
|
+
import { resolveInstallationId } from '../../core/id/installationId';
|
|
17
|
+
import { detectOtaEnvironment, type OtaEnvironment } from './environment';
|
|
18
|
+
import { retryWithBackoff, isRetryableNetworkError, HttpStatusError } from './retry';
|
|
19
|
+
import {
|
|
20
|
+
stageSignedWithRetry,
|
|
21
|
+
verdictErrorCode,
|
|
22
|
+
signedPatchSupported,
|
|
23
|
+
trySignedPatch,
|
|
24
|
+
type StageVerdict,
|
|
25
|
+
} from './signedDownload';
|
|
26
|
+
import { createStorageBackend, type StorageBackend } from '../../storage/StorageBackend';
|
|
27
|
+
import { resolveMobileOS } from '../../core/context/device';
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Identity marker injected into the bundle by `scalebun ota publish` before
|
|
31
|
+
* Hermes compilation. Lets the SDK prove that the bundle it *installed* is the
|
|
32
|
+
* bundle actually *running* — see `verifyRunningBundleIdentity`.
|
|
33
|
+
*/
|
|
34
|
+
function readRunningBundleMarker(): string | null {
|
|
35
|
+
try {
|
|
36
|
+
const id = (globalThis as unknown as { __SCALEBUN_OTA_ID__?: string }).__SCALEBUN_OTA_ID__;
|
|
37
|
+
return typeof id === 'string' && id.length > 0 ? id : null;
|
|
38
|
+
} catch {
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Download progress: native → JS → (hook + backend live tracker).
|
|
45
|
+
*
|
|
46
|
+
* Both native downloaders emit throttled `ScaleBunOtaProgress` ticks — Android
|
|
47
|
+
* over `RCTDeviceEventEmitter`, iOS via the `ScaleBunOtaEvents` RCTEventEmitter.
|
|
48
|
+
* This subscribes for the duration of one download. Until this existed, the
|
|
49
|
+
* hook's `downloadProgress` and the backend's `/sdk/ota/progress` +
|
|
50
|
+
* live-downloads dashboard were declared on both ends and connected by
|
|
51
|
+
* NOTHING — the tracker had never received a single real row.
|
|
52
|
+
*/
|
|
53
|
+
interface NativeProgressTick {
|
|
54
|
+
bytesRead: number;
|
|
55
|
+
/** -1 when the server sent no usable length (chunked / gzip). */
|
|
56
|
+
totalBytes: number;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function subscribeNativeProgress(
|
|
60
|
+
onTick: (tick: NativeProgressTick) => void,
|
|
61
|
+
): () => void {
|
|
62
|
+
try {
|
|
63
|
+
if (Platform.OS === 'ios') {
|
|
64
|
+
const eventsModule = NativeModules.ScaleBunOtaEvents;
|
|
65
|
+
if (!eventsModule) return () => {};
|
|
66
|
+
const emitter = new NativeEventEmitter(eventsModule);
|
|
67
|
+
const sub = emitter.addListener('ScaleBunOtaProgress', onTick);
|
|
68
|
+
return () => sub.remove();
|
|
69
|
+
}
|
|
70
|
+
const sub = DeviceEventEmitter.addListener('ScaleBunOtaProgress', onTick);
|
|
71
|
+
return () => sub.remove();
|
|
72
|
+
} catch {
|
|
73
|
+
return () => {};
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/** POST at most once per this interval; first and terminal ticks always go. */
|
|
78
|
+
const PROGRESS_POST_INTERVAL_MS = 1_000;
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Deliver queued OTA lifecycle events to the backend's batch endpoint.
|
|
83
|
+
*
|
|
84
|
+
* ── Why the orchestrator owns this ──────────────────────────────────────────
|
|
85
|
+
*
|
|
86
|
+
* `otaEventEmitter.setFlushCallback` documents itself as "called by the SDK
|
|
87
|
+
* core to wire into the existing batch transport" — and nothing, anywhere,
|
|
88
|
+
* ever called it. `flush()` drained the queue into a null callback, so every
|
|
89
|
+
* OTA event the real SDK ever emitted was silently discarded in-process.
|
|
90
|
+
* All 129 ota_events rows in the dev database came from the test app's
|
|
91
|
+
* SIMULATOR; the funnel, adoption charts, and guard thresholds had never seen
|
|
92
|
+
* one byte of real-device telemetry.
|
|
93
|
+
*
|
|
94
|
+
* Owning delivery here (with the params sync() already has) instead of relying
|
|
95
|
+
* on bootstrap wiring means OTA telemetry works in every integration shape —
|
|
96
|
+
* full SDK, standalone orchestrator, CodePush shim — because the component
|
|
97
|
+
* that EMITS the events is the component that delivers them.
|
|
98
|
+
*/
|
|
99
|
+
async function deliverOtaEvents(params: {
|
|
100
|
+
apiUrl: string;
|
|
101
|
+
clientKey: string;
|
|
102
|
+
appVersion: string;
|
|
103
|
+
installationId: string;
|
|
104
|
+
}): Promise<void> {
|
|
105
|
+
const events = otaEventEmitter.flush();
|
|
106
|
+
if (!events.length) return;
|
|
107
|
+
// A platform OTA cannot serve is not reported as Android — it is not reported at all.
|
|
108
|
+
const mobileOS = resolveMobileOS();
|
|
109
|
+
if (!mobileOS) return;
|
|
110
|
+
|
|
111
|
+
const items = events.map((e) => ({
|
|
112
|
+
kind: 'ota_event' as const,
|
|
113
|
+
type: e.type,
|
|
114
|
+
bundleId: e.bundleId,
|
|
115
|
+
// Without this the backend stored a null releaseId on every row it ingested,
|
|
116
|
+
// while serving the release id on every check — so the delivery funnel could
|
|
117
|
+
// only ever be grouped by bundle, and a bundle re-promoted under a second
|
|
118
|
+
// release merged the two into one indistinguishable series.
|
|
119
|
+
releaseId: e.releaseId,
|
|
120
|
+
installationId: params.installationId,
|
|
121
|
+
// OTA bundles are compiled per platform, so this genuinely is ios|android. Narrowed via
|
|
122
|
+
// resolveMobileOS so a non-mobile RN target is skipped rather than served an Android bundle.
|
|
123
|
+
platform: mobileOS,
|
|
124
|
+
appVersion: params.appVersion,
|
|
125
|
+
durationMs: e.durationMs,
|
|
126
|
+
errorCode: e.error ?? e.reason,
|
|
127
|
+
patchUsed: e.patchUsed,
|
|
128
|
+
timestamp: e.timestamp,
|
|
129
|
+
}));
|
|
130
|
+
|
|
131
|
+
try {
|
|
132
|
+
const base = params.apiUrl.replace(/\/+$/, '');
|
|
133
|
+
// The batch lane is session-scoped for replay items; OTA rows carry their
|
|
134
|
+
// own installationId and ignore the session, so a synthetic id is correct.
|
|
135
|
+
const res = await fetch(
|
|
136
|
+
`${base}/ingestion/sessions/ota-${params.installationId}/batch`,
|
|
137
|
+
{
|
|
138
|
+
method: 'POST',
|
|
139
|
+
headers: {
|
|
140
|
+
'Content-Type': 'application/json',
|
|
141
|
+
'x-scalebun-client-key': params.clientKey,
|
|
142
|
+
},
|
|
143
|
+
body: JSON.stringify({ items }),
|
|
144
|
+
},
|
|
145
|
+
);
|
|
146
|
+
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
|
147
|
+
logger.debug(`[OTA] Delivered ${items.length} telemetry event(s)`);
|
|
148
|
+
} catch (err: any) {
|
|
149
|
+
// Failed delivery must not fail the update — but the events must not be
|
|
150
|
+
// lost either: requeue so the next sync retries them.
|
|
151
|
+
for (const e of events) otaEventEmitter.emit(e);
|
|
152
|
+
logger.debug(`[OTA] Telemetry delivery failed (will retry next sync): ${err?.message}`);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
export interface SyncResult {
|
|
157
|
+
/**
|
|
158
|
+
* `WITHHELD` means the device reported root/jailbreak indicators and the host
|
|
159
|
+
* configured `onCompromisedDevice: 'withhold'`. It is NOT `UP_TO_DATE`: the
|
|
160
|
+
* device may well be behind, we simply declined to find out. Collapsing the
|
|
161
|
+
* two would make a withheld fleet indistinguishable from a current one.
|
|
162
|
+
*/
|
|
163
|
+
status:
|
|
164
|
+
| 'UP_TO_DATE'
|
|
165
|
+
| 'UPDATE_INSTALLED'
|
|
166
|
+
| 'ROLLED_BACK'
|
|
167
|
+
| 'ERROR'
|
|
168
|
+
| 'WITHHELD';
|
|
169
|
+
bundle?: OtaBundlePayload;
|
|
170
|
+
error?: string;
|
|
171
|
+
/** True when the server-side release has isMandatory set */
|
|
172
|
+
isMandatory?: boolean;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/** Boot-guard configuration */
|
|
176
|
+
export interface BootGuardConfig {
|
|
177
|
+
/**
|
|
178
|
+
* Milliseconds the app must run without crashing after an OTA update
|
|
179
|
+
* before the boot-guard marker is cleared. Default: 10_000 (10s).
|
|
180
|
+
*/
|
|
181
|
+
healthyAfterMs?: number;
|
|
182
|
+
/**
|
|
183
|
+
* @deprecated Not honoured, and never was — nothing in JS reads this field.
|
|
184
|
+
*
|
|
185
|
+
* The boot-attempt limit lives in native code, where the counter it bounds is
|
|
186
|
+
* incremented (`MAX_BOOT_ATTEMPTS` in `SlotManager.kt` / `OtaSlotManager.swift`,
|
|
187
|
+
* both 2). Setting it here has no effect; the field is kept only so existing
|
|
188
|
+
* call sites keep compiling. Change the limit natively, or file a request for
|
|
189
|
+
* it to be plumbed through `initOutbox`-style native config.
|
|
190
|
+
*/
|
|
191
|
+
maxRevertAttempts?: number;
|
|
192
|
+
/**
|
|
193
|
+
* What to do when the device reports root/jailbreak indicators.
|
|
194
|
+
*
|
|
195
|
+
* 'allow' — DEFAULT. Check, download and install as normal. The
|
|
196
|
+
* indicators are still detected, still logged, and still sent
|
|
197
|
+
* with the check request as telemetry.
|
|
198
|
+
* 'withhold' — Do not check for or install updates on this device. The
|
|
199
|
+
* device stays on whatever bundle it is already running.
|
|
200
|
+
*
|
|
201
|
+
* The default is `'allow'` and must stay `'allow'`. Flipping it would
|
|
202
|
+
* silently stop updates for every existing host app — including the ones
|
|
203
|
+
* whose "indicators" are a developer's test-keys build or a false positive —
|
|
204
|
+
* which is a far worse outcome than the risk it mitigates. Withholding is an
|
|
205
|
+
* opt-in a security-sensitive host makes with knowledge of its own users.
|
|
206
|
+
*
|
|
207
|
+
* What this is and is not: root defeats any check made from inside the app,
|
|
208
|
+
* so this is a POLICY, not a security boundary. A rooted device can patch the
|
|
209
|
+
* check out. What it does buy is that the SDK stops handing fresh, signed
|
|
210
|
+
* bundles to a device whose OTA slot other privileged processes can rewrite,
|
|
211
|
+
* and that the decision is auditable — an `UPDATE_WITHHELD` OTA event is
|
|
212
|
+
* emitted every time it fires, so the host never has to infer it from
|
|
213
|
+
* silence.
|
|
214
|
+
*
|
|
215
|
+
* Mobile only by nature. The web SDK ships no OTA lane at all — there is no
|
|
216
|
+
* bundle slot on a device to protect and no web counterpart to this flag.
|
|
217
|
+
*/
|
|
218
|
+
onCompromisedDevice?: 'allow' | 'withhold';
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
import { resolveCompromisedDevicePolicy } from '../../config/otaPolicyState';
|
|
222
|
+
|
|
223
|
+
const DEFAULT_HEALTHY_AFTER_MS = 10_000;
|
|
224
|
+
|
|
225
|
+
export class OtaOrchestrator {
|
|
226
|
+
private enabled = false;
|
|
227
|
+
private currentBundle: OtaBundlePayload | null = null;
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* The bundle this process is running, once hydrated from the native slots.
|
|
231
|
+
* Read-only view for integrations (e.g. the CodePush shim's
|
|
232
|
+
* getUpdateMetadata) that need to report the running version.
|
|
233
|
+
*/
|
|
234
|
+
public getCurrentBundle(): OtaBundlePayload | null {
|
|
235
|
+
return this.currentBundle;
|
|
236
|
+
}
|
|
237
|
+
private isRestartRequiredState = false;
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* The sync currently running, if any. sync() had NO concurrency control:
|
|
241
|
+
* every caller started a full check/download/stage/apply cycle against the
|
|
242
|
+
* SAME slot directories. Two of them overlapping is not a wasted request,
|
|
243
|
+
* it is a corrupted install — native stage() ends with "remove next/,
|
|
244
|
+
* rename tmp -> next/" while apply() is doing "move next/ -> current/", so
|
|
245
|
+
* one racing the other throws and apply() can leave NO current/ slot at
|
|
246
|
+
* all. A host with a screen that syncs on mount and another that syncs on
|
|
247
|
+
* foreground reproduces this in seconds; on 2026-09-21 it cost two devices
|
|
248
|
+
* that could only be recovered by reinstalling.
|
|
249
|
+
*/
|
|
250
|
+
private syncInFlight: Promise<SyncResult> | null = null;
|
|
251
|
+
|
|
252
|
+
/** When the last network check actually went out (see MIN_CHECK_INTERVAL_MS). */
|
|
253
|
+
private lastCheckAt = 0;
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* A bundle that is staged and waiting for its restart.
|
|
257
|
+
*
|
|
258
|
+
* Kept SEPARATE from `currentBundle`, which must keep naming the bundle
|
|
259
|
+
* this process is actually executing. Overwriting `currentBundle` at apply
|
|
260
|
+
* time told the server the device was already running code it had never
|
|
261
|
+
* run, so the next check was answered with the NEXT release instead of
|
|
262
|
+
* "up to date" — which is how a single launch chained three installs. It
|
|
263
|
+
* also mis-attributed every CHECK and BOOT_SUCCESS event to a bundle the
|
|
264
|
+
* device had not booted.
|
|
265
|
+
*/
|
|
266
|
+
private pendingBundle: OtaBundlePayload | null = null;
|
|
267
|
+
/** Root/jailbreak indicators observed at init; empty when none found. */
|
|
268
|
+
private integrityIndicators: string[] = [];
|
|
269
|
+
/**
|
|
270
|
+
* The native detector was actually asked and answered on this launch.
|
|
271
|
+
*
|
|
272
|
+
* Kept separate from an empty `integrityIndicators` because the two are not
|
|
273
|
+
* the same fact: `false` means never measured (no native module, or the read
|
|
274
|
+
* threw), `true` with an empty list means measured and clean. Reporting the
|
|
275
|
+
* first as the second would show an unmeasured fleet as a clean one.
|
|
276
|
+
*/
|
|
277
|
+
private integrityMeasured = false;
|
|
278
|
+
private healthyTimer: ReturnType<typeof setTimeout> | null = null;
|
|
279
|
+
private bootGuardConfig: BootGuardConfig = {};
|
|
280
|
+
private environment: OtaEnvironment | null = null;
|
|
281
|
+
/** One-time boot checks have run in this process (init is idempotent). */
|
|
282
|
+
private initialized = false;
|
|
283
|
+
private storageBackend: StorageBackend | null = null;
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
* Initialize the orchestrator. Checks for boot-guard recovery state.
|
|
287
|
+
*
|
|
288
|
+
* Signature enforcement is no longer a JS concern: signed releases are
|
|
289
|
+
* verified and staged in NATIVE code against RSA keys embedded in the app
|
|
290
|
+
* package (SB-OTA-RSA-SHA256-V1), and unsigned releases keep working on
|
|
291
|
+
* SHA-256 integrity alone. There is nothing for the host to configure here.
|
|
292
|
+
*/
|
|
293
|
+
init(config?: BootGuardConfig): void {
|
|
294
|
+
noThrow(() => {
|
|
295
|
+
// Idempotent: init() runs its one-time checks ONCE per process. Found
|
|
296
|
+
// live — a host that calls init() again after installing an update (any
|
|
297
|
+
// retry/re-entry path does this) re-runs the identity verification in a
|
|
298
|
+
// process whose running JS legitimately predates the install, producing
|
|
299
|
+
// a false "INSTALL DID NOT TAKE EFFECT" alarm. The boot-time checks are
|
|
300
|
+
// only meaningful in a fresh process; a second init() must not repeat
|
|
301
|
+
// them. Config is still refreshed so late signature setup works.
|
|
302
|
+
if (this.initialized) {
|
|
303
|
+
this.bootGuardConfig = config ?? this.bootGuardConfig;
|
|
304
|
+
return;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
if (!NativeScaleBunOta) {
|
|
308
|
+
logger.warn('[OTA] NativeScaleBunOta module unavailable — OTA disabled');
|
|
309
|
+
return;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
// ── RUNTIME GATE ─────────────────────────────────────────────────────
|
|
313
|
+
// Refuse to run on a runtime where the install would silently no-op.
|
|
314
|
+
// See environment.ts: bridgeless below RN 0.76.1 ignores
|
|
315
|
+
// getJSBundleFile() entirely, so a bundle installs, reports INSTALLED,
|
|
316
|
+
// and the app keeps running the APK asset. Staying loudly disabled is
|
|
317
|
+
// strictly better than reporting success that never happened — those
|
|
318
|
+
// false INSTALLED rows feed adoption metrics and guard thresholds.
|
|
319
|
+
this.environment = detectOtaEnvironment();
|
|
320
|
+
if (!this.environment.supported) {
|
|
321
|
+
logger.error(`[OTA] DISABLED — ${this.environment.blockReason}`);
|
|
322
|
+
this.enabled = false;
|
|
323
|
+
return;
|
|
324
|
+
}
|
|
325
|
+
if (this.environment.bridgeless && !this.environment.rnVersion) {
|
|
326
|
+
logger.warn(
|
|
327
|
+
'[OTA] Running bridgeless but the React Native version could not be determined. ' +
|
|
328
|
+
'If this app is below RN 0.76.1, OTA updates will install and never load.',
|
|
329
|
+
);
|
|
330
|
+
}
|
|
331
|
+
if (this.environment.requiresReactHostIntegration) {
|
|
332
|
+
// RN 0.82 removed ReactNativeHost, so the legacy
|
|
333
|
+
// `override fun getJSBundleFile()` integration does not exist there.
|
|
334
|
+
// We cannot see which form the host used — the identity check after
|
|
335
|
+
// restart is what proves it — but naming the right fix up front turns
|
|
336
|
+
// a confusing silent failure into a one-line correction.
|
|
337
|
+
logger.debug(
|
|
338
|
+
'[OTA] React Native >= 0.82 detected. The only valid Android integration is ' +
|
|
339
|
+
'getDefaultReactHost(..., jsBundleFilePath = ScaleBunOtaModule.getJSBundleFile(ctx)) — ' +
|
|
340
|
+
'ReactNativeHost and its getJSBundleFile() override no longer exist.',
|
|
341
|
+
);
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
this.initialized = true;
|
|
345
|
+
this.enabled = true;
|
|
346
|
+
|
|
347
|
+
// Config lands BEFORE the integrity read, so the log below can state the
|
|
348
|
+
// policy that will actually be applied rather than assume the default.
|
|
349
|
+
// (It used to be assigned after; the boot-guard heartbeat hit the same
|
|
350
|
+
// ordering bug once and silently fell back to the default window.)
|
|
351
|
+
this.bootGuardConfig = config ?? {};
|
|
352
|
+
|
|
353
|
+
// Device integrity signal.
|
|
354
|
+
//
|
|
355
|
+
// Reported, not enforced. Root defeats any check made from inside the app,
|
|
356
|
+
// so this is not a boundary — it is context. It tells an operator that a
|
|
357
|
+
// device's OTA slot is writable by other processes, which is what makes the
|
|
358
|
+
// launch-time hash re-check in SlotManager meaningful rather than paranoid.
|
|
359
|
+
//
|
|
360
|
+
// A host app that wants to act on it sets `onCompromisedDevice:
|
|
361
|
+
// 'withhold'` (default `'allow'` — see BootGuardConfig), or reads
|
|
362
|
+
// `deviceIntegrityIndicators()` and decides for itself. The DETECTION here
|
|
363
|
+
// is unconditional either way; only the decision downstream changes.
|
|
364
|
+
try {
|
|
365
|
+
const raw = NativeScaleBunOta?.getDeviceIntegrity?.();
|
|
366
|
+
this.integrityIndicators = raw ? (JSON.parse(raw) as string[]) : [];
|
|
367
|
+
// Only a string the detector actually returned counts as a measurement;
|
|
368
|
+
// an absent method (older native, unsupported platform) leaves this
|
|
369
|
+
// false so the check request omits the field instead of claiming clean.
|
|
370
|
+
this.integrityMeasured = typeof raw === 'string';
|
|
371
|
+
if (this.integrityIndicators.length > 0) {
|
|
372
|
+
logger.warn(
|
|
373
|
+
`[OTA] Device integrity indicators present: ${this.integrityIndicators.join(', ')}. ` +
|
|
374
|
+
(this.shouldWithhold()
|
|
375
|
+
? "onCompromisedDevice is 'withhold' — this device will NOT be offered or " +
|
|
376
|
+
'installed any update while the indicators persist.'
|
|
377
|
+
: 'Updates still install and are still signature-verified, but an installed ' +
|
|
378
|
+
'bundle on this device can be modified by other privileged processes. ' +
|
|
379
|
+
"Set ota.onCompromisedDevice: 'withhold' to stop updating such devices."),
|
|
380
|
+
);
|
|
381
|
+
}
|
|
382
|
+
} catch {
|
|
383
|
+
this.integrityIndicators = [];
|
|
384
|
+
this.integrityMeasured = false;
|
|
385
|
+
}
|
|
386
|
+
logger.debug(
|
|
387
|
+
`[OTA] Orchestrator initialized (RN ${this.environment.rnVersionString ?? 'unknown'}` +
|
|
388
|
+
`${this.environment.bridgeless ? ', bridgeless' : ''}` +
|
|
389
|
+
`${this.environment.hermes ? `, Hermes HBC v${this.environment.hermesBytecodeVersion ?? '?'}` : ''})`,
|
|
390
|
+
);
|
|
391
|
+
|
|
392
|
+
// ONE read of the slot state, shared by both boot-time consumers.
|
|
393
|
+
//
|
|
394
|
+
// `getSlotState()` is CONSUME-ON-READ for the revert record: both native
|
|
395
|
+
// implementations delete it as they serialise, so that a rollback is
|
|
396
|
+
// reported exactly once instead of on every launch forever. Calling it
|
|
397
|
+
// twice therefore means the second caller never sees the revert — which is
|
|
398
|
+
// precisely what happened, since hydration ran first and swallowed it. It
|
|
399
|
+
// is also a blocking synchronous bridge call, so one read is cheaper.
|
|
400
|
+
const slotState = this.readSlotState();
|
|
401
|
+
|
|
402
|
+
// Rehydrate which bundle we are running from the native slot state.
|
|
403
|
+
// Without this `currentBundle` stays null for the whole process after a
|
|
404
|
+
// restart, so every check reported no current bundle and the backend had
|
|
405
|
+
// no way to know what the device was actually on.
|
|
406
|
+
this.hydrateCurrentBundleFromSlots(slotState);
|
|
407
|
+
|
|
408
|
+
// Warm the device-country cache (edge Worker lookup) so checks can carry
|
|
409
|
+
// a country when the API itself sits behind no geo-stamping CDN.
|
|
410
|
+
// Fire-and-forget and failure-soft — a check without a country is valid.
|
|
411
|
+
void prefetchDeviceCountry();
|
|
412
|
+
|
|
413
|
+
// Check if the boot guard fired on this launch (native reverted before JS
|
|
414
|
+
// loaded). BEFORE the identity check, because both read the install record
|
|
415
|
+
// and the identity check retires it — a revert must get its chance to name
|
|
416
|
+
// the bundle that failed while the record still describes it.
|
|
417
|
+
this.checkBootGuardRecovery(slotState);
|
|
418
|
+
|
|
419
|
+
// Prove the bundle we installed is the bundle that loaded.
|
|
420
|
+
this.verifyRunningBundleIdentity();
|
|
421
|
+
|
|
422
|
+
// Boot-guard health is a LOCAL fact: this process booted from the staged
|
|
423
|
+
// bundle and did not crash. The heartbeat used to start only on the
|
|
424
|
+
// `action === 'NONE'` branch of sync(), so confirming a good bundle needed
|
|
425
|
+
// a successful network round-trip that returned exactly that answer — a
|
|
426
|
+
// device offline, rate-limited, or still being offered an update could
|
|
427
|
+
// never clear its marker, and the guard reverted a bundle that was working.
|
|
428
|
+
//
|
|
429
|
+
// It runs HERE, last, and is handed the slot state that was already read.
|
|
430
|
+
// Starting it earlier made it call getSlotState() itself, and that read is
|
|
431
|
+
// consume-on-read for the revert record — it swallowed the revert before
|
|
432
|
+
// checkBootGuardRecovery() could name the bundle that failed, so a
|
|
433
|
+
// crash-rollback went unreported. It also read bootGuardConfig before
|
|
434
|
+
// init() had assigned it, so healthyAfterMs silently fell back to the
|
|
435
|
+
// default.
|
|
436
|
+
this.startBootGuardHeartbeat(slotState);
|
|
437
|
+
});
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
/**
|
|
441
|
+
* Read the active slot back into `currentBundle` so the next check reports
|
|
442
|
+
* what this device is genuinely running.
|
|
443
|
+
*/
|
|
444
|
+
/**
|
|
445
|
+
* Parse the native slot state once. Returns null when the module is absent or
|
|
446
|
+
* the payload is unreadable — every caller treats that as "factory bundle".
|
|
447
|
+
*/
|
|
448
|
+
private readSlotState(): Record<string, any> | null {
|
|
449
|
+
if (!NativeScaleBunOta) return null;
|
|
450
|
+
try {
|
|
451
|
+
return JSON.parse(NativeScaleBunOta.getSlotState());
|
|
452
|
+
} catch {
|
|
453
|
+
return null;
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
private hydrateCurrentBundleFromSlots(state: Record<string, any> | null): void {
|
|
458
|
+
try {
|
|
459
|
+
const current = state?.current;
|
|
460
|
+
if (!current?.sha256) return;
|
|
461
|
+
|
|
462
|
+
// Keyed on sha256, NOT bundleId. The native `stageBundle` is never told
|
|
463
|
+
// the bundleId — it writes a meta.json containing only `sha256` and
|
|
464
|
+
// `installedAt` — so `current.bundleId` is always absent. Hydrating from
|
|
465
|
+
// it left `currentBundle` null on every launch, which meant the device
|
|
466
|
+
// never told the server what it was running and the server re-offered
|
|
467
|
+
// the same bundle forever. sha256 is the field that is actually there.
|
|
468
|
+
const record = this.readInstallRecord();
|
|
469
|
+
if (record && record.sha256 === current.sha256) {
|
|
470
|
+
this.currentBundle = {
|
|
471
|
+
id: record.bundleId,
|
|
472
|
+
version: record.version,
|
|
473
|
+
releaseId: record.releaseId ?? undefined,
|
|
474
|
+
sha256: record.sha256,
|
|
475
|
+
} as OtaBundlePayload;
|
|
476
|
+
logger.debug(`[OTA] Running bundle v${record.version} (${record.bundleId})`);
|
|
477
|
+
return;
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
// An OTA bundle is installed but we have no record of installing it —
|
|
481
|
+
// a reinstall of the app, cleared storage, or a bundle staged by an
|
|
482
|
+
// older SDK. Report the hash so the server can still recognise it.
|
|
483
|
+
this.currentBundle = { sha256: current.sha256 } as OtaBundlePayload;
|
|
484
|
+
logger.debug(
|
|
485
|
+
`[OTA] Running an OTA bundle (sha ${String(current.sha256).slice(0, 12)}…) with no local install record.`,
|
|
486
|
+
);
|
|
487
|
+
} catch {
|
|
488
|
+
// Slot state unreadable — treat as factory bundle.
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
/**
|
|
493
|
+
* Compare the bundle the slot manager believes is active against the identity
|
|
494
|
+
* marker compiled into the bundle that actually loaded.
|
|
495
|
+
*
|
|
496
|
+
* A mismatch means the install path reported success while the runtime kept
|
|
497
|
+
* executing different code — the silent no-op this whole guard exists for.
|
|
498
|
+
* It is reported as APPLY_FAILED, because that is what happened, regardless
|
|
499
|
+
* of what the previous session's telemetry claimed.
|
|
500
|
+
*
|
|
501
|
+
* The expectation recorded at install time is what makes this conclusive.
|
|
502
|
+
* Without it, a missing marker is ambiguous — it could be a bundle published
|
|
503
|
+
* before markers existed, or an install that never took effect. Having
|
|
504
|
+
* written down "this bundle definitely carries marker X" before restarting,
|
|
505
|
+
* a missing marker afterwards can only mean the second.
|
|
506
|
+
*/
|
|
507
|
+
private verifyRunningBundleIdentity(): void {
|
|
508
|
+
if (!this.currentBundle) return;
|
|
509
|
+
|
|
510
|
+
const running = readRunningBundleMarker();
|
|
511
|
+
const record = this.readInstallRecord();
|
|
512
|
+
|
|
513
|
+
// No record at all — installed by an SDK that predates install records, or
|
|
514
|
+
// local storage was cleared. NOTHING can be concluded here and nothing is
|
|
515
|
+
// reported: the marker is a random per-publish token, never the bundle id,
|
|
516
|
+
// so comparing the two would flag a false mismatch on every healthy launch.
|
|
517
|
+
if (!record) return;
|
|
518
|
+
|
|
519
|
+
// The record describes a bundle that is no longer the active one (a revert,
|
|
520
|
+
// or a bundle staged by another path). It cannot verify this launch, and
|
|
521
|
+
// keeping it would make the next launch mis-report what is running.
|
|
522
|
+
if (record.sha256 !== this.currentBundle.sha256) {
|
|
523
|
+
this.clearInstallExpectation();
|
|
524
|
+
return;
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
// Already proven on an earlier launch. The token is retired once verified
|
|
528
|
+
// while the REST of the record stays — it is also the sha256 -> bundleId map
|
|
529
|
+
// that `hydrateCurrentBundleFromSlots` reads to report what this device is
|
|
530
|
+
// running. Wiping the whole record here is what made every launch after the
|
|
531
|
+
// first send `currentBundleId: undefined`, which the server reads as "not on
|
|
532
|
+
// this bundle" and answers by serving the same bundle again, forever.
|
|
533
|
+
if (!record.identityToken) return;
|
|
534
|
+
|
|
535
|
+
if (running === record.identityToken) {
|
|
536
|
+
logger.debug('[OTA] Install verified — running bundle matches what was installed.');
|
|
537
|
+
this.retireIdentityToken(record);
|
|
538
|
+
return;
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
logger.error(
|
|
542
|
+
`[OTA] INSTALL DID NOT TAKE EFFECT — bundle ${this.currentBundle.id} was installed and ` +
|
|
543
|
+
`carries a known identity marker, but the running bundle reports ` +
|
|
544
|
+
`${running ?? 'no marker at all'}. The app is executing different code than the slot ` +
|
|
545
|
+
'manager believes. Check that the host app resolves the OTA bundle path at launch ' +
|
|
546
|
+
'(see the ScaleBunOta integration for your React Native version).',
|
|
547
|
+
);
|
|
548
|
+
otaEventEmitter.emitSimple('APPLY_FAILED', this.currentBundle.id, {
|
|
549
|
+
releaseId: this.currentBundle.releaseId,
|
|
550
|
+
error: `install_not_effective — expected ${record.identityToken}, running ${running ?? 'none'}`,
|
|
551
|
+
});
|
|
552
|
+
// Deliberately NOT retired: the condition is still true on the next boot and
|
|
553
|
+
// should keep reporting until the integration is fixed. Retiring the token
|
|
554
|
+
// here would make a permanently broken install look like a one-off.
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
// ── Install expectation ────────────────────────────────────────────────────
|
|
558
|
+
// Written just before the restart that activates a bundle, read on the next
|
|
559
|
+
// boot. Synchronous, disk-backed, and the same store used for the device id —
|
|
560
|
+
// it has to survive a process death that happens moments after the write.
|
|
561
|
+
|
|
562
|
+
private static readonly INSTALL_EXPECTATION_KEY = 'scalebun.ota.pendingInstall';
|
|
563
|
+
|
|
564
|
+
private recordInstallExpectation(bundle: OtaBundlePayload): void {
|
|
565
|
+
try {
|
|
566
|
+
this.storage().set(
|
|
567
|
+
OtaOrchestrator.INSTALL_EXPECTATION_KEY,
|
|
568
|
+
JSON.stringify({
|
|
569
|
+
bundleId: bundle.id,
|
|
570
|
+
version: bundle.version,
|
|
571
|
+
// Carried so telemetry emitted on a LATER launch (a boot-guard
|
|
572
|
+
// rollback, an ineffective install) can still be attributed to the
|
|
573
|
+
// release, not merely the bundle. The check response is long gone by
|
|
574
|
+
// then; this record is the only thing that remembers.
|
|
575
|
+
releaseId: bundle.releaseId ?? null,
|
|
576
|
+
// The join key back to the native slot, which records sha256 and
|
|
577
|
+
// nothing else identifying.
|
|
578
|
+
sha256: bundle.sha256,
|
|
579
|
+
identityToken: bundle.identityToken ?? null,
|
|
580
|
+
}),
|
|
581
|
+
);
|
|
582
|
+
} catch {
|
|
583
|
+
// Verification is a safety net, never a precondition for installing.
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
private readInstallRecord(): {
|
|
588
|
+
bundleId: string;
|
|
589
|
+
version: number;
|
|
590
|
+
releaseId?: string | null;
|
|
591
|
+
sha256: string;
|
|
592
|
+
identityToken: string | null;
|
|
593
|
+
} | null {
|
|
594
|
+
try {
|
|
595
|
+
const raw = this.storage().get(OtaOrchestrator.INSTALL_EXPECTATION_KEY);
|
|
596
|
+
if (!raw) return null;
|
|
597
|
+
const parsed = JSON.parse(raw);
|
|
598
|
+
return typeof parsed?.bundleId === 'string' && typeof parsed?.sha256 === 'string'
|
|
599
|
+
? parsed
|
|
600
|
+
: null;
|
|
601
|
+
} catch {
|
|
602
|
+
return null;
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
/**
|
|
607
|
+
* Drop the identity token once the install has been proven, keeping the rest
|
|
608
|
+
* of the record.
|
|
609
|
+
*
|
|
610
|
+
* The record does two jobs: it proves an install took effect (once), and it
|
|
611
|
+
* maps the native slot's sha256 back to a bundle id (for the life of that
|
|
612
|
+
* bundle). Only the first job is finished after a successful verification, so
|
|
613
|
+
* only the token is retired.
|
|
614
|
+
*/
|
|
615
|
+
private retireIdentityToken(record: {
|
|
616
|
+
bundleId: string;
|
|
617
|
+
version: number;
|
|
618
|
+
releaseId?: string | null;
|
|
619
|
+
sha256: string;
|
|
620
|
+
}): void {
|
|
621
|
+
try {
|
|
622
|
+
this.storage().set(
|
|
623
|
+
OtaOrchestrator.INSTALL_EXPECTATION_KEY,
|
|
624
|
+
JSON.stringify({
|
|
625
|
+
bundleId: record.bundleId,
|
|
626
|
+
version: record.version,
|
|
627
|
+
releaseId: record.releaseId ?? null,
|
|
628
|
+
sha256: record.sha256,
|
|
629
|
+
identityToken: null,
|
|
630
|
+
}),
|
|
631
|
+
);
|
|
632
|
+
} catch {
|
|
633
|
+
/* non-fatal */
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
private clearInstallExpectation(): void {
|
|
638
|
+
try {
|
|
639
|
+
this.storage().set(OtaOrchestrator.INSTALL_EXPECTATION_KEY, '');
|
|
640
|
+
} catch {
|
|
641
|
+
/* non-fatal */
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
private storage(): StorageBackend {
|
|
646
|
+
if (!this.storageBackend) this.storageBackend = createStorageBackend();
|
|
647
|
+
return this.storageBackend;
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
/**
|
|
651
|
+
* Sprint 5 (S5-NAT-1): Check if the native boot guard reverted us on this cold start.
|
|
652
|
+
* If getSlotState() shows bootMarkerPresent=false but we have a 'previous' slot
|
|
653
|
+
* and no 'current' OTA bundle, the native layer already reverted.
|
|
654
|
+
*/
|
|
655
|
+
private checkBootGuardRecovery(state: Record<string, any> | null): void {
|
|
656
|
+
try {
|
|
657
|
+
if (!state?.bootGuardReverted) return;
|
|
658
|
+
|
|
659
|
+
// WHICH bundle was rolled back, and it has to come from the install
|
|
660
|
+
// record. The obvious source — `state.previous` — is wrong twice over:
|
|
661
|
+
// slot meta.json carries only `{sha256, installedAt}` so it has no bundle
|
|
662
|
+
// id to read, and `revert()` deletes the previous slot as its last step,
|
|
663
|
+
// so by the time this runs there is no previous meta at all. The event
|
|
664
|
+
// therefore reported `bundleId: 'unknown'` on every rollback, and the
|
|
665
|
+
// backend drops any ota_event whose bundleId does not resolve to a bundle
|
|
666
|
+
// the app owns — so the crash-guard's own rollback signal never arrived.
|
|
667
|
+
//
|
|
668
|
+
// Native now stamps the sha256 of the bundle it reverted AWAY FROM into
|
|
669
|
+
// the revert record; the install record maps that back to a bundle id.
|
|
670
|
+
const record = this.readInstallRecord();
|
|
671
|
+
const revertedSha: string | undefined = state.bootGuardRevertedSha256;
|
|
672
|
+
const matchesRecord = !!record && (!revertedSha || record.sha256 === revertedSha);
|
|
673
|
+
|
|
674
|
+
// Report the reason native actually determined — a hash mismatch and a
|
|
675
|
+
// crash loop are different incidents and were being collapsed into one.
|
|
676
|
+
const reason: string = state.bootGuardRevertReason || 'boot_crash_guard';
|
|
677
|
+
|
|
678
|
+
if (matchesRecord && record) {
|
|
679
|
+
logger.warn(
|
|
680
|
+
`[OTA] Boot guard fired — reverted away from bundle ${record.bundleId} ` +
|
|
681
|
+
`(v${record.version}); reason: ${reason}`,
|
|
682
|
+
);
|
|
683
|
+
otaEventEmitter.emitSimple('AUTO_ROLLBACK', record.bundleId, {
|
|
684
|
+
releaseId: record.releaseId ?? undefined,
|
|
685
|
+
version: record.version,
|
|
686
|
+
reason,
|
|
687
|
+
});
|
|
688
|
+
// The record describes a bundle this device is no longer running.
|
|
689
|
+
this.clearInstallExpectation();
|
|
690
|
+
return;
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
// No usable record (older SDK, cleared storage). Say so rather than
|
|
694
|
+
// emitting an event the server is obliged to discard.
|
|
695
|
+
logger.warn(
|
|
696
|
+
`[OTA] Boot guard fired (reason: ${reason}) but the rolled-back bundle could not be ` +
|
|
697
|
+
'identified locally — no install record. The rollback is not reported to the server.',
|
|
698
|
+
);
|
|
699
|
+
} catch {
|
|
700
|
+
// Slot state parsing failed — non-fatal
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
isEnabled(): boolean {
|
|
705
|
+
return this.enabled;
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
/**
|
|
709
|
+
* Root/jailbreak indicators detected on this device, as stable reason strings.
|
|
710
|
+
* Empty means nothing was detected — not proof the device is clean.
|
|
711
|
+
*/
|
|
712
|
+
deviceIntegrityIndicators(): string[] {
|
|
713
|
+
return [...this.integrityIndicators];
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
/**
|
|
717
|
+
* Should this device be refused an update?
|
|
718
|
+
*
|
|
719
|
+
* TRUE only when BOTH are so: the host opted into `'withhold'`, and the native
|
|
720
|
+
* detector actually found something. An empty indicator list is never a reason
|
|
721
|
+
* to withhold — "nothing detected" is the normal state of every healthy
|
|
722
|
+
* device, and treating it as a reason would brick updates for the whole fleet
|
|
723
|
+
* the moment a host set the flag.
|
|
724
|
+
*/
|
|
725
|
+
private shouldWithhold(): boolean {
|
|
726
|
+
// Resolved at DECISION time, not at init: /config is fetched asynchronously,
|
|
727
|
+
// so a policy that arrives mid-session takes effect on the next check rather
|
|
728
|
+
// than only on the next launch.
|
|
729
|
+
//
|
|
730
|
+
// STRICTEST WINS and the server can only TIGHTEN — see
|
|
731
|
+
// resolveCompromisedDevicePolicy. A host that asked for 'withhold' keeps it
|
|
732
|
+
// whatever the server says; a customer who has shipped nothing can still be
|
|
733
|
+
// switched on without an app release, which is the point of the feature.
|
|
734
|
+
return (
|
|
735
|
+
resolveCompromisedDevicePolicy(this.bootGuardConfig.onCompromisedDevice) ===
|
|
736
|
+
'withhold' && this.integrityIndicators.length > 0
|
|
737
|
+
);
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
/**
|
|
741
|
+
* Announce the refusal, once per decision point.
|
|
742
|
+
*
|
|
743
|
+
* A withheld update must be visible as a decision, not as an absence. The
|
|
744
|
+
* event carries the indicators that drove it so the host can log WHY, and it
|
|
745
|
+
* names the bundle the device stays on (`'unknown'` for a device still on the
|
|
746
|
+
* factory bundle, which owns no OTA bundle id).
|
|
747
|
+
*/
|
|
748
|
+
private emitWithheld(): void {
|
|
749
|
+
logger.warn(
|
|
750
|
+
`[OTA] Update WITHHELD — device integrity indicators present (${this.integrityIndicators.join(', ')}) ` +
|
|
751
|
+
"and ota.onCompromisedDevice is 'withhold'. This device stays on its current bundle.",
|
|
752
|
+
);
|
|
753
|
+
otaEventEmitter.emitSimple('UPDATE_WITHHELD', this.currentBundle?.id ?? 'unknown', {
|
|
754
|
+
releaseId: this.currentBundle?.releaseId,
|
|
755
|
+
version: this.currentBundle?.version,
|
|
756
|
+
indicators: [...this.integrityIndicators],
|
|
757
|
+
});
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
isRestartRequired(): boolean {
|
|
761
|
+
return this.isRestartRequiredState;
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
/**
|
|
765
|
+
* Perform an OTA check against the backend endpoint.
|
|
766
|
+
*/
|
|
767
|
+
async checkForUpdate(requestParams: {
|
|
768
|
+
apiUrl: string;
|
|
769
|
+
clientKey: string;
|
|
770
|
+
appVersion: string;
|
|
771
|
+
installationId: string;
|
|
772
|
+
channelName?: string;
|
|
773
|
+
country?: string;
|
|
774
|
+
attributes?: Record<string, string | number>;
|
|
775
|
+
segmentIds?: string[];
|
|
776
|
+
lifecycleStage?: string;
|
|
777
|
+
}): Promise<OtaCheckResponse> {
|
|
778
|
+
try {
|
|
779
|
+
// ── COMPROMISED-DEVICE POLICY ────────────────────────────────────────
|
|
780
|
+
// Opt-in, default 'allow' (see BootGuardConfig.onCompromisedDevice). The
|
|
781
|
+
// short-circuit is HERE as well as in sync() because checkForUpdate is a
|
|
782
|
+
// public entry point in its own right — the CodePush shim's
|
|
783
|
+
// `checkForUpdate` and any host that drives the check by hand reach the
|
|
784
|
+
// network through this method without going through sync().
|
|
785
|
+
if (this.shouldWithhold()) {
|
|
786
|
+
this.emitWithheld();
|
|
787
|
+
return { action: 'NONE' };
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
const env = this.environment ?? detectOtaEnvironment();
|
|
791
|
+
|
|
792
|
+
// C1: send the channel and targeting context. `channelName` was already an
|
|
793
|
+
// accepted parameter here and was then dropped on the floor — the payload
|
|
794
|
+
// never carried it — so every device resolved to `default` no matter what
|
|
795
|
+
// the host app asked for. Undefined fields are omitted by JSON.stringify,
|
|
796
|
+
// so an app that supplies nothing behaves exactly as before.
|
|
797
|
+
// OTA has no bundle for a non-mobile target; claiming Android there would install the wrong one.
|
|
798
|
+
const mobileOS = resolveMobileOS();
|
|
799
|
+
if (!mobileOS) throw new Error('OTA is not supported on this platform');
|
|
800
|
+
// SDK trust coverage (CICDPAGE §12): report which trusted key IDs are
|
|
801
|
+
// embedded in this binary so the dashboard can measure key-rotation
|
|
802
|
+
// adoption. Telemetry only — the server never trusts a key a device claims.
|
|
803
|
+
// Defensive: optional native method, never let it break a check.
|
|
804
|
+
let trustedKeyIds: string[] | undefined;
|
|
805
|
+
try {
|
|
806
|
+
const raw = NativeScaleBunOta?.getTrustedKeyIds?.();
|
|
807
|
+
if (raw) {
|
|
808
|
+
const arr = JSON.parse(raw);
|
|
809
|
+
if (Array.isArray(arr) && arr.length) trustedKeyIds = arr.slice(0, 16);
|
|
810
|
+
}
|
|
811
|
+
} catch {
|
|
812
|
+
/* trust reporting is best-effort — never break the update check */
|
|
813
|
+
}
|
|
814
|
+
const payload: OtaCheckRequest = {
|
|
815
|
+
appVersion: requestParams.appVersion,
|
|
816
|
+
platform: mobileOS,
|
|
817
|
+
installationId: requestParams.installationId,
|
|
818
|
+
currentBundleId: this.currentBundle?.id,
|
|
819
|
+
currentBundleHash: this.currentBundle?.sha256,
|
|
820
|
+
...(signedPatchSupported(NativeScaleBunOta) ? { otaCapabilities: ['signed_patch_v1'] } : {}),
|
|
821
|
+
trustedKeyIds,
|
|
822
|
+
// Root/jailbreak indicators, so a console can report how much of a fleet
|
|
823
|
+
// is affected instead of every operator guessing. Telemetry only — the
|
|
824
|
+
// server must never trust it (a rooted device can send `[]`), and the
|
|
825
|
+
// withholding decision above is made entirely on-device.
|
|
826
|
+
//
|
|
827
|
+
// `[]` and absent mean DIFFERENT things and are kept apart: `[]` is
|
|
828
|
+
// "the detector ran and found nothing", absent is "never measured"
|
|
829
|
+
// (native module missing, or the read threw). Sending `[]` for both
|
|
830
|
+
// would report an unmeasured fleet as a clean one.
|
|
831
|
+
integrityIndicators: this.integrityMeasured
|
|
832
|
+
? [...this.integrityIndicators]
|
|
833
|
+
: undefined,
|
|
834
|
+
channelName: requestParams.channelName,
|
|
835
|
+
// Host-supplied country wins; otherwise the cached edge-Worker lookup
|
|
836
|
+
// fills in (see geoCountry.ts). Undefined when neither knows — the
|
|
837
|
+
// server renders unknown as unknown rather than guessing.
|
|
838
|
+
country: requestParams.country ?? getDeviceCountry() ?? undefined,
|
|
839
|
+
// Hardware/OS context under the host's own attributes (host wins on
|
|
840
|
+
// collision) — this is what lets ATTRIBUTE rules target device classes
|
|
841
|
+
// (deviceModel / manufacturer / brand / osVersion / formFactor).
|
|
842
|
+
attributes: { ...deviceTargetingAttributes(), ...requestParams.attributes },
|
|
843
|
+
segmentIds: requestParams.segmentIds,
|
|
844
|
+
lifecycleStage: requestParams.lifecycleStage,
|
|
845
|
+
// Runtime compatibility context. Without these the backend cannot tell
|
|
846
|
+
// whether a bundle is safe for this binary: appVersion is a marketing
|
|
847
|
+
// string, while the RN version and the Hermes bytecode format are the
|
|
848
|
+
// actual runtime contract. A bundle compiled against a different HBC
|
|
849
|
+
// version either fails to load or crashes later on a missing path.
|
|
850
|
+
rnVersion: env.rnVersionString ?? undefined,
|
|
851
|
+
hermes: env.hermes,
|
|
852
|
+
hermesBytecodeVersion: env.hermesBytecodeVersion ?? undefined,
|
|
853
|
+
bridgeless: env.bridgeless,
|
|
854
|
+
};
|
|
855
|
+
|
|
856
|
+
const url = `${requestParams.apiUrl.replace(/\/+$/, '')}/sdk/ota/check`;
|
|
857
|
+
|
|
858
|
+
const data = await retryWithBackoff<OtaCheckResponse>(
|
|
859
|
+
async () => {
|
|
860
|
+
const res = await fetch(url, {
|
|
861
|
+
method: 'POST',
|
|
862
|
+
headers: {
|
|
863
|
+
'Content-Type': 'application/json',
|
|
864
|
+
'x-scalebun-client-key': requestParams.clientKey,
|
|
865
|
+
},
|
|
866
|
+
body: JSON.stringify(payload),
|
|
867
|
+
});
|
|
868
|
+
|
|
869
|
+
if (!res.ok) {
|
|
870
|
+
throw new HttpStatusError(
|
|
871
|
+
`OTA check failed with status ${res.status}`,
|
|
872
|
+
res.status,
|
|
873
|
+
);
|
|
874
|
+
}
|
|
875
|
+
return (await res.json()) as OtaCheckResponse;
|
|
876
|
+
},
|
|
877
|
+
{ label: 'check', isRetryable: isRetryableNetworkError },
|
|
878
|
+
);
|
|
879
|
+
|
|
880
|
+
return data;
|
|
881
|
+
} catch (err: any) {
|
|
882
|
+
logger.error(`[OTA] checkForUpdate failed: ${err.message}`);
|
|
883
|
+
return { action: 'NONE' };
|
|
884
|
+
}
|
|
885
|
+
}
|
|
886
|
+
|
|
887
|
+
/**
|
|
888
|
+
* Downloads, stage, and apply an update in one step.
|
|
889
|
+
* Sprint 5: emits lifecycle events + starts boot-guard heartbeat.
|
|
890
|
+
*/
|
|
891
|
+
/**
|
|
892
|
+
* Minimum gap between network checks. Not a policy, a stampede guard: an
|
|
893
|
+
* app that syncs on mount AND on every foreground transition can otherwise
|
|
894
|
+
* issue a burst of identical checks. Small enough not to get in the way of
|
|
895
|
+
* publishing and testing a release.
|
|
896
|
+
*/
|
|
897
|
+
private static readonly MIN_CHECK_INTERVAL_MS = 15_000;
|
|
898
|
+
|
|
899
|
+
/**
|
|
900
|
+
* Check, download, stage and apply — at most ONE at a time.
|
|
901
|
+
*
|
|
902
|
+
* Concurrent callers join the sync already running rather than starting a
|
|
903
|
+
* second one, and a call made while an update is already staged returns
|
|
904
|
+
* without touching the slots: the staged bundle has to be activated by a
|
|
905
|
+
* restart before anything else can usefully happen, and re-checking here
|
|
906
|
+
* is what used to chain a second install onto the first.
|
|
907
|
+
*/
|
|
908
|
+
async sync(rawParams: Parameters<OtaOrchestrator['runSync']>[0]): Promise<SyncResult> {
|
|
909
|
+
if (this.isRestartRequiredState) {
|
|
910
|
+
logger.debug(
|
|
911
|
+
'[OTA] An update is staged and waiting for a restart — skipping this check.',
|
|
912
|
+
);
|
|
913
|
+
return { status: 'UP_TO_DATE', bundle: this.pendingBundle ?? undefined };
|
|
914
|
+
}
|
|
915
|
+
if (this.syncInFlight) {
|
|
916
|
+
logger.debug('[OTA] A sync is already running — joining it.');
|
|
917
|
+
return this.syncInFlight;
|
|
918
|
+
}
|
|
919
|
+
const elapsed = Date.now() - this.lastCheckAt;
|
|
920
|
+
if (this.lastCheckAt > 0 && elapsed < OtaOrchestrator.MIN_CHECK_INTERVAL_MS) {
|
|
921
|
+
logger.debug(
|
|
922
|
+
`[OTA] Checked ${Math.round(elapsed / 1000)}s ago — within the ${
|
|
923
|
+
OtaOrchestrator.MIN_CHECK_INTERVAL_MS / 1000
|
|
924
|
+
}s minimum interval, skipping.`,
|
|
925
|
+
);
|
|
926
|
+
return { status: 'UP_TO_DATE' };
|
|
927
|
+
}
|
|
928
|
+
this.lastCheckAt = Date.now();
|
|
929
|
+
this.syncInFlight = this.runSync(rawParams).finally(() => {
|
|
930
|
+
this.syncInFlight = null;
|
|
931
|
+
});
|
|
932
|
+
return this.syncInFlight;
|
|
933
|
+
}
|
|
934
|
+
|
|
935
|
+
private async runSync(rawParams: {
|
|
936
|
+
apiUrl: string;
|
|
937
|
+
clientKey: string;
|
|
938
|
+
appVersion: string;
|
|
939
|
+
/** Omit to use the SDK's canonical per-install id (recommended — P8). */
|
|
940
|
+
installationId?: string;
|
|
941
|
+
// P1: these five existed on checkForUpdate and on the wire contract, but
|
|
942
|
+
// sync() — the only caller that matters — never accepted them, so they
|
|
943
|
+
// were structurally undefined on every real device: every install pinned
|
|
944
|
+
// to the `default` channel and the whole targeting engine matched nothing.
|
|
945
|
+
// The C1 comment in OtaTypes.ts claimed this was fixed; the plumbing had
|
|
946
|
+
// stopped exactly one function short of true.
|
|
947
|
+
channelName?: string;
|
|
948
|
+
country?: string;
|
|
949
|
+
attributes?: Record<string, string | number>;
|
|
950
|
+
segmentIds?: string[];
|
|
951
|
+
lifecycleStage?: string;
|
|
952
|
+
/**
|
|
953
|
+
* @deprecated IGNORED. Activation is driven strictly by the release's
|
|
954
|
+
* installMode (IMMEDIATE / ON_NEXT_RESUME / ON_NEXT_RESTART). This flag is
|
|
955
|
+
* pinned false internally and honoured nowhere — publish an IMMEDIATE
|
|
956
|
+
* release instead of forcing a client-side restart.
|
|
957
|
+
*/
|
|
958
|
+
autoRestart?: boolean;
|
|
959
|
+
}): Promise<SyncResult> {
|
|
960
|
+
const params = {
|
|
961
|
+
...rawParams,
|
|
962
|
+
installationId: rawParams.installationId ?? resolveInstallationId(),
|
|
963
|
+
// ENFORCED GLOBALLY: the legacy client `autoRestart` override is ignored —
|
|
964
|
+
// pinned false here so no app config (or future code path that reads
|
|
965
|
+
// params.autoRestart) can re-enable it. Activation is driven STRICTLY by
|
|
966
|
+
// the release's server-set installMode: IMMEDIATE restarts now,
|
|
967
|
+
// ON_NEXT_RESUME arms a resume-restart, ON_NEXT_RESTART (default) applies
|
|
968
|
+
// on the next cold start. Honouring `autoRestart:true` forced every release
|
|
969
|
+
// to restart immediately (defeating installMode), and on bridgeless it
|
|
970
|
+
// forced the in-process restart that cannot load a staged bundle.
|
|
971
|
+
autoRestart: false as boolean,
|
|
972
|
+
};
|
|
973
|
+
try {
|
|
974
|
+
if (!this.enabled || !NativeScaleBunOta) {
|
|
975
|
+
return { status: 'ERROR', error: 'OTA native module disabled or missing' };
|
|
976
|
+
}
|
|
977
|
+
|
|
978
|
+
// ── COMPROMISED-DEVICE POLICY ────────────────────────────────────────
|
|
979
|
+
// Before ANY network call and before the CHECK event: a withheld device
|
|
980
|
+
// must not appear in the funnel's denominator as a device that asked and
|
|
981
|
+
// was told nothing. It did not ask. Default is 'allow', so this is inert
|
|
982
|
+
// unless the host opted in (see BootGuardConfig.onCompromisedDevice).
|
|
983
|
+
if (this.shouldWithhold()) {
|
|
984
|
+
this.emitWithheld();
|
|
985
|
+
return { status: 'WITHHELD' };
|
|
986
|
+
}
|
|
987
|
+
|
|
988
|
+
logger.debug('[OTA] Sync started…');
|
|
989
|
+
|
|
990
|
+
// The funnel's denominator, for devices already on an OTA bundle. Emitted
|
|
991
|
+
// before the request, so a check that fails outright still counts as a
|
|
992
|
+
// check — the only CHECK rows before this came from a server-side geo
|
|
993
|
+
// side-effect that is skipped whenever the request carries no country.
|
|
994
|
+
//
|
|
995
|
+
// Only when a current bundle is known: every ota_event must name a bundle
|
|
996
|
+
// the app owns or the server drops it, and a device still on the binary's
|
|
997
|
+
// factory bundle has no such id to give. Those devices are counted
|
|
998
|
+
// server-side when they are offered something.
|
|
999
|
+
if (this.currentBundle?.id) {
|
|
1000
|
+
otaEventEmitter.emitSimple('CHECK', this.currentBundle.id, {
|
|
1001
|
+
releaseId: this.currentBundle.releaseId,
|
|
1002
|
+
version: this.currentBundle.version,
|
|
1003
|
+
});
|
|
1004
|
+
}
|
|
1005
|
+
|
|
1006
|
+
const checkRes = await this.checkForUpdate(params);
|
|
1007
|
+
|
|
1008
|
+
if (checkRes.action === 'NONE') {
|
|
1009
|
+
logger.debug('[OTA] App is up to date');
|
|
1010
|
+
// If we booted from an OTA bundle and it's healthy, clear the boot guard
|
|
1011
|
+
this.startBootGuardHeartbeat();
|
|
1012
|
+
return { status: 'UP_TO_DATE' };
|
|
1013
|
+
}
|
|
1014
|
+
|
|
1015
|
+
// Handle server-initiated rollback (Sprint 4)
|
|
1016
|
+
if (checkRes.action === 'ROLLBACK') {
|
|
1017
|
+
logger.warn('[OTA] Server requested ROLLBACK — reverting to previous bundle');
|
|
1018
|
+
otaEventEmitter.emitSimple('MANUAL_ROLLBACK', this.currentBundle?.id ?? 'unknown', {
|
|
1019
|
+
releaseId: this.currentBundle?.releaseId,
|
|
1020
|
+
});
|
|
1021
|
+
const reverted = await NativeScaleBunOta.revertToPrevious();
|
|
1022
|
+
if (reverted) {
|
|
1023
|
+
this.currentBundle = null;
|
|
1024
|
+
// isRestartRequired flips true; the reverted bundle binds on the next
|
|
1025
|
+
// cold start. autoRestart is intentionally not consulted (pinned false
|
|
1026
|
+
// in sync()) — activation policy is uniform across update and rollback.
|
|
1027
|
+
this.isRestartRequiredState = true;
|
|
1028
|
+
return { status: 'ROLLED_BACK' };
|
|
1029
|
+
}
|
|
1030
|
+
return { status: 'ERROR', error: 'Native rollback failed' };
|
|
1031
|
+
}
|
|
1032
|
+
|
|
1033
|
+
if (!checkRes.bundle) {
|
|
1034
|
+
return { status: 'UP_TO_DATE' };
|
|
1035
|
+
}
|
|
1036
|
+
|
|
1037
|
+
const bundle = checkRes.bundle;
|
|
1038
|
+
// When the presigned download URL (300 s TTL) was issued — a retry past 240 s re-checks.
|
|
1039
|
+
const offeredAt = Date.now();
|
|
1040
|
+
let patchUsed = false;
|
|
1041
|
+
|
|
1042
|
+
// The offer itself. Everything downstream (download, install, activation)
|
|
1043
|
+
// is a conversion against this, so without it the top of the funnel was
|
|
1044
|
+
// unmeasurable and a rollout that never reached devices looked identical
|
|
1045
|
+
// to one whose devices all declined to download.
|
|
1046
|
+
otaEventEmitter.emitSimple('OFFERED', bundle.id, {
|
|
1047
|
+
releaseId: bundle.releaseId,
|
|
1048
|
+
version: bundle.version,
|
|
1049
|
+
});
|
|
1050
|
+
|
|
1051
|
+
// ── DOWNLOAD ─────────────────────────────────────────────────────────
|
|
1052
|
+
// Authenticity is enforced NATIVELY now (see the envelope branch below):
|
|
1053
|
+
// SHA-256 proves the bytes arrived intact, and for signed releases the
|
|
1054
|
+
// RSA signature — checked in native code against keys embedded in the app
|
|
1055
|
+
// package — proves they came from you. JS neither pins keys nor verifies
|
|
1056
|
+
// signatures.
|
|
1057
|
+
otaEventEmitter.emitSimple('DOWNLOAD_STARTED', bundle.id, {
|
|
1058
|
+
releaseId: bundle.releaseId,
|
|
1059
|
+
version: bundle.version,
|
|
1060
|
+
});
|
|
1061
|
+
const downloadStart = Date.now();
|
|
1062
|
+
logger.debug(`[OTA] Downloading update v${bundle.version}…`);
|
|
1063
|
+
|
|
1064
|
+
// Live progress: native tick → DOWNLOAD_PROGRESS (feeds useOtaUpdate) →
|
|
1065
|
+
// throttled POST to the live tracker. `bundle.size` is the authoritative
|
|
1066
|
+
// total — the server just told us — so an unknown native total (-1 on
|
|
1067
|
+
// chunked/gzip bodies) still yields a truthful percentage.
|
|
1068
|
+
let lastPostAt = 0;
|
|
1069
|
+
const postProgress = (bytesRead: number, status: string) => {
|
|
1070
|
+
const total = bundle.size > 0 ? bundle.size : Math.max(bytesRead, 1);
|
|
1071
|
+
const percent = Math.min(100, Math.round((bytesRead / total) * 100));
|
|
1072
|
+
const elapsedS = (Date.now() - downloadStart) / 1000;
|
|
1073
|
+
const payload = {
|
|
1074
|
+
installationId: params.installationId,
|
|
1075
|
+
bundleId: bundle.id,
|
|
1076
|
+
bytesRead,
|
|
1077
|
+
totalBytes: total,
|
|
1078
|
+
progressPercent: percent,
|
|
1079
|
+
speedKbps: elapsedS > 0 ? Math.round(bytesRead / 1024 / elapsedS) : 0,
|
|
1080
|
+
status,
|
|
1081
|
+
};
|
|
1082
|
+
// Fire-and-forget: progress display must never delay or fail the
|
|
1083
|
+
// download it describes.
|
|
1084
|
+
fetch(`${params.apiUrl.replace(/\/+$/, '')}/sdk/ota/progress`, {
|
|
1085
|
+
method: 'POST',
|
|
1086
|
+
headers: {
|
|
1087
|
+
'Content-Type': 'application/json',
|
|
1088
|
+
'x-scalebun-client-key': params.clientKey,
|
|
1089
|
+
},
|
|
1090
|
+
body: JSON.stringify(payload),
|
|
1091
|
+
}).catch(() => {});
|
|
1092
|
+
};
|
|
1093
|
+
|
|
1094
|
+
const unsubscribeProgress = subscribeNativeProgress(({ bytesRead }) => {
|
|
1095
|
+
const total = bundle.size > 0 ? bundle.size : bytesRead;
|
|
1096
|
+
otaEventEmitter.emit({
|
|
1097
|
+
type: 'DOWNLOAD_PROGRESS',
|
|
1098
|
+
bundleId: bundle.id,
|
|
1099
|
+
timestamp: Date.now(),
|
|
1100
|
+
progress: Math.min(100, Math.round((bytesRead / Math.max(total, 1)) * 100)),
|
|
1101
|
+
});
|
|
1102
|
+
const now = Date.now();
|
|
1103
|
+
if (now - lastPostAt >= PROGRESS_POST_INTERVAL_MS) {
|
|
1104
|
+
lastPostAt = now;
|
|
1105
|
+
postProgress(bytesRead, 'DOWNLOADING');
|
|
1106
|
+
}
|
|
1107
|
+
});
|
|
1108
|
+
|
|
1109
|
+
let staged = false;
|
|
1110
|
+
|
|
1111
|
+
if (bundle.envelope) {
|
|
1112
|
+
// ── NATIVE-ENFORCED VERIFY + STAGE (SB-OTA-RSA-SHA256-V1) ───────────
|
|
1113
|
+
// Hand the whole envelope to native code, which verifies the RSA
|
|
1114
|
+
// signature against the keys EMBEDDED in the app package, then downloads
|
|
1115
|
+
// the artifact, enforces the SIGNED size + SHA-256, and stages it into
|
|
1116
|
+
// the `next` slot — all outside JS. JavaScript never stages bytes it has
|
|
1117
|
+
// not proven authentic. No patch path here: a signed offer is always a
|
|
1118
|
+
// full verified download (native patch is a documented follow-up).
|
|
1119
|
+
//
|
|
1120
|
+
// Retried when — and only when — a retry can help (see signedDownload.ts): one dropped
|
|
1121
|
+
// connection used to lose a 19 MB update until the next cold start. A retry after an
|
|
1122
|
+
// expired URL asks the server again, and stops quietly if the release was withdrawn.
|
|
1123
|
+
const native = NativeScaleBunOta;
|
|
1124
|
+
|
|
1125
|
+
// Patch FIRST when the server offered one and this binary can apply it signed. One
|
|
1126
|
+
// attempt, no retry: on any failure the full download below runs exactly as before, so a
|
|
1127
|
+
// patch can only ever save bytes. Security lives in native (verifyAndStagePatch): the
|
|
1128
|
+
// rebuilt bundle must hash to the SIGNED artifactSha256, from a signed base.
|
|
1129
|
+
if (bundle.patch && signedPatchSupported(native)) {
|
|
1130
|
+
const pv = await trySignedPatch(native as any, bundle.envelope, bundle.url, bundle.patch);
|
|
1131
|
+
if (pv.ok) {
|
|
1132
|
+
patchUsed = true;
|
|
1133
|
+
staged = true;
|
|
1134
|
+
logger.debug(`[OTA] Staged v${bundle.version} from a ${(bundle.patch.size / 1024).toFixed(0)} KB patch`);
|
|
1135
|
+
} else {
|
|
1136
|
+
logger.info(`[OTA] Patch not applied (${pv.code ?? "unknown"}) — downloading the full bundle`);
|
|
1137
|
+
}
|
|
1138
|
+
}
|
|
1139
|
+
|
|
1140
|
+
if (!staged) {
|
|
1141
|
+
const result = await stageSignedWithRetry<typeof bundle>(
|
|
1142
|
+
{
|
|
1143
|
+
attempt: async (b) => {
|
|
1144
|
+
try {
|
|
1145
|
+
const verdictJson = await native.verifyAndStageRelease(
|
|
1146
|
+
JSON.stringify({ ...b.envelope, downloadUrl: b.url }),
|
|
1147
|
+
);
|
|
1148
|
+
return JSON.parse(verdictJson) as StageVerdict;
|
|
1149
|
+
} catch (err: any) {
|
|
1150
|
+
// A rejected promise (or an unparseable verdict) is machinery failure,
|
|
1151
|
+
// never a valid result — treat it as a hard failure (not retried).
|
|
1152
|
+
return { ok: false, code: 'verify_stage_threw', message: err?.message ?? String(err) };
|
|
1153
|
+
}
|
|
1154
|
+
},
|
|
1155
|
+
recheck: async () => {
|
|
1156
|
+
const again = await this.checkForUpdate(params);
|
|
1157
|
+
return again.action === 'DOWNLOAD' && again.bundle?.envelope ? again.bundle : null;
|
|
1158
|
+
},
|
|
1159
|
+
now: () => Date.now(),
|
|
1160
|
+
sleep: (ms) => new Promise((r) => setTimeout(r, ms)),
|
|
1161
|
+
},
|
|
1162
|
+
bundle,
|
|
1163
|
+
offeredAt,
|
|
1164
|
+
);
|
|
1165
|
+
const verdict = result.verdict;
|
|
1166
|
+
|
|
1167
|
+
if (result.withdrawn) {
|
|
1168
|
+
unsubscribeProgress();
|
|
1169
|
+
// No progress post: the live tracker's vocabulary has no "withdrawn", and FAILED would
|
|
1170
|
+
// report a release the server stopped offering as a device that failed to download it.
|
|
1171
|
+
logger.info(
|
|
1172
|
+
`[OTA] Release v${bundle.version} is no longer offered — stopping the download without reporting a failure.`,
|
|
1173
|
+
);
|
|
1174
|
+
return { status: 'UP_TO_DATE' };
|
|
1175
|
+
}
|
|
1176
|
+
|
|
1177
|
+
if (!verdict.ok) {
|
|
1178
|
+
unsubscribeProgress();
|
|
1179
|
+
postProgress(0, 'FAILED');
|
|
1180
|
+
logger.error(
|
|
1181
|
+
`[OTA] Native verify/stage refused the release: ${verdict.code ?? 'unknown'}` +
|
|
1182
|
+
`${verdict.message ? ` — ${verdict.message}` : ''}`,
|
|
1183
|
+
);
|
|
1184
|
+
otaEventEmitter.emitSimple('APPLY_FAILED', bundle.id, {
|
|
1185
|
+
releaseId: bundle.releaseId,
|
|
1186
|
+
// `error` is mapped onto the wire `errorCode` by deliverOtaEvents —
|
|
1187
|
+
// carry the stable native verdict code there, with the download
|
|
1188
|
+
// reason when native gave one from the fixed vocabulary
|
|
1189
|
+
// (`DOWNLOAD_FAILED:http_403`). Emitted ONCE, after the last attempt.
|
|
1190
|
+
error: verdictErrorCode(verdict),
|
|
1191
|
+
version: bundle.version,
|
|
1192
|
+
});
|
|
1193
|
+
return {
|
|
1194
|
+
status: 'ERROR',
|
|
1195
|
+
error: `Release verification failed (${verdict.code ?? 'unknown'})`,
|
|
1196
|
+
bundle,
|
|
1197
|
+
};
|
|
1198
|
+
}
|
|
1199
|
+
|
|
1200
|
+
staged = true;
|
|
1201
|
+
}
|
|
1202
|
+
} else {
|
|
1203
|
+
// ── LEGACY / UNSIGNED (SHA-256 integrity only) ─────────────────────
|
|
1204
|
+
// No envelope means the app embeds no trusted keys and the release is
|
|
1205
|
+
// unsigned: keep the pre-RSA behaviour so those apps still update.
|
|
1206
|
+
|
|
1207
|
+
// Sprint 7: Try patch first if offered, fall back to full bundle
|
|
1208
|
+
if (bundle.patch && NativeScaleBunOta.stagePatch) {
|
|
1209
|
+
logger.debug(
|
|
1210
|
+
`[OTA] Patch available (${(bundle.patch.size / 1024).toFixed(0)} KB vs ` +
|
|
1211
|
+
`${(bundle.size / 1024).toFixed(0)} KB full) — trying patch…`,
|
|
1212
|
+
);
|
|
1213
|
+
try {
|
|
1214
|
+
staged = await NativeScaleBunOta.stagePatch(
|
|
1215
|
+
bundle.patch.url,
|
|
1216
|
+
bundle.patch.sha256,
|
|
1217
|
+
bundle.sha256, // expected full-bundle hash after patching
|
|
1218
|
+
);
|
|
1219
|
+
if (staged) {
|
|
1220
|
+
patchUsed = true;
|
|
1221
|
+
logger.debug('[OTA] Patch applied successfully');
|
|
1222
|
+
} else {
|
|
1223
|
+
logger.warn('[OTA] Patch staging returned false — falling back to full bundle');
|
|
1224
|
+
}
|
|
1225
|
+
} catch (patchErr: any) {
|
|
1226
|
+
logger.warn(`[OTA] Patch staging failed: ${patchErr.message} — falling back to full bundle`);
|
|
1227
|
+
}
|
|
1228
|
+
}
|
|
1229
|
+
|
|
1230
|
+
// Full-bundle fallback (or primary path when no patch is available).
|
|
1231
|
+
// Retried with backoff: on a mobile network a transient drop mid-download
|
|
1232
|
+
// is the common case, and previously one failure meant waiting for the
|
|
1233
|
+
// next cold start — often hours away, sometimes never.
|
|
1234
|
+
if (!staged) {
|
|
1235
|
+
try {
|
|
1236
|
+
staged = await retryWithBackoff(
|
|
1237
|
+
async () => {
|
|
1238
|
+
const ok = await NativeScaleBunOta!.stageBundle(bundle.url, bundle.sha256);
|
|
1239
|
+
if (!ok) {
|
|
1240
|
+
// Native returns a bare boolean, so a hash mismatch and a
|
|
1241
|
+
// dropped connection are indistinguishable here. Retrying a
|
|
1242
|
+
// genuine mismatch a couple of times is cheap; not retrying a
|
|
1243
|
+
// dropped connection costs the whole update.
|
|
1244
|
+
throw new Error('stageBundle returned false');
|
|
1245
|
+
}
|
|
1246
|
+
return ok;
|
|
1247
|
+
},
|
|
1248
|
+
{ label: `download v${bundle.version}`, attempts: 3 },
|
|
1249
|
+
);
|
|
1250
|
+
} catch {
|
|
1251
|
+
staged = false;
|
|
1252
|
+
}
|
|
1253
|
+
}
|
|
1254
|
+
}
|
|
1255
|
+
|
|
1256
|
+
unsubscribeProgress();
|
|
1257
|
+
|
|
1258
|
+
if (!staged) {
|
|
1259
|
+
postProgress(0, 'FAILED');
|
|
1260
|
+
logger.error('[OTA] Staging bundle failed after retries');
|
|
1261
|
+
otaEventEmitter.emitSimple('APPLY_FAILED', bundle.id, {
|
|
1262
|
+
releaseId: bundle.releaseId,
|
|
1263
|
+
error: 'Staging failed — SHA-256 mismatch or download error',
|
|
1264
|
+
version: bundle.version,
|
|
1265
|
+
});
|
|
1266
|
+
return { status: 'ERROR', error: 'Staging bundle failed', bundle };
|
|
1267
|
+
}
|
|
1268
|
+
|
|
1269
|
+
postProgress(bundle.size, 'COMPLETED');
|
|
1270
|
+
|
|
1271
|
+
const downloadDuration = Date.now() - downloadStart;
|
|
1272
|
+
otaEventEmitter.emitSimple('DOWNLOAD_COMPLETE', bundle.id, {
|
|
1273
|
+
releaseId: bundle.releaseId,
|
|
1274
|
+
version: bundle.version,
|
|
1275
|
+
durationMs: downloadDuration,
|
|
1276
|
+
patchUsed,
|
|
1277
|
+
});
|
|
1278
|
+
|
|
1279
|
+
// ── APPLY ────────────────────────────────────────────────────────────
|
|
1280
|
+
// 2. Apply update atomically (next→current, current→previous, sets boot marker)
|
|
1281
|
+
const applied = await NativeScaleBunOta.applyUpdate();
|
|
1282
|
+
if (!applied) {
|
|
1283
|
+
logger.error('[OTA] Applying update failed');
|
|
1284
|
+
otaEventEmitter.emitSimple('APPLY_FAILED', bundle.id, {
|
|
1285
|
+
releaseId: bundle.releaseId,
|
|
1286
|
+
error: 'Atomic slot swap failed',
|
|
1287
|
+
version: bundle.version,
|
|
1288
|
+
});
|
|
1289
|
+
return { status: 'ERROR', error: 'Applying update failed', bundle };
|
|
1290
|
+
}
|
|
1291
|
+
|
|
1292
|
+
// NOT `this.currentBundle` — see the field docs. This process is still
|
|
1293
|
+
// executing the previous bundle; the staged one becomes current when a
|
|
1294
|
+
// restart binds it, and hydrateCurrentBundleFromSlots reads it back from
|
|
1295
|
+
// the install record on the next launch.
|
|
1296
|
+
this.pendingBundle = bundle;
|
|
1297
|
+
this.isRestartRequiredState = true;
|
|
1298
|
+
|
|
1299
|
+
// Written BEFORE the restart, so the next boot can prove whether this
|
|
1300
|
+
// install actually took effect. Only recorded when the server told us the
|
|
1301
|
+
// bundle carries a marker — otherwise there is nothing to compare against
|
|
1302
|
+
// and a recorded expectation would produce a false alarm.
|
|
1303
|
+
// Recorded unconditionally, not only when a marker is present: this is
|
|
1304
|
+
// also the record that tells the next launch WHICH bundle the slot's
|
|
1305
|
+
// sha256 belongs to, without which the device cannot report what it is
|
|
1306
|
+
// running. Identity verification is the second job of this record, and
|
|
1307
|
+
// it is skipped when there is no marker to compare.
|
|
1308
|
+
this.recordInstallExpectation(bundle);
|
|
1309
|
+
|
|
1310
|
+
otaEventEmitter.emitSimple('INSTALLED', bundle.id, {
|
|
1311
|
+
releaseId: bundle.releaseId,
|
|
1312
|
+
version: bundle.version,
|
|
1313
|
+
});
|
|
1314
|
+
logger.debug(`[OTA] Update v${bundle.version} installed successfully!`);
|
|
1315
|
+
|
|
1316
|
+
// Deliver telemetry BEFORE a restart tears the JS runtime down —
|
|
1317
|
+
// an INSTALLED event queued but not sent would vanish with the process.
|
|
1318
|
+
await deliverOtaEvents(params);
|
|
1319
|
+
|
|
1320
|
+
// Activation is driven STRICTLY by the release's installMode. The legacy
|
|
1321
|
+
// client `autoRestart` override is pinned false in sync() (see above), so
|
|
1322
|
+
// it no longer participates — a release activates exactly as published:
|
|
1323
|
+
// IMMEDIATE restarts now, ON_NEXT_RESUME on next foreground, and
|
|
1324
|
+
// ON_NEXT_RESTART (the default) on the next cold start.
|
|
1325
|
+
const installMode = bundle.installMode ?? 'ON_NEXT_RESTART';
|
|
1326
|
+
if (installMode === 'IMMEDIATE') {
|
|
1327
|
+
this.restart();
|
|
1328
|
+
} else if (installMode === 'ON_NEXT_RESUME') {
|
|
1329
|
+
this.armResumeRestart();
|
|
1330
|
+
}
|
|
1331
|
+
|
|
1332
|
+
return {
|
|
1333
|
+
status: 'UPDATE_INSTALLED',
|
|
1334
|
+
bundle,
|
|
1335
|
+
isMandatory: bundle.isMandatory,
|
|
1336
|
+
};
|
|
1337
|
+
} catch (err: any) {
|
|
1338
|
+
logger.error(`[OTA] Sync error: ${err.message}`);
|
|
1339
|
+
return { status: 'ERROR', error: err.message ?? 'Unexpected sync failure' };
|
|
1340
|
+
} finally {
|
|
1341
|
+
// Every other exit (UP_TO_DATE, ROLLBACK, staging failure, throw)
|
|
1342
|
+
// delivers here. This also carries events queued at INIT — boot-guard
|
|
1343
|
+
// AUTO_ROLLBACKs and identity-check APPLY_FAILEDs fire before any
|
|
1344
|
+
// apiUrl/clientKey exist, so they wait in the queue for the first sync.
|
|
1345
|
+
// Fire-and-forget: delivery is never allowed to delay a result.
|
|
1346
|
+
void deliverOtaEvents(params);
|
|
1347
|
+
}
|
|
1348
|
+
}
|
|
1349
|
+
|
|
1350
|
+
/**
|
|
1351
|
+
* Sprint 5 (S5-NAT-1): Boot-guard heartbeat.
|
|
1352
|
+
*
|
|
1353
|
+
* After booting from an OTA bundle, start a timer. If the app survives
|
|
1354
|
+
* `healthyAfterMs` without crashing, call `markHealthy()` to clear the
|
|
1355
|
+
* native boot-guard marker. If JS crashes before the timer fires, the
|
|
1356
|
+
* marker stays set and native code reverts on next cold start.
|
|
1357
|
+
*/
|
|
1358
|
+
private startBootGuardHeartbeat(preRead?: Record<string, any> | null): void {
|
|
1359
|
+
if (!NativeScaleBunOta) return;
|
|
1360
|
+
// A render error already condemned this launch; nothing may re-arm the heartbeat.
|
|
1361
|
+
if (this.bootFailedThisLaunch) return;
|
|
1362
|
+
|
|
1363
|
+
// Cancel any existing timer
|
|
1364
|
+
if (this.healthyTimer) {
|
|
1365
|
+
clearTimeout(this.healthyTimer);
|
|
1366
|
+
this.healthyTimer = null;
|
|
1367
|
+
}
|
|
1368
|
+
|
|
1369
|
+
try {
|
|
1370
|
+
// getSlotState() is CONSUME-ON-READ for the revert record, so a caller that
|
|
1371
|
+
// has already read it passes the parsed result down rather than letting
|
|
1372
|
+
// this read again and swallow a rollback. `null` is a real answer (module
|
|
1373
|
+
// absent or payload unreadable) — only `undefined` means "nobody read it".
|
|
1374
|
+
const state =
|
|
1375
|
+
preRead !== undefined
|
|
1376
|
+
? preRead
|
|
1377
|
+
: (JSON.parse(NativeScaleBunOta.getSlotState()) as Record<string, any>);
|
|
1378
|
+
|
|
1379
|
+
// Only run the heartbeat if boot-guard marker is present
|
|
1380
|
+
// (meaning we're running from a freshly-applied OTA bundle)
|
|
1381
|
+
if (!state?.bootMarkerPresent) {
|
|
1382
|
+
return;
|
|
1383
|
+
}
|
|
1384
|
+
|
|
1385
|
+
const healthyMs = this.bootGuardConfig.healthyAfterMs ?? DEFAULT_HEALTHY_AFTER_MS;
|
|
1386
|
+
logger.debug(`[OTA] Boot guard active — marking healthy after ${healthyMs}ms`);
|
|
1387
|
+
|
|
1388
|
+
this.healthyTimer = setTimeout(() => {
|
|
1389
|
+
noThrow(() => {
|
|
1390
|
+
NativeScaleBunOta?.markHealthy();
|
|
1391
|
+
logger.info('[OTA] Boot guard cleared — bundle marked healthy ✓');
|
|
1392
|
+
this.healthyTimer = null;
|
|
1393
|
+
|
|
1394
|
+
// The honest activation signal. INSTALLED is emitted optimistically,
|
|
1395
|
+
// BEFORE the bundle has ever executed, so counting it as adoption
|
|
1396
|
+
// credits bundles that were installed and then crash-reverted. This
|
|
1397
|
+
// fires only once the bundle has actually booted and survived.
|
|
1398
|
+
const running = this.currentBundle;
|
|
1399
|
+
if (running?.id) {
|
|
1400
|
+
otaEventEmitter.emitSimple('BOOT_SUCCESS', running.id, {
|
|
1401
|
+
releaseId: running.releaseId,
|
|
1402
|
+
version: running.version,
|
|
1403
|
+
durationMs: healthyMs,
|
|
1404
|
+
});
|
|
1405
|
+
}
|
|
1406
|
+
});
|
|
1407
|
+
}, healthyMs);
|
|
1408
|
+
} catch {
|
|
1409
|
+
// State parsing failed — non-fatal, don't block
|
|
1410
|
+
}
|
|
1411
|
+
}
|
|
1412
|
+
|
|
1413
|
+
private bootFailedThisLaunch = false;
|
|
1414
|
+
|
|
1415
|
+
/**
|
|
1416
|
+
* A render error was caught while a freshly-applied bundle is still inside its health window.
|
|
1417
|
+
*
|
|
1418
|
+
* WHY. The boot guard's only evidence of a bad bundle is that the process died before
|
|
1419
|
+
* `healthyAfterMs`. An error boundary with a fallback keeps the process ALIVE: the user sees the
|
|
1420
|
+
* fallback, the timer fires, `markHealthy()` runs, and a bundle that cannot render its first
|
|
1421
|
+
* screen is recorded as healthy and never reverted. ScaleBunErrorBoundary calls this so the
|
|
1422
|
+
* marker stays set: the next cold start counts toward the bundle's revert exactly as a crash
|
|
1423
|
+
* would have.
|
|
1424
|
+
*
|
|
1425
|
+
* No-op outside the window (the bundle already proved itself, or this launch is not an OTA
|
|
1426
|
+
* bundle's first run), so a boundary catching an ordinary bug later in the session never
|
|
1427
|
+
* condemns a healthy release.
|
|
1428
|
+
*
|
|
1429
|
+
* @returns true when the window was open and `markHealthy` has been withheld.
|
|
1430
|
+
*/
|
|
1431
|
+
noteBootFailure(): boolean {
|
|
1432
|
+
if (!this.healthyTimer) return false;
|
|
1433
|
+
clearTimeout(this.healthyTimer);
|
|
1434
|
+
this.healthyTimer = null;
|
|
1435
|
+
this.bootFailedThisLaunch = true;
|
|
1436
|
+
logger.warn(
|
|
1437
|
+
'[OTA] Boot guard: a render error was caught inside the health window — this bundle is NOT ' +
|
|
1438
|
+
'marked healthy, and the next cold start counts toward reverting it.',
|
|
1439
|
+
);
|
|
1440
|
+
return true;
|
|
1441
|
+
}
|
|
1442
|
+
|
|
1443
|
+
/** One-shot guard so repeated syncs cannot stack resume listeners. */
|
|
1444
|
+
private resumeRestartArmed = false;
|
|
1445
|
+
|
|
1446
|
+
/**
|
|
1447
|
+
* ON_NEXT_RESUME: restart the JS runtime the next time the app returns to
|
|
1448
|
+
* the foreground after genuinely being in the background.
|
|
1449
|
+
*
|
|
1450
|
+
* The staged bundle is already applied at this point — the restart is purely
|
|
1451
|
+
* about WHEN the user experiences the reload. Two deliberate choices:
|
|
1452
|
+
*
|
|
1453
|
+
* - Armed only after a real background→active transition, never on the
|
|
1454
|
+
* initial 'active' state, so installing while the user is mid-session
|
|
1455
|
+
* does not instantly yank the screen out from under them.
|
|
1456
|
+
* - One-shot. The listener removes itself after firing (or after the
|
|
1457
|
+
* restart makes the whole question moot).
|
|
1458
|
+
*/
|
|
1459
|
+
private armResumeRestart(): void {
|
|
1460
|
+
if (this.resumeRestartArmed) return;
|
|
1461
|
+
this.resumeRestartArmed = true;
|
|
1462
|
+
|
|
1463
|
+
let wasBackground = false;
|
|
1464
|
+
try {
|
|
1465
|
+
const sub = AppState.addEventListener('change', (state) => {
|
|
1466
|
+
if (state === 'background') {
|
|
1467
|
+
wasBackground = true;
|
|
1468
|
+
return;
|
|
1469
|
+
}
|
|
1470
|
+
if (state === 'active' && wasBackground) {
|
|
1471
|
+
try {
|
|
1472
|
+
sub.remove();
|
|
1473
|
+
} catch {
|
|
1474
|
+
/* the restart below makes cleanup moot */
|
|
1475
|
+
}
|
|
1476
|
+
this.resumeRestartArmed = false;
|
|
1477
|
+
logger.info('[OTA] App resumed — activating the staged update now');
|
|
1478
|
+
this.restart();
|
|
1479
|
+
}
|
|
1480
|
+
});
|
|
1481
|
+
logger.debug('[OTA] Update staged — will activate on next resume (ON_NEXT_RESUME)');
|
|
1482
|
+
} catch {
|
|
1483
|
+
// AppState unavailable (tests, exotic hosts): the update still activates
|
|
1484
|
+
// on the next cold start, which is the documented fallback behaviour.
|
|
1485
|
+
this.resumeRestartArmed = false;
|
|
1486
|
+
}
|
|
1487
|
+
}
|
|
1488
|
+
|
|
1489
|
+
/**
|
|
1490
|
+
* Reload the JS app bundle to apply the new update immediately.
|
|
1491
|
+
*
|
|
1492
|
+
* Order matters: the native `restartApp()` is the only path that works in
|
|
1493
|
+
* RELEASE builds. `DevSettings.reload()` looks callable there too — it
|
|
1494
|
+
* logged, returned, and reloaded nothing when tested live on a release APK,
|
|
1495
|
+
* which made every "restart" this SDK ever promised a silent no-op in
|
|
1496
|
+
* production. DevSettings remains as the dev-build fallback for hosts that
|
|
1497
|
+
* have not rebuilt native yet.
|
|
1498
|
+
*/
|
|
1499
|
+
restart(): void {
|
|
1500
|
+
noThrow(() => {
|
|
1501
|
+
logger.info('[OTA] Triggering app restart…');
|
|
1502
|
+
if (NativeScaleBunOta && typeof NativeScaleBunOta.restartApp === 'function') {
|
|
1503
|
+
NativeScaleBunOta.restartApp();
|
|
1504
|
+
} else if (DevSettings && typeof DevSettings.reload === 'function') {
|
|
1505
|
+
DevSettings.reload();
|
|
1506
|
+
} else if (NativeModules.RNReload) {
|
|
1507
|
+
NativeModules.RNReload.reload();
|
|
1508
|
+
}
|
|
1509
|
+
});
|
|
1510
|
+
}
|
|
1511
|
+
}
|
|
1512
|
+
|
|
1513
|
+
export const otaOrchestrator = new OtaOrchestrator();
|