@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
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Builds the body for POST /ingestion/sessions/:sessionId/bug-reports.
|
|
3
|
+
*
|
|
4
|
+
* Kept as a PURE function with no transport, no bridge and no clock of its own, because the wire
|
|
5
|
+
* shape is a cross-repo contract and the only way to keep it honest is to be able to assert it
|
|
6
|
+
* directly. Every bound below is copied from the server DTO
|
|
7
|
+
* (wzb-dashboard-backend/src/features/bug-reports/dto/submit-bug-report.dto.ts) rather than guessed:
|
|
8
|
+
*
|
|
9
|
+
* clientReportId? string idempotency key -> BugReport.clientReportId (@unique)
|
|
10
|
+
* title? string <= 300
|
|
11
|
+
* message string <= 20000 -> BugReport.description (REQUIRED)
|
|
12
|
+
* email? string <= 320
|
|
13
|
+
* url? string <= 2000
|
|
14
|
+
* console? unknown[] recent breadcrumbs
|
|
15
|
+
* screenshotKey? string <= 512, [A-Za-z0-9._/-] and no ".."
|
|
16
|
+
* timestamp? number 2000-01-01 .. 2100-01-01 in epoch MILLIS
|
|
17
|
+
* replaySeq? int 0 .. 2147483647
|
|
18
|
+
* replaySegmentId? string <= 256
|
|
19
|
+
*
|
|
20
|
+
* Truncating here rather than letting the server 400 is deliberate: a 20,001-character description
|
|
21
|
+
* is a real report from a real annoyed user, and losing it to a validation error helps nobody. The
|
|
22
|
+
* ceiling is theirs, so we respect it at the edge. Nothing is silently REWRITTEN — only cut, and
|
|
23
|
+
* only at bounds the server would otherwise reject outright.
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
/** Mirrors SubmitBugReportDto. Optional keys are omitted entirely rather than sent as undefined. */
|
|
27
|
+
|
|
28
|
+
const MAX_TITLE = 300;
|
|
29
|
+
const MAX_MESSAGE = 20_000;
|
|
30
|
+
const MAX_EMAIL = 320;
|
|
31
|
+
const MAX_SEGMENT_ID = 256;
|
|
32
|
+
/** The server rejects outside these; a drifted device clock must not cost us the whole report. */
|
|
33
|
+
const TS_MIN = 946684800000; // 2000-01-01
|
|
34
|
+
const TS_MAX = 4102444800000; // 2100-01-01
|
|
35
|
+
const MAX_BREADCRUMBS = 50;
|
|
36
|
+
function clip(v, max) {
|
|
37
|
+
if (typeof v !== 'string') return undefined;
|
|
38
|
+
const t = v.trim();
|
|
39
|
+
return t.length === 0 ? undefined : t.slice(0, max);
|
|
40
|
+
}
|
|
41
|
+
export function buildBugReportPayload(input, breadcrumbs = [], ctx = {}) {
|
|
42
|
+
const message = (input.message ?? '').trim().slice(0, MAX_MESSAGE);
|
|
43
|
+
const payload = {
|
|
44
|
+
message
|
|
45
|
+
};
|
|
46
|
+
const title = clip(input.title, MAX_TITLE);
|
|
47
|
+
if (title) payload.title = title;
|
|
48
|
+
const email = clip(input.email, MAX_EMAIL);
|
|
49
|
+
if (email) payload.email = email;
|
|
50
|
+
const clientReportId = clip(input.clientReportId, 200);
|
|
51
|
+
if (clientReportId) payload.clientReportId = clientReportId;
|
|
52
|
+
if (breadcrumbs.length > 0) payload.console = breadcrumbs.slice(-MAX_BREADCRUMBS);
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* An out-of-range clock is DROPPED, not clamped.
|
|
56
|
+
*
|
|
57
|
+
* Clamping would invent a `reportedAt` the device never observed, and the server keeps that column
|
|
58
|
+
* separate from `createdAt` precisely so the two can be compared. A missing client time is honest;
|
|
59
|
+
* a fabricated one silently corrupts that comparison.
|
|
60
|
+
*/
|
|
61
|
+
const ts = ctx.timestamp;
|
|
62
|
+
if (typeof ts === 'number' && Number.isFinite(ts) && ts >= TS_MIN && ts <= TS_MAX) {
|
|
63
|
+
payload.timestamp = ts;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* The replay anchor is included ONLY when a frame actually existed at submit time.
|
|
68
|
+
*
|
|
69
|
+
* Defaulting `replaySeq` to 0 would point every anchor-less report at the start of the session,
|
|
70
|
+
* which reads in the dashboard exactly like a real anchor and is wrong. Replay may be disabled,
|
|
71
|
+
* paused, or yet to produce a frame; absent must stay absent.
|
|
72
|
+
*/
|
|
73
|
+
const seq = ctx.replayCoord?.seq;
|
|
74
|
+
if (typeof seq === 'number' && Number.isInteger(seq) && seq >= 0 && seq <= 2147483647) {
|
|
75
|
+
payload.replaySeq = seq;
|
|
76
|
+
const segmentId = clip(ctx.replayCoord?.segmentId ?? undefined, MAX_SEGMENT_ID);
|
|
77
|
+
if (segmentId) payload.replaySegmentId = segmentId;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* `url` is intentionally NEVER set on React Native.
|
|
82
|
+
*
|
|
83
|
+
* The field means a page URL. The nearest mobile concept is the current screen name, and putting
|
|
84
|
+
* a screen name in a column called `url` is exactly the field-misuse that makes a dashboard show
|
|
85
|
+
* one thing and mean another. Screen context already reaches the report through the session it is
|
|
86
|
+
* attached to. If mobile needs its own field, that is a schema change, not a reused column.
|
|
87
|
+
*/
|
|
88
|
+
|
|
89
|
+
return payload;
|
|
90
|
+
}
|
|
91
|
+
//# sourceMappingURL=buildPayload.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"names":["MAX_TITLE","MAX_MESSAGE","MAX_EMAIL","MAX_SEGMENT_ID","TS_MIN","TS_MAX","MAX_BREADCRUMBS","clip","v","max","undefined","t","trim","length","slice","buildBugReportPayload","input","breadcrumbs","ctx","message","payload","title","email","clientReportId","console","ts","timestamp","Number","isFinite","seq","replayCoord","isInteger","replaySeq","segmentId","replaySegmentId"],"sourceRoot":"../../../../src","sources":["features/bugreport/buildPayload.ts"],"mappings":"AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;;AAmBA,MAAMA,SAAS,GAAG,GAAG;AACrB,MAAMC,WAAW,GAAG,MAAM;AAC1B,MAAMC,SAAS,GAAG,GAAG;AACrB,MAAMC,cAAc,GAAG,GAAG;AAC1B;AACA,MAAMC,MAAM,GAAG,YAAY,CAAC,CAAC;AAC7B,MAAMC,MAAM,GAAG,aAAa,CAAC,CAAC;AAC9B,MAAMC,eAAe,GAAG,EAAE;AAE1B,SAASC,IAAIA,CAACC,CAA4B,EAAEC,GAAW,EAAsB;EAC3E,IAAI,OAAOD,CAAC,KAAK,QAAQ,EAAE,OAAOE,SAAS;EAC3C,MAAMC,CAAC,GAAGH,CAAC,CAACI,IAAI,CAAC,CAAC;EAClB,OAAOD,CAAC,CAACE,MAAM,KAAK,CAAC,GAAGH,SAAS,GAAGC,CAAC,CAACG,KAAK,CAAC,CAAC,EAAEL,GAAG,CAAC;AACrD;AAEA,OAAO,SAASM,qBAAqBA,CACnCC,KAAmF,EACnFC,WAA+B,GAAG,EAAE,EACpCC,GAA6D,GAAG,CAAC,CAAC,EAChD;EAClB,MAAMC,OAAO,GAAG,CAACH,KAAK,CAACG,OAAO,IAAI,EAAE,EAAEP,IAAI,CAAC,CAAC,CAACE,KAAK,CAAC,CAAC,EAAEb,WAAW,CAAC;EAElE,MAAMmB,OAAyB,GAAG;IAAED;EAAQ,CAAC;EAE7C,MAAME,KAAK,GAAGd,IAAI,CAACS,KAAK,CAACK,KAAK,EAAErB,SAAS,CAAC;EAC1C,IAAIqB,KAAK,EAAED,OAAO,CAACC,KAAK,GAAGA,KAAK;EAEhC,MAAMC,KAAK,GAAGf,IAAI,CAACS,KAAK,CAACM,KAAK,EAAEpB,SAAS,CAAC;EAC1C,IAAIoB,KAAK,EAAEF,OAAO,CAACE,KAAK,GAAGA,KAAK;EAEhC,MAAMC,cAAc,GAAGhB,IAAI,CAACS,KAAK,CAACO,cAAc,EAAE,GAAG,CAAC;EACtD,IAAIA,cAAc,EAAEH,OAAO,CAACG,cAAc,GAAGA,cAAc;EAE3D,IAAIN,WAAW,CAACJ,MAAM,GAAG,CAAC,EAAEO,OAAO,CAACI,OAAO,GAAGP,WAAW,CAACH,KAAK,CAAC,CAACR,eAAe,CAAC;;EAEjF;AACF;AACA;AACA;AACA;AACA;AACA;EACE,MAAMmB,EAAE,GAAGP,GAAG,CAACQ,SAAS;EACxB,IAAI,OAAOD,EAAE,KAAK,QAAQ,IAAIE,MAAM,CAACC,QAAQ,CAACH,EAAE,CAAC,IAAIA,EAAE,IAAIrB,MAAM,IAAIqB,EAAE,IAAIpB,MAAM,EAAE;IACjFe,OAAO,CAACM,SAAS,GAAGD,EAAE;EACxB;;EAEA;AACF;AACA;AACA;AACA;AACA;AACA;EACE,MAAMI,GAAG,GAAGX,GAAG,CAACY,WAAW,EAAED,GAAG;EAChC,IAAI,OAAOA,GAAG,KAAK,QAAQ,IAAIF,MAAM,CAACI,SAAS,CAACF,GAAG,CAAC,IAAIA,GAAG,IAAI,CAAC,IAAIA,GAAG,IAAI,UAAU,EAAE;IACrFT,OAAO,CAACY,SAAS,GAAGH,GAAG;IACvB,MAAMI,SAAS,GAAG1B,IAAI,CAACW,GAAG,CAACY,WAAW,EAAEG,SAAS,IAAIvB,SAAS,EAAEP,cAAc,CAAC;IAC/E,IAAI8B,SAAS,EAAEb,OAAO,CAACc,eAAe,GAAGD,SAAS;EACpD;;EAEA;AACF;AACA;AACA;AACA;AACA;AACA;AACA;;EAEE,OAAOb,OAAO;AAChB","ignoreList":[]}
|
|
@@ -84,11 +84,35 @@ export class BackendErrorsSink {
|
|
|
84
84
|
deliver(report) {
|
|
85
85
|
const transport = SessionManager.getExistingInstance()?.getBackendTransport();
|
|
86
86
|
if (!transport) return;
|
|
87
|
+
// D12: this used to forward four fields and drop the rest — so a native
|
|
88
|
+
// crash's signal/thread/exception-type were captured on device and
|
|
89
|
+
// discarded one call before the wire, every RN error row had a NULL
|
|
90
|
+
// platform (making Issue.primaryPlatform mean "web-only"), and fatality
|
|
91
|
+
// was indistinguishable from a handled error. The receiver has always
|
|
92
|
+
// accepted all of these.
|
|
87
93
|
transport.queueError({
|
|
88
94
|
type: report.type,
|
|
89
95
|
message: report.message,
|
|
90
96
|
stack: report.stack,
|
|
91
|
-
screenName: report.screenName
|
|
97
|
+
screenName: report.screenName,
|
|
98
|
+
platform: report.platform,
|
|
99
|
+
isFatal: report.isFatal,
|
|
100
|
+
metadata: report.metadata,
|
|
101
|
+
/**
|
|
102
|
+
* Carried through so the transport can address the ORIGINAL session.
|
|
103
|
+
*
|
|
104
|
+
* Set only on a recovered native crash, whose session died with its process. Omitted
|
|
105
|
+
* for every live report, where the current session is the right answer — so this
|
|
106
|
+
* changes nothing about normal error delivery.
|
|
107
|
+
*/
|
|
108
|
+
...(report.sessionId ? {
|
|
109
|
+
sessionId: report.sessionId
|
|
110
|
+
} : {}),
|
|
111
|
+
// Becomes SessionError.clientId, which is @unique — that constraint is what turns a
|
|
112
|
+
// retried crash into one record instead of a duplicate.
|
|
113
|
+
...(report.crashId ? {
|
|
114
|
+
clientId: report.crashId
|
|
115
|
+
} : {})
|
|
92
116
|
});
|
|
93
117
|
// Best-effort immediate delivery so the crash surfaces quickly.
|
|
94
118
|
transport.flushErrors().catch(() => {});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"names":["noThrow","SessionManager","CrashReporter","sinks","registerSink","sink","push","i","indexOf","splice","report","deliver","DesktopEventSink","name","constructor","trackFn","message","stack","isFatal","type","source","screenName","platform","metadata","BackendErrorsSink","transport","getExistingInstance","getBackendTransport","queueError","flushErrors","catch","_reporter","getCrashReporter","resetCrashReporter"],"sourceRoot":"../../../../src","sources":["features/crash/CrashReporter.ts"],"mappings":"AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA,SAASA,OAAO,QAAQ,gCAAgC;AACxD,SAASC,cAAc,QAAQ,2BAA2B;;AAE1D;;AAGA;;
|
|
1
|
+
{"version":3,"names":["noThrow","SessionManager","CrashReporter","sinks","registerSink","sink","push","i","indexOf","splice","report","deliver","DesktopEventSink","name","constructor","trackFn","message","stack","isFatal","type","source","screenName","platform","metadata","BackendErrorsSink","transport","getExistingInstance","getBackendTransport","queueError","sessionId","crashId","clientId","flushErrors","catch","_reporter","getCrashReporter","resetCrashReporter"],"sourceRoot":"../../../../src","sources":["features/crash/CrashReporter.ts"],"mappings":"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","ignoreList":[]}
|
|
@@ -1,23 +1,42 @@
|
|
|
1
|
-
import { createEnvelope } from '../../pipeline/envelope/envelope';
|
|
2
|
-
import { memoryQueue } from '../../pipeline/queue/memoryQueue';
|
|
3
1
|
import { noThrow } from '../../core/lifecycle/crashSafe';
|
|
2
|
+
import { getCrashReporter } from './CrashReporter';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Manual error/message capture — the `Sentry.captureException` equivalent.
|
|
6
|
+
*
|
|
7
|
+
* D3: these used to write envelopes into `memoryQueue`, a queue whose only
|
|
8
|
+
* drain (`Dispatcher.flush`) was registered in the DI container and never
|
|
9
|
+
* resolved by anything — every capture was accumulated and discarded. They
|
|
10
|
+
* were also never exported, so the two entry points were reachable by nobody
|
|
11
|
+
* and delivered to nowhere.
|
|
12
|
+
*
|
|
13
|
+
* Now they ride the SAME pipeline as automatic crash capture (CrashReporter →
|
|
14
|
+
* BackendErrorsSink → `/ingestion/sessions/:id/errors`) — the one path in this
|
|
15
|
+
* SDK that is proven to land rows.
|
|
16
|
+
*/
|
|
4
17
|
export function captureException(error, extra) {
|
|
5
18
|
noThrow(() => {
|
|
6
|
-
|
|
19
|
+
getCrashReporter().report({
|
|
20
|
+
type: 'handled',
|
|
7
21
|
message: error.message,
|
|
8
22
|
stack: error.stack,
|
|
9
|
-
|
|
23
|
+
isFatal: false,
|
|
24
|
+
source: 'manual',
|
|
25
|
+
screenName: extra?.screenName,
|
|
26
|
+
metadata: extra?.metadata
|
|
10
27
|
});
|
|
11
|
-
memoryQueue.add(envelope);
|
|
12
28
|
});
|
|
13
29
|
}
|
|
14
|
-
export function captureMessage(message) {
|
|
30
|
+
export function captureMessage(message, extra) {
|
|
15
31
|
noThrow(() => {
|
|
16
|
-
|
|
32
|
+
getCrashReporter().report({
|
|
33
|
+
type: 'handled',
|
|
17
34
|
message,
|
|
18
|
-
|
|
35
|
+
isFatal: false,
|
|
36
|
+
source: 'manual',
|
|
37
|
+
screenName: extra?.screenName,
|
|
38
|
+
metadata: extra?.metadata
|
|
19
39
|
});
|
|
20
|
-
memoryQueue.add(envelope);
|
|
21
40
|
});
|
|
22
41
|
}
|
|
23
42
|
//# sourceMappingURL=capture.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"names":["
|
|
1
|
+
{"version":3,"names":["noThrow","getCrashReporter","captureException","error","extra","report","type","message","stack","isFatal","source","screenName","metadata","captureMessage"],"sourceRoot":"../../../../src","sources":["features/crash/capture.ts"],"mappings":"AAAA,SAASA,OAAO,QAAQ,gCAAgC;AACxD,SAASC,gBAAgB,QAAQ,iBAAiB;;AAElD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,OAAO,SAASC,gBAAgBA,CAC5BC,KAAY,EACZC,KAAmE,EAC/D;EACJJ,OAAO,CAAC,MAAM;IACVC,gBAAgB,CAAC,CAAC,CAACI,MAAM,CAAC;MACtBC,IAAI,EAAE,SAAS;MACfC,OAAO,EAAEJ,KAAK,CAACI,OAAO;MACtBC,KAAK,EAAEL,KAAK,CAACK,KAAK;MAClBC,OAAO,EAAE,KAAK;MACdC,MAAM,EAAE,QAAQ;MAChBC,UAAU,EAAEP,KAAK,EAAEO,UAAU;MAC7BC,QAAQ,EAAER,KAAK,EAAEQ;IACrB,CAAC,CAAC;EACN,CAAC,CAAC;AACN;AAEA,OAAO,SAASC,cAAcA,CAC1BN,OAAe,EACfH,KAAmE,EAC/D;EACJJ,OAAO,CAAC,MAAM;IACVC,gBAAgB,CAAC,CAAC,CAACI,MAAM,CAAC;MACtBC,IAAI,EAAE,SAAS;MACfC,OAAO;MACPE,OAAO,EAAE,KAAK;MACdC,MAAM,EAAE,QAAQ;MAChBC,UAAU,EAAEP,KAAK,EAAEO,UAAU;MAC7BC,QAAQ,EAAER,KAAK,EAAEQ;IACrB,CAAC,CAAC;EACN,CAAC,CAAC;AACN","ignoreList":[]}
|
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
import { NativeModules } from 'react-native';
|
|
2
2
|
import { nativeModuleVia } from '../../core/architecture';
|
|
3
|
-
import { createEnvelope } from '../../pipeline/envelope/envelope';
|
|
4
|
-
import { memoryQueue } from '../../pipeline/queue/memoryQueue';
|
|
5
3
|
import { logger } from '../../core/logger/internalLogger';
|
|
6
4
|
import { getCrashReporter } from './CrashReporter';
|
|
7
5
|
|
|
@@ -48,6 +46,26 @@ function getCrashModule() {
|
|
|
48
46
|
* NSSetUncaughtExceptionHandler + signal handlers on iOS).
|
|
49
47
|
* Safe to call multiple times — native side is idempotent.
|
|
50
48
|
*/
|
|
49
|
+
/**
|
|
50
|
+
* Tell native which session is live, so a crash that happens next can name it.
|
|
51
|
+
*
|
|
52
|
+
* Called once per session — never per event. Native writes it to crash-safe storage immediately,
|
|
53
|
+
* because a fatal handler cannot call back into JS to ask, and by the time it runs the JS thread may
|
|
54
|
+
* already be gone.
|
|
55
|
+
*
|
|
56
|
+
* Fire-and-forget and no-throw: losing this degrades a recovered crash to unattributed, which is the
|
|
57
|
+
* old behaviour. It must never be able to fail a session start.
|
|
58
|
+
*/
|
|
59
|
+
export async function setNativeActiveSession(sessionId) {
|
|
60
|
+
try {
|
|
61
|
+
const mod = getCrashModule();
|
|
62
|
+
if (!mod?.setActiveSessionId || !sessionId) return;
|
|
63
|
+
await mod.setActiveSessionId(sessionId);
|
|
64
|
+
logger.debug(`[NativeCrash] Active session → ${sessionId}`);
|
|
65
|
+
} catch {
|
|
66
|
+
// No-throw
|
|
67
|
+
}
|
|
68
|
+
}
|
|
51
69
|
export async function installNativeCrashHandler() {
|
|
52
70
|
try {
|
|
53
71
|
const mod = getCrashModule();
|
|
@@ -63,38 +81,66 @@ export async function installNativeCrashHandler() {
|
|
|
63
81
|
}
|
|
64
82
|
|
|
65
83
|
/**
|
|
66
|
-
*
|
|
67
|
-
*
|
|
68
|
-
*
|
|
84
|
+
* How many pending crashes one launch will deliver.
|
|
85
|
+
*
|
|
86
|
+
* The native spool holds three (a device that crashes, restarts offline, and crashes again), but
|
|
87
|
+
* `drainPendingCrash` hands back the OLDEST ONE ONLY. Draining once per launch therefore needed three
|
|
88
|
+
* launches to clear three crashes — and a fourth crash evicts the oldest, so on a device crashing in a
|
|
89
|
+
* loop the spool loses faster than a one-per-launch drain empties it.
|
|
90
|
+
*
|
|
91
|
+
* Bounded, not `while (true)`: this loop is driven by native state and stops the moment delivery
|
|
92
|
+
* fails, but the bound is what makes it impossible for a native bug — a clear that silently does
|
|
93
|
+
* nothing — to spin the drain forever at startup.
|
|
94
|
+
*/
|
|
95
|
+
const MAX_DRAIN_PER_LAUNCH = 4;
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Drain the crash records left from previous process runs.
|
|
99
|
+
* Returns true if at least one pending crash was found.
|
|
100
|
+
*
|
|
101
|
+
* Stops early the first time delivery fails: if the network is down for one crash it is down for the
|
|
102
|
+
* next, and every extra attempt is a request that cannot succeed made during app startup.
|
|
69
103
|
*/
|
|
70
104
|
export async function drainNativeCrash() {
|
|
105
|
+
let found = false;
|
|
106
|
+
const seen = new Set();
|
|
107
|
+
for (let i = 0; i < MAX_DRAIN_PER_LAUNCH; i++) {
|
|
108
|
+
const outcome = await drainOne(seen);
|
|
109
|
+
if (outcome === 'none') break;
|
|
110
|
+
found = true;
|
|
111
|
+
// 'kept' means the backend did not take it. The rest of the spool is behind the same
|
|
112
|
+
// network, so stop rather than fail three more times.
|
|
113
|
+
if (outcome === 'kept') break;
|
|
114
|
+
}
|
|
115
|
+
return found;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/** One record: read → report → clear if the backend took it. */
|
|
119
|
+
async function drainOne(seen) {
|
|
71
120
|
try {
|
|
72
121
|
const mod = getCrashModule();
|
|
73
|
-
if (!mod?.drainPendingCrash) return
|
|
122
|
+
if (!mod?.drainPendingCrash) return 'none';
|
|
74
123
|
const crash = await mod.drainPendingCrash();
|
|
75
|
-
if (!crash) return
|
|
124
|
+
if (!crash) return 'none';
|
|
125
|
+
|
|
126
|
+
/*
|
|
127
|
+
* The same record twice means the previous clear did not take effect — a native failure we
|
|
128
|
+
* cannot fix from here. Reporting it again would deliver a duplicate on every iteration, so
|
|
129
|
+
* treat it as the end of the spool.
|
|
130
|
+
*/
|
|
131
|
+
const seenKey = typeof crash.crashId === 'string' ? crash.crashId : '';
|
|
132
|
+
// `''` is a deliberate sentinel for the single legacy record, which has no id. Tracking it
|
|
133
|
+
// too means a legacy clear that silently fails stops the loop instead of redelivering the
|
|
134
|
+
// same crash on every one of the remaining iterations.
|
|
135
|
+
if (seen.has(seenKey)) return 'none';
|
|
136
|
+
seen.add(seenKey);
|
|
76
137
|
const stackArr = Array.isArray(crash.stackTrace) ? crash.stackTrace : [];
|
|
77
138
|
|
|
78
139
|
// 1) Desktop debug stream — keep the rich native-crash envelope.
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
stackTrace: stackArr,
|
|
84
|
-
threadName: crash.threadName,
|
|
85
|
-
timestamp: crash.timestamp,
|
|
86
|
-
platform: crash.platform,
|
|
87
|
-
...(crash.signal !== undefined ? {
|
|
88
|
-
signal: crash.signal
|
|
89
|
-
} : {}),
|
|
90
|
-
...(crash.cause !== undefined ? {
|
|
91
|
-
cause: crash.cause
|
|
92
|
-
} : {}),
|
|
93
|
-
...(crash.userInfo !== undefined ? {
|
|
94
|
-
userInfo: crash.userInfo
|
|
95
|
-
} : {})
|
|
96
|
-
});
|
|
97
|
-
memoryQueue.add(envelope);
|
|
140
|
+
// D2: a rich envelope used to be written here into memoryQueue — a queue
|
|
141
|
+
// whose drain was never wired, so the native crash's forensic detail was
|
|
142
|
+
// captured and then discarded. The flattened report below now carries the
|
|
143
|
+
// same fields in `metadata` (D12), through the pipeline that actually lands.
|
|
98
144
|
|
|
99
145
|
// 2) SaaS backend errors endpoint (dashboard Crashes view) — route the
|
|
100
146
|
// same normalized report through the central reporter. This is what
|
|
@@ -106,6 +152,30 @@ export async function drainNativeCrash() {
|
|
|
106
152
|
message: crash.message ?? crash.exceptionType ?? 'Native crash',
|
|
107
153
|
stack: stackArr.length ? stackArr.join('\n') : undefined,
|
|
108
154
|
platform: crash.platform,
|
|
155
|
+
/**
|
|
156
|
+
* THE ORIGINAL SESSION — the entire point of the recovery path.
|
|
157
|
+
*
|
|
158
|
+
* This crash happened in a process that no longer exists, so it does NOT belong to the
|
|
159
|
+
* session running now. Passing it explicitly is what stops the reporter routing it
|
|
160
|
+
* through the current SessionManager and attributing it to the session created after
|
|
161
|
+
* the restart.
|
|
162
|
+
*
|
|
163
|
+
* Undefined on records written before this shipped, and on a crash that beat the first
|
|
164
|
+
* session. The sink then keeps the legacy behaviour rather than guessing an owner.
|
|
165
|
+
*/
|
|
166
|
+
...(crash.sessionId ? {
|
|
167
|
+
sessionId: String(crash.sessionId)
|
|
168
|
+
} : {}),
|
|
169
|
+
/**
|
|
170
|
+
* The stable id, carried through so it becomes the backend's dedupe key.
|
|
171
|
+
*
|
|
172
|
+
* Written once when the crash was first persisted — never regenerated here. If this were
|
|
173
|
+
* minted per attempt, every retry would arrive as a brand-new crash and at-least-once
|
|
174
|
+
* delivery would turn into duplicate-every-time.
|
|
175
|
+
*/
|
|
176
|
+
...(crash.crashId ? {
|
|
177
|
+
crashId: String(crash.crashId)
|
|
178
|
+
} : {}),
|
|
109
179
|
metadata: {
|
|
110
180
|
exceptionType: crash.exceptionType,
|
|
111
181
|
threadName: crash.threadName,
|
|
@@ -114,8 +184,71 @@ export async function drainNativeCrash() {
|
|
|
114
184
|
} : {})
|
|
115
185
|
}
|
|
116
186
|
});
|
|
117
|
-
|
|
118
|
-
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* DELETE ONLY AFTER THE BACKEND HAS IT.
|
|
190
|
+
*
|
|
191
|
+
* The read above is non-destructive, so the crash is still on disk right now. It is removed
|
|
192
|
+
* only once delivery has actually succeeded — previously the record was destroyed at read
|
|
193
|
+
* time, so a failed upload lost the crash permanently: the app had proof of a crash, deleted
|
|
194
|
+
* it, and then failed to send it.
|
|
195
|
+
*
|
|
196
|
+
* On failure we simply do NOT clear. The crash stays pending and is retried on the next
|
|
197
|
+
* launch, which is what makes this at-least-once. Duplicate delivery is safe because the
|
|
198
|
+
* crash carries a stable `crashId` that the backend dedupes on — a retry produces no second
|
|
199
|
+
* record.
|
|
200
|
+
*
|
|
201
|
+
* A record with no `crashId` is legacy: it predates stable ids, so it cannot be safely
|
|
202
|
+
* retried (a redelivery WOULD duplicate) and is cleared immediately, preserving exactly the
|
|
203
|
+
* old at-most-once behaviour for old data rather than silently changing it.
|
|
204
|
+
*/
|
|
205
|
+
const crashId = seenKey;
|
|
206
|
+
if (!crashId) {
|
|
207
|
+
await mod.clearPendingCrash?.('');
|
|
208
|
+
logger.info('[NativeCrash] Legacy crash (no id) drained and cleared');
|
|
209
|
+
return 'cleared';
|
|
210
|
+
}
|
|
211
|
+
const delivered = await confirmDelivery();
|
|
212
|
+
if (delivered) {
|
|
213
|
+
await mod.clearPendingCrash?.(crashId);
|
|
214
|
+
logger.info(`[NativeCrash] Crash ${crashId} delivered → cleared`);
|
|
215
|
+
return 'cleared';
|
|
216
|
+
}
|
|
217
|
+
logger.info(`[NativeCrash] Crash ${crashId} not yet delivered → kept for retry`);
|
|
218
|
+
return 'kept';
|
|
219
|
+
} catch {
|
|
220
|
+
// Unknown state — stop the loop rather than risk spinning on a failure we cannot diagnose.
|
|
221
|
+
return 'none';
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* Has the crash actually reached the backend?
|
|
227
|
+
*
|
|
228
|
+
* The reporter's sinks are fire-and-forget by design (a crash report must never block or throw), so
|
|
229
|
+
* "delivered" is asked of the transport rather than inferred from `report()` returning. If the
|
|
230
|
+
* transport is not up yet — the drain can win the race against SessionManager — the answer is NO and
|
|
231
|
+
* the crash stays on disk for the next launch. That is strictly better than the old behaviour, where
|
|
232
|
+
* exactly that race deleted the record and dropped the crash silently.
|
|
233
|
+
*/
|
|
234
|
+
async function confirmDelivery() {
|
|
235
|
+
try {
|
|
236
|
+
const {
|
|
237
|
+
SessionManager
|
|
238
|
+
} = await import('../session/SessionManager');
|
|
239
|
+
const transport = SessionManager.getExistingInstance()?.getBackendTransport();
|
|
240
|
+
// No transport yet — the drain can win the startup race. That is a delivery FAILURE, not a
|
|
241
|
+
// reason to give up: the record stays on disk and the next launch retries it. This exact
|
|
242
|
+
// path used to delete the crash and send nothing.
|
|
243
|
+
if (!transport) return false;
|
|
244
|
+
/**
|
|
245
|
+
* The BOOLEAN, not merely "it resolved".
|
|
246
|
+
*
|
|
247
|
+
* flushErrors used to return void and swallow its own errors, so awaiting it proved only
|
|
248
|
+
* that the call finished. Deleting a crash on that basis would have thrown away crashes
|
|
249
|
+
* that never reached the backend — the failure this whole change exists to prevent.
|
|
250
|
+
*/
|
|
251
|
+
return (await transport.flushErrors()) === true;
|
|
119
252
|
} catch {
|
|
120
253
|
return false;
|
|
121
254
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"names":["NativeModules","nativeModuleVia","
|
|
1
|
+
{"version":3,"names":["NativeModules","nativeModuleVia","logger","getCrashReporter","_crashModule","getCrashModule","mod","turbo","require","default","debug","legacy","ScaleBunCrash","setNativeActiveSession","sessionId","setActiveSessionId","installNativeCrashHandler","install","MAX_DRAIN_PER_LAUNCH","drainNativeCrash","found","seen","Set","i","outcome","drainOne","drainPendingCrash","crash","seenKey","crashId","has","add","stackArr","Array","isArray","stackTrace","report","source","type","isFatal","message","exceptionType","stack","length","join","undefined","platform","String","metadata","threadName","signal","clearPendingCrash","info","delivered","confirmDelivery","SessionManager","transport","getExistingInstance","getBackendTransport","flushErrors","triggerNativeCrash","crashNow","warn"],"sourceRoot":"../../../../src","sources":["features/crash/nativeCrashBridge.ts"],"mappings":"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","ignoreList":[]}
|
|
@@ -405,7 +405,39 @@ export function EngagePromptProvider({
|
|
|
405
405
|
appVersion: currentAppVersion()
|
|
406
406
|
};
|
|
407
407
|
const drafts = result.attachments ?? [];
|
|
408
|
-
|
|
408
|
+
|
|
409
|
+
/**
|
|
410
|
+
* Bug-report lane — opt in per campaign via `fileAsBugReport`.
|
|
411
|
+
*
|
|
412
|
+
* A FEEDBACK campaign fired by `shake_device` is almost always "something is
|
|
413
|
+
* broken", not "how do you feel", and those are different products: a bug report
|
|
414
|
+
* gets a status, a priority and an assignee on /diagnose/bug-reports, while a survey
|
|
415
|
+
* response is counted in aggregate and cannot be triaged.
|
|
416
|
+
*
|
|
417
|
+
* Routed INSTEAD of submitResponse rather than in addition — a dual write would
|
|
418
|
+
* create two rows for one submission and double-count the campaign on the Engage
|
|
419
|
+
* side.
|
|
420
|
+
*
|
|
421
|
+
* `responseId` doubles as the idempotency key so a retried submit cannot file the
|
|
422
|
+
* same report twice (BugReport.clientReportId is @unique).
|
|
423
|
+
*
|
|
424
|
+
* Falls through to the normal lane when the host facade predates `reportBugDetailed`
|
|
425
|
+
* — an older app bundling a newer config degrades to existing behaviour instead of
|
|
426
|
+
* dropping the user's report on the floor.
|
|
427
|
+
*/
|
|
428
|
+
const bugLane = campaign.fileAsBugReport && typeof engage.reportBugDetailed === 'function' ? engage.reportBugDetailed : null;
|
|
429
|
+
if (bugLane) {
|
|
430
|
+
const outcome = await bugLane({
|
|
431
|
+
message: result.reason ?? '',
|
|
432
|
+
title: campaign.name,
|
|
433
|
+
clientReportId: responseId
|
|
434
|
+
});
|
|
435
|
+
// Surface a refusal as a throw so the existing retry/error UI handles it; a
|
|
436
|
+
// silent success here would tell the user their report was filed when it wasn't.
|
|
437
|
+
if (outcome && typeof outcome === 'object' && 'e' in outcome) {
|
|
438
|
+
throw new Error(`bug report failed: ${outcome.e}`);
|
|
439
|
+
}
|
|
440
|
+
} else if (engage.submitResponseConfirmed) {
|
|
409
441
|
// Confirmed lane: awaits real persistence (presign+PUT bytes → response
|
|
410
442
|
// POST), reports stages, idempotent on responseId, throws on failure.
|
|
411
443
|
await engage.submitResponseConfirmed(input, drafts, {
|
|
@@ -474,7 +506,9 @@ export function EngagePromptProvider({
|
|
|
474
506
|
action: options?.action
|
|
475
507
|
});
|
|
476
508
|
if (!campaign) {
|
|
477
|
-
|
|
509
|
+
// Name the inputs so a silent mismatch (wrong trigger name, throttle
|
|
510
|
+
// suppression, type filter) is diagnosable from device logs alone.
|
|
511
|
+
logger.debug(`[Engage] no eligible campaign to render (trigger=${options?.trigger ?? '—'} ` + `candidates=${config.campaigns.length} ` + `[${config.campaigns.map(c => `${c.type}:${c.trigger ?? '—'}`).join(', ')}])`);
|
|
478
512
|
return false;
|
|
479
513
|
}
|
|
480
514
|
|