@scalebun/react-native 1.13.0 → 2.0.1

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 (115) hide show
  1. package/android/build.gradle +8 -0
  2. package/android/src/androidTest/java/com/scalebun/rn/ota/ScaleBunOtaVerifierInstrumentedTest.kt +112 -0
  3. package/android/src/main/java/com/scalebun/rn/ota/BundleDownloader.kt +10 -0
  4. package/android/src/main/java/com/scalebun/rn/ota/DeviceIntegrity.kt +75 -0
  5. package/android/src/main/java/com/scalebun/rn/ota/OtaProtocol.kt +244 -0
  6. package/android/src/main/java/com/scalebun/rn/ota/ScaleBunOtaKeyRegistry.kt +100 -0
  7. package/android/src/main/java/com/scalebun/rn/ota/ScaleBunOtaModule.kt +132 -62
  8. package/android/src/main/java/com/scalebun/rn/ota/ScaleBunOtaReleaseVerifier.kt +129 -0
  9. package/android/src/main/java/com/scalebun/rn/ota/SlotManager.kt +80 -1
  10. package/android/src/main/java/com/scalebun/rn/ota/TlsPinning.kt +118 -0
  11. package/android/src/oldarch/java/com/scalebun/rn/ota/ScaleBunOtaSpec.kt +6 -8
  12. package/dist/scalebun.full.js +964 -533
  13. package/dist/scalebun.slim.js +962 -531
  14. package/ios/Ota/OtaProtocol.swift +242 -0
  15. package/ios/Ota/OtaSlotManager.swift +26 -0
  16. package/ios/Ota/ScaleBunOtaBridge.mm +6 -3
  17. package/ios/Ota/ScaleBunOtaKeyRegistry.swift +147 -0
  18. package/ios/Ota/ScaleBunOtaModule.swift +125 -53
  19. package/ios/Ota/ScaleBunOtaReleaseVerifier.swift +115 -0
  20. package/ios/Ota/ScaleBunOtaVerifierTests.swift +92 -0
  21. package/ios/ReplaySdk.swift +65 -2
  22. package/lib/commonjs/bootstrap/SDKBootstrapper.js +30 -0
  23. package/lib/commonjs/core/config/schema.js +19 -12
  24. package/lib/commonjs/core/constants/version.js +1 -1
  25. package/lib/commonjs/features/journey/ScaleBunDebugRoot.js +293 -5
  26. package/lib/commonjs/features/journey/ScaleBunScrollView.js +28 -0
  27. package/lib/commonjs/features/journey/autoInstrumentScroll.js +161 -0
  28. package/lib/commonjs/features/journey/interactionProtocol.js +131 -10
  29. package/lib/commonjs/features/journey/scrollContext.js +146 -0
  30. package/lib/commonjs/features/journey/targetGeometry.js +164 -0
  31. package/lib/commonjs/features/journey/touchTarget.js +152 -24
  32. package/lib/commonjs/features/journey/uiState.js +81 -1
  33. package/lib/commonjs/features/navigation/AutoScreenDetector.js +74 -5
  34. package/lib/commonjs/features/network/NetworkFeature.js +39 -5
  35. package/lib/commonjs/features/network/thirdParty.js +90 -0
  36. package/lib/commonjs/features/ota/OtaOrchestrator.js +175 -80
  37. package/lib/commonjs/features/session/SessionManager.js +63 -0
  38. package/lib/commonjs/public/ScaleBunFacade.js +23 -57
  39. package/lib/module/bootstrap/SDKBootstrapper.js +30 -0
  40. package/lib/module/core/config/schema.js +19 -12
  41. package/lib/module/core/constants/version.js +1 -1
  42. package/lib/module/features/journey/ScaleBunDebugRoot.js +295 -7
  43. package/lib/module/features/journey/ScaleBunScrollView.js +28 -0
  44. package/lib/module/features/journey/autoInstrumentScroll.js +155 -0
  45. package/lib/module/features/journey/interactionProtocol.js +128 -8
  46. package/lib/module/features/journey/scrollContext.js +135 -0
  47. package/lib/module/features/journey/targetGeometry.js +155 -0
  48. package/lib/module/features/journey/touchTarget.js +151 -24
  49. package/lib/module/features/journey/uiState.js +78 -1
  50. package/lib/module/features/navigation/AutoScreenDetector.js +74 -5
  51. package/lib/module/features/network/NetworkFeature.js +38 -4
  52. package/lib/module/features/network/thirdParty.js +83 -0
  53. package/lib/module/features/ota/OtaOrchestrator.js +175 -80
  54. package/lib/module/features/session/SessionManager.js +63 -0
  55. package/lib/module/public/ScaleBunFacade.js +23 -57
  56. package/lib/typescript/core/config/schema.d.ts +2 -3
  57. package/lib/typescript/core/constants/version.d.ts +1 -1
  58. package/lib/typescript/features/journey/autoInstrumentScroll.d.ts +46 -0
  59. package/lib/typescript/features/journey/interactionProtocol.d.ts +139 -1
  60. package/lib/typescript/features/journey/scrollContext.d.ts +85 -0
  61. package/lib/typescript/features/journey/targetGeometry.d.ts +114 -0
  62. package/lib/typescript/features/journey/touchTarget.d.ts +148 -15
  63. package/lib/typescript/features/journey/uiState.d.ts +39 -0
  64. package/lib/typescript/features/navigation/AutoScreenDetector.d.ts +48 -2
  65. package/lib/typescript/features/network/index.d.ts +18 -0
  66. package/lib/typescript/features/network/thirdParty.d.ts +65 -0
  67. package/lib/typescript/features/ota/OtaOrchestrator.d.ts +12 -8
  68. package/lib/typescript/features/ota/OtaTypes.d.ts +22 -0
  69. package/lib/typescript/features/session/SessionManager.d.ts +55 -0
  70. package/lib/typescript/public/ScaleBunFacade.d.ts +5 -11
  71. package/lib/typescript/public/types.d.ts +10 -0
  72. package/lib/typescript/specs/NativeScaleBunOta.d.ts +32 -20
  73. package/package.json +3 -13
  74. package/scalebun-react-native.podspec +4 -0
  75. package/src/bootstrap/SDKBootstrapper.ts +40 -0
  76. package/src/core/config/schema.ts +21 -9
  77. package/src/core/constants/version.ts +1 -1
  78. package/src/features/journey/ScaleBunDebugRoot.tsx +288 -7
  79. package/src/features/journey/ScaleBunScrollView.tsx +29 -0
  80. package/src/features/journey/autoInstrumentScroll.ts +155 -0
  81. package/src/features/journey/interactionProtocol.ts +188 -10
  82. package/src/features/journey/scrollContext.ts +137 -0
  83. package/src/features/journey/targetGeometry.ts +176 -0
  84. package/src/features/journey/touchTarget.ts +237 -26
  85. package/src/features/journey/uiState.ts +80 -1
  86. package/src/features/navigation/AutoScreenDetector.ts +65 -5
  87. package/src/features/network/NetworkFeature.ts +41 -4
  88. package/src/features/network/index.ts +18 -0
  89. package/src/features/network/thirdParty.ts +92 -0
  90. package/src/features/ota/OtaOrchestrator.ts +190 -90
  91. package/src/features/ota/OtaTypes.ts +23 -1
  92. package/src/features/session/SessionManager.ts +113 -0
  93. package/src/public/ScaleBunFacade.ts +23 -69
  94. package/src/public/types.ts +10 -0
  95. package/src/specs/NativeScaleBunOta.ts +35 -20
  96. package/lib/commonjs/features/ota/crypto/builtinVerifier.js +0 -248
  97. package/lib/commonjs/features/ota/crypto/loadEd25519.js +0 -40
  98. package/lib/commonjs/features/ota/crypto/loadSha512.js +0 -40
  99. package/lib/commonjs/features/ota/crypto/nativeVerifier.js +0 -121
  100. package/lib/commonjs/features/ota/signature.js +0 -175
  101. package/lib/module/features/ota/crypto/builtinVerifier.js +0 -240
  102. package/lib/module/features/ota/crypto/loadEd25519.js +0 -34
  103. package/lib/module/features/ota/crypto/loadSha512.js +0 -34
  104. package/lib/module/features/ota/crypto/nativeVerifier.js +0 -113
  105. package/lib/module/features/ota/signature.js +0 -169
  106. package/lib/typescript/features/ota/crypto/builtinVerifier.d.ts +0 -53
  107. package/lib/typescript/features/ota/crypto/loadEd25519.d.ts +0 -30
  108. package/lib/typescript/features/ota/crypto/loadSha512.d.ts +0 -15
  109. package/lib/typescript/features/ota/crypto/nativeVerifier.d.ts +0 -35
  110. package/lib/typescript/features/ota/signature.d.ts +0 -81
  111. package/src/features/ota/crypto/builtinVerifier.ts +0 -257
  112. package/src/features/ota/crypto/loadEd25519.ts +0 -41
  113. package/src/features/ota/crypto/loadSha512.ts +0 -35
  114. package/src/features/ota/crypto/nativeVerifier.ts +0 -117
  115. package/src/features/ota/signature.ts +0 -206
