@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.
- package/android/src/main/java/com/scalebun/replaysdk/tracking/InteractionTracker.kt +25 -25
- package/android/src/main/java/com/scalebun/rn/ota/SlotManager.kt +15 -3
- package/dist/scalebun.full.js +467 -255
- package/dist/scalebun.slim.js +466 -254
- package/ios/Capture/InteractionTracker.swift +8 -4
- package/ios/Ota/OtaSlotManager.swift +19 -5
- package/lib/commonjs/analytics/EventTracker.js +5 -5
- package/lib/commonjs/analytics/automaticEvents.js +3 -2
- package/lib/commonjs/core/constants/version.js +7 -2
- package/lib/commonjs/features/journey/ScaleBunDebugRoot.js +80 -103
- package/lib/commonjs/features/journey/interactionProtocol.js +47 -0
- package/lib/commonjs/features/journey/uiState.js +8 -1
- package/lib/commonjs/features/ota/OtaOrchestrator.js +174 -48
- package/lib/commonjs/features/ota/OtaTypes.js +4 -0
- package/lib/commonjs/features/ota/useOtaUpdate.js +11 -2
- package/lib/commonjs/features/session/JourneyEventPipeline.js +6 -5
- package/lib/commonjs/features/session/SessionManager.js +37 -38
- package/lib/commonjs/public/ScaleBunFacade.js +115 -2
- package/lib/module/analytics/EventTracker.js +5 -5
- package/lib/module/analytics/automaticEvents.js +3 -2
- package/lib/module/core/constants/version.js +7 -2
- package/lib/module/features/journey/ScaleBunDebugRoot.js +80 -103
- package/lib/module/features/journey/interactionProtocol.js +38 -0
- package/lib/module/features/journey/uiState.js +8 -1
- package/lib/module/features/ota/OtaOrchestrator.js +174 -48
- package/lib/module/features/ota/OtaTypes.js +1 -1
- package/lib/module/features/ota/useOtaUpdate.js +11 -2
- package/lib/module/features/session/JourneyEventPipeline.js +6 -5
- package/lib/module/features/session/SessionManager.js +37 -38
- package/lib/module/public/ScaleBunFacade.js +115 -2
- package/lib/typescript/analytics/EventTracker.d.ts +1 -1
- package/lib/typescript/analytics/automaticEvents.d.ts +3 -1
- package/lib/typescript/core/constants/version.d.ts +7 -2
- package/lib/typescript/features/journey/interactionProtocol.d.ts +21 -0
- package/lib/typescript/features/ota/OtaEventEmitter.d.ts +15 -1
- package/lib/typescript/features/ota/OtaOrchestrator.d.ts +22 -3
- package/lib/typescript/features/ota/OtaTypes.d.ts +29 -32
- package/lib/typescript/features/session/JourneyEventPipeline.d.ts +1 -0
- package/lib/typescript/features/session/SessionManager.d.ts +15 -10
- package/lib/typescript/public/ScaleBunFacade.d.ts +27 -0
- package/package.json +4 -3
- package/src/analytics/EventTracker.ts +5 -5
- package/src/analytics/automaticEvents.ts +4 -0
- package/src/core/constants/version.ts +7 -2
- package/src/features/journey/ScaleBunDebugRoot.tsx +96 -97
- package/src/features/journey/interactionProtocol.ts +65 -0
- package/src/features/journey/uiState.ts +9 -4
- package/src/features/ota/OtaEventEmitter.ts +12 -0
- package/src/features/ota/OtaOrchestrator.ts +209 -62
- package/src/features/ota/OtaTypes.ts +37 -39
- package/src/features/ota/useOtaUpdate.ts +11 -2
- package/src/features/session/JourneyEventPipeline.ts +7 -5
- package/src/features/session/SessionManager.ts +75 -38
- 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
|
|
38
|
-
private
|
|
39
|
-
|
|
40
|
-
|
|
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
|
-
|
|
141
|
-
|
|
142
|
-
//
|
|
143
|
-
|
|
144
|
-
|
|
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
|
-
|
|
279
|
-
|
|
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
|
}
|