@absolutejs/absolute 0.20.0-beta.59 → 0.20.0-beta.60

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.
@@ -167,7 +167,8 @@ var init_startupBanner = __esm(() => {
167
167
 
168
168
  // src/mobile/config.ts
169
169
  import { resolve as resolve6 } from "path";
170
- var APP_ID_PATTERN, SCHEME_PATTERN, APPLE_APP_ID_PREFIX_PATTERN, CERTIFICATE_FINGERPRINT_PATTERN, HOSTNAME_PATTERN, EXPO_RESERVED_ROUTE_PREFIXES, resolveProjectPath = (projectRoot, value, field2) => {
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
172
  const root = resolve6(projectRoot);
172
173
  const path = resolve6(root, value);
173
174
  if (path !== root && !path.startsWith(`${root}/`)) {
@@ -240,7 +241,52 @@ var APP_ID_PATTERN, SCHEME_PATTERN, APPLE_APP_ID_PREFIX_PATTERN, CERTIFICATE_FIN
240
241
  }
241
242
  return value.match(/.{2}/g)?.join(":") ?? value;
242
243
  }))
243
- ].sort(), validateExpoNativeRouteSegment = (path, segment, index, count, parameters) => {
244
+ ].sort(), normalizeUpdates = (config, productionOrigin) => {
245
+ if (!config.updates)
246
+ return;
247
+ const channel = requireText(config.updates.channel ?? "production", "mobile.updates.channel");
248
+ if (!UPDATE_NAME_PATTERN.test(channel))
249
+ throw new TypeError("mobile.updates.channel contains unsupported characters.");
250
+ const manifestUrl = new URL(config.updates.manifestUrl ?? `/__absolute/mobile/updates/${encodeURIComponent(channel)}/update.json`, `${productionOrigin}/`);
251
+ const loopback = manifestUrl.hostname === "localhost" || manifestUrl.hostname === "127.0.0.1" || manifestUrl.hostname === "[::1]";
252
+ if (manifestUrl.protocol !== "https:" && !loopback)
253
+ throw new TypeError("mobile.updates.manifestUrl must use HTTPS outside loopback development.");
254
+ if (manifestUrl.username || manifestUrl.password || manifestUrl.hash)
255
+ throw new TypeError("mobile.updates.manifestUrl cannot contain credentials or a fragment.");
256
+ const entries = Object.entries(config.updates.publicKeys).sort(([left], [right]) => left.localeCompare(right));
257
+ if (entries.length === 0)
258
+ throw new TypeError("mobile.updates.publicKeys must contain at least one key.");
259
+ const publicKeys = Object.fromEntries(entries.map(([keyId, key]) => {
260
+ if (!UPDATE_NAME_PATTERN.test(keyId))
261
+ throw new TypeError("mobile.updates.publicKeys contains an invalid key ID.");
262
+ const normalized = requireText(key, `mobile.updates.publicKeys.${keyId}`);
263
+ if (!UPDATE_PUBLIC_KEY_PATTERN.test(normalized))
264
+ throw new TypeError(`mobile.updates.publicKeys.${keyId} must be base64-encoded ECDSA P-256 SPKI DER.`);
265
+ let decoded;
266
+ try {
267
+ decoded = Buffer.from(normalized, "base64");
268
+ } catch {
269
+ throw new TypeError(`mobile.updates.publicKeys.${keyId} must be canonical base64.`);
270
+ }
271
+ let keyType;
272
+ let namedCurve;
273
+ try {
274
+ const publicKey = createPublicKey({
275
+ format: "der",
276
+ key: decoded,
277
+ type: "spki"
278
+ });
279
+ keyType = publicKey.asymmetricKeyType;
280
+ namedCurve = publicKey.asymmetricKeyDetails?.namedCurve;
281
+ } catch {
282
+ keyType = undefined;
283
+ }
284
+ if (decoded.toString("base64") !== normalized || keyType !== "ec" || namedCurve !== "prime256v1")
285
+ throw new TypeError(`mobile.updates.publicKeys.${keyId} is not an ECDSA P-256 SPKI public key.`);
286
+ return [keyId, normalized];
287
+ }));
288
+ return { channel, manifestUrl: manifestUrl.href, publicKeys };
289
+ }, validateExpoNativeRouteSegment = (path, segment, index, count, parameters) => {
244
290
  if (segment === "*" && (index !== count - 1 || count === 1)) {
245
291
  throw new TypeError(`mobile.routes.native route ${path} must use * once, as the final segment after a static or parameterized prefix.`);
246
292
  }
@@ -298,6 +344,7 @@ var APP_ID_PATTERN, SCHEME_PATTERN, APPLE_APP_ID_PREFIX_PATTERN, CERTIFICATE_FIN
298
344
  if (deepLinkScheme && !SCHEME_PATTERN.test(deepLinkScheme)) {
299
345
  throw new TypeError("mobile.deepLinks.scheme is not a valid URL scheme.");
300
346
  }
347
+ const updates = normalizeUpdates(config, productionOrigin);
301
348
  return {
302
349
  androidCertificateFingerprints: normalizeCertificateFingerprints(config.deepLinks?.android?.sha256CertificateFingerprints),
303
350
  appId,
@@ -314,7 +361,8 @@ var APP_ID_PATTERN, SCHEME_PATTERN, APPLE_APP_ID_PREFIX_PATTERN, CERTIFICATE_FIN
314
361
  nativeProjectDirectory: resolveProjectPath(projectRoot, config.nativeProject?.directory ?? (config.engine === "expo" ? ".absolutejs/mobile/expo" : "mobile"), "mobile.nativeProject.directory"),
315
362
  platforms: normalizePlatforms(config.platforms),
316
363
  productionOrigin,
317
- pushAndroidGoogleServicesFile: resolveProjectPath(projectRoot, config.pushNotifications?.android?.googleServicesFile ?? "google-services.json", "mobile.pushNotifications.android.googleServicesFile")
364
+ pushAndroidGoogleServicesFile: resolveProjectPath(projectRoot, config.pushNotifications?.android?.googleServicesFile ?? "google-services.json", "mobile.pushNotifications.android.googleServicesFile"),
365
+ ...updates ? { updates } : {}
318
366
  };
319
367
  };
