@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,1687 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ScaleBun SDK — Session Manager
|
|
3
|
+
*
|
|
4
|
+
* Central orchestrator for the unified session system.
|
|
5
|
+
* Manages session lifecycle, wires pipeline + capture + transport + sync.
|
|
6
|
+
*
|
|
7
|
+
* Architecture:
|
|
8
|
+
* - Singleton per debug session
|
|
9
|
+
* - Creates and owns JourneyEventPipeline, ReplayCaptureManager,
|
|
10
|
+
* DesktopSessionTransport, SyncDecisionEngine
|
|
11
|
+
* - Receives events from ScaleBunDebugRoot (nav, interactions), DebugEventStream
|
|
12
|
+
* (network, logs), and breadcrumbs
|
|
13
|
+
* - Coordinates crash anchoring across events AND frames
|
|
14
|
+
* - Auto-timeout via maxDurationMs
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { frameLinkVerdict } from './frameLink';
|
|
18
|
+
import { logger } from '../../core/logger/internalLogger';
|
|
19
|
+
import type { DebugTransport } from '../../debug/transport';
|
|
20
|
+
import type {
|
|
21
|
+
Session,
|
|
22
|
+
SessionMetadata,
|
|
23
|
+
SessionReplayConfig,
|
|
24
|
+
JourneyEventType,
|
|
25
|
+
JourneyEventSeverity,
|
|
26
|
+
JourneyEventSource,
|
|
27
|
+
CrashAnchor,
|
|
28
|
+
NativeSyncMetadata,
|
|
29
|
+
SyncDecisionCallback,
|
|
30
|
+
} from './sessionTypes';
|
|
31
|
+
import { DEFAULT_SESSION_REPLAY_CONFIG } from './sessionTypes';
|
|
32
|
+
import { JourneyEventPipeline } from './JourneyEventPipeline';
|
|
33
|
+
import { ReplayCaptureManager, type NativeCaptureFn, type OutboxFrameCapture } from './ReplayCaptureManager';
|
|
34
|
+
import { makeOutboxFrameCapture } from './outboxFrameCapture';
|
|
35
|
+
import { DesktopSessionTransport } from './DesktopSessionTransport';
|
|
36
|
+
import { SyncDecisionEngine } from './SyncDecisionEngine';
|
|
37
|
+
import { ScrollTracker, type ScrollMetadata } from './ScrollTracker';
|
|
38
|
+
import type { BackendSessionAdapter } from './BackendSessionAdapter';
|
|
39
|
+
import type { JourneyEvent } from './sessionTypes';
|
|
40
|
+
import { generateEventId } from '../replay/core/ids/sessionId';
|
|
41
|
+
import { bridgeAdapter } from '../replay/bridge/adapters/bridgeAdapter';
|
|
42
|
+
import { redactBody } from '../../debug/redaction';
|
|
43
|
+
import { consumeCalibrationTarget } from '../journey/calibrationContext';
|
|
44
|
+
import { resolvePlatformOS } from '../../core/context/device';
|
|
45
|
+
import { emitAutomaticEvent } from '../../analytics/automaticEvents';
|
|
46
|
+
import { checkViewport } from './viewportPlausibility';
|
|
47
|
+
import { quotaState } from '../../config/quotaState';
|
|
48
|
+
import {
|
|
49
|
+
INTERACTION_PROTOCOL_VERSION,
|
|
50
|
+
automaticInteractionProperties,
|
|
51
|
+
generateInteractionId,
|
|
52
|
+
type InteractionStateStatus,
|
|
53
|
+
} from '../journey/interactionProtocol';
|
|
54
|
+
|
|
55
|
+
// ─── Types ──────────────────────────────────────────────────────────────────
|
|
56
|
+
|
|
57
|
+
export interface SessionManagerConfig {
|
|
58
|
+
sessionReplay?: Partial<SessionReplayConfig>;
|
|
59
|
+
syncPolicy?: SyncDecisionCallback;
|
|
60
|
+
/**
|
|
61
|
+
* Feature #4 (heatmaps): when true, tap/gesture coordinates are ALSO routed
|
|
62
|
+
* to the always-on analytics lane so coords are captured even when no replay
|
|
63
|
+
* recording is active. Additive — does NOT change replay-recording capture.
|
|
64
|
+
* Unset → false at this layer (safety floor for direct construction); the
|
|
65
|
+
* product default (true) is applied by ConfigSchema and passed in by the
|
|
66
|
+
* bootstrapper. See ConfigSchema.captureInteractionHeatmap.
|
|
67
|
+
*/
|
|
68
|
+
captureInteractionHeatmap?: boolean;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// ─── Singleton ──────────────────────────────────────────────────────────────
|
|
72
|
+
|
|
73
|
+
let _instance: SessionManager | null = null;
|
|
74
|
+
|
|
75
|
+
// ─── Session Manager ────────────────────────────────────────────────────────
|
|
76
|
+
|
|
77
|
+
export class SessionManager {
|
|
78
|
+
private desktopTransport: DesktopSessionTransport | null = null;
|
|
79
|
+
private pipeline: JourneyEventPipeline;
|
|
80
|
+
private captureManager: ReplayCaptureManager;
|
|
81
|
+
private syncEngine: SyncDecisionEngine;
|
|
82
|
+
private scrollTracker: ScrollTracker | null = null;
|
|
83
|
+
private _backendTransport: BackendSessionAdapter | null = null;
|
|
84
|
+
private config: SessionReplayConfig;
|
|
85
|
+
/**
|
|
86
|
+
* Feature #4 (heatmaps): route tap/gesture coords to the always-on analytics
|
|
87
|
+
* lane even when no replay recording is active. Additive, opt-in (default off).
|
|
88
|
+
*/
|
|
89
|
+
private _captureInteractionHeatmap = false;
|
|
90
|
+
private session: Session | null = null;
|
|
91
|
+
private active = false;
|
|
92
|
+
private timeoutTimer: ReturnType<typeof setTimeout> | null = null;
|
|
93
|
+
private crashAnchors: CrashAnchor[] = [];
|
|
94
|
+
/** Tracks loading-start timestamps per screen for duration computation */
|
|
95
|
+
private loadingStartTimes = new Map<string, number>();
|
|
96
|
+
/**
|
|
97
|
+
* Always-tracked current screen name.
|
|
98
|
+
* Updated by onScreenChange even when session is inactive, so that when
|
|
99
|
+
* startSession is called we know what screen the user is currently on and
|
|
100
|
+
* can emit the initial SCREEN_VIEW + navigation capture immediately.
|
|
101
|
+
* This is read-only metadata — does NOT produce events or frames while inactive.
|
|
102
|
+
*/
|
|
103
|
+
private _lastKnownScreen: string | null = null;
|
|
104
|
+
/** When the current screen was entered — the basis for SCREEN_EXIT's measured dwellMs. */
|
|
105
|
+
private _screenEnteredAt: number | null = null;
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Last screenshot-base dimensions reported by the native interaction tracker.
|
|
109
|
+
* Stored in JS-coordinate units (dp on Android, pt on iOS) — both are the same
|
|
110
|
+
* unit RN uses for pageX/pageY, so JS-fallback taps can divide directly without
|
|
111
|
+
* additional conversion. This is the EXACT base the native screenshot is captured
|
|
112
|
+
* in, and avoids using Dimensions.get('window') (which excludes system insets
|
|
113
|
+
* differently across devices and pushes markers ~10% off vertically).
|
|
114
|
+
*/
|
|
115
|
+
private _lastNativeViewportJsUnits: { width: number; height: number; space: string } | null = null;
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Exact-duplicate gesture suppression (QA: one physical tap recorded 2–3×).
|
|
119
|
+
*
|
|
120
|
+
* Measured in production data: the native tracker re-emitted the SAME tap — identical
|
|
121
|
+
* gestureType and identical coordinates to the SUB-PIXEL (e.g. x=466.962890625) — at
|
|
122
|
+
* 160–240ms gaps, sometimes twice. A human re-tap cannot reproduce a sub-pixel float
|
|
123
|
+
* exactly, so exact coordinate equality within the window is a safe discriminator: real
|
|
124
|
+
* double-taps land on at least slightly different pixels and pass through untouched.
|
|
125
|
+
* The pipeline's own 100ms dedup was too narrow for these; this window covers what was
|
|
126
|
+
* actually observed with margin. Sits at the ONE entry point every gesture source funnels
|
|
127
|
+
* through, so recording and analytics lanes are protected equally.
|
|
128
|
+
*/
|
|
129
|
+
private _lastGestureSig: string | null = null;
|
|
130
|
+
private _lastGestureTs = 0;
|
|
131
|
+
private static readonly GESTURE_DEDUP_WINDOW_MS = 400;
|
|
132
|
+
|
|
133
|
+
private _nativeCapture: NativeCaptureFn;
|
|
134
|
+
private _rawTransport: DebugTransport | null;
|
|
135
|
+
/** Memoized native file-outbox frame adapter (gated by the backend transport). */
|
|
136
|
+
private _outboxFrameCapture: OutboxFrameCapture | null = null;
|
|
137
|
+
/** Unsubscribe from quotaState (released in resetInstance). */
|
|
138
|
+
private _quotaUnsub: (() => void) | null = null;
|
|
139
|
+
/** True while frame capture is paused by a `frames` quota block inside an active session. */
|
|
140
|
+
private _framesQuotaPaused = false;
|
|
141
|
+
/**
|
|
142
|
+
* Whether capture has run at all in the CURRENT session. Decides start() vs resume() when a
|
|
143
|
+
* frames block lifts: resume() continues the session's sequence, which is only right if this
|
|
144
|
+
* session already produced frames; a session that began blocked gets a clean start().
|
|
145
|
+
*/
|
|
146
|
+
private _captureStartedThisSession = false;
|
|
147
|
+
|
|
148
|
+
constructor(
|
|
149
|
+
debugTransport: DebugTransport | null,
|
|
150
|
+
nativeCapture: NativeCaptureFn,
|
|
151
|
+
managerConfig?: SessionManagerConfig,
|
|
152
|
+
) {
|
|
153
|
+
this._rawTransport = debugTransport;
|
|
154
|
+
this._nativeCapture = nativeCapture;
|
|
155
|
+
|
|
156
|
+
// Merge config with defaults
|
|
157
|
+
this.config = {
|
|
158
|
+
...DEFAULT_SESSION_REPLAY_CONFIG,
|
|
159
|
+
...managerConfig?.sessionReplay,
|
|
160
|
+
privacyPolicy: {
|
|
161
|
+
...DEFAULT_SESSION_REPLAY_CONFIG.privacyPolicy,
|
|
162
|
+
...managerConfig?.sessionReplay?.privacyPolicy,
|
|
163
|
+
},
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
// Create subsystems
|
|
167
|
+
this.desktopTransport = debugTransport ? new DesktopSessionTransport(debugTransport) : null;
|
|
168
|
+
|
|
169
|
+
this.pipeline = new JourneyEventPipeline('', {
|
|
170
|
+
maxEventCount: this.config.maxEventCount,
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
this.captureManager = new ReplayCaptureManager(
|
|
174
|
+
'',
|
|
175
|
+
this.config,
|
|
176
|
+
nativeCapture,
|
|
177
|
+
this._getOutboxFrameCapture(),
|
|
178
|
+
);
|
|
179
|
+
|
|
180
|
+
this._captureInteractionHeatmap = managerConfig?.captureInteractionHeatmap === true;
|
|
181
|
+
|
|
182
|
+
this.syncEngine = new SyncDecisionEngine(managerConfig?.syncPolicy);
|
|
183
|
+
|
|
184
|
+
// Wire pipeline → transport (desktop + optional backend)
|
|
185
|
+
this.pipeline.setEmitCallback((event) => {
|
|
186
|
+
this.desktopTransport?.sendEvent(event);
|
|
187
|
+
this._backendTransport?.sendEvent(event);
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
// Wire capture → transport (desktop + optional backend)
|
|
191
|
+
this.captureManager.setFrameEmitCallback((frame, imageData) => {
|
|
192
|
+
this.desktopTransport?.sendFrame(frame, imageData);
|
|
193
|
+
this._backendTransport?.sendFrame(frame, imageData);
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
this._wireLoadingIntervals();
|
|
197
|
+
|
|
198
|
+
// Per-feature quota (contract v1): a `frames` block stops capture NOW (not just upload), and
|
|
199
|
+
// its end resumes capture in the SAME session starting from a fresh keyframe.
|
|
200
|
+
this._quotaUnsub = quotaState.subscribe((feature, blocked) => {
|
|
201
|
+
if (feature === 'frames') this._onFramesQuota(blocked);
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Stop or resume frame capture for a `frames` quota block.
|
|
207
|
+
*
|
|
208
|
+
* Stopping goes through `captureManager.stop()` — every pending capture timer is cancelled and
|
|
209
|
+
* the generation bump discards captures already in flight. The adapter purges what was queued.
|
|
210
|
+
* The session itself keeps running: events, network and errors are governed by their own quotas.
|
|
211
|
+
*/
|
|
212
|
+
private _onFramesQuota(blocked: boolean): void {
|
|
213
|
+
try {
|
|
214
|
+
if (blocked) {
|
|
215
|
+
if (this.active && !this._framesQuotaPaused) {
|
|
216
|
+
this.captureManager.stop('quota_pause');
|
|
217
|
+
this._framesQuotaPaused = true;
|
|
218
|
+
logger.warn('[SessionManager] Replay frames quota exceeded — frame capture stopped');
|
|
219
|
+
}
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
if (!this._framesQuotaPaused) return;
|
|
223
|
+
this._framesQuotaPaused = false;
|
|
224
|
+
if (!this.active) return;
|
|
225
|
+
if (this._captureStartedThisSession) {
|
|
226
|
+
this.captureManager.resume();
|
|
227
|
+
} else {
|
|
228
|
+
this.captureManager.start(); // fresh per-session state: sequence 0, keyframe first
|
|
229
|
+
this._captureStartedThisSession = true;
|
|
230
|
+
}
|
|
231
|
+
// A keyframe right away: the frames before the pause were discarded, so without one the
|
|
232
|
+
// replay would show nothing until the next navigation/interaction capture.
|
|
233
|
+
this.captureManager.captureManual('manual').catch(() => { });
|
|
234
|
+
__DEV__ && logger.info('[SessionManager] Replay frames quota lifted — capture resumed with a keyframe');
|
|
235
|
+
} catch (err) {
|
|
236
|
+
logger.warn('[SessionManager] frames quota transition failed:', err);
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* A wait the capture manager sat through becomes a pair of journey events.
|
|
242
|
+
*
|
|
243
|
+
* The frame it eventually takes shows the LOADED screen, which leaves the span between the
|
|
244
|
+
* tap and that frame unaccounted for — and the player resolves an unaccounted playhead to the
|
|
245
|
+
* previous frame, so it would show the screen the visitor had already left. These two events
|
|
246
|
+
* are what lets the timeline say "this screen was loading, for this long" instead, at roughly
|
|
247
|
+
* 1 KB against the ~56 KB a second screenshot would cost.
|
|
248
|
+
*
|
|
249
|
+
* `reason` rides on the payload rather than becoming a third event type: `timeout` and
|
|
250
|
+
* `network_idle` are the same interval with different endings, and a consumer that does not
|
|
251
|
+
* know about the distinction should still see the band.
|
|
252
|
+
*/
|
|
253
|
+
private _wireLoadingIntervals(): void {
|
|
254
|
+
this.captureManager.setLoadingIntervalCallback((phase, screen, detail) => {
|
|
255
|
+
if (phase === 'start') {
|
|
256
|
+
this.emitEvent('LOADING_STARTED', {
|
|
257
|
+
screen,
|
|
258
|
+
payload: { screen, source: 'replay-hold' },
|
|
259
|
+
source: 'sdk',
|
|
260
|
+
});
|
|
261
|
+
return;
|
|
262
|
+
}
|
|
263
|
+
this.emitEvent('LOADING_COMPLETED', {
|
|
264
|
+
screen,
|
|
265
|
+
payload: {
|
|
266
|
+
screen,
|
|
267
|
+
durationMs: detail.durationMs,
|
|
268
|
+
reason: detail.reason,
|
|
269
|
+
source: 'replay-hold',
|
|
270
|
+
},
|
|
271
|
+
source: 'sdk',
|
|
272
|
+
});
|
|
273
|
+
});
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// ─── Singleton Access ───────────────────────────────────────────────
|
|
277
|
+
|
|
278
|
+
static getInstance(
|
|
279
|
+
debugTransport: DebugTransport | null,
|
|
280
|
+
nativeCapture: NativeCaptureFn,
|
|
281
|
+
config?: SessionManagerConfig,
|
|
282
|
+
): SessionManager {
|
|
283
|
+
if (!_instance) {
|
|
284
|
+
_instance = new SessionManager(debugTransport, nativeCapture, config);
|
|
285
|
+
}
|
|
286
|
+
return _instance;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
static getExistingInstance(): SessionManager | null {
|
|
290
|
+
return _instance;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
static resetInstance(): void {
|
|
294
|
+
if (_instance) {
|
|
295
|
+
_instance.endSession();
|
|
296
|
+
_instance._quotaUnsub?.();
|
|
297
|
+
_instance._quotaUnsub = null;
|
|
298
|
+
}
|
|
299
|
+
_instance = null;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
/**
|
|
303
|
+
* Attach a backend transport for uploading replay data to the backend API.
|
|
304
|
+
* This is additive — the existing desktop transport remains active if present.
|
|
305
|
+
*/
|
|
306
|
+
setBackendTransport(transport: BackendSessionAdapter): void {
|
|
307
|
+
this._backendTransport = transport;
|
|
308
|
+
// A session whose START the backend refused for quota has no row and never will: end the
|
|
309
|
+
// recording instead of capturing into a transport that drops everything for it.
|
|
310
|
+
transport.setSessionRefusedHandler?.((sessionId) => {
|
|
311
|
+
if (this.active && this.currentSessionId === sessionId) this.endSession();
|
|
312
|
+
});
|
|
313
|
+
__DEV__ && logger.info('[SessionManager] Backend transport attached (replay → backend upload enabled)');
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
/** Expose the backend transport so features can queue network/errors/performance. */
|
|
317
|
+
getBackendTransport(): BackendSessionAdapter | null {
|
|
318
|
+
return this._backendTransport;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
/**
|
|
322
|
+
* Memoized native file-outbox frame adapter. Always injected into the capture
|
|
323
|
+
* manager; its readiness gate (backed by the backend transport) resolves false
|
|
324
|
+
* when the outbox flags are off / the binary is old, so capture transparently
|
|
325
|
+
* falls back to the legacy `captureScreenshot` path. The closure reads the
|
|
326
|
+
* backend transport lazily at capture time (it attaches after construction).
|
|
327
|
+
*/
|
|
328
|
+
private _getOutboxFrameCapture(): OutboxFrameCapture {
|
|
329
|
+
if (!this._outboxFrameCapture) {
|
|
330
|
+
this._outboxFrameCapture = makeOutboxFrameCapture(
|
|
331
|
+
() => this._backendTransport?.ensureNativeOutboxReady() ?? Promise.resolve(false),
|
|
332
|
+
);
|
|
333
|
+
}
|
|
334
|
+
return this._outboxFrameCapture;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
/**
|
|
338
|
+
* Attach a desktop transport dynamically.
|
|
339
|
+
* Called when debug mode connects after SDK initialization.
|
|
340
|
+
*/
|
|
341
|
+
setDesktopTransport(debugTransport: DebugTransport): void {
|
|
342
|
+
this.desktopTransport = new DesktopSessionTransport(debugTransport);
|
|
343
|
+
__DEV__ && logger.info('[SessionManager] Desktop transport attached');
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
// ─── Session Lifecycle ──────────────────────────────────────────────
|
|
347
|
+
|
|
348
|
+
get isActive(): boolean {
|
|
349
|
+
return this.active;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
get currentSessionId(): string | null {
|
|
353
|
+
return this.session?.sessionId ?? null;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
get currentSessionMetadata(): SessionMetadata | null {
|
|
357
|
+
return this.session?.metadata ?? null;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
/**
|
|
361
|
+
* The active RECORDING session ID (`ses-xxx`), or null when no recording
|
|
362
|
+
* is in progress.
|
|
363
|
+
*
|
|
364
|
+
* This is the canonical field that DebugEventStream stamps into every
|
|
365
|
+
* outgoing event's `data.recordingSessionId`. It is the single source of
|
|
366
|
+
* truth for correlating network and performance events with the session
|
|
367
|
+
* that was active when they were captured.
|
|
368
|
+
*
|
|
369
|
+
* Distinct from the TRANSPORT session ID (`debug-xxx`) which lives on
|
|
370
|
+
* the wire envelope's top-level `sessionId` field and never changes.
|
|
371
|
+
*/
|
|
372
|
+
get recordingSessionId(): string | null {
|
|
373
|
+
return this.active ? (this.session?.sessionId ?? null) : null;
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
/**
|
|
377
|
+
* Update the native capture function at runtime.
|
|
378
|
+
* Called by ScaleBunDebugRoot once the root View ref is ready for ViewShot.
|
|
379
|
+
* Also updates the active session's capture manager if one exists.
|
|
380
|
+
*/
|
|
381
|
+
setNativeCapture(captureFn: NativeCaptureFn): void {
|
|
382
|
+
this._nativeCapture = captureFn;
|
|
383
|
+
// Hot-swap into active capture manager if session is running
|
|
384
|
+
if (this.active && this.captureManager) {
|
|
385
|
+
(this.captureManager as any).nativeCapture = captureFn;
|
|
386
|
+
__DEV__ && logger.info('[SessionManager] Capture function updated (hot-swap)');
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
/**
|
|
391
|
+
* Start a new session. Idempotent — no-op if already active.
|
|
392
|
+
*/
|
|
393
|
+
startSession(metadata?: Partial<SessionMetadata>): void {
|
|
394
|
+
try {
|
|
395
|
+
if (this.active) {
|
|
396
|
+
__DEV__ && logger.debug('[SessionManager] Session already active, skipping start');
|
|
397
|
+
return;
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
// Quota (contract v1): a `sessions` block stops NEW sessions. Adopting an analytics
|
|
401
|
+
// session that is already open is not a new session, so that path stays allowed; minting
|
|
402
|
+
// a fresh recording id to post a new INGESTION_START is not.
|
|
403
|
+
if (this._backendTransport && !this._backendTransport.analyticsSessionId && quotaState.isBlocked('sessions')) {
|
|
404
|
+
logger.warn('[SessionManager] Session not started — sessions quota exceeded');
|
|
405
|
+
return;
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
// One-row merge: adopt the always-on analytics ingestion id as this
|
|
409
|
+
// recording session's id when the backend (SaaS) lane has one open.
|
|
410
|
+
// This threads the analytics id through the pipeline + capture manager
|
|
411
|
+
// below, so every frame/event body carries it AND it becomes
|
|
412
|
+
// currentSessionId at sendSessionStart — keeping endpoint id == body id
|
|
413
|
+
// while attaching frames/journey to the single analytics row (no second
|
|
414
|
+
// INGESTION_START). Legacy/no-clientKey mode has no analytics id → mint
|
|
415
|
+
// a fresh recording id and post to /replay/... as before.
|
|
416
|
+
const sessionId = this._backendTransport?.analyticsSessionId ?? generateSessionId();
|
|
417
|
+
|
|
418
|
+
// Create fresh subsystems for this session
|
|
419
|
+
this.pipeline = new JourneyEventPipeline(sessionId, {
|
|
420
|
+
maxEventCount: this.config.maxEventCount,
|
|
421
|
+
});
|
|
422
|
+
this.pipeline.setEmitCallback((event) => {
|
|
423
|
+
this.desktopTransport?.sendEvent(event);
|
|
424
|
+
this._backendTransport?.sendEvent(event);
|
|
425
|
+
});
|
|
426
|
+
|
|
427
|
+
this.captureManager = new ReplayCaptureManager(
|
|
428
|
+
sessionId,
|
|
429
|
+
this.config,
|
|
430
|
+
this._nativeCapture,
|
|
431
|
+
this._getOutboxFrameCapture(),
|
|
432
|
+
);
|
|
433
|
+
this.captureManager.setFrameEmitCallback((frame, imageData) => {
|
|
434
|
+
this.desktopTransport?.sendFrame(frame, imageData);
|
|
435
|
+
this._backendTransport?.sendFrame(frame, imageData);
|
|
436
|
+
});
|
|
437
|
+
|
|
438
|
+
// A NEW capture manager starts with no callbacks. Frame emission is re-wired above;
|
|
439
|
+
// loading intervals have to be re-wired here for the same reason, or every session
|
|
440
|
+
// after the first records frames and silently stops recording the waits between them.
|
|
441
|
+
this._wireLoadingIntervals();
|
|
442
|
+
|
|
443
|
+
// Create scroll tracker — wired to emit events + trigger capture on settle
|
|
444
|
+
this.scrollTracker = new ScrollTracker((scrollMeta: ScrollMetadata) => {
|
|
445
|
+
if (!this.active) return;
|
|
446
|
+
|
|
447
|
+
// Synthesize representative coordinates for the scroll.
|
|
448
|
+
// ScrollTracker only knows content deltas — it has no absolute touch
|
|
449
|
+
// point — but the dashboard CorrelationEngine DROPS any interaction
|
|
450
|
+
// without x/y, and the replay overlay draws scroll/swipe as an arrow
|
|
451
|
+
// that needs normalizedEnd coords. Without this, ScaleBunScrollView scrolls
|
|
452
|
+
// were invisible AND uncounted. Anchor the gesture at screen center and
|
|
453
|
+
// extend by the finger displacement (opposite the content offset delta),
|
|
454
|
+
// matching the finger-path convention native swipes already use.
|
|
455
|
+
const vw = this.session?.metadata?.screenWidth ?? 0;
|
|
456
|
+
const vh = this.session?.metadata?.screenHeight ?? 0;
|
|
457
|
+
const coords: Record<string, number | boolean> = {};
|
|
458
|
+
if (vw > 0 && vh > 0) {
|
|
459
|
+
const cx = vw / 2;
|
|
460
|
+
const cy = vh / 2;
|
|
461
|
+
const ex = Math.max(0, Math.min(vw, cx - scrollMeta.totalDeltaX));
|
|
462
|
+
const ey = Math.max(0, Math.min(vh, cy - scrollMeta.totalDeltaY));
|
|
463
|
+
coords.x = cx;
|
|
464
|
+
coords.y = cy;
|
|
465
|
+
coords.endX = ex;
|
|
466
|
+
coords.endY = ey;
|
|
467
|
+
coords.normalizedX = cx / vw;
|
|
468
|
+
coords.normalizedY = cy / vh;
|
|
469
|
+
coords.normalizedEndX = ex / vw;
|
|
470
|
+
coords.normalizedEndY = ey / vh;
|
|
471
|
+
coords.viewportWidth = vw;
|
|
472
|
+
coords.viewportHeight = vh;
|
|
473
|
+
coords.normalizedPrecomputed = true;
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
// Emit gesture:scroll event.
|
|
477
|
+
// emitEvent() triggers captureManager.onScrollSettle() internally
|
|
478
|
+
// for USER_ACTION events with gestureType='scroll' — no explicit call needed.
|
|
479
|
+
this.emitEvent('USER_ACTION', {
|
|
480
|
+
subtype: 'gesture:scroll',
|
|
481
|
+
payload: {
|
|
482
|
+
gestureType: 'scroll',
|
|
483
|
+
totalDeltaX: scrollMeta.totalDeltaX,
|
|
484
|
+
totalDeltaY: scrollMeta.totalDeltaY,
|
|
485
|
+
dominantDirection: scrollMeta.dominantDirection,
|
|
486
|
+
direction: scrollMeta.dominantDirection,
|
|
487
|
+
durationMs: scrollMeta.durationMs,
|
|
488
|
+
startTs: scrollMeta.startTs,
|
|
489
|
+
endTs: scrollMeta.endTs,
|
|
490
|
+
confidence: 'high',
|
|
491
|
+
...coords,
|
|
492
|
+
},
|
|
493
|
+
source: 'user',
|
|
494
|
+
});
|
|
495
|
+
});
|
|
496
|
+
|
|
497
|
+
this.crashAnchors = [];
|
|
498
|
+
this.loadingStartTimes.clear();
|
|
499
|
+
|
|
500
|
+
// Build session object
|
|
501
|
+
this.session = {
|
|
502
|
+
sessionId,
|
|
503
|
+
deviceId: this._rawTransport?.deviceId ?? 'unknown',
|
|
504
|
+
startedAt: Date.now(),
|
|
505
|
+
status: 'active',
|
|
506
|
+
metadata: {
|
|
507
|
+
// The real Platform.OS. This used to be a ternary whose else-branch was 'android'
|
|
508
|
+
// and whose catch was ALSO 'android' — so a macOS session, a Windows session, and
|
|
509
|
+
// a failure to load react-native at all were reported identically, as Android.
|
|
510
|
+
// Enriched by native device info when the bridge answers.
|
|
511
|
+
platform: resolvePlatformOS(),
|
|
512
|
+
buildType: __DEV__ ? 'debug' : 'release',
|
|
513
|
+
...metadata,
|
|
514
|
+
// Capture window dp dims here so emitEvent() can normalize coords
|
|
515
|
+
// without re-querying Dimensions on every gesture event.
|
|
516
|
+
...(() => {
|
|
517
|
+
if (metadata?.screenWidth && metadata?.screenHeight) return {};
|
|
518
|
+
try {
|
|
519
|
+
const { Dimensions } = require('react-native');
|
|
520
|
+
const dims = Dimensions.get('window');
|
|
521
|
+
if (dims.width > 0 && dims.height > 0) {
|
|
522
|
+
return { screenWidth: dims.width, screenHeight: dims.height };
|
|
523
|
+
}
|
|
524
|
+
} catch { /* no-throw */ }
|
|
525
|
+
return {};
|
|
526
|
+
})(),
|
|
527
|
+
},
|
|
528
|
+
transportState: 'local',
|
|
529
|
+
syncState: 'pending-evaluation',
|
|
530
|
+
artifactIndex: {
|
|
531
|
+
frameRefs: [],
|
|
532
|
+
eventRefs: [],
|
|
533
|
+
networkRefs: [],
|
|
534
|
+
logRefs: [],
|
|
535
|
+
crashAnchors: [],
|
|
536
|
+
},
|
|
537
|
+
};
|
|
538
|
+
|
|
539
|
+
this.active = true;
|
|
540
|
+
|
|
541
|
+
// Enrich the live session metadata with native device info (appVersion,
|
|
542
|
+
// deviceModel, osVersion, …) so synchronous consumers — e.g. the engage
|
|
543
|
+
// response builder reading `currentSessionMetadata.appVersion` — see real
|
|
544
|
+
// values rather than the platform-only subset known at create time. The
|
|
545
|
+
// session-start metadata above is the fallback when the bridge is
|
|
546
|
+
// unavailable (old native binary). Fire-and-forget, never throws.
|
|
547
|
+
Promise.resolve()
|
|
548
|
+
.then(() => bridgeAdapter.getDeviceInfo())
|
|
549
|
+
.then(info => {
|
|
550
|
+
const s = this.session;
|
|
551
|
+
if (!info || !s) return;
|
|
552
|
+
const m = s.metadata;
|
|
553
|
+
s.metadata = {
|
|
554
|
+
...m,
|
|
555
|
+
appVersion: info.appVersion ?? m.appVersion,
|
|
556
|
+
appName: info.appName ?? m.appName,
|
|
557
|
+
bundleId: info.bundleId ?? m.bundleId,
|
|
558
|
+
osVersion: info.osVersion ?? m.osVersion,
|
|
559
|
+
deviceModel: info.deviceModel ?? m.deviceModel,
|
|
560
|
+
};
|
|
561
|
+
})
|
|
562
|
+
.catch(() => {
|
|
563
|
+
/* no-throw: metadata-only fallback */
|
|
564
|
+
});
|
|
565
|
+
|
|
566
|
+
// Notify transport (desktop + optional backend)
|
|
567
|
+
this.desktopTransport?.sendSessionStart(this.session);
|
|
568
|
+
this._backendTransport?.sendSessionStart(this.session);
|
|
569
|
+
|
|
570
|
+
// Emit SESSION_STARTED event
|
|
571
|
+
this.pipeline.emit('SESSION_STARTED', {
|
|
572
|
+
payload: { sessionId },
|
|
573
|
+
source: 'system',
|
|
574
|
+
});
|
|
575
|
+
|
|
576
|
+
// Start capture manager — unless the `frames` quota is spent, in which case the session
|
|
577
|
+
// runs without frame capture and resumes it (with a keyframe) when the block lifts.
|
|
578
|
+
if (quotaState.isBlocked('frames')) {
|
|
579
|
+
this._framesQuotaPaused = true;
|
|
580
|
+
this._captureStartedThisSession = false;
|
|
581
|
+
logger.warn('[SessionManager] Replay frames quota exceeded — session started without frame capture');
|
|
582
|
+
} else {
|
|
583
|
+
this._framesQuotaPaused = false;
|
|
584
|
+
this._captureStartedThisSession = true;
|
|
585
|
+
this.captureManager.start();
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
// Set timeout
|
|
589
|
+
if (this.config.maxDurationMs > 0) {
|
|
590
|
+
this.timeoutTimer = setTimeout(() => {
|
|
591
|
+
__DEV__ && logger.debug('[SessionManager] Session timeout reached');
|
|
592
|
+
this.endSession('timeout');
|
|
593
|
+
}, this.config.maxDurationMs);
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
__DEV__ && logger.info(`[SessionManager] Session started: ${sessionId}`);
|
|
597
|
+
|
|
598
|
+
// ── Replay initial screen ──────────────────────────────────────
|
|
599
|
+
// If the user was already on a screen before pressing Record
|
|
600
|
+
// (typical: app mounted → screen hook fired → screen tracked),
|
|
601
|
+
// emit the initial SCREEN_VIEW now so the session captures the
|
|
602
|
+
// starting screen and triggers the first navigation frame.
|
|
603
|
+
if (this._lastKnownScreen) {
|
|
604
|
+
this.emitEvent('SCREEN_VIEW', {
|
|
605
|
+
screen: this._lastKnownScreen,
|
|
606
|
+
subtype: this._lastKnownScreen,
|
|
607
|
+
source: 'sdk',
|
|
608
|
+
});
|
|
609
|
+
}
|
|
610
|
+
} catch (err) {
|
|
611
|
+
logger.error('[SessionManager] Failed to start session:', err);
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
/**
|
|
616
|
+
* End the current session.
|
|
617
|
+
*/
|
|
618
|
+
endSession(reason: 'ended' | 'timeout' = 'ended'): void {
|
|
619
|
+
// Fully idempotent — safe to call multiple times
|
|
620
|
+
if (!this.active) {
|
|
621
|
+
return;
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
// Disable collection FIRST to prevent any re-entrant emission during teardown
|
|
625
|
+
this.active = false;
|
|
626
|
+
|
|
627
|
+
// Guard: session must exist (should always be true if active was true)
|
|
628
|
+
if (!this.session) return;
|
|
629
|
+
|
|
630
|
+
try {
|
|
631
|
+
|
|
632
|
+
// Clear timeout
|
|
633
|
+
if (this.timeoutTimer) {
|
|
634
|
+
clearTimeout(this.timeoutTimer);
|
|
635
|
+
this.timeoutTimer = null;
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
// Dispose scroll tracker — cancels pending settle timer
|
|
639
|
+
if (this.scrollTracker) {
|
|
640
|
+
this.scrollTracker.dispose();
|
|
641
|
+
this.scrollTracker = null;
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
// Clear loading state
|
|
645
|
+
this.loadingStartTimes.clear();
|
|
646
|
+
|
|
647
|
+
// Emit SESSION_ENDED event
|
|
648
|
+
this.pipeline.emit('SESSION_ENDED', {
|
|
649
|
+
payload: { reason },
|
|
650
|
+
source: 'system',
|
|
651
|
+
});
|
|
652
|
+
|
|
653
|
+
// Stop capture — this cancels ALL pending timers and increments generation
|
|
654
|
+
this.captureManager.stop();
|
|
655
|
+
|
|
656
|
+
// Build final artifact index
|
|
657
|
+
const eventRefs = this.pipeline.buildEventRefs();
|
|
658
|
+
this.session.artifactIndex = {
|
|
659
|
+
...eventRefs,
|
|
660
|
+
frameRefs: this.captureManager.getFrameRefs(),
|
|
661
|
+
crashAnchors: this.crashAnchors,
|
|
662
|
+
};
|
|
663
|
+
|
|
664
|
+
// Finalize session
|
|
665
|
+
this.session.endedAt = Date.now();
|
|
666
|
+
this.session.status = reason === 'timeout' ? 'timeout' : 'ended';
|
|
667
|
+
|
|
668
|
+
// Run sync evaluation
|
|
669
|
+
const hasCrash = this.crashAnchors.length > 0;
|
|
670
|
+
const hasError = this.pipeline.getEvents().some(
|
|
671
|
+
e => e.type === 'ERROR_CAPTURED',
|
|
672
|
+
);
|
|
673
|
+
|
|
674
|
+
const candidate = SyncDecisionEngine.buildCandidate(
|
|
675
|
+
this.session.sessionId,
|
|
676
|
+
this.session.metadata,
|
|
677
|
+
this.pipeline.count,
|
|
678
|
+
this.captureManager.count,
|
|
679
|
+
(this.session.endedAt - this.session.startedAt),
|
|
680
|
+
hasCrash,
|
|
681
|
+
hasError,
|
|
682
|
+
);
|
|
683
|
+
|
|
684
|
+
const decision = this.syncEngine.evaluate(candidate);
|
|
685
|
+
this.session.syncState = decision.shouldSync ? 'eligible' : 'ineligible';
|
|
686
|
+
|
|
687
|
+
// Notify transport (desktop + optional backend)
|
|
688
|
+
this.desktopTransport?.sendSessionEnd(this.session);
|
|
689
|
+
this._backendTransport?.sendSessionEnd(this.session);
|
|
690
|
+
|
|
691
|
+
// Clean up
|
|
692
|
+
this.pipeline.clear();
|
|
693
|
+
this.captureManager.clear();
|
|
694
|
+
// (this.active already set to false above)
|
|
695
|
+
|
|
696
|
+
__DEV__ && logger.info(
|
|
697
|
+
`[SessionManager] Session ended: ${this.session.sessionId} ` +
|
|
698
|
+
`(${reason}, sync=${decision.shouldSync}, priority=${decision.priority})`,
|
|
699
|
+
);
|
|
700
|
+
} catch (err) {
|
|
701
|
+
logger.error('[SessionManager] Failed to end session:', err);
|
|
702
|
+
// active is already false — no further cleanup needed
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
// ─── Event Emission ─────────────────────────────────────────────────
|
|
707
|
+
|
|
708
|
+
/**
|
|
709
|
+
* Resolve the dp-space base used to normalize JS-fallback tap coordinates.
|
|
710
|
+
*
|
|
711
|
+
* The screenshot is captured by native in the content-view at its physical
|
|
712
|
+
* pixel size. Normalized coords must divide by that SAME region (converted
|
|
713
|
+
* to dp, since pageX/pageY are dp). Using Dimensions.get('window') (or
|
|
714
|
+
* session.metadata, which is seeded from it) excludes system insets and is
|
|
715
|
+
* ~10% smaller than the content-view — pushing markers off. Preference order:
|
|
716
|
+
* native content-view (px→dp) → session metadata → payload → Dimensions.
|
|
717
|
+
*/
|
|
718
|
+
private _resolveNormalizationBaseDp(
|
|
719
|
+
p: Record<string, any>,
|
|
720
|
+
): { width: number; height: number; space: string } | null {
|
|
721
|
+
// Native screenshot-base size in JS-coordinate units (dp on Android, pt on iOS).
|
|
722
|
+
// This is the EXACT region the native screenshot was captured in, so JS-fallback
|
|
723
|
+
// taps (pageX/pageY in dp/pt) divide directly into the same coord space the
|
|
724
|
+
// marker overlay uses on the dashboard. Highest priority — session metadata
|
|
725
|
+
// and Dimensions.get('window') report the app window which excludes system
|
|
726
|
+
// insets differently across devices and pushes markers off vertically.
|
|
727
|
+
if (this._lastNativeViewportJsUnits) {
|
|
728
|
+
return {
|
|
729
|
+
width: this._lastNativeViewportJsUnits.width,
|
|
730
|
+
height: this._lastNativeViewportJsUnits.height,
|
|
731
|
+
space: this._lastNativeViewportJsUnits.space,
|
|
732
|
+
};
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
const mw = this.session?.metadata?.screenWidth;
|
|
736
|
+
const mh = this.session?.metadata?.screenHeight;
|
|
737
|
+
if (mw && mh) return { width: mw, height: mh, space: 'session-metadata-dp' };
|
|
738
|
+
|
|
739
|
+
if (typeof p.viewportWidth === 'number' && p.viewportWidth > 0
|
|
740
|
+
&& typeof p.viewportHeight === 'number' && p.viewportHeight > 0) {
|
|
741
|
+
return { width: p.viewportWidth, height: p.viewportHeight, space: 'payload-viewport-dp' };
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
try {
|
|
745
|
+
const { Dimensions } = require('react-native');
|
|
746
|
+
const dims = Dimensions.get('window');
|
|
747
|
+
if (dims.width > 0 && dims.height > 0) {
|
|
748
|
+
return { width: dims.width, height: dims.height, space: 'root-view-dp' };
|
|
749
|
+
}
|
|
750
|
+
} catch { /* no-throw */ }
|
|
751
|
+
|
|
752
|
+
return null;
|
|
753
|
+
}
|
|
754
|
+
|
|
755
|
+
/**
|
|
756
|
+
* Inject/resolve normalized tap coordinates into a USER_ACTION payload, in place.
|
|
757
|
+
*
|
|
758
|
+
* Mutates `p` to carry normalizedX/Y (+ end coords for swipes/scrolls) so the
|
|
759
|
+
* heatmap can overlay them across viewport sizes. Returns true when the source
|
|
760
|
+
* already provided high-confidence / pre-computed normalized coords (native or
|
|
761
|
+
* JS-root-measured), false when this method JS-estimated them.
|
|
762
|
+
*
|
|
763
|
+
* Extracted from emitEvent so BOTH the replay-recording lane AND the always-on
|
|
764
|
+
* analytics lane (Feature #4) normalize identically — keeping the tap payload
|
|
765
|
+
* keys byte-for-byte the same on both routes.
|
|
766
|
+
*/
|
|
767
|
+
private _normalizeInteractionPayload(p: Record<string, unknown>): boolean {
|
|
768
|
+
// If native tracker already provided high-confidence normalized coords,
|
|
769
|
+
// OR the JS path measured the root-view rect and precomputed normalized
|
|
770
|
+
// coords against it, do not overwrite them with JS estimation.
|
|
771
|
+
const hasNormalizedXY = typeof p.normalizedX === 'number'
|
|
772
|
+
&& typeof p.normalizedY === 'number';
|
|
773
|
+
const alreadyNormalized = hasNormalizedXY
|
|
774
|
+
&& (p.confidence === 'high' || p.normalizedPrecomputed === true);
|
|
775
|
+
|
|
776
|
+
if (alreadyNormalized) {
|
|
777
|
+
// Cache the native screenshot base in JS-coordinate units (dp/pt)
|
|
778
|
+
// so subsequent JS-fallback taps normalize against the same region
|
|
779
|
+
// the screenshot was captured from. Android sends px (suffix -px),
|
|
780
|
+
// iOS sends pt (suffix -pt). Both are converted to the unit RN
|
|
781
|
+
// pageX/pageY use, so JS taps divide directly with no extra math.
|
|
782
|
+
const cs = typeof p.coordinateSpace === 'string' ? p.coordinateSpace : '';
|
|
783
|
+
|
|
784
|
+
/**
|
|
785
|
+
* TRUST, BUT CHECK. `confidence: 'high'` means the NATIVE layer measured it — not that
|
|
786
|
+
* the number is right. One production device reports a content view of 288x448 px while
|
|
787
|
+
* telling us its layout width is 434.7dp, an implied density of 0.663 that no screen
|
|
788
|
+
* has. Cached unchecked, that wrong base then normalised every tap in the session and
|
|
789
|
+
* became the screenshot's coordinate space too.
|
|
790
|
+
*
|
|
791
|
+
* `windowWidth` and `density` are attached here so the check has both sides of the
|
|
792
|
+
* invariant and so the BACKEND can re-derive the same verdict from the stored payload —
|
|
793
|
+
* see `viewportPlausibility.ts` for the rule and the 56-device measurement behind it.
|
|
794
|
+
*/
|
|
795
|
+
let density = 0;
|
|
796
|
+
let windowWidth = 0;
|
|
797
|
+
try {
|
|
798
|
+
const { PixelRatio, Dimensions } = require('react-native');
|
|
799
|
+
density = PixelRatio.get();
|
|
800
|
+
windowWidth = Dimensions.get('window').width;
|
|
801
|
+
} catch { /* no-throw */ }
|
|
802
|
+
|
|
803
|
+
const check = checkViewport({
|
|
804
|
+
viewportWidth: typeof p.viewportWidth === 'number' ? p.viewportWidth : null,
|
|
805
|
+
viewportHeight: typeof p.viewportHeight === 'number' ? p.viewportHeight : null,
|
|
806
|
+
windowWidth: windowWidth > 0 ? windowWidth : null,
|
|
807
|
+
coordinateSpace: cs,
|
|
808
|
+
density: density > 0 ? density : null,
|
|
809
|
+
});
|
|
810
|
+
p.viewportCheck = check.verdict;
|
|
811
|
+
if (check.reason) p.viewportCheckReason = check.reason;
|
|
812
|
+
if (check.impliedDensity != null) p.impliedDensity = check.impliedDensity;
|
|
813
|
+
|
|
814
|
+
if (typeof p.viewportWidth === 'number' && p.viewportWidth > 0
|
|
815
|
+
&& typeof p.viewportHeight === 'number' && p.viewportHeight > 0
|
|
816
|
+
&& check.verdict !== 'implausible') {
|
|
817
|
+
if (cs.endsWith('-px')) {
|
|
818
|
+
if (density > 0) {
|
|
819
|
+
this._lastNativeViewportJsUnits = {
|
|
820
|
+
width: p.viewportWidth / density,
|
|
821
|
+
height: p.viewportHeight / density,
|
|
822
|
+
space: 'native-content-view-dp',
|
|
823
|
+
};
|
|
824
|
+
}
|
|
825
|
+
} else if (cs.endsWith('-pt')) {
|
|
826
|
+
// iOS already in pt — same unit as RN pageX/pageY
|
|
827
|
+
this._lastNativeViewportJsUnits = {
|
|
828
|
+
width: p.viewportWidth,
|
|
829
|
+
height: p.viewportHeight,
|
|
830
|
+
space: 'native-window-pt',
|
|
831
|
+
};
|
|
832
|
+
}
|
|
833
|
+
} else if (check.verdict === 'implausible') {
|
|
834
|
+
/**
|
|
835
|
+
* The native base is unusable, so fall back to the window and RE-NORMALISE.
|
|
836
|
+
*
|
|
837
|
+
* The coordinates already on the payload were divided by the bad base, so leaving
|
|
838
|
+
* them would keep every tap in the wrong place — the point of rejecting the base is
|
|
839
|
+
* to stop using it, including for the numbers already computed from it. The
|
|
840
|
+
* original is kept under `rejectedViewportW/H` so the device stays diagnosable
|
|
841
|
+
* from stored data instead of needing the handset.
|
|
842
|
+
*/
|
|
843
|
+
p.rejectedViewportWidth = p.viewportWidth;
|
|
844
|
+
p.rejectedViewportHeight = p.viewportHeight;
|
|
845
|
+
this._lastNativeViewportJsUnits = null;
|
|
846
|
+
|
|
847
|
+
/**
|
|
848
|
+
* The window, taken DIRECTLY — not via `_resolveNormalizationBaseDp`.
|
|
849
|
+
*
|
|
850
|
+
* That helper would walk its priority list and land straight back on the rejected
|
|
851
|
+
* number: its third branch reads `p.viewportWidth` off this very payload, and its
|
|
852
|
+
* second reads `session.metadata.screenWidth`, which on a device that misreports
|
|
853
|
+
* its content view has no reason to be any more trustworthy. Rejecting a source and
|
|
854
|
+
* then re-selecting it through a helper is how a fix becomes a no-op.
|
|
855
|
+
*
|
|
856
|
+
* `Dimensions.get('window')` is the measurement the invariant just CONFIRMED is
|
|
857
|
+
* sound — on the failing device its 434.7dp is exactly right for a 1440px screen —
|
|
858
|
+
* so it is the one source that has earned being the fallback here.
|
|
859
|
+
*/
|
|
860
|
+
let winW = 0;
|
|
861
|
+
let winH = 0;
|
|
862
|
+
try {
|
|
863
|
+
const { Dimensions } = require('react-native');
|
|
864
|
+
const d = Dimensions.get('window');
|
|
865
|
+
winW = d.width;
|
|
866
|
+
winH = d.height;
|
|
867
|
+
} catch { /* no-throw */ }
|
|
868
|
+
|
|
869
|
+
const rx = typeof p.x === 'number' ? p.x : undefined;
|
|
870
|
+
const ry = typeof p.y === 'number' ? p.y : undefined;
|
|
871
|
+
if (winW > 0 && winH > 0) {
|
|
872
|
+
// `x`/`y` arrived in the REJECTED space (px), so they have to be converted into
|
|
873
|
+
// the window's unit before dividing, or the normalisation is wrong a second time.
|
|
874
|
+
const rejW = typeof p.rejectedViewportWidth === 'number' ? p.rejectedViewportWidth : 0;
|
|
875
|
+
const rejH = typeof p.rejectedViewportHeight === 'number' ? p.rejectedViewportHeight : 0;
|
|
876
|
+
if (rx !== undefined && ry !== undefined && rejW > 0 && rejH > 0) {
|
|
877
|
+
// Scale-free: the tap's POSITION within the rejected frame is still the best
|
|
878
|
+
// estimate of where the finger was, even though the frame's absolute size was
|
|
879
|
+
// nonsense. Re-express it against the window instead of re-dividing raw px.
|
|
880
|
+
const nx = Math.max(0, Math.min(1, rx / rejW));
|
|
881
|
+
const ny = Math.max(0, Math.min(1, ry / rejH));
|
|
882
|
+
p.normalizedX = nx;
|
|
883
|
+
p.normalizedY = ny;
|
|
884
|
+
p.x = nx * winW;
|
|
885
|
+
p.y = ny * winH;
|
|
886
|
+
}
|
|
887
|
+
p.viewportWidth = winW;
|
|
888
|
+
p.viewportHeight = winH;
|
|
889
|
+
p.coordinateSpace = 'window-fallback-dp';
|
|
890
|
+
p.confidence = 'derived';
|
|
891
|
+
}
|
|
892
|
+
}
|
|
893
|
+
} else {
|
|
894
|
+
// Resolve the normalization base (in dp). Priority:
|
|
895
|
+
// 1. session metadata screen size
|
|
896
|
+
// 2. native content-view size (px → dp via PixelRatio) — matches screenshot
|
|
897
|
+
// 3. payload viewport
|
|
898
|
+
// 4. Dimensions.get('window') — last resort (excludes insets, ~10% off)
|
|
899
|
+
const base = this._resolveNormalizationBaseDp(p);
|
|
900
|
+
|
|
901
|
+
const x = typeof p.x === 'number' ? p.x : undefined;
|
|
902
|
+
const y = typeof p.y === 'number' ? p.y : undefined;
|
|
903
|
+
if (x !== undefined && y !== undefined && base) {
|
|
904
|
+
p.normalizedX = Math.max(0, Math.min(1, x / base.width));
|
|
905
|
+
p.normalizedY = Math.max(0, Math.min(1, y / base.height));
|
|
906
|
+
p.viewportWidth = base.width;
|
|
907
|
+
p.viewportHeight = base.height;
|
|
908
|
+
p.coordinateSpace = base.space;
|
|
909
|
+
}
|
|
910
|
+
|
|
911
|
+
const endX = typeof p.endX === 'number' ? p.endX : undefined;
|
|
912
|
+
const endY = typeof p.endY === 'number' ? p.endY : undefined;
|
|
913
|
+
if (endX !== undefined && endY !== undefined && base) {
|
|
914
|
+
p.normalizedEndX = Math.max(0, Math.min(1, endX / base.width));
|
|
915
|
+
p.normalizedEndY = Math.max(0, Math.min(1, endY / base.height));
|
|
916
|
+
}
|
|
917
|
+
}
|
|
918
|
+
|
|
919
|
+
return alreadyNormalized;
|
|
920
|
+
}
|
|
921
|
+
|
|
922
|
+
/**
|
|
923
|
+
* Feature #4 (heatmaps) — additive analytics emission.
|
|
924
|
+
*
|
|
925
|
+
* Routes a tap/gesture to the always-on ANALYTICS lane (BackendSessionAdapter
|
|
926
|
+
* .trackEvent) so coords are captured even when no replay recording is active.
|
|
927
|
+
*
|
|
928
|
+
* Strictly additive and isolated:
|
|
929
|
+
* - Only runs when `_captureInteractionHeatmap` is ON (opt-in flag) AND a
|
|
930
|
+
* backend analytics transport is attached AND no recording is active
|
|
931
|
+
* (when active, the recording lane already emits the SAME USER_ACTION —
|
|
932
|
+
* emitting here too would duplicate it).
|
|
933
|
+
* - Reuses the EXACT recording payload shape (USER_ACTION + the same keys:
|
|
934
|
+
* normalizedX, normalizedY, gestureType, screen, …) so the backend
|
|
935
|
+
* aggregate reads them identically (no protocol/schema change).
|
|
936
|
+
* - Normalizes via the same _normalizeInteractionPayload used by recording.
|
|
937
|
+
* - Respects redaction: payload is passed through redactBody so any caller-
|
|
938
|
+
* injected sensitive keys (e.g. a label) are stripped before transport.
|
|
939
|
+
* - Never throws.
|
|
940
|
+
*/
|
|
941
|
+
private _emitInteractionToAnalytics(
|
|
942
|
+
gestureType: string,
|
|
943
|
+
payload: Record<string, unknown>,
|
|
944
|
+
occurredAt: number,
|
|
945
|
+
screenName?: string,
|
|
946
|
+
): boolean {
|
|
947
|
+
if (!this._captureInteractionHeatmap) return false;
|
|
948
|
+
if (this.active) return false; // recording lane already carries this tap
|
|
949
|
+
const adapter = this._backendTransport;
|
|
950
|
+
if (!adapter) return false;
|
|
951
|
+
|
|
952
|
+
try {
|
|
953
|
+
// Normalize coords in place (same logic as the recording lane).
|
|
954
|
+
this._normalizeInteractionPayload(payload);
|
|
955
|
+
|
|
956
|
+
// Respect redaction before handing to transport. redactBody returns a
|
|
957
|
+
// new object and strips known-sensitive keys; coords/gesture survive.
|
|
958
|
+
const safePayload = redactBody(payload) as Record<string, unknown>;
|
|
959
|
+
|
|
960
|
+
/**
|
|
961
|
+
* NO `frameId` HERE, and it is not an oversight.
|
|
962
|
+
*
|
|
963
|
+
* This method is guarded by `if (this.active) return;` — it runs ONLY when no replay
|
|
964
|
+
* recording is active, which is precisely when no screenshots are being captured.
|
|
965
|
+
* There is no frame on screen to point at, so an analytics-lane tap can never be
|
|
966
|
+
* attributed to a visual state.
|
|
967
|
+
*
|
|
968
|
+
* That makes it a permanent property of this lane rather than a gap to close later,
|
|
969
|
+
* and the dashboard has to account for it: these taps live in the heatmap grid
|
|
970
|
+
* alongside recorded ones, so a state filter necessarily covers a subset of the
|
|
971
|
+
* traffic. `heat:screen-coverage` reports that subset's size per app for exactly this
|
|
972
|
+
* reason.
|
|
973
|
+
*/
|
|
974
|
+
const event: JourneyEvent = {
|
|
975
|
+
eventId: generateEventId(),
|
|
976
|
+
// sessionId is (re)stamped by the analytics lane at flush time.
|
|
977
|
+
sessionId: adapter.analyticsSessionId ?? '',
|
|
978
|
+
ts: occurredAt,
|
|
979
|
+
type: 'USER_ACTION',
|
|
980
|
+
subtype: `gesture:${gestureType}`,
|
|
981
|
+
screen: screenName ?? this._lastKnownScreen ?? undefined,
|
|
982
|
+
payload: safePayload,
|
|
983
|
+
source: 'user',
|
|
984
|
+
};
|
|
985
|
+
adapter.trackEvent(event);
|
|
986
|
+
return true;
|
|
987
|
+
} catch (err) {
|
|
988
|
+
logger.error('[SessionManager] heatmap analytics emit failed:', err);
|
|
989
|
+
return false;
|
|
990
|
+
}
|
|
991
|
+
}
|
|
992
|
+
|
|
993
|
+
/**
|
|
994
|
+
* Emit a canonical event into the session pipeline.
|
|
995
|
+
* This is the primary entry point for all event sources.
|
|
996
|
+
*/
|
|
997
|
+
emitEvent(
|
|
998
|
+
type: JourneyEventType,
|
|
999
|
+
opts?: {
|
|
1000
|
+
subtype?: string;
|
|
1001
|
+
severity?: JourneyEventSeverity;
|
|
1002
|
+
screen?: string;
|
|
1003
|
+
payload?: Record<string, unknown>;
|
|
1004
|
+
correlationId?: string;
|
|
1005
|
+
traceId?: string;
|
|
1006
|
+
source?: JourneyEventSource;
|
|
1007
|
+
journeyId?: string;
|
|
1008
|
+
/** Original observation time; interaction capture may resolve asynchronously. */
|
|
1009
|
+
timestamp?: number;
|
|
1010
|
+
},
|
|
1011
|
+
): void {
|
|
1012
|
+
if (!this.active) return;
|
|
1013
|
+
|
|
1014
|
+
try {
|
|
1015
|
+
// Pre-process USER_ACTION events to inject normalized coordinates for heatmap readiness
|
|
1016
|
+
if (type === 'USER_ACTION' && opts?.payload) {
|
|
1017
|
+
const p = opts.payload;
|
|
1018
|
+
|
|
1019
|
+
const alreadyNormalized = this._normalizeInteractionPayload(p);
|
|
1020
|
+
|
|
1021
|
+
__DEV__ && logger.debug(
|
|
1022
|
+
`[TapAccuracy] sink platform=${p.platform ?? '?'} space=${p.coordinateSpace ?? '?'} ` +
|
|
1023
|
+
`source=${alreadyNormalized ? (p.confidence === 'high' ? 'native-high' : 'js-precomputed') : 'js-estimated'} ` +
|
|
1024
|
+
`norm=(${p.normalizedX},${p.normalizedY}) base=(${p.viewportWidth}x${p.viewportHeight})`,
|
|
1025
|
+
);
|
|
1026
|
+
|
|
1027
|
+
// ─── Diagnostic: merge calibration expected target (if a calibration
|
|
1028
|
+
// screen stamped it within the TTL) and emit ONE unified [TapDiag]
|
|
1029
|
+
// JSON object per gesture. Observability only — no math runs here.
|
|
1030
|
+
const cal = consumeCalibrationTarget();
|
|
1031
|
+
if (cal) {
|
|
1032
|
+
p.expectedTargetId = cal.targetId;
|
|
1033
|
+
p.expectedNX = cal.expectedNX;
|
|
1034
|
+
p.expectedNY = cal.expectedNY;
|
|
1035
|
+
}
|
|
1036
|
+
|
|
1037
|
+
try {
|
|
1038
|
+
const meta = this.session?.metadata as any | undefined;
|
|
1039
|
+
const diag = {
|
|
1040
|
+
tag: 'TapDiag',
|
|
1041
|
+
side: 'sdk',
|
|
1042
|
+
platform: p.platform ?? null,
|
|
1043
|
+
coordinateSource: alreadyNormalized
|
|
1044
|
+
? (p.confidence === 'high' ? 'native-high' : 'js-precomputed')
|
|
1045
|
+
: 'js-estimated',
|
|
1046
|
+
coordinateSpace: p.coordinateSpace ?? null,
|
|
1047
|
+
targetId: p.expectedTargetId ?? p.target ?? null,
|
|
1048
|
+
expectedNX: p.expectedNX ?? null,
|
|
1049
|
+
expectedNY: p.expectedNY ?? null,
|
|
1050
|
+
rawX: p.x ?? null,
|
|
1051
|
+
rawY: p.y ?? null,
|
|
1052
|
+
pageX: p.pageX ?? null,
|
|
1053
|
+
pageY: p.pageY ?? null,
|
|
1054
|
+
locationX: p.locationX ?? null,
|
|
1055
|
+
locationY: p.locationY ?? null,
|
|
1056
|
+
normalizedX: p.normalizedX ?? null,
|
|
1057
|
+
normalizedY: p.normalizedY ?? null,
|
|
1058
|
+
viewportWidth: p.viewportWidth ?? null,
|
|
1059
|
+
viewportHeight: p.viewportHeight ?? null,
|
|
1060
|
+
rootViewWidth: p.rootViewWidth ?? null,
|
|
1061
|
+
rootViewHeight: p.rootViewHeight ?? null,
|
|
1062
|
+
rootViewLeft: p.rootViewLeft ?? null,
|
|
1063
|
+
rootViewTop: p.rootViewTop ?? null,
|
|
1064
|
+
screenWidth: p.screenWidth ?? meta?.screenWidth ?? null,
|
|
1065
|
+
screenHeight: p.screenHeight ?? meta?.screenHeight ?? null,
|
|
1066
|
+
windowWidth: p.windowWidth ?? null,
|
|
1067
|
+
windowHeight: p.windowHeight ?? null,
|
|
1068
|
+
statusBarHeight: p.statusBarHeight ?? null,
|
|
1069
|
+
safeAreaTop: p.safeAreaTop ?? null,
|
|
1070
|
+
timestamp: Date.now(),
|
|
1071
|
+
confidence: p.confidence ?? null,
|
|
1072
|
+
source: p.source ?? null,
|
|
1073
|
+
};
|
|
1074
|
+
// Dev-only diagnostic echo. Must NOT emit in release: the console
|
|
1075
|
+
// capture integration (captureConsoleLogs) would otherwise forward
|
|
1076
|
+
// one [TapDiag] line per tap into the customer-facing Logs lane and
|
|
1077
|
+
// bury their own logs. Sibling [TapAccuracy] log above is __DEV__-gated
|
|
1078
|
+
// for the same reason; the heatmap/replay data flows through the
|
|
1079
|
+
// interaction pipeline, not this echo, so gating it loses nothing.
|
|
1080
|
+
// eslint-disable-next-line no-console
|
|
1081
|
+
if (__DEV__) console.log('[TapDiag]', JSON.stringify(diag));
|
|
1082
|
+
} catch { /* no-throw */ }
|
|
1083
|
+
}
|
|
1084
|
+
|
|
1085
|
+
/**
|
|
1086
|
+
* Which frame was on screen when this happened.
|
|
1087
|
+
*
|
|
1088
|
+
* Resolved BEFORE `pipeline.emit`, and deliberately before the capture triggers
|
|
1089
|
+
* below: those capture the frame that shows the RESULT of the interaction, and a tap
|
|
1090
|
+
* belongs to the state it was made IN, not the one it produced. Reading after them
|
|
1091
|
+
* would attribute every tap to its own consequence.
|
|
1092
|
+
*
|
|
1093
|
+
* USER_ACTION only. A SCREEN_VIEW's frame is the screen being left, which is
|
|
1094
|
+
* misleading rather than useful, and no consumer asks for it.
|
|
1095
|
+
*
|
|
1096
|
+
* `currentKeyframe` returns null unless the frame is from this same screen and still
|
|
1097
|
+
* fresh — see its contract. Null is the common, correct answer and stays absent on
|
|
1098
|
+
* the event rather than becoming a default.
|
|
1099
|
+
*/
|
|
1100
|
+
/**
|
|
1101
|
+
* The keyframe is LINKED only when it can show this tap: a tap made after the list was
|
|
1102
|
+
* scrolled is not a tap on the picture taken before it (frameLink.ts). The verdict and the
|
|
1103
|
+
* frame's age ride on the payload, so a missing or unverified link is visible, not silent.
|
|
1104
|
+
*/
|
|
1105
|
+
let framedOpts: (typeof opts & { frameId?: string }) | undefined = opts;
|
|
1106
|
+
if (type === 'USER_ACTION') {
|
|
1107
|
+
const kf = this.captureManager?.currentKeyframe(opts?.screen ?? this._lastKnownScreen ?? undefined);
|
|
1108
|
+
const link = frameLinkVerdict(kf, opts?.payload?.['scroll_context']);
|
|
1109
|
+
const at = typeof opts?.timestamp === 'number' ? opts.timestamp : Date.now();
|
|
1110
|
+
framedOpts = {
|
|
1111
|
+
...opts,
|
|
1112
|
+
frameId: kf && (link === 'linked' || link === 'unverified') ? kf.frameId : undefined,
|
|
1113
|
+
payload: {
|
|
1114
|
+
...(opts?.payload ?? {}),
|
|
1115
|
+
frame_link: link,
|
|
1116
|
+
...(kf ? { frame_age_ms: Math.max(0, at - kf.capturedAt) } : {}),
|
|
1117
|
+
},
|
|
1118
|
+
};
|
|
1119
|
+
}
|
|
1120
|
+
|
|
1121
|
+
const event = this.pipeline.emit(type, framedOpts);
|
|
1122
|
+
|
|
1123
|
+
// Trigger capture on screen view or interaction
|
|
1124
|
+
if (event) {
|
|
1125
|
+
if (type === 'SCREEN_VIEW' && opts?.screen) {
|
|
1126
|
+
this.captureManager.onScreenChange(opts.screen);
|
|
1127
|
+
} else if (type === 'USER_ACTION') {
|
|
1128
|
+
// Extract interaction coordinates from the gesture payload
|
|
1129
|
+
// so the captured frame carries tap location metadata.
|
|
1130
|
+
const p = opts?.payload;
|
|
1131
|
+
const x = typeof p?.x === 'number' ? p.x : undefined;
|
|
1132
|
+
const y = typeof p?.y === 'number' ? p.y : undefined;
|
|
1133
|
+
const gestureType = typeof p?.gestureType === 'string' ? p.gestureType : undefined;
|
|
1134
|
+
|
|
1135
|
+
// Scroll gestures use onScrollSettle (which debounces into a single
|
|
1136
|
+
// capture). Using onInteraction for scroll would produce a DUPLICATE
|
|
1137
|
+
// screenshot alongside the ScrollTracker's onScrollSettle call.
|
|
1138
|
+
if (gestureType === 'scroll') {
|
|
1139
|
+
this.captureManager.onScrollSettle(opts?.screen ?? this._lastKnownScreen ?? undefined);
|
|
1140
|
+
} else {
|
|
1141
|
+
let interactionPoint: import('./sessionTypes').ReplayFrame['interactionPoint'];
|
|
1142
|
+
if (x !== undefined && y !== undefined && gestureType) {
|
|
1143
|
+
interactionPoint = {
|
|
1144
|
+
x,
|
|
1145
|
+
y,
|
|
1146
|
+
normalizedX: typeof p?.normalizedX === 'number' ? p.normalizedX : undefined,
|
|
1147
|
+
normalizedY: typeof p?.normalizedY === 'number' ? p.normalizedY : undefined,
|
|
1148
|
+
screenWidth: this.session?.metadata?.screenWidth ?? 0,
|
|
1149
|
+
screenHeight: this.session?.metadata?.screenHeight ?? 0,
|
|
1150
|
+
viewportWidth: typeof p?.viewportWidth === 'number' ? p.viewportWidth : undefined,
|
|
1151
|
+
viewportHeight: typeof p?.viewportHeight === 'number' ? p.viewportHeight : undefined,
|
|
1152
|
+
gestureType,
|
|
1153
|
+
endX: typeof p?.endX === 'number' ? p.endX : undefined,
|
|
1154
|
+
endY: typeof p?.endY === 'number' ? p.endY : undefined,
|
|
1155
|
+
normalizedEndX: typeof p?.normalizedEndX === 'number' ? p.normalizedEndX : undefined,
|
|
1156
|
+
normalizedEndY: typeof p?.normalizedEndY === 'number' ? p.normalizedEndY : undefined,
|
|
1157
|
+
durationMs: typeof p?.durationMs === 'number' ? p.durationMs : undefined,
|
|
1158
|
+
direction: typeof p?.direction === 'string' ? p.direction : undefined,
|
|
1159
|
+
};
|
|
1160
|
+
}
|
|
1161
|
+
|
|
1162
|
+
this.captureManager.onInteraction(interactionPoint);
|
|
1163
|
+
}
|
|
1164
|
+
}
|
|
1165
|
+
}
|
|
1166
|
+
} catch (err) {
|
|
1167
|
+
logger.error('[SessionManager] emitEvent failed:', err);
|
|
1168
|
+
}
|
|
1169
|
+
}
|
|
1170
|
+
|
|
1171
|
+
/**
|
|
1172
|
+
* Anchor a crash in the session context.
|
|
1173
|
+
* Protects nearby events and frames from eviction.
|
|
1174
|
+
*/
|
|
1175
|
+
anchorCrash(crashEventId: string): CrashAnchor | null {
|
|
1176
|
+
if (!this.active) return null;
|
|
1177
|
+
|
|
1178
|
+
try {
|
|
1179
|
+
// Anchor events
|
|
1180
|
+
const anchor = this.pipeline.anchorCrash(crashEventId);
|
|
1181
|
+
|
|
1182
|
+
// Anchor frames (protect frames near the crash timestamp)
|
|
1183
|
+
const crashEvent = this.pipeline.getEvents().find(
|
|
1184
|
+
e => e.eventId === crashEventId,
|
|
1185
|
+
);
|
|
1186
|
+
if (crashEvent) {
|
|
1187
|
+
const protectedFrameIds = this.captureManager.protectFramesNearCrash(crashEvent.ts);
|
|
1188
|
+
anchor.nearbyFrameIds = protectedFrameIds;
|
|
1189
|
+
}
|
|
1190
|
+
|
|
1191
|
+
// Capture an error-context frame
|
|
1192
|
+
this.captureManager.captureManual('error-context').catch(() => { });
|
|
1193
|
+
|
|
1194
|
+
this.crashAnchors.push(anchor);
|
|
1195
|
+
|
|
1196
|
+
__DEV__ && logger.debug(
|
|
1197
|
+
`[SessionManager] Crash anchored: ${crashEventId}, ` +
|
|
1198
|
+
`protected ${anchor.nearbyEventIds.length} events, ` +
|
|
1199
|
+
`${anchor.nearbyFrameIds.length} frames`,
|
|
1200
|
+
);
|
|
1201
|
+
|
|
1202
|
+
return anchor;
|
|
1203
|
+
} catch (err) {
|
|
1204
|
+
logger.error('[SessionManager] anchorCrash failed:', err);
|
|
1205
|
+
return null;
|
|
1206
|
+
}
|
|
1207
|
+
}
|
|
1208
|
+
|
|
1209
|
+
// ─── Navigation / Screen Integration ────────────────────────────────
|
|
1210
|
+
|
|
1211
|
+
/**
|
|
1212
|
+
* Notify of a screen change. Called by ScaleBunDebugRoot via NavDetector.
|
|
1213
|
+
*/
|
|
1214
|
+
/** The screen currently on top, or null before the first navigation is observed. */
|
|
1215
|
+
getLastKnownScreen(): string | null {
|
|
1216
|
+
return this._lastKnownScreen;
|
|
1217
|
+
}
|
|
1218
|
+
|
|
1219
|
+
onScreenChange(screenName: string): void {
|
|
1220
|
+
// Always track the current screen — even when inactive.
|
|
1221
|
+
// This allows startSession to emit the initial SCREEN_VIEW for
|
|
1222
|
+
// the screen that was already mounted before Record was pressed.
|
|
1223
|
+
const previous = this._lastKnownScreen;
|
|
1224
|
+
const enteredAt = this._screenEnteredAt;
|
|
1225
|
+
this._lastKnownScreen = screenName;
|
|
1226
|
+
this._screenEnteredAt = Date.now();
|
|
1227
|
+
|
|
1228
|
+
if (!this.active) return; // Defense-in-depth: no emission when recording is OFF
|
|
1229
|
+
|
|
1230
|
+
// SCREEN_EXIT for the screen being left, carrying its dwell time.
|
|
1231
|
+
//
|
|
1232
|
+
// The exit was previously detectable ONLY inside the Engage trigger engine and never became an
|
|
1233
|
+
// event, so time-on-screen had to be inferred from the gap to the NEXT SCREEN_VIEW — which is
|
|
1234
|
+
// wrong whenever the app backgrounds in between, and impossible for the last screen of a
|
|
1235
|
+
// session. Emitting it here makes dwell a measured value rather than a derived guess.
|
|
1236
|
+
if (previous && previous !== screenName && enteredAt != null) {
|
|
1237
|
+
this.emitEvent('SCREEN_EXIT', {
|
|
1238
|
+
screen: previous,
|
|
1239
|
+
subtype: previous,
|
|
1240
|
+
source: 'sdk',
|
|
1241
|
+
payload: { dwellMs: Math.max(0, Date.now() - enteredAt), to: screenName },
|
|
1242
|
+
});
|
|
1243
|
+
}
|
|
1244
|
+
|
|
1245
|
+
this.emitEvent('SCREEN_VIEW', {
|
|
1246
|
+
screen: screenName,
|
|
1247
|
+
subtype: screenName,
|
|
1248
|
+
source: 'sdk',
|
|
1249
|
+
...(previous && previous !== screenName ? { payload: { from: previous } } : {}),
|
|
1250
|
+
});
|
|
1251
|
+
}
|
|
1252
|
+
|
|
1253
|
+
/**
|
|
1254
|
+
* Notify of a user interaction. Called by ScaleBunDebugRoot touch handlers.
|
|
1255
|
+
* Also triggers frame capture for desktop-initiated recordings.
|
|
1256
|
+
*/
|
|
1257
|
+
onUserAction(
|
|
1258
|
+
subtype: string,
|
|
1259
|
+
payload?: Record<string, unknown>,
|
|
1260
|
+
context?: { screen?: string; timestamp?: number },
|
|
1261
|
+
): void {
|
|
1262
|
+
if (!this.active) return; // Defense-in-depth: no emission when recording is OFF
|
|
1263
|
+
this.emitEvent('USER_ACTION', {
|
|
1264
|
+
subtype,
|
|
1265
|
+
screen: context?.screen ?? this._lastKnownScreen ?? undefined,
|
|
1266
|
+
payload,
|
|
1267
|
+
source: 'user',
|
|
1268
|
+
timestamp: context?.timestamp,
|
|
1269
|
+
});
|
|
1270
|
+
// NOTE: Do NOT call captureManager.onInteraction() here.
|
|
1271
|
+
// emitEvent() already triggers onInteraction() for USER_ACTION events (line ~405).
|
|
1272
|
+
// Calling it again here caused DUPLICATE frame captures per gesture.
|
|
1273
|
+
}
|
|
1274
|
+
|
|
1275
|
+
/**
|
|
1276
|
+
* Detailed gesture detection — called by ScaleBunDebugRoot touch handlers.
|
|
1277
|
+
* Emits a USER_ACTION event with gesture-specific subtype and payload.
|
|
1278
|
+
*/
|
|
1279
|
+
onGestureDetected(gestureType: string, details?: {
|
|
1280
|
+
x?: number;
|
|
1281
|
+
y?: number;
|
|
1282
|
+
endX?: number;
|
|
1283
|
+
endY?: number;
|
|
1284
|
+
duration?: number;
|
|
1285
|
+
durationMs?: number;
|
|
1286
|
+
direction?: string;
|
|
1287
|
+
target?: string;
|
|
1288
|
+
viewportWidth?: number;
|
|
1289
|
+
viewportHeight?: number;
|
|
1290
|
+
pageX?: number;
|
|
1291
|
+
pageY?: number;
|
|
1292
|
+
locationX?: number;
|
|
1293
|
+
locationY?: number;
|
|
1294
|
+
normalizedX?: number;
|
|
1295
|
+
normalizedY?: number;
|
|
1296
|
+
normalizedEndX?: number;
|
|
1297
|
+
normalizedEndY?: number;
|
|
1298
|
+
confidence?: string;
|
|
1299
|
+
coordinateSpace?: string;
|
|
1300
|
+
source?: string;
|
|
1301
|
+
// Pre-computed normalization flag — set by JS fallback when it normalized
|
|
1302
|
+
// against the measured root-view rect (the exact view the screenshot captures).
|
|
1303
|
+
// Without this flag, SessionManager.emitEvent would overwrite the pre-computed
|
|
1304
|
+
// values via _resolveNormalizationBaseDp using a potentially-different base.
|
|
1305
|
+
normalizedPrecomputed?: boolean;
|
|
1306
|
+
// Diagnostic fields used by the dashboard to verify capture-space alignment.
|
|
1307
|
+
statusBarHeight?: number;
|
|
1308
|
+
safeAreaTop?: number;
|
|
1309
|
+
rootViewTop?: number;
|
|
1310
|
+
rootViewLeft?: number;
|
|
1311
|
+
rootViewWidth?: number;
|
|
1312
|
+
rootViewHeight?: number;
|
|
1313
|
+
windowWidth?: number;
|
|
1314
|
+
windowHeight?: number;
|
|
1315
|
+
screenshotCropTop?: number;
|
|
1316
|
+
screenWidth?: number;
|
|
1317
|
+
screenHeight?: number;
|
|
1318
|
+
platform?: string;
|
|
1319
|
+
interactionId?: string;
|
|
1320
|
+
interactionProtocol?: number;
|
|
1321
|
+
occurredAt?: number;
|
|
1322
|
+
ui?: string;
|
|
1323
|
+
stateStatus?: InteractionStateStatus;
|
|
1324
|
+
targetId?: string;
|
|
1325
|
+
screenName?: string;
|
|
1326
|
+
emitAutomaticAnalytics?: boolean;
|
|
1327
|
+
/**
|
|
1328
|
+
* RN-6 — the native↔JS join's own verdict, and it MUST be declared here.
|
|
1329
|
+
*
|
|
1330
|
+
* This signature ENUMERATES its fields rather than spreading, and the only caller
|
|
1331
|
+
* (ScaleBunDebugRoot's native listener) invokes it through `(sm as any)`. So a field
|
|
1332
|
+
* added at the call site and not added here is dropped in silence, with no type
|
|
1333
|
+
* error anywhere — which is precisely how these three nearly shipped as a no-op.
|
|
1334
|
+
*
|
|
1335
|
+
* 'exact' = one JS start was clearly closest. 'ambiguous' = two were within
|
|
1336
|
+
* CORRELATION_MARGIN_MS of each other, so target/ui/screenName were deliberately
|
|
1337
|
+
* withheld rather than guessed. 'none' = no start within tolerance, meaning the JS
|
|
1338
|
+
* handler never fired. The last two are kept distinct because they have different
|
|
1339
|
+
* fixes and collapsing them would hide which is happening in production.
|
|
1340
|
+
*/
|
|
1341
|
+
correlation?: 'exact' | 'ambiguous' | 'none';
|
|
1342
|
+
correlationDeltaMs?: number | null;
|
|
1343
|
+
correlationRunnerUpDeltaMs?: number | null;
|
|
1344
|
+
/**
|
|
1345
|
+
* RN-1/RN-2 — which kind of identity `targetId` is. Declared here for the same reason as
|
|
1346
|
+
* the correlation fields above: this signature enumerates, and its only caller goes
|
|
1347
|
+
* through `(sm as any)`, so an undeclared field is dropped in silence.
|
|
1348
|
+
*/
|
|
1349
|
+
targetSource?: 'testID' | 'path' | 'component' | 'none';
|
|
1350
|
+
/** RN-8: where the screen name came from. Declared here because this signature enumerates. */
|
|
1351
|
+
screenSource?: 'manual' | 'navigation' | 'none';
|
|
1352
|
+
/**
|
|
1353
|
+
* RN-13: the target control's rectangle in capture-base fractions. Declared here because
|
|
1354
|
+
* this signature ENUMERATES — the sixth field to need it, after three correlation fields,
|
|
1355
|
+
* targetSource and screenSource.
|
|
1356
|
+
*/
|
|
1357
|
+
targetRect?: { x: number; y: number; width: number; height: number };
|
|
1358
|
+
/**
|
|
1359
|
+
* RN-3: content offset of the nearest ancestor scroller. Declared here because this
|
|
1360
|
+
* signature ENUMERATES — the seventh field to need it.
|
|
1361
|
+
*/
|
|
1362
|
+
scrollX?: number;
|
|
1363
|
+
scrollY?: number;
|
|
1364
|
+
scrollSource?: 'wrapper' | 'native' | 'none';
|
|
1365
|
+
/**
|
|
1366
|
+
* RN-4: list row identity. Declared here because this signature ENUMERATES — the eighth
|
|
1367
|
+
* and ninth fields to need it.
|
|
1368
|
+
*/
|
|
1369
|
+
itemKey?: string;
|
|
1370
|
+
itemIndex?: number;
|
|
1371
|
+
/**
|
|
1372
|
+
* RN-15: tap position within the target control. Declared here because this signature
|
|
1373
|
+
* ENUMERATES — the tenth and eleventh fields to need it.
|
|
1374
|
+
*/
|
|
1375
|
+
localU?: number;
|
|
1376
|
+
localV?: number;
|
|
1377
|
+
/**
|
|
1378
|
+
* Native scroll chain at touch-down (innermost first, capture pixels), the device pixel
|
|
1379
|
+
* density, and the tap-contract version. Declared here because this signature ENUMERATES.
|
|
1380
|
+
*/
|
|
1381
|
+
scrollContext?: import('../journey/nativeScroll').NativeScrollEntry[];
|
|
1382
|
+
pixelDensity?: number;
|
|
1383
|
+
heatmapSchemaVersion?: number;
|
|
1384
|
+
}): void {
|
|
1385
|
+
// Exact-duplicate suppression — see _lastGestureSig. Signature is the gesture type plus
|
|
1386
|
+
// raw coordinates verbatim; String() keeps undefined coords distinct from 0 (a payload
|
|
1387
|
+
// with no coords never collides with a real origin tap).
|
|
1388
|
+
const suppliedInteractionId = details?.interactionId;
|
|
1389
|
+
const dedupSig = suppliedInteractionId
|
|
1390
|
+
? `id:${suppliedInteractionId}`
|
|
1391
|
+
: `${gestureType}|${String(details?.x)}|${String(details?.y)}|${String(details?.endX)}|${String(details?.endY)}`;
|
|
1392
|
+
const nowTs = Date.now();
|
|
1393
|
+
if (
|
|
1394
|
+
dedupSig === this._lastGestureSig &&
|
|
1395
|
+
nowTs - this._lastGestureTs <= SessionManager.GESTURE_DEDUP_WINDOW_MS
|
|
1396
|
+
) {
|
|
1397
|
+
this._lastGestureTs = nowTs; // a burst of 3 stays suppressed even if gaps chain past the window
|
|
1398
|
+
return;
|
|
1399
|
+
}
|
|
1400
|
+
this._lastGestureSig = dedupSig;
|
|
1401
|
+
this._lastGestureTs = nowTs;
|
|
1402
|
+
|
|
1403
|
+
// Build the canonical gesture payload ONCE so the recording lane and the
|
|
1404
|
+
// (additive) analytics lane carry byte-identical keys (normalizedX/Y,
|
|
1405
|
+
// gestureType, etc.). Same object shape that was previously inlined.
|
|
1406
|
+
const interactionId = suppliedInteractionId ?? generateInteractionId();
|
|
1407
|
+
const occurredAt = details?.occurredAt ?? Date.now();
|
|
1408
|
+
const stateStatus = details?.stateStatus ?? (details?.ui ? 'captured_nonempty' : 'not_captured');
|
|
1409
|
+
const payload: Record<string, unknown> = {
|
|
1410
|
+
interaction_id: interactionId,
|
|
1411
|
+
interaction_protocol: details?.interactionProtocol ?? INTERACTION_PROTOCOL_VERSION,
|
|
1412
|
+
state_status: stateStatus,
|
|
1413
|
+
ui: details?.ui,
|
|
1414
|
+
target_id: details?.targetId,
|
|
1415
|
+
/**
|
|
1416
|
+
* RN-6 — shipped with the row so the join is auditable instead of assumed.
|
|
1417
|
+
*
|
|
1418
|
+
* Without it, an interaction with no `target_id` is ambiguous in the worst way:
|
|
1419
|
+
* it could mean the control genuinely had no identity, or that the correlation
|
|
1420
|
+
* refused and the identity exists but could not be attributed. Those are
|
|
1421
|
+
* different defects with different owners, and the accounting lane cannot give
|
|
1422
|
+
* an honest unplaceable REASON without knowing which.
|
|
1423
|
+
*
|
|
1424
|
+
* Undefined on the JS-fallback path (no native event, so nothing was joined),
|
|
1425
|
+
* which is itself the correct answer there rather than a fabricated 'exact'.
|
|
1426
|
+
*/
|
|
1427
|
+
correlation: details?.correlation,
|
|
1428
|
+
correlation_delta_ms: details?.correlationDeltaMs,
|
|
1429
|
+
correlation_runner_up_delta_ms: details?.correlationRunnerUpDeltaMs,
|
|
1430
|
+
/**
|
|
1431
|
+
* RN-1/RN-2 — stored alongside `target_id` so a reader can tell an authored
|
|
1432
|
+
* identifier from a structural inference. 'path' means the app authored no testID
|
|
1433
|
+
* and the identity was derived from component names, which is useful but will
|
|
1434
|
+
* change under refactoring; a dashboard that presents the two identically invites
|
|
1435
|
+
* someone to trust a key that is about to move.
|
|
1436
|
+
*/
|
|
1437
|
+
target_source: details?.targetSource,
|
|
1438
|
+
/**
|
|
1439
|
+
* RN-8 — stored so "13.8% of interactions have a screen" becomes answerable.
|
|
1440
|
+
* 'none' means the app instrumented nothing the SDK could see; 'manual' means a
|
|
1441
|
+
* <ScaleBunScreen> beacon; 'navigation' means the React Navigation hook. Three
|
|
1442
|
+
* different owners behind one number that today has none.
|
|
1443
|
+
*/
|
|
1444
|
+
screen_source: details?.screenSource,
|
|
1445
|
+
/**
|
|
1446
|
+
* RN-13 — what makes "the heat is on this control" checkable instead of assumed.
|
|
1447
|
+
* Absent when the control could not be measured synchronously, which is a fact worth
|
|
1448
|
+
* storing: an unmeasurable control and a mis-placed tap are different findings.
|
|
1449
|
+
*/
|
|
1450
|
+
target_rect: details?.targetRect,
|
|
1451
|
+
/**
|
|
1452
|
+
* RN-3 — the backend already reads scrollX/scrollY off the payload into the fact
|
|
1453
|
+
* table's own columns (it has done since the web SDK, which sends `sx`/`sy`), so
|
|
1454
|
+
* these need no migration. Mobile simply never sent them.
|
|
1455
|
+
*
|
|
1456
|
+
* `scroll_source` says whether an offset was available at all: 'none' means the app
|
|
1457
|
+
* uses a plain <ScrollView> rather than the SDK's wrapper, which is a coverage gap in
|
|
1458
|
+
* the integration and not a tap at the top of a list.
|
|
1459
|
+
*/
|
|
1460
|
+
scrollX: details?.scrollX,
|
|
1461
|
+
scrollY: details?.scrollY,
|
|
1462
|
+
scroll_source: details?.scrollSource,
|
|
1463
|
+
/**
|
|
1464
|
+
* Every scroll container under the finger at touch-down, innermost first, in the
|
|
1465
|
+
* capture view's pixel space (native probe). The single scrollX/Y above is the
|
|
1466
|
+
* innermost; the chain is what lets a nested list be placed without guessing.
|
|
1467
|
+
*/
|
|
1468
|
+
scroll_context: details?.scrollContext,
|
|
1469
|
+
pixel_density: details?.pixelDensity,
|
|
1470
|
+
heatmap_schema_version: details?.heatmapSchemaVersion,
|
|
1471
|
+
/**
|
|
1472
|
+
* RN-4 — what stops a recycled native view merging two different rows into one
|
|
1473
|
+
* hotspot. The key is the app's own keyExtractor output, so it is stable across
|
|
1474
|
+
* scrolling and across sessions; the index is carried so a recycled slot (same index,
|
|
1475
|
+
* different key) can be proven rather than inferred.
|
|
1476
|
+
*/
|
|
1477
|
+
item_key: details?.itemKey,
|
|
1478
|
+
item_index: details?.itemIndex,
|
|
1479
|
+
/**
|
|
1480
|
+
* RN-15 — where in the control, not just which control. Turns "they tapped Add to
|
|
1481
|
+
* Cart" into "they tapped its left edge", which is what tells you whether a hit area
|
|
1482
|
+
* is right. Outside 0..1 is a hitSlop tap and is kept as such.
|
|
1483
|
+
*/
|
|
1484
|
+
local_u: details?.localU,
|
|
1485
|
+
local_v: details?.localV,
|
|
1486
|
+
gestureType,
|
|
1487
|
+
x: details?.x,
|
|
1488
|
+
y: details?.y,
|
|
1489
|
+
endX: details?.endX,
|
|
1490
|
+
endY: details?.endY,
|
|
1491
|
+
duration: details?.duration ?? details?.durationMs,
|
|
1492
|
+
durationMs: details?.durationMs ?? details?.duration,
|
|
1493
|
+
direction: details?.direction,
|
|
1494
|
+
target: details?.target,
|
|
1495
|
+
viewportWidth: details?.viewportWidth,
|
|
1496
|
+
viewportHeight: details?.viewportHeight,
|
|
1497
|
+
pageX: details?.pageX,
|
|
1498
|
+
pageY: details?.pageY,
|
|
1499
|
+
locationX: details?.locationX,
|
|
1500
|
+
locationY: details?.locationY,
|
|
1501
|
+
// Native / JS-precomputed coords — passed through without overwriting
|
|
1502
|
+
normalizedX: details?.normalizedX,
|
|
1503
|
+
normalizedY: details?.normalizedY,
|
|
1504
|
+
normalizedEndX: details?.normalizedEndX,
|
|
1505
|
+
normalizedEndY: details?.normalizedEndY,
|
|
1506
|
+
confidence: details?.confidence,
|
|
1507
|
+
coordinateSpace: details?.coordinateSpace,
|
|
1508
|
+
source: details?.source,
|
|
1509
|
+
normalizedPrecomputed: details?.normalizedPrecomputed,
|
|
1510
|
+
statusBarHeight: details?.statusBarHeight,
|
|
1511
|
+
safeAreaTop: details?.safeAreaTop,
|
|
1512
|
+
rootViewTop: details?.rootViewTop,
|
|
1513
|
+
rootViewLeft: details?.rootViewLeft,
|
|
1514
|
+
rootViewWidth: details?.rootViewWidth,
|
|
1515
|
+
rootViewHeight: details?.rootViewHeight,
|
|
1516
|
+
windowWidth: details?.windowWidth,
|
|
1517
|
+
windowHeight: details?.windowHeight,
|
|
1518
|
+
screenshotCropTop: details?.screenshotCropTop,
|
|
1519
|
+
screenWidth: details?.screenWidth,
|
|
1520
|
+
screenHeight: details?.screenHeight,
|
|
1521
|
+
platform: details?.platform,
|
|
1522
|
+
};
|
|
1523
|
+
|
|
1524
|
+
// ── Feature #4 (heatmaps): additive analytics emission ──
|
|
1525
|
+
// When NOT recording, route the tap to the always-on analytics lane so
|
|
1526
|
+
// heatmaps still get coords (opt-in via captureInteractionHeatmap). No-op
|
|
1527
|
+
// when the flag is OFF, no backend transport is attached, or recording is
|
|
1528
|
+
// active (the recording path below already carries this tap). Uses a fresh
|
|
1529
|
+
// payload clone so analytics normalization never mutates the recording one.
|
|
1530
|
+
const analyticsReplayCarrier = this._emitInteractionToAnalytics(
|
|
1531
|
+
gestureType,
|
|
1532
|
+
{ ...payload },
|
|
1533
|
+
occurredAt,
|
|
1534
|
+
details?.screenName,
|
|
1535
|
+
);
|
|
1536
|
+
const replayCarrier = !!this._backendTransport && (this.active || analyticsReplayCarrier);
|
|
1537
|
+
if (details?.emitAutomaticAnalytics) {
|
|
1538
|
+
try {
|
|
1539
|
+
const safePayload = redactBody(payload) as Record<string, unknown>;
|
|
1540
|
+
emitAutomaticEvent(
|
|
1541
|
+
'element_interacted',
|
|
1542
|
+
automaticInteractionProperties(
|
|
1543
|
+
safePayload,
|
|
1544
|
+
details?.screenName ?? this._lastKnownScreen ?? undefined,
|
|
1545
|
+
replayCarrier,
|
|
1546
|
+
),
|
|
1547
|
+
occurredAt,
|
|
1548
|
+
);
|
|
1549
|
+
} catch { /* automatic projection must never affect interaction capture */ }
|
|
1550
|
+
}
|
|
1551
|
+
|
|
1552
|
+
if (!this.active) return; // Defense-in-depth: no emission when recording is OFF
|
|
1553
|
+
// The subtype becomes `ReplayEvent.label`, which is what the Events explorer GROUPS BY. Naming
|
|
1554
|
+
// the tapped control here is what splits taps per control instead of collapsing every tap in the
|
|
1555
|
+
// app into one `gesture:tap` row. Falls back to the bare gesture when nothing identified the
|
|
1556
|
+
// target, so behaviour is unchanged for taps that cannot be resolved.
|
|
1557
|
+
//
|
|
1558
|
+
// ⚠️ `gestureType` also travels in the payload, and the backend's `resolveGesture` reads THAT
|
|
1559
|
+
// first — so enriching the label cannot change heatmap gesture classification.
|
|
1560
|
+
const target = typeof details?.target === 'string' ? details.target.trim() : '';
|
|
1561
|
+
this.onUserAction(
|
|
1562
|
+
target ? `gesture:${gestureType} · ${target}` : `gesture:${gestureType}`,
|
|
1563
|
+
payload,
|
|
1564
|
+
{ screen: details?.screenName, timestamp: occurredAt },
|
|
1565
|
+
);
|
|
1566
|
+
}
|
|
1567
|
+
|
|
1568
|
+
/**
|
|
1569
|
+
* Manual frame capture — triggered by Desktop "Capture Step" button.
|
|
1570
|
+
*/
|
|
1571
|
+
async captureManualFrame(): Promise<{ frameId?: string } | null> {
|
|
1572
|
+
if (!this.active || !this.captureManager) return null; // No capture when recording is OFF
|
|
1573
|
+
const frame = await this.captureManager.captureManual('manual');
|
|
1574
|
+
return frame ? { frameId: frame.frameId } : null;
|
|
1575
|
+
}
|
|
1576
|
+
|
|
1577
|
+
/**
|
|
1578
|
+
* Notify of app lifecycle change.
|
|
1579
|
+
*/
|
|
1580
|
+
onLifecycleChange(state: 'foreground' | 'background'): void {
|
|
1581
|
+
this.emitEvent(
|
|
1582
|
+
state === 'foreground' ? 'APP_FOREGROUND' : 'APP_BACKGROUND',
|
|
1583
|
+
{ source: 'system' },
|
|
1584
|
+
);
|
|
1585
|
+
}
|
|
1586
|
+
|
|
1587
|
+
// ─── Scroll Integration ─────────────────────────────────────────────
|
|
1588
|
+
|
|
1589
|
+
/**
|
|
1590
|
+
* Notify of a scroll event from a scrollable surface (ScaleBunScrollView).
|
|
1591
|
+
* Delegates to ScrollTracker for accumulation and settle detection.
|
|
1592
|
+
*/
|
|
1593
|
+
onScrollEvent(contentOffsetX: number, contentOffsetY: number): void {
|
|
1594
|
+
if (!this.active) return;
|
|
1595
|
+
this.scrollTracker?.onScrollEvent(contentOffsetX, contentOffsetY);
|
|
1596
|
+
}
|
|
1597
|
+
|
|
1598
|
+
// ─── Loading State Integration ──────────────────────────────────────
|
|
1599
|
+
|
|
1600
|
+
/**
|
|
1601
|
+
* Notify that a screen has started loading.
|
|
1602
|
+
* Explicit instrumentation — NOT auto-called on screen entry.
|
|
1603
|
+
* Call from app code: ScaleBun.markLoadingStarted('ScreenName')
|
|
1604
|
+
*/
|
|
1605
|
+
onLoadingStarted(screenName: string): void {
|
|
1606
|
+
if (!this.active) return;
|
|
1607
|
+
|
|
1608
|
+
this.loadingStartTimes.set(screenName, Date.now());
|
|
1609
|
+
|
|
1610
|
+
this.emitEvent('LOADING_STARTED', {
|
|
1611
|
+
screen: screenName,
|
|
1612
|
+
subtype: screenName,
|
|
1613
|
+
payload: { screen: screenName },
|
|
1614
|
+
source: 'sdk',
|
|
1615
|
+
});
|
|
1616
|
+
}
|
|
1617
|
+
|
|
1618
|
+
/**
|
|
1619
|
+
* Notify that a screen has finished loading.
|
|
1620
|
+
* Explicit instrumentation — call from app code.
|
|
1621
|
+
* Triggers a 'screen_loaded' frame capture for the settled state.
|
|
1622
|
+
*/
|
|
1623
|
+
onLoadingCompleted(screenName: string): void {
|
|
1624
|
+
if (!this.active) return;
|
|
1625
|
+
|
|
1626
|
+
const startTime = this.loadingStartTimes.get(screenName);
|
|
1627
|
+
const durationMs = startTime ? Date.now() - startTime : undefined;
|
|
1628
|
+
this.loadingStartTimes.delete(screenName);
|
|
1629
|
+
|
|
1630
|
+
this.emitEvent('LOADING_COMPLETED', {
|
|
1631
|
+
screen: screenName,
|
|
1632
|
+
subtype: screenName,
|
|
1633
|
+
payload: {
|
|
1634
|
+
screen: screenName,
|
|
1635
|
+
durationMs,
|
|
1636
|
+
},
|
|
1637
|
+
source: 'sdk',
|
|
1638
|
+
});
|
|
1639
|
+
|
|
1640
|
+
// Capture the settled loaded screen
|
|
1641
|
+
this.captureManager.onLoadingEnd(screenName);
|
|
1642
|
+
}
|
|
1643
|
+
|
|
1644
|
+
/**
|
|
1645
|
+
* App network activity, forwarded to replay capture so a frame can wait for the screen's
|
|
1646
|
+
* data instead of photographing the wait. Called by NetworkFeature, which has already
|
|
1647
|
+
* excluded this SDK's own uploads and third-party beacons.
|
|
1648
|
+
*
|
|
1649
|
+
* Deliberately NOT gated on `this.active`: the counter must stay balanced across a session
|
|
1650
|
+
* boundary. Dropping the `end` of a request whose `start` was counted would leave replay
|
|
1651
|
+
* believing the app is permanently loading, and a session can start or stop mid-request.
|
|
1652
|
+
* The capture manager ignores the signal when it is not capturing anyway.
|
|
1653
|
+
*/
|
|
1654
|
+
onNetworkActivity(phase: 'start' | 'end'): void {
|
|
1655
|
+
try {
|
|
1656
|
+
this.captureManager.onNetworkActivity(phase);
|
|
1657
|
+
} catch {
|
|
1658
|
+
// Advisory signal — it must never take down a network request.
|
|
1659
|
+
}
|
|
1660
|
+
}
|
|
1661
|
+
|
|
1662
|
+
// ─── Sync Policy ────────────────────────────────────────────────────
|
|
1663
|
+
|
|
1664
|
+
/**
|
|
1665
|
+
* Override the sync policy. SDK-owned — native only provides metadata.
|
|
1666
|
+
*/
|
|
1667
|
+
setSyncPolicy(policy: SyncDecisionCallback): void {
|
|
1668
|
+
this.syncEngine.setPolicy(policy);
|
|
1669
|
+
}
|
|
1670
|
+
|
|
1671
|
+
/**
|
|
1672
|
+
* Provide native-gathered sync metadata for the current session.
|
|
1673
|
+
* Does NOT make the sync decision — that stays in SDK's SyncDecisionEngine.
|
|
1674
|
+
*/
|
|
1675
|
+
setNativeSyncMetadata(metadata: NativeSyncMetadata): void {
|
|
1676
|
+
// Stored for use at session end evaluation
|
|
1677
|
+
if (this.session) {
|
|
1678
|
+
(this.session as any)._nativeSyncMetadata = metadata;
|
|
1679
|
+
}
|
|
1680
|
+
}
|
|
1681
|
+
}
|
|
1682
|
+
|
|
1683
|
+
// ─── Helpers ────────────────────────────────────────────────────────────────
|
|
1684
|
+
|
|
1685
|
+
function generateSessionId(): string {
|
|
1686
|
+
return `ses-${Date.now().toString(36)}-${Math.random().toString(36).substring(2, 8)}`;
|
|
1687
|
+
}
|