@scalebun/react-native 1.10.7 → 1.11.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 (54) hide show
  1. package/android/src/main/java/com/scalebun/replaysdk/tracking/InteractionTracker.kt +25 -25
  2. package/android/src/main/java/com/scalebun/rn/ota/SlotManager.kt +15 -3
  3. package/dist/scalebun.full.js +467 -255
  4. package/dist/scalebun.slim.js +466 -254
  5. package/ios/Capture/InteractionTracker.swift +8 -4
  6. package/ios/Ota/OtaSlotManager.swift +19 -5
  7. package/lib/commonjs/analytics/EventTracker.js +5 -5
  8. package/lib/commonjs/analytics/automaticEvents.js +3 -2
  9. package/lib/commonjs/core/constants/version.js +7 -2
  10. package/lib/commonjs/features/journey/ScaleBunDebugRoot.js +80 -103
  11. package/lib/commonjs/features/journey/interactionProtocol.js +47 -0
  12. package/lib/commonjs/features/journey/uiState.js +8 -1
  13. package/lib/commonjs/features/ota/OtaOrchestrator.js +174 -48
  14. package/lib/commonjs/features/ota/OtaTypes.js +4 -0
  15. package/lib/commonjs/features/ota/useOtaUpdate.js +11 -2
  16. package/lib/commonjs/features/session/JourneyEventPipeline.js +6 -5
  17. package/lib/commonjs/features/session/SessionManager.js +37 -38
  18. package/lib/commonjs/public/ScaleBunFacade.js +115 -2
  19. package/lib/module/analytics/EventTracker.js +5 -5
  20. package/lib/module/analytics/automaticEvents.js +3 -2
  21. package/lib/module/core/constants/version.js +7 -2
  22. package/lib/module/features/journey/ScaleBunDebugRoot.js +80 -103
  23. package/lib/module/features/journey/interactionProtocol.js +38 -0
  24. package/lib/module/features/journey/uiState.js +8 -1
  25. package/lib/module/features/ota/OtaOrchestrator.js +174 -48
  26. package/lib/module/features/ota/OtaTypes.js +1 -1
  27. package/lib/module/features/ota/useOtaUpdate.js +11 -2
  28. package/lib/module/features/session/JourneyEventPipeline.js +6 -5
  29. package/lib/module/features/session/SessionManager.js +37 -38
  30. package/lib/module/public/ScaleBunFacade.js +115 -2
  31. package/lib/typescript/analytics/EventTracker.d.ts +1 -1
  32. package/lib/typescript/analytics/automaticEvents.d.ts +3 -1
  33. package/lib/typescript/core/constants/version.d.ts +7 -2
  34. package/lib/typescript/features/journey/interactionProtocol.d.ts +21 -0
  35. package/lib/typescript/features/ota/OtaEventEmitter.d.ts +15 -1
  36. package/lib/typescript/features/ota/OtaOrchestrator.d.ts +22 -3
  37. package/lib/typescript/features/ota/OtaTypes.d.ts +29 -32
  38. package/lib/typescript/features/session/JourneyEventPipeline.d.ts +1 -0
  39. package/lib/typescript/features/session/SessionManager.d.ts +15 -10
  40. package/lib/typescript/public/ScaleBunFacade.d.ts +27 -0
  41. package/package.json +4 -3
  42. package/src/analytics/EventTracker.ts +5 -5
  43. package/src/analytics/automaticEvents.ts +4 -0
  44. package/src/core/constants/version.ts +7 -2
  45. package/src/features/journey/ScaleBunDebugRoot.tsx +96 -97
  46. package/src/features/journey/interactionProtocol.ts +65 -0
  47. package/src/features/journey/uiState.ts +9 -4
  48. package/src/features/ota/OtaEventEmitter.ts +12 -0
  49. package/src/features/ota/OtaOrchestrator.ts +209 -62
  50. package/src/features/ota/OtaTypes.ts +37 -39
  51. package/src/features/ota/useOtaUpdate.ts +11 -2
  52. package/src/features/session/JourneyEventPipeline.ts +7 -5
  53. package/src/features/session/SessionManager.ts +75 -38
  54. package/src/public/ScaleBunFacade.ts +127 -3
@@ -9,6 +9,7 @@ import android.view.Window
9
9
  import android.view.WindowManager
10
10
  import java.lang.ref.WeakReference
11
11
  import java.util.WeakHashMap
12
+ import java.util.UUID
12
13
  import com.facebook.react.bridge.Arguments