320
368
  var init_config = __esm(() => {
@@ -322,6 +370,8 @@ var init_config = __esm(() => {
322
370
  SCHEME_PATTERN = /^[a-z][a-z0-9+.-]*$/;
323
371
  APPLE_APP_ID_PREFIX_PATTERN = /^[A-Z0-9]{10}$/;
324
372
  CERTIFICATE_FINGERPRINT_PATTERN = /^[0-9A-F]{64}$/;
373
+ UPDATE_NAME_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/u;
374
+ UPDATE_PUBLIC_KEY_PATTERN = /^[A-Za-z0-9+/]+={0,2}$/u;
325
375
  HOSTNAME_PATTERN = /^(?=.{1,253}$)(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?)(?:\.(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?))*$/;
326
376
  EXPO_RESERVED_ROUTE_PREFIXES = new Set([
327
377
  "_expo",
@@ -7497,6 +7547,12 @@ var shellExpoDevicesModule = () => {
7497
7547
  return candidate;
7498
7548
  throw new TypeError("AbsoluteJS Expo device bridge module is missing.");
7499
7549
  };
7550
+ var shellUpdateModule = () => {
7551
+ const candidate = ["js", "ts"].map((extension) => join9(import.meta.dir, `shellUpdate.${extension}`)).find(existsSync2);
7552
+ if (candidate)
7553
+ return candidate;
7554
+ throw new TypeError("AbsoluteJS mobile update shell module is missing.");
7555
+ };
7500
7556
  var escapeHtml = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;");
7501
7557
  var contentSecurityPolicy = (productionOrigin) => {
7502
7558
  const backend = new URL(productionOrigin);
@@ -7557,7 +7613,7 @@ var resolveProjectImport = async (projectRoot, specifier) => {
7557
7613
  throw new TypeError(`${specifier} has an unsafe import entry.`);
7558
7614
  return resolved;
7559
7615
  };
7560
- var buildShellBootstrap = async (staging, auth, sync, storagePrefix, engine, deviceCapabilities, projectRoot) => {
7616
+ var buildShellBootstrap = async (staging, auth, sync, storagePrefix, engine, updates, deviceCapabilities, projectRoot) => {
7561
7617
  const capacitor = engine !== "expo";
7562
7618
  const shellCapabilities = capacitor ? deviceCapabilities.capabilities : [];
7563
7619
  const modulePath = shellBootstrapModule();
@@ -7565,12 +7621,13 @@ var buildShellBootstrap = async (staging, auth, sync, storagePrefix, engine, dev
7565
7621
  const syncFactory = capacitor ? "installAbsoluteMobileShellSync" : "installAbsoluteExpoShellSync";
7566
7622
  const authImport = auth ? `import { ${authFactory} } from ${JSON.stringify(capacitor ? shellAuthModule() : shellExpoAuthModule())};
7567
7623
  ` : "";
7568
- const options = auth ? `{ createAuth: ${authFactory}${sync ? `, installSync: ${syncFactory}` : ""} }` : "";
7569
7624
  const syncImport = sync ? `import { ${syncFactory} } from ${JSON.stringify(capacitor ? shellSyncModule() : shellExpoSyncModule())};
7570
7625
  ` : "";
7571
7626
  const pushIndex = shellCapabilities.indexOf("pushNotifications");
7572
7627
  const push = pushIndex !== -1;
7573
7628
  const pushImport = push ? `import { createAbsoluteMobileShellPush } from ${JSON.stringify(shellPushModule())};
7629
+ ` : "";
7630
+ const updateImport = updates ? `import { installAbsoluteMobileShellUpdates } from ${JSON.stringify(shellUpdateModule())};
7574
7631
  ` : "";
7575
7632
  const capabilityImports = (await Promise.all(shellCapabilities.map(async (name, index) => {
7576
7633
  const provider = deviceCapabilities.providers[name];
@@ -7587,15 +7644,21 @@ const absoluteMobilePushCapability = absoluteDeviceCapability${pushIndex}(absolu
7587
7644
  const baseAdapterModule = capacitor ? await resolveProjectImport(projectRoot, "@absolutejs/devices-capacitor") : shellExpoDevicesModule();
7588
7645
  const adapterImport = capacitor ? `import { installCapacitorDeviceAdapterIfNative } from ${JSON.stringify(baseAdapterModule)};` : `import { createAbsoluteExpoBridgeFetch, installAbsoluteExpoWebDeviceAdapter } from ${JSON.stringify(baseAdapterModule)};`;
7589
7646
  const adapterInstall = capacitor ? `installCapacitorDeviceAdapterIfNative({ storagePrefix: ${JSON.stringify(storagePrefix)}${capabilityOptions ? `, ${capabilityOptions}` : ""} });` : `installAbsoluteExpoWebDeviceAdapter(${JSON.stringify(deviceCapabilities.capabilities)});`;
7590
- let shellOptions = auth ? options : "{ createFetch: createAbsoluteExpoBridgeFetch }";
7591
- if (capacitor)
7592
- shellOptions = options;
7647
+ const shellOptionProperties = [];
7593
7648
  if (push) {
7594
- shellOptions = `{ createAuth: (config, options) => createAbsoluteMobileShellAuth(config, options), beforeSignOut: absoluteMobilePush.beforeSignOut, connectPush: (auth) => absoluteMobilePush.connect(auth, absoluteMobilePushCapability)${sync ? `, installSync: ${syncFactory}` : ""} }`;
7595
- }
7649
+ shellOptionProperties.push("createAuth: (config, options) => createAbsoluteMobileShellAuth(config, options)", "beforeSignOut: absoluteMobilePush.beforeSignOut", "connectPush: (auth) => absoluteMobilePush.connect(auth, absoluteMobilePushCapability)");
7650
+ } else if (auth)
7651
+ shellOptionProperties.push(`createAuth: ${authFactory}`);
7652
+ else if (!capacitor)
7653
+ shellOptionProperties.push("createFetch: createAbsoluteExpoBridgeFetch");
7654
+ if (sync)
7655
+ shellOptionProperties.push(`installSync: ${syncFactory}`);
7656
+ if (updates)
7657
+ shellOptionProperties.push("installUpdates: installAbsoluteMobileShellUpdates");
7658
+ const shellOptions = `{ ${shellOptionProperties.join(", ")} }`;
7596
7659
  await writeFile10(entryPath, `import { startAbsoluteMobileShell } from ${JSON.stringify(modulePath)};
7597
7660
  ${adapterImport}
7598
- ${authImport}${syncImport}${pushImport}${capabilityImports}
7661
+ ${authImport}${syncImport}${pushImport}${updateImport}${capabilityImports}
7599
7662
  ${pushSetup}${adapterInstall}
7600
7663
  void startAbsoluteMobileShell(${shellOptions});
7601
7664
  `);
@@ -7738,10 +7801,12 @@ var materializeAbsoluteCapacitorWebBundle = async (options) => {
7738
7801
  deviceCapabilities: options.deviceCapabilities.capabilities,
7739
7802
  entry: options.config.entry,
7740
7803
  format: ABSOLUTE_MOBILE_CLIENT_MANIFEST_FORMAT,
7804
+ nativeRuntime: options.runtimeFingerprint,
7741
7805
  pages,
7742
7806
  productionOrigin: options.config.productionOrigin,
7743
7807
  routes: options.artifact.routes,
7744
7808
  runtime: options.artifact.runtime,
7809
+ ...options.config.updates ? { updates: options.config.updates } : {},
7745
7810
  ...options.sync ? {
7746
7811
  sync: {
7747
7812
  background: {
@@ -7761,7 +7826,7 @@ var materializeAbsoluteCapacitorWebBundle = async (options) => {
7761
7826
  writeFile10(join9(staging, MANIFEST_FILE), `${JSON.stringify(manifest, null, "\t")}
7762
7827
  `),
7763
7828
  writeFile10(join9(staging, INDEX_FILE), indexHtml(options.config.appName, options.config.productionOrigin)),
7764
- buildShellBootstrap(staging, options.auth !== undefined, options.auth !== undefined && options.sync === true, `absolutejs.${options.auth?.clientId ?? options.config.appId}.`, options.config.engine, options.deviceCapabilities, options.projectRoot)
7829
+ buildShellBootstrap(staging, options.auth !== undefined, options.auth !== undefined && options.sync === true, `absolutejs.${options.auth?.clientId ?? options.config.appId}.`, options.config.engine, options.config.engine === "capacitor" && options.config.updates !== undefined, options.deviceCapabilities, options.projectRoot)
7765
7830
  ]);
7766
7831
  await installBundle(staging, destination);
7767
7832
  return manifest;
@@ -9261,6 +9326,149 @@ var syncAbsoluteExpoWebAssets = async (config) => {
9261
9326
  return { appBuild, assets: assets.length, bundleId, path: destination };
9262
9327
  };
9263
9328
 
9329
+ // src/mobile/updateRuntime.ts
9330
+ import { createHash as createHash11 } from "crypto";
9331
+
9332
+ // src/mobile/updateProtocol.ts
9333
+ var ABSOLUTE_MOBILE_UPDATE_FORMAT = 1;
9334
+ var ABSOLUTE_MOBILE_UPDATE_MAX_FILE_BYTES = 32 * 1024 * 1024;
9335
+ var ABSOLUTE_MOBILE_UPDATE_MAX_FILES = 1e4;
9336
+ var ABSOLUTE_MOBILE_UPDATE_MAX_TOTAL_BYTES = 128 * 1024 * 1024;
9337
+ var ABSOLUTE_MOBILE_UPDATE_SIGNATURE_ALGORITHM = "ecdsa-p256-sha256";
9338
+ var HASH_PATTERN = /^[a-f0-9]{64}$/u;
9339
+ var RELEASE_PATTERN = /^amu_[a-f0-9]{64}$/u;
9340
+ var KEY_ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/u;
9341
+ var CHANNEL_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/u;
9342
+ var isClassification = (value) => value === "bug-fix" || value === "content" || value === "security";
9343
+ var object3 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
9344
+ var canonicalValue = (value) => {
9345
+ if (Array.isArray(value))
9346
+ return value.map(canonicalValue);
9347
+ if (!object3(value))
9348
+ return value;
9349
+ return Object.fromEntries(Object.keys(value).sort().map((key) => [key, canonicalValue(value[key])]));
9350
+ };
9351
+ var absoluteMobileUpdateSigningPayload = (manifest) => new TextEncoder().encode(canonicalizeAbsoluteMobileUpdate(manifest));
9352
+ var canonicalizeAbsoluteMobileUpdate = (value) => JSON.stringify(canonicalValue(value));
9353
+ var requireText2 = (value, field2) => {
9354
+ if (typeof value !== "string" || value.length === 0)
9355
+ throw new TypeError(`${field2} must be a non-empty string.`);
9356
+ return value;
9357
+ };
9358
+ var normalizeAbsoluteMobileUpdatePath = (value) => {
9359
+ const path = requireText2(value, "Update file path").replaceAll("\\", "/");
9360
+ if (path.startsWith("/") || path.includes("\x00") || path.split("/").some((segment) => !segment || segment === "." || segment === "..")) {
9361
+ throw new TypeError("Update file paths must be normalized relative paths.");
9362
+ }
9363
+ return path;
9364
+ };
9365
+ var parseFile = (value) => {
9366
+ if (!object3(value))
9367
+ throw new TypeError("Update files must be objects.");
9368
+ const path = normalizeAbsoluteMobileUpdatePath(value.path);
9369
+ if (typeof value.bytes !== "number" || !Number.isSafeInteger(value.bytes) || value.bytes < 0 || value.bytes > ABSOLUTE_MOBILE_UPDATE_MAX_FILE_BYTES) {
9370
+ throw new TypeError(`Update file ${path} has an invalid byte length.`);
9371
+ }
9372
+ if (typeof value.sha256 !== "string" || !HASH_PATTERN.test(value.sha256))
9373
+ throw new TypeError(`Update file ${path} has an invalid SHA-256 digest.`);
9374
+ return { bytes: value.bytes, path, sha256: value.sha256 };
9375
+ };
9376
+ var parseAbsoluteMobileUnsignedUpdateManifest = (value) => {
9377
+ if (!object3(value) || value.format !== ABSOLUTE_MOBILE_UPDATE_FORMAT)
9378
+ throw new TypeError("Invalid AbsoluteJS mobile update manifest.");
9379
+ const appId = requireText2(value.appId, "Update appId");
9380
+ const channel = requireText2(value.channel, "Update channel");
9381
+ if (!CHANNEL_PATTERN.test(channel))
9382
+ throw new TypeError("Update channel contains unsupported characters.");
9383
+ if (!isClassification(value.classification))
9384
+ throw new TypeError("Update classification is invalid.");
9385
+ const createdAt = requireText2(value.createdAt, "Update createdAt");
9386
+ if (!Number.isFinite(Date.parse(createdAt)) || new Date(createdAt).toISOString() !== createdAt)
9387
+ throw new TypeError("Update createdAt must be a canonical ISO timestamp.");
9388
+ if (!Array.isArray(value.files) || value.files.length === 0)
9389
+ throw new TypeError("An update must contain at least one file.");
9390
+ if (value.files.length > ABSOLUTE_MOBILE_UPDATE_MAX_FILES)
9391
+ throw new TypeError("Update contains too many files.");
9392
+ const files = value.files.map(parseFile);
9393
+ const sorted = [...files].sort((left, right) => left.path.localeCompare(right.path));
9394
+ if (files.some((file, index) => file.path !== sorted[index]?.path))
9395
+ throw new TypeError("Update files must be sorted by path.");
9396
+ if (new Set(files.map(({ path }) => path)).size !== files.length)
9397
+ throw new TypeError("Update file paths must be unique.");
9398
+ if (files.reduce((total, file) => total + file.bytes, 0) > ABSOLUTE_MOBILE_UPDATE_MAX_TOTAL_BYTES)
9399
+ throw new TypeError("Update exceeds the maximum uncompressed size.");
9400
+ if (typeof value.releaseId !== "string" || !RELEASE_PATTERN.test(value.releaseId))
9401
+ throw new TypeError("Update releaseId is invalid.");
9402
+ if (typeof value.runtimeFingerprint !== "string" || !HASH_PATTERN.test(value.runtimeFingerprint))
9403
+ throw new TypeError("Update runtime fingerprint is invalid.");
9404
+ if (value.withinSubmittedPurpose !== true)
9405
+ throw new TypeError("OTA updates must attest that they remain within the submitted app purpose.");
9406
+ return {
9407
+ appId,
9408
+ channel,
9409
+ classification: value.classification,
9410
+ createdAt,
9411
+ files,
9412
+ format: ABSOLUTE_MOBILE_UPDATE_FORMAT,
9413
+ releaseId: value.releaseId,
9414
+ runtimeFingerprint: value.runtimeFingerprint,
9415
+ withinSubmittedPurpose: true
9416
+ };
9417
+ };
9418
+ var parseAbsoluteMobileUpdateManifest = (value) => {
9419
+ if (!object3(value))
9420
+ throw new TypeError("Invalid AbsoluteJS mobile update manifest.");
9421
+ const { signature: signatureValue, ...unsignedValue } = value;
9422
+ const unsigned = parseAbsoluteMobileUnsignedUpdateManifest(unsignedValue);
9423
+ if (!object3(signatureValue))
9424
+ throw new TypeError("Update signature is missing.");
9425
+ if (signatureValue.algorithm !== ABSOLUTE_MOBILE_UPDATE_SIGNATURE_ALGORITHM)
9426
+ throw new TypeError("Update signature algorithm is unsupported.");
9427
+ const keyId = requireText2(signatureValue.keyId, "Update signature keyId");
9428
+ if (!KEY_ID_PATTERN.test(keyId))
9429
+ throw new TypeError("Update signature keyId is invalid.");
9430
+ const signature = requireText2(signatureValue.value, "Update signature value");
9431
+ let signatureBytes;
9432
+ try {
9433
+ signatureBytes = Uint8Array.from(atob(signature), (character) => character.charCodeAt(0));
9434
+ } catch {
9435
+ signatureBytes = new Uint8Array;
9436
+ }
9437
+ if (!/^[A-Za-z0-9+/]+={0,2}$/u.test(signature) || signatureBytes.byteLength !== 64 || btoa(String.fromCharCode(...signatureBytes)) !== signature)
9438
+ throw new TypeError("Update signature is not canonical base64.");
9439
+ return {
9440
+ ...unsigned,
9441
+ signature: {
9442
+ algorithm: ABSOLUTE_MOBILE_UPDATE_SIGNATURE_ALGORITHM,
9443
+ keyId,
9444
+ value: signature
9445
+ }
9446
+ };
9447
+ };
9448
+ var unsignedAbsoluteMobileUpdate = (manifest) => {
9449
+ const { signature: _signature, ...unsigned } = manifest;
9450
+ return unsigned;
9451
+ };
9452
+
9453
+ // src/mobile/updateRuntime.ts
9454
+ var ABSOLUTE_MOBILE_SHELL_ABI = 1;
9455
+ var ABSOLUTE_MOBILE_UPDATE_RUNTIME_FORMAT = 1;
9456
+ var createAbsoluteMobileUpdateRuntimeDescriptor = (options) => ({
9457
+ appId: options.config.appId,
9458
+ auth: options.auth ?? null,
9459
+ deepLinks: {
9460
+ hosts: options.config.deepLinkHosts,
9461
+ ...options.config.deepLinkScheme ? { scheme: options.config.deepLinkScheme } : {}
9462
+ },
9463
+ deviceCapabilities: options.deviceCapabilities,
9464
+ engine: options.config.engine,
9465
+ format: ABSOLUTE_MOBILE_UPDATE_RUNTIME_FORMAT,
9466
+ shellAbi: ABSOLUTE_MOBILE_SHELL_ABI,
9467
+ syncSchema: options.syncSchema ?? null,
9468
+ updates: options.config.updates ?? null
9469
+ });
9470
+ var fingerprintAbsoluteMobileUpdateRuntime = (descriptor) => createHash11("sha256").update(canonicalizeAbsoluteMobileUpdate(descriptor)).digest("hex");
9471
+
9264
9472
  // src/mobile/buildPipeline.ts
9265
9473
  var isElysiaApp = (value) => typeof value === "object" && value !== null && typeof Reflect.get(value, "compile") === "function" && Array.isArray(Reflect.get(value, "routes"));
9266
9474
  var isStringRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value) && Object.values(value).every((entry) => typeof entry === "string");
@@ -9336,6 +9544,12 @@ var finalizeAbsoluteMobileCompatibilityBuild = async (options) => {
9336
9544
  const sync = auth !== undefined && projectUsesAbsoluteSync(options.projectRoot);
9337
9545
  const syncSchema = sync ? discoverAbsoluteSyncSchema(options.projectRoot) : undefined;
9338
9546
  const deviceCapabilities = resolveAbsoluteDeviceCapabilityPlan(options.projectRoot, mobile.engine);
9547
+ const runtimeFingerprint = fingerprintAbsoluteMobileUpdateRuntime(createAbsoluteMobileUpdateRuntimeDescriptor({
9548
+ ...auth ? { auth } : {},
9549
+ config: mobile,
9550
+ deviceCapabilities,
9551
+ ...syncSchema ? { syncSchema } : {}
9552
+ }));
9339
9553
  const usesPush = deviceCapabilities.capabilities.includes("pushNotifications");
9340
9554
  if (usesPush && !auth)
9341
9555
  throw new TypeError("Portable push notifications require @absolutejs/auth so provider tokens can be registered without exposing identity controls to page code.");
@@ -9364,6 +9578,7 @@ var finalizeAbsoluteMobileCompatibilityBuild = async (options) => {
9364
9578
  config: mobile,
9365
9579
  deviceCapabilities,
9366
9580
  projectRoot: options.projectRoot,
9581
+ runtimeFingerprint,
9367
9582
  ...sync ? { sync: true } : {},
9368
9583
  ...syncSchema ? { syncSchema: { components: syncSchema.components } } : {}
9369
9584
  });
@@ -11291,6 +11506,65 @@ var applyAbsoluteNativeDeviceCapabilities = async (projectRoot, config, platform
11291
11506
  // src/mobile/index.ts
11292
11507
  init_nativeAuth();
11293
11508
 
11509
+ // src/mobile/nativeUpdates.ts
11510
+ import { readFile as readFile19, rename as rename14, writeFile as writeFile16 } from "fs/promises";
11511
+ import { join as join21 } from "path";
11512
+ var START = "// absolutejs:mobile-updates:start";
11513
+ var END = "// absolutejs:mobile-updates:end";
11514
+ var writeChanged = async (path, source) => {
11515
+ const current = await readFile19(path, "utf8");
11516
+ if (current === source)
11517
+ return false;
11518
+ const temporary = `${path}.${crypto.randomUUID()}.tmp`;
11519
+ await writeFile16(temporary, source, { flag: "wx" });
11520
+ await rename14(temporary, path);
11521
+ return true;
11522
+ };
11523
+ var replaceRegion = (source, region) => {
11524
+ const start = source.indexOf(START);
11525
+ const end = source.indexOf(END);
11526
+ if (start < 0 !== end < 0 || end < start)
11527
+ throw new TypeError("AbsoluteJS mobile update markers are malformed.");
11528
+ if (start >= 0) {
11529
+ const from = source.lastIndexOf(`
11530
+ `, start) + 1;
11531
+ const newline = source.indexOf(`
11532
+ `, end + END.length);
11533
+ return `${source.slice(0, from)}${region}${source.slice(newline < 0 ? source.length : newline + 1)}`;
11534
+ }
11535
+ if (!region)
11536
+ return source;
11537
+ const launch = source.indexOf("didFinishLaunchingWithOptions");
11538
+ const brace = launch < 0 ? -1 : source.indexOf("{", launch);
11539
+ const insert = brace < 0 ? -1 : source.indexOf(`
11540
+ `, brace) + 1;
11541
+ if (insert <= 0)
11542
+ throw new TypeError("Could not find a safe iOS location for mobile update recovery.");
11543
+ return `${source.slice(0, insert)}${region}${source.slice(insert)}`;
11544
+ };
11545
+ var iosRecoveryRegion = ` ${START}
11546
+ // A confirmed Capacitor snapshot lives in Library/NoCloud and is not
11547
+ // restored during device migration. Clear only a dangling pointer so
11548
+ // Capacitor falls back to the store-signed embedded bundle.
11549
+ if let persisted = UserDefaults.standard.string(forKey: "serverBasePath"), !persisted.isEmpty,
11550
+ let library = FileManager.default.urls(for: .libraryDirectory, in: .userDomainMask).first {
11551
+ let snapshot = library
11552
+ .appendingPathComponent("NoCloud/ionic_built_snapshots", isDirectory: true)
11553
+ .appendingPathComponent(URL(fileURLWithPath: persisted).lastPathComponent, isDirectory: true)
11554
+ if !FileManager.default.fileExists(atPath: snapshot.path) {
11555
+ UserDefaults.standard.removeObject(forKey: "serverBasePath")
11556
+ }
11557
+ }
11558
+ ${END}
11559
+ `;
11560
+ var applyAbsoluteNativeUpdates = async (config, platforms = config.platforms) => {
11561
+ if (config.engine !== "capacitor" || !platforms.includes("ios"))
11562
+ return { changed: false };
11563
+ const path = join21(config.nativeProjectDirectory, "ios/App/App/AppDelegate.swift");
11564
+ const source = await readFile19(path, "utf8");
11565
+ const updated = replaceRegion(source, config.updates ? iosRecoveryRegion : "");
11566
+ return { changed: await writeChanged(path, updated) };
11567
+ };
11294
11568
  // src/mobile/releasePublisher.ts
11295
11569
  import { access as access12 } from "fs/promises";
11296
11570
  import { isAbsolute as isAbsolute6, relative as relative12, resolve as resolve15, sep as sep8 } from "path";
@@ -11468,8 +11742,8 @@ var propertyName = (property) => {
11468
11742
  return property.name.text;
11469
11743
  return;
11470
11744
  };
11471
- var objectPropertyExpression = (object3, name) => {
11472
- const property = object3.properties.find((candidate) => propertyName(candidate) === name);
11745
+ var objectPropertyExpression = (object4, name) => {
11746
+ const property = object4.properties.find((candidate) => propertyName(candidate) === name);
11473
11747
  if (property && ts2.isPropertyAssignment(property)) {
11474
11748
  return property.initializer;
11475
11749
  }
@@ -11665,8 +11939,8 @@ var spreadObject = (expression, checker, bindings) => {
11665
11939
  return;
11666
11940
  return callableObject(expression, checker);
11667
11941
  };
11668
- var objectAssetKey = (object3, name, checker, bindings = new Map) => {
11669
- for (const property of [...object3.properties].reverse()) {
11942
+ var objectAssetKey = (object4, name, checker, bindings = new Map) => {
11943
+ for (const property of [...object4.properties].reverse()) {
11670
11944
  if (propertyName(property) === name && ts2.isShorthandPropertyAssignment(property)) {
11671
11945
  return assetKeyWithBindings(property.name, checker, bindings);
11672
11946
  }
@@ -12380,6 +12654,344 @@ var installAbsoluteMobileUiPrimitives = () => {
12380
12654
  }
12381
12655
  };
12382
12656
  };
12657
+ // src/mobile/updateClient.ts
12658
+ var exactManifestUrl = (value) => {
12659
+ const url = new URL(value);
12660
+ if (url.protocol !== "https:" && url.hostname !== "localhost" && url.hostname !== "127.0.0.1")
12661
+ throw new TypeError("Mobile update manifests require HTTPS outside loopback development.");
12662
+ if (url.username || url.password || url.hash)
12663
+ throw new TypeError("Mobile update manifest URLs cannot contain credentials or fragments.");
12664
+ return url;
12665
+ };
12666
+ var fileUrl = (manifestUrl, releaseId, path) => {
12667
+ const encodedPath = path.split("/").map(encodeURIComponent).join("/");
12668
+ const base = new URL(`./${encodeURIComponent(releaseId)}/files/`, manifestUrl);
12669
+ const result = new URL(encodedPath, base);
12670
+ if (result.origin !== manifestUrl.origin || !result.pathname.startsWith(base.pathname))
12671
+ throw new TypeError("Mobile update asset escaped its signed release origin.");
12672
+ return result;
12673
+ };
12674
+ var readChunks = async (reader, maximum2, chunks = [], received = 0) => {
12675
+ const result = await reader.read();
12676
+ if (result.done)
12677
+ return { chunks, received };
12678
+ const total = received + result.value.byteLength;
12679
+ if (total > maximum2)
12680
+ throw new TypeError("Mobile update response exceeds its signed size.");
12681
+ chunks.push(result.value);
12682
+ return readChunks(reader, maximum2, chunks, total);
12683
+ };
12684
+ var readBounded = async (response, maximum2) => {
12685
+ const declared = Number(response.headers.get("content-length"));
12686
+ if (Number.isFinite(declared) && declared > maximum2)
12687
+ throw new TypeError("Mobile update response exceeds its signed size.");
12688
+ if (!response.body)
12689
+ return new Uint8Array;
12690
+ const reader = response.body.getReader();
12691
+ let result;
12692
+ try {
12693
+ result = await readChunks(reader, maximum2);
12694
+ } catch (error) {
12695
+ await reader.cancel().catch(() => {
12696
+ return;
12697
+ });
12698
+ throw error;
12699
+ }
12700
+ const contents = new Uint8Array(result.received);
12701
+ let offset = 0;
12702
+ for (const chunk of result.chunks) {
12703
+ contents.set(chunk, offset);
12704
+ offset += chunk.byteLength;
12705
+ }
12706
+ return contents;
12707
+ };
12708
+ var requestHeaders = (config) => ({
12709
+ "x-absolute-mobile-app": config.appId,
12710
+ "x-absolute-mobile-channel": config.channel,
12711
+ "x-absolute-mobile-installation": config.installationId,
12712
+ "x-absolute-mobile-release": config.currentReleaseId,
12713
+ "x-absolute-mobile-runtime": config.runtimeFingerprint
12714
+ });
12715
+ var requireCompatible = (manifest, config) => {
12716
+ if (manifest.appId !== config.appId)
12717
+ throw new TypeError("Mobile update belongs to another app.");
12718
+ if (manifest.channel !== config.channel)
12719
+ throw new TypeError("Mobile update belongs to another channel.");
12720
+ if (manifest.runtimeFingerprint !== config.runtimeFingerprint)
12721
+ throw new TypeError("Mobile update requires a different native runtime.");
12722
+ };
12723
+ var createAbsoluteMobileUpdateClient = (options) => {
12724
+ const manifestUrl = exactManifestUrl(options.config.manifestUrl);
12725
+ const request = options.fetch ?? globalThis.fetch;
12726
+ const downloadFiles = async (manifest, index = 0, received = 0) => {
12727
+ const file = manifest.files[index];
12728
+ if (!file)
12729
+ return received;
12730
+ const asset = await request(fileUrl(manifestUrl, manifest.releaseId, file.path), {
12731
+ cache: "no-store",
12732
+ credentials: "omit",
12733
+ redirect: "error",
12734
+ signal: AbortSignal.timeout(30000)
12735
+ });
12736
+ if (!asset.ok)
12737
+ throw new TypeError(`Mobile update asset ${file.path} failed with HTTP ${asset.status}.`);
12738
+ const contents = await readBounded(asset, file.bytes);
12739
+ const total = received + contents.byteLength;
12740
+ if (contents.byteLength !== file.bytes || total > ABSOLUTE_MOBILE_UPDATE_MAX_TOTAL_BYTES)
12741
+ throw new TypeError(`Mobile update asset ${file.path} has an invalid size.`);
12742
+ if (await options.verifier.digest(contents) !== file.sha256)
12743
+ throw new TypeError(`Mobile update asset ${file.path} failed integrity verification.`);
12744
+ await options.store.write(file, contents);
12745
+ return downloadFiles(manifest, index + 1, total);
12746
+ };
12747
+ const check = async (download = false) => {
12748
+ const response = await request(manifestUrl, {
12749
+ cache: "no-store",
12750
+ credentials: "omit",
12751
+ headers: requestHeaders(options.config),
12752
+ redirect: "error",
12753
+ signal: AbortSignal.timeout(15000)
12754
+ });
12755
+ if (response.status === 204 || response.status === 304)
12756
+ return { kind: "current" };
12757
+ if (!response.ok)
12758
+ throw new TypeError(`Mobile update check failed with HTTP ${response.status}.`);
12759
+ const manifestBytes = await readBounded(response, 1024 * 1024);
12760
+ let manifestValue;
12761
+ try {
12762
+ manifestValue = JSON.parse(new TextDecoder().decode(manifestBytes));
12763
+ } catch {
12764
+ throw new TypeError("Mobile update manifest is not valid JSON.");
12765
+ }
12766
+ const manifest = parseAbsoluteMobileUpdateManifest(manifestValue);
12767
+ requireCompatible(manifest, options.config);
12768
+ if (!await options.verifier.verify(manifest))
12769
+ throw new TypeError("Mobile update signature verification failed.");
12770
+ if (manifest.releaseId === options.config.currentReleaseId)
12771
+ return { kind: "current" };
12772
+ if (!download)
12773
+ return { kind: "update-available", manifest };
12774
+ await options.store.begin(manifest);
12775
+ try {
12776
+ await downloadFiles(manifest);
12777
+ await options.store.commit(manifest);
12778
+ } catch (error) {
12779
+ await options.store.abort(manifest.releaseId);
12780
+ throw error;
12781
+ }
12782
+ return { kind: "downloaded", manifest };
12783
+ };
12784
+ return {
12785
+ check,
12786
+ activate: (releaseId) => options.store.activate(releaseId),
12787
+ download: () => check(true)
12788
+ };
12789
+ };
12790
+ // src/mobile/updatePublisher.ts
12791
+ import { access as access13 } from "fs/promises";
12792
+ import { isAbsolute as isAbsolute7, relative as relative15, resolve as resolve18, sep as sep9 } from "path";
12793
+ import { pathToFileURL as pathToFileURL4 } from "url";
12794
+
12795
+ // src/mobile/updateSigning.ts
12796
+ import { createHash as createHash12, sign, verify } from "crypto";
12797
+ import {
12798
+ cp as cp3,
12799
+ mkdir as mkdir13,
12800
+ mkdtemp as mkdtemp8,
12801
+ readdir as readdir5,
12802
+ readFile as readFile20,
12803
+ rename as rename15,
12804
+ rm as rm11,
12805
+ stat as stat4,
12806
+ writeFile as writeFile17
12807
+ } from "fs/promises";
12808
+ import { dirname as dirname14, join as join22, relative as relative14, resolve as resolve17 } from "path";
12809
+ var UPDATE_MANIFEST_FILE = "update.json";
12810
+ var UPDATE_FILES_DIRECTORY = "files";
12811
+ var sha2562 = (value) => createHash12("sha256").update(value).digest("hex");
12812
+ var listFiles = async (root, directory = root) => {
12813
+ const entries = await readdir5(directory, { withFileTypes: true });
12814
+ const paths = await Promise.all(entries.map(async (entry) => {
12815
+ const path = join22(directory, entry.name);
12816
+ if (entry.isDirectory())
12817
+ return listFiles(root, path);
12818
+ if (!entry.isFile())
12819
+ throw new TypeError("Mobile updates cannot contain links or special files.");
12820
+ return [relative14(root, path).replaceAll("\\", "/")];
12821
+ }));
12822
+ return paths.flat().sort();
12823
+ };
12824
+ var inspectFiles = async (root, paths) => Promise.all(paths.map(async (path) => {
12825
+ const source = join22(root, path);
12826
+ const [metadata, contents] = await Promise.all([
12827
+ stat4(source),
12828
+ readFile20(source)
12829
+ ]);
12830
+ return { bytes: metadata.size, path, sha256: sha2562(contents) };
12831
+ }));
12832
+ var releaseIdFor = (value) => `amu_${sha2562(canonicalizeAbsoluteMobileUpdate(value))}`;
12833
+ var buildAbsoluteMobileUpdate = async (options) => {
12834
+ const bundleDirectory = resolve17(options.bundleDirectory);
12835
+ const outputRoot = resolve17(options.outputDirectory);
12836
+ if (outputRoot === bundleDirectory || outputRoot.startsWith(`${bundleDirectory}/`))
12837
+ throw new TypeError("Mobile update output must be outside the embedded bundle.");
12838
+ const paths = await listFiles(bundleDirectory);
12839
+ const files = await inspectFiles(bundleDirectory, paths);
12840
+ const withoutId = {
12841
+ appId: options.appId,
12842
+ channel: options.channel,
12843
+ classification: options.classification,
12844
+ createdAt: (options.createdAt ?? new Date).toISOString(),
12845
+ files,
12846
+ format: ABSOLUTE_MOBILE_UPDATE_FORMAT,
12847
+ runtimeFingerprint: options.runtimeFingerprint,
12848
+ withinSubmittedPurpose: true
12849
+ };
12850
+ const unsigned = {
12851
+ ...withoutId,
12852
+ releaseId: releaseIdFor(withoutId)
12853
+ };
12854
+ const signature = sign("sha256", absoluteMobileUpdateSigningPayload(unsigned), { dsaEncoding: "ieee-p1363", key: options.privateKey });
12855
+ const manifest = parseAbsoluteMobileUpdateManifest({
12856
+ ...unsigned,
12857
+ signature: {
12858
+ algorithm: "ecdsa-p256-sha256",
12859
+ keyId: options.keyId,
12860
+ value: signature.toString("base64")
12861
+ }
12862
+ });
12863
+ await mkdir13(outputRoot, { recursive: true });
12864
+ const outputDirectory = join22(outputRoot, manifest.releaseId);
12865
+ const staging = await mkdtemp8(join22(outputRoot, ".stage-"));
12866
+ try {
12867
+ await cp3(bundleDirectory, join22(staging, UPDATE_FILES_DIRECTORY), {
12868
+ force: true,
12869
+ recursive: true
12870
+ });
12871
+ await writeFile17(join22(staging, UPDATE_MANIFEST_FILE), `${JSON.stringify(manifest, null, "\t")}
12872
+ `);
12873
+ await rename15(staging, outputDirectory);
12874
+ } catch (error) {
12875
+ await rm11(staging, { force: true, recursive: true });
12876
+ if (typeof error === "object" && error !== null && Reflect.get(error, "code") === "EEXIST")
12877
+ throw new TypeError(`Mobile update ${manifest.releaseId} already exists.`, { cause: error });
12878
+ throw error;
12879
+ }
12880
+ return {
12881
+ manifest,
12882
+ manifestPath: join22(outputDirectory, UPDATE_MANIFEST_FILE),
12883
+ outputDirectory
12884
+ };
12885
+ };
12886
+ var copyAbsoluteMobileUpdateFile = async (updateDirectory, path, destination) => {
12887
+ const source = resolve17(updateDirectory, UPDATE_FILES_DIRECTORY, path);
12888
+ const root = resolve17(updateDirectory, UPDATE_FILES_DIRECTORY);
12889
+ if (!source.startsWith(`${root}/`))
12890
+ throw new TypeError("Update file escaped its release.");
12891
+ await mkdir13(dirname14(destination), { recursive: true });
12892
+ await cp3(source, destination, { force: true });
12893
+ };
12894
+ var readAbsoluteMobileUpdate = async (directory) => parseAbsoluteMobileUpdateManifest(JSON.parse(await readFile20(join22(resolve17(directory), UPDATE_MANIFEST_FILE), "utf8")));
12895
+ var verifyAbsoluteMobileUpdateSignature = (manifestValue, publicKey) => {
12896
+ const manifest = parseAbsoluteMobileUpdateManifest(manifestValue);
12897
+ const valid = verify("sha256", absoluteMobileUpdateSigningPayload(unsignedAbsoluteMobileUpdate(manifest)), { dsaEncoding: "ieee-p1363", key: publicKey }, Buffer.from(manifest.signature.value, "base64"));
12898
+ if (!valid)
12899
+ throw new TypeError("Mobile update signature verification failed.");
12900
+ return manifest;
12901
+ };
12902
+
12903
+ // src/mobile/updatePublisher.ts
12904
+ var object4 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
12905
+ var isPublisher2 = (value) => object4(value) && typeof value.publishUpdate === "function" && typeof value.promoteUpdate === "function" && typeof value.rollbackUpdate === "function";
12906
+ var projectPath2 = (projectRoot, requested, label) => {
12907
+ const root = resolve18(projectRoot);
12908
+ const path = resolve18(root, requested);
12909
+ const projectRelative = relative15(root, path);
12910
+ if (projectRelative === ".." || projectRelative.startsWith(`..${sep9}`) || isAbsolute7(projectRelative))
12911
+ throw new TypeError(`${label} must remain inside the project.`);
12912
+ return path;
12913
+ };
12914
+ var loadAbsoluteMobileUpdatePublisher = async (projectRoot, requestedModulePath) => {
12915
+ const modulePath = projectPath2(projectRoot, requestedModulePath, "mobile update registry");
12916
+ await access13(modulePath).catch(() => {
12917
+ throw new TypeError(`Mobile update registry does not exist: ${modulePath}`);
12918
+ });
12919
+ const loaded = await import(pathToFileURL4(modulePath).href);
12920
+ const publisher = object4(loaded) ? loaded.default ?? loaded.registry : undefined;
12921
+ if (!isPublisher2(publisher))
12922
+ throw new TypeError("Mobile update registry must implement publishUpdate, promoteUpdate, and rollbackUpdate.");
12923
+ return publisher;
12924
+ };
12925
+ var promoteAbsoluteMobileUpdate = async (options) => {
12926
+ const result = await options.publisher.promoteUpdate({
12927
+ appId: options.appId,
12928
+ channel: options.channel,
12929
+ releaseId: options.releaseId,
12930
+ rollout: options.rollout,
12931
+ signal: options.signal
12932
+ });
12933
+ if (result.appId !== options.appId || result.channel !== options.channel || result.releaseId !== options.releaseId || result.rollout !== options.rollout || result.stage !== "promoted")
12934
+ throw new TypeError("Mobile update registry returned a different promotion identity.");
12935
+ return result;
12936
+ };
12937
+ var publishAbsoluteMobileUpdate = async (options) => {
12938
+ const releaseDirectory = projectPath2(options.projectRoot, options.releaseDirectory, "mobile update release directory");
12939
+ const manifest = await readAbsoluteMobileUpdate(releaseDirectory);
12940
+ const result = await options.publisher.publishUpdate({
12941
+ manifest,
12942
+ releaseDirectory,
12943
+ rollout: options.rollout,
12944
+ signal: options.signal
12945
+ });
12946
+ if (result.appId !== manifest.appId || result.channel !== manifest.channel || result.releaseId !== manifest.releaseId || result.rollout !== options.rollout || result.stage !== "published" || typeof result.reused !== "boolean")
12947
+ throw new TypeError("Mobile update registry returned a different publication identity.");
12948
+ return result;
12949
+ };
12950
+ var rollbackAbsoluteMobileUpdate = async (options) => {
12951
+ const result = await options.publisher.rollbackUpdate({
12952
+ appId: options.appId,
12953
+ channel: options.channel,
12954
+ ...options.releaseId ? { releaseId: options.releaseId } : {},
12955
+ signal: options.signal
12956
+ });
12957
+ if (result.appId !== options.appId || result.channel !== options.channel || result.releaseId !== options.releaseId || result.stage !== "rolled-back")
12958
+ throw new TypeError("Mobile update registry returned a different rollback identity.");
12959
+ return result;
12960
+ };
12961
+ // src/mobile/updateRollout.ts
12962
+ import { createHash as createHash13 } from "crypto";
12963
+ var UUID_PATTERN = /^[a-f0-9]{8}-[a-f0-9]{4}-[1-8][a-f0-9]{3}-[89ab][a-f0-9]{3}-[a-f0-9]{12}$/u;
12964
+ var requireHeader = (headers, name) => {
12965
+ const value = headers.get(name);
12966
+ if (!value)
12967
+ throw new TypeError(`Mobile update request is missing ${name}.`);
12968
+ return value;
12969
+ };
12970
+ var isAbsoluteMobileUpdateRolloutMember = (options) => {
12971
+ if (!Number.isFinite(options.rollout) || options.rollout < 0 || options.rollout > 1)
12972
+ throw new TypeError("Mobile update rollout must be between 0 and 1.");
12973
+ if (options.rollout === 0)
12974
+ return false;
12975
+ if (options.rollout === 1)
12976
+ return true;
12977
+ const digest = createHash13("sha256").update(`${options.appId}\x00${options.channel}\x00${options.releaseId}\x00${options.installationId}`).digest();
12978
+ const bucket = digest.readUInt32BE(0) / 4294967296;
12979
+ return bucket < options.rollout;
12980
+ };
12981
+ var parseAbsoluteMobileUpdateRequest = (request) => {
12982
+ const identity = {
12983
+ appId: requireHeader(request.headers, "x-absolute-mobile-app"),
12984
+ channel: requireHeader(request.headers, "x-absolute-mobile-channel"),
12985
+ currentReleaseId: requireHeader(request.headers, "x-absolute-mobile-release"),
12986
+ installationId: requireHeader(request.headers, "x-absolute-mobile-installation"),
12987
+ runtimeFingerprint: requireHeader(request.headers, "x-absolute-mobile-runtime")
12988
+ };
12989
+ if (!UUID_PATTERN.test(identity.installationId))
12990
+ throw new TypeError("Mobile update installation identity is invalid.");
12991
+ if (!/^[a-f0-9]{64}$/u.test(identity.runtimeFingerprint))
12992
+ throw new TypeError("Mobile update runtime identity is invalid.");
12993
+ return identity;
12994
+ };
12383
12995
  export {
12384
12996
  ABSOLUTE_ANDROID_RELEASE_FORMAT,
12385
12997
  ABSOLUTE_AUTH_PACKAGE,
@@ -12403,8 +13015,15 @@ export {
12403
13015
  ABSOLUTE_MOBILE_PREVIEW_PATH,
12404
13016
  ABSOLUTE_MOBILE_RETAINED_GENERATIONS,
12405
13017
  ABSOLUTE_MOBILE_ROUTE_DETAIL,
13018
+ ABSOLUTE_MOBILE_SHELL_ABI,
12406
13019
  ABSOLUTE_MOBILE_SHELL_DEVICE_CAPABILITIES,
12407
13020
  ABSOLUTE_MOBILE_TRANSFORM_PROTOCOL,
13021
+ ABSOLUTE_MOBILE_UPDATE_FORMAT,
13022
+ ABSOLUTE_MOBILE_UPDATE_MAX_FILES,
13023
+ ABSOLUTE_MOBILE_UPDATE_MAX_FILE_BYTES,
13024
+ ABSOLUTE_MOBILE_UPDATE_MAX_TOTAL_BYTES,
13025
+ ABSOLUTE_MOBILE_UPDATE_RUNTIME_FORMAT,
13026
+ ABSOLUTE_MOBILE_UPDATE_SIGNATURE_ALGORITHM,
12408
13027
  ABSOLUTE_NATIVE_AUTH_CLIENTS_ENV,
12409
13028
  ABSOLUTE_NATIVE_AUTH_SCOPES,
12410
13029
  ABSOLUTE_NATIVE_ROUTE_DATA_MEDIA_TYPE,
@@ -12419,6 +13038,7 @@ export {
12419
13038
  absoluteExpoExecutable,
12420
13039
  absoluteIosDeviceAcceptanceCommands,
12421
13040
  absoluteMobilePreviewDocument,
13041
+ absoluteMobileUpdateSigningPayload,
12422
13042
  absoluteRemoteMacSshBase,
12423
13043
  absoluteRemoteProjectSyncCommands,
12424
13044
  absoluteRemoteReleaseInputSyncCommands,
@@ -12428,16 +13048,20 @@ export {
12428
13048
  activateAbsoluteMobilePage,
12429
13049
  applyAbsoluteNativeDeepLinks,
12430
13050
  applyAbsoluteNativeDeviceCapabilities,
13051
+ applyAbsoluteNativeUpdates,
12431
13052
  assertAbsoluteDeviceCapabilityPackages,
12432
13053
  buildAbsoluteAndroidRelease,
12433
13054
  buildAbsoluteIosRelease,
12434
13055
  buildAbsoluteMobileCompatibilityRelease,
13056
+ buildAbsoluteMobileUpdate,
12435
13057
  buildAbsoluteRemoteIosRelease,
13058
+ canonicalizeAbsoluteMobileUpdate,
12436
13059
  captureAbsoluteMobileRouteGraph,
12437
13060
  captureAbsoluteRemoteMacCommand,
12438
13061
  carryForwardAbsoluteMobileCompatibilityReleases,
12439
13062
  cleanAbsoluteRemoteMacWorkspace,
12440
13063
  closeAbsoluteMobileSheet,
13064
+ copyAbsoluteMobileUpdateFile,
12441
13065
  createAbsoluteExpoBridgeError,
12442
13066
  createAbsoluteExpoBridgeResponse,
12443
13067
  createAbsoluteIosNativeWatcher,
@@ -12454,6 +13078,8 @@ export {
12454
13078
  createAbsoluteMobilePageRequest,
12455
13079
  createAbsoluteMobilePreviewPlugin,
12456
13080
  createAbsoluteMobileRouteMetadataPlugin,
13081
+ createAbsoluteMobileUpdateClient,
13082
+ createAbsoluteMobileUpdateRuntimeDescriptor,
12457
13083
  createAbsoluteMobileUpgradeResponse,
12458
13084
  createAbsoluteRemoteExpoIosDevProject,
12459
13085
  createAbsoluteRemoteIosDevProject,
@@ -12466,6 +13092,7 @@ export {
12466
13092
  finalizeAbsoluteMobilePage,
12467
13093
  fingerprintAbsoluteIosDevProject,
12468
13094
  fingerprintAbsoluteIosNativeProject,
13095
+ fingerprintAbsoluteMobileUpdateRuntime,
12469
13096
  getAbsoluteMobileSyncRemediation,
12470
13097
  getAbsoluteRemoteMacProfile,
12471
13098
  getCurrentAbsoluteMobileProducerContext,
@@ -12483,9 +13110,11 @@ export {
12483
13110
  installAbsoluteMobileUiPrimitives,
12484
13111
  installAbsoluteRemoteMacAgent,
12485
13112
  isAbsoluteIosNativeRootInput,
13113
+ isAbsoluteMobileUpdateRolloutMember,
12486
13114
  listAbsoluteRemoteMacProfiles,
12487
13115
  loadAbsoluteDeviceCapabilityProviders,
12488
13116
  loadAbsoluteMobileMaterializedBundle,
13117
+ loadAbsoluteMobileUpdatePublisher,
12489
13118
  loadAbsoluteNativeReleasePublisher,
12490
13119
  matchesAbsoluteMobileRoutePattern,
12491
13120
  materializeAbsoluteCapacitorWebBundle,
@@ -12497,6 +13126,7 @@ export {
12497
13126
  normalizeAbsoluteIosDeviceHost,
12498
13127
  normalizeAbsoluteIosDeviceIdentifier,
12499
13128
  normalizeAbsoluteMobileConfig,
13129
+ normalizeAbsoluteMobileUpdatePath,
12500
13130
  openAbsoluteMobileSheet,
12501
13131
  pairAbsoluteRemoteMac,
12502
13132
  parseAbsoluteAndroidInstalledApp,
@@ -12507,6 +13137,9 @@ export {
12507
13137
  parseAbsoluteMobileCompatibilityArtifact,
12508
13138
  parseAbsoluteMobilePageEnvelope,
12509
13139
  parseAbsoluteMobilePageRequest,
13140
+ parseAbsoluteMobileUnsignedUpdateManifest,
13141
+ parseAbsoluteMobileUpdateManifest,
13142
+ parseAbsoluteMobileUpdateRequest,
12510
13143
  parseIosDeviceTypes,
12511
13144
  parseIosRuntimes,
12512
13145
  parseIosSimulators,
@@ -12517,10 +13150,13 @@ export {
12517
13150
  projectImportsAbsoluteDeviceCapability,
12518
13151
  projectUsesAbsoluteAuth,
12519
13152
  projectUsesAbsoluteSync,
13153
+ promoteAbsoluteMobileUpdate,
12520
13154
  publishAbsoluteAndroidRelease,
12521
13155
  publishAbsoluteIosRelease,
13156
+ publishAbsoluteMobileUpdate,
12522
13157
  readAbsoluteMobileLinkIntent,
12523
13158
  readAbsoluteMobileMaterializedReleases,
13159
+ readAbsoluteMobileUpdate,
12524
13160
  redactAbsoluteIosLog,
12525
13161
  removeAbsoluteRemoteMacProfile,
12526
13162
  repairAbsoluteIosDevSession,
@@ -12534,6 +13170,7 @@ export {
12534
13170
  resolveAbsoluteMobileNavigation,
12535
13171
  resolveAbsoluteMobileRoute,
12536
13172
  retainAbsoluteMobileCompatibilityArtifacts,
13173
+ rollbackAbsoluteMobileUpdate,
12537
13174
  runAbsoluteAndroidUpgradeConformance,
12538
13175
  runWithAbsoluteMobileProducer,
12539
13176
  serializeAbsoluteMobileAuthEnvironment,
@@ -12547,15 +13184,17 @@ export {
12547
13184
  syncAbsoluteRemoteMacProject,
12548
13185
  syncAbsoluteRemoteMacReleaseInputs,
12549
13186
  testAbsoluteIosPhysicalDevice,
13187
+ unsignedAbsoluteMobileUpdate,
12550
13188
  validateAbsoluteRemoteMacProfileName,
12551
13189
  validateAbsoluteSshDestination,
12552
13190
  verifyAbsoluteMobileAssociationFiles,
12553
13191
  verifyAbsoluteMobileCompatibilityProducer,
13192
+ verifyAbsoluteMobileUpdateSignature,
12554
13193
  waitForAbsoluteIosHmrLog,
12555
13194
  writeAbsoluteCapacitorConfig,
12556
13195
  writeAbsoluteExpoProject,
12557
13196
  writeAbsoluteMobileGithubWorkflow
12558
13197
  };
12559
13198
 
12560
- //# debugId=AC4084DD1C6BCD7664756E2164756E21
13199
+ //# debugId=FA0645035026C26B64756E2164756E21
12561
13200
  //# sourceMappingURL=index.js.map