@scalebun/react-native 1.11.0 → 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 +0 -1
- package/android/src/main/java/com/scalebun/rn/ota/SlotManager.kt +15 -3
- package/dist/scalebun.full.js +232 -69
- package/dist/scalebun.slim.js +232 -69
- package/ios/Ota/OtaSlotManager.swift +19 -5
- package/lib/commonjs/core/constants/version.js +1 -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/public/ScaleBunFacade.js +115 -2
- package/lib/module/core/constants/version.js +1 -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/public/ScaleBunFacade.js +115 -2
- package/lib/typescript/core/constants/version.d.ts +1 -1
- 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/public/ScaleBunFacade.d.ts +27 -0
- package/package.json +2 -2
- package/src/core/constants/version.ts +1 -1
- 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/public/ScaleBunFacade.ts +127 -3
package/dist/scalebun.slim.js
CHANGED
|
@@ -600,7 +600,7 @@ var SDK_VERSION;
|
|
|
600
600
|
var init_version = __esm({
|
|
601
601
|
"lib/module/core/constants/version.js"() {
|
|
602
602
|
"use strict";
|
|
603
|
-
SDK_VERSION = "1.11.
|
|
603
|
+
SDK_VERSION = "1.11.1";
|
|
604
604
|
}
|
|
605
605
|
});
|
|
606
606
|
|
|
@@ -5874,6 +5874,42 @@ var init_bootstrap = __esm({
|
|
|
5874
5874
|
}
|
|
5875
5875
|
});
|
|
5876
5876
|
|
|
5877
|
+
// lib/module/core/lifecycle/appLifecycle.js
|
|
5878
|
+
var appLifecycle_exports = {};
|
|
5879
|
+
__export(appLifecycle_exports, {
|
|
5880
|
+
appLifecycle: () => appLifecycle
|
|
5881
|
+
});
|
|
5882
|
+
var import_react_native6, AppLifecycle, appLifecycle;
|
|
5883
|
+
var init_appLifecycle = __esm({
|
|
5884
|
+
"lib/module/core/lifecycle/appLifecycle.js"() {
|
|
5885
|
+
"use strict";
|
|
5886
|
+
import_react_native6 = require("react-native");
|
|
5887
|
+
init_internalLogger();
|
|
5888
|
+
AppLifecycle = class {
|
|
5889
|
+
listeners = [];
|
|
5890
|
+
currentState = import_react_native6.AppState.currentState;
|
|
5891
|
+
constructor() {
|
|
5892
|
+
import_react_native6.AppState.addEventListener("change", this.handleStateChange);
|
|
5893
|
+
}
|
|
5894
|
+
handleStateChange = (nextState) => {
|
|
5895
|
+
__DEV__ && logger.debug(`App state changed: ${this.currentState} -> ${nextState}`);
|
|
5896
|
+
this.currentState = nextState;
|
|
5897
|
+
this.listeners.forEach((l) => l(nextState));
|
|
5898
|
+
};
|
|
5899
|
+
addListener(listener) {
|
|
5900
|
+
this.listeners.push(listener);
|
|
5901
|
+
}
|
|
5902
|
+
removeListener(listener) {
|
|
5903
|
+
this.listeners = this.listeners.filter((l) => l !== listener);
|
|
5904
|
+
}
|
|
5905
|
+
getCurrentState() {
|
|
5906
|
+
return this.currentState;
|
|
5907
|
+
}
|
|
5908
|
+
};
|
|
5909
|
+
appLifecycle = new AppLifecycle();
|
|
5910
|
+
}
|
|
5911
|
+
});
|
|
5912
|
+
|
|
5877
5913
|
// lib/module/features/crash/CrashReporter.js
|
|
5878
5914
|
function getCrashReporter() {
|
|
5879
5915
|
if (!_reporter) _reporter = new CrashReporter();
|
|
@@ -7472,6 +7508,11 @@ async function deliverOtaEvents(params) {
|
|
|
7472
7508
|
kind: "ota_event",
|
|
7473
7509
|
type: e.type,
|
|
7474
7510
|
bundleId: e.bundleId,
|
|
7511
|
+
// Without this the backend stored a null releaseId on every row it ingested,
|
|
7512
|
+
// while serving the release id on every check — so the delivery funnel could
|
|
7513
|
+
// only ever be grouped by bundle, and a bundle re-promoted under a second
|
|
7514
|
+
// release merged the two into one indistinguishable series.
|
|
7515
|
+
releaseId: e.releaseId,
|
|
7475
7516
|
installationId: params.installationId,
|
|
7476
7517
|
// OTA bundles are compiled per platform, so this genuinely is ios|android. Narrowed via
|
|
7477
7518
|
// resolveMobileOS so a non-mobile RN target is skipped rather than served an Android bundle.
|
|
@@ -7573,20 +7614,31 @@ var init_OtaOrchestrator = __esm({
|
|
|
7573
7614
|
this.bootGuardConfig = config ?? {};
|
|
7574
7615
|
this.signatureConfig = config?.signature;
|
|
7575
7616
|
__DEV__ && logger.debug(`[OTA] Orchestrator initialized (RN ${this.environment.rnVersionString ?? "unknown"}${this.environment.bridgeless ? ", bridgeless" : ""}${this.environment.hermes ? `, Hermes HBC v${this.environment.hermesBytecodeVersion ?? "?"}` : ""})`);
|
|
7576
|
-
this.
|
|
7617
|
+
const slotState = this.readSlotState();
|
|
7618
|
+
this.hydrateCurrentBundleFromSlots(slotState);
|
|
7577
7619
|
void prefetchDeviceCountry();
|
|
7620
|
+
this.checkBootGuardRecovery(slotState);
|
|
7578
7621
|
this.verifyRunningBundleIdentity();
|
|
7579
|
-
this.checkBootGuardRecovery();
|
|
7580
7622
|
});
|
|
7581
7623
|
}
|
|
7582
7624
|
/**
|
|
7583
7625
|
* Read the active slot back into `currentBundle` so the next check reports
|
|
7584
7626
|
* what this device is genuinely running.
|
|
7585
7627
|
*/
|
|
7586
|
-
|
|
7587
|
-
|
|
7628
|
+
/**
|
|
7629
|
+
* Parse the native slot state once. Returns null when the module is absent or
|
|
7630
|
+
* the payload is unreadable — every caller treats that as "factory bundle".
|
|
7631
|
+
*/
|
|
7632
|
+
readSlotState() {
|
|
7633
|
+
if (!NativeScaleBunOta_default) return null;
|
|
7634
|
+
try {
|
|
7635
|
+
return JSON.parse(NativeScaleBunOta_default.getSlotState());
|
|
7636
|
+
} catch {
|
|
7637
|
+
return null;
|
|
7638
|
+
}
|
|
7639
|
+
}
|
|
7640
|
+
hydrateCurrentBundleFromSlots(state) {
|
|
7588
7641
|
try {
|
|
7589
|
-
const state = JSON.parse(NativeScaleBunOta_default.getSlotState());
|
|
7590
7642
|
const current = state?.current;
|
|
7591
7643
|
if (!current?.sha256) return;
|
|
7592
7644
|
const record4 = this.readInstallRecord();
|
|
@@ -7594,6 +7646,7 @@ var init_OtaOrchestrator = __esm({
|
|
|
7594
7646
|
this.currentBundle = {
|
|
7595
7647
|
id: record4.bundleId,
|
|
7596
7648
|
version: record4.version,
|
|
7649
|
+
releaseId: record4.releaseId ?? void 0,
|
|
7597
7650
|
sha256: record4.sha256
|
|
7598
7651
|
};
|
|
7599
7652
|
__DEV__ && logger.debug(`[OTA] Running bundle v${record4.version} (${record4.bundleId})`);
|
|
@@ -7624,29 +7677,23 @@ var init_OtaOrchestrator = __esm({
|
|
|
7624
7677
|
verifyRunningBundleIdentity() {
|
|
7625
7678
|
if (!this.currentBundle) return;
|
|
7626
7679
|
const running = readRunningBundleMarker();
|
|
7627
|
-
const
|
|
7628
|
-
if (
|
|
7680
|
+
const record4 = this.readInstallRecord();
|
|
7681
|
+
if (!record4) return;
|
|
7682
|
+
if (record4.sha256 !== this.currentBundle.sha256) {
|
|
7629
7683
|
this.clearInstallExpectation();
|
|
7630
7684
|
return;
|
|
7631
7685
|
}
|
|
7632
|
-
if (
|
|
7633
|
-
|
|
7634
|
-
|
|
7635
|
-
|
|
7636
|
-
return;
|
|
7637
|
-
}
|
|
7638
|
-
logger.error(`[OTA] INSTALL DID NOT TAKE EFFECT \u2014 bundle ${this.currentBundle.id} was installed and carries a known identity marker, but the running bundle reports ${running ?? "no marker at all"}. The app is executing different code than the slot manager believes. Check that the host app resolves the OTA bundle path at launch (see the ScaleBunOta integration for your React Native version).`);
|
|
7639
|
-
otaEventEmitter.emitSimple("APPLY_FAILED", this.currentBundle.id, {
|
|
7640
|
-
error: `install_not_effective \u2014 expected ${expected.identityToken}, running ${running ?? "none"}`
|
|
7641
|
-
});
|
|
7686
|
+
if (!record4.identityToken) return;
|
|
7687
|
+
if (running === record4.identityToken) {
|
|
7688
|
+
__DEV__ && logger.debug("[OTA] Install verified \u2014 running bundle matches what was installed.");
|
|
7689
|
+
this.retireIdentityToken(record4);
|
|
7642
7690
|
return;
|
|
7643
7691
|
}
|
|
7644
|
-
|
|
7645
|
-
|
|
7646
|
-
|
|
7647
|
-
|
|
7648
|
-
|
|
7649
|
-
}
|
|
7692
|
+
logger.error(`[OTA] INSTALL DID NOT TAKE EFFECT \u2014 bundle ${this.currentBundle.id} was installed and carries a known identity marker, but the running bundle reports ${running ?? "no marker at all"}. The app is executing different code than the slot manager believes. Check that the host app resolves the OTA bundle path at launch (see the ScaleBunOta integration for your React Native version).`);
|
|
7693
|
+
otaEventEmitter.emitSimple("APPLY_FAILED", this.currentBundle.id, {
|
|
7694
|
+
releaseId: this.currentBundle.releaseId,
|
|
7695
|
+
error: `install_not_effective \u2014 expected ${record4.identityToken}, running ${running ?? "none"}`
|
|
7696
|
+
});
|
|
7650
7697
|
}
|
|
7651
7698
|
// ── Install expectation ────────────────────────────────────────────────────
|
|
7652
7699
|
// Written just before the restart that activates a bundle, read on the next
|
|
@@ -7658,6 +7705,11 @@ var init_OtaOrchestrator = __esm({
|
|
|
7658
7705
|
this.storage().set(_OtaOrchestrator.INSTALL_EXPECTATION_KEY, JSON.stringify({
|
|
7659
7706
|
bundleId: bundle.id,
|
|
7660
7707
|
version: bundle.version,
|
|
7708
|
+
// Carried so telemetry emitted on a LATER launch (a boot-guard
|
|
7709
|
+
// rollback, an ineffective install) can still be attributed to the
|
|
7710
|
+
// release, not merely the bundle. The check response is long gone by
|
|
7711
|
+
// then; this record is the only thing that remembers.
|
|
7712
|
+
releaseId: bundle.releaseId ?? null,
|
|
7661
7713
|
// The join key back to the native slot, which records sha256 and
|
|
7662
7714
|
// nothing else identifying.
|
|
7663
7715
|
sha256: bundle.sha256,
|
|
@@ -7676,12 +7728,26 @@ var init_OtaOrchestrator = __esm({
|
|
|
7676
7728
|
return null;
|
|
7677
7729
|
}
|
|
7678
7730
|
}
|
|
7679
|
-
|
|
7680
|
-
|
|
7681
|
-
|
|
7682
|
-
|
|
7683
|
-
|
|
7684
|
-
|
|
7731
|
+
/**
|
|
7732
|
+
* Drop the identity token once the install has been proven, keeping the rest
|
|
7733
|
+
* of the record.
|
|
7734
|
+
*
|
|
7735
|
+
* The record does two jobs: it proves an install took effect (once), and it
|
|
7736
|
+
* maps the native slot's sha256 back to a bundle id (for the life of that
|
|
7737
|
+
* bundle). Only the first job is finished after a successful verification, so
|
|
7738
|
+
* only the token is retired.
|
|
7739
|
+
*/
|
|
7740
|
+
retireIdentityToken(record4) {
|
|
7741
|
+
try {
|
|
7742
|
+
this.storage().set(_OtaOrchestrator.INSTALL_EXPECTATION_KEY, JSON.stringify({
|
|
7743
|
+
bundleId: record4.bundleId,
|
|
7744
|
+
version: record4.version,
|
|
7745
|
+
releaseId: record4.releaseId ?? null,
|
|
7746
|
+
sha256: record4.sha256,
|
|
7747
|
+
identityToken: null
|
|
7748
|
+
}));
|
|
7749
|
+
} catch {
|
|
7750
|
+
}
|
|
7685
7751
|
}
|
|
7686
7752
|
clearInstallExpectation() {
|
|
7687
7753
|
try {
|
|
@@ -7698,17 +7764,24 @@ var init_OtaOrchestrator = __esm({
|
|
|
7698
7764
|
* If getSlotState() shows bootMarkerPresent=false but we have a 'previous' slot
|
|
7699
7765
|
* and no 'current' OTA bundle, the native layer already reverted.
|
|
7700
7766
|
*/
|
|
7701
|
-
checkBootGuardRecovery() {
|
|
7702
|
-
if (!NativeScaleBunOta_default) return;
|
|
7767
|
+
checkBootGuardRecovery(state) {
|
|
7703
7768
|
try {
|
|
7704
|
-
|
|
7705
|
-
const
|
|
7706
|
-
|
|
7707
|
-
|
|
7708
|
-
|
|
7709
|
-
|
|
7769
|
+
if (!state?.bootGuardReverted) return;
|
|
7770
|
+
const record4 = this.readInstallRecord();
|
|
7771
|
+
const revertedSha = state.bootGuardRevertedSha256;
|
|
7772
|
+
const matchesRecord = !!record4 && (!revertedSha || record4.sha256 === revertedSha);
|
|
7773
|
+
const reason = state.bootGuardRevertReason || "boot_crash_guard";
|
|
7774
|
+
if (matchesRecord && record4) {
|
|
7775
|
+
logger.warn(`[OTA] Boot guard fired \u2014 reverted away from bundle ${record4.bundleId} (v${record4.version}); reason: ${reason}`);
|
|
7776
|
+
otaEventEmitter.emitSimple("AUTO_ROLLBACK", record4.bundleId, {
|
|
7777
|
+
releaseId: record4.releaseId ?? void 0,
|
|
7778
|
+
version: record4.version,
|
|
7779
|
+
reason
|
|
7710
7780
|
});
|
|
7781
|
+
this.clearInstallExpectation();
|
|
7782
|
+
return;
|
|
7711
7783
|
}
|
|
7784
|
+
logger.warn(`[OTA] Boot guard fired (reason: ${reason}) but the rolled-back bundle could not be identified locally \u2014 no install record. The rollback is not reported to the server.`);
|
|
7712
7785
|
} catch {
|
|
7713
7786
|
}
|
|
7714
7787
|
}
|
|
@@ -7799,6 +7872,12 @@ var init_OtaOrchestrator = __esm({
|
|
|
7799
7872
|
};
|
|
7800
7873
|
}
|
|
7801
7874
|
__DEV__ && logger.debug("[OTA] Sync started\u2026");
|
|
7875
|
+
if (this.currentBundle?.id) {
|
|
7876
|
+
otaEventEmitter.emitSimple("CHECK", this.currentBundle.id, {
|
|
7877
|
+
releaseId: this.currentBundle.releaseId,
|
|
7878
|
+
version: this.currentBundle.version
|
|
7879
|
+
});
|
|
7880
|
+
}
|
|
7802
7881
|
const checkRes = await this.checkForUpdate(params);
|
|
7803
7882
|
if (checkRes.action === "NONE") {
|
|
7804
7883
|
__DEV__ && logger.debug("[OTA] App is up to date");
|
|
@@ -7809,7 +7888,9 @@ var init_OtaOrchestrator = __esm({
|
|
|
7809
7888
|
}
|
|
7810
7889
|
if (checkRes.action === "ROLLBACK") {
|
|
7811
7890
|
logger.warn("[OTA] Server requested ROLLBACK \u2014 reverting to previous bundle");
|
|
7812
|
-
otaEventEmitter.emitSimple("MANUAL_ROLLBACK", this.currentBundle?.id ?? "unknown"
|
|
7891
|
+
otaEventEmitter.emitSimple("MANUAL_ROLLBACK", this.currentBundle?.id ?? "unknown", {
|
|
7892
|
+
releaseId: this.currentBundle?.releaseId
|
|
7893
|
+
});
|
|
7813
7894
|
const reverted = await NativeScaleBunOta_default.revertToPrevious();
|
|
7814
7895
|
if (reverted) {
|
|
7815
7896
|
this.currentBundle = null;
|
|
@@ -7833,9 +7914,14 @@ var init_OtaOrchestrator = __esm({
|
|
|
7833
7914
|
}
|
|
7834
7915
|
const bundle = checkRes.bundle;
|
|
7835
7916
|
let patchUsed = false;
|
|
7917
|
+
otaEventEmitter.emitSimple("OFFERED", bundle.id, {
|
|
7918
|
+
releaseId: bundle.releaseId,
|
|
7919
|
+
version: bundle.version
|
|
7920
|
+
});
|
|
7836
7921
|
const signatureOutcome = await verifyBundleSignature(bundle.sha256, bundle.signature, this.signatureConfig);
|
|
7837
7922
|
if (!signatureOutcome.ok) {
|
|
7838
7923
|
otaEventEmitter.emitSimple("APPLY_FAILED", bundle.id, {
|
|
7924
|
+
releaseId: bundle.releaseId,
|
|
7839
7925
|
error: `Signature check failed: ${signatureOutcome.reason}`,
|
|
7840
7926
|
version: bundle.version
|
|
7841
7927
|
});
|
|
@@ -7846,6 +7932,7 @@ var init_OtaOrchestrator = __esm({
|
|
|
7846
7932
|
};
|
|
7847
7933
|
}
|
|
7848
7934
|
otaEventEmitter.emitSimple("DOWNLOAD_STARTED", bundle.id, {
|
|
7935
|
+
releaseId: bundle.releaseId,
|
|
7849
7936
|
version: bundle.version
|
|
7850
7937
|
});
|
|
7851
7938
|
const downloadStart = Date.now();
|
|
@@ -7931,6 +8018,7 @@ var init_OtaOrchestrator = __esm({
|
|
|
7931
8018
|
postProgress(0, "FAILED");
|
|
7932
8019
|
logger.error("[OTA] Staging bundle failed after retries");
|
|
7933
8020
|
otaEventEmitter.emitSimple("APPLY_FAILED", bundle.id, {
|
|
8021
|
+
releaseId: bundle.releaseId,
|
|
7934
8022
|
error: "Staging failed \u2014 SHA-256 mismatch or download error",
|
|
7935
8023
|
version: bundle.version
|
|
7936
8024
|
});
|
|
@@ -7943,6 +8031,7 @@ var init_OtaOrchestrator = __esm({
|
|
|
7943
8031
|
postProgress(bundle.size, "COMPLETED");
|
|
7944
8032
|
const downloadDuration = Date.now() - downloadStart;
|
|
7945
8033
|
otaEventEmitter.emitSimple("DOWNLOAD_COMPLETE", bundle.id, {
|
|
8034
|
+
releaseId: bundle.releaseId,
|
|
7946
8035
|
version: bundle.version,
|
|
7947
8036
|
durationMs: downloadDuration,
|
|
7948
8037
|
patchUsed
|
|
@@ -7951,6 +8040,7 @@ var init_OtaOrchestrator = __esm({
|
|
|
7951
8040
|
if (!applied) {
|
|
7952
8041
|
logger.error("[OTA] Applying update failed");
|
|
7953
8042
|
otaEventEmitter.emitSimple("APPLY_FAILED", bundle.id, {
|
|
8043
|
+
releaseId: bundle.releaseId,
|
|
7954
8044
|
error: "Atomic slot swap failed",
|
|
7955
8045
|
version: bundle.version
|
|
7956
8046
|
});
|
|
@@ -7964,6 +8054,7 @@ var init_OtaOrchestrator = __esm({
|
|
|
7964
8054
|
this.isRestartRequiredState = true;
|
|
7965
8055
|
this.recordInstallExpectation(bundle);
|
|
7966
8056
|
otaEventEmitter.emitSimple("INSTALLED", bundle.id, {
|
|
8057
|
+
releaseId: bundle.releaseId,
|
|
7967
8058
|
version: bundle.version
|
|
7968
8059
|
});
|
|
7969
8060
|
__DEV__ && logger.debug(`[OTA] Update v${bundle.version} installed successfully!`);
|
|
@@ -8016,6 +8107,14 @@ var init_OtaOrchestrator = __esm({
|
|
|
8016
8107
|
NativeScaleBunOta_default?.markHealthy();
|
|
8017
8108
|
__DEV__ && logger.info("[OTA] Boot guard cleared \u2014 bundle marked healthy \u2713");
|
|
8018
8109
|
this.healthyTimer = null;
|
|
8110
|
+
const running = this.currentBundle;
|
|
8111
|
+
if (running?.id) {
|
|
8112
|
+
otaEventEmitter.emitSimple("BOOT_SUCCESS", running.id, {
|
|
8113
|
+
releaseId: running.releaseId,
|
|
8114
|
+
version: running.version,
|
|
8115
|
+
durationMs: healthyMs
|
|
8116
|
+
});
|
|
8117
|
+
}
|
|
8019
8118
|
});
|
|
8020
8119
|
}, healthyMs);
|
|
8021
8120
|
} catch {
|
|
@@ -10386,31 +10485,8 @@ var FlushScheduler = class {
|
|
|
10386
10485
|
}
|
|
10387
10486
|
};
|
|
10388
10487
|
|
|
10389
|
-
// lib/module/
|
|
10390
|
-
|
|
10391
|
-
init_internalLogger();
|
|
10392
|
-
var AppLifecycle = class {
|
|
10393
|
-
listeners = [];
|
|
10394
|
-
currentState = import_react_native6.AppState.currentState;
|
|
10395
|
-
constructor() {
|
|
10396
|
-
import_react_native6.AppState.addEventListener("change", this.handleStateChange);
|
|
10397
|
-
}
|
|
10398
|
-
handleStateChange = (nextState) => {
|
|
10399
|
-
__DEV__ && logger.debug(`App state changed: ${this.currentState} -> ${nextState}`);
|
|
10400
|
-
this.currentState = nextState;
|
|
10401
|
-
this.listeners.forEach((l) => l(nextState));
|
|
10402
|
-
};
|
|
10403
|
-
addListener(listener) {
|
|
10404
|
-
this.listeners.push(listener);
|
|
10405
|
-
}
|
|
10406
|
-
removeListener(listener) {
|
|
10407
|
-
this.listeners = this.listeners.filter((l) => l !== listener);
|
|
10408
|
-
}
|
|
10409
|
-
getCurrentState() {
|
|
10410
|
-
return this.currentState;
|
|
10411
|
-
}
|
|
10412
|
-
};
|
|
10413
|
-
var appLifecycle = new AppLifecycle();
|
|
10488
|
+
// lib/module/bootstrap/SDKBootstrapper.js
|
|
10489
|
+
init_appLifecycle();
|
|
10414
10490
|
|
|
10415
10491
|
// lib/module/bootstrap/FeatureRegistry.js
|
|
10416
10492
|
init_internalLogger();
|
|
@@ -16896,7 +16972,7 @@ async function captureInstallReferrerOnce(sink) {
|
|
|
16896
16972
|
}
|
|
16897
16973
|
|
|
16898
16974
|
// lib/module/public/ScaleBunFacade.js
|
|
16899
|
-
var ScaleBunFacade = class {
|
|
16975
|
+
var ScaleBunFacade = class _ScaleBunFacade {
|
|
16900
16976
|
initialized = false;
|
|
16901
16977
|
/** In-flight init promise — guards against a second init() racing before the first resolves. */
|
|
16902
16978
|
_initInFlight = null;
|
|
@@ -17056,6 +17132,7 @@ var ScaleBunFacade = class {
|
|
|
17056
17132
|
logger.warn("[ScaleBun] A signing key is pinned but no signature verifier is available. Signature checking is fail-closed: updates will be REJECTED until one exists. Install the optional peers `@noble/ed25519` + `@noble/hashes` (no further code needed), or supply `ota.verifySignature`.");
|
|
17057
17133
|
}
|
|
17058
17134
|
}
|
|
17135
|
+
const healthyAfterMs = typeof ota.healthyAfterMs === "number" ? ota.healthyAfterMs : typeof ota.healthyTimeoutMs === "number" ? ota.healthyTimeoutMs : void 0;
|
|
17059
17136
|
otaOrchestrator2.init({
|
|
17060
17137
|
...signingRequested ? {
|
|
17061
17138
|
signature: {
|
|
@@ -17065,15 +17142,99 @@ var ScaleBunFacade = class {
|
|
|
17065
17142
|
verifier
|
|
17066
17143
|
}
|
|
17067
17144
|
} : {},
|
|
17068
|
-
...
|
|
17069
|
-
|
|
17145
|
+
...healthyAfterMs !== void 0 ? {
|
|
17146
|
+
healthyAfterMs
|
|
17070
17147
|
} : {}
|
|
17071
17148
|
});
|
|
17072
17149
|
logger.info("[ScaleBun] OTA enabled from init config.");
|
|
17150
|
+
this._startOtaChecks(ota);
|
|
17073
17151
|
} catch (err) {
|
|
17074
17152
|
logger.warn(`[ScaleBun] OTA init failed: ${err?.message ?? err}`);
|
|
17075
17153
|
}
|
|
17076
17154
|
}
|
|
17155
|
+
/** Guards against overlapping config-driven OTA checks. */
|
|
17156
|
+
_otaCheckInFlight = false;
|
|
17157
|
+
/** Wall clock of the last config-driven check, for the foreground floor. */
|
|
17158
|
+
_otaLastCheckAt = 0;
|
|
17159
|
+
_otaForegroundListener = null;
|
|
17160
|
+
/**
|
|
17161
|
+
* Minimum gap between config-driven checks.
|
|
17162
|
+
*
|
|
17163
|
+
* A foreground transition is cheap to trigger — app switchers, permission
|
|
17164
|
+
* dialogs and share sheets all produce one — so an unthrottled check would
|
|
17165
|
+
* put a request on the hot path every time the user glanced away. Ten
|
|
17166
|
+
* minutes is well below any realistic release cadence and well above that
|
|
17167
|
+
* noise. A host that wants a check on demand calls `useOtaUpdate().sync()`,
|
|
17168
|
+
* which is never throttled.
|
|
17169
|
+
*/
|
|
17170
|
+
static OTA_MIN_CHECK_INTERVAL_MS = 10 * 60 * 1e3;
|
|
17171
|
+
/**
|
|
17172
|
+
* Drive OTA checks from init config: once at startup, then on each
|
|
17173
|
+
* foreground when `checkOnForeground` is on (the schema default).
|
|
17174
|
+
*
|
|
17175
|
+
* `appVersion` is resolved from the native bridge rather than asked of the
|
|
17176
|
+
* integrator, because it gates the server's `targetAppVersion` semver check
|
|
17177
|
+
* — sending a wrong or invented value is worse than sending none, and there
|
|
17178
|
+
* is no honest default. If it cannot be resolved, the check is skipped with
|
|
17179
|
+
* a warning instead of guessing.
|
|
17180
|
+
*/
|
|
17181
|
+
_startOtaChecks(ota) {
|
|
17182
|
+
if (__DEV__) {
|
|
17183
|
+
logger.info("[ScaleBun] OTA checks are skipped in debug builds (Metro owns the bundle).");
|
|
17184
|
+
return;
|
|
17185
|
+
}
|
|
17186
|
+
const runCheck = async (trigger) => {
|
|
17187
|
+
if (this._otaCheckInFlight) return;
|
|
17188
|
+
if (trigger === "foreground" && Date.now() - this._otaLastCheckAt < _ScaleBunFacade.OTA_MIN_CHECK_INTERVAL_MS) {
|
|
17189
|
+
return;
|
|
17190
|
+
}
|
|
17191
|
+
const clientKey = this._clientKey;
|
|
17192
|
+
const apiUrl = this._apiBaseUrl;
|
|
17193
|
+
if (!clientKey || !apiUrl) return;
|
|
17194
|
+
this._otaCheckInFlight = true;
|
|
17195
|
+
try {
|
|
17196
|
+
const info = await bridgeAdapter.getDeviceInfo();
|
|
17197
|
+
const appVersion = info?.appVersion;
|
|
17198
|
+
if (!appVersion) {
|
|
17199
|
+
logger.warn("[ScaleBun] OTA check skipped \u2014 the app version could not be read from the native bridge. Rebuild the native app, or drive checks yourself with useOtaUpdate({ appVersion }).");
|
|
17200
|
+
return;
|
|
17201
|
+
}
|
|
17202
|
+
this._otaLastCheckAt = Date.now();
|
|
17203
|
+
const {
|
|
17204
|
+
otaOrchestrator: otaOrchestrator2
|
|
17205
|
+
} = (init_OtaOrchestrator(), __toCommonJS(OtaOrchestrator_exports));
|
|
17206
|
+
await otaOrchestrator2.sync({
|
|
17207
|
+
apiUrl,
|
|
17208
|
+
clientKey,
|
|
17209
|
+
appVersion,
|
|
17210
|
+
// The documented option, finally connected. Omitted means the
|
|
17211
|
+
// server's `default` channel, exactly as before.
|
|
17212
|
+
channelName: typeof ota.channelOverride === "string" ? ota.channelOverride : void 0
|
|
17213
|
+
// Never forced from config: the release's own installMode
|
|
17214
|
+
// decides when the app restarts, and yanking the screen out
|
|
17215
|
+
// from under a user is not a decision this switch should make.
|
|
17216
|
+
});
|
|
17217
|
+
} catch (err) {
|
|
17218
|
+
logger.warn(`[ScaleBun] OTA check failed: ${err?.message ?? err}`);
|
|
17219
|
+
} finally {
|
|
17220
|
+
this._otaCheckInFlight = false;
|
|
17221
|
+
}
|
|
17222
|
+
};
|
|
17223
|
+
void runCheck("startup");
|
|
17224
|
+
if (ota.checkOnForeground === false) return;
|
|
17225
|
+
if (this._otaForegroundListener) return;
|
|
17226
|
+
try {
|
|
17227
|
+
const {
|
|
17228
|
+
appLifecycle: appLifecycle2
|
|
17229
|
+
} = (init_appLifecycle(), __toCommonJS(appLifecycle_exports));
|
|
17230
|
+
this._otaForegroundListener = (state) => {
|
|
17231
|
+
if (state === "active") void runCheck("foreground");
|
|
17232
|
+
};
|
|
17233
|
+
appLifecycle2.addListener(this._otaForegroundListener);
|
|
17234
|
+
} catch {
|
|
17235
|
+
this._otaForegroundListener = null;
|
|
17236
|
+
}
|
|
17237
|
+
}
|
|
17077
17238
|
_autoEnableDebug(debugConfig) {
|
|
17078
17239
|
try {
|
|
17079
17240
|
const dbgConfig = buildDebugConfigFromInitConfig(debugConfig);
|
|
@@ -18485,6 +18646,7 @@ init_internalLogger();
|
|
|
18485
18646
|
|
|
18486
18647
|
// lib/module/features/engage/engageTriggerEngine.js
|
|
18487
18648
|
init_internalLogger();
|
|
18649
|
+
init_appLifecycle();
|
|
18488
18650
|
init_AutoScreenDetector();
|
|
18489
18651
|
init_StorageBackend();
|
|
18490
18652
|
init_engageSignals();
|
|
@@ -18792,6 +18954,7 @@ function installEngageTriggerEngine(deps) {
|
|
|
18792
18954
|
}
|
|
18793
18955
|
|
|
18794
18956
|
// lib/module/features/engage/EngagePromptProvider.js
|
|
18957
|
+
init_appLifecycle();
|
|
18795
18958
|
init_deviceId();
|
|
18796
18959
|
init_api();
|
|
18797
18960
|
init_SessionManager();
|
|
@@ -31025,7 +31188,7 @@ function useOtaUpdate(options) {
|
|
|
31025
31188
|
setIsSyncing(true);
|
|
31026
31189
|
setDownloadProgress(0);
|
|
31027
31190
|
try {
|
|
31028
|
-
const result = await otaOrchestrator.sync(
|
|
31191
|
+
const result = await otaOrchestrator.sync(optionsRef.current);
|
|
31029
31192
|
setSyncResult(result);
|
|
31030
31193
|
if (result.isMandatory && result.status === "UPDATE_INSTALLED" && optionsRef.current.mandatoryBlocksUi) {
|
|
31031
31194
|
setMandatoryUpdatePending(true);
|
|
@@ -31034,7 +31197,7 @@ function useOtaUpdate(options) {
|
|
|
31034
31197
|
} finally {
|
|
31035
31198
|
setIsSyncing(false);
|
|
31036
31199
|
}
|
|
31037
|
-
}, [
|
|
31200
|
+
}, []);
|
|
31038
31201
|
const restart = (0, import_react16.useCallback)(() => {
|
|
31039
31202
|
setMandatoryUpdatePending(false);
|
|
31040
31203
|
otaOrchestrator.restart();
|
|
@@ -151,10 +151,19 @@ class OtaSlotManager {
|
|
|
151
151
|
// it `bootGuardReverted` was read by the orchestrator and written by
|
|
152
152
|
// nobody on iOS, so an auto-rollback that DID occur reported nothing —
|
|
153
153
|
// the dashboard counted zero rollbacks while devices were reverting.
|
|
154
|
-
|
|
154
|
+
// Stamp WHICH bundle is being reverted away from, read before current/ is
|
|
155
|
+
// removed below. Without it JS had nothing to name: slot meta carries no
|
|
156
|
+
// bundleId and the previous slot is gone by the time JS looks, so every
|
|
157
|
+
// AUTO_ROLLBACK reported bundleId "unknown" and the server dropped it as
|
|
158
|
+
// unresolvable — leaving the crash guard's rollback signal at zero.
|
|
159
|
+
var record: [String: Any] = [
|
|
155
160
|
"reason": reason,
|
|
156
161
|
"at": Int(Date().timeIntervalSince1970 * 1000),
|
|
157
162
|
]
|
|
163
|
+
if let meta = readMeta(slot: currentSlot),
|
|
164
|
+
let failingSha = meta["sha256"] as? String, !failingSha.isEmpty {
|
|
165
|
+
record["sha256"] = failingSha
|
|
166
|
+
}
|
|
158
167
|
if let data = try? JSONSerialization.data(withJSONObject: record) {
|
|
159
168
|
try? data.write(to: revertRecordFile, options: .atomic)
|
|
160
169
|
}
|
|
@@ -195,14 +204,14 @@ class OtaSlotManager {
|
|
|
195
204
|
let markerId = (markerJson["sha256"] as? String)
|
|
196
205
|
?? (markerJson["bundleId"] as? String) else {
|
|
197
206
|
NSLog("[ScaleBunOta] Invalid boot marker — reverting")
|
|
198
|
-
_ = revert()
|
|
207
|
+
_ = revert(reason: "invalid_marker")
|
|
199
208
|
return
|
|
200
209
|
}
|
|
201
210
|
|
|
202
211
|
let metaURL = currentSlot.appendingPathComponent(OtaSlotManager.metaFilename)
|
|
203
212
|
guard fm.fileExists(atPath: metaURL.path) else {
|
|
204
213
|
NSLog("[ScaleBunOta] Boot marker present but no current/meta.json — reverting")
|
|
205
|
-
_ = revert()
|
|
214
|
+
_ = revert(reason: "missing_meta")
|
|
206
215
|
return
|
|
207
216
|
}
|
|
208
217
|
|
|
@@ -210,7 +219,7 @@ class OtaSlotManager {
|
|
|
210
219
|
guard let metaJson = try JSONSerialization.jsonObject(with: metaData) as? [String: Any],
|
|
211
220
|
let currentId = metaJson["sha256"] as? String else {
|
|
212
221
|
NSLog("[ScaleBunOta] Invalid current/meta.json — reverting")
|
|
213
|
-
_ = revert()
|
|
222
|
+
_ = revert(reason: "invalid_meta")
|
|
214
223
|
return
|
|
215
224
|
}
|
|
216
225
|
|
|
@@ -250,7 +259,7 @@ class OtaSlotManager {
|
|
|
250
259
|
}
|
|
251
260
|
} catch {
|
|
252
261
|
NSLog("[ScaleBunOta] checkBootGuard() failed — reverting: \(error)")
|
|
253
|
-
_ = revert()
|
|
262
|
+
_ = revert(reason: "boot_guard_error")
|
|
254
263
|
}
|
|
255
264
|
}
|
|
256
265
|
|
|
@@ -273,6 +282,11 @@ class OtaSlotManager {
|
|
|
273
282
|
state["bootGuardReverted"] = true
|
|
274
283
|
state["bootGuardRevertReason"] = rec["reason"] as? String ?? "boot_guard"
|
|
275
284
|
state["bootGuardRevertedAt"] = rec["at"] as? Int ?? 0
|
|
285
|
+
// The bundle reverted AWAY from. JS joins it back to a bundle id
|
|
286
|
+
// through its install record so the rollback can be reported.
|
|
287
|
+
if let sha = rec["sha256"] as? String, !sha.isEmpty {
|
|
288
|
+
state["bootGuardRevertedSha256"] = sha
|
|
289
|
+
}
|
|
276
290
|
}
|
|
277
291
|
try? fm.removeItem(at: revertRecordFile)
|
|
278
292
|
}
|
|
@@ -14,5 +14,5 @@ exports.SDK_VERSION = void 0;
|
|
|
14
14
|
* value, so a stale one makes a rollout unobservable — which is the exact problem sending an SDK
|
|
15
15
|
* version was introduced to solve.
|
|
16
16
|
*/
|
|
17
|
-
const SDK_VERSION = exports.SDK_VERSION = '1.11.
|
|
17
|
+
const SDK_VERSION = exports.SDK_VERSION = '1.11.1';
|
|
18
18
|
//# sourceMappingURL=version.js.map
|