@scalebun/react-native 1.10.7 → 1.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. package/android/src/main/java/com/scalebun/replaysdk/tracking/InteractionTracker.kt +25 -24
  2. package/dist/scalebun.full.js +236 -187
  3. package/dist/scalebun.slim.js +235 -186
  4. package/ios/Capture/InteractionTracker.swift +8 -4
  5. package/lib/commonjs/analytics/EventTracker.js +5 -5
  6. package/lib/commonjs/analytics/automaticEvents.js +3 -2
  7. package/lib/commonjs/core/constants/version.js +7 -2
  8. package/lib/commonjs/features/journey/ScaleBunDebugRoot.js +80 -103
  9. package/lib/commonjs/features/journey/interactionProtocol.js +47 -0
  10. package/lib/commonjs/features/journey/uiState.js +8 -1
  11. package/lib/commonjs/features/session/JourneyEventPipeline.js +6 -5
  12. package/lib/commonjs/features/session/SessionManager.js +37 -38
  13. package/lib/module/analytics/EventTracker.js +5 -5
  14. package/lib/module/analytics/automaticEvents.js +3 -2
  15. package/lib/module/core/constants/version.js +7 -2
  16. package/lib/module/features/journey/ScaleBunDebugRoot.js +80 -103
  17. package/lib/module/features/journey/interactionProtocol.js +38 -0
  18. package/lib/module/features/journey/uiState.js +8 -1
  19. package/lib/module/features/session/JourneyEventPipeline.js +6 -5
  20. package/lib/module/features/session/SessionManager.js +37 -38
  21. package/lib/typescript/analytics/EventTracker.d.ts +1 -1
  22. package/lib/typescript/analytics/automaticEvents.d.ts +3 -1
  23. package/lib/typescript/core/constants/version.d.ts +7 -2
  24. package/lib/typescript/features/journey/interactionProtocol.d.ts +21 -0
  25. package/lib/typescript/features/session/JourneyEventPipeline.d.ts +1 -0
  26. package/lib/typescript/features/session/SessionManager.d.ts +15 -10
  27. package/package.json +3 -2
  28. package/src/analytics/EventTracker.ts +5 -5
  29. package/src/analytics/automaticEvents.ts +4 -0
  30. package/src/core/constants/version.ts +7 -2
  31. package/src/features/journey/ScaleBunDebugRoot.tsx +96 -97
  32. package/src/features/journey/interactionProtocol.ts +65 -0
  33. package/src/features/journey/uiState.ts +9 -4
  34. package/src/features/session/JourneyEventPipeline.ts +7 -5
  35. package/src/features/session/SessionManager.ts +75 -38
@@ -605,7 +605,7 @@ var SDK_VERSION;
605
605
  var init_version = __esm({
606
606
  "lib/module/core/constants/version.js"() {
607
607
  "use strict";
608
- SDK_VERSION = "1.10.7";
608
+ SDK_VERSION = "1.11.0";
609
609
  }
610
610
  });
611
611
 