13
14
  import com.facebook.react.bridge.ReactApplicationContext
14
15
  import com.facebook.react.modules.core.DeviceEventManagerModule
@@ -34,13 +35,10 @@ class InteractionTracker(private val reactContext: ReactApplicationContext) {
34
35
  private var touchDownX = 0f
35
36
  private var touchDownY = 0f
36
37
  private var touchDownTs = 0L
37
- private var lastEmitTs = 0L
38
- private val THROTTLE_MS = 150L
39
- // Same-coordinate duplicate window — see handleTouchUp. Wider than THROTTLE_MS on purpose:
40
- // it only ever suppresses a bit-identical re-delivery, never a real second tap.
41
- private val SAME_COORD_WINDOW_MS = 500L
42
- private var lastEmitRawX = Float.NaN
43
- private var lastEmitRawY = Float.NaN
38
+ private var touchDownOccurredAt = 0L
39
+ private var touchInteractionId = ""
40
+ private var lastEmittedInteractionId = ""
41
+ private val processInteractionNonce = UUID.randomUUID().toString().take(8)
44
42
  private val mainHandler = Handler(Looper.getMainLooper())
45
43
 
46
44
  fun attach(activity: Activity) {
@@ -63,6 +61,9 @@ class InteractionTracker(private val reactContext: ReactApplicationContext) {
63
61
  touchDownX = rawX
64
62
  touchDownY = rawY
65
63
  touchDownTs = ts
64
+ val id = "ixn-a-$processInteractionNonce-$ts"
65
+ if (id != touchInteractionId) touchDownOccurredAt = System.currentTimeMillis()
66
+ touchInteractionId = id
66
67
  },
67
68
  onUp = { rawX, rawY, ts ->
68
69
  handleTouchUp(rawX, rawY, ts, resolveCaptureView(window))
@@ -86,6 +87,9 @@ class InteractionTracker(private val reactContext: ReactApplicationContext) {
86
87
  touchDownX = rawX
87
88
  touchDownY = rawY
88
89
  touchDownTs = ts
90
+ val id = "ixn-a-$processInteractionNonce-$ts"
91
+ if (id != touchInteractionId) touchDownOccurredAt = System.currentTimeMillis()
92
+ touchInteractionId = id
89
93
  },
90
94
  onUp = { rawX, rawY, ts ->
91
95
  val main = attachedWindow ?: return@DialogTouchObserver
@@ -137,21 +141,11 @@ class InteractionTracker(private val reactContext: ReactApplicationContext) {
137
141
  ReplayLogger.debug("Native touch up dropped: no usable capture view")
138
142
  return
139
143
  }
140
- val now = System.currentTimeMillis()
141
- if (now - lastEmitTs < THROTTLE_MS) return
142
- // Same-coordinate suppression: the same UP can be re-delivered through more than one
143
- // hooked window callback at gaps just past THROTTLE_MS (measured in production:
144
- // identical sub-pixel coords 158–240ms apart, recorded as 2–3 taps for one press).
145
- // Bit-identical float coordinates within half a second cannot be a second physical
146
- // tap — a real double-tap lands on at least slightly different pixels — so this
147
- // drops only machine duplicates. Distinct-coordinate taps still pass at THROTTLE_MS.
148
- if (rawX == lastEmitRawX && rawY == lastEmitRawY && now - lastEmitTs < SAME_COORD_WINDOW_MS) {
149
- lastEmitTs = now // keep a chained burst suppressed
150
- return
151
- }
152
- lastEmitTs = now
153
- lastEmitRawX = rawX
154
- lastEmitRawY = rawY
144
+ // Window/dialog hooks can re-deliver one physical touch. The ACTION_DOWN identity is stable
145
+ // across those deliveries, unlike coordinates: two legitimate rapid taps may land on the
146
+ // exact same pixel and must both survive.
147
+ if (touchInteractionId.isNotEmpty() && touchInteractionId == lastEmittedInteractionId) return
148
+ lastEmittedInteractionId = touchInteractionId
155
149
 
156
150
  val durationMs = ts - touchDownTs
157
151
  val dx = rawX - touchDownX
@@ -215,6 +209,8 @@ class InteractionTracker(private val reactContext: ReactApplicationContext) {
215
209
  screenshotW = viewW,
216
210
  screenshotH = viewH,
217
211
  durationMs = durationMs,
212
+ interactionId = touchInteractionId,
213
+ occurredAt = touchDownOccurredAt,
218
214
  onDialogWindow = onDialogWindow
219
215
  )
220
216
  }
@@ -226,11 +222,16 @@ class InteractionTracker(private val reactContext: ReactApplicationContext) {
226
222
  normalizedEndX: Float, normalizedEndY: Float,
227
223
  screenshotW: Float, screenshotH: Float,
228
224
  durationMs: Long,
225
+ interactionId: String,
226
+ occurredAt: Long,
229
227
  onDialogWindow: Boolean = false
230
228
  ) {
231
229
  try {
232
230
  val params = Arguments.createMap().apply {
233
231
  putString("gestureType", gestureType)
232
+ putString("interactionId", interactionId)
233
+ putInt("interactionProtocol", 1)
234
+ putDouble("occurredAt", occurredAt.toDouble())
234
235
  putDouble("rawX", rawX.toDouble())
235
236
  putDouble("rawY", rawY.toDouble())
236
237
  putDouble("normalizedX", normalizedX.toDouble())
@@ -275,9 +276,8 @@ class InteractionTracker(private val reactContext: ReactApplicationContext) {
275
276
 
276
277
  fun reset() {
277
278
  lastInteractionMs = 0
278
- lastEmitTs = 0
279
- lastEmitRawX = Float.NaN
280
- lastEmitRawY = Float.NaN
279
+ touchInteractionId = ""
280
+ lastEmittedInteractionId = ""
281
281
  touchDownTs = 0
282
282
  }
283
283
  }
@@ -179,10 +179,18 @@ class SlotManager(private val filesDir: File) {
179
179
  if (reason != null) {
180
180
  runCatching {
181
181
  otaRoot.mkdirs()
182
+ // Stamp WHICH bundle is being reverted away from, read before
183
+ // current/ is deleted below. Without it JS had nothing to name:
184
+ // slot meta carries no bundleId and the previous slot is gone by
185
+ // the time JS looks, so every AUTO_ROLLBACK reported bundleId
186
+ // "unknown" and the server dropped it as unresolvable — leaving
187
+ // the crash guard's own rollback signal permanently at zero.
188
+ val failingSha = readMeta(currentSlot)?.optString("sha256", "").orEmpty()
182
189
  revertRecord.writeText(
183
190
  JSONObject()
184
191
  .put("reason", reason)
185
192
  .put("at", System.currentTimeMillis())
193
+ .apply { if (failingSha.isNotEmpty()) put("sha256", failingSha) }
186
194
  .toString(),
187
195
  )
188
196
  }
@@ -237,7 +245,7 @@ class SlotManager(private val filesDir: File) {
237
245
  val metaFile = File(currentSlot, META_FILENAME)
238
246
  if (!metaFile.exists()) {
239
247
  Log.w(TAG, "Boot marker present but no current/meta.json — auto-reverting")
240
- revert()
248
+ revert(reason = "missing_meta")
241
249
  return
242
250
  }
243
251
 
@@ -259,7 +267,7 @@ class SlotManager(private val filesDir: File) {
259
267
  }
260
268
  if (currentId != markerId) {
261
269
  Log.w(TAG, "Boot marker sha256 mismatch — auto-reverting")
262
- revert()
270
+ revert(reason = "hash_mismatch")
263
271
  return
264
272
  }
265
273
 
@@ -290,7 +298,7 @@ class SlotManager(private val filesDir: File) {
290
298
  Log.i(TAG, "Boot guard: attempt $attempts/$MAX_BOOT_ATTEMPTS for this bundle")
291
299
  } catch (e: Exception) {
292
300
  Log.e(TAG, "checkBootGuard() failed — reverting to be safe", e)
293
- revert()
301
+ revert(reason = "boot_guard_error")
294
302
  }
295
303
  }
296
304
 
@@ -312,6 +320,10 @@ class SlotManager(private val filesDir: File) {
312
320
  state.put("bootGuardReverted", true)
313
321
  state.put("bootGuardRevertReason", rec.optString("reason", "boot_crash_guard"))
314
322
  state.put("bootGuardRevertedAt", rec.optLong("at", 0L))
323
+ // The bundle reverted AWAY from. JS joins it back to a bundle id
324
+ // through its install record so the rollback can be reported.
325
+ val sha = rec.optString("sha256", "")
326
+ if (sha.isNotEmpty()) state.put("bootGuardRevertedSha256", sha)
315
327
  }
316
328
  revertRecord.delete()
317
329
  }