@scalebun/react-native 1.0.3 → 1.1.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/android/src/main/java/com/scalebun/core/crash/CrashStorage.kt +115 -1
- package/android/src/main/java/com/scalebun/core/crash/NativeCrashHandler.kt +14 -0
- package/android/src/main/java/com/scalebun/replaysdk/ReplaySdkModule.kt +25 -9
- package/android/src/main/java/com/scalebun/replaysdk/voice/VoiceTranscriber.kt +68 -2
- package/android/src/main/java/com/scalebun/rn/crash/ScaleBunCrashModule.kt +66 -2
- package/android/src/main/java/com/scalebun/rn/ota/SlotManager.kt +70 -4
- package/ios/Crash/ScaleBunCrashBridge.mm +6 -0
- package/ios/Crash/ScaleBunCrashHandler.swift +14 -1
- package/ios/Crash/ScaleBunCrashModule.swift +14 -0
- package/ios/Crash/ScaleBunCrashSignalBridge.h +32 -1
- package/ios/Crash/ScaleBunCrashSignalBridge.m +172 -7
- package/ios/Crash/ScaleBunCrashSignalStress.c +137 -0
- package/ios/Crash/ScaleBunCrashStorage.swift +75 -0
- package/lib/commonjs/analytics/EventTracker.js +30 -2
- package/lib/commonjs/analytics/EventTracker.js.map +1 -1
- package/lib/commonjs/bootstrap/SDKBootstrapper.js +31 -23
- package/lib/commonjs/bootstrap/SDKBootstrapper.js.map +1 -1
- package/lib/commonjs/compat/codepush.js +78 -15
- package/lib/commonjs/compat/codepush.js.map +1 -1
- package/lib/commonjs/core/config/schema.js +26 -5
- package/lib/commonjs/core/config/schema.js.map +1 -1
- package/lib/commonjs/core/constants/endpoints.js +9 -6
- package/lib/commonjs/core/constants/endpoints.js.map +1 -1
- package/lib/commonjs/core/context/device.js +130 -0
- package/lib/commonjs/core/context/device.js.map +1 -1
- package/lib/commonjs/debug/replayWiring.js +2 -1
- package/lib/commonjs/debug/replayWiring.js.map +1 -1
- package/lib/commonjs/features/bugreport/BugReportFeature.js +112 -0
- package/lib/commonjs/features/bugreport/BugReportFeature.js.map +1 -0
- package/lib/commonjs/features/bugreport/buildPayload.js +97 -0
- package/lib/commonjs/features/bugreport/buildPayload.js.map +1 -0
- package/lib/commonjs/features/crash/CrashReporter.js +25 -1
- package/lib/commonjs/features/crash/CrashReporter.js.map +1 -1
- package/lib/commonjs/features/crash/capture.js +27 -9
- package/lib/commonjs/features/crash/capture.js.map +1 -1
- package/lib/commonjs/features/crash/nativeCrashBridge.js +163 -28
- package/lib/commonjs/features/crash/nativeCrashBridge.js.map +1 -1
- package/lib/commonjs/features/engage/EngagePromptProvider.js +36 -2
- package/lib/commonjs/features/engage/EngagePromptProvider.js.map +1 -1
- package/lib/commonjs/features/engage/EngagePromptView.js +47 -14
- package/lib/commonjs/features/engage/EngagePromptView.js.map +1 -1
- package/lib/commonjs/features/engage/engageTriggerEngine.js +10 -0
- package/lib/commonjs/features/engage/engageTriggerEngine.js.map +1 -1
- package/lib/commonjs/features/engage/engageTypes.js.map +1 -1
- package/lib/commonjs/features/journey/ScaleBunDebugRoot.js +2 -1
- package/lib/commonjs/features/journey/ScaleBunDebugRoot.js.map +1 -1
- package/lib/commonjs/features/ota/OtaOrchestrator.js +72 -5
- package/lib/commonjs/features/ota/OtaOrchestrator.js.map +1 -1
- package/lib/commonjs/features/ota/deviceAttributes.js +48 -0
- package/lib/commonjs/features/ota/deviceAttributes.js.map +1 -0
- package/lib/commonjs/features/ota/geoCountry.js +93 -0
- package/lib/commonjs/features/ota/geoCountry.js.map +1 -0
- package/lib/commonjs/features/ota/useOtaUpdate.js +4 -1
- package/lib/commonjs/features/ota/useOtaUpdate.js.map +1 -1
- package/lib/commonjs/features/performance/PerformanceFeature.js +23 -9
- package/lib/commonjs/features/performance/PerformanceFeature.js.map +1 -1
- package/lib/commonjs/features/performance/collectors/FrameMetricsCollector.js +69 -9
- package/lib/commonjs/features/performance/collectors/FrameMetricsCollector.js.map +1 -1
- package/lib/commonjs/features/performance/transport/PerformanceTransport.js +25 -0
- package/lib/commonjs/features/performance/transport/PerformanceTransport.js.map +1 -1
- package/lib/commonjs/features/session/BackendSessionAdapter.js +128 -11
- package/lib/commonjs/features/session/BackendSessionAdapter.js.map +1 -1
- package/lib/commonjs/features/session/JourneyEventPipeline.js +1 -0
- package/lib/commonjs/features/session/JourneyEventPipeline.js.map +1 -1
- package/lib/commonjs/features/session/ReplayCaptureManager.js +46 -0
- package/lib/commonjs/features/session/ReplayCaptureManager.js.map +1 -1
- package/lib/commonjs/features/session/SessionManager.js +42 -12
- package/lib/commonjs/features/session/SessionManager.js.map +1 -1
- package/lib/commonjs/features/session/nativeCapture.js +4 -4
- package/lib/commonjs/features/session/nativeCapture.js.map +1 -1
- package/lib/commonjs/features/session/outboxFrameCapture.js +3 -2
- package/lib/commonjs/features/session/outboxFrameCapture.js.map +1 -1
- package/lib/commonjs/features/session/sessionTypes.js.map +1 -1
- package/lib/commonjs/index.js +13 -0
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/public/ScaleBunFacade.js +199 -2
- package/lib/commonjs/public/ScaleBunFacade.js.map +1 -1
- package/lib/commonjs/specs/NativeScaleBunCrash.js.map +1 -1
- package/lib/module/analytics/EventTracker.js +29 -1
- package/lib/module/analytics/EventTracker.js.map +1 -1
- package/lib/module/bootstrap/SDKBootstrapper.js +29 -21
- package/lib/module/bootstrap/SDKBootstrapper.js.map +1 -1
- package/lib/module/compat/codepush.js +77 -14
- package/lib/module/compat/codepush.js.map +1 -1
- package/lib/module/core/config/schema.js +26 -5
- package/lib/module/core/config/schema.js.map +1 -1
- package/lib/module/core/constants/endpoints.js +9 -6
- package/lib/module/core/constants/endpoints.js.map +1 -1
- package/lib/module/core/context/device.js +119 -0
- package/lib/module/core/context/device.js.map +1 -1
- package/lib/module/debug/replayWiring.js +2 -1
- package/lib/module/debug/replayWiring.js.map +1 -1
- package/lib/module/features/bugreport/BugReportFeature.js +105 -0
- package/lib/module/features/bugreport/BugReportFeature.js.map +1 -0
- package/lib/module/features/bugreport/buildPayload.js +91 -0
- package/lib/module/features/bugreport/buildPayload.js.map +1 -0
- package/lib/module/features/crash/CrashReporter.js +25 -1
- package/lib/module/features/crash/CrashReporter.js.map +1 -1
- package/lib/module/features/crash/capture.js +28 -9
- package/lib/module/features/crash/capture.js.map +1 -1
- package/lib/module/features/crash/nativeCrashBridge.js +161 -28
- package/lib/module/features/crash/nativeCrashBridge.js.map +1 -1
- package/lib/module/features/engage/EngagePromptProvider.js +36 -2
- package/lib/module/features/engage/EngagePromptProvider.js.map +1 -1
- package/lib/module/features/engage/EngagePromptView.js +47 -14
- package/lib/module/features/engage/EngagePromptView.js.map +1 -1
- package/lib/module/features/engage/engageTriggerEngine.js +10 -0
- package/lib/module/features/engage/engageTriggerEngine.js.map +1 -1
- package/lib/module/features/engage/engageTypes.js.map +1 -1
- package/lib/module/features/journey/ScaleBunDebugRoot.js +2 -1
- package/lib/module/features/journey/ScaleBunDebugRoot.js.map +1 -1
- package/lib/module/features/ota/OtaOrchestrator.js +72 -5
- package/lib/module/features/ota/OtaOrchestrator.js.map +1 -1
- package/lib/module/features/ota/deviceAttributes.js +43 -0
- package/lib/module/features/ota/deviceAttributes.js.map +1 -0
- package/lib/module/features/ota/geoCountry.js +86 -0
- package/lib/module/features/ota/geoCountry.js.map +1 -0
- package/lib/module/features/ota/useOtaUpdate.js +4 -1
- package/lib/module/features/ota/useOtaUpdate.js.map +1 -1
- package/lib/module/features/performance/PerformanceFeature.js +23 -9
- package/lib/module/features/performance/PerformanceFeature.js.map +1 -1
- package/lib/module/features/performance/collectors/FrameMetricsCollector.js +70 -10
- package/lib/module/features/performance/collectors/FrameMetricsCollector.js.map +1 -1
- package/lib/module/features/performance/transport/PerformanceTransport.js +25 -0
- package/lib/module/features/performance/transport/PerformanceTransport.js.map +1 -1
- package/lib/module/features/session/BackendSessionAdapter.js +128 -11
- package/lib/module/features/session/BackendSessionAdapter.js.map +1 -1
- package/lib/module/features/session/JourneyEventPipeline.js +1 -0
- package/lib/module/features/session/JourneyEventPipeline.js.map +1 -1
- package/lib/module/features/session/ReplayCaptureManager.js +46 -0
- package/lib/module/features/session/ReplayCaptureManager.js.map +1 -1
- package/lib/module/features/session/SessionManager.js +42 -12
- package/lib/module/features/session/SessionManager.js.map +1 -1
- package/lib/module/features/session/nativeCapture.js +4 -5
- package/lib/module/features/session/nativeCapture.js.map +1 -1
- package/lib/module/features/session/outboxFrameCapture.js +3 -2
- package/lib/module/features/session/outboxFrameCapture.js.map +1 -1
- package/lib/module/features/session/sessionTypes.js.map +1 -1
- package/lib/module/index.js +4 -1
- package/lib/module/index.js.map +1 -1
- package/lib/module/public/ScaleBunFacade.js +199 -2
- package/lib/module/public/ScaleBunFacade.js.map +1 -1
- package/lib/module/specs/NativeScaleBunCrash.js.map +1 -1
- package/lib/typescript/analytics/EventTracker.d.ts +20 -0
- package/lib/typescript/analytics/EventTracker.d.ts.map +1 -1
- package/lib/typescript/bootstrap/SDKBootstrapper.d.ts.map +1 -1
- package/lib/typescript/compat/codepush.d.ts +27 -4
- package/lib/typescript/compat/codepush.d.ts.map +1 -1
- package/lib/typescript/core/config/schema.d.ts +4 -1
- package/lib/typescript/core/config/schema.d.ts.map +1 -1
- package/lib/typescript/core/constants/endpoints.d.ts +9 -6
- package/lib/typescript/core/constants/endpoints.d.ts.map +1 -1
- package/lib/typescript/core/context/device.d.ts +46 -0
- package/lib/typescript/core/context/device.d.ts.map +1 -1
- package/lib/typescript/debug/replayWiring.d.ts.map +1 -1
- package/lib/typescript/features/bugreport/BugReportFeature.d.ts +59 -0
- package/lib/typescript/features/bugreport/BugReportFeature.d.ts.map +1 -0
- package/lib/typescript/features/bugreport/buildPayload.d.ts +51 -0
- package/lib/typescript/features/bugreport/buildPayload.d.ts.map +1 -0
- package/lib/typescript/features/crash/CrashReporter.d.ts +20 -1
- package/lib/typescript/features/crash/CrashReporter.d.ts.map +1 -1
- package/lib/typescript/features/crash/capture.d.ts +21 -2
- package/lib/typescript/features/crash/capture.d.ts.map +1 -1
- package/lib/typescript/features/crash/nativeCrashBridge.d.ts +16 -3
- package/lib/typescript/features/crash/nativeCrashBridge.d.ts.map +1 -1
- package/lib/typescript/features/engage/EngagePromptProvider.d.ts.map +1 -1
- package/lib/typescript/features/engage/EngagePromptView.d.ts.map +1 -1
- package/lib/typescript/features/engage/EngageTransport.d.ts +1 -1
- package/lib/typescript/features/engage/EngageTransport.d.ts.map +1 -1
- package/lib/typescript/features/engage/engageTriggerEngine.d.ts.map +1 -1
- package/lib/typescript/features/engage/engageTypes.d.ts +15 -0
- package/lib/typescript/features/engage/engageTypes.d.ts.map +1 -1
- package/lib/typescript/features/journey/ScaleBunDebugRoot.d.ts.map +1 -1
- package/lib/typescript/features/ota/OtaOrchestrator.d.ts +14 -2
- package/lib/typescript/features/ota/OtaOrchestrator.d.ts.map +1 -1
- package/lib/typescript/features/ota/deviceAttributes.d.ts +18 -0
- package/lib/typescript/features/ota/deviceAttributes.d.ts.map +1 -0
- package/lib/typescript/features/ota/geoCountry.d.ts +14 -0
- package/lib/typescript/features/ota/geoCountry.d.ts.map +1 -0
- package/lib/typescript/features/ota/useOtaUpdate.d.ts +20 -2
- package/lib/typescript/features/ota/useOtaUpdate.d.ts.map +1 -1
- package/lib/typescript/features/performance/PerformanceFeature.d.ts +0 -1
- package/lib/typescript/features/performance/PerformanceFeature.d.ts.map +1 -1
- package/lib/typescript/features/performance/collectors/FrameMetricsCollector.d.ts +33 -0
- package/lib/typescript/features/performance/collectors/FrameMetricsCollector.d.ts.map +1 -1
- package/lib/typescript/features/performance/transport/PerformanceTransport.d.ts.map +1 -1
- package/lib/typescript/features/session/BackendSessionAdapter.d.ts +42 -2
- package/lib/typescript/features/session/BackendSessionAdapter.d.ts.map +1 -1
- package/lib/typescript/features/session/JourneyEventPipeline.d.ts +2 -0
- package/lib/typescript/features/session/JourneyEventPipeline.d.ts.map +1 -1
- package/lib/typescript/features/session/ReplayCaptureManager.d.ts +38 -1
- package/lib/typescript/features/session/ReplayCaptureManager.d.ts.map +1 -1
- package/lib/typescript/features/session/SessionManager.d.ts.map +1 -1
- package/lib/typescript/features/session/nativeCapture.d.ts +2 -1
- package/lib/typescript/features/session/nativeCapture.d.ts.map +1 -1
- package/lib/typescript/features/session/outboxFrameCapture.d.ts.map +1 -1
- package/lib/typescript/features/session/sessionTypes.d.ts +20 -3
- package/lib/typescript/features/session/sessionTypes.d.ts.map +1 -1
- package/lib/typescript/index.d.ts +1 -0
- package/lib/typescript/index.d.ts.map +1 -1
- package/lib/typescript/public/ScaleBunFacade.d.ts +69 -0
- package/lib/typescript/public/ScaleBunFacade.d.ts.map +1 -1
- package/lib/typescript/public/types.d.ts +34 -8
- package/lib/typescript/public/types.d.ts.map +1 -1
- package/lib/typescript/specs/NativeScaleBunCrash.d.ts +14 -0
- package/lib/typescript/specs/NativeScaleBunCrash.d.ts.map +1 -1
- package/lib/typescript/vendor/protocol/index.d.ts +17 -17
- package/package.json +1 -1
- package/src/analytics/EventTracker.ts +28 -1
- package/src/bootstrap/SDKBootstrapper.ts +27 -23
- package/src/compat/codepush.ts +87 -16
- package/src/core/config/schema.ts +26 -5
- package/src/core/constants/endpoints.ts +9 -6
- package/src/core/context/device.ts +146 -0
- package/src/debug/replayWiring.ts +2 -1
- package/src/features/bugreport/BugReportFeature.ts +128 -0
- package/src/features/bugreport/buildPayload.ts +116 -0
- package/src/features/crash/CrashReporter.ts +40 -1
- package/src/features/crash/capture.ts +36 -9
- package/src/features/crash/nativeCrashBridge.ts +156 -22
- package/src/features/engage/EngagePromptProvider.tsx +48 -2
- package/src/features/engage/EngagePromptView.tsx +51 -15
- package/src/features/engage/EngageTransport.ts +1 -1
- package/src/features/engage/engageTriggerEngine.ts +15 -0
- package/src/features/engage/engageTypes.ts +15 -0
- package/src/features/journey/ScaleBunDebugRoot.tsx +2 -1
- package/src/features/ota/OtaOrchestrator.ts +84 -6
- package/src/features/ota/deviceAttributes.ts +46 -0
- package/src/features/ota/geoCountry.ts +85 -0
- package/src/features/ota/useOtaUpdate.ts +20 -2
- package/src/features/performance/PerformanceFeature.ts +22 -9
- package/src/features/performance/collectors/FrameMetricsCollector.ts +72 -10
- package/src/features/performance/transport/PerformanceTransport.ts +18 -0
- package/src/features/session/BackendSessionAdapter.ts +137 -10
- package/src/features/session/JourneyEventPipeline.ts +3 -0
- package/src/features/session/ReplayCaptureManager.ts +53 -1
- package/src/features/session/SessionManager.ts +46 -7
- package/src/features/session/nativeCapture.ts +3 -2
- package/src/features/session/outboxFrameCapture.ts +3 -2
- package/src/features/session/sessionTypes.ts +26 -3
- package/src/index.ts +4 -1
- package/src/public/ScaleBunFacade.ts +198 -2
- package/src/public/types.ts +35 -8
- package/src/specs/NativeScaleBunCrash.ts +16 -0
|
@@ -258,6 +258,7 @@ export class BackendSessionAdapter implements SessionTransport {
|
|
|
258
258
|
bridgeAdapter.getDeviceInfo()
|
|
259
259
|
.then(nativeInfo => {
|
|
260
260
|
const m = session.metadata;
|
|
261
|
+
this._cachedAppVersion = nativeInfo?.appVersion ?? m?.appVersion ?? null;
|
|
261
262
|
const payload = {
|
|
262
263
|
sessionId: session.sessionId,
|
|
263
264
|
deviceId: session.deviceId,
|
|
@@ -394,6 +395,7 @@ export class BackendSessionAdapter implements SessionTransport {
|
|
|
394
395
|
try {
|
|
395
396
|
const nativeInfo = await bridgeAdapter.getDeviceInfo();
|
|
396
397
|
const m = session.metadata;
|
|
398
|
+
this._cachedAppVersion = nativeInfo?.appVersion ?? m?.appVersion ?? null;
|
|
397
399
|
const payload = {
|
|
398
400
|
sessionId: session.sessionId,
|
|
399
401
|
deviceId: session.deviceId,
|
|
@@ -639,18 +641,56 @@ export class BackendSessionAdapter implements SessionTransport {
|
|
|
639
641
|
}
|
|
640
642
|
}
|
|
641
643
|
|
|
644
|
+
// D8/D9/D12: this signature was {type, message, stack, screenName} — four
|
|
645
|
+
// fields — while the backend row and every dashboard surface were already
|
|
646
|
+
// built for the rest. The consequences of the four-field bottleneck:
|
|
647
|
+
// regression detection permanently false (no appVersion on any mobile
|
|
648
|
+
// error), Issue.primaryPlatform NULL for every RN crash (no platform), and
|
|
649
|
+
// native crash signal/thread/exception-type discarded one call before the
|
|
650
|
+
// wire (no metadata). The receiver needed nothing; only this signature did.
|
|
651
|
+
/** App version resolved at session start; errors stamp it so regression
|
|
652
|
+
* detection has a version to work with (D8). Null until a session starts. */
|
|
653
|
+
private _cachedAppVersion: string | null = null;
|
|
654
|
+
|
|
655
|
+
private _appVersion(): string | undefined {
|
|
656
|
+
return this._cachedAppVersion ?? undefined;
|
|
657
|
+
}
|
|
658
|
+
|
|
642
659
|
queueError(data: {
|
|
643
660
|
type: string; message: string; stack?: string; screenName?: string;
|
|
661
|
+
platform?: string;
|
|
662
|
+
appVersion?: string;
|
|
663
|
+
isFatal?: boolean;
|
|
664
|
+
metadata?: Record<string, unknown>;
|
|
665
|
+
/** Target session. Set ONLY by a recovered native crash; absent means the current session. */
|
|
666
|
+
sessionId?: string;
|
|
667
|
+
/** Stable idempotency key. Set ONLY by a recovered crash; the backend dedupes on it. */
|
|
668
|
+
clientId?: string;
|
|
644
669
|
}): void {
|
|
645
670
|
if (this.destroyed || !this.config.clientKey) return;
|
|
646
|
-
this.pendingErrors.push({
|
|
671
|
+
this.pendingErrors.push({
|
|
672
|
+
...data,
|
|
673
|
+
// The session already knows the app version — never make a crash
|
|
674
|
+
// reporter's callers supply what the transport can fill itself.
|
|
675
|
+
appVersion: data.appVersion ?? this._appVersion(),
|
|
676
|
+
// A recovered crash brings its OWN clientId — the stable id written when the crash was
|
|
677
|
+
// first persisted. Generating a fresh one here would mint a new identity on every retry
|
|
678
|
+
// and defeat the unique constraint the backend dedupes with.
|
|
679
|
+
clientId: data.clientId ?? this._clientId(),
|
|
680
|
+
timestamp: new Date().toISOString(),
|
|
681
|
+
});
|
|
647
682
|
if (this.pendingErrors.length >= 10) {
|
|
648
683
|
this._flushErrors().catch(() => {});
|
|
649
684
|
}
|
|
650
685
|
}
|
|
651
686
|
|
|
652
|
-
/**
|
|
653
|
-
|
|
687
|
+
/**
|
|
688
|
+
* Public: force-flush queued errors immediately (handled-error delivery).
|
|
689
|
+
*
|
|
690
|
+
* @returns whether the batch was DURABLY accepted. Crash recovery deletes its on-disk record on
|
|
691
|
+
* this answer, so it must reflect the backend and not merely that the call returned.
|
|
692
|
+
*/
|
|
693
|
+
flushErrors(): Promise<boolean> {
|
|
654
694
|
return this._flushErrors();
|
|
655
695
|
}
|
|
656
696
|
|
|
@@ -724,13 +764,23 @@ export class BackendSessionAdapter implements SessionTransport {
|
|
|
724
764
|
private async _enqueueNative(
|
|
725
765
|
lane: 'events' | 'network' | 'errors' | 'performance' | 'logs',
|
|
726
766
|
records: Array<Record<string, unknown>>,
|
|
767
|
+
/**
|
|
768
|
+
* Session to enqueue UNDER. Defaults to the current one, which is right for every live lane.
|
|
769
|
+
*
|
|
770
|
+
* A recovered native crash must override it. Without this parameter the outbox stamped
|
|
771
|
+
* `currentSessionId` on everything, so with the outbox enabled a recovered crash was filed
|
|
772
|
+
* against the session created after the restart — the exact defect the routing above exists
|
|
773
|
+
* to prevent, reappearing through a path that routing never touched.
|
|
774
|
+
*/
|
|
775
|
+
targetSessionId?: string,
|
|
727
776
|
): Promise<boolean> {
|
|
728
|
-
|
|
777
|
+
const sessionId = targetSessionId || this.currentSessionId;
|
|
778
|
+
if (!this.nativeOutboxEnabled || !sessionId) return false;
|
|
729
779
|
const native = getReplaySdkNative();
|
|
730
780
|
if (!native?.enqueueOutboxRecords) return false;
|
|
731
781
|
if (!(await this._ensureNativeOutbox(native))) return false;
|
|
732
782
|
try {
|
|
733
|
-
return await native.enqueueOutboxRecords(lane,
|
|
783
|
+
return await native.enqueueOutboxRecords(lane, sessionId, records);
|
|
734
784
|
} catch {
|
|
735
785
|
return false;
|
|
736
786
|
}
|
|
@@ -852,6 +902,17 @@ export class BackendSessionAdapter implements SessionTransport {
|
|
|
852
902
|
if (screen) out.screenName = screen;
|
|
853
903
|
const label = e.subtype ?? e.type;
|
|
854
904
|
if (label && label !== e.type) out.label = label;
|
|
905
|
+
/**
|
|
906
|
+
* THIS MAPPER ENUMERATES. Every field the wire carries has to be named here, so a field
|
|
907
|
+
* added to JourneyEvent and not added here is dropped silently — no error, no warning, and
|
|
908
|
+
* nothing downstream can tell the difference between "the SDK did not send it" and "the
|
|
909
|
+
* user never produced it". `frameId` was exactly that case: the column, the DTO field and
|
|
910
|
+
* the ingest write all existed already, and the value died on this line.
|
|
911
|
+
*
|
|
912
|
+
* Omitted when absent rather than sent as null, matching how screenName and label behave
|
|
913
|
+
* above — the v2 lean shape drops empty fields and the server reconstructs them.
|
|
914
|
+
*/
|
|
915
|
+
if (e.frameId) out.frameId = e.frameId;
|
|
855
916
|
const data = this._pruneEmpty(e.payload);
|
|
856
917
|
if (data && Object.keys(data).length > 0) out.data = data;
|
|
857
918
|
return out;
|
|
@@ -1265,15 +1326,81 @@ export class BackendSessionAdapter implements SessionTransport {
|
|
|
1265
1326
|
}
|
|
1266
1327
|
}
|
|
1267
1328
|
|
|
1268
|
-
|
|
1269
|
-
|
|
1329
|
+
/**
|
|
1330
|
+
* D11: deliver a bug report to the SAME ingest the web SDK uses
|
|
1331
|
+
* (`POST /ingestion/sessions/:id/bug-reports`). Fire-and-forget with one
|
|
1332
|
+
* retry deferral: RN previously had NO path to this endpoint at all —
|
|
1333
|
+
* `reportBug()` emitted an analytics event no handler consumed, so the
|
|
1334
|
+
* bug-reports page was empty forever for every RN app.
|
|
1335
|
+
*/
|
|
1336
|
+
submitBugReport(report: { message: string; title?: string; email?: string }): void {
|
|
1337
|
+
if (this.destroyed || !this.config.clientKey || !this.currentSessionId) return;
|
|
1338
|
+
void this._post(
|
|
1339
|
+
`/ingestion/sessions/${this.currentSessionId}/bug-reports`,
|
|
1340
|
+
{ ...report, timestamp: Date.now() },
|
|
1341
|
+
).catch(() => {
|
|
1342
|
+
/* best-effort; the $bug_report analytics breadcrumb still records intent */
|
|
1343
|
+
});
|
|
1344
|
+
}
|
|
1345
|
+
|
|
1346
|
+
/**
|
|
1347
|
+
* @returns TRUE only when every group in this batch was DURABLY accepted — an HTTP 2xx, or a
|
|
1348
|
+
* handoff to the native outbox, which is itself a persistent retrying queue.
|
|
1349
|
+
*
|
|
1350
|
+
* The return value is not cosmetic: `drainNativeCrash` deletes the on-disk crash record on the
|
|
1351
|
+
* strength of it. This method used to return `void` and swallow its own errors in the catch
|
|
1352
|
+
* below, so "the promise resolved" meant nothing at all — a failed POST looked exactly like a
|
|
1353
|
+
* successful one, and the caller would have deleted the only durable copy of a crash that never
|
|
1354
|
+
* arrived. Anything less than a real acknowledgement here reintroduces the loss this whole
|
|
1355
|
+
* change exists to prevent.
|
|
1356
|
+
*/
|
|
1357
|
+
private async _flushErrors(): Promise<boolean> {
|
|
1358
|
+
if (this.pendingErrors.length === 0 || !this.currentSessionId || !this.config.clientKey) return false;
|
|
1270
1359
|
const batch = this.pendingErrors.splice(0, 20);
|
|
1271
1360
|
try {
|
|
1272
|
-
if (
|
|
1273
|
-
|
|
1274
|
-
|
|
1361
|
+
if (!this._sessionRowReady()) { this.pendingErrors.unshift(...batch); return false; }
|
|
1362
|
+
/**
|
|
1363
|
+
* ROUTED BY THE ERROR'S OWN SESSION, not by whichever session is current.
|
|
1364
|
+
*
|
|
1365
|
+
* A recovered native crash carries the id of the session it actually happened in — that
|
|
1366
|
+
* process is gone, so the current session is the wrong owner. Sending it under
|
|
1367
|
+
* `currentSessionId` is what marked a healthy session crashed and left the crashed one
|
|
1368
|
+
* looking clean.
|
|
1369
|
+
*
|
|
1370
|
+
* The URL still carries a session because that is the established ingest shape; the
|
|
1371
|
+
* error's own `sessionId` simply chooses WHICH one. Errors are grouped by their target
|
|
1372
|
+
* so a batch mixing a recovered crash with live errors cannot drag the live ones onto
|
|
1373
|
+
* the dead session.
|
|
1374
|
+
*/
|
|
1375
|
+
const byTarget = new Map<string, typeof batch>();
|
|
1376
|
+
for (const e of batch) {
|
|
1377
|
+
const target = (e as { sessionId?: string }).sessionId || this.currentSessionId;
|
|
1378
|
+
byTarget.set(target, [...(byTarget.get(target) ?? []), e]);
|
|
1379
|
+
}
|
|
1380
|
+
let allAccepted = true;
|
|
1381
|
+
for (const [target, errors] of byTarget) {
|
|
1382
|
+
// `sessionId` is a routing instruction, not payload — strip it so the stored row is
|
|
1383
|
+
// byte-identical to one sent the ordinary way.
|
|
1384
|
+
const cleaned = errors.map(({ sessionId: _s, ...rest }: any) => rest);
|
|
1385
|
+
|
|
1386
|
+
// The outbox is enqueued PER TARGET, so a recovered crash keeps its own session.
|
|
1387
|
+
if (await this._enqueueNative('errors', cleaned, target)) continue;
|
|
1388
|
+
|
|
1389
|
+
try {
|
|
1390
|
+
await this._post(ENDPOINTS.INGESTION_ERRORS(target), { errors: cleaned });
|
|
1391
|
+
} catch (err) {
|
|
1392
|
+
allAccepted = false;
|
|
1393
|
+
// A permanent error (4xx) will never succeed, so re-queuing it would spin
|
|
1394
|
+
// forever; anything else is worth another attempt.
|
|
1395
|
+
if (!BackendSessionAdapter._isPermanentHttpError(err)) {
|
|
1396
|
+
this.pendingErrors.unshift(...errors);
|
|
1397
|
+
}
|
|
1398
|
+
}
|
|
1399
|
+
}
|
|
1400
|
+
return allAccepted;
|
|
1275
1401
|
} catch (err) {
|
|
1276
1402
|
if (!BackendSessionAdapter._isPermanentHttpError(err)) this.pendingErrors.unshift(...batch);
|
|
1403
|
+
return false;
|
|
1277
1404
|
}
|
|
1278
1405
|
}
|
|
1279
1406
|
|
|
@@ -91,6 +91,8 @@ export class JourneyEventPipeline {
|
|
|
91
91
|
traceId?: string;
|
|
92
92
|
source?: JourneyEventSource;
|
|
93
93
|
journeyId?: string;
|
|
94
|
+
/** Frame that was on screen when this happened. Resolved by the caller; see sessionTypes. */
|
|
95
|
+
frameId?: string;
|
|
94
96
|
},
|
|
95
97
|
): JourneyEvent | null {
|
|
96
98
|
try {
|
|
@@ -120,6 +122,7 @@ export class JourneyEventPipeline {
|
|
|
120
122
|
subtype: opts?.subtype,
|
|
121
123
|
severity: opts?.severity ?? inferSeverity(type),
|
|
122
124
|
screen: opts?.screen,
|
|
125
|
+
frameId: opts?.frameId,
|
|
123
126
|
payload: opts?.payload,
|
|
124
127
|
correlationId: opts?.correlationId,
|
|
125
128
|
traceId: opts?.traceId,
|
|
@@ -36,6 +36,7 @@ import {
|
|
|
36
36
|
getFrameRetentionPriority,
|
|
37
37
|
QUALITY_PRESETS,
|
|
38
38
|
} from './sessionTypes';
|
|
39
|
+
import type { PlatformOS } from '../../core/context/device';
|
|
39
40
|
|
|
40
41
|
// ─── Types ──────────────────────────────────────────────────────────────────
|
|
41
42
|
|
|
@@ -70,7 +71,8 @@ export interface StagedFrameResult {
|
|
|
70
71
|
frameHash: string;
|
|
71
72
|
masked: boolean;
|
|
72
73
|
timestamp: number;
|
|
73
|
-
|
|
74
|
+
// Widened: frames now carry the REAL Platform.OS instead of being relabelled 'android'.
|
|
75
|
+
platform: PlatformOS;
|
|
74
76
|
}
|
|
75
77
|
|
|
76
78
|
/** JS-authoritative frame metadata written to the outbox on commit. */
|
|
@@ -258,6 +260,15 @@ export class ReplayCaptureManager {
|
|
|
258
260
|
private _lastKeyframeTs = 0;
|
|
259
261
|
/** P2-5: frameId of the last emitted keyframe — delta base reference. */
|
|
260
262
|
private _lastKeyframeId: string | null = null;
|
|
263
|
+
/**
|
|
264
|
+
* Screen the last emitted keyframe was captured on.
|
|
265
|
+
*
|
|
266
|
+
* Tracked alongside the id purely so `currentKeyframe()` can refuse to hand a frame to an
|
|
267
|
+
* event on a different screen. The id and timestamp alone cannot answer that, and a frame
|
|
268
|
+
* from the previous screen is worse than none — it attaches a tap to a photograph of
|
|
269
|
+
* somewhere else, which every layer downstream would then present with full confidence.
|
|
270
|
+
*/
|
|
271
|
+
private _lastKeyframeScreen: string | null = null;
|
|
261
272
|
/** P2-5: emitted frames since the last keyframe (keyframe-interval guard). */
|
|
262
273
|
private _framesSinceKeyframe = 0;
|
|
263
274
|
|
|
@@ -275,6 +286,45 @@ export class ReplayCaptureManager {
|
|
|
275
286
|
|
|
276
287
|
// ─── Public API ─────────────────────────────────────────────────────
|
|
277
288
|
|
|
289
|
+
/**
|
|
290
|
+
* The keyframe that is still showing, or null.
|
|
291
|
+
*
|
|
292
|
+
* Answers "which picture was the user looking at" for an event that is about to be emitted,
|
|
293
|
+
* so a tap can carry the id of the frame it happened ON. Read-only; captures nothing.
|
|
294
|
+
*
|
|
295
|
+
* TWO CONDITIONS, both refusals rather than approximations:
|
|
296
|
+
*
|
|
297
|
+
* SAME SCREEN. A keyframe from the previous screen is not what the user was looking at.
|
|
298
|
+
* Navigation evicts the pending capture rather than re-pointing it, so after a screen
|
|
299
|
+
* change the newest keyframe genuinely belongs to somewhere else.
|
|
300
|
+
*
|
|
301
|
+
* STILL FRESH. A screen mutates constantly — a list loads, a banner rotates, a sheet is
|
|
302
|
+
* dismissed. Past `maxAgeMs` the stored picture stops being evidence of what is on screen,
|
|
303
|
+
* and stale evidence presented as current is the failure mode this whole feature exists to
|
|
304
|
+
* avoid. 10 s is deliberately conservative: the capture manager already photographs on
|
|
305
|
+
* interaction, navigation and scroll settle, so a genuinely active screen refreshes well
|
|
306
|
+
* inside it, and a screen quiet for longer than that is one nobody is interacting with.
|
|
307
|
+
*
|
|
308
|
+
* Returning null is a correct, common answer and downstream MUST treat it as "not captured"
|
|
309
|
+
* rather than as a default.
|
|
310
|
+
*/
|
|
311
|
+
currentKeyframe(
|
|
312
|
+
screenName?: string,
|
|
313
|
+
maxAgeMs = 10_000,
|
|
314
|
+
now: number = Date.now(),
|
|
315
|
+
): { frameId: string; screen: string | null; capturedAt: number } | null {
|
|
316
|
+
if (!this._lastKeyframeId || this._lastKeyframeTs <= 0) return null;
|
|
317
|
+
if (now - this._lastKeyframeTs > maxAgeMs) return null;
|
|
318
|
+
// Compared only when the caller knows its screen. An event with no screen cannot be shown
|
|
319
|
+
// to belong to this frame, so it does not get one.
|
|
320
|
+
if (!screenName || this._lastKeyframeScreen !== screenName) return null;
|
|
321
|
+
return {
|
|
322
|
+
frameId: this._lastKeyframeId,
|
|
323
|
+
screen: this._lastKeyframeScreen,
|
|
324
|
+
capturedAt: this._lastKeyframeTs,
|
|
325
|
+
};
|
|
326
|
+
}
|
|
327
|
+
|
|
278
328
|
/** Set callback for when a frame is captured */
|
|
279
329
|
setFrameEmitCallback(cb: FrameEmitCallback): void {
|
|
280
330
|
this.onFrameEmit = cb;
|
|
@@ -291,6 +341,7 @@ export class ReplayCaptureManager {
|
|
|
291
341
|
this._lastFrameHash = null;
|
|
292
342
|
this._lastKeyframeTs = 0;
|
|
293
343
|
this._lastKeyframeId = null;
|
|
344
|
+
this._lastKeyframeScreen = null;
|
|
294
345
|
this._framesSinceKeyframe = 0;
|
|
295
346
|
|
|
296
347
|
// Start frequency timer if in frequency mode
|
|
@@ -808,6 +859,7 @@ export class ReplayCaptureManager {
|
|
|
808
859
|
if (frameType === 'keyframe') {
|
|
809
860
|
this._lastKeyframeId = frameId;
|
|
810
861
|
this._lastKeyframeTs = now;
|
|
862
|
+
this._lastKeyframeScreen = screenName ?? null;
|
|
811
863
|
this._framesSinceKeyframe = 0;
|
|
812
864
|
} else {
|
|
813
865
|
this._framesSinceKeyframe++;
|
|
@@ -40,6 +40,7 @@ import { generateEventId } from '../replay/core/ids/sessionId';
|
|
|
40
40
|
import { bridgeAdapter } from '../replay/bridge/adapters/bridgeAdapter';
|
|
41
41
|
import { redactBody } from '../../debug/redaction';
|
|
42
42
|
import { consumeCalibrationTarget } from '../journey/calibrationContext';
|
|
43
|
+
import { resolvePlatformOS } from '../../core/context/device';
|
|
43
44
|
|
|
44
45
|
// ─── Types ──────────────────────────────────────────────────────────────────
|
|
45
46
|
|
|
@@ -385,12 +386,11 @@ export class SessionManager {
|
|
|
385
386
|
startedAt: Date.now(),
|
|
386
387
|
status: 'active',
|
|
387
388
|
metadata: {
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
})(), // Real OS; enriched by native device info when available
|
|
389
|
+
// The real Platform.OS. This used to be a ternary whose else-branch was 'android'
|
|
390
|
+
// and whose catch was ALSO 'android' — so a macOS session, a Windows session, and
|
|
391
|
+
// a failure to load react-native at all were reported identically, as Android.
|
|
392
|
+
// Enriched by native device info when the bridge answers.
|
|
393
|
+
platform: resolvePlatformOS(),
|
|
394
394
|
buildType: __DEV__ ? 'debug' : 'release',
|
|
395
395
|
...metadata,
|
|
396
396
|
// Capture window dp dims here so emitEvent() can normalize coords
|
|
@@ -748,6 +748,20 @@ export class SessionManager {
|
|
|
748
748
|
// new object and strips known-sensitive keys; coords/gesture survive.
|
|
749
749
|
const safePayload = redactBody(payload) as Record<string, unknown>;
|
|
750
750
|
|
|
751
|
+
/**
|
|
752
|
+
* NO `frameId` HERE, and it is not an oversight.
|
|
753
|
+
*
|
|
754
|
+
* This method is guarded by `if (this.active) return;` — it runs ONLY when no replay
|
|
755
|
+
* recording is active, which is precisely when no screenshots are being captured.
|
|
756
|
+
* There is no frame on screen to point at, so an analytics-lane tap can never be
|
|
757
|
+
* attributed to a visual state.
|
|
758
|
+
*
|
|
759
|
+
* That makes it a permanent property of this lane rather than a gap to close later,
|
|
760
|
+
* and the dashboard has to account for it: these taps live in the heatmap grid
|
|
761
|
+
* alongside recorded ones, so a state filter necessarily covers a subset of the
|
|
762
|
+
* traffic. `heat:screen-coverage` reports that subset's size per app for exactly this
|
|
763
|
+
* reason.
|
|
764
|
+
*/
|
|
751
765
|
const event: JourneyEvent = {
|
|
752
766
|
eventId: generateEventId(),
|
|
753
767
|
// sessionId is (re)stamped by the analytics lane at flush time.
|
|
@@ -849,7 +863,32 @@ export class SessionManager {
|
|
|
849
863
|
} catch { /* no-throw */ }
|
|
850
864
|
}
|
|
851
865
|
|
|
852
|
-
|
|
866
|
+
/**
|
|
867
|
+
* Which frame was on screen when this happened.
|
|
868
|
+
*
|
|
869
|
+
* Resolved BEFORE `pipeline.emit`, and deliberately before the capture triggers
|
|
870
|
+
* below: those capture the frame that shows the RESULT of the interaction, and a tap
|
|
871
|
+
* belongs to the state it was made IN, not the one it produced. Reading after them
|
|
872
|
+
* would attribute every tap to its own consequence.
|
|
873
|
+
*
|
|
874
|
+
* USER_ACTION only. A SCREEN_VIEW's frame is the screen being left, which is
|
|
875
|
+
* misleading rather than useful, and no consumer asks for it.
|
|
876
|
+
*
|
|
877
|
+
* `currentKeyframe` returns null unless the frame is from this same screen and still
|
|
878
|
+
* fresh — see its contract. Null is the common, correct answer and stays absent on
|
|
879
|
+
* the event rather than becoming a default.
|
|
880
|
+
*/
|
|
881
|
+
const framedOpts =
|
|
882
|
+
type === 'USER_ACTION'
|
|
883
|
+
? {
|
|
884
|
+
...opts,
|
|
885
|
+
frameId: this.captureManager?.currentKeyframe(
|
|
886
|
+
opts?.screen ?? this._lastKnownScreen ?? undefined,
|
|
887
|
+
)?.frameId,
|
|
888
|
+
}
|
|
889
|
+
: opts;
|
|
890
|
+
|
|
891
|
+
const event = this.pipeline.emit(type, framedOpts);
|
|
853
892
|
|
|
854
893
|
// Trigger capture on screen view or interaction
|
|
855
894
|
if (event) {
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
* Provides the callback for the SessionManager to request screenshots
|
|
5
5
|
* from the native layer (PixelCopy on Android, UIWindow on iOS).
|
|
6
6
|
*/
|
|
7
|
+
import { resolvePlatformOS, type PlatformOS } from '../../core/context/device';
|
|
7
8
|
|
|
8
9
|
export const nativeCapture = async (sessionId: string, reason: string, _quality: any) => {
|
|
9
10
|
try {
|
|
@@ -17,8 +18,8 @@ export const nativeCapture = async (sessionId: string, reason: string, _quality:
|
|
|
17
18
|
const result = await replaySdk.captureScreenshot(reason);
|
|
18
19
|
if (!result?.base64) return null;
|
|
19
20
|
|
|
20
|
-
|
|
21
|
-
const platform:
|
|
21
|
+
// Platform.OS verbatim. The ternary this replaces labelled every non-iOS frame 'android'.
|
|
22
|
+
const platform: PlatformOS = resolvePlatformOS();
|
|
22
23
|
|
|
23
24
|
return {
|
|
24
25
|
base64: result.base64 as string,
|
|
@@ -15,13 +15,14 @@
|
|
|
15
15
|
* transparently falls back to the legacy `captureScreenshot` path.
|
|
16
16
|
*/
|
|
17
17
|
|
|
18
|
-
import { Platform } from 'react-native';
|
|
19
18
|
import { getReplaySdkNative } from '../replay/bridge/nativeModule';
|
|
19
|
+
import { resolvePlatformOS, type PlatformOS } from '../../core/context/device';
|
|
20
20
|
import type { OutboxFrameCapture, StagedFrameResult, OutboxFrameMeta } from './ReplayCaptureManager';
|
|
21
21
|
|
|
22
22
|
/** Build the adapter. `ensureReady` resolves false when the native outbox is off. */
|
|
23
23
|
export function makeOutboxFrameCapture(ensureReady: () => Promise<boolean>): OutboxFrameCapture {
|
|
24
|
-
|
|
24
|
+
// Platform.OS verbatim — see core/context/device.
|
|
25
|
+
const platform: PlatformOS = resolvePlatformOS();
|
|
25
26
|
|
|
26
27
|
return {
|
|
27
28
|
async capture(sessionId, screenName, trigger): Promise<StagedFrameResult | null> {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { PlatformOS } from '../../core/context/device';
|
|
1
2
|
/**
|
|
2
3
|
* ScaleBun SDK — Unified Session Domain Model
|
|
3
4
|
*
|
|
@@ -21,7 +22,9 @@ export interface SessionMetadata {
|
|
|
21
22
|
appName?: string;
|
|
22
23
|
appVersion?: string;
|
|
23
24
|
bundleId?: string;
|
|
24
|
-
|
|
25
|
+
// Widened to the real Platform.OS — see core/context/device. The wire's platform field is an
|
|
26
|
+
// open enum, so a value this union did not anticipate travels rather than being coerced.
|
|
27
|
+
platform?: PlatformOS;
|
|
25
28
|
osVersion?: string;
|
|
26
29
|
deviceModel?: string;
|
|
27
30
|
deviceName?: string;
|
|
@@ -78,6 +81,22 @@ export interface JourneyEvent {
|
|
|
78
81
|
severity?: JourneyEventSeverity;
|
|
79
82
|
/** Screen name at time of event */
|
|
80
83
|
screen?: string;
|
|
84
|
+
/**
|
|
85
|
+
* frameId of the screenshot that was ON SCREEN when this event happened.
|
|
86
|
+
*
|
|
87
|
+
* The picture the user was looking at, not the one that follows. Mobile has no DOM to
|
|
88
|
+
* reconstruct, so a captured frame is the only record of what a screen looked like at a
|
|
89
|
+
* moment — which makes this the join key between a tap and the state it was made in.
|
|
90
|
+
*
|
|
91
|
+
* Set only when a keyframe from the SAME screen was captured recently enough to still be
|
|
92
|
+
* showing (see SessionManager.currentFrameForEvent). Absent means "not captured", which is a
|
|
93
|
+
* DIFFERENT answer from "nothing was open" and must never be folded into it:
|
|
94
|
+
* · the analytics lane never has one — it runs only when no recording is active, so no
|
|
95
|
+
* screenshots exist at all
|
|
96
|
+
* · a tap right after navigation has none — the capture is debounced and evicted on
|
|
97
|
+
* screen change
|
|
98
|
+
*/
|
|
99
|
+
frameId?: string;
|
|
81
100
|
payload?: Record<string, unknown>;
|
|
82
101
|
/** Links related events (e.g. network request start → complete) */
|
|
83
102
|
correlationId?: string;
|
|
@@ -114,7 +133,9 @@ export interface ReplayFrame {
|
|
|
114
133
|
quality: ReplayQuality;
|
|
115
134
|
captureReason: CaptureReason;
|
|
116
135
|
/** Native-only capture — JS never captures frames */
|
|
117
|
-
|
|
136
|
+
// Widened: a frame reports the REAL Platform.OS. The old union forced every non-iOS capture to
|
|
137
|
+
// be relabelled 'android', which is how RN-macOS and RN-Windows sessions became Android ones.
|
|
138
|
+
sourcePlatform: PlatformOS;
|
|
118
139
|
redactionState?: 'none' | 'text-masked' | 'full';
|
|
119
140
|
/** Screen name at capture time — for frame-to-screen correlation */
|
|
120
141
|
screen?: string;
|
|
@@ -363,7 +384,9 @@ export interface NativeFrameResult {
|
|
|
363
384
|
/** Byte size of the compressed image */
|
|
364
385
|
byteSize: number;
|
|
365
386
|
/** Platform that performed the capture */
|
|
366
|
-
|
|
387
|
+
// Widened from 'android'|'ios': a frame is labelled with the REAL Platform.OS now, and the
|
|
388
|
+
// old union forced every non-iOS capture to be relabelled 'android'.
|
|
389
|
+
platform: PlatformOS;
|
|
367
390
|
/** Actual on-device encoding after the AVIF->WebP->JPEG fallback (P2-2). */
|
|
368
391
|
format?: string;
|
|
369
392
|
/** Optional native frame hash for change detection (P2-1). JS falls back to hashing base64. */
|
package/src/index.ts
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
|
|
13
13
|
// ─── Default Export: SDK Facade Singleton ───────────────────────────────────
|
|
14
14
|
// Usage: import ScaleBun from '@scalebun/react-native';
|
|
15
|
-
// await ScaleBun.init({ projectId: '...', publishableKey: '...'
|
|
15
|
+
// await ScaleBun.init({ projectId: '...', publishableKey: '...' });
|
|
16
16
|
|
|
17
17
|
export { default } from './public/ScaleBunFacade';
|
|
18
18
|
export { ScaleBunProvider } from './public/ScaleBunProvider';
|
|
@@ -49,6 +49,9 @@ export {
|
|
|
49
49
|
} from './features/engage/EngageAnchor';
|
|
50
50
|
|
|
51
51
|
// ─── OTA Updates ───────────────────────────────────────────────────────────
|
|
52
|
+
// D3: manual capture — existed for a year, exported to nobody, delivered to a
|
|
53
|
+
// queue nothing drained. Now rides the proven crash pipeline.
|
|
54
|
+
export { captureException, captureMessage } from './features/crash/capture';
|
|
52
55
|
export { otaOrchestrator, OtaOrchestrator } from './features/ota/OtaOrchestrator';
|
|
53
56
|
export type { SyncResult, BootGuardConfig } from './features/ota/OtaOrchestrator';
|
|
54
57
|
export { useOtaUpdate } from './features/ota/useOtaUpdate';
|