@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.
Files changed (111) hide show
  1. package/android/src/main/java/com/scalebun/rn/ota/BundleDownloader.kt +170 -0
  2. package/android/src/main/java/com/scalebun/rn/ota/ScaleBunOtaModule.kt +160 -12
  3. package/android/src/main/java/com/scalebun/rn/ota/SlotManager.kt +32 -6
  4. package/android/src/oldarch/java/com/scalebun/rn/ota/ScaleBunOtaSpec.kt +7 -1
  5. package/ios/Ota/BundleDownloader.swift +182 -0
  6. package/ios/Ota/OtaSlotManager.swift +35 -9
  7. package/ios/Ota/ScaleBunOtaBridge.mm +9 -0
  8. package/ios/Ota/ScaleBunOtaEventsModule.swift +61 -0
  9. package/ios/Ota/ScaleBunOtaModule.swift +58 -5
  10. package/lib/commonjs/bootstrap/SDKBootstrapper.js +22 -0
  11. package/lib/commonjs/bootstrap/SDKBootstrapper.js.map +1 -1
  12. package/lib/commonjs/features/crash/CrashFeature.js +18 -0
  13. package/lib/commonjs/features/crash/CrashFeature.js.map +1 -1
  14. package/lib/commonjs/features/crash/rejectionHandler.js +123 -0
  15. package/lib/commonjs/features/crash/rejectionHandler.js.map +1 -0
  16. package/lib/commonjs/features/engage/EngagePromptProvider.js +4 -2
  17. package/lib/commonjs/features/engage/EngagePromptProvider.js.map +1 -1
  18. package/lib/commonjs/features/engage/engageThrottle.js +41 -10
  19. package/lib/commonjs/features/engage/engageThrottle.js.map +1 -1
  20. package/lib/commonjs/features/engage/engageTypes.js.map +1 -1
  21. package/lib/commonjs/features/navigation/AutoScreenDetector.js +53 -10
  22. package/lib/commonjs/features/navigation/AutoScreenDetector.js.map +1 -1
  23. package/lib/commonjs/features/ota/OtaOrchestrator.js +515 -20
  24. package/lib/commonjs/features/ota/OtaOrchestrator.js.map +1 -1
  25. package/lib/commonjs/features/ota/environment.js +172 -0
  26. package/lib/commonjs/features/ota/environment.js.map +1 -0
  27. package/lib/commonjs/features/ota/retry.js +96 -0
  28. package/lib/commonjs/features/ota/retry.js.map +1 -0
  29. package/lib/commonjs/features/ota/signature.js +115 -0
  30. package/lib/commonjs/features/ota/signature.js.map +1 -0
  31. package/lib/commonjs/features/session/BackendSessionAdapter.js +231 -26
  32. package/lib/commonjs/features/session/BackendSessionAdapter.js.map +1 -1
  33. package/lib/commonjs/index.js +7 -0
  34. package/lib/commonjs/index.js.map +1 -1
  35. package/lib/commonjs/public/ScaleBunErrorBoundary.js +79 -0
  36. package/lib/commonjs/public/ScaleBunErrorBoundary.js.map +1 -0
  37. package/lib/commonjs/specs/NativeScaleBunOta.js.map +1 -1
  38. package/lib/module/bootstrap/SDKBootstrapper.js +22 -0
  39. package/lib/module/bootstrap/SDKBootstrapper.js.map +1 -1
  40. package/lib/module/features/crash/CrashFeature.js +18 -0
  41. package/lib/module/features/crash/CrashFeature.js.map +1 -1
  42. package/lib/module/features/crash/rejectionHandler.js +116 -0
  43. package/lib/module/features/crash/rejectionHandler.js.map +1 -0
  44. package/lib/module/features/engage/EngagePromptProvider.js +4 -2
  45. package/lib/module/features/engage/EngagePromptProvider.js.map +1 -1
  46. package/lib/module/features/engage/engageThrottle.js +41 -10
  47. package/lib/module/features/engage/engageThrottle.js.map +1 -1
  48. package/lib/module/features/engage/engageTypes.js.map +1 -1
  49. package/lib/module/features/navigation/AutoScreenDetector.js +53 -10
  50. package/lib/module/features/navigation/AutoScreenDetector.js.map +1 -1
  51. package/lib/module/features/ota/OtaOrchestrator.js +516 -21
  52. package/lib/module/features/ota/OtaOrchestrator.js.map +1 -1
  53. package/lib/module/features/ota/environment.js +165 -0
  54. package/lib/module/features/ota/environment.js.map +1 -0
  55. package/lib/module/features/ota/retry.js +87 -0
  56. package/lib/module/features/ota/retry.js.map +1 -0
  57. package/lib/module/features/ota/signature.js +109 -0
  58. package/lib/module/features/ota/signature.js.map +1 -0
  59. package/lib/module/features/session/BackendSessionAdapter.js +230 -26
  60. package/lib/module/features/session/BackendSessionAdapter.js.map +1 -1
  61. package/lib/module/index.js +6 -0
  62. package/lib/module/index.js.map +1 -1
  63. package/lib/module/public/ScaleBunErrorBoundary.js +70 -0
  64. package/lib/module/public/ScaleBunErrorBoundary.js.map +1 -0
  65. package/lib/module/specs/NativeScaleBunOta.js.map +1 -1
  66. package/lib/typescript/bootstrap/SDKBootstrapper.d.ts.map +1 -1
  67. package/lib/typescript/features/crash/CrashFeature.d.ts.map +1 -1
  68. package/lib/typescript/features/crash/rejectionHandler.d.ts +46 -0
  69. package/lib/typescript/features/crash/rejectionHandler.d.ts.map +1 -0
  70. package/lib/typescript/features/engage/EngagePromptProvider.d.ts.map +1 -1
  71. package/lib/typescript/features/engage/engageThrottle.d.ts +24 -4
  72. package/lib/typescript/features/engage/engageThrottle.d.ts.map +1 -1
  73. package/lib/typescript/features/engage/engageTypes.d.ts +6 -0
  74. package/lib/typescript/features/engage/engageTypes.d.ts.map +1 -1
  75. package/lib/typescript/features/navigation/AutoScreenDetector.d.ts +9 -0
  76. package/lib/typescript/features/navigation/AutoScreenDetector.d.ts.map +1 -1
  77. package/lib/typescript/features/ota/OtaOrchestrator.d.ts +67 -1
  78. package/lib/typescript/features/ota/OtaOrchestrator.d.ts.map +1 -1
  79. package/lib/typescript/features/ota/OtaTypes.d.ts +42 -0
  80. package/lib/typescript/features/ota/OtaTypes.d.ts.map +1 -1
  81. package/lib/typescript/features/ota/environment.d.ts +99 -0
  82. package/lib/typescript/features/ota/environment.d.ts.map +1 -0
  83. package/lib/typescript/features/ota/retry.d.ts +53 -0
  84. package/lib/typescript/features/ota/retry.d.ts.map +1 -0
  85. package/lib/typescript/features/ota/signature.d.ts +66 -0
  86. package/lib/typescript/features/ota/signature.d.ts.map +1 -0
  87. package/lib/typescript/features/session/BackendSessionAdapter.d.ts +93 -2
  88. package/lib/typescript/features/session/BackendSessionAdapter.d.ts.map +1 -1
  89. package/lib/typescript/index.d.ts +6 -0
  90. package/lib/typescript/index.d.ts.map +1 -1
  91. package/lib/typescript/public/ScaleBunErrorBoundary.d.ts +44 -0
  92. package/lib/typescript/public/ScaleBunErrorBoundary.d.ts.map +1 -0
  93. package/lib/typescript/specs/NativeScaleBunOta.d.ts +11 -0
  94. package/lib/typescript/specs/NativeScaleBunOta.d.ts.map +1 -1
  95. package/package.json +3 -3
  96. package/src/bootstrap/SDKBootstrapper.ts +24 -0
  97. package/src/features/crash/CrashFeature.ts +19 -0
  98. package/src/features/crash/rejectionHandler.ts +134 -0
  99. package/src/features/engage/EngagePromptProvider.tsx +4 -2
  100. package/src/features/engage/engageThrottle.ts +44 -6
  101. package/src/features/engage/engageTypes.ts +6 -0
  102. package/src/features/navigation/AutoScreenDetector.ts +59 -4
  103. package/src/features/ota/OtaOrchestrator.ts +594 -23
  104. package/src/features/ota/OtaTypes.ts +56 -0
  105. package/src/features/ota/environment.ts +199 -0
  106. package/src/features/ota/retry.ts +123 -0
  107. package/src/features/ota/signature.ts +123 -0
  108. package/src/features/session/BackendSessionAdapter.ts +233 -25
  109. package/src/index.ts +6 -0
  110. package/src/public/ScaleBunErrorBoundary.tsx +79 -0
  111. 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
- /** Cap on frames persisted per snapshot — bounds KV footprint (keeps the most recent). */
93
- private static readonly MAX_PERSIST_FRAMES;
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":"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"}
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":"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"}
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;CACtC;AAED;;;;;;GAMG;;AACH,wBAA4D"}
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.1",
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
- storeRef.current?.markCompleted(current.id);
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
  }