@absolutejs/absolute 0.20.0-beta.60 → 0.20.0-beta.61
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/dist/angular/components/core/streamingSlotRegistrar.js +1 -1
- package/dist/angular/components/core/streamingSlotRegistry.js +2 -2
- package/dist/cli/index.js +547 -94
- package/dist/index.js +11 -3
- package/dist/index.js.map +3 -3
- package/dist/mobile/index.js +495 -50
- package/dist/mobile/index.js.map +7 -7
- package/dist/mobile/remoteMacAgentEntry.js +195 -195
- package/dist/mobile/shellUpdate.js +82 -6
- package/dist/src/mobile/capacitorBundle.d.ts +1 -0
- package/dist/src/mobile/config.d.ts +1 -0
- package/dist/src/mobile/transport.d.ts +1 -0
- package/dist/src/mobile/updateClient.d.ts +4 -0
- package/dist/src/mobile/updateRuntime.d.ts +1 -1
- package/dist/types/build.d.ts +2 -0
- package/package.json +7 -7
package/dist/mobile/index.js
CHANGED
|
@@ -168,7 +168,7 @@ var init_startupBanner = __esm(() => {
|
|
|
168
168
|
// src/mobile/config.ts
|
|
169
169
|
import { resolve as resolve6 } from "path";
|
|
170
170
|
import { createPublicKey } from "crypto";
|
|
171
|
-
var APP_ID_PATTERN, SCHEME_PATTERN, APPLE_APP_ID_PREFIX_PATTERN, CERTIFICATE_FINGERPRINT_PATTERN, UPDATE_NAME_PATTERN, UPDATE_PUBLIC_KEY_PATTERN, HOSTNAME_PATTERN, EXPO_RESERVED_ROUTE_PREFIXES, resolveProjectPath = (projectRoot, value, field2) => {
|
|
171
|
+
var APP_ID_PATTERN, SCHEME_PATTERN, APPLE_APP_ID_PREFIX_PATTERN, CERTIFICATE_FINGERPRINT_PATTERN, UPDATE_NAME_PATTERN, UPDATE_PUBLIC_KEY_PATTERN, DEFAULT_UPDATE_BOOT_TIMEOUT_MS = 20000, MINIMUM_UPDATE_BOOT_TIMEOUT_MS = 5000, MAXIMUM_UPDATE_BOOT_TIMEOUT_MS = 120000, HOSTNAME_PATTERN, EXPO_RESERVED_ROUTE_PREFIXES, resolveProjectPath = (projectRoot, value, field2) => {
|
|
172
172
|
const root = resolve6(projectRoot);
|
|
173
173
|
const path = resolve6(root, value);
|
|
174
174
|
if (path !== root && !path.startsWith(`${root}/`)) {
|
|
@@ -244,6 +244,9 @@ var APP_ID_PATTERN, SCHEME_PATTERN, APPLE_APP_ID_PREFIX_PATTERN, CERTIFICATE_FIN
|
|
|
244
244
|
].sort(), normalizeUpdates = (config, productionOrigin) => {
|
|
245
245
|
if (!config.updates)
|
|
246
246
|
return;
|
|
247
|
+
const bootTimeoutMs = config.updates.bootTimeoutMs ?? DEFAULT_UPDATE_BOOT_TIMEOUT_MS;
|
|
248
|
+
if (!Number.isSafeInteger(bootTimeoutMs) || bootTimeoutMs < MINIMUM_UPDATE_BOOT_TIMEOUT_MS || bootTimeoutMs > MAXIMUM_UPDATE_BOOT_TIMEOUT_MS)
|
|
249
|
+
throw new TypeError("mobile.updates.bootTimeoutMs must be an integer from 5000 through 120000.");
|
|
247
250
|
const channel = requireText(config.updates.channel ?? "production", "mobile.updates.channel");
|
|
248
251
|
if (!UPDATE_NAME_PATTERN.test(channel))
|
|
249
252
|
throw new TypeError("mobile.updates.channel contains unsupported characters.");
|
|
@@ -285,7 +288,12 @@ var APP_ID_PATTERN, SCHEME_PATTERN, APPLE_APP_ID_PREFIX_PATTERN, CERTIFICATE_FIN
|
|
|
285
288
|
throw new TypeError(`mobile.updates.publicKeys.${keyId} is not an ECDSA P-256 SPKI public key.`);
|
|
286
289
|
return [keyId, normalized];
|
|
287
290
|
}));
|
|
288
|
-
return {
|
|
291
|
+
return {
|
|
292
|
+
bootTimeoutMs,
|
|
293
|
+
channel,
|
|
294
|
+
manifestUrl: manifestUrl.href,
|
|
295
|
+
publicKeys
|
|
296
|
+
};
|
|
289
297
|
}, validateExpoNativeRouteSegment = (path, segment, index, count, parameters) => {
|
|
290
298
|
if (segment === "*" && (index !== count - 1 || count === 1)) {
|
|
291
299
|
throw new TypeError(`mobile.routes.native route ${path} must use * once, as the final segment after a static or parameterized prefix.`);
|
|
@@ -9451,7 +9459,7 @@ var unsignedAbsoluteMobileUpdate = (manifest) => {
|
|
|
9451
9459
|
};
|
|
9452
9460
|
|
|
9453
9461
|
// src/mobile/updateRuntime.ts
|
|
9454
|
-
var ABSOLUTE_MOBILE_SHELL_ABI =
|
|
9462
|
+
var ABSOLUTE_MOBILE_SHELL_ABI = 2;
|
|
9455
9463
|
var ABSOLUTE_MOBILE_UPDATE_RUNTIME_FORMAT = 1;
|
|
9456
9464
|
var createAbsoluteMobileUpdateRuntimeDescriptor = (options) => ({
|
|
9457
9465
|
appId: options.config.appId,
|
|
@@ -11507,63 +11515,498 @@ var applyAbsoluteNativeDeviceCapabilities = async (projectRoot, config, platform
|
|
|
11507
11515
|
init_nativeAuth();
|
|
11508
11516
|
|
|
11509
11517
|
// src/mobile/nativeUpdates.ts
|
|
11510
|
-
import {
|
|
11511
|
-
|
|
11518
|
+
import {
|
|
11519
|
+
mkdir as mkdir13,
|
|
11520
|
+
readFile as readFile19,
|
|
11521
|
+
readdir as readdir5,
|
|
11522
|
+
rename as rename14,
|
|
11523
|
+
rm as rm11,
|
|
11524
|
+
writeFile as writeFile16
|
|
11525
|
+
} from "fs/promises";
|
|
11526
|
+
import { dirname as dirname13, join as join21 } from "path";
|
|
11512
11527
|
var START = "// absolutejs:mobile-updates:start";
|
|
11513
11528
|
var END = "// absolutejs:mobile-updates:end";
|
|
11529
|
+
var PLUGIN_START = "// absolutejs:mobile-update-plugin:start";
|
|
11530
|
+
var PLUGIN_END = "// absolutejs:mobile-update-plugin:end";
|
|
11531
|
+
var ANDROID_PLUGIN = "AbsoluteMobileUpdateWatchdogPlugin.java";
|
|
11532
|
+
var optionalSource2 = async (path) => {
|
|
11533
|
+
try {
|
|
11534
|
+
return await readFile19(path, "utf8");
|
|
11535
|
+
} catch (error) {
|
|
11536
|
+
if (typeof error === "object" && error !== null && Reflect.get(error, "code") === "ENOENT")
|
|
11537
|
+
return null;
|
|
11538
|
+
throw error;
|
|
11539
|
+
}
|
|
11540
|
+
};
|
|
11541
|
+
var isRecord11 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
11514
11542
|
var writeChanged = async (path, source) => {
|
|
11515
|
-
const current = await
|
|
11543
|
+
const current = await optionalSource2(path);
|
|
11516
11544
|
if (current === source)
|
|
11517
11545
|
return false;
|
|
11546
|
+
await mkdir13(dirname13(path), { recursive: true });
|
|
11547
|
+
if (current === null) {
|
|
11548
|
+
await writeFile16(path, source, { flag: "wx" });
|
|
11549
|
+
return true;
|
|
11550
|
+
}
|
|
11518
11551
|
const temporary = `${path}.${crypto.randomUUID()}.tmp`;
|
|
11519
11552
|
await writeFile16(temporary, source, { flag: "wx" });
|
|
11520
11553
|
await rename14(temporary, path);
|
|
11521
11554
|
return true;
|
|
11522
11555
|
};
|
|
11523
|
-
var
|
|
11524
|
-
|
|
11525
|
-
|
|
11526
|
-
|
|
11556
|
+
var removeOwnedFile = async (path) => {
|
|
11557
|
+
if (await optionalSource2(path) === null)
|
|
11558
|
+
return false;
|
|
11559
|
+
await rm11(path);
|
|
11560
|
+
return true;
|
|
11561
|
+
};
|
|
11562
|
+
var replaceMarkedRegion = (source, start, end, region, insertion, error) => {
|
|
11563
|
+
const existingStart = source.indexOf(start);
|
|
11564
|
+
const existingEnd = source.indexOf(end);
|
|
11565
|
+
if (existingStart < 0 !== existingEnd < 0 || existingStart >= 0 && existingEnd < existingStart)
|
|
11527
11566
|
throw new TypeError("AbsoluteJS mobile update markers are malformed.");
|
|
11528
|
-
if (
|
|
11567
|
+
if (existingStart >= 0) {
|
|
11529
11568
|
const from = source.lastIndexOf(`
|
|
11530
|
-
`,
|
|
11569
|
+
`, existingStart) + 1;
|
|
11531
11570
|
const newline = source.indexOf(`
|
|
11532
|
-
`,
|
|
11571
|
+
`, existingEnd + end.length);
|
|
11533
11572
|
return `${source.slice(0, from)}${region}${source.slice(newline < 0 ? source.length : newline + 1)}`;
|
|
11534
11573
|
}
|
|
11535
11574
|
if (!region)
|
|
11536
11575
|
return source;
|
|
11576
|
+
if (insertion < 0)
|
|
11577
|
+
throw new TypeError(error);
|
|
11578
|
+
return `${source.slice(0, insertion)}${region}${source.slice(insertion)}`;
|
|
11579
|
+
};
|
|
11580
|
+
var iosPluginRegion = (timeoutMs) => `${PLUGIN_START}
|
|
11581
|
+
@objc(AbsoluteMobileUpdateWatchdogPlugin)
|
|
11582
|
+
public final class AbsoluteMobileUpdateWatchdogPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
11583
|
+
public let identifier = "AbsoluteMobileUpdateWatchdogPlugin"
|
|
11584
|
+
public let jsName = "AbsoluteMobileUpdateWatchdog"
|
|
11585
|
+
public let pluginMethods: [CAPPluginMethod] = [
|
|
11586
|
+
CAPPluginMethod(name: "arm", returnType: CAPPluginReturnPromise),
|
|
11587
|
+
CAPPluginMethod(name: "confirm", returnType: CAPPluginReturnPromise)
|
|
11588
|
+
]
|
|
11589
|
+
|
|
11590
|
+
private static let stateKey = "CapacitorStorage.absolute.mobile.update.state.v1"
|
|
11591
|
+
private static let releasePattern = try! NSRegularExpression(pattern: "^amu_[a-f0-9]{64}$")
|
|
11592
|
+
private var deadline: DispatchWorkItem?
|
|
11593
|
+
|
|
11594
|
+
private static func state() -> [String: Any] {
|
|
11595
|
+
guard let encoded = UserDefaults.standard.string(forKey: stateKey),
|
|
11596
|
+
let data = encoded.data(using: .utf8),
|
|
11597
|
+
let object = try? JSONSerialization.jsonObject(with: data),
|
|
11598
|
+
let value = object as? [String: Any] else { return [:] }
|
|
11599
|
+
return value
|
|
11600
|
+
}
|
|
11601
|
+
|
|
11602
|
+
private static func write(_ state: [String: Any]) {
|
|
11603
|
+
guard let data = try? JSONSerialization.data(withJSONObject: state),
|
|
11604
|
+
let encoded = String(data: data, encoding: .utf8) else { return }
|
|
11605
|
+
UserDefaults.standard.set(encoded, forKey: stateKey)
|
|
11606
|
+
}
|
|
11607
|
+
|
|
11608
|
+
private static func validRelease(_ value: String) -> Bool {
|
|
11609
|
+
releasePattern.firstMatch(in: value, range: NSRange(value.startIndex..., in: value)) != nil
|
|
11610
|
+
}
|
|
11611
|
+
|
|
11612
|
+
private static func snapshotRoot() -> URL? {
|
|
11613
|
+
FileManager.default.urls(for: .libraryDirectory, in: .userDomainMask).first?
|
|
11614
|
+
.appendingPathComponent("NoCloud/ionic_built_snapshots", isDirectory: true)
|
|
11615
|
+
}
|
|
11616
|
+
|
|
11617
|
+
@discardableResult
|
|
11618
|
+
private static func recover(_ reason: String) -> (String, String?, Bool)? {
|
|
11619
|
+
var value = state()
|
|
11620
|
+
guard let release = value["pendingRelease"] as? String, validRelease(release) else { return nil }
|
|
11621
|
+
let previous = value["previousPath"] as? String
|
|
11622
|
+
let active = value["activeRelease"] as? String
|
|
11623
|
+
let hasActive = active.map(validRelease) ?? false
|
|
11624
|
+
let started = value["pendingStartedAt"] as? Double ?? Date().timeIntervalSince1970 * 1000
|
|
11625
|
+
let duration = max(0, Date().timeIntervalSince1970 * 1000 - started)
|
|
11626
|
+
value.removeValue(forKey: "pendingRelease")
|
|
11627
|
+
value.removeValue(forKey: "pendingStartedAt")
|
|
11628
|
+
value.removeValue(forKey: "previousPath")
|
|
11629
|
+
value.removeValue(forKey: "readyRelease")
|
|
11630
|
+
var quarantined = value["quarantinedReleases"] as? [String] ?? []
|
|
11631
|
+
quarantined.removeAll(where: { $0 == release })
|
|
11632
|
+
quarantined.append(release)
|
|
11633
|
+
value["quarantinedReleases"] = Array(quarantined.suffix(8))
|
|
11634
|
+
value["recovery"] = ["durationMs": duration, "reason": reason, "releaseId": release]
|
|
11635
|
+
write(value)
|
|
11636
|
+
|
|
11637
|
+
if hasActive, let previous, let root = snapshotRoot(),
|
|
11638
|
+
URL(fileURLWithPath: previous).standardizedFileURL.path.hasPrefix(root.standardizedFileURL.path + "/") {
|
|
11639
|
+
UserDefaults.standard.set(previous, forKey: "serverBasePath")
|
|
11640
|
+
} else {
|
|
11641
|
+
UserDefaults.standard.removeObject(forKey: "serverBasePath")
|
|
11642
|
+
}
|
|
11643
|
+
if let root = snapshotRoot() {
|
|
11644
|
+
try? FileManager.default.removeItem(at: root.appendingPathComponent(release, isDirectory: true))
|
|
11645
|
+
}
|
|
11646
|
+
|
|
11647
|
+
return (release, previous, hasActive)
|
|
11648
|
+
}
|
|
11649
|
+
|
|
11650
|
+
public static func recoverInterruptedBoot() {
|
|
11651
|
+
if recover("boot-interrupted") != nil { return }
|
|
11652
|
+
guard let persisted = UserDefaults.standard.string(forKey: "serverBasePath"),
|
|
11653
|
+
let root = snapshotRoot() else { return }
|
|
11654
|
+
let path = URL(fileURLWithPath: persisted).standardizedFileURL.path
|
|
11655
|
+
if path.hasPrefix(root.standardizedFileURL.path + "/") &&
|
|
11656
|
+
!FileManager.default.fileExists(atPath: path) {
|
|
11657
|
+
UserDefaults.standard.removeObject(forKey: "serverBasePath")
|
|
11658
|
+
}
|
|
11659
|
+
}
|
|
11660
|
+
|
|
11661
|
+
@objc public func arm(_ call: CAPPluginCall) {
|
|
11662
|
+
guard let requested = call.getString("releaseId"),
|
|
11663
|
+
Self.validRelease(requested),
|
|
11664
|
+
Self.state()["pendingRelease"] as? String == requested else {
|
|
11665
|
+
call.reject("Mobile update watchdog cannot arm an unknown release.")
|
|
11666
|
+
return
|
|
11667
|
+
}
|
|
11668
|
+
deadline?.cancel()
|
|
11669
|
+
let work = DispatchWorkItem { [weak self] in
|
|
11670
|
+
guard let self, let recovery = Self.recover("boot-timeout") else { return }
|
|
11671
|
+
if recovery.2, let previous = recovery.1,
|
|
11672
|
+
let root = Self.snapshotRoot(),
|
|
11673
|
+
URL(fileURLWithPath: previous).standardizedFileURL.path.hasPrefix(root.standardizedFileURL.path + "/") {
|
|
11674
|
+
(self.bridge?.viewController as? CAPBridgeViewController)?.setServerBasePath(path: previous)
|
|
11675
|
+
} else if let embedded = Bundle.main.url(forResource: "public", withExtension: nil)?.path {
|
|
11676
|
+
(self.bridge?.viewController as? CAPBridgeViewController)?.setServerBasePath(path: embedded)
|
|
11677
|
+
}
|
|
11678
|
+
}
|
|
11679
|
+
deadline = work
|
|
11680
|
+
DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(${timeoutMs}), execute: work)
|
|
11681
|
+
call.resolve()
|
|
11682
|
+
}
|
|
11683
|
+
|
|
11684
|
+
@objc public func confirm(_ call: CAPPluginCall) {
|
|
11685
|
+
deadline?.cancel()
|
|
11686
|
+
deadline = nil
|
|
11687
|
+
call.resolve()
|
|
11688
|
+
}
|
|
11689
|
+
}
|
|
11690
|
+
${PLUGIN_END}
|
|
11691
|
+
`;
|
|
11692
|
+
var configureIos3 = async (config, enabled) => {
|
|
11693
|
+
const path = join21(config.nativeProjectDirectory, "ios/App/App/AppDelegate.swift");
|
|
11694
|
+
const capacitorConfigPath = join21(config.nativeProjectDirectory, "ios/App/App/capacitor.config.json");
|
|
11695
|
+
let source = await readFile19(path, "utf8");
|
|
11537
11696
|
const launch = source.indexOf("didFinishLaunchingWithOptions");
|
|
11538
11697
|
const brace = launch < 0 ? -1 : source.indexOf("{", launch);
|
|
11539
|
-
const
|
|
11540
|
-
|
|
11541
|
-
|
|
11542
|
-
|
|
11543
|
-
|
|
11544
|
-
|
|
11545
|
-
|
|
11546
|
-
|
|
11547
|
-
|
|
11548
|
-
|
|
11549
|
-
|
|
11550
|
-
|
|
11551
|
-
|
|
11552
|
-
|
|
11553
|
-
|
|
11554
|
-
|
|
11555
|
-
|
|
11698
|
+
const launchRegion = enabled ? ` ${START}
|
|
11699
|
+
AbsoluteMobileUpdateWatchdogPlugin.recoverInterruptedBoot()
|
|
11700
|
+
${END}
|
|
11701
|
+
` : "";
|
|
11702
|
+
source = replaceMarkedRegion(source, START, END, launchRegion, brace < 0 ? -1 : source.indexOf(`
|
|
11703
|
+
`, brace) + 1, "Could not find a safe iOS location for mobile update recovery.");
|
|
11704
|
+
const plugin = enabled ? iosPluginRegion(config.updates?.bootTimeoutMs ?? 20000) : "";
|
|
11705
|
+
source = replaceMarkedRegion(source, PLUGIN_START, PLUGIN_END, plugin, source.length, "Could not find a safe iOS location for the mobile update watchdog.");
|
|
11706
|
+
const capacitorConfigSource2 = await readFile19(capacitorConfigPath, "utf8");
|
|
11707
|
+
let capacitorConfig;
|
|
11708
|
+
try {
|
|
11709
|
+
const value = JSON.parse(capacitorConfigSource2);
|
|
11710
|
+
if (!isRecord11(value))
|
|
11711
|
+
throw new TypeError;
|
|
11712
|
+
capacitorConfig = value;
|
|
11713
|
+
} catch {
|
|
11714
|
+
throw new TypeError("iOS capacitor.config.json is invalid; run Capacitor sync before projecting mobile updates.");
|
|
11715
|
+
}
|
|
11716
|
+
const packageClassList = Reflect.get(capacitorConfig, "packageClassList");
|
|
11717
|
+
if (!Array.isArray(packageClassList))
|
|
11718
|
+
throw new TypeError("iOS capacitor.config.json has no packageClassList; run Capacitor sync before projecting mobile updates.");
|
|
11719
|
+
const classes = packageClassList.filter((value) => typeof value === "string");
|
|
11720
|
+
const nextClasses = enabled ? [...new Set([...classes, "AbsoluteMobileUpdateWatchdogPlugin"])] : classes.filter((value) => value !== "AbsoluteMobileUpdateWatchdogPlugin");
|
|
11721
|
+
const nextCapacitorConfig = `${JSON.stringify({ ...capacitorConfig, packageClassList: nextClasses }, null, "\t")}
|
|
11722
|
+
`;
|
|
11723
|
+
const changed = await Promise.all([
|
|
11724
|
+
writeChanged(path, source),
|
|
11725
|
+
writeChanged(capacitorConfigPath, nextCapacitorConfig)
|
|
11726
|
+
]);
|
|
11727
|
+
return changed.some(Boolean);
|
|
11728
|
+
};
|
|
11729
|
+
var androidPluginSource = (packageName, timeoutMs) => `package ${packageName};
|
|
11730
|
+
|
|
11731
|
+
import android.app.Activity;
|
|
11732
|
+
import android.content.Context;
|
|
11733
|
+
import android.content.SharedPreferences;
|
|
11734
|
+
import android.os.Handler;
|
|
11735
|
+
import android.os.Looper;
|
|
11736
|
+
import com.getcapacitor.JSObject;
|
|
11737
|
+
import com.getcapacitor.Plugin;
|
|
11738
|
+
import com.getcapacitor.PluginCall;
|
|
11739
|
+
import com.getcapacitor.PluginMethod;
|
|
11740
|
+
import com.getcapacitor.annotation.CapacitorPlugin;
|
|
11741
|
+
import java.io.File;
|
|
11742
|
+
import org.json.JSONArray;
|
|
11743
|
+
import org.json.JSONObject;
|
|
11744
|
+
|
|
11745
|
+
@CapacitorPlugin(name = "AbsoluteMobileUpdateWatchdog")
|
|
11746
|
+
public final class AbsoluteMobileUpdateWatchdogPlugin extends Plugin {
|
|
11747
|
+
private static final String STATE_KEY = "absolute.mobile.update.state.v1";
|
|
11748
|
+
private static final String RELEASE_PATTERN = "^amu_[a-f0-9]{64}$";
|
|
11749
|
+
private final Handler handler = new Handler(Looper.getMainLooper());
|
|
11750
|
+
private Runnable deadline;
|
|
11751
|
+
|
|
11752
|
+
private static SharedPreferences statePreferences(Context context) {
|
|
11753
|
+
return context.getSharedPreferences("CapacitorStorage", Activity.MODE_PRIVATE);
|
|
11754
|
+
}
|
|
11755
|
+
|
|
11756
|
+
private static JSONObject state(Context context) {
|
|
11757
|
+
try {
|
|
11758
|
+
return new JSONObject(statePreferences(context).getString(STATE_KEY, "{}"));
|
|
11759
|
+
} catch (Exception ignored) {
|
|
11760
|
+
return new JSONObject();
|
|
11761
|
+
}
|
|
11762
|
+
}
|
|
11763
|
+
|
|
11764
|
+
private static void write(Context context, JSONObject state) {
|
|
11765
|
+
statePreferences(context).edit().putString(STATE_KEY, state.toString()).commit();
|
|
11766
|
+
}
|
|
11767
|
+
|
|
11768
|
+
private static File snapshotRoot(Context context) {
|
|
11769
|
+
return new File(context.getFilesDir(), "NoCloud/ionic_built_snapshots");
|
|
11770
|
+
}
|
|
11771
|
+
|
|
11772
|
+
private static boolean validRelease(String release) {
|
|
11773
|
+
return release != null && release.matches(RELEASE_PATTERN);
|
|
11774
|
+
}
|
|
11775
|
+
|
|
11776
|
+
private static boolean inside(File root, String path) {
|
|
11777
|
+
try {
|
|
11778
|
+
String base = root.getCanonicalPath() + File.separator;
|
|
11779
|
+
return new File(path).getCanonicalPath().startsWith(base);
|
|
11780
|
+
} catch (Exception ignored) {
|
|
11781
|
+
return false;
|
|
11782
|
+
}
|
|
11783
|
+
}
|
|
11784
|
+
|
|
11785
|
+
private static void deleteTree(File file) {
|
|
11786
|
+
File[] children = file.listFiles();
|
|
11787
|
+
if (children != null) for (File child : children) deleteTree(child);
|
|
11788
|
+
file.delete();
|
|
11789
|
+
}
|
|
11790
|
+
|
|
11791
|
+
private static Recovery recover(Context context, String reason) {
|
|
11792
|
+
JSONObject value = state(context);
|
|
11793
|
+
String release = value.optString("pendingRelease", "");
|
|
11794
|
+
if (!validRelease(release)) return null;
|
|
11795
|
+
String previous = value.optString("previousPath", "");
|
|
11796
|
+
boolean hasActive = validRelease(value.optString("activeRelease", ""));
|
|
11797
|
+
long started = value.optLong("pendingStartedAt", System.currentTimeMillis());
|
|
11798
|
+
long duration = Math.max(0, System.currentTimeMillis() - started);
|
|
11799
|
+
value.remove("pendingRelease");
|
|
11800
|
+
value.remove("pendingStartedAt");
|
|
11801
|
+
value.remove("previousPath");
|
|
11802
|
+
value.remove("readyRelease");
|
|
11803
|
+
try {
|
|
11804
|
+
JSONArray prior = value.optJSONArray("quarantinedReleases");
|
|
11805
|
+
JSONArray quarantined = new JSONArray();
|
|
11806
|
+
if (prior != null) {
|
|
11807
|
+
int start = Math.max(0, prior.length() - 7);
|
|
11808
|
+
for (int index = start; index < prior.length(); index++) {
|
|
11809
|
+
String candidate = prior.optString(index, "");
|
|
11810
|
+
if (validRelease(candidate) && !release.equals(candidate)) quarantined.put(candidate);
|
|
11811
|
+
}
|
|
11556
11812
|
}
|
|
11813
|
+
quarantined.put(release);
|
|
11814
|
+
value.put("quarantinedReleases", quarantined);
|
|
11815
|
+
value.put("recovery", new JSONObject()
|
|
11816
|
+
.put("durationMs", duration)
|
|
11817
|
+
.put("reason", reason)
|
|
11818
|
+
.put("releaseId", release));
|
|
11819
|
+
} catch (Exception ignored) {}
|
|
11820
|
+
write(context, value);
|
|
11821
|
+
|
|
11822
|
+
SharedPreferences webView = context.getSharedPreferences("CapWebViewSettings", Activity.MODE_PRIVATE);
|
|
11823
|
+
if (hasActive && inside(snapshotRoot(context), previous)) {
|
|
11824
|
+
webView.edit().putString("serverBasePath", previous).commit();
|
|
11825
|
+
} else {
|
|
11826
|
+
webView.edit().remove("serverBasePath").commit();
|
|
11557
11827
|
}
|
|
11828
|
+
deleteTree(new File(snapshotRoot(context), release));
|
|
11829
|
+
return new Recovery(previous, hasActive);
|
|
11830
|
+
}
|
|
11831
|
+
|
|
11832
|
+
public static void recoverInterruptedBoot(Context context) {
|
|
11833
|
+
recover(context, "boot-interrupted");
|
|
11834
|
+
}
|
|
11835
|
+
|
|
11836
|
+
@PluginMethod
|
|
11837
|
+
public void arm(PluginCall call) {
|
|
11838
|
+
String requested = call.getString("releaseId");
|
|
11839
|
+
if (!validRelease(requested) || !requested.equals(state(getContext()).optString("pendingRelease", ""))) {
|
|
11840
|
+
call.reject("Mobile update watchdog cannot arm an unknown release.");
|
|
11841
|
+
return;
|
|
11842
|
+
}
|
|
11843
|
+
if (deadline != null) handler.removeCallbacks(deadline);
|
|
11844
|
+
deadline = () -> {
|
|
11845
|
+
Recovery recovery = recover(getContext(), "boot-timeout");
|
|
11846
|
+
if (recovery == null || bridge == null) return;
|
|
11847
|
+
if (recovery.hasActive && inside(snapshotRoot(getContext()), recovery.previousPath)) {
|
|
11848
|
+
bridge.setServerBasePath(recovery.previousPath);
|
|
11849
|
+
} else {
|
|
11850
|
+
bridge.setServerAssetPath("public");
|
|
11851
|
+
}
|
|
11852
|
+
};
|
|
11853
|
+
handler.postDelayed(deadline, ${timeoutMs}L);
|
|
11854
|
+
call.resolve(new JSObject());
|
|
11855
|
+
}
|
|
11856
|
+
|
|
11857
|
+
@PluginMethod
|
|
11858
|
+
public void confirm(PluginCall call) {
|
|
11859
|
+
if (deadline != null) handler.removeCallbacks(deadline);
|
|
11860
|
+
deadline = null;
|
|
11861
|
+
call.resolve(new JSObject());
|
|
11862
|
+
}
|
|
11863
|
+
|
|
11864
|
+
private static final class Recovery {
|
|
11865
|
+
final String previousPath;
|
|
11866
|
+
final boolean hasActive;
|
|
11867
|
+
Recovery(String previousPath, boolean hasActive) {
|
|
11868
|
+
this.previousPath = previousPath;
|
|
11869
|
+
this.hasActive = hasActive;
|
|
11870
|
+
}
|
|
11871
|
+
}
|
|
11872
|
+
}
|
|
11873
|
+
`;
|
|
11874
|
+
var androidActivityInRoot = async (root) => {
|
|
11875
|
+
let entries;
|
|
11876
|
+
try {
|
|
11877
|
+
entries = await readdir5(root, { recursive: true });
|
|
11878
|
+
} catch (error) {
|
|
11879
|
+
if (typeof error === "object" && error !== null && Reflect.get(error, "code") === "ENOENT")
|
|
11880
|
+
return;
|
|
11881
|
+
throw error;
|
|
11882
|
+
}
|
|
11883
|
+
const candidate = entries.find((entry) => entry.endsWith("/MainActivity.java") || entry.endsWith("/MainActivity.kt") || entry === "MainActivity.java" || entry === "MainActivity.kt");
|
|
11884
|
+
return candidate ? join21(root, candidate) : undefined;
|
|
11885
|
+
};
|
|
11886
|
+
var androidActivity = async (config) => {
|
|
11887
|
+
const roots = [
|
|
11888
|
+
join21(config.nativeProjectDirectory, "android/app/src/main/java"),
|
|
11889
|
+
join21(config.nativeProjectDirectory, "android/app/src/main/kotlin")
|
|
11890
|
+
];
|
|
11891
|
+
const candidates = await Promise.all(roots.map(androidActivityInRoot));
|
|
11892
|
+
const candidate = candidates.find((value) => value !== undefined);
|
|
11893
|
+
if (candidate)
|
|
11894
|
+
return candidate;
|
|
11895
|
+
throw new TypeError("Android MainActivity.java or MainActivity.kt was not found.");
|
|
11896
|
+
};
|
|
11897
|
+
var androidManagedRegion = (kotlin, generatedMethod) => {
|
|
11898
|
+
if (generatedMethod)
|
|
11899
|
+
return kotlin ? ` ${START}
|
|
11900
|
+
override fun onCreate(savedInstanceState: android.os.Bundle?) {
|
|
11901
|
+
AbsoluteMobileUpdateWatchdogPlugin.recoverInterruptedBoot(this)
|
|
11902
|
+
registerPlugin(AbsoluteMobileUpdateWatchdogPlugin::class.java)
|
|
11903
|
+
super.onCreate(savedInstanceState)
|
|
11904
|
+
}
|
|
11905
|
+
${END}
|
|
11906
|
+
` : ` ${START}
|
|
11907
|
+
@Override
|
|
11908
|
+
public void onCreate(android.os.Bundle savedInstanceState) {
|
|
11909
|
+
AbsoluteMobileUpdateWatchdogPlugin.recoverInterruptedBoot(this);
|
|
11910
|
+
registerPlugin(AbsoluteMobileUpdateWatchdogPlugin.class);
|
|
11911
|
+
super.onCreate(savedInstanceState);
|
|
11912
|
+
}
|
|
11913
|
+
${END}
|
|
11914
|
+
`;
|
|
11915
|
+
return kotlin ? ` ${START}
|
|
11916
|
+
AbsoluteMobileUpdateWatchdogPlugin.recoverInterruptedBoot(this)
|
|
11917
|
+
registerPlugin(AbsoluteMobileUpdateWatchdogPlugin::class.java)
|
|
11918
|
+
${END}
|
|
11919
|
+
` : ` ${START}
|
|
11920
|
+
AbsoluteMobileUpdateWatchdogPlugin.recoverInterruptedBoot(this);
|
|
11921
|
+
registerPlugin(AbsoluteMobileUpdateWatchdogPlugin.class);
|
|
11922
|
+
${END}
|
|
11923
|
+
`;
|
|
11924
|
+
};
|
|
11925
|
+
var injectAndroidActivity = (source, enabled) => {
|
|
11926
|
+
const kotlin = /\bfun\s+onCreate\s*\(/u.test(source) || source.includes("BridgeActivity()");
|
|
11927
|
+
const existingStart = source.indexOf(START);
|
|
11928
|
+
const existingEnd = source.indexOf(END);
|
|
11929
|
+
if (existingStart < 0 !== existingEnd < 0)
|
|
11930
|
+
throw new TypeError("AbsoluteJS mobile update markers are malformed.");
|
|
11931
|
+
if (existingStart >= 0) {
|
|
11932
|
+
const from = source.lastIndexOf(`
|
|
11933
|
+
`, existingStart) + 1;
|
|
11934
|
+
const newline = source.indexOf(`
|
|
11935
|
+
`, existingEnd + END.length);
|
|
11936
|
+
const through = newline < 0 ? source.length : newline + 1;
|
|
11937
|
+
const owned = source.slice(from, through);
|
|
11938
|
+
const generatedMethod = owned.includes("onCreate(");
|
|
11939
|
+
if (!enabled)
|
|
11940
|
+
return `${source.slice(0, from)}${source.slice(through)}`;
|
|
11941
|
+
const region2 = androidManagedRegion(kotlin, generatedMethod);
|
|
11942
|
+
return `${source.slice(0, from)}${region2}${source.slice(through)}`;
|
|
11943
|
+
}
|
|
11944
|
+
if (!enabled)
|
|
11945
|
+
return source;
|
|
11946
|
+
const onCreate = kotlin ? source.search(/\boverride\s+fun\s+onCreate\s*\([^)]*\)\s*\{/u) : source.search(/\b(?:public|protected)\s+void\s+onCreate\s*\([^)]*\)\s*\{/u);
|
|
11947
|
+
if (onCreate >= 0) {
|
|
11948
|
+
const brace = source.indexOf("{", onCreate);
|
|
11949
|
+
const insert = source.indexOf(`
|
|
11950
|
+
`, brace) + 1;
|
|
11951
|
+
const region2 = kotlin ? ` ${START}
|
|
11952
|
+
AbsoluteMobileUpdateWatchdogPlugin.recoverInterruptedBoot(this)
|
|
11953
|
+
registerPlugin(AbsoluteMobileUpdateWatchdogPlugin::class.java)
|
|
11954
|
+
${END}
|
|
11955
|
+
` : ` ${START}
|
|
11956
|
+
AbsoluteMobileUpdateWatchdogPlugin.recoverInterruptedBoot(this);
|
|
11957
|
+
registerPlugin(AbsoluteMobileUpdateWatchdogPlugin.class);
|
|
11558
11958
|
${END}
|
|
11559
11959
|
`;
|
|
11960
|
+
return `${source.slice(0, insert)}${region2}${source.slice(insert)}`;
|
|
11961
|
+
}
|
|
11962
|
+
const close = source.lastIndexOf("}");
|
|
11963
|
+
if (close < 0)
|
|
11964
|
+
throw new TypeError("Could not find the Android MainActivity class body.");
|
|
11965
|
+
const region = kotlin ? ` ${START}
|
|
11966
|
+
override fun onCreate(savedInstanceState: android.os.Bundle?) {
|
|
11967
|
+
AbsoluteMobileUpdateWatchdogPlugin.recoverInterruptedBoot(this)
|
|
11968
|
+
registerPlugin(AbsoluteMobileUpdateWatchdogPlugin::class.java)
|
|
11969
|
+
super.onCreate(savedInstanceState)
|
|
11970
|
+
}
|
|
11971
|
+
${END}
|
|
11972
|
+
` : ` ${START}
|
|
11973
|
+
@Override
|
|
11974
|
+
public void onCreate(android.os.Bundle savedInstanceState) {
|
|
11975
|
+
AbsoluteMobileUpdateWatchdogPlugin.recoverInterruptedBoot(this);
|
|
11976
|
+
registerPlugin(AbsoluteMobileUpdateWatchdogPlugin.class);
|
|
11977
|
+
super.onCreate(savedInstanceState);
|
|
11978
|
+
}
|
|
11979
|
+
${END}
|
|
11980
|
+
`;
|
|
11981
|
+
const separator = source[close - 1] === `
|
|
11982
|
+
` ? "" : `
|
|
11983
|
+
`;
|
|
11984
|
+
return `${source.slice(0, close)}${separator}${region}${source.slice(close)}`;
|
|
11985
|
+
};
|
|
11986
|
+
var configureAndroid3 = async (config, enabled) => {
|
|
11987
|
+
const activityPath = await androidActivity(config);
|
|
11988
|
+
const activity = await readFile19(activityPath, "utf8");
|
|
11989
|
+
const packageName = activity.match(/^\s*package\s+([A-Za-z0-9_.]+)\s*[;\n]/mu)?.[1];
|
|
11990
|
+
if (!packageName)
|
|
11991
|
+
throw new TypeError("Android MainActivity package declaration was not found.");
|
|
11992
|
+
const packagePath = packageName.replaceAll(".", "/");
|
|
11993
|
+
const pluginPath = join21(config.nativeProjectDirectory, "android/app/src/main/java", packagePath, ANDROID_PLUGIN);
|
|
11994
|
+
const changed = await Promise.all([
|
|
11995
|
+
writeChanged(activityPath, injectAndroidActivity(activity, enabled)),
|
|
11996
|
+
enabled ? writeChanged(pluginPath, androidPluginSource(packageName, config.updates?.bootTimeoutMs ?? 20000)) : removeOwnedFile(pluginPath)
|
|
11997
|
+
]);
|
|
11998
|
+
return changed.some(Boolean);
|
|
11999
|
+
};
|
|
11560
12000
|
var applyAbsoluteNativeUpdates = async (config, platforms = config.platforms) => {
|
|
11561
|
-
if (config.engine !== "capacitor"
|
|
12001
|
+
if (config.engine !== "capacitor")
|
|
11562
12002
|
return { changed: false };
|
|
11563
|
-
const
|
|
11564
|
-
const
|
|
11565
|
-
|
|
11566
|
-
|
|
12003
|
+
const enabled = config.updates !== undefined;
|
|
12004
|
+
const changed = [];
|
|
12005
|
+
if (platforms.includes("ios"))
|
|
12006
|
+
changed.push(await configureIos3(config, enabled));
|
|
12007
|
+
if (platforms.includes("android"))
|
|
12008
|
+
changed.push(await configureAndroid3(config, enabled));
|
|
12009
|
+
return { changed: changed.some(Boolean) };
|
|
11567
12010
|
};
|
|
11568
12011
|
// src/mobile/releasePublisher.ts
|
|
11569
12012
|
import { access as access12 } from "fs/promises";
|
|
@@ -11590,8 +12033,8 @@ var prepareAbsoluteAndroidRelease = async (publisher, options) => {
|
|
|
11590
12033
|
}
|
|
11591
12034
|
return versionCode;
|
|
11592
12035
|
};
|
|
11593
|
-
var
|
|
11594
|
-
var isPublisher = (value) =>
|
|
12036
|
+
var isRecord12 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
12037
|
+
var isPublisher = (value) => isRecord12(value) && typeof value.publish === "function";
|
|
11595
12038
|
var publisherModulePath = (projectRoot, requested) => {
|
|
11596
12039
|
const root = resolve15(projectRoot);
|
|
11597
12040
|
const path = resolve15(root, requested);
|
|
@@ -11607,7 +12050,7 @@ var loadAbsoluteNativeReleasePublisher = async (projectRoot, requestedModulePath
|
|
|
11607
12050
|
throw new TypeError(`Native release registry module does not exist: ${modulePath}`);
|
|
11608
12051
|
});
|
|
11609
12052
|
const loaded = await import(pathToFileURL3(modulePath).href);
|
|
11610
|
-
const publisher =
|
|
12053
|
+
const publisher = isRecord12(loaded) ? loaded.default ?? loaded.registry : undefined;
|
|
11611
12054
|
if (!isPublisher(publisher)) {
|
|
11612
12055
|
throw new TypeError("Native release registry module must default-export a registry with publish(options).");
|
|
11613
12056
|
}
|
|
@@ -11665,7 +12108,7 @@ var publishAbsoluteIosRelease = async (options) => {
|
|
|
11665
12108
|
};
|
|
11666
12109
|
// src/mobile/routeMetadataTransform.ts
|
|
11667
12110
|
import { existsSync as existsSync7, readFileSync as readFileSync7 } from "fs";
|
|
11668
|
-
import { dirname as
|
|
12111
|
+
import { dirname as dirname14, extname as extname5, relative as relative13, resolve as resolve16 } from "path";
|
|
11669
12112
|
import ts2 from "typescript";
|
|
11670
12113
|
var ROUTE_METHODS = new Set(["get", "head"]);
|
|
11671
12114
|
var SOURCE_FILTER = /\.[cm]?[jt]sx?$/;
|
|
@@ -11716,7 +12159,7 @@ var PAGE_HANDLERS = new Map([
|
|
|
11716
12159
|
]
|
|
11717
12160
|
]);
|
|
11718
12161
|
var posixPath = (value) => value.replace(/\\/g, "/");
|
|
11719
|
-
var findTsconfig = (entry, projectRoot) => ts2.findConfigFile(
|
|
12162
|
+
var findTsconfig = (entry, projectRoot) => ts2.findConfigFile(dirname14(entry), existsSync7, "tsconfig.json") ?? ts2.findConfigFile(projectRoot, existsSync7, "tsconfig.json");
|
|
11720
12163
|
var createProgram = (entry, projectRoot) => {
|
|
11721
12164
|
const configPath2 = findTsconfig(entry, projectRoot);
|
|
11722
12165
|
if (!configPath2) {
|
|
@@ -11728,7 +12171,7 @@ var createProgram = (entry, projectRoot) => {
|
|
|
11728
12171
|
target: ts2.ScriptTarget.ESNext
|
|
11729
12172
|
});
|
|
11730
12173
|
}
|
|
11731
|
-
const parsed = ts2.parseJsonConfigFileContent(ts2.readConfigFile(configPath2, (path) => readFileSync7(path, "utf8")).config, ts2.sys,
|
|
12174
|
+
const parsed = ts2.parseJsonConfigFileContent(ts2.readConfigFile(configPath2, (path) => readFileSync7(path, "utf8")).config, ts2.sys, dirname14(configPath2));
|
|
11732
12175
|
if (!parsed.fileNames.includes(entry))
|
|
11733
12176
|
parsed.fileNames.push(entry);
|
|
11734
12177
|
return ts2.createProgram(parsed.fileNames, parsed.options);
|
|
@@ -12769,6 +13212,8 @@ var createAbsoluteMobileUpdateClient = (options) => {
|
|
|
12769
13212
|
throw new TypeError("Mobile update signature verification failed.");
|
|
12770
13213
|
if (manifest.releaseId === options.config.currentReleaseId)
|
|
12771
13214
|
return { kind: "current" };
|
|
13215
|
+
if (options.config.blockedReleaseIds?.includes(manifest.releaseId))
|
|
13216
|
+
return { kind: "quarantined", releaseId: manifest.releaseId };
|
|
12772
13217
|
if (!download)
|
|
12773
13218
|
return { kind: "update-available", manifest };
|
|
12774
13219
|
await options.store.begin(manifest);
|
|
@@ -12796,21 +13241,21 @@ import { pathToFileURL as pathToFileURL4 } from "url";
|
|
|
12796
13241
|
import { createHash as createHash12, sign, verify } from "crypto";
|
|
12797
13242
|
import {
|
|
12798
13243
|
cp as cp3,
|
|
12799
|
-
mkdir as
|
|
13244
|
+
mkdir as mkdir14,
|
|
12800
13245
|
mkdtemp as mkdtemp8,
|
|
12801
|
-
readdir as
|
|
13246
|
+
readdir as readdir6,
|
|
12802
13247
|
readFile as readFile20,
|
|
12803
13248
|
rename as rename15,
|
|
12804
|
-
rm as
|
|
13249
|
+
rm as rm12,
|
|
12805
13250
|
stat as stat4,
|
|
12806
13251
|
writeFile as writeFile17
|
|
12807
13252
|
} from "fs/promises";
|
|
12808
|
-
import { dirname as
|
|
13253
|
+
import { dirname as dirname15, join as join22, relative as relative14, resolve as resolve17 } from "path";
|
|
12809
13254
|
var UPDATE_MANIFEST_FILE = "update.json";
|
|
12810
13255
|
var UPDATE_FILES_DIRECTORY = "files";
|
|
12811
13256
|
var sha2562 = (value) => createHash12("sha256").update(value).digest("hex");
|
|
12812
13257
|
var listFiles = async (root, directory = root) => {
|
|
12813
|
-
const entries = await
|
|
13258
|
+
const entries = await readdir6(directory, { withFileTypes: true });
|
|
12814
13259
|
const paths = await Promise.all(entries.map(async (entry) => {
|
|
12815
13260
|
const path = join22(directory, entry.name);
|
|
12816
13261
|
if (entry.isDirectory())
|
|
@@ -12860,7 +13305,7 @@ var buildAbsoluteMobileUpdate = async (options) => {
|
|
|
12860
13305
|
value: signature.toString("base64")
|
|
12861
13306
|
}
|
|
12862
13307
|
});
|
|
12863
|
-
await
|
|
13308
|
+
await mkdir14(outputRoot, { recursive: true });
|
|
12864
13309
|
const outputDirectory = join22(outputRoot, manifest.releaseId);
|
|
12865
13310
|
const staging = await mkdtemp8(join22(outputRoot, ".stage-"));
|
|
12866
13311
|
try {
|
|
@@ -12872,7 +13317,7 @@ var buildAbsoluteMobileUpdate = async (options) => {
|
|
|
12872
13317
|
`);
|
|
12873
13318
|
await rename15(staging, outputDirectory);
|
|
12874
13319
|
} catch (error) {
|
|
12875
|
-
await
|
|
13320
|
+
await rm12(staging, { force: true, recursive: true });
|
|
12876
13321
|
if (typeof error === "object" && error !== null && Reflect.get(error, "code") === "EEXIST")
|
|
12877
13322
|
throw new TypeError(`Mobile update ${manifest.releaseId} already exists.`, { cause: error });
|
|
12878
13323
|
throw error;
|
|
@@ -12888,7 +13333,7 @@ var copyAbsoluteMobileUpdateFile = async (updateDirectory, path, destination) =>
|
|
|
12888
13333
|
const root = resolve17(updateDirectory, UPDATE_FILES_DIRECTORY);
|
|
12889
13334
|
if (!source.startsWith(`${root}/`))
|
|
12890
13335
|
throw new TypeError("Update file escaped its release.");
|
|
12891
|
-
await
|
|
13336
|
+
await mkdir14(dirname15(destination), { recursive: true });
|
|
12892
13337
|
await cp3(source, destination, { force: true });
|
|
12893
13338
|
};
|
|
12894
13339
|
var readAbsoluteMobileUpdate = async (directory) => parseAbsoluteMobileUpdateManifest(JSON.parse(await readFile20(join22(resolve17(directory), UPDATE_MANIFEST_FILE), "utf8")));
|
|
@@ -13196,5 +13641,5 @@ export {
|
|
|
13196
13641
|
writeAbsoluteMobileGithubWorkflow
|
|
13197
13642
|
};
|
|
13198
13643
|
|
|
13199
|
-
//# debugId=
|
|
13644
|
+
//# debugId=15870DEEAD912CA464756E2164756E21
|
|
13200
13645
|
//# sourceMappingURL=index.js.map
|