@@ -4567,22 +4567,23 @@ var init_JourneyEventPipeline = __esm({
4567
4567
  emit(type, opts) {
4568
4568
  try {
4569
4569
  const key = `${type}:${opts?.subtype ?? ""}`;
4570
- const now2 = Date.now();
4571
- if (key === this.lastEventKey && now2 - this.lastEventTs < this.config.dedupeWindowMs) {
4570
+ const receivedAt = Date.now();
4571
+ const occurredAt = opts?.timestamp ?? receivedAt;
4572
+ if (key === this.lastEventKey && receivedAt - this.lastEventTs < this.config.dedupeWindowMs) {
4572
4573
  const incomingConfidence = opts?.payload?.confidence;
4573
4574
  if (incomingConfidence === "high" && this.lastEventConfidence !== "high") {
4574
- this._replaceLastEvent(key, now2, opts);
4575
+ this._replaceLastEvent(key, receivedAt, opts);
4575
4576
  }
4576
4577
  return null;
4577
4578
  }
4578
4579
  this.lastEventKey = key;
4579
- this.lastEventTs = now2;
4580
+ this.lastEventTs = receivedAt;
4580
4581
  this.lastEventConfidence = opts?.payload?.confidence ?? null;
4581
4582
  const event = {
4582
4583
  eventId: generateEventId2(),
4583
4584
  sessionId: this.sessionId,
4584
4585
  journeyId: opts?.journeyId,
4585
- ts: now2,
4586
+ ts: occurredAt,
4586
4587
  type,
4587
4588
  subtype: opts?.subtype,
4588
4589
  severity: opts?.severity ?? inferSeverity(type),
@@ -5682,6 +5683,99 @@ var init_calibrationContext = __esm({
5682
5683
  }
5683
5684
  });
5684
5685
 
5686
+ // lib/module/analytics/automaticEvents.js
5687
+ function compactProperties(properties) {
5688
+ const out = {
5689
+ capture_source: "automatic"
5690
+ };
5691
+ for (const [key, value] of Object.entries(properties ?? {})) {
5692
+ if (value !== void 0) out[key] = value;
5693
+ }
5694
+ return out;
5695
+ }
5696
+ function emitAutomaticEvent(name, properties, timestamp) {
5697
+ const event = {
5698
+ name,
5699
+ properties: compactProperties(properties),
5700
+ timestamp
5701
+ };
5702
+ if (listeners.size === 0) {
5703
+ pending2.push(event);
5704
+ if (pending2.length > MAX_PENDING) pending2.shift();
5705
+ return;
5706
+ }
5707
+ for (const listener of listeners) {
5708
+ try {
5709
+ listener(event);
5710
+ } catch {
5711
+ }
5712
+ }
5713
+ }
5714
+ function subscribeAutomaticEvents(listener) {
5715
+ listeners.add(listener);
5716
+ if (pending2.length > 0) {
5717
+ const buffered = pending2.splice(0, pending2.length);
5718
+ for (const event of buffered) {
5719
+ try {
5720
+ listener(event);
5721
+ } catch {
5722
+ }
5723
+ }
5724
+ }
5725
+ return () => {
5726
+ listeners.delete(listener);
5727
+ };
5728
+ }
5729
+ var listeners, pending2, MAX_PENDING;
5730
+ var init_automaticEvents = __esm({
5731
+ "lib/module/analytics/automaticEvents.js"() {
5732
+ "use strict";
5733
+ listeners = /* @__PURE__ */ new Set();
5734
+ pending2 = [];
5735
+ MAX_PENDING = 50;
5736
+ }
5737
+ });
5738
+
5739
+ // lib/module/features/journey/interactionProtocol.js
5740
+ function generateInteractionId() {
5741
+ return `ixj-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}`;
5742
+ }
5743
+ function nearestInteractionStart(starts, occurredAt, toleranceMs = 1500) {
5744
+ let best;
5745
+ let bestDelta = toleranceMs + 1;
5746
+ for (const start of starts) {
5747
+ const delta = Math.abs(start.occurredAt - occurredAt);
5748
+ if (delta < bestDelta) {
5749
+ best = start;
5750
+ bestDelta = delta;
5751
+ }
5752
+ }
5753
+ return bestDelta <= toleranceMs ? best : void 0;
5754
+ }
5755
+ function automaticInteractionProperties(payload, screenName, canonicalMirror) {
5756
+ return {
5757
+ gesture_type: payload.gestureType,
5758
+ screen_name: screenName,
5759
+ interaction_id: payload.interaction_id,
5760
+ interaction_protocol: payload.interaction_protocol,
5761
+ state_status: payload.state_status,
5762
+ ui: payload.ui,
5763
+ target_id: payload.target_id,
5764
+ normalized_x: payload.normalizedX,
5765
+ normalized_y: payload.normalizedY,
5766
+ direction: payload.direction,
5767
+ duration_ms: payload.durationMs,
5768
+ canonical_mirror: canonicalMirror || void 0
5769
+ };
5770
+ }
5771
+ var INTERACTION_PROTOCOL_VERSION;
5772
+ var init_interactionProtocol = __esm({
5773
+ "lib/module/features/journey/interactionProtocol.js"() {
5774
+ "use strict";
5775
+ INTERACTION_PROTOCOL_VERSION = 1;
5776
+ }
5777
+ });
5778
+
5685
5779
  // lib/module/features/session/SessionManager.js
5686
5780
  var SessionManager_exports = {};
5687
5781
  __export(SessionManager_exports, {
@@ -5690,7 +5784,7 @@ __export(SessionManager_exports, {
5690
5784
  function generateSessionId2() {
5691
5785
  return `ses-${Date.now().toString(36)}-${Math.random().toString(36).substring(2, 8)}`;
5692
5786
  }
5693
- var HEATMAP_MAX_INTERACTIONS_PER_WINDOW, _instance2, SessionManager;
5787
+ var _instance2, SessionManager;
5694
5788
  var init_SessionManager = __esm({
5695
5789
  "lib/module/features/session/SessionManager.js"() {
5696
5790
  "use strict";
@@ -5707,7 +5801,8 @@ var init_SessionManager = __esm({
5707
5801
  init_redaction();
5708
5802
  init_calibrationContext();
5709
5803
  init_device();
5710
- HEATMAP_MAX_INTERACTIONS_PER_WINDOW = 200;
5804
+ init_automaticEvents();
5805
+ init_interactionProtocol();
5711
5806
  _instance2 = null;
5712
5807
  SessionManager = class _SessionManager {
5713
5808
  desktopTransport = null;
@@ -5718,15 +5813,6 @@ var init_SessionManager = __esm({
5718
5813
  * lane even when no replay recording is active. Additive, opt-in (default off).
5719
5814
  */
5720
5815
  _captureInteractionHeatmap = false;
5721
- /**
5722
- * Sampling cap for the analytics-lane heatmap emission. Now that capture is
5723
- * ON by default, an unbounded one-event-per-gesture stream could materially
5724
- * inflate ingest volume. We cap emitted interactions per analytics-session
5725
- * window (finalize-scoped per foreground): the first N gestures define the
5726
- * hotspot shape; the long tail is dropped. Resets when the window changes.
5727
- */
5728
- _heatmapWindowSessionId = null;
5729
- _heatmapWindowCount = 0;
5730
5816
  session = null;
5731
5817
  active = false;
5732
5818
  timeoutTimer = null;
@@ -6207,18 +6293,11 @@ var init_SessionManager = __esm({
6207
6293
  * injected sensitive keys (e.g. a label) are stripped before transport.
6208
6294
  * - Never throws.
6209
6295
  */
6210
- _emitInteractionToAnalytics(gestureType, payload) {
6211
- if (!this._captureInteractionHeatmap) return;
6212
- if (this.active) return;
6296
+ _emitInteractionToAnalytics(gestureType, payload, occurredAt, screenName) {
6297
+ if (!this._captureInteractionHeatmap) return false;
6298
+ if (this.active) return false;
6213
6299
  const adapter = this._backendTransport;
6214
- if (!adapter) return;
6215
- const windowId = adapter.analyticsSessionId ?? "";
6216
- if (windowId !== this._heatmapWindowSessionId) {
6217
- this._heatmapWindowSessionId = windowId;
6218
- this._heatmapWindowCount = 0;
6219
- }
6220
- if (this._heatmapWindowCount >= HEATMAP_MAX_INTERACTIONS_PER_WINDOW) return;
6221
- this._heatmapWindowCount++;
6300
+ if (!adapter) return false;
6222
6301
  try {
6223
6302
  this._normalizeInteractionPayload(payload);
6224
6303
  const safePayload = redactBody(payload);
@@ -6226,16 +6305,18 @@ var init_SessionManager = __esm({
6226
6305
  eventId: generateEventId(),
6227
6306
  // sessionId is (re)stamped by the analytics lane at flush time.
6228
6307
  sessionId: adapter.analyticsSessionId ?? "",
6229
- ts: Date.now(),
6308
+ ts: occurredAt,
6230
6309
  type: "USER_ACTION",
6231
6310
  subtype: `gesture:${gestureType}`,
6232
- screen: this._lastKnownScreen ?? void 0,
6311
+ screen: screenName ?? this._lastKnownScreen ?? void 0,
6233
6312
  payload: safePayload,
6234
6313
  source: "user"
6235
6314
  };
6236
6315
  adapter.trackEvent(event);
6316
+ return true;
6237
6317
  } catch (err) {
6238
6318
  logger.error("[SessionManager] heatmap analytics emit failed:", err);
6319
+ return false;
6239
6320
  }
6240
6321
  }
6241
6322
  /**
@@ -6401,13 +6482,14 @@ var init_SessionManager = __esm({
6401
6482
  * Notify of a user interaction. Called by ScaleBunDebugRoot touch handlers.
6402
6483
  * Also triggers frame capture for desktop-initiated recordings.
6403
6484
  */
6404
- onUserAction(subtype, payload) {
6485
+ onUserAction(subtype, payload, context) {
6405
6486
  if (!this.active) return;
6406
6487
  this.emitEvent("USER_ACTION", {
6407
6488
  subtype,
6408
- screen: this._lastKnownScreen ?? void 0,
6489
+ screen: context?.screen ?? this._lastKnownScreen ?? void 0,
6409
6490
  payload,
6410
- source: "user"
6491
+ source: "user",
6492
+ timestamp: context?.timestamp
6411
6493
  });
6412
6494
  }
6413
6495
  /**
@@ -6415,7 +6497,8 @@ var init_SessionManager = __esm({
6415
6497
  * Emits a USER_ACTION event with gesture-specific subtype and payload.
6416
6498
  */
6417
6499
  onGestureDetected(gestureType, details) {
6418
- const dedupSig = `${gestureType}|${String(details?.x)}|${String(details?.y)}|${String(details?.endX)}|${String(details?.endY)}`;
6500
+ const suppliedInteractionId = details?.interactionId;
6501
+ const dedupSig = suppliedInteractionId ? `id:${suppliedInteractionId}` : `${gestureType}|${String(details?.x)}|${String(details?.y)}|${String(details?.endX)}|${String(details?.endY)}`;
6419
6502
  const nowTs = Date.now();
6420
6503
  if (dedupSig === this._lastGestureSig && nowTs - this._lastGestureTs <= _SessionManager.GESTURE_DEDUP_WINDOW_MS) {
6421
6504
  this._lastGestureTs = nowTs;
@@ -6423,7 +6506,15 @@ var init_SessionManager = __esm({
6423
6506
  }
6424
6507
  this._lastGestureSig = dedupSig;
6425
6508
  this._lastGestureTs = nowTs;
6509
+ const interactionId = suppliedInteractionId ?? generateInteractionId();
6510
+ const occurredAt = details?.occurredAt ?? Date.now();
6511
+ const stateStatus = details?.stateStatus ?? (details?.ui ? "captured_nonempty" : "not_captured");
6426
6512
  const payload = {
6513
+ interaction_id: interactionId,
6514
+ interaction_protocol: details?.interactionProtocol ?? INTERACTION_PROTOCOL_VERSION,
6515
+ state_status: stateStatus,
6516
+ ui: details?.ui,
6517
+ target_id: details?.targetId,
6427
6518
  gestureType,
6428
6519
  x: details?.x,
6429
6520
  y: details?.y,
@@ -6461,12 +6552,23 @@ var init_SessionManager = __esm({
6461
6552
  screenHeight: details?.screenHeight,
6462
6553
  platform: details?.platform
6463
6554
  };
6464
- this._emitInteractionToAnalytics(gestureType, {
6555
+ const analyticsReplayCarrier = this._emitInteractionToAnalytics(gestureType, {
6465
6556
  ...payload
6466
- });
6557
+ }, occurredAt, details?.screenName);
6558
+ const replayCarrier = !!this._backendTransport && (this.active || analyticsReplayCarrier);
6559
+ if (details?.emitAutomaticAnalytics) {
6560
+ try {
6561
+ const safePayload = redactBody(payload);
6562
+ emitAutomaticEvent("element_interacted", automaticInteractionProperties(safePayload, details?.screenName ?? this._lastKnownScreen ?? void 0, replayCarrier), occurredAt);
6563
+ } catch {
6564
+ }
6565
+ }
6467
6566
  if (!this.active) return;
6468
6567
  const target = typeof details?.target === "string" ? details.target.trim() : "";
6469
- this.onUserAction(target ? `gesture:${gestureType} \xB7 ${target}` : `gesture:${gestureType}`, payload);
6568
+ this.onUserAction(target ? `gesture:${gestureType} \xB7 ${target}` : `gesture:${gestureType}`, payload, {
6569
+ screen: details?.screenName,
6570
+ timestamp: occurredAt
6571
+ });
6470
6572
  }
6471
6573
  /**
6472
6574
  * Manual frame capture — triggered by Desktop "Capture Step" button.
@@ -7781,12 +7883,12 @@ var init_AppLaunchCollector = __esm({
7781
7883
  });
7782
7884
 
7783
7885
  // lib/module/features/performance/collectors/ScreenLoadCollector.js
7784
- var MAX_PENDING, ScreenLoadCollector;
7886
+ var MAX_PENDING2, ScreenLoadCollector;
7785
7887
  var init_ScreenLoadCollector = __esm({
7786
7888
  "lib/module/features/performance/collectors/ScreenLoadCollector.js"() {
7787
7889
  "use strict";
7788
7890
  init_models();
7789
- MAX_PENDING = 20;
7891
+ MAX_PENDING2 = 20;
7790
7892
  ScreenLoadCollector = class {
7791
7893
  pending = /* @__PURE__ */ new Map();
7792
7894
  lastScreen = null;
@@ -7801,7 +7903,7 @@ var init_ScreenLoadCollector = __esm({
7801
7903
  */
7802
7904
  markStart(screenName) {
7803
7905
  if (!this.config.screenLoad) return;
7804
- if (this.pending.size >= MAX_PENDING) {
7906
+ if (this.pending.size >= MAX_PENDING2) {
7805
7907
  const oldest = this.pending.keys().next().value;
7806
7908
  if (oldest) this.pending.delete(oldest);
7807
7909
  }
@@ -10933,58 +11035,6 @@ var init_batching = __esm({
10933
11035
  }
10934
11036
  });
10935
11037
 
10936
- // lib/module/analytics/automaticEvents.js
10937
- function compactProperties(properties) {
10938
- const out = {
10939
- capture_source: "automatic"
10940
- };
10941
- for (const [key, value] of Object.entries(properties ?? {})) {
10942
- if (value !== void 0) out[key] = value;
10943
- }
10944
- return out;
10945
- }
10946
- function emitAutomaticEvent(name, properties) {
10947
- const event = {
10948
- name,
10949
- properties: compactProperties(properties)
10950
- };
10951
- if (listeners.size === 0) {
10952
- pending3.push(event);
10953
- if (pending3.length > MAX_PENDING2) pending3.shift();
10954
- return;
10955
- }
10956
- for (const listener of listeners) {
10957
- try {
10958
- listener(event);
10959
- } catch {
10960
- }
10961
- }
10962
- }
10963
- function subscribeAutomaticEvents(listener) {
10964
- listeners.add(listener);
10965
- if (pending3.length > 0) {
10966
- const buffered = pending3.splice(0, pending3.length);
10967
- for (const event of buffered) {
10968
- try {
10969
- listener(event);
10970
- } catch {
10971
- }
10972
- }
10973
- }
10974
- return () => {
10975
- listeners.delete(listener);
10976
- };
10977
- }
10978
- var listeners, pending3, MAX_PENDING2;
10979
- var init_automaticEvents = __esm({
10980
- "lib/module/analytics/automaticEvents.js"() {
10981
- "use strict";
10982
- listeners = /* @__PURE__ */ new Set();
10983
- pending3 = [];
10984
- MAX_PENDING2 = 50;
10985
- }
10986
- });
10987
-
10988
11038
  // lib/module/analytics/EventTracker.js
10989
11039
  function uuid() {
10990
11040
  return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
@@ -11053,7 +11103,7 @@ var init_EventTracker = __esm({
11053
11103
  this.started = true;
11054
11104
  if (this.cfg.automaticEventTracking) {
11055
11105
  this.automaticEventsUnsubscribe = subscribeAutomaticEvents((event) => {
11056
- this.track(event.name, event.properties);
11106
+ this.track(event.name, event.properties, event.timestamp);
11057
11107
  });
11058
11108
  }
11059
11109
  if (this.cfg.autoLifecycleEvents) {
@@ -11157,9 +11207,9 @@ var init_EventTracker = __esm({
11157
11207
  }
11158
11208
  }
11159
11209
  // ─── tracking ────────────────────────────────────────────────────────────
11160
- track(eventName, properties) {
11210
+ track(eventName, properties, timestamp) {
11161
11211
  try {
11162
- this.enqueue(this.buildEnvelope(eventName, properties));
11212
+ this.enqueue(this.buildEnvelope(eventName, properties, timestamp));
11163
11213
  try {
11164
11214
  this.cfg.onEvent?.(eventName);
11165
11215
  } catch {
@@ -11263,7 +11313,7 @@ var init_EventTracker = __esm({
11263
11313
  };
11264
11314
  }
11265
11315
  // ─── internals ─────────────────────────────────────────────────────────────
11266
- buildEnvelope(eventName, properties) {
11316
+ buildEnvelope(eventName, properties, timestamp) {
11267
11317
  const ctx = this.cfg.context ?? {};
11268
11318
  let canonicalSessionId;
11269
11319
  try {
@@ -11274,7 +11324,7 @@ var init_EventTracker = __esm({
11274
11324
  const env = {
11275
11325
  event_id: uuid(),
11276
11326
  event_name: eventName,
11277
- event_time: Date.now(),
11327
+ event_time: timestamp ?? Date.now(),
11278
11328
  app_id: this.cfg.appId,
11279
11329
  platform: this.cfg.platform ?? resolveEventPlatform(),
11280
11330
  installation_id: this.installationId,
@@ -12760,7 +12810,14 @@ function clearUiState(name) {
12760
12810
  }
12761
12811
  function uiStateSignature() {
12762
12812
  if (!declared.size) return void 0;
12763
- return [...declared.keys()].sort().map((k) => `${k}:${declared.get(k)}`).join(";").slice(0, 96);
12813
+ const pairs = [...declared.keys()].sort().map((k) => `${k}:${declared.get(k)}`);
12814
+ let signature = "";
12815
+ for (const pair of pairs) {
12816
+ const next = signature ? `${signature};${pair}` : pair;
12817
+ if (next.length > 96) break;
12818
+ signature = next;
12819
+ }
12820
+ return signature || void 0;
12764
12821
  }
12765
12822
  var declared, clean;
12766
12823
  var init_uiState = __esm({
@@ -15668,14 +15725,14 @@ function installRejectionHandler(onRejection, deps) {
15668
15725
  allRejections: true,
15669
15726
  onUnhandled: (id, error) => {
15670
15727
  const err = toError(error);
15671
- pending2.set(id, err);
15728
+ pending3.set(id, err);
15672
15729
  onRejection(err);
15673
15730
  if (isDev) {
15674
15731
  console.warn(`Possible unhandled promise rejection (id: ${id}):`, err?.message ?? err);
15675
15732
  }
15676
15733
  },
15677
15734
  onHandled: (id) => {
15678
- pending2.delete(id);
15735
+ pending3.delete(id);
15679
15736
  if (isDev) {
15680
15737
  console.warn(`Promise rejection handled late (id: ${id}) \u2014 it was already reported.`);
15681
15738
  }
@@ -15685,7 +15742,7 @@ function installRejectionHandler(onRejection, deps) {
15685
15742
  return true;
15686
15743
  }) ?? false;
15687
15744
  }
15688
- var pending2 = /* @__PURE__ */ new Map();
15745
+ var pending3 = /* @__PURE__ */ new Map();
15689
15746
 
15690
15747
  // lib/module/features/crash/CrashFeature.js
15691
15748
  init_CrashReporter();
@@ -33194,7 +33251,6 @@ function useEngagePrompt() {
33194
33251
  // lib/module/features/journey/ScaleBunDebugRoot.js
33195
33252
  var import_react9 = __toESM(require("react"));
33196
33253
  var import_react_native43 = require("react-native");
33197
- init_automaticEvents();
33198
33254
 
33199
33255
  // lib/module/features/journey/touchTarget.js
33200
33256
  var MAX_DEPTH = 12;
@@ -33266,6 +33322,7 @@ function describeTouchTarget(t) {
33266
33322
 
33267
33323
  // lib/module/features/journey/ScaleBunDebugRoot.js
33268
33324
  init_device();
33325
+ init_interactionProtocol();
33269
33326
  var import_jsx_runtime8 = require("react/jsx-runtime");
33270
33327
  var _bootstrap = null;
33271
33328
  function getBootstrap() {
@@ -33455,7 +33512,9 @@ function ScaleBunDebugRoot({
33455
33512
  }, [navigationRef]);
33456
33513
  const lastNativeTouchTsRef = (0, import_react9.useRef)(0);
33457
33514
  const nativeTrackingConfirmedRef = (0, import_react9.useRef)(false);
33458
- const pendingJsEmitRef = (0, import_react9.useRef)(null);
33515
+ const touchStartRef = (0, import_react9.useRef)(null);
33516
+ const interactionStartsRef = (0, import_react9.useRef)([]);
33517
+ const pendingJsEmitsRef = (0, import_react9.useRef)(/* @__PURE__ */ new Map());
33459
33518
  (0, import_react9.useEffect)(() => {
33460
33519
  let subscription = null;
33461
33520
  try {
@@ -33466,9 +33525,12 @@ function ScaleBunDebugRoot({
33466
33525
  try {
33467
33526
  lastNativeTouchTsRef.current = Date.now();
33468
33527
  nativeTrackingConfirmedRef.current = true;
33469
- if (pendingJsEmitRef.current !== null) {
33470
- clearTimeout(pendingJsEmitRef.current);
33471
- pendingJsEmitRef.current = null;
33528
+ const nativeOccurredAt = typeof event.occurredAt === "number" ? event.occurredAt : (typeof event.timestamp === "number" ? event.timestamp : Date.now()) - (typeof event.durationMs === "number" ? event.durationMs : 0);
33529
+ const start = nearestInteractionStart(interactionStartsRef.current, nativeOccurredAt);
33530
+ if (start) {
33531
+ const pending4 = pendingJsEmitsRef.current.get(start.interactionId);
33532
+ if (pending4 !== void 0) clearTimeout(pending4);
33533
+ pendingJsEmitsRef.current.delete(start.interactionId);
33472
33534
  }
33473
33535
  const {
33474
33536
  SessionManager: SessionManager2
@@ -33487,6 +33549,15 @@ function ScaleBunDebugRoot({
33487
33549
  } catch {
33488
33550
  }
33489
33551
  sm.onGestureDetected(event.gestureType || "tap", {
33552
+ interactionId: event.interactionId || start?.interactionId || generateInteractionId(),
33553
+ interactionProtocol: event.interactionProtocol || INTERACTION_PROTOCOL_VERSION,
33554
+ occurredAt: nativeOccurredAt,
33555
+ ui: start?.ui,
33556
+ stateStatus: start?.stateStatus ?? "not_captured",
33557
+ target: start?.target,
33558
+ targetId: start?.targetId,
33559
+ screenName: start?.screenName,
33560
+ emitAutomaticAnalytics: start?.emitAutomaticAnalytics ?? captureAutomaticInteractions,
33490
33561
  x: event.rawX,
33491
33562
  y: event.rawY,
33492
33563
  // Use the actual native end coords when present, not the down
@@ -33519,9 +33590,10 @@ function ScaleBunDebugRoot({
33519
33590
  subscription?.remove();
33520
33591
  } catch {
33521
33592
  }
33593
+ for (const timer of pendingJsEmitsRef.current.values()) clearTimeout(timer);
33594
+ pendingJsEmitsRef.current.clear();
33522
33595
  };
33523
- }, []);
33524
- const touchStartRef = (0, import_react9.useRef)(null);
33596
+ }, [captureAutomaticInteractions]);
33525
33597
  const rootRectRef = (0, import_react9.useRef)(null);
33526
33598
  const measureRoot = () => {
33527
33599
  try {
@@ -33553,11 +33625,39 @@ function ScaleBunDebugRoot({
33553
33625
  const handleTouchStart = (e) => {
33554
33626
  try {
33555
33627
  const touch = e.nativeEvent;
33556
- touchStartRef.current = {
33628
+ const target = resolveTouchTarget(e);
33629
+ let ui;
33630
+ let screenName;
33631
+ try {
33632
+ const {
33633
+ uiStateSignature: uiStateSignature2
33634
+ } = (init_uiState(), __toCommonJS(uiState_exports));
33635
+ ui = uiStateSignature2();
33636
+ } catch {
33637
+ }
33638
+ try {
33639
+ const {
33640
+ AutoScreenDetector: AutoScreenDetector2
33641
+ } = (init_AutoScreenDetector(), __toCommonJS(AutoScreenDetector_exports));
33642
+ screenName = AutoScreenDetector2.getInstance().getCurrentScreen() || void 0;
33643
+ } catch {
33644
+ }
33645
+ const start = {
33646
+ interactionId: generateInteractionId(),
33647
+ occurredAt: Date.now(),
33557
33648
  x: touch.pageX,
33558
33649
  y: touch.pageY,
33559
- ts: Date.now()
33650
+ locationX: touch.locationX,
33651
+ locationY: touch.locationY,
33652
+ target: describeTouchTarget(target),
33653
+ targetId: target?.testID,
33654
+ screenName,
33655
+ ui,
33656
+ stateStatus: ui ? "captured_nonempty" : "not_instrumented",
33657
+ emitAutomaticAnalytics: captureAutomaticInteractions
33560
33658
  };
33659
+ touchStartRef.current = start;
33660
+ interactionStartsRef.current = interactionStartsRef.current.filter((candidate) => start.occurredAt - candidate.occurredAt < 5e3).concat(start).slice(-8);
33561
33661
  } catch {
33562
33662
  }
33563
33663
  };
@@ -33588,21 +33688,29 @@ function ScaleBunDebugRoot({
33588
33688
  }
33589
33689
  measureRoot();
33590
33690
  const rootRect = rootRectRef.current;
33591
- const tapped = describeTouchTarget(resolveTouchTarget(e));
33691
+ const tapped = start?.target ?? describeTouchTarget(resolveTouchTarget(e));
33592
33692
  const gestureDetails = {
33593
33693
  target: tapped,
33594
- x: touch.pageX,
33595
- y: touch.pageY,
33596
- pageX: touch.pageX,
33597
- pageY: touch.pageY,
33598
- locationX: touch.locationX,
33599
- locationY: touch.locationY,
33694
+ interactionId: start?.interactionId ?? generateInteractionId(),
33695
+ interactionProtocol: INTERACTION_PROTOCOL_VERSION,
33696
+ occurredAt: start?.occurredAt ?? Date.now(),
33697
+ ui: start?.ui,
33698
+ stateStatus: start?.stateStatus ?? "not_captured",
33699
+ targetId: start?.targetId,
33700
+ screenName: start?.screenName,
33701
+ emitAutomaticAnalytics: start?.emitAutomaticAnalytics ?? captureAutomaticInteractions,
33702
+ x: start?.x ?? touch.pageX,
33703
+ y: start?.y ?? touch.pageY,
33704
+ pageX: start?.x ?? touch.pageX,
33705
+ pageY: start?.y ?? touch.pageY,
33706
+ locationX: start?.locationX ?? touch.locationX,
33707
+ locationY: start?.locationY ?? touch.locationY,
33600
33708
  viewportWidth: vpW > 0 ? vpW : void 0,
33601
33709
  viewportHeight: vpH > 0 ? vpH : void 0
33602
33710
  };
33603
33711
  if (rootRect && rootRect.w > 0 && rootRect.h > 0) {
33604
- const nx = Math.max(0, Math.min(1, touch.pageX / rootRect.w));
33605
- const ny = Math.max(0, Math.min(1, touch.pageY / rootRect.h));
33712
+ const nx = Math.max(0, Math.min(1, (start?.x ?? touch.pageX) / rootRect.w));
33713
+ const ny = Math.max(0, Math.min(1, (start?.y ?? touch.pageY) / rootRect.h));
33606
33714
  gestureDetails.normalizedX = nx;
33607
33715
  gestureDetails.normalizedY = ny;
33608
33716
  gestureDetails.normalizedPrecomputed = true;
@@ -33633,7 +33741,7 @@ function ScaleBunDebugRoot({
33633
33741
  const dx = touch.pageX - start.x;
33634
33742
  const dy = touch.pageY - start.y;
33635
33743
  const dist = Math.sqrt(dx * dx + dy * dy);
33636
- const duration = Date.now() - start.ts;
33744
+ const duration = Date.now() - start.occurredAt;
33637
33745
  gestureDetails.duration = duration;
33638
33746
  if (dist >= 15) {
33639
33747
  gestureDetails.endX = touch.pageX;
@@ -33651,60 +33759,16 @@ function ScaleBunDebugRoot({
33651
33759
  gestureType = "long_press";
33652
33760
  }
33653
33761
  }
33654
- const targetInfo = _extractTarget(e);
33655
- gestureDetails.target = targetInfo?.testId || targetInfo?.accessibilityLabel;
33656
- if (captureAutomaticInteractions) {
33657
- let screen;
33658
- try {
33659
- const {
33660
- AutoScreenDetector: AutoScreenDetector2
33661
- } = (init_AutoScreenDetector(), __toCommonJS(AutoScreenDetector_exports));
33662
- screen = AutoScreenDetector2.getInstance().getCurrentScreen() || void 0;
33663
- } catch {
33664
- }
33665
- let ui;
33666
- try {
33667
- const {
33668
- uiStateSignature: uiStateSignature2
33669
- } = (init_uiState(), __toCommonJS(uiState_exports));
33670
- ui = uiStateSignature2();
33671
- } catch {
33672
- }
33673
- emitAutomaticEvent("element_interacted", {
33674
- gesture_type: gestureType,
33675
- screen_name: screen,
33676
- /* THIS MAP ENUMERATES. A field added to the payload and forgotten here reaches the
33677
- backend as undefined with no error anywhere — the recurring defect class in this
33678
- codebase. `ui` is the key the grid aggregate reads for state, byte-identical to
33679
- the web SDK's, so one dashboard control queries both platforms. */
33680
- ui,
33681
- // testID/nativeID is an author-controlled stable identifier.
33682
- // Accessibility labels and rendered text are deliberately omitted.
33683
- target_id: targetInfo?.testId,
33684
- normalized_x: gestureDetails.normalizedX,
33685
- normalized_y: gestureDetails.normalizedY,
33686
- direction: gestureDetails.direction,
33687
- duration_ms: gestureDetails.duration
33688
- });
33689
- }
33690
33762
  if (gestureType === "tap") {
33691
33763
  try {
33692
33764
  const {
33693
33765
  gestureTriggerDetector: gestureTriggerDetector2
33694
33766
  } = (init_gestureTriggerDetector(), __toCommonJS(gestureTriggerDetector_exports));
33695
- let screen;
33696
- try {
33697
- const {
33698
- AutoScreenDetector: AutoScreenDetector2
33699
- } = (init_AutoScreenDetector(), __toCommonJS(AutoScreenDetector_exports));
33700
- screen = AutoScreenDetector2.getInstance().getCurrentScreen() || void 0;
33701
- } catch {
33702
- }
33703
33767
  gestureTriggerDetector2.recordTap({
33704
- x: touch.pageX,
33705
- y: touch.pageY,
33706
- hasTarget: !!(targetInfo?.testId || targetInfo?.accessibilityLabel),
33707
- screen
33768
+ x: start?.x ?? touch.pageX,
33769
+ y: start?.y ?? touch.pageY,
33770
+ hasTarget: !!(start?.targetId || start?.target),
33771
+ screen: start?.screenName
33708
33772
  });
33709
33773
  } catch {
33710
33774
  }
@@ -33717,11 +33781,9 @@ function ScaleBunDebugRoot({
33717
33781
  const capturedDetails = {
33718
33782
  ...gestureDetails
33719
33783
  };
33720
- if (pendingJsEmitRef.current !== null) {
33721
- clearTimeout(pendingJsEmitRef.current);
33722
- }
33723
- pendingJsEmitRef.current = setTimeout(() => {
33724
- pendingJsEmitRef.current = null;
33784
+ const interactionId = capturedDetails.interactionId;
33785
+ const timer = setTimeout(() => {
33786
+ pendingJsEmitsRef.current.delete(interactionId);
33725
33787
  if (nativeTrackingConfirmedRef.current) return;
33726
33788
  try {
33727
33789
  const {
@@ -33738,6 +33800,7 @@ function ScaleBunDebugRoot({
33738
33800
  } catch {
33739
33801
  }
33740
33802
  }, 600);
33803
+ pendingJsEmitsRef.current.set(interactionId, timer);
33741
33804
  }
33742
33805
  touchStartRef.current = null;
33743
33806
  } catch {
@@ -33813,20 +33876,6 @@ function useScaleBunScreen(name) {
33813
33876
  }
33814
33877
  }, [name, manager]);
33815
33878
  }
33816
- function _extractTarget(e) {
33817
- try {
33818
- const target = e?.target;
33819
- if (!target) return void 0;
33820
- const props = target._internalFiberInstanceHandleDEV?.memoizedProps ?? {};
33821
- return {
33822
- testId: props.testID || props.nativeID || void 0,
33823
- accessibilityLabel: props.accessibilityLabel || void 0,
33824
- text: void 0
33825
- };
33826
- } catch {
33827
- return void 0;
33828
- }
33829
- }
33830
33879
  var styles4 = {
33831
33880
  root: {
33832
33881
  flex: 1