@@ -9,33 +9,166 @@
9
9
  * WHY THE FIBER. There is no DOM. The only place a tapped view's identity exists in JS is the React
10
10
  * fiber hanging off the touch event (`_targetInst`), which carries `memoizedProps` — where
11
11
  * `accessibilityLabel` and `testID` live. This is a React internal, so every access is defensive and
12
- * the whole thing degrades to `null` rather than throwing: a analytics label is never worth a crash.
12
+ * the whole thing degrades to `null` rather than throwing: an analytics label is never worth a crash.
13
13
  *
14
- * IDENTITY PREFERENCE, strongest first:
15
- * 1. `accessibilityLabel`authored to describe the control, and already required for a11y.
16
- * 2. `testID` — authored to identify the control, stable across copy changes.
17
- * 3. a literal string child — the button's visible caption.
18
- * 4. the nearest named component — coarse, but far better than nothing.
14
+ * ─────────────────────────────────────────────────────────────────────────────────────────────
15
+ * RN-2THE IDENTITY LADDER USED TO CAPTURE USER TEXT, AND THAT IS A PRIVACY DEFECT, NOT A
16
+ * TRADE-OFF.
17
+ *
18
+ * The previous preference order was:
19
+ *
20
+ * 1. accessibilityLabel 2. testID 3. a literal string child 4. nearest component
21
+ *
22
+ * Ranks 1 and 3 are FREE TEXT authored for humans, and in a real app they routinely contain user
23
+ * data. An accessible order row is labelled "Order 1042 for mohamed@scalebun.com". A pay button
24
+ * reads "Pay EGP 1,442". A contact row's caption is a person's name. Rank 1 was preferred over
25
+ * `testID`, so the leakiest source won whenever it existed.
26
+ *
27
+ * Two separate harms, and the second is easy to miss:
28
+ *
29
+ * PRIVACY. `describeTouchTarget`'s output is persisted as the interaction's `target`. So names,
30
+ * email addresses and amounts were being written into an analytics store, under a field nobody
31
+ * would think to audit for PII. The same product already found 27 rows in `replay_events`
32
+ * carrying email addresses through a different route; this was a second one.
33
+ *
34
+ * CORRECTNESS. `target` is an AGGREGATION KEY. Keyed on text that varies per user, a single
35
+ * button produces one bucket per person — so the grouping this feature exists to provide is
36
+ * destroyed precisely in the screens that matter most. A leaky key is also a useless key.
37
+ *
38
+ * SO THE LADDER IS NOW DERIVED FROM CODE, NEVER FROM CONTENT:
39
+ *
40
+ * 1. `testID` — authored to identify, stable across copy edits, and by convention never
41
+ * user data. The strongest signal and now the first.
42
+ * 2. `path` — the component-name chain from the tapped node upward, e.g.
43
+ * `CartScreen>OrderRow>Pressable`. Structural: it changes when the code
44
+ * changes, not when the data does. This is what makes the ladder useful in
45
+ * the overwhelming majority of apps, which have no testIDs — without it,
46
+ * removing free text would have meant removing identity.
47
+ * 3. `component` — the nearest named component alone. Coarse, but a code identifier.
48
+ *
49
+ * Free text is still RESOLVABLE, because a team that has audited its own labels may legitimately
50
+ * want them, but it is opt-in per call (`includeText`) and it is NEVER what
51
+ * `describeTouchTarget` returns. Default-off matters more than the option: a default that leaks
52
+ * is a leak, whatever the documentation says.
53
+ *
54
+ * WHY THIS SHIPS WITH RN-1 (persistence) RATHER THAN AFTER IT. RN-1 makes the resolved target
55
+ * actually reach the backend. Landing that first would persist the leaky ladder and create a
56
+ * historical PII problem to clean up; landing the sanitised ladder first and persistence second
57
+ * leaves a window where the feature looks broken. They are one change.
58
+ * ─────────────────────────────────────────────────────────────────────────────────────────────
19
59
  */
