@scalebun/react-native 1.4.0 → 1.6.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/README.md +33 -0
- package/android/src/main/java/com/scalebun/rn/ota/BsPatch.kt +182 -0
- package/android/src/main/java/com/scalebun/rn/ota/ScaleBunOtaModule.kt +88 -13
- package/android/src/oldarch/java/com/scalebun/rn/crash/ScaleBunCrashSpec.kt +16 -0
- package/bin/lib/androidCodemod.js +370 -0
- package/bin/scalebun.js +305 -13
- package/dist/scalebun.full.js +129 -97
- package/dist/scalebun.full.js.map +1 -1
- package/dist/scalebun.slim.js +129 -97
- package/dist/scalebun.slim.js.map +1 -1
- package/ios/Ota/BsPatch.swift +180 -0
- package/ios/Ota/OtaSlotManager.swift +59 -2
- package/ios/Ota/ScaleBunOtaModule.swift +89 -10
- package/lib/commonjs/analytics/EventTracker.js +21 -1
- package/lib/commonjs/analytics/EventTracker.js.map +1 -1
- package/lib/commonjs/core/constants/version.js +1 -1
- package/lib/commonjs/core/id/installationId.js +55 -0
- package/lib/commonjs/core/id/installationId.js.map +1 -0
- package/lib/commonjs/debug/bootstrap.js +12 -0
- package/lib/commonjs/debug/bootstrap.js.map +1 -1
- package/lib/commonjs/features/ota/OtaOrchestrator.js +2 -30
- package/lib/commonjs/features/ota/OtaOrchestrator.js.map +1 -1
- package/lib/commonjs/features/session/BackendSessionAdapter.js +8 -0
- package/lib/commonjs/features/session/BackendSessionAdapter.js.map +1 -1
- package/lib/module/analytics/EventTracker.js +21 -1
- package/lib/module/analytics/EventTracker.js.map +1 -1
- package/lib/module/core/constants/version.js +1 -1
- package/lib/module/core/id/installationId.js +50 -0
- package/lib/module/core/id/installationId.js.map +1 -0
- package/lib/module/debug/bootstrap.js +12 -0
- package/lib/module/debug/bootstrap.js.map +1 -1
- package/lib/module/features/ota/OtaOrchestrator.js +1 -29
- package/lib/module/features/ota/OtaOrchestrator.js.map +1 -1
- package/lib/module/features/session/BackendSessionAdapter.js +8 -0
- package/lib/module/features/session/BackendSessionAdapter.js.map +1 -1
- package/lib/typescript/analytics/EventTracker.d.ts.map +1 -1
- package/lib/typescript/core/constants/version.d.ts +1 -1
- package/lib/typescript/core/id/installationId.d.ts +20 -0
- package/lib/typescript/core/id/installationId.d.ts.map +1 -0
- package/lib/typescript/debug/bootstrap.d.ts +18 -0
- package/lib/typescript/debug/bootstrap.d.ts.map +1 -1
- package/lib/typescript/features/ota/OtaOrchestrator.d.ts.map +1 -1
- package/lib/typescript/features/session/BackendSessionAdapter.d.ts.map +1 -1
- package/package.json +3 -2
- package/src/analytics/EventTracker.ts +21 -1
- package/src/core/constants/version.ts +1 -1
- package/src/core/id/installationId.ts +52 -0
- package/src/debug/bootstrap.ts +36 -0
- package/src/features/ota/OtaOrchestrator.ts +1 -30
- package/src/features/session/BackendSessionAdapter.ts +8 -0
package/README.md
CHANGED
|
@@ -18,6 +18,39 @@ yarn add @scalebun/react-native
|
|
|
18
18
|
cd ios && pod install
|
|
19
19
|
```
|
|
20
20
|
|
|
21
|
+
To wire the AppDelegate for Direct APNs push (optional):
|
|
22
|
+
|
|
23
|
+
```sh
|
|
24
|
+
npx scalebun init ios # patch the AppDelegate (ObjC auto-patched; Swift prints steps)
|
|
25
|
+
npx scalebun init ios --check # verify hooks exist (exit 1 if missing)
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
### Android
|
|
29
|
+
|
|
30
|
+
OTA updates need one wiring step in `MainApplication` so a downloaded bundle
|
|
31
|
+
actually loads — without it an update installs, reports success, and the app
|
|
32
|
+
silently keeps running the bundle baked into the APK. Run:
|
|
33
|
+
|
|
34
|
+
```sh
|
|
35
|
+
npx scalebun init android # wire MainApplication for OTA bundle loading
|
|
36
|
+
npx scalebun init android --check # verify (exit 1 if missing)
|
|
37
|
+
npx scalebun init android --dry-run # preview changes without writing
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
This adds `ScaleBunOtaModule.getJSBundleFile(...)` in the form matching your
|
|
41
|
+
React Native version (Kotlin is auto-patched; Java prints manual instructions).
|
|
42
|
+
**Rebuild the native app afterwards** — a JS reload does not load the newly
|
|
43
|
+
wired native path.
|
|
44
|
+
|
|
45
|
+
For push on Android (optional): add `android/app/google-services.json`, apply
|
|
46
|
+
the Google Services Gradle plugin with `@react-native-firebase/messaging`, and
|
|
47
|
+
declare the `POST_NOTIFICATIONS` permission. Run `npx scalebun doctor` to see
|
|
48
|
+
exactly what's missing.
|
|
49
|
+
|
|
50
|
+
> Run `npx scalebun -h` to list every command — SDK setup (`doctor`, `init ios`,
|
|
51
|
+
> `init android`) plus the OTA publishing commands (`login`, `ota publish`,
|
|
52
|
+
> `rollout`, …).
|
|
53
|
+
|
|
21
54
|
### Optional peer dependencies
|
|
22
55
|
|
|
23
56
|
Some features activate only when their peer package is installed:
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
package com.scalebun.rn.ota
|
|
2
|
+
|
|
3
|
+
import java.io.ByteArrayOutputStream
|
|
4
|
+
import java.util.zip.Inflater
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* bspatch — reconstructs a bundle from a previous bundle plus a delta.
|
|
8
|
+
*
|
|
9
|
+
* WHAT WAS WRONG. `stagePatch` resolved `false` unconditionally on both
|
|
10
|
+
* platforms, so the whole delta pipeline was decorative: the server generated
|
|
11
|
+
* patches, the check response advertised them, the orchestrator asked for
|
|
12
|
+
* them, and the device always fell back to a full download. Nobody ever
|
|
13
|
+
* downloaded less than the entire bundle.
|
|
14
|
+
*
|
|
15
|
+
* FORMAT (produced by the backend's `bsdiff.ts`; all integers little-endian):
|
|
16
|
+
*
|
|
17
|
+
* 0 8 magic "SBDIFF01"
|
|
18
|
+
* 8 4 target size — the exact number of bytes this must produce
|
|
19
|
+
* 12 4 deflated control-block length
|
|
20
|
+
* 16 4 inflated control-block length
|
|
21
|
+
* 20 4 deflated diff-block length
|
|
22
|
+
* 24 4 inflated diff-block length
|
|
23
|
+
* 28 4 deflated extra-block length
|
|
24
|
+
* 32 4 inflated extra-block length
|
|
25
|
+
* 36 . deflate(control) ++ deflate(diff) ++ deflate(extra)
|
|
26
|
+
*
|
|
27
|
+
* Control is a run of (addLen, extraLen, seek) int32 triples. `addLen` bytes
|
|
28
|
+
* are read from the diff block and added, bytewise mod 256, to the source at
|
|
29
|
+
* the current read cursor; `extraLen` bytes are copied verbatim from the extra
|
|
30
|
+
* block; then the source cursor moves by the signed `seek`.
|
|
31
|
+
*
|
|
32
|
+
* WHY RAW DEFLATE. Android has `java.util.zip` and iOS has the Compression
|
|
33
|
+
* framework; neither ships zstd. On real Hermes bundles zstd-19 beat deflate-9
|
|
34
|
+
* by 0.3 percentage points (95.79% vs 95.51%) — not worth a native dependency
|
|
35
|
+
* on two platforms forever. See the backend module for the measurements.
|
|
36
|
+
*
|
|
37
|
+
* EVERY FIELD HERE IS UNTRUSTED. The patch arrives over the network, so a
|
|
38
|
+
* malformed or hostile one must fail cleanly rather than index out of bounds.
|
|
39
|
+
* Each run is bounds-checked against all four buffers before it is used, and
|
|
40
|
+
* the caller still verifies the SHA-256 of the result before anything is
|
|
41
|
+
* promoted — this routine producing the wrong bytes can never install them.
|
|
42
|
+
*/
|
|
43
|
+
internal object BsPatch {
|
|
44
|
+
|
|
45
|
+
private const val MAGIC = "SBDIFF01"
|
|
46
|
+
private const val HEADER_BYTES = 36
|
|
47
|
+
|
|
48
|
+
/** Refuse absurd allocations from a corrupt header (bundles are single-digit MB). */
|
|
49
|
+
private const val MAX_TARGET_BYTES = 256 * 1024 * 1024
|
|
50
|
+
|
|
51
|
+
class PatchFormatException(message: String) : Exception(message)
|
|
52
|
+
|
|
53
|
+
private fun u32(b: ByteArray, off: Int): Long =
|
|
54
|
+
(b[off].toLong() and 0xff) or
|
|
55
|
+
((b[off + 1].toLong() and 0xff) shl 8) or
|
|
56
|
+
((b[off + 2].toLong() and 0xff) shl 16) or
|
|
57
|
+
((b[off + 3].toLong() and 0xff) shl 24)
|
|
58
|
+
|
|
59
|
+
private fun i32(b: ByteArray, off: Int): Int =
|
|
60
|
+
(b[off].toInt() and 0xff) or
|
|
61
|
+
((b[off + 1].toInt() and 0xff) shl 8) or
|
|
62
|
+
((b[off + 2].toInt() and 0xff) shl 16) or
|
|
63
|
+
((b[off + 3].toInt() and 0xff) shl 24)
|
|
64
|
+
|
|
65
|
+
/** Raw DEFLATE (no zlib header) — matches Node's `deflateRawSync`. */
|
|
66
|
+
private fun inflateRaw(data: ByteArray, offset: Int, length: Int): ByteArray {
|
|
67
|
+
val inflater = Inflater(true)
|
|
68
|
+
try {
|
|
69
|
+
inflater.setInput(data, offset, length)
|
|
70
|
+
val out = ByteArrayOutputStream(maxOf(length * 4, 1024))
|
|
71
|
+
val buf = ByteArray(64 * 1024)
|
|
72
|
+
while (true) {
|
|
73
|
+
val n = inflater.inflate(buf)
|
|
74
|
+
if (n > 0) {
|
|
75
|
+
out.write(buf, 0, n)
|
|
76
|
+
continue
|
|
77
|
+
}
|
|
78
|
+
// ORDER MATTERS. A block that legitimately decodes to zero bytes
|
|
79
|
+
// — an empty extra block, which happens whenever a patch is one
|
|
80
|
+
// unbroken run — also reports needsInput() once the stream is
|
|
81
|
+
// consumed. Testing needsInput() first therefore rejected valid
|
|
82
|
+
// patches as truncated; `finished()` has to be asked first.
|
|
83
|
+
if (inflater.finished()) break
|
|
84
|
+
if (inflater.needsInput() || inflater.needsDictionary()) {
|
|
85
|
+
throw PatchFormatException("compressed block is truncated")
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
return out.toByteArray()
|
|
89
|
+
} finally {
|
|
90
|
+
inflater.end()
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Apply [patch] to [source], returning the reconstructed bundle.
|
|
96
|
+
*
|
|
97
|
+
* @throws PatchFormatException if the patch is malformed, truncated, or
|
|
98
|
+
* inconsistent with [source]. Callers treat this as "fall back to a full
|
|
99
|
+
* download", never as a fatal error.
|
|
100
|
+
*/
|
|
101
|
+
fun apply(source: ByteArray, patch: ByteArray): ByteArray {
|
|
102
|
+
if (patch.size < HEADER_BYTES) throw PatchFormatException("patch too short")
|
|
103
|
+
val magic = String(patch, 0, 8, Charsets.ISO_8859_1)
|
|
104
|
+
if (magic != MAGIC) throw PatchFormatException("bad patch magic '$magic'")
|
|
105
|
+
|
|
106
|
+
val targetSizeL = u32(patch, 8)
|
|
107
|
+
val cLen = u32(patch, 12)
|
|
108
|
+
val cRaw = u32(patch, 16)
|
|
109
|
+
val dLen = u32(patch, 20)
|
|
110
|
+
val dRaw = u32(patch, 24)
|
|
111
|
+
val eLen = u32(patch, 28)
|
|
112
|
+
val eRaw = u32(patch, 32)
|
|
113
|
+
|
|
114
|
+
if (targetSizeL > MAX_TARGET_BYTES) {
|
|
115
|
+
throw PatchFormatException("declared target size $targetSizeL is implausible")
|
|
116
|
+
}
|
|
117
|
+
if (cRaw > MAX_TARGET_BYTES || dRaw > MAX_TARGET_BYTES || eRaw > MAX_TARGET_BYTES) {
|
|
118
|
+
throw PatchFormatException("declared block size is implausible")
|
|
119
|
+
}
|
|
120
|
+
if (HEADER_BYTES + cLen + dLen + eLen != patch.size.toLong()) {
|
|
121
|
+
throw PatchFormatException("block lengths do not match patch size")
|
|
122
|
+
}
|
|
123
|
+
val targetSize = targetSizeL.toInt()
|
|
124
|
+
|
|
125
|
+
var off = HEADER_BYTES
|
|
126
|
+
val control = inflateRaw(patch, off, cLen.toInt()); off += cLen.toInt()
|
|
127
|
+
val diff = inflateRaw(patch, off, dLen.toInt()); off += dLen.toInt()
|
|
128
|
+
val extra = inflateRaw(patch, off, eLen.toInt())
|
|
129
|
+
|
|
130
|
+
// A block that inflates to a size other than the declared one is
|
|
131
|
+
// corrupt; iOS sizes its buffers from these numbers, so Android holds
|
|
132
|
+
// the format to the same contract rather than trusting the stream.
|
|
133
|
+
if (control.size.toLong() != cRaw) throw PatchFormatException("control block size mismatch")
|
|
134
|
+
if (diff.size.toLong() != dRaw) throw PatchFormatException("diff block size mismatch")
|
|
135
|
+
if (extra.size.toLong() != eRaw) throw PatchFormatException("extra block size mismatch")
|
|
136
|
+
|
|
137
|
+
if (control.size % 12 != 0) {
|
|
138
|
+
throw PatchFormatException("control block is not a whole number of triples")
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
val out = ByteArray(targetSize)
|
|
142
|
+
var newpos = 0
|
|
143
|
+
var oldpos = 0
|
|
144
|
+
var dpos = 0
|
|
145
|
+
var epos = 0
|
|
146
|
+
|
|
147
|
+
var c = 0
|
|
148
|
+
while (c < control.size) {
|
|
149
|
+
val addLen = i32(control, c)
|
|
150
|
+
val extraLen = i32(control, c + 4)
|
|
151
|
+
val seek = i32(control, c + 8)
|
|
152
|
+
c += 12
|
|
153
|
+
|
|
154
|
+
if (addLen < 0 || extraLen < 0) throw PatchFormatException("negative run length")
|
|
155
|
+
if (newpos + addLen > targetSize) throw PatchFormatException("add run overruns target")
|
|
156
|
+
if (dpos + addLen > diff.size) throw PatchFormatException("add run overruns diff block")
|
|
157
|
+
if (oldpos < 0 || oldpos + addLen > source.size) {
|
|
158
|
+
throw PatchFormatException("add run overruns source")
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
for (i in 0 until addLen) {
|
|
162
|
+
out[newpos + i] = ((diff[dpos + i].toInt() + source[oldpos + i].toInt()) and 0xff).toByte()
|
|
163
|
+
}
|
|
164
|
+
newpos += addLen
|
|
165
|
+
oldpos += addLen
|
|
166
|
+
dpos += addLen
|
|
167
|
+
|
|
168
|
+
if (newpos + extraLen > targetSize) throw PatchFormatException("extra run overruns target")
|
|
169
|
+
if (epos + extraLen > extra.size) throw PatchFormatException("extra run overruns extra block")
|
|
170
|
+
System.arraycopy(extra, epos, out, newpos, extraLen)
|
|
171
|
+
newpos += extraLen
|
|
172
|
+
epos += extraLen
|
|
173
|
+
|
|
174
|
+
oldpos += seek
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
if (newpos != targetSize) {
|
|
178
|
+
throw PatchFormatException("patch produced $newpos bytes, declared $targetSize")
|
|
179
|
+
}
|
|
180
|
+
return out
|
|
181
|
+
}
|
|
182
|
+
}
|
|
@@ -191,20 +191,21 @@ class ScaleBunOtaModule(reactContext: ReactApplicationContext) :
|
|
|
191
191
|
}
|
|
192
192
|
|
|
193
193
|
/**
|
|
194
|
-
* Binary-patch staging (S7-NAT-1)
|
|
194
|
+
* Binary-patch staging (S7-NAT-1).
|
|
195
195
|
*
|
|
196
|
-
*
|
|
197
|
-
*
|
|
198
|
-
*
|
|
199
|
-
*
|
|
200
|
-
*
|
|
201
|
-
*
|
|
196
|
+
* Downloads a delta, reconstructs the full bundle from the currently
|
|
197
|
+
* installed one, and hands the result to the ordinary staging path — so a
|
|
198
|
+
* patched bundle passes exactly the same SHA-256 and slot-promotion checks
|
|
199
|
+
* as a fully downloaded one. [expectedSha256] is the hash of the COMPLETE
|
|
200
|
+
* bundle, which is what makes a bad patch unable to install: reconstruct
|
|
201
|
+
* the wrong bytes and staging rejects them.
|
|
202
202
|
*
|
|
203
|
-
*
|
|
204
|
-
*
|
|
205
|
-
*
|
|
206
|
-
*
|
|
207
|
-
*
|
|
203
|
+
* Resolves `false` — never rejects — for every "cannot patch" case, because
|
|
204
|
+
* `src/specs/NativeScaleBunOta.ts` defines false as "patching unavailable,
|
|
205
|
+
* fall back to a full download". The update still lands, just larger. The
|
|
206
|
+
* cases are: no OTA bundle installed yet (nothing to patch against — the
|
|
207
|
+
* first install is always full), a patch that does not match its own
|
|
208
|
+
* advertised hash, and a malformed patch.
|
|
208
209
|
*/
|
|
209
210
|
@ReactMethod
|
|
210
211
|
override fun stagePatch(
|
|
@@ -213,7 +214,81 @@ class ScaleBunOtaModule(reactContext: ReactApplicationContext) :
|
|
|
213
214
|
expectedSha256: String,
|
|
214
215
|
promise: Promise,
|
|
215
216
|
) {
|
|
216
|
-
|
|
217
|
+
var downloadedPatch: File? = null
|
|
218
|
+
var rebuilt: File? = null
|
|
219
|
+
try {
|
|
220
|
+
// Patching is relative to the bundle already on disk. With no OTA
|
|
221
|
+
// bundle installed the source would be the APK's built-in asset,
|
|
222
|
+
// which the server did not diff against — so there is nothing valid
|
|
223
|
+
// to apply and a full download is the correct path.
|
|
224
|
+
val currentBundle = slotManager.currentBundlePath()
|
|
225
|
+
if (currentBundle == null || !currentBundle.exists()) {
|
|
226
|
+
android.util.Log.i(NAME, "stagePatch: no current OTA bundle to patch — full download")
|
|
227
|
+
promise.resolve(false)
|
|
228
|
+
return
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
val temp = File(reactApplicationContext.cacheDir, "scalebun_ota_patch_${System.nanoTime()}")
|
|
232
|
+
downloadedPatch = temp
|
|
233
|
+
if (!BundleDownloader.download(patchUrl, temp, ::emitProgress)) {
|
|
234
|
+
promise.resolve(false)
|
|
235
|
+
return
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
val patchBytes = temp.readBytes()
|
|
239
|
+
|
|
240
|
+
// Verify the patch against its own hash before parsing it. This is
|
|
241
|
+
// cheap and keeps obviously-corrupt input away from the parser.
|
|
242
|
+
val actualPatchSha = sha256Hex(patchBytes)
|
|
243
|
+
if (!actualPatchSha.equals(patchSha256, ignoreCase = true)) {
|
|
244
|
+
android.util.Log.w(NAME, "stagePatch: patch hash mismatch — full download")
|
|
245
|
+
promise.resolve(false)
|
|
246
|
+
return
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
val sourceBytes = currentBundle.readBytes()
|
|
250
|
+
val rebuiltBytes = try {
|
|
251
|
+
BsPatch.apply(sourceBytes, patchBytes)
|
|
252
|
+
} catch (e: BsPatch.PatchFormatException) {
|
|
253
|
+
android.util.Log.w(NAME, "stagePatch: ${e.message} — full download")
|
|
254
|
+
promise.resolve(false)
|
|
255
|
+
return
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
val out = File(reactApplicationContext.cacheDir, "scalebun_ota_rebuilt_${System.nanoTime()}")
|
|
259
|
+
rebuilt = out
|
|
260
|
+
out.writeBytes(rebuiltBytes)
|
|
261
|
+
|
|
262
|
+
val meta = JSONObject()
|
|
263
|
+
meta.put("sha256", expectedSha256)
|
|
264
|
+
meta.put("installedAt", System.currentTimeMillis())
|
|
265
|
+
meta.put("patched", true)
|
|
266
|
+
|
|
267
|
+
// SlotManager re-hashes and compares against the full-bundle hash;
|
|
268
|
+
// a mis-applied patch cannot get past this.
|
|
269
|
+
val success = slotManager.stage(out, expectedSha256, meta)
|
|
270
|
+
if (!success) {
|
|
271
|
+
android.util.Log.w(NAME, "stagePatch: rebuilt bundle failed verification — full download")
|
|
272
|
+
}
|
|
273
|
+
promise.resolve(success)
|
|
274
|
+
} catch (e: Exception) {
|
|
275
|
+
// Any unexpected failure is still just "no patch" — the caller has
|
|
276
|
+
// a working full-download path and a rejected promise would turn a
|
|
277
|
+
// recoverable situation into a failed update.
|
|
278
|
+
android.util.Log.w(NAME, "stagePatch: ${e.message} — full download")
|
|
279
|
+
promise.resolve(false)
|
|
280
|
+
} finally {
|
|
281
|
+
downloadedPatch?.delete()
|
|
282
|
+
rebuilt?.delete()
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
private fun sha256Hex(bytes: ByteArray): String {
|
|
287
|
+
val digest = java.security.MessageDigest.getInstance("SHA-256")
|
|
288
|
+
val hash = digest.digest(bytes)
|
|
289
|
+
val sb = StringBuilder(hash.size * 2)
|
|
290
|
+
for (b in hash) sb.append(String.format("%02x", b))
|
|
291
|
+
return sb.toString()
|
|
217
292
|
}
|
|
218
293
|
|
|
219
294
|
@ReactMethod
|
|
@@ -18,4 +18,20 @@ abstract class ScaleBunCrashSpec(reactContext: ReactApplicationContext) :
|
|
|
18
18
|
abstract fun install(promise: Promise)
|
|
19
19
|
abstract fun crashNow(promise: Promise)
|
|
20
20
|
abstract fun drainPendingCrash(promise: Promise)
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* These two were added to the JS spec and to [ScaleBunCrashModule] without
|
|
24
|
+
* being added here, and the omission is invisible on the New Architecture:
|
|
25
|
+
* there the base is GENERATED from the JS spec, so it grew the methods
|
|
26
|
+
* automatically. On the Old Architecture this file IS the base and it has
|
|
27
|
+
* to be edited by hand — so `ScaleBunCrashModule` was overriding two
|
|
28
|
+
* methods that did not exist, and the SDK failed to compile for every
|
|
29
|
+
* old-arch consumer with "'setActiveSessionId' overrides nothing".
|
|
30
|
+
*
|
|
31
|
+
* Any change to `src/specs/NativeScaleBunCrash.ts` has to be mirrored here.
|
|
32
|
+
* That is the cost of the two-base design described above; the alternative
|
|
33
|
+
* is that old-arch apps silently stop building.
|
|
34
|
+
*/
|
|
35
|
+
abstract fun setActiveSessionId(sessionId: String?, promise: Promise)
|
|
36
|
+
abstract fun clearPendingCrash(crashId: String?, promise: Promise)
|
|
21
37
|
}
|