@scalebun/react-native 1.10.6 → 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.
- package/android/src/main/java/com/scalebun/replaysdk/tracking/InteractionTracker.kt +25 -24
- package/android/src/main/java/com/scalebun/rn/ota/ScaleBunOtaModule.kt +75 -0
- package/android/src/oldarch/java/com/scalebun/rn/ota/ScaleBunOtaSpec.kt +12 -0
- package/dist/scalebun.full.js +653 -223
- package/dist/scalebun.slim.js +652 -222
- package/ios/Capture/InteractionTracker.swift +8 -4
- package/ios/Ota/ScaleBunOtaBridge.mm +6 -0
- package/ios/Ota/ScaleBunOtaModule.swift +67 -0
- package/lib/commonjs/analytics/EventTracker.js +5 -5
- package/lib/commonjs/analytics/automaticEvents.js +3 -2
- package/lib/commonjs/core/config/schema.js +34 -1
- package/lib/commonjs/core/constants/version.js +7 -2
- package/lib/commonjs/features/journey/ScaleBunDebugRoot.js +80 -79
- package/lib/commonjs/features/journey/interactionProtocol.js +47 -0
- package/lib/commonjs/features/journey/uiState.js +94 -0
- package/lib/commonjs/features/ota/crypto/builtinVerifier.js +248 -0
- package/lib/commonjs/features/ota/crypto/loadEd25519.js +40 -0
- package/lib/commonjs/features/ota/crypto/loadSha512.js +40 -0
- package/lib/commonjs/features/ota/crypto/nativeVerifier.js +121 -0
- package/lib/commonjs/features/ota/signature.js +87 -27
- package/lib/commonjs/features/session/JourneyEventPipeline.js +6 -5
- package/lib/commonjs/features/session/SessionManager.js +37 -38
- package/lib/commonjs/metro/serializerCompose.js +32 -0
- package/lib/commonjs/public/ScaleBunFacade.js +74 -11
- package/lib/module/analytics/EventTracker.js +5 -5
- package/lib/module/analytics/automaticEvents.js +3 -2
- package/lib/module/core/config/schema.js +34 -1
- package/lib/module/core/constants/version.js +7 -2
- package/lib/module/features/journey/ScaleBunDebugRoot.js +80 -79
- package/lib/module/features/journey/interactionProtocol.js +38 -0
- package/lib/module/features/journey/uiState.js +86 -0
- package/lib/module/features/ota/crypto/builtinVerifier.js +240 -0
- package/lib/module/features/ota/crypto/loadEd25519.js +34 -0
- package/lib/module/features/ota/crypto/loadSha512.js +34 -0
- package/lib/module/features/ota/crypto/nativeVerifier.js +113 -0
- package/lib/module/features/ota/signature.js +87 -27
- package/lib/module/features/session/JourneyEventPipeline.js +6 -5
- package/lib/module/features/session/SessionManager.js +37 -38
- package/lib/module/metro/serializerCompose.js +32 -0
- package/lib/module/public/ScaleBunFacade.js +74 -11
- package/lib/typescript/analytics/EventTracker.d.ts +1 -1
- package/lib/typescript/analytics/automaticEvents.d.ts +3 -1
- package/lib/typescript/core/config/schema.d.ts +2 -0
- package/lib/typescript/core/constants/version.d.ts +7 -2
- package/lib/typescript/features/journey/interactionProtocol.d.ts +21 -0
- package/lib/typescript/features/journey/uiState.d.ts +53 -0
- package/lib/typescript/features/ota/OtaTypes.d.ts +50 -0
- package/lib/typescript/features/ota/crypto/builtinVerifier.d.ts +53 -0
- package/lib/typescript/features/ota/crypto/loadEd25519.d.ts +30 -0
- package/lib/typescript/features/ota/crypto/loadSha512.d.ts +15 -0
- package/lib/typescript/features/ota/crypto/nativeVerifier.d.ts +35 -0
- package/lib/typescript/features/ota/signature.d.ts +22 -7
- 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 +35 -6
- package/lib/typescript/specs/NativeScaleBunOta.d.ts +23 -0
- package/package.json +19 -3
- package/src/analytics/EventTracker.ts +5 -5
- package/src/analytics/automaticEvents.ts +4 -0
- package/src/core/config/schema.ts +30 -3
- package/src/core/constants/version.ts +7 -2
- package/src/features/journey/ScaleBunDebugRoot.tsx +96 -75
- package/src/features/journey/interactionProtocol.ts +65 -0
- package/src/features/journey/uiState.ts +89 -0
- package/src/features/ota/OtaTypes.ts +51 -0
- package/src/features/ota/crypto/builtinVerifier.ts +257 -0
- package/src/features/ota/crypto/loadEd25519.ts +41 -0
- package/src/features/ota/crypto/loadSha512.ts +35 -0
- package/src/features/ota/crypto/nativeVerifier.ts +117 -0
- package/src/features/ota/signature.ts +108 -25
- package/src/features/session/JourneyEventPipeline.ts +7 -5
- package/src/features/session/SessionManager.ts +75 -38
- package/src/metro/serializerCompose.ts +38 -2
- package/src/public/ScaleBunFacade.ts +87 -13
- package/src/specs/NativeScaleBunOta.ts +24 -0
|
@@ -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())
|
|
@@ -276,8 +277,8 @@ class InteractionTracker(private val reactContext: ReactApplicationContext) {
|
|
|
276
277
|
fun reset() {
|
|
277
278
|
lastInteractionMs = 0
|
|
278
279
|
lastEmitTs = 0
|
|
279
|
-
|
|
280
|
-
|
|
280
|
+
touchInteractionId = ""
|
|
281
|
+
lastEmittedInteractionId = ""
|
|
281
282
|
touchDownTs = 0
|
|
282
283
|
}
|
|
283
284
|
}
|
|
@@ -283,6 +283,81 @@ class ScaleBunOtaModule(reactContext: ReactApplicationContext) :
|
|
|
283
283
|
}
|
|
284
284
|
}
|
|
285
285
|
|
|
286
|
+
/**
|
|
287
|
+
* Verify a detached ed25519 signature with PLATFORM crypto.
|
|
288
|
+
*
|
|
289
|
+
* Why native: the JS verifier lives inside the very bundle it protects, so
|
|
290
|
+
* one malicious bundle can neuter it for every update after. Platform
|
|
291
|
+
* crypto sits outside the bundle's reach. It also drops the runtime need
|
|
292
|
+
* for the optional @noble peers on Android 13+.
|
|
293
|
+
*
|
|
294
|
+
* Contract (mirrors the JS spec doc):
|
|
295
|
+
* - inputs are lowercase hex, pre-validated by the JS caller
|
|
296
|
+
* - the message is the RAW 32 BYTES of the bundle SHA-256 — the CLI
|
|
297
|
+
* signs the digest bytes, not their hex text
|
|
298
|
+
* - resolves 'valid' / 'invalid' / 'unavailable'; never rejects for a
|
|
299
|
+
* wrong signature, and any unexpected throw resolves 'unavailable' so
|
|
300
|
+
* machinery failure is distinguishable from a forged bundle
|
|
301
|
+
*
|
|
302
|
+
* Availability: `Signature.getInstance("Ed25519")` ships in Android's
|
|
303
|
+
* conscrypt from API 33. Below that the JCA lookup throws
|
|
304
|
+
* NoSuchAlgorithmException — reported as 'unavailable', and JS falls back
|
|
305
|
+
* to its own verifier. Checked by attempting the lookup rather than by
|
|
306
|
+
* SDK_INT, because the algorithm's presence is the fact that matters.
|
|
307
|
+
*/
|
|
308
|
+
@ReactMethod
|
|
309
|
+
override fun verifyEd25519(
|
|
310
|
+
messageHex: String,
|
|
311
|
+
signatureHex: String,
|
|
312
|
+
publicKeyHex: String,
|
|
313
|
+
promise: Promise,
|
|
314
|
+
) {
|
|
315
|
+
try {
|
|
316
|
+
val message = hexToBytes(messageHex)
|
|
317
|
+
val signature = hexToBytes(signatureHex)
|
|
318
|
+
val rawKey = hexToBytes(publicKeyHex)
|
|
319
|
+
if (message == null || signature == null || rawKey == null || rawKey.size != 32) {
|
|
320
|
+
// Malformed input from our own caller is not a forgery verdict.
|
|
321
|
+
promise.resolve("unavailable")
|
|
322
|
+
return
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
// JCA takes X.509 SubjectPublicKeyInfo, not raw key bytes: the
|
|
326
|
+
// 12-byte ed25519 SPKI prefix (OID 1.3.101.112) + the raw 32.
|
|
327
|
+
val spkiPrefix = byteArrayOf(
|
|
328
|
+
0x30, 0x2a, 0x30, 0x05, 0x06, 0x03, 0x2b, 0x65, 0x70, 0x03, 0x21, 0x00,
|
|
329
|
+
)
|
|
330
|
+
val verifier = try {
|
|
331
|
+
java.security.Signature.getInstance("Ed25519")
|
|
332
|
+
} catch (_: java.security.NoSuchAlgorithmException) {
|
|
333
|
+
promise.resolve("unavailable") // API < 33
|
|
334
|
+
return
|
|
335
|
+
}
|
|
336
|
+
val key = java.security.KeyFactory.getInstance("Ed25519")
|
|
337
|
+
.generatePublic(java.security.spec.X509EncodedKeySpec(spkiPrefix + rawKey))
|
|
338
|
+
|
|
339
|
+
verifier.initVerify(key)
|
|
340
|
+
verifier.update(message)
|
|
341
|
+
promise.resolve(if (verifier.verify(signature)) "valid" else "invalid")
|
|
342
|
+
} catch (_: Throwable) {
|
|
343
|
+
// Fail toward "cannot check", never toward "checked out fine".
|
|
344
|
+
promise.resolve("unavailable")
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
/** Hex → bytes; null for odd length or a non-hex character. */
|
|
349
|
+
private fun hexToBytes(hex: String): ByteArray? {
|
|
350
|
+
if (hex.isEmpty() || hex.length % 2 != 0) return null
|
|
351
|
+
val out = ByteArray(hex.length / 2)
|
|
352
|
+
for (i in out.indices) {
|
|
353
|
+
val hi = Character.digit(hex[i * 2], 16)
|
|
354
|
+
val lo = Character.digit(hex[i * 2 + 1], 16)
|
|
355
|
+
if (hi < 0 || lo < 0) return null
|
|
356
|
+
out[i] = ((hi shl 4) or lo).toByte()
|
|
357
|
+
}
|
|
358
|
+
return out
|
|
359
|
+
}
|
|
360
|
+
|
|
286
361
|
private fun sha256Hex(bytes: ByteArray): String {
|
|
287
362
|
val digest = java.security.MessageDigest.getInstance("SHA-256")
|
|
288
363
|
val hash = digest.digest(bytes)
|
|
@@ -28,4 +28,16 @@ abstract class ScaleBunOtaSpec(reactContext: ReactApplicationContext) :
|
|
|
28
28
|
abstract fun markHealthy()
|
|
29
29
|
abstract fun revertToPrevious(promise: Promise)
|
|
30
30
|
abstract fun restartApp()
|
|
31
|
+
|
|
32
|
+
// Hand-maintained: this file does NOT regenerate from the JS spec. A method
|
|
33
|
+
// added to NativeScaleBunOta.ts that is missing here compiles green on New
|
|
34
|
+
// Arch and breaks EVERY old-arch consumer's build (the ScaleBunCrashModule
|
|
35
|
+
// incident). Non-null Strings to match the codegen base's signature, so the
|
|
36
|
+
// one concrete override compiles against either architecture.
|
|
37
|
+
abstract fun verifyEd25519(
|
|
38
|
+
messageHex: String,
|
|
39
|
+
signatureHex: String,
|
|
40
|
+
publicKeyHex: String,
|
|
41
|
+
promise: Promise,
|
|
42
|
+
)
|
|
31
43
|
}
|