@scalebun/react-native 2.0.0 → 2.0.2
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 +10 -0
- package/android/src/main/java/com/scalebun/rn/ota/DeviceIntegrity.kt +75 -0
- package/android/src/main/java/com/scalebun/rn/ota/ScaleBunOtaModule.kt +18 -3
- package/android/src/main/java/com/scalebun/rn/ota/SlotManager.kt +62 -1
- package/android/src/main/java/com/scalebun/rn/ota/TlsPinning.kt +118 -0
- package/android/src/oldarch/java/com/scalebun/rn/ota/ScaleBunOtaSpec.kt +2 -0
- package/dist/scalebun.full.js +840 -114
- package/dist/scalebun.slim.js +838 -112
- package/ios/ReplaySdk.swift +65 -2
- package/lib/commonjs/bootstrap/SDKBootstrapper.js +30 -0
- package/lib/commonjs/core/config/schema.js +16 -0
- package/lib/commonjs/core/constants/version.js +1 -1
- package/lib/commonjs/features/journey/ScaleBunDebugRoot.js +293 -5
- package/lib/commonjs/features/journey/ScaleBunScrollView.js +28 -0
- package/lib/commonjs/features/journey/autoInstrumentScroll.js +161 -0
- package/lib/commonjs/features/journey/interactionProtocol.js +131 -10
- package/lib/commonjs/features/journey/scrollContext.js +146 -0
- package/lib/commonjs/features/journey/targetGeometry.js +164 -0
- package/lib/commonjs/features/journey/touchTarget.js +152 -24
- package/lib/commonjs/features/journey/uiState.js +81 -1
- package/lib/commonjs/features/navigation/AutoScreenDetector.js +74 -5
- package/lib/commonjs/features/ota/OtaOrchestrator.js +69 -22
- package/lib/commonjs/features/session/SessionManager.js +63 -0
- package/lib/commonjs/public/ScaleBunFacade.js +18 -16
- package/lib/module/bootstrap/SDKBootstrapper.js +30 -0
- package/lib/module/core/config/schema.js +16 -0
- package/lib/module/core/constants/version.js +1 -1
- package/lib/module/features/journey/ScaleBunDebugRoot.js +295 -7
- package/lib/module/features/journey/ScaleBunScrollView.js +28 -0
- package/lib/module/features/journey/autoInstrumentScroll.js +155 -0
- package/lib/module/features/journey/interactionProtocol.js +128 -8
- package/lib/module/features/journey/scrollContext.js +135 -0
- package/lib/module/features/journey/targetGeometry.js +155 -0
- package/lib/module/features/journey/touchTarget.js +151 -24
- package/lib/module/features/journey/uiState.js +78 -1
- package/lib/module/features/navigation/AutoScreenDetector.js +74 -5
- package/lib/module/features/ota/OtaOrchestrator.js +69 -22
- package/lib/module/features/session/SessionManager.js +63 -0
- package/lib/module/public/ScaleBunFacade.js +18 -16
- package/lib/typescript/core/config/schema.d.ts +2 -0
- package/lib/typescript/core/constants/version.d.ts +1 -1
- package/lib/typescript/features/journey/autoInstrumentScroll.d.ts +46 -0
- package/lib/typescript/features/journey/interactionProtocol.d.ts +139 -1
- package/lib/typescript/features/journey/scrollContext.d.ts +85 -0
- package/lib/typescript/features/journey/targetGeometry.d.ts +114 -0
- package/lib/typescript/features/journey/touchTarget.d.ts +148 -15
- package/lib/typescript/features/journey/uiState.d.ts +39 -0
- package/lib/typescript/features/navigation/AutoScreenDetector.d.ts +48 -2
- package/lib/typescript/features/ota/OtaOrchestrator.d.ts +7 -0
- package/lib/typescript/features/session/SessionManager.d.ts +55 -0
- package/lib/typescript/public/types.d.ts +10 -0
- package/lib/typescript/specs/NativeScaleBunOta.d.ts +2 -0
- package/package.json +2 -2
- package/src/bootstrap/SDKBootstrapper.ts +40 -0
- package/src/core/config/schema.ts +18 -0
- package/src/core/constants/version.ts +1 -1
- package/src/features/journey/ScaleBunDebugRoot.tsx +288 -7
- package/src/features/journey/ScaleBunScrollView.tsx +29 -0
- package/src/features/journey/autoInstrumentScroll.ts +155 -0
- package/src/features/journey/interactionProtocol.ts +188 -10
- package/src/features/journey/scrollContext.ts +137 -0
- package/src/features/journey/targetGeometry.ts +176 -0
- package/src/features/journey/touchTarget.ts +237 -26
- package/src/features/journey/uiState.ts +80 -1
- package/src/features/navigation/AutoScreenDetector.ts +65 -5
- package/src/features/ota/OtaOrchestrator.ts +76 -22
- package/src/features/session/SessionManager.ts +113 -0
- package/src/public/ScaleBunFacade.ts +18 -16
- package/src/public/types.ts +10 -0
- package/src/specs/NativeScaleBunOta.ts +3 -0
|
@@ -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
|
-
*
|
|
76
|
-
*
|
|
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 {};
|
|
@@ -34,6 +34,8 @@ export declare class OtaOrchestrator {
|
|
|
34
34
|
*/
|
|
35
35
|
getCurrentBundle(): OtaBundlePayload | null;
|
|
36
36
|
private isRestartRequiredState;
|
|
37
|
+
/** Root/jailbreak indicators observed at init; empty when none found. */
|
|
38
|
+
private integrityIndicators;
|
|
37
39
|
private healthyTimer;
|
|
38
40
|
private bootGuardConfig;
|
|
39
41
|
private environment;
|
|
@@ -97,6 +99,11 @@ export declare class OtaOrchestrator {
|
|
|
97
99
|
*/
|
|
98
100
|
private checkBootGuardRecovery;
|
|
99
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[];
|
|
100
107
|
isRestartRequired(): boolean;
|
|
101
108
|
/**
|
|
102
109
|
* Perform an OTA check against the backend endpoint.
|
|
@@ -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.
|
|
@@ -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
|
/**
|
|
@@ -30,6 +30,8 @@ export interface Spec extends TurboModule {
|
|
|
30
30
|
* Empty array when no keys are embedded.
|
|
31
31
|
*/
|
|
32
32
|
getTrustedKeyIds(): string;
|
|
33
|
+
/** JSON array of root/jailbreak indicator strings; [] when none found. */
|
|
34
|
+
getDeviceIntegrity(): string;
|
|
33
35
|
/**
|
|
34
36
|
* Returns JSON with current slot state:
|
|
35
37
|
* { current: { bundleId, version, sha256, installedAt } | null,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@scalebun/react-native",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.2",
|
|
4
4
|
"description": "React Native SDK for ScaleBun",
|
|
5
5
|
"main": "lib/commonjs/index",
|
|
6
6
|
"module": "lib/module/index",
|
|
@@ -111,7 +111,7 @@
|
|
|
111
111
|
"@babel/runtime": "^7.25.0",
|
|
112
112
|
"@jridgewell/sourcemap-codec": "1.5.5",
|
|
113
113
|
"@jridgewell/trace-mapping": "0.3.31",
|
|
114
|
-
"@scalebun/cli": "^2.0.
|
|
114
|
+
"@scalebun/cli": "^2.0.2"
|
|
115
115
|
},
|
|
116
116
|
"codegenConfig": {
|
|
117
117
|
"name": "ScaleBunSpec",
|
|
@@ -91,6 +91,24 @@ export class SDKBootstrapper {
|
|
|
91
91
|
// apiBaseUrl is LOCKED: whatever an integrator passed (or the schema default) is
|
|
92
92
|
// discarded here so the SDK can never be repointed away from the ScaleBun cloud.
|
|
93
93
|
// Change DEFAULT_API_BASE_URL at build time to target a different backend.
|
|
94
|
+
// Say so when we discard it. The value was accepted by the schema, silently
|
|
95
|
+
// overwritten here, and the SDK then talked to a different server than the
|
|
96
|
+
// integrator configured — with no log, no warning, and no error. Someone
|
|
97
|
+
// pointing the SDK at a staging or local backend would watch requests arrive
|
|
98
|
+
// at production and have nothing to go on.
|
|
99
|
+
const requestedApiBaseUrl = (rawConfig as any)?.apiBaseUrl;
|
|
100
|
+
if (
|
|
101
|
+
typeof requestedApiBaseUrl === 'string' &&
|
|
102
|
+
requestedApiBaseUrl &&
|
|
103
|
+
requestedApiBaseUrl !== DEFAULT_API_BASE_URL
|
|
104
|
+
) {
|
|
105
|
+
logger.warn(
|
|
106
|
+
`[ScaleBun] Ignoring apiBaseUrl '${requestedApiBaseUrl}' — the endpoint is ` +
|
|
107
|
+
`locked at build time and this build targets ${DEFAULT_API_BASE_URL}. To ` +
|
|
108
|
+
`target another backend, change DEFAULT_API_BASE_URL in the SDK source and ` +
|
|
109
|
+
`rebuild.`,
|
|
110
|
+
);
|
|
111
|
+
}
|
|
94
112
|
this.config.apiBaseUrl = DEFAULT_API_BASE_URL;
|
|
95
113
|
// Explicit logLevel wins; otherwise debug:true implies verbose 'debug'.
|
|
96
114
|
if (this.config.logLevel) {
|
|
@@ -340,6 +358,28 @@ export class SDKBootstrapper {
|
|
|
340
358
|
await this.featureRegistry.initializeAll(featureContext);
|
|
341
359
|
|
|
342
360
|
// ── Session Replay Initialization (Core) ───────────────
|
|
361
|
+
/**
|
|
362
|
+
* RN-3 zero-config scroll instrumentation, opt-in.
|
|
363
|
+
*
|
|
364
|
+
* Placed before replay init because it patches a module export and must be in place before the
|
|
365
|
+
* host renders its first scroll view. Reports rather than assumes: the equivalent React
|
|
366
|
+
* Navigation hook once claimed success it had not verified and left a whole pipeline silently
|
|
367
|
+
* empty, so a failure here is said out loud with a reason an integrator can act on.
|
|
368
|
+
*/
|
|
369
|
+
if (this.config.autoInstrumentScrollViews === true) {
|
|
370
|
+
try {
|
|
371
|
+
const { autoInstrumentScrollViews } = require('../features/journey/autoInstrumentScroll');
|
|
372
|
+
const outcome = autoInstrumentScrollViews();
|
|
373
|
+
if (!outcome.installed) {
|
|
374
|
+
logger.warn(
|
|
375
|
+
'[Bootstrap] autoInstrumentScrollViews was requested but could not be ' +
|
|
376
|
+
'installed: ' + outcome.reason + ' — taps will carry no content depth.',
|
|
377
|
+
);
|
|
378
|
+
} else {
|
|
379
|
+
__DEV__ && logger.debug('[Bootstrap] ScrollView auto-instrumentation installed');
|
|
380
|
+
}
|
|
381
|
+
} catch { /* no-throw */ }
|
|
382
|
+
}
|
|
343
383
|
if (features.replay !== false) {
|
|
344
384
|
try {
|
|
345
385
|
__DEV__ && logger.debug('[Bootstrap] Initializing unified SessionManager');
|
|
@@ -188,6 +188,22 @@ const SHAPE: Record<string, Field> = {
|
|
|
188
188
|
},
|
|
189
189
|
},
|
|
190
190
|
captureInteractionHeatmap: bool(true),
|
|
191
|
+
/**
|
|
192
|
+
* RN-3 zero-config: patch React Native's ScrollView export so EVERY scroll view reports its
|
|
193
|
+
* offset, not just ones wrapped in ScaleBunScrollView.
|
|
194
|
+
*
|
|
195
|
+
* DEFAULT FALSE, unlike captureInteractionHeatmap above, and the difference is deliberate.
|
|
196
|
+
* That flag changes what the SDK records about the host; this one changes what the HOST
|
|
197
|
+
* RENDERS — every scroll view in the app, including ones inside third-party libraries the app
|
|
198
|
+
* did not write, starts routing through an SDK component. The failure mode is not a wrong
|
|
199
|
+
* number in a dashboard, it is the host's UI behaving differently because an analytics SDK
|
|
200
|
+
* replaced a primitive. That is a decision an integrator makes deliberately, not one they
|
|
201
|
+
* discover afterwards.
|
|
202
|
+
*
|
|
203
|
+
* With it off, a plain <ScrollView> yields no content depth and the tap says so
|
|
204
|
+
* (scroll_source: 'none') rather than defaulting its offset to 0.
|
|
205
|
+
*/
|
|
206
|
+
autoInstrumentScrollViews: bool(false),
|
|
191
207
|
// Opt-in: intercept console.* and ship the lines to the Diagnose → Logs lane in SaaS
|
|
192
208
|
// mode. OFF by default — console output can carry PII/secrets and adds ingest volume,
|
|
193
209
|
// so capturing it is the host's explicit choice, not a silent default. When true and a
|
|
@@ -396,6 +412,8 @@ export interface ScaleBunConfig {
|
|
|
396
412
|
redactBodies?: boolean;
|
|
397
413
|
};
|
|
398
414
|
captureInteractionHeatmap: boolean;
|
|
415
|
+
/** RN-3 zero-config scroll instrumentation. OFF by default — it changes what the HOST renders. */
|
|
416
|
+
autoInstrumentScrollViews: boolean;
|
|
399
417
|
/** Opt-in: capture console.* into the Logs lane in SaaS mode. Default false. */
|
|
400
418
|
captureConsoleLogs: boolean;
|
|
401
419
|
ota?: {
|