20
60
  export interface TouchTarget {
21
- accessibilityLabel?: string;
61
+ /** Authored identifier. Safe to persist and to aggregate on. */
22
62
  testID?: string;
23
- text?: string;
63
+ /**
64
+ * Structural path of component names, tapped node first, e.g. `Pressable<OrderRow<CartScreen`.
65
+ * Derived from the code's shape, so it never contains user data.
66
+ */
67
+ path?: string;
68
+ /** Nearest named component. A code identifier, safe. */
24
69
  component?: string;
70
+ /**
71
+ * FREE TEXT — may contain user data. Populated ONLY when `includeText` is requested, and never
72
+ * returned by `describeTouchTarget`. Treat as unsafe to persist without an audit.
73
+ */
74
+ accessibilityLabel?: string;
75
+ /** FREE TEXT — the visible caption. Same warning as `accessibilityLabel`. */
76
+ text?: string;
77
+ /**
78
+ * RN-13 — the host node the IDENTITY was taken from, so a rect read off it describes the
79
+ * same element the tap was attributed to.
80
+ *
81
+ * This is the reason the node is captured during the identity walk rather than by a second,
82
+ * independent walk: two walks can disagree about which ancestor is "the control", and a rect
83
+ * belonging to a different element than the name is worse than no rect at all — it would make
84
+ * `heat ∈ target` answer confidently about the wrong box.
85
+ *
86
+ * Deliberately NOT serialisable and never persisted. Callers read a rectangle off it and
87
+ * discard it; nothing downstream should hold a React internal.
88
+ */
89
+ node?: unknown;
90
+ /**
91
+ * RN-3 — every ancestor host instance from the tapped node upward, INNERMOST FIRST.
92
+ *
93
+ * Collected on the identity walk rather than by a second traversal, for the same reason the
94
+ * rect's node is: two walks can disagree about the chain, and attributing a tap to the wrong
95
+ * scroller's offset places it by how far a DIFFERENT container had scrolled. Order is the
96
+ * contract — `nearestRecordedOffset` takes the first ancestor that has an offset, because that
97
+ * is the scroller the content under the finger actually moved with.
98
+ *
99
+ * Like `node`, these are React internals: read a native tag off them and discard.
100
+ */
101
+ ancestors?: unknown[];
102
+ /**
103
+ * RN-4 — the list row's own identity, for FREE.
104
+ *
105
+ * MEASURED BEFORE IT WAS DESIGNED, because the obvious fix was to make every app author a
106
+ * testID per row and that would have been a large, permanent burden on every integration. It is
107
+ * unnecessary. React Native's `VirtualizedList` builds each cell as
108
+ *
109
+ * const key = VirtualizedList._keyExtractor(item, ii, this.props); // line 797
110
+ * <CellRenderer cellKey={key} index={ii} key={key} ... /> // line 817
111
+ *
112
+ * so the app's OWN `keyExtractor` output — which every correct list already provides, because
113
+ * React requires keys — sits on the cell fiber as an ordinary prop. The walk that resolves
114
+ * identity already passes that fiber and simply never read it.
115
+ *
116
+ * `cellKey` is preferred over the fiber's `key` field: it is the same value, but reached
117
+ * through `memoizedProps` like every other prop this module reads, rather than through a React
118
+ * internal whose exposure is not part of any contract.
119
+ *
120
+ * WHY THIS MATTERS MORE THAN AN ORDINARY LABEL. A virtualised list REUSES native views: row 40
121
+ * scrolls into the view row 1 vacated. Without the cell key those two taps share a native
122
+ * identity and a screen position, so they merge — one hotspot on a row nobody in particular
123
+ * tapped. `itemIndex` is carried alongside precisely so the recycling is visible: the same index
124
+ * with a different key is a recycled slot, and that is a fact worth being able to prove rather
125
+ * than infer.
126
+ */
127
+ itemKey?: string;
128
+ /** The cell's position in the list at render time. See `itemKey` for why both are kept. */
129
+ itemIndex?: number;
130
+ }
131
+ /** Where a resolved identity came from — so a consumer can tell an authored id from a guess. */
132
+ export type TargetIdentitySource = 'testID' | 'path' | 'component' | 'none';
133
+ export interface ResolveTouchTargetOptions {
134
+ /** How far up the tree to look. */
135
+ maxDepth?: number;
136
+ /**
137
+ * Collect `accessibilityLabel` and the visible caption.
138
+ *
139
+ * OFF BY DEFAULT AND MEANT TO STAY THAT WAY. Turning it on means user-authored text may be
140
+ * carried out of the app, so it belongs to a team that has audited its own labels — not to a
141
+ * default. Even when on, the text is never used as the identity.
142
+ */
143
+ includeText?: boolean;
25
144
  }
26
145
  /**
27
146
  * Walk up from the touched fiber collecting the best identity available.
28
147
  *
29
- * Keeps walking after a hit so a wrapper's `testID` can still be picked up when the leaf only had text,
30
- * but stops as soon as BOTH an accessibilityLabel and a testID are known — nothing above can improve on
31
- * that pair.
148
+ * Keeps walking after a hit so a wrapper's `testID` can still be picked up when the leaf had none,
149
+ * and stops as soon as a `testID` is known AND the path is full — nothing above can improve on that.
32
150
  */
33
- export declare function resolveTouchTarget(event: unknown, maxDepth?: number): TouchTarget | null;
151
+ export declare function resolveTouchTarget(event: unknown, optsOrMaxDepth?: ResolveTouchTargetOptions | number): TouchTarget | null;
34
152
  /**
35
- * One short human label for the tapped thing, or undefined when nothing identified it.
153
+ * One short, STABLE, non-identifying label for the tapped thing.
36
154
  *
37
- * This is what ends up grouping the Events explorer, so it must be STABLE: a11y label and testID are
38
- * authored and rarely change, whereas visible text moves with copy edits hence the ordering.
155
+ * This is what ends up grouping the Events explorer and what gets persisted as the interaction's
156
+ * target, so it must satisfy two constraints at once: stable across copy edits, and free of user
157
+ * data. Both rule out visible text, which is why the free-text fields are not consulted here even
158
+ * when they have been resolved — see the RN-2 note in the header.
39
159
  */
40
160
  export declare function describeTouchTarget(t: TouchTarget | null): string | undefined;
161
+ /**
162
+ * The identity plus WHERE IT CAME FROM.
163
+ *
164
+ * A consumer cannot treat an authored `testID` and a structural `path` as equally trustworthy: the
165
+ * first is a promise from the app's authors that this control is this control, the second is an
166
+ * inference that breaks when the tree is refactored. Returning the source lets the accounting lane
167
+ * say WHY a tap could not be attributed, instead of reporting a missing target and leaving the
168
+ * reader to guess whether the control had no identity or the walk failed.
169
+ */
170
+ export declare function targetIdentity(t: TouchTarget | null): {
171
+ id?: string;
172
+ source: TargetIdentitySource;
173
+ };
41
174
  //# sourceMappingURL=touchTarget.d.ts.map
@@ -50,4 +50,43 @@ export declare function clearUiState(name?: string): void;
50
50
  * open"; on RN there is nothing to look at, so the honest answer is silence.
51
51
  */
52
52
  export declare function uiStateSignature(): string | undefined;
53
+ /**
54
+ * RN-11 — TELL SOMEONE THE STATE CHANGED, so a frame can exist for it.
55
+ *
56
+ * THE GAP. Declaring a state used to mutate a Map and nothing else. Nothing captured a frame, so
57
+ * the visual for a declared state existed only if a screenshot happened to be taken for some other
58
+ * reason — a navigation, a timer, or the interaction capture that fires AFTER the touch and
59
+ * photographs the outcome. A state could therefore be recorded as a fact with no picture of it
60
+ * anywhere, or with a picture of the state that replaced it.
61
+ *
62
+ * The native capture reasons are NAVIGATION, MANUAL, INTERACTION and TIMER. None of them is "the
63
+ * UI changed shape", which is exactly the moment a heatmap needs a frame.
64
+ *
65
+ * FIRING ON A REAL CHANGE ONLY, AND WHY THAT IS THE SDK'S JOB. The advice above says to call
66
+ * `setUiState` when the state changes rather than on every render — but that is advice to the
67
+ * host, not a guarantee. A component that declares its state inside render is ordinary React, and
68
+ * trusting the caller would mean a screenshot per render: a performance disaster in exactly the
69
+ * apps that instrument most carefully. So a redundant set — same dimension, same value — is now a
70
+ * no-op, and the change test lives here where it cannot be got wrong by a host.
71
+ *
72
+ * COALESCED, because a transition usually declares several dimensions in one tick ("drawer:open"
73
+ * and "overlay:visible" belong to one visual change). Notifying per call would capture the
74
+ * intermediate shapes, which are states the user never saw.
75
+ *
76
+ * The listener is deliberately NOT given the signature. A subscriber that wants it calls
77
+ * `uiStateSignature()` at the moment it acts, which is the value that will actually be attached
78
+ * to the next tap; handing over a snapshot invites acting on a state that has already moved on.
79
+ */
80
+ export type UiStateChangeListener = () => void;
81
+ /**
82
+ * Subscribe to declared-state changes. Returns an unsubscribe.
83
+ *
84
+ * Registration is idempotent per function reference, so a component that re-subscribes on every
85
+ * render does not accumulate listeners — a leak that would only show up in long sessions.
86
+ */
87
+ export declare function onUiStateChange(listener: UiStateChangeListener): () => void;
88
+ /** Drop every listener and any pending notification. For teardown between sessions. */
89
+ export declare function resetUiStateListeners(): void;
90
+ /** For assertions that a re-subscribing caller has not leaked. */
91
+ export declare function uiStateListenerCount(): number;
53
92
  //# sourceMappingURL=uiState.d.ts.map
@@ -30,6 +30,15 @@ export declare class AutoScreenDetector {
30
30
  private _manualScreen;
31
31
  private _manualScreenTs;
32
32
  private _autoScreen;
33
+ /**
34
+ * When `_autoScreen` was last written.
35
+ *
36
+ * Needed so `getCurrentScreen()` can prefer whichever source spoke MOST RECENTLY.
37
+ * Without a timestamp the only comparison available was "is the manual value still
38
+ * fresh", which is a different question and gave the wrong answer — see the note on
39
+ * `_currentScreen()`.
40
+ */
41
+ private _autoScreenTs;
33
42
  /** `name#key` of the last auto-detected screen VISIT — see _extractActiveVisit. */
34
43
  private _autoVisitSig;
35
44
  private _navigationRef;
@@ -72,8 +81,19 @@ export declare class AutoScreenDetector {
72
81
  */
73
82
  setNativeScreen(name: string): void;
74
83
  /**
75
- * Check if a manual screen is currently active.
76
- * Used by auto-detection to yield to manual overrides.
84
+ * Should a WRITER defer to the manual screen right now?
85
+ *
86
+ * THIS IS A PRECEDENCE GATE, NOT A VALIDITY TEST, and conflating the two was a real
87
+ * bug. Its job is what the cooldown constant says: for a short window after a manual
88
+ * beacon fires, an auto source must not overwrite it — the poller runs every 1500 ms
89
+ * and would otherwise clobber a fresh declaration with whatever it inferred.
90
+ *
91
+ * `getCurrentScreen()` used to reuse this as "is the manual screen still the current
92
+ * screen", which made a declared screen READABLE for only 2000 ms. Nothing called
93
+ * `setManualScreen` in production, so the effect was latent — but the moment the
94
+ * `<ScaleBunScreen>` beacon was wired to it (RN-8), every tap more than two seconds
95
+ * after a screen mounted would have lost its name, which is very nearly every tap. A
96
+ * fix that introduced that would have been worse than the gap it closed.
77
97
  */
78
98
  isManualScreenActive(): boolean;
79
99
  /**
@@ -144,6 +164,32 @@ export declare class AutoScreenDetector {
144
164
  teardown(): void;
145
165
  /** Get the current best-known screen name */
146
166
  getCurrentScreen(): string | null;
167
+ /**
168
+ * WHERE the current screen name came from, or 'none'.
169
+ *
170
+ * Recorded on every interaction (RN-8) so low screen coverage is DIAGNOSABLE rather
171
+ * than merely visible. Production shows 13.8% of Android interactions carrying a
172
+ * screen and 0.5% on iOS, and those numbers cannot today distinguish three different
173
+ * causes: the app never instrumented anything, the app instrumented manually and the
174
+ * value never reached interactions, or auto-detection ran and failed. Those have
175
+ * different owners and different fixes, and guessing between them is how a P0 stays
176
+ * open.
177
+ */
178
+ getCurrentScreenSource(): 'manual' | 'navigation' | 'none';
179
+ /**
180
+ * LAST WRITER WINS between the manual beacon and auto-detection.
181
+ *
182
+ * A screen declared by `<ScaleBunScreen name>` stays current until something else
183
+ * declares a different one — that is what mounting a screen means. It does not
184
+ * expire, which is the correction here: the previous rule returned the manual name
185
+ * only while it was under 2000 ms old and then silently fell back to the auto value,
186
+ * which in a manually-instrumented app is `null`.
187
+ *
188
+ * Comparing timestamps rather than hard-coding a priority also handles the real
189
+ * interleaving: an app can use React Navigation for most screens AND drop a manual
190
+ * beacon on one modal, and whichever spoke last is the truth.
191
+ */
192
+ private _currentScreen;
147
193
  private _emitScreenChange;
148
194
  }
149
195
  export {};
@@ -13,6 +13,24 @@ export interface NetworkMonitoringOptions {
13
13
  redactNetwork?: boolean;
14
14
  /** SDK backend base URL — used to filter out SDK's own upload calls from capture */
15
15
  sdkBaseUrl?: string;
16
+ /**
17
+ * Never capture requests whose URL matches (substring or RegExp).
18
+ *
19
+ * Parity with the web SDK, which has had this since F-NET. Mobile had no way to exclude a
20
+ * URL at all, so an app that wanted one endpoint out of its replays had no answer.
21
+ */
22
+ denyUrls?: (string | RegExp)[];
23
+ /**
24
+ * Keep known telemetry beacons: analytics collectors, ad pixels, IP-enrichment calls.
25
+ *
26
+ * Default false — they are dropped before capture. They are not the app, they cost the
27
+ * device real battery and cellular data to upload, and a beacon that fails counts against
28
+ * the SESSION HEALTH score. Matching is by hostname only, never by path, so an app route
29
+ * called /collect is never touched. See features/network/thirdParty.ts.
30
+ */
31
+ captureThirdParty?: boolean;
32
+ /** Replace the built-in beacon host list wholesale. Comma-joined hostname suffixes. */
33
+ thirdPartyHosts?: string;
16
34
  }
17
35
  export declare function installNetworkMonitoring(onEvent: (data: any) => void, options?: NetworkMonitoringOptions): void;
18
36
  //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,65 @@
1
+ /**
2
+ * THIRD-PARTY BEACON HOSTS — the one list, and the one matcher, for every layer that drops them.
3
+ *
4
+ * WHAT THIS IS FOR
5
+ * A session on an instrumented site spends most of its network tab on traffic the app did not
6
+ * meaningfully make: analytics collectors, ad pixels, IP-enrichment calls, tag managers. It is
7
+ * captured, uploaded, stored and rendered, and none of it answers a question anyone asks a session
8
+ * replay. One real sign-in screen was 27 requests, most of them beacons.
9
+ *
10
+ * It is not only clutter. Session health scores `failedRequests` and `slowRequests`, so a tracker
11
+ * killed by an ad blocker (a transport error) or one that is simply slow moves a number that is
12
+ * supposed to describe the APP. That is the part that makes this a correctness fix rather than a
13
+ * tidy-up.
14
+ *
15
+ * WHY HOSTS ONLY, NEVER PATHS
16
+ * The obvious rules from looking at a network tab are `/collect`, `/p`, `/api/v2/pixel`. Every one
17
+ * of them is a path that a customer's own API is allowed to have, and dropping a real API call is
18
+ * far worse than keeping a beacon: the noise is visible and annoying, the missing request is
19
+ * invisible and misleading. So matching is on the HOSTNAME, with a dot boundary, and a request to
20
+ * `/collect` on the app's own origin is kept — correctly.
21
+ *
22
+ * WHY IT IS A SUFFIX LIST AND NOT A REGEX
23
+ * Two reasons, both practical. It is applied inside an SDK whose eager bundle is measured in bytes,
24
+ * and a comma-joined string of suffixes compresses to almost nothing next to an equivalent pattern
25
+ * source. And it has to be COPIED, byte for byte, into the React Native SDK and into the backend's
26
+ * ingest path — a list with no syntax is a list that cannot drift in meaning between three
27
+ * languages' worth of tooling. `third-party:parity` hashes all three copies.
28
+ *
29
+ * ZERO IMPORTS, DELIBERATELY. That is what makes the byte-identical copy possible. If an edit here
30
+ * ever needs an import, the parity gate is the thing that will say so — keep the module pure rather
31
+ * than weakening the gate.
32
+ *
33
+ * WHAT IS DELIBERATELY NOT ON THE LIST
34
+ * The rule is: a host whose ONLY purpose is to receive telemetry. Everything else stays, because a
35
+ * dropped request is invisible and a kept one is merely noisy.
36
+ *
37
+ * - Feature-flag and experiment services (LaunchDarkly, Optimizely, Split). Their RESPONSES
38
+ * change what the app does, so a failed flag fetch is an application incident and the single
39
+ * most useful request in the session.
40
+ * - Chat and support widgets (Intercom, Zendesk, Drift). A broken widget is a broken feature the
41
+ * user can see.
42
+ * - `facebook.com` as a whole. `graph.facebook.com` is an API real apps call; only the pixel
43
+ * delivery hosts are listed.
44
+ * - CDNs of any kind, even ones that mostly serve tags: they also serve application assets, and
45
+ * dropping them would hide a real asset failure.
46
+ *
47
+ * When in doubt, leave it out. `denyUrls` stays the per-app escape hatch in both SDKs, and it can
48
+ * do what this list will not.
49
+ */
50
+ /**
51
+ * Known telemetry, advertising and enrichment endpoints, as hostname suffixes.
52
+ *
53
+ * One comma-joined string rather than an array: identical meaning, materially smaller once gzipped,
54
+ * and it keeps the copy in three repos a single line to compare by eye.
55
+ */
56
+ export declare const THIRD_PARTY_BEACON_HOSTS: string;
57
+ /**
58
+ * True when `url` is addressed to a known beacon host.
59
+ *
60
+ * Deliberately total: an unparseable or relative URL is NOT third party. A relative URL is
61
+ * same-origin by definition — the app's own call — and guessing on garbage input would drop real
62
+ * requests for no benefit.
63
+ */
64
+ export declare function isThirdPartyBeacon(url: string, hosts?: string): boolean;
65
+ //# sourceMappingURL=thirdParty.d.ts.map
@@ -1,5 +1,4 @@
1
1
  import type { OtaCheckResponse, OtaBundlePayload } from './OtaTypes';
2
- import { type SignatureConfig } from './signature';
3
2
  export interface SyncResult {
4
3
  status: 'UP_TO_DATE' | 'UPDATE_INSTALLED' | 'ROLLED_BACK' | 'ERROR';
5
4
  bundle?: OtaBundlePayload;
@@ -35,9 +34,10 @@ export declare class OtaOrchestrator {
35
34
  */
36
35
  getCurrentBundle(): OtaBundlePayload | null;
37
36
  private isRestartRequiredState;
37
+ /** Root/jailbreak indicators observed at init; empty when none found. */
38
+ private integrityIndicators;
38
39
  private healthyTimer;
39
40
  private bootGuardConfig;
40
- private signatureConfig;
41
41
  private environment;
42
42
  /** One-time boot checks have run in this process (init is idempotent). */
43
43
  private initialized;
@@ -45,13 +45,12 @@ export declare class OtaOrchestrator {
45
45
  /**
46
46
  * Initialize the orchestrator. Checks for boot-guard recovery state.
47
47
  *
48
- * `signature` is optional: an app that has not adopted signing keeps working
49
- * on SHA-256 integrity alone. Once a public key IS configured, a bundle that
50
- * cannot be verified is refused rather than installed (OTA-03).
48
+ * Signature enforcement is no longer a JS concern: signed releases are
49
+ * verified and staged in NATIVE code against RSA keys embedded in the app
50
+ * package (SB-OTA-RSA-SHA256-V1), and unsigned releases keep working on
51
+ * SHA-256 integrity alone. There is nothing for the host to configure here.
51
52
  */
52
- init(config?: BootGuardConfig & {
53
- signature?: SignatureConfig;
54
- }): void;
53
+ init(config?: BootGuardConfig): void;
55
54
  /**
56
55
  * Read the active slot back into `currentBundle` so the next check reports
57
56
  * what this device is genuinely running.
@@ -100,6 +99,11 @@ export declare class OtaOrchestrator {
100
99
  */
101
100
  private checkBootGuardRecovery;
102
101
  isEnabled(): boolean;
102
+ /**
103
+ * Root/jailbreak indicators detected on this device, as stable reason strings.
104
+ * Empty means nothing was detected — not proof the device is clean.
105
+ */
106
+ deviceIntegrityIndicators(): string[];
103
107
  isRestartRequired(): boolean;
104
108
  /**
105
109
  * Perform an OTA check against the backend endpoint.
@@ -51,6 +51,11 @@ export interface OtaCheckRequest {
51
51
  installationId: string;
52
52
  currentBundleId?: string;
53
53
  currentBundleHash?: string;
54
+ /**
55
+ * Trusted key IDs embedded in this app binary, for SDK trust coverage
56
+ * (CICDPAGE §12). Telemetry only — never a source of server-side trust.
57
+ */
58
+ trustedKeyIds?: string[];
54
59
  /** Channel to pull from. Omitted means `default`. */
55
60
  channelName?: string;
56
61
  /** ISO 3166-1 alpha-2. The server fills this from the edge when absent. */
@@ -93,6 +98,23 @@ export interface OtaBundlePayload {
93
98
  size: number;
94
99
  sha256: string;
95
100
  signature?: string;
101
+ /**
102
+ * SB-OTA-RSA-SHA256-V1 release envelope. Present on signed releases; carries
103
+ * the protocol id, the id of the trusted RSA key the release was signed with,
104
+ * the canonical signed payload, and its signature. When present, the SDK hands
105
+ * the whole envelope to the native module (`verifyAndStageRelease`), which
106
+ * verifies the signature against the RSA public keys EMBEDDED in the app
107
+ * package and then downloads + integrity-checks + stages the artifact — JS
108
+ * never stages bytes it has not proven authentic, and never pins or verifies a
109
+ * key itself. Absent on legacy/unsigned releases, which fall back to
110
+ * `stageBundle` on SHA-256 integrity alone.
111
+ */
112
+ envelope?: {
113
+ protocol: string;
114
+ keyId: string;
115
+ signedPayload: string;
116
+ signature: string;
117
+ };
96
118
  isMandatory: boolean;
97
119
  /**
98
120
  * How to activate the bundle after install:
@@ -268,6 +268,61 @@ export declare class SessionManager {
268
268
  targetId?: string;
269
269
  screenName?: string;
270
270
  emitAutomaticAnalytics?: boolean;
271
+ /**
272
+ * RN-6 — the native↔JS join's own verdict, and it MUST be declared here.
273
+ *
274
+ * This signature ENUMERATES its fields rather than spreading, and the only caller
275
+ * (ScaleBunDebugRoot's native listener) invokes it through `(sm as any)`. So a field
276
+ * added at the call site and not added here is dropped in silence, with no type
277
+ * error anywhere — which is precisely how these three nearly shipped as a no-op.
278
+ *
279
+ * 'exact' = one JS start was clearly closest. 'ambiguous' = two were within
280
+ * CORRELATION_MARGIN_MS of each other, so target/ui/screenName were deliberately
281
+ * withheld rather than guessed. 'none' = no start within tolerance, meaning the JS
282
+ * handler never fired. The last two are kept distinct because they have different
283
+ * fixes and collapsing them would hide which is happening in production.
284
+ */
285
+ correlation?: 'exact' | 'ambiguous' | 'none';
286
+ correlationDeltaMs?: number | null;
287
+ correlationRunnerUpDeltaMs?: number | null;
288
+ /**
289
+ * RN-1/RN-2 — which kind of identity `targetId` is. Declared here for the same reason as
290
+ * the correlation fields above: this signature enumerates, and its only caller goes
291
+ * through `(sm as any)`, so an undeclared field is dropped in silence.
292
+ */
293
+ targetSource?: 'testID' | 'path' | 'component' | 'none';
294
+ /** RN-8: where the screen name came from. Declared here because this signature enumerates. */
295
+ screenSource?: 'manual' | 'navigation' | 'none';
296
+ /**
297
+ * RN-13: the target control's rectangle in capture-base fractions. Declared here because
298
+ * this signature ENUMERATES — the sixth field to need it, after three correlation fields,
299
+ * targetSource and screenSource.
300
+ */
301
+ targetRect?: {
302
+ x: number;
303
+ y: number;
304
+ width: number;
305
+ height: number;
306
+ };
307
+ /**
308
+ * RN-3: content offset of the nearest ancestor scroller. Declared here because this
309
+ * signature ENUMERATES — the seventh field to need it.
310
+ */
311
+ scrollX?: number;
312
+ scrollY?: number;
313
+ scrollSource?: 'wrapper' | 'none';
314
+ /**
315
+ * RN-4: list row identity. Declared here because this signature ENUMERATES — the eighth
316
+ * and ninth fields to need it.
317
+ */
318
+ itemKey?: string;
319
+ itemIndex?: number;
320
+ /**
321
+ * RN-15: tap position within the target control. Declared here because this signature
322
+ * ENUMERATES — the tenth and eleventh fields to need it.
323
+ */
324
+ localU?: number;
325
+ localV?: number;
271
326
  }): void;
272
327
  /**
273
328
  * Manual frame capture — triggered by Desktop "Capture Step" button.
@@ -69,17 +69,11 @@ declare class ScaleBunFacade {
69
69
  /**
70
70
  * Boot the OTA orchestrator when the init config asks for it.
71
71
  *
72
- * `publicSigningKey` / `publicSigningKeys` are honoured here so signature
73
- * enforcement is reachable from configuration alone. The list form exists
74
- * for key ROTATION: a build pinning [old, new] keeps verifying while the
75
- * server moves to the new key, so replacing a key never needs an emergency
76
- * store release. Both fields merge (deduplicated) into one pinned set.
77
- *
78
- * Verification comes from `ota.verifySignature` when supplied, else the
79
- * built-in @noble-based verifier (optional peers). Pinning keys with
80
- * NEITHER available is fail-CLOSED by design (signature.ts) — an update
81
- * that cannot be verified is not installed. We say that out loud rather
82
- * than letting the app discover it as a silent no-update condition.
72
+ * Signature enforcement is no longer configured from JS. Signed releases are
73
+ * verified and staged in NATIVE code against RSA keys embedded in the app
74
+ * package (SB-OTA-RSA-SHA256-V1); unsigned releases keep working on SHA-256
75
+ * integrity. There are no signing-key or `verifySignature` options to pass
76
+ * through the host embeds its trusted keys natively.
83
77
  */
84
78
  private _maybeStartOta;
85
79
  /** Guards against overlapping config-driven OTA checks. */
@@ -287,6 +287,16 @@ export interface SimplifiedInitConfig {
287
287
  appId?: string;
288
288
  /** SDK client key (SaaS mode). Also used to authenticate the envelope lane. */
289
289
  clientKey?: string;
290
+ /**
291
+ * Patch React Native's ScrollView export so every scroll view reports its scroll offset,
292
+ * giving taps their content depth without the app swapping to ScaleBunScrollView.
293
+ *
294
+ * OFF BY DEFAULT because it changes what the host renders, not merely what the SDK records:
295
+ * every scroll view in the app — including ones inside libraries you did not write — routes
296
+ * through an SDK wrapper. Turn it on deliberately. With it off, taps on a plain ScrollView
297
+ * report no content depth and say so, rather than reporting a fabricated offset of 0.
298
+ */
299
+ autoInstrumentScrollViews?: boolean;
290
300
  /** Platform OVERRIDE — auto-detected from Platform.OS when omitted (integrations should omit it). */
291
301
  platform?: 'ios' | 'android' | 'web' | 'react_native';
292
302
  /**