@scalebun/react-native 1.0.1 → 1.0.3
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/rn/ota/BundleDownloader.kt +170 -0
- package/android/src/main/java/com/scalebun/rn/ota/ScaleBunOtaModule.kt +160 -12
- package/android/src/main/java/com/scalebun/rn/ota/SlotManager.kt +32 -6
- package/android/src/oldarch/java/com/scalebun/rn/ota/ScaleBunOtaSpec.kt +7 -1
- package/ios/Ota/BundleDownloader.swift +182 -0
- package/ios/Ota/OtaSlotManager.swift +35 -9
- package/ios/Ota/ScaleBunOtaBridge.mm +9 -0
- package/ios/Ota/ScaleBunOtaEventsModule.swift +61 -0
- package/ios/Ota/ScaleBunOtaModule.swift +58 -5
- package/lib/commonjs/bootstrap/SDKBootstrapper.js +22 -0
- package/lib/commonjs/bootstrap/SDKBootstrapper.js.map +1 -1
- package/lib/commonjs/features/crash/CrashFeature.js +18 -0
- package/lib/commonjs/features/crash/CrashFeature.js.map +1 -1
- package/lib/commonjs/features/crash/rejectionHandler.js +123 -0
- package/lib/commonjs/features/crash/rejectionHandler.js.map +1 -0
- package/lib/commonjs/features/engage/EngagePromptProvider.js +4 -2
- package/lib/commonjs/features/engage/EngagePromptProvider.js.map +1 -1
- package/lib/commonjs/features/engage/engageThrottle.js +41 -10
- package/lib/commonjs/features/engage/engageThrottle.js.map +1 -1
- package/lib/commonjs/features/engage/engageTypes.js.map +1 -1
- package/lib/commonjs/features/navigation/AutoScreenDetector.js +53 -10
- package/lib/commonjs/features/navigation/AutoScreenDetector.js.map +1 -1
- package/lib/commonjs/features/ota/OtaOrchestrator.js +515 -20
- package/lib/commonjs/features/ota/OtaOrchestrator.js.map +1 -1
- package/lib/commonjs/features/ota/environment.js +172 -0
- package/lib/commonjs/features/ota/environment.js.map +1 -0
- package/lib/commonjs/features/ota/retry.js +96 -0
- package/lib/commonjs/features/ota/retry.js.map +1 -0
- package/lib/commonjs/features/ota/signature.js +115 -0
- package/lib/commonjs/features/ota/signature.js.map +1 -0
- package/lib/commonjs/features/session/BackendSessionAdapter.js +231 -26
- package/lib/commonjs/features/session/BackendSessionAdapter.js.map +1 -1
- package/lib/commonjs/index.js +7 -0
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/public/ScaleBunErrorBoundary.js +79 -0
- package/lib/commonjs/public/ScaleBunErrorBoundary.js.map +1 -0
- package/lib/commonjs/specs/NativeScaleBunOta.js.map +1 -1
- package/lib/module/bootstrap/SDKBootstrapper.js +22 -0
- package/lib/module/bootstrap/SDKBootstrapper.js.map +1 -1
- package/lib/module/features/crash/CrashFeature.js +18 -0
- package/lib/module/features/crash/CrashFeature.js.map +1 -1
- package/lib/module/features/crash/rejectionHandler.js +116 -0
- package/lib/module/features/crash/rejectionHandler.js.map +1 -0
- package/lib/module/features/engage/EngagePromptProvider.js +4 -2
- package/lib/module/features/engage/EngagePromptProvider.js.map +1 -1
- package/lib/module/features/engage/engageThrottle.js +41 -10
- package/lib/module/features/engage/engageThrottle.js.map +1 -1
- package/lib/module/features/engage/engageTypes.js.map +1 -1
- package/lib/module/features/navigation/AutoScreenDetector.js +53 -10
- package/lib/module/features/navigation/AutoScreenDetector.js.map +1 -1
- package/lib/module/features/ota/OtaOrchestrator.js +516 -21
- package/lib/module/features/ota/OtaOrchestrator.js.map +1 -1
- package/lib/module/features/ota/environment.js +165 -0
- package/lib/module/features/ota/environment.js.map +1 -0
- package/lib/module/features/ota/retry.js +87 -0
- package/lib/module/features/ota/retry.js.map +1 -0
- package/lib/module/features/ota/signature.js +109 -0
- package/lib/module/features/ota/signature.js.map +1 -0
- package/lib/module/features/session/BackendSessionAdapter.js +230 -26
- package/lib/module/features/session/BackendSessionAdapter.js.map +1 -1
- package/lib/module/index.js +6 -0
- package/lib/module/index.js.map +1 -1
- package/lib/module/public/ScaleBunErrorBoundary.js +70 -0
- package/lib/module/public/ScaleBunErrorBoundary.js.map +1 -0
- package/lib/module/specs/NativeScaleBunOta.js.map +1 -1
- package/lib/typescript/bootstrap/SDKBootstrapper.d.ts.map +1 -1
- package/lib/typescript/features/crash/CrashFeature.d.ts.map +1 -1
- package/lib/typescript/features/crash/rejectionHandler.d.ts +46 -0
- package/lib/typescript/features/crash/rejectionHandler.d.ts.map +1 -0
- package/lib/typescript/features/engage/EngagePromptProvider.d.ts.map +1 -1
- package/lib/typescript/features/engage/engageThrottle.d.ts +24 -4
- package/lib/typescript/features/engage/engageThrottle.d.ts.map +1 -1
- package/lib/typescript/features/engage/engageTypes.d.ts +6 -0
- package/lib/typescript/features/engage/engageTypes.d.ts.map +1 -1
- package/lib/typescript/features/navigation/AutoScreenDetector.d.ts +9 -0
- package/lib/typescript/features/navigation/AutoScreenDetector.d.ts.map +1 -1
- package/lib/typescript/features/ota/OtaOrchestrator.d.ts +67 -1
- package/lib/typescript/features/ota/OtaOrchestrator.d.ts.map +1 -1
- package/lib/typescript/features/ota/OtaTypes.d.ts +42 -0
- package/lib/typescript/features/ota/OtaTypes.d.ts.map +1 -1
- package/lib/typescript/features/ota/environment.d.ts +99 -0
- package/lib/typescript/features/ota/environment.d.ts.map +1 -0
- package/lib/typescript/features/ota/retry.d.ts +53 -0
- package/lib/typescript/features/ota/retry.d.ts.map +1 -0
- package/lib/typescript/features/ota/signature.d.ts +66 -0
- package/lib/typescript/features/ota/signature.d.ts.map +1 -0
- package/lib/typescript/features/session/BackendSessionAdapter.d.ts +93 -2
- package/lib/typescript/features/session/BackendSessionAdapter.d.ts.map +1 -1
- package/lib/typescript/index.d.ts +6 -0
- package/lib/typescript/index.d.ts.map +1 -1
- package/lib/typescript/public/ScaleBunErrorBoundary.d.ts +44 -0
- package/lib/typescript/public/ScaleBunErrorBoundary.d.ts.map +1 -0
- package/lib/typescript/specs/NativeScaleBunOta.d.ts +11 -0
- package/lib/typescript/specs/NativeScaleBunOta.d.ts.map +1 -1
- package/package.json +3 -3
- package/src/bootstrap/SDKBootstrapper.ts +24 -0
- package/src/features/crash/CrashFeature.ts +19 -0
- package/src/features/crash/rejectionHandler.ts +134 -0
- package/src/features/engage/EngagePromptProvider.tsx +4 -2
- package/src/features/engage/engageThrottle.ts +44 -6
- package/src/features/engage/engageTypes.ts +6 -0
- package/src/features/navigation/AutoScreenDetector.ts +59 -4
- package/src/features/ota/OtaOrchestrator.ts +594 -23
- package/src/features/ota/OtaTypes.ts +56 -0
- package/src/features/ota/environment.ts +199 -0
- package/src/features/ota/retry.ts +123 -0
- package/src/features/ota/signature.ts +123 -0
- package/src/features/session/BackendSessionAdapter.ts +233 -25
- package/src/index.ts +6 -0
- package/src/public/ScaleBunErrorBoundary.tsx +79 -0
- package/src/specs/NativeScaleBunOta.ts +12 -0
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bundle signature verification (OTA-03).
|
|
3
|
+
*
|
|
4
|
+
* WHAT WAS WRONG. `ota-signing-key.service.ts` documents the intended model
|
|
5
|
+
* exactly: the CLI generates an ed25519 keypair locally, uploads only the public
|
|
6
|
+
* half, and "the device verifies every bundle's signature before install and
|
|
7
|
+
* rejects forgeries." Two of those three steps did not exist. The CLI had no
|
|
8
|
+
* signing code, and this SDK received `bundle.signature` in the check response
|
|
9
|
+
* and never read it. The only client-side check was SHA-256 against a hash
|
|
10
|
+
* delivered in the same response as the URL — which defends against a corrupted
|
|
11
|
+
* download and nothing else. On a platform whose entire purpose is remote code
|
|
12
|
+
* delivery, that is the control that matters most.
|
|
13
|
+
*
|
|
14
|
+
* WHY IT IS SHAPED LIKE THIS. The SDK ships zero third-party runtime
|
|
15
|
+
* dependencies, and React Native has no built-in ed25519. So verification is
|
|
16
|
+
* delegated to a host-provided verifier when one is installed, and the SDK's job
|
|
17
|
+
* is to decide — unambiguously — what happens when there is not one.
|
|
18
|
+
*
|
|
19
|
+
* THE POLICY, which is the important part:
|
|
20
|
+
*
|
|
21
|
+
* - A bundle carrying a signature, with a configured public key and a
|
|
22
|
+
* verifier: verified. Failure REJECTS the install. This is the goal state.
|
|
23
|
+
* - A bundle carrying a signature, with a public key configured, but no
|
|
24
|
+
* verifier available: REJECT. The app asked for signature enforcement by
|
|
25
|
+
* configuring a key; silently installing unverified code because a helper is
|
|
26
|
+
* missing would turn a security feature into a placebo.
|
|
27
|
+
* - No public key configured: skip, and say so once. Signing is opt-in per app,
|
|
28
|
+
* and an app that has not adopted it must keep working.
|
|
29
|
+
*
|
|
30
|
+
* The failure mode is the whole design: an app that opts in cannot be
|
|
31
|
+
* accidentally downgraded to unverified installs.
|
|
32
|
+
*/
|
|
33
|
+
/** Verifies a detached ed25519 signature. Supplied by the host app. */
|
|
34
|
+
export type SignatureVerifier = (input: {
|
|
35
|
+
/** Hex-encoded SHA-256 of the bundle — what the signature is over. */
|
|
36
|
+
messageHex: string;
|
|
37
|
+
/** Hex or base64 detached signature from the check response. */
|
|
38
|
+
signature: string;
|
|
39
|
+
/** The app's configured ed25519 public key. */
|
|
40
|
+
publicKey: string;
|
|
41
|
+
}) => boolean | Promise<boolean>;
|
|
42
|
+
export interface SignatureConfig {
|
|
43
|
+
/** ed25519 public key shipped in the app binary. Absent = signing not adopted. */
|
|
44
|
+
publicKey?: string;
|
|
45
|
+
/** Host-provided ed25519 verification function. */
|
|
46
|
+
verifier?: SignatureVerifier;
|
|
47
|
+
}
|
|
48
|
+
export type SignatureOutcome = {
|
|
49
|
+
ok: true;
|
|
50
|
+
reason: 'verified' | 'not_configured';
|
|
51
|
+
} | {
|
|
52
|
+
ok: false;
|
|
53
|
+
reason: 'no_verifier' | 'invalid_signature' | 'missing_signature' | 'verifier_threw';
|
|
54
|
+
};
|
|
55
|
+
/**
|
|
56
|
+
* Decide whether a bundle may be staged.
|
|
57
|
+
*
|
|
58
|
+
* Returns a structured outcome rather than a boolean so the caller can emit a
|
|
59
|
+
* precise telemetry reason — "we shipped unverified code because no key was
|
|
60
|
+
* configured" and "we refused because the signature was forged" are very
|
|
61
|
+
* different operational events and must not collapse into one.
|
|
62
|
+
*/
|
|
63
|
+
export declare function verifyBundleSignature(bundleSha256: string, signature: string | undefined, config: SignatureConfig | undefined): Promise<SignatureOutcome>;
|
|
64
|
+
/** Test seam — resets the once-per-process "not configured" warning. */
|
|
65
|
+
export declare function _resetSignatureWarnings(): void;
|
|
66
|
+
//# sourceMappingURL=signature.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"signature.d.ts","sourceRoot":"","sources":["../../../../src/features/ota/signature.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AAEH,uEAAuE;AACvE,MAAM,MAAM,iBAAiB,GAAG,CAAC,KAAK,EAAE;IACtC,sEAAsE;IACtE,UAAU,EAAE,MAAM,CAAC;IACnB,gEAAgE;IAChE,SAAS,EAAE,MAAM,CAAC;IAClB,+CAA+C;IAC/C,SAAS,EAAE,MAAM,CAAC;CACnB,KAAK,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;AAEjC,MAAM,WAAW,eAAe;IAC9B,kFAAkF;IAClF,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,mDAAmD;IACnD,QAAQ,CAAC,EAAE,iBAAiB,CAAC;CAC9B;AAED,MAAM,MAAM,gBAAgB,GACxB;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,MAAM,EAAE,UAAU,GAAG,gBAAgB,CAAA;CAAE,GACnD;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,aAAa,GAAG,mBAAmB,GAAG,mBAAmB,GAAG,gBAAgB,CAAA;CAAE,CAAC;AAIxG;;;;;;;GAOG;AACH,wBAAsB,qBAAqB,CACzC,YAAY,EAAE,MAAM,EACpB,SAAS,EAAE,MAAM,GAAG,SAAS,EAC7B,MAAM,EAAE,eAAe,GAAG,SAAS,GAClC,OAAO,CAAC,gBAAgB,CAAC,CA+C3B;AAED,wEAAwE;AACxE,wBAAgB,uBAAuB,IAAI,IAAI,CAE9C"}
|
|
@@ -89,8 +89,31 @@ export declare class BackendSessionAdapter implements SessionTransport {
|
|
|
89
89
|
private readonly _frameStore;
|
|
90
90
|
/** Storage key for the snapshotted unflushed frame buffer (see persistPendingFrames). */
|
|
91
91
|
private static readonly PENDING_FRAMES_KEY;
|
|
92
|
-
/**
|
|
93
|
-
|
|
92
|
+
/**
|
|
93
|
+
* Byte budget for the cross-kill frame snapshot (see `_selectFramesToPersist`).
|
|
94
|
+
*
|
|
95
|
+
* Replaces a flat 10-frame cap. A count could not bound the KV write at all — these frames carry
|
|
96
|
+
* base64 screenshots, so ten of them is anywhere from ~200 KB to several MB depending on device
|
|
97
|
+
* resolution, and an oversized value is exactly what fails on the background transition where
|
|
98
|
+
* this write happens.
|
|
99
|
+
*
|
|
100
|
+
* 2 MB is deliberately modest: this is a last-resort snapshot for frames that never reached the
|
|
101
|
+
* network, not a mirror of the session. Enough for a keyframe plus a run of deltas on any device;
|
|
102
|
+
* small enough that writing it cannot itself be the reason backgrounding stalls.
|
|
103
|
+
*/
|
|
104
|
+
private static readonly MAX_PERSIST_BYTES;
|
|
105
|
+
/**
|
|
106
|
+
* Frames the backend accepts in ONE request — `'Max 100 frames per batch'`
|
|
107
|
+
* (`IngestionService.uploadFrames`). Exceeding it is a 4xx, which this SDK classifies as
|
|
108
|
+
* permanent.
|
|
109
|
+
*
|
|
110
|
+
* This has to exist alongside the byte budget, not instead of it. The budget alone cannot bound
|
|
111
|
+
* the COUNT, and the count is what the server checks: measured on real traffic, mobile keyframes
|
|
112
|
+
* average ~16 KB, so a 2 MB snapshot is around 128 frames — over the cap by construction. The old
|
|
113
|
+
* flat 10-frame rule was under it by construction, which is why this failure could not happen
|
|
114
|
+
* before and can now.
|
|
115
|
+
*/
|
|
116
|
+
private static readonly MAX_FRAMES_PER_BATCH;
|
|
94
117
|
private objectStorageActive;
|
|
95
118
|
private readonly nativeOutboxEnabled;
|
|
96
119
|
private _nativeOutboxReady;
|
|
@@ -104,6 +127,14 @@ export declare class BackendSessionAdapter implements SessionTransport {
|
|
|
104
127
|
private pendingLogs;
|
|
105
128
|
private flushTimer;
|
|
106
129
|
private destroyed;
|
|
130
|
+
/**
|
|
131
|
+
* Latch for `sdk_replay_capture_failed` — reported once per adapter, not once per rejected batch.
|
|
132
|
+
*
|
|
133
|
+
* A session whose row was never created rejects EVERY frame batch, so an unlatched report would
|
|
134
|
+
* emit one analytics event per flush for as long as the app stays open, on the same lane it is
|
|
135
|
+
* trying to report through.
|
|
136
|
+
*/
|
|
137
|
+
private _captureFailureReported;
|
|
107
138
|
private static readonly ANALYTICS_BUFFER_CAP;
|
|
108
139
|
/** Logs lane: queue length that triggers an eager flush (mirrors the network lane). */
|
|
109
140
|
private static readonly LOGS_BATCH_SIZE;
|
|
@@ -272,6 +303,44 @@ export declare class BackendSessionAdapter implements SessionTransport {
|
|
|
272
303
|
/** Shallow-prune null/undefined/'' leaves from a payload object. */
|
|
273
304
|
private _pruneEmpty;
|
|
274
305
|
private _flushFrames;
|
|
306
|
+
/**
|
|
307
|
+
* Report frames the SDK gave up on, on the analytics lane.
|
|
308
|
+
*
|
|
309
|
+
* SAME EVENT NAME AS THE WEB SDK (`sdk_replay_capture_failed`) with platform-specific `reason`
|
|
310
|
+
* values, so one dashboard breakdown reads both platforms without a concept-map alias — the two
|
|
311
|
+
* SDKs share no event vocabulary otherwise (`$pageview` vs `screen_viewed`), and adding a second
|
|
312
|
+
* name here would need an entry in `event-concepts.ts` for no benefit.
|
|
313
|
+
*
|
|
314
|
+
* Latched to once per session: a session whose row was never created rejects every batch, and an
|
|
315
|
+
* unlatched report would emit one event per flush for as long as the app is open.
|
|
316
|
+
*
|
|
317
|
+
* Best-effort and never throws — a diagnostic must not be able to break the capture path it
|
|
318
|
+
* describes.
|
|
319
|
+
*/
|
|
320
|
+
private _reportCaptureFailure;
|
|
321
|
+
/**
|
|
322
|
+
* Post frames in BACKEND-SAFE CHUNKS, and return the ones that were not durably accepted.
|
|
323
|
+
*
|
|
324
|
+
* WHY THIS EXISTS. `_postFrameBatch` issues exactly one request for whatever array it is handed,
|
|
325
|
+
* and both callers could hand it more than the server's 100-frame cap — the live flush via
|
|
326
|
+
* `config.batchSize`, and cross-kill recovery via the 2 MB persistence budget (~128 frames at the
|
|
327
|
+
* measured 16 KB average). The result was a 4xx, classified permanent, and on the recovery path
|
|
328
|
+
* that meant `_clearPersistedFrames()` — the ENTIRE cross-kill snapshot deleted because it was
|
|
329
|
+
* one frame too big to send in a single request it never needed to be sent in.
|
|
330
|
+
*
|
|
331
|
+
* THE RETURN VALUE IS THE POINT. The caller must be able to delete exactly what landed and keep
|
|
332
|
+
* exactly what did not, so this reports the survivors rather than throwing:
|
|
333
|
+
* - chunk accepted → dropped from the result (it is durably stored)
|
|
334
|
+
* - chunk PERMANENTLY refused → dropped, and reported once. Re-sending identical bytes cannot
|
|
335
|
+
* succeed, and holding it would wedge every future launch on the
|
|
336
|
+
* same poison chunk. Only that chunk is abandoned; the rest still
|
|
337
|
+
* gets its chance, which is the whole difference from before.
|
|
338
|
+
* - chunk TRANSIENTLY failed → that chunk AND every chunk after it are kept, in order. We stop
|
|
339
|
+
* on the first transient failure rather than pressing on, because
|
|
340
|
+
* the usual cause is the network being gone and the rest would
|
|
341
|
+
* fail identically.
|
|
342
|
+
*/
|
|
343
|
+
private _postFramesChunked;
|
|
275
344
|
/**
|
|
276
345
|
* POST one frame batch under an EXPLICIT sessionId (object-storage path when
|
|
277
346
|
* active, else legacy inline base64). Used by both the live flush and cross-kill
|
|
@@ -287,6 +356,28 @@ export declare class BackendSessionAdapter implements SessionTransport {
|
|
|
287
356
|
* next coalesced flush. No-op if recovery storage is absent or nothing pending.
|
|
288
357
|
*/
|
|
289
358
|
persistPendingFrames(): void;
|
|
359
|
+
/**
|
|
360
|
+
* Choose which unflushed frames survive a process kill.
|
|
361
|
+
*
|
|
362
|
+
* WHY THIS IS NOT `slice(-10)`.
|
|
363
|
+
*
|
|
364
|
+
* The old rule kept the ten most RECENT frames, which is the wrong end of the buffer for replay.
|
|
365
|
+
* A delta reconstructs against its base keyframe, so ten trailing deltas whose keyframe was
|
|
366
|
+
* dropped are unplayable on their own — they arrive as `orphaned`, the state the dashboard flags
|
|
367
|
+
* as a capture defect. Keeping the keyframe and fewer deltas yields a SHORTER replay; keeping the
|
|
368
|
+
* deltas and no keyframe yields NO replay. That asymmetry is the whole decision.
|
|
369
|
+
*
|
|
370
|
+
* So: every keyframe is kept, then the most recent deltas fill whatever byte budget is left. This
|
|
371
|
+
* mirrors the web SDK's `capBytes` policy (which sheds deltas first and never the keyframe), so
|
|
372
|
+
* the two platforms lose the same thing under pressure instead of two different things.
|
|
373
|
+
*
|
|
374
|
+
* BYTES, not a frame count. A count is a poor proxy here: these carry base64 screenshots, so ten
|
|
375
|
+
* frames may be 200 KB or 4 MB depending on device resolution — and this write goes to a KV store
|
|
376
|
+
* on the background transition, where an oversized value is the thing most likely to fail and
|
|
377
|
+
* lose the snapshot entirely. `imageData.length` is the base64 length, which is within ~1% of the
|
|
378
|
+
* serialized cost and needs no extra pass.
|
|
379
|
+
*/
|
|
380
|
+
private static _selectFramesToPersist;
|
|
290
381
|
/** Drop the persisted frame snapshot (after successful delivery). Never throws. */
|
|
291
382
|
private _clearPersistedFrames;
|
|
292
383
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"BackendSessionAdapter.d.ts","sourceRoot":"","sources":["../../../../src/features/session/BackendSessionAdapter.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AASH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAC;
|
|
1
|
+
{"version":3,"file":"BackendSessionAdapter.d.ts","sourceRoot":"","sources":["../../../../src/features/session/BackendSessionAdapter.ts"],"names":[],"mappings":"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"}
|
|
@@ -11,6 +11,12 @@
|
|
|
11
11
|
*/
|
|
12
12
|
export { default } from './public/ScaleBunFacade';
|
|
13
13
|
export { ScaleBunProvider } from './public/ScaleBunProvider';
|
|
14
|
+
/**
|
|
15
|
+
* Render-error boundary. Named and shaped to match `@scalebun/web-react`'s export so the same code
|
|
16
|
+
* works on both platforms — see the note in ScaleBunErrorBoundary.tsx for why a host boundary
|
|
17
|
+
* without this silently swallowed every React render error.
|
|
18
|
+
*/
|
|
19
|
+
export { ScaleBunErrorBoundary, type ScaleBunErrorBoundaryProps } from './public/ScaleBunErrorBoundary';
|
|
14
20
|
export { ScaleBunDebugRoot, ScaleBunScreen, useScaleBunScreen } from './features/journey/ScaleBunDebugRoot';
|
|
15
21
|
export { ScaleBunScrollView } from './features/journey/ScaleBunScrollView';
|
|
16
22
|
export { ScaleBunFlatList } from './features/journey/ScaleBunFlatList';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAMH,OAAO,EAAE,OAAO,EAAE,MAAM,yBAAyB,CAAC;AAClD,OAAO,EAAE,gBAAgB,EAAE,MAAM,2BAA2B,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ScaleBunErrorBoundary — React render errors, forwarded to the SDK.
|
|
3
|
+
*
|
|
4
|
+
* THE GAP THIS CLOSES
|
|
5
|
+
* `ErrorUtils.setGlobalHandler` catches a render error only when NO boundary intercepts it first.
|
|
6
|
+
* The moment a host wraps its tree in its own boundary — which most production apps do — that
|
|
7
|
+
* boundary's `componentDidCatch` consumes the error, it never reaches `ErrorUtils`, and the SDK
|
|
8
|
+
* recorded nothing at all. The screen went blank or fell back, and the crash list stayed empty.
|
|
9
|
+
*
|
|
10
|
+
* The web SDK has shipped `ScaleBunErrorBoundary` from `@scalebun/web-react` for exactly this reason;
|
|
11
|
+
* React Native had no equivalent and no `componentDidCatch` hook a host could forward through. This
|
|
12
|
+
* is the counterpart, and the two are deliberately the same component with the same prop names so a
|
|
13
|
+
* team working across both platforms writes the same thing twice.
|
|
14
|
+
*
|
|
15
|
+
* Use it INSTEAD of your own boundary, or nest it inside one — `onError` runs after capture so an
|
|
16
|
+
* existing boundary's logging keeps working.
|
|
17
|
+
*
|
|
18
|
+
* ```tsx
|
|
19
|
+
* <ScaleBunErrorBoundary fallback={(e) => <Text>Something broke: {e.message}</Text>}>
|
|
20
|
+
* <App />
|
|
21
|
+
* </ScaleBunErrorBoundary>
|
|
22
|
+
* ```
|
|
23
|
+
*/
|
|
24
|
+
import { Component, type ErrorInfo, type ReactNode } from 'react';
|
|
25
|
+
export interface ScaleBunErrorBoundaryProps {
|
|
26
|
+
children: ReactNode;
|
|
27
|
+
/** Rendered after a caught error. A function receives the error; a node is rendered as-is. */
|
|
28
|
+
fallback?: ReactNode | ((error: Error) => ReactNode);
|
|
29
|
+
/** Extra hook after the error is captured (e.g. custom logging). */
|
|
30
|
+
onError?: (error: Error, info: ErrorInfo) => void;
|
|
31
|
+
/** Screen name to attribute the error to, when the host knows it. */
|
|
32
|
+
screenName?: string;
|
|
33
|
+
}
|
|
34
|
+
interface State {
|
|
35
|
+
error: Error | null;
|
|
36
|
+
}
|
|
37
|
+
export declare class ScaleBunErrorBoundary extends Component<ScaleBunErrorBoundaryProps, State> {
|
|
38
|
+
state: State;
|
|
39
|
+
static getDerivedStateFromError(error: Error): State;
|
|
40
|
+
componentDidCatch(error: Error, info: ErrorInfo): void;
|
|
41
|
+
render(): ReactNode;
|
|
42
|
+
}
|
|
43
|
+
export {};
|
|
44
|
+
//# sourceMappingURL=ScaleBunErrorBoundary.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ScaleBunErrorBoundary.d.ts","sourceRoot":"","sources":["../../../src/public/ScaleBunErrorBoundary.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,OAAO,EAAE,SAAS,EAAE,KAAK,SAAS,EAAE,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAGlE,MAAM,WAAW,0BAA0B;IACvC,QAAQ,EAAE,SAAS,CAAC;IACpB,8FAA8F;IAC9F,QAAQ,CAAC,EAAE,SAAS,GAAG,CAAC,CAAC,KAAK,EAAE,KAAK,KAAK,SAAS,CAAC,CAAC;IACrD,oEAAoE;IACpE,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,EAAE,IAAI,EAAE,SAAS,KAAK,IAAI,CAAC;IAClD,qEAAqE;IACrE,UAAU,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,UAAU,KAAK;IACX,KAAK,EAAE,KAAK,GAAG,IAAI,CAAC;CACvB;AAED,qBAAa,qBAAsB,SAAQ,SAAS,CAAC,0BAA0B,EAAE,KAAK,CAAC;IACnF,KAAK,EAAE,KAAK,CAAmB;IAE/B,MAAM,CAAC,wBAAwB,CAAC,KAAK,EAAE,KAAK,GAAG,KAAK;IAIpD,iBAAiB,CAAC,KAAK,EAAE,KAAK,EAAE,IAAI,EAAE,SAAS,GAAG,IAAI;IAwBtD,MAAM,IAAI,SAAS;CAOtB"}
|
|
@@ -65,6 +65,17 @@ export interface Spec extends TurboModule {
|
|
|
65
65
|
* at cold start, BEFORE JS loads.
|
|
66
66
|
*/
|
|
67
67
|
revertToPrevious(): Promise<boolean>;
|
|
68
|
+
/**
|
|
69
|
+
* Restart the React Native runtime so the applied bundle loads NOW.
|
|
70
|
+
*
|
|
71
|
+
* Exists because `DevSettings.reload()` is a silent no-op in RELEASE builds
|
|
72
|
+
* (verified live: "Triggering app restart…" logged, nothing happened, same
|
|
73
|
+
* process, old bundle still running). That made IMMEDIATE / ON_NEXT_RESUME /
|
|
74
|
+
* `autoRestart` / the CodePush shim's restart all fiction in production.
|
|
75
|
+
* Android: `ReactHost.reload()` (bridgeless) or
|
|
76
|
+
* `recreateReactContextInBackground()` (bridge). iOS: RCTReloadCommand.
|
|
77
|
+
*/
|
|
78
|
+
restartApp(): void;
|
|
68
79
|
}
|
|
69
80
|
/**
|
|
70
81
|
* Resolved via `get` (not `getEnforcing`) so the SDK degrades gracefully when
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"NativeScaleBunOta.d.ts","sourceRoot":"","sources":["../../../src/specs/NativeScaleBunOta.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAGhD;;;;;;;;;;;;;;;GAeG;AACH,MAAM,WAAW,IAAK,SAAQ,WAAW;IACvC;;;;OAIG;IACH,eAAe,IAAI,MAAM,GAAG,IAAI,CAAC;IAEjC;;;;;OAKG;IACH,YAAY,IAAI,MAAM,CAAC;IAEvB;;;OAGG;IACH,WAAW,CAAC,UAAU,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAElE;;;;;;;;;;;;OAYG;IACH,UAAU,CAAC,QAAQ,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAE5F;;;OAGG;IACH,WAAW,IAAI,OAAO,CAAC,OAAO,CAAC,CAAC;IAEhC;;;;OAIG;IACH,WAAW,IAAI,IAAI,CAAC;IAEpB;;;;OAIG;IACH,gBAAgB,IAAI,OAAO,CAAC,OAAO,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"NativeScaleBunOta.d.ts","sourceRoot":"","sources":["../../../src/specs/NativeScaleBunOta.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAGhD;;;;;;;;;;;;;;;GAeG;AACH,MAAM,WAAW,IAAK,SAAQ,WAAW;IACvC;;;;OAIG;IACH,eAAe,IAAI,MAAM,GAAG,IAAI,CAAC;IAEjC;;;;;OAKG;IACH,YAAY,IAAI,MAAM,CAAC;IAEvB;;;OAGG;IACH,WAAW,CAAC,UAAU,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAElE;;;;;;;;;;;;OAYG;IACH,UAAU,CAAC,QAAQ,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAE5F;;;OAGG;IACH,WAAW,IAAI,OAAO,CAAC,OAAO,CAAC,CAAC;IAEhC;;;;OAIG;IACH,WAAW,IAAI,IAAI,CAAC;IAEpB;;;;OAIG;IACH,gBAAgB,IAAI,OAAO,CAAC,OAAO,CAAC,CAAC;IAErC;;;;;;;;;OASG;IACH,UAAU,IAAI,IAAI,CAAC;CACpB;AAED;;;;;;GAMG;;AACH,wBAA4D"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@scalebun/react-native",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.3",
|
|
4
4
|
"description": "Production-grade React Native SDK for ScaleBun",
|
|
5
5
|
"main": "lib/commonjs/index",
|
|
6
6
|
"module": "lib/module/index",
|
|
@@ -82,8 +82,8 @@
|
|
|
82
82
|
"typescript": "^5.4.5"
|
|
83
83
|
},
|
|
84
84
|
"peerDependencies": {
|
|
85
|
-
"react": "
|
|
86
|
-
"react-native": "
|
|
85
|
+
"react": ">=18.0.0",
|
|
86
|
+
"react-native": ">=0.74.0",
|
|
87
87
|
"react-native-view-shot": ">=3.0.0",
|
|
88
88
|
"@notifee/react-native": ">=5.0.0",
|
|
89
89
|
"expo-router": ">=1.0.0"
|
|
@@ -484,6 +484,30 @@ export class SDKBootstrapper {
|
|
|
484
484
|
});
|
|
485
485
|
detector.start();
|
|
486
486
|
logger.info('[Bootstrap] Auto screen detection started');
|
|
487
|
+
|
|
488
|
+
/**
|
|
489
|
+
* Screen detection that never resolves anything must SAY so.
|
|
490
|
+
*
|
|
491
|
+
* Wrapping React Navigation only helps a container that mounts afterwards, so an app that
|
|
492
|
+
* initialises the SDK late still ends up with no screen names — and until now that was
|
|
493
|
+
* indistinguishable from working. Everything downstream inherits the silence: no
|
|
494
|
+
* SCREEN_VIEW events, so every session's screens read "Unknown"; and no `screenName` on
|
|
495
|
+
* taps, which is the column heatmaps group by, so every route collapses into one bucket.
|
|
496
|
+
*
|
|
497
|
+
* One warning, once, naming the fix.
|
|
498
|
+
*/
|
|
499
|
+
let sawScreen = false;
|
|
500
|
+
detector.onScreenChange(() => { sawScreen = true; });
|
|
501
|
+
const warnTimer = setTimeout(() => {
|
|
502
|
+
if (sawScreen) return;
|
|
503
|
+
logger.warn(
|
|
504
|
+
'[ScaleBun] No screen has been detected. Screens will show as "Unknown" and heatmaps ' +
|
|
505
|
+
'cannot be split by route. If you use React Navigation, pass your container ref to ' +
|
|
506
|
+
'ScaleBun.setNavigationRef(ref); otherwise mark screens with <ScaleBunScreen name="…">.',
|
|
507
|
+
);
|
|
508
|
+
}, 15_000);
|
|
509
|
+
// Never hold the JS timer queue open on a background/exiting app.
|
|
510
|
+
(warnTimer as unknown as { unref?: () => void }).unref?.();
|
|
487
511
|
} catch (err: any) {
|
|
488
512
|
logger.warn('[Bootstrap] Failed to start AutoScreenDetector:', err?.message);
|
|
489
513
|
}
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
|
|
12
12
|
import type { IFeature, FeatureContext } from '../../core/contracts/IFeature';
|
|
13
13
|
import { installCrashReporting } from './index';
|
|
14
|
+
import { installRejectionHandler } from './rejectionHandler';
|
|
14
15
|
import { getCrashReporter } from './CrashReporter';
|
|
15
16
|
|
|
16
17
|
export class CrashFeature implements IFeature {
|
|
@@ -38,6 +39,24 @@ export class CrashFeature implements IFeature {
|
|
|
38
39
|
this.flushFn();
|
|
39
40
|
}
|
|
40
41
|
});
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Unhandled promise rejections — a SEPARATE intake from ErrorUtils, which never sees them.
|
|
45
|
+
*
|
|
46
|
+
* Never fatal: a rejected promise does not stop the app, so it is reported as 'handled' and
|
|
47
|
+
* does not flush. Marking these fatal would inflate the crash count with errors the user
|
|
48
|
+
* never experienced as a crash.
|
|
49
|
+
*/
|
|
50
|
+
installRejectionHandler((error: Error) => {
|
|
51
|
+
getCrashReporter().report({
|
|
52
|
+
source: 'js',
|
|
53
|
+
type: 'handled',
|
|
54
|
+
isFatal: false,
|
|
55
|
+
message: error.message,
|
|
56
|
+
stack: error.stack,
|
|
57
|
+
});
|
|
58
|
+
});
|
|
59
|
+
|
|
41
60
|
this.active = true;
|
|
42
61
|
}
|
|
43
62
|
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { noThrow } from '../../core/lifecycle/crashSafe';
|
|
2
|
+
import { logger } from '../../core/logger/internalLogger';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Capture UNHANDLED PROMISE REJECTIONS.
|
|
6
|
+
*
|
|
7
|
+
* THE GAP THIS CLOSES
|
|
8
|
+
* `ErrorUtils.setGlobalHandler` (see ./globalHandler) catches uncaught *throws* and nothing else. A
|
|
9
|
+
* rejected promise with no `.catch()` does not pass through it — React Native routes those to its own
|
|
10
|
+
* rejection tracker, which prints a LogBox warning in development and does nothing whatsoever in
|
|
11
|
+
* production. So `await doThing()` with no catch was reported by the WEB SDK (which listens for
|
|
12
|
+
* `unhandledrejection`) and silently lost on mobile. Same product, same mistake in the host's code,
|
|
13
|
+
* one platform reporting it — the cross-SDK asymmetry CLAUDE.md §2 exists to catch.
|
|
14
|
+
*
|
|
15
|
+
* WHY `require`, GUARDED, AND NOT AN IMPORT
|
|
16
|
+
* `promise/setimmediate/rejection-tracking` is React Native's own tracker: it arrives with
|
|
17
|
+
* react-native in any real app, and adding it to this package's dependencies would put a third-party
|
|
18
|
+
* runtime dep into the SDK for something the platform already ships. It is therefore resolved at
|
|
19
|
+
* runtime and treated as optional — the same shape as `globalHandler`'s `ErrorUtils` feature-detect,
|
|
20
|
+
* for the same reason. When it cannot be resolved this logs once and no-ops; nothing throws.
|
|
21
|
+
*
|
|
22
|
+
* ⚠️ `enable()` REPLACES the current handlers — it does not chain, and the module exposes no way to
|
|
23
|
+
* read the previous ones. Calling it therefore takes over from React Native's own dev-mode warning,
|
|
24
|
+
* so this re-emits that warning itself under `__DEV__`. Without it, installing the SDK would make
|
|
25
|
+
* unhandled rejections *quieter* in development than they were before, which is a poor trade for
|
|
26
|
+
* catching them in production.
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
/** The slice of `promise/setimmediate/rejection-tracking` this uses. */
|
|
30
|
+
export interface RejectionTracker {
|
|
31
|
+
enable(options: {
|
|
32
|
+
allRejections: boolean;
|
|
33
|
+
onUnhandled: (id: number, error: unknown) => void;
|
|
34
|
+
onHandled: (id: number) => void;
|
|
35
|
+
}): void;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** Default resolver — optional at runtime, never a build-time dependency. */
|
|
39
|
+
function defaultResolveTracker(): RejectionTracker | null {
|
|
40
|
+
try {
|
|
41
|
+
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
42
|
+
return require('promise/setimmediate/rejection-tracking') as RejectionTracker;
|
|
43
|
+
} catch {
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** Anything can be rejected — `throw 'a string'` is legal. Normalise without inventing a stack. */
|
|
49
|
+
function toError(value: unknown): Error {
|
|
50
|
+
if (value instanceof Error) return value;
|
|
51
|
+
if (typeof value === 'string') return new Error(value);
|
|
52
|
+
try {
|
|
53
|
+
return new Error(`Non-Error rejection: ${JSON.stringify(value)}`);
|
|
54
|
+
} catch {
|
|
55
|
+
return new Error('Non-Error rejection');
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Install the unhandled-rejection handler.
|
|
61
|
+
*
|
|
62
|
+
* @returns true when the tracker was available and handlers were installed.
|
|
63
|
+
*/
|
|
64
|
+
export function installRejectionHandler(
|
|
65
|
+
onRejection: (error: Error) => void,
|
|
66
|
+
deps?: { resolveTracker?: () => RejectionTracker | null; isDev?: boolean },
|
|
67
|
+
): boolean {
|
|
68
|
+
return (
|
|
69
|
+
noThrow(() => {
|
|
70
|
+
const tracker = (deps?.resolveTracker ?? defaultResolveTracker)();
|
|
71
|
+
if (!tracker || typeof tracker.enable !== 'function') {
|
|
72
|
+
logger.debug(
|
|
73
|
+
'[Crash] promise rejection tracking unavailable — unhandled rejections not captured',
|
|
74
|
+
);
|
|
75
|
+
return false;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const isDev = deps?.isDev ?? (typeof __DEV__ !== 'undefined' && __DEV__);
|
|
79
|
+
|
|
80
|
+
tracker.enable({
|
|
81
|
+
/**
|
|
82
|
+
* `allRejections: true` reports a rejection that is still unhandled when the tracker's
|
|
83
|
+
* grace period elapses. `onHandled` below is the other half: a promise caught LATE —
|
|
84
|
+
* a very common pattern, e.g. attaching `.catch()` after an `await` chain — fires
|
|
85
|
+
* `onUnhandled` first and `onHandled` afterwards. Reporting on the first and saying
|
|
86
|
+
* nothing on the second would file a crash for code that handled its own error.
|
|
87
|
+
*/
|
|
88
|
+
allRejections: true,
|
|
89
|
+
onUnhandled: (id: number, error: unknown) => {
|
|
90
|
+
const err = toError(error);
|
|
91
|
+
pending.set(id, err);
|
|
92
|
+
onRejection(err);
|
|
93
|
+
if (isDev) {
|
|
94
|
+
// Re-emit what RN's own tracker would have said, since enable() displaced it.
|
|
95
|
+
// eslint-disable-next-line no-console
|
|
96
|
+
console.warn(`Possible unhandled promise rejection (id: ${id}):`, err?.message ?? err);
|
|
97
|
+
}
|
|
98
|
+
},
|
|
99
|
+
onHandled: (id: number) => {
|
|
100
|
+
pending.delete(id);
|
|
101
|
+
if (isDev) {
|
|
102
|
+
// eslint-disable-next-line no-console
|
|
103
|
+
console.warn(
|
|
104
|
+
`Promise rejection handled late (id: ${id}) — it was already reported.`,
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
},
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
logger.debug('Unhandled promise rejection handler installed');
|
|
111
|
+
return true;
|
|
112
|
+
}) ?? false
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Rejections reported but not yet seen handled.
|
|
118
|
+
*
|
|
119
|
+
* Deliberately NOT used to suppress the report — by the time `onUnhandled` fires the rejection has
|
|
120
|
+
* already gone unhandled for the tracker's whole grace period, and holding it back on the chance
|
|
121
|
+
* that a late `.catch()` arrives would mean losing genuine production errors to a guess. This exists
|
|
122
|
+
* so `onHandled` has somewhere to clear from, and so a test can observe the pairing.
|
|
123
|
+
*/
|
|
124
|
+
const pending = new Map<number, Error>();
|
|
125
|
+
|
|
126
|
+
/** Test seam — number of reported-and-not-yet-handled rejections. */
|
|
127
|
+
export function __pendingRejectionCount(): number {
|
|
128
|
+
return pending.size;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/** Test seam — clear state between cases. */
|
|
132
|
+
export function __resetRejectionsForTests(): void {
|
|
133
|
+
pending.clear();
|
|
134
|
+
}
|
|
@@ -800,7 +800,9 @@ export function EngagePromptProvider({
|
|
|
800
800
|
engage.flushEvents?.();
|
|
801
801
|
logger.debug(`[Engage] test ${runId} ${eventType} persisted + ACK flushed`);
|
|
802
802
|
} else {
|
|
803
|
-
|
|
803
|
+
// Stamp the SERVED revision: without it the completion can never be invalidated,
|
|
804
|
+
// and an edited campaign would never return to a device that closed the old one.
|
|
805
|
+
storeRef.current?.markCompleted(current.id, Date.now(), current.revision);
|
|
804
806
|
engage.flushEvents?.();
|
|
805
807
|
logger.debug(`[Engage] in-app ${current.id} ${eventType} — marked done on device`);
|
|
806
808
|
}
|
|
@@ -854,7 +856,7 @@ export function EngagePromptProvider({
|
|
|
854
856
|
logger.debug(`[Engage] test ${runId} CTA — persisted locally + ACK flushed`);
|
|
855
857
|
} else {
|
|
856
858
|
// Production message: a CTA tap is as final as a close — don't re-show it.
|
|
857
|
-
storeRef.current?.markCompleted(message.id);
|
|
859
|
+
storeRef.current?.markCompleted(message.id, Date.now(), message.revision);
|
|
858
860
|
engage.flushEvents?.();
|
|
859
861
|
logger.debug(`[Engage] in-app ${message.id} CTA tapped — marked done on device`);
|
|
860
862
|
}
|