@absolutejs/absolute 0.20.0-beta.82 → 0.20.0-beta.84
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/build.js +286 -9
- package/dist/build.js.map +3 -3
- package/dist/cli/index.js +1 -1
- package/dist/cli/{mobile-hkxh2ab5.js → mobile-pdck6a35.js} +158 -3
- package/dist/index.js +286 -9
- package/dist/index.js.map +3 -3
- package/dist/mobile/index.js +366 -17
- package/dist/mobile/index.js.map +5 -5
- package/dist/mobile/shellUpdate.js +46 -8
- package/dist/src/mobile/updateClient.d.ts +11 -0
- package/dist/src/mobile/updatePublisher.d.ts +23 -0
- package/package.json +2 -2
package/dist/mobile/index.js
CHANGED
|
@@ -19970,7 +19970,7 @@ import {
|
|
|
19970
19970
|
} from "crypto";
|
|
19971
19971
|
import { readFile as readFile23, stat as stat5 } from "fs/promises";
|
|
19972
19972
|
import path from "path";
|
|
19973
|
-
var MOBILE_UPDATE_REGISTRY_FORMAT = 1, DEFAULT_PREFIX = "absolutejs/mobile-updates", MAX_FILE_BYTES, MAX_TOTAL_BYTES, HASH, RELEASE, APP_ID, NAME, EXPO_DESCRIPTOR = "_absolute/expo-update.json", EXPO_CODE_SIGNING_ALGORITHM = "rsa-v1_5-sha256", MobileUpdateRegistryError, object6 = (value) => typeof value === "object" && value !== null && !Array.isArray(value), text2 = (value, field2) => {
|
|
19973
|
+
var MOBILE_UPDATE_REGISTRY_FORMAT = 1, DEFAULT_PREFIX = "absolutejs/mobile-updates", MAX_FILE_BYTES, MAX_TOTAL_BYTES, DAY_MS, DEFAULT_MIN_AGE_MS, DEFAULT_GRACE_PERIOD_MS, DEFAULT_RETAIN_RECENT = 5, HASH, RELEASE, APP_ID, NAME, EXPO_DESCRIPTOR = "_absolute/expo-update.json", EXPO_CODE_SIGNING_ALGORITHM = "rsa-v1_5-sha256", MobileUpdateRegistryError, object6 = (value) => typeof value === "object" && value !== null && !Array.isArray(value), text2 = (value, field2) => {
|
|
19974
19974
|
if (typeof value !== "string" || value.length === 0)
|
|
19975
19975
|
throw new MobileUpdateRegistryError(`Mobile update ${field2} is invalid`);
|
|
19976
19976
|
return value;
|
|
@@ -20137,6 +20137,8 @@ var MOBILE_UPDATE_REGISTRY_FORMAT = 1, DEFAULT_PREFIX = "absolutejs/mobile-updat
|
|
|
20137
20137
|
const releaseRoot = (manifest) => `${root(manifest.appId)}/releases/${manifest.releaseId}`;
|
|
20138
20138
|
const manifestKey = (manifest) => `${releaseRoot(manifest)}/update.json`;
|
|
20139
20139
|
const fileKey = (manifest, file) => `${releaseRoot(manifest)}/files/${file.path}`;
|
|
20140
|
+
const contentBlobKey = (appId, sha2563) => `${root(appId)}/blobs/${sha2563}`;
|
|
20141
|
+
const tombstoneKey = (appId, releaseId) => `${root(appId)}/gc/${releaseId}.json`;
|
|
20140
20142
|
const channelKey = (appId, channel) => {
|
|
20141
20143
|
if (!APP_ID.test(appId) || !NAME.test(channel))
|
|
20142
20144
|
throw new MobileUpdateRegistryError("Mobile update channel identity is invalid");
|
|
@@ -20165,6 +20167,12 @@ var MOBILE_UPDATE_REGISTRY_FORMAT = 1, DEFAULT_PREFIX = "absolutejs/mobile-updat
|
|
|
20165
20167
|
throw new MobileUpdateRegistryError("Stored mobile update channel identity changed");
|
|
20166
20168
|
return value;
|
|
20167
20169
|
};
|
|
20170
|
+
const assertNotMarked = async (appId, releaseId) => {
|
|
20171
|
+
if (!APP_ID.test(appId) || !RELEASE.test(releaseId))
|
|
20172
|
+
throw new MobileUpdateRegistryError("Mobile update release identity is invalid");
|
|
20173
|
+
if (await options.store.head(tombstoneKey(appId, releaseId)))
|
|
20174
|
+
throw new MobileUpdateRegistryError("Mobile update release is marked for collection. Increase retention and apply garbage collection to restore it before promotion");
|
|
20175
|
+
};
|
|
20168
20176
|
const writeChannel = async (input, signal) => {
|
|
20169
20177
|
const value = {
|
|
20170
20178
|
...input,
|
|
@@ -20189,6 +20197,7 @@ var MOBILE_UPDATE_REGISTRY_FORMAT = 1, DEFAULT_PREFIX = "absolutejs/mobile-updat
|
|
|
20189
20197
|
input.signal?.throwIfAborted();
|
|
20190
20198
|
if (input.rollout <= 0 || input.rollout > 1)
|
|
20191
20199
|
throw new MobileUpdateRegistryError("Mobile update rollout is invalid");
|
|
20200
|
+
await assertNotMarked(input.appId, input.releaseId);
|
|
20192
20201
|
const release = await readManifest(input.appId, input.releaseId);
|
|
20193
20202
|
if (!release || release.manifest.channel !== input.channel)
|
|
20194
20203
|
throw new MobileUpdateRegistryError("Mobile update was not published to this channel");
|
|
@@ -20234,19 +20243,271 @@ var MOBILE_UPDATE_REGISTRY_FORMAT = 1, DEFAULT_PREFIX = "absolutejs/mobile-updat
|
|
|
20234
20243
|
status: "selected"
|
|
20235
20244
|
};
|
|
20236
20245
|
};
|
|
20246
|
+
const retentionValues = (input) => {
|
|
20247
|
+
if (!APP_ID.test(input.appId))
|
|
20248
|
+
throw new MobileUpdateRegistryError("Mobile update appId is invalid");
|
|
20249
|
+
const retainRecent = input.retainRecent ?? DEFAULT_RETAIN_RECENT;
|
|
20250
|
+
const minAgeMs = input.minAgeMs ?? DEFAULT_MIN_AGE_MS;
|
|
20251
|
+
if (!Number.isSafeInteger(retainRecent) || retainRecent < 0)
|
|
20252
|
+
throw new MobileUpdateRegistryError("Mobile update retained release count is invalid");
|
|
20253
|
+
if (!Number.isSafeInteger(minAgeMs) || minAgeMs < 0)
|
|
20254
|
+
throw new MobileUpdateRegistryError("Mobile update minimum release age is invalid");
|
|
20255
|
+
return { minAgeMs, retainRecent };
|
|
20256
|
+
};
|
|
20257
|
+
const listObjects = async (appId, signal) => {
|
|
20258
|
+
const list = options.store.list;
|
|
20259
|
+
if (!list)
|
|
20260
|
+
throw new MobileUpdateRegistryError("Mobile update storage does not support lifecycle listing");
|
|
20261
|
+
const objects = [];
|
|
20262
|
+
const cursors = new Set;
|
|
20263
|
+
let cursor;
|
|
20264
|
+
do {
|
|
20265
|
+
signal?.throwIfAborted();
|
|
20266
|
+
const page = await list({
|
|
20267
|
+
...cursor ? { cursor } : {},
|
|
20268
|
+
prefix: `${root(appId)}/`
|
|
20269
|
+
});
|
|
20270
|
+
objects.push(...page.objects);
|
|
20271
|
+
if (!page.truncated)
|
|
20272
|
+
break;
|
|
20273
|
+
if (!page.cursor || cursors.has(page.cursor))
|
|
20274
|
+
throw new MobileUpdateRegistryError("Mobile update storage returned an invalid lifecycle cursor");
|
|
20275
|
+
cursors.add(page.cursor);
|
|
20276
|
+
cursor = page.cursor;
|
|
20277
|
+
} while (true);
|
|
20278
|
+
return objects;
|
|
20279
|
+
};
|
|
20280
|
+
const inventory = async (input) => {
|
|
20281
|
+
const { minAgeMs, retainRecent } = retentionValues(input);
|
|
20282
|
+
const objects = await listObjects(input.appId, input.signal);
|
|
20283
|
+
const appRoot = `${root(input.appId)}/`;
|
|
20284
|
+
const releasePrefix = `${appRoot}releases/`;
|
|
20285
|
+
const channelPrefix = `${appRoot}channels/`;
|
|
20286
|
+
const markerPrefix = `${appRoot}gc/`;
|
|
20287
|
+
const blobPrefix = `${appRoot}blobs/`;
|
|
20288
|
+
const objectKeysByRelease = new Map;
|
|
20289
|
+
const objectBytesByRelease = new Map;
|
|
20290
|
+
const objectKeysByDigest = new Map;
|
|
20291
|
+
const objectBytesByDigest = new Map;
|
|
20292
|
+
for (const blobObject of objects) {
|
|
20293
|
+
if (blobObject.key.startsWith(blobPrefix)) {
|
|
20294
|
+
const sha2563 = blobObject.key.slice(blobPrefix.length);
|
|
20295
|
+
if (HASH.test(sha2563)) {
|
|
20296
|
+
objectKeysByDigest.set(sha2563, blobObject.key);
|
|
20297
|
+
objectBytesByDigest.set(sha2563, blobObject.size);
|
|
20298
|
+
}
|
|
20299
|
+
}
|
|
20300
|
+
if (!blobObject.key.startsWith(releasePrefix))
|
|
20301
|
+
continue;
|
|
20302
|
+
const suffix = blobObject.key.slice(releasePrefix.length);
|
|
20303
|
+
const releaseId = suffix.slice(0, suffix.indexOf("/"));
|
|
20304
|
+
if (!RELEASE.test(releaseId))
|
|
20305
|
+
continue;
|
|
20306
|
+
objectKeysByRelease.set(releaseId, [
|
|
20307
|
+
...objectKeysByRelease.get(releaseId) ?? [],
|
|
20308
|
+
blobObject.key
|
|
20309
|
+
]);
|
|
20310
|
+
objectBytesByRelease.set(releaseId, (objectBytesByRelease.get(releaseId) ?? 0) + blobObject.size);
|
|
20311
|
+
}
|
|
20312
|
+
const channels = [];
|
|
20313
|
+
for (const blobObject of objects) {
|
|
20314
|
+
if (!blobObject.key.startsWith(channelPrefix) || !blobObject.key.endsWith(".json"))
|
|
20315
|
+
continue;
|
|
20316
|
+
const bytes = await options.store.get(blobObject.key);
|
|
20317
|
+
if (!bytes)
|
|
20318
|
+
throw new MobileUpdateRegistryError("Mobile update channel disappeared during lifecycle inspection");
|
|
20319
|
+
const channel = parseChannel(decode2(bytes));
|
|
20320
|
+
if (channel.appId !== input.appId)
|
|
20321
|
+
throw new MobileUpdateRegistryError("Stored mobile update channel identity changed");
|
|
20322
|
+
channels.push(channel);
|
|
20323
|
+
}
|
|
20324
|
+
const markedAt = new Map;
|
|
20325
|
+
for (const blobObject of objects) {
|
|
20326
|
+
if (!blobObject.key.startsWith(markerPrefix) || !blobObject.key.endsWith(".json"))
|
|
20327
|
+
continue;
|
|
20328
|
+
const releaseId = blobObject.key.slice(markerPrefix.length, -".json".length);
|
|
20329
|
+
if (!RELEASE.test(releaseId))
|
|
20330
|
+
continue;
|
|
20331
|
+
const bytes = await options.store.get(blobObject.key);
|
|
20332
|
+
if (!bytes)
|
|
20333
|
+
continue;
|
|
20334
|
+
const marker = decode2(bytes);
|
|
20335
|
+
if (!object6(marker) || marker.format !== 1 || marker.appId !== input.appId || marker.releaseId !== releaseId || !iso(marker.markedAt))
|
|
20336
|
+
throw new MobileUpdateRegistryError("Mobile update collection marker is invalid");
|
|
20337
|
+
markedAt.set(releaseId, marker.markedAt);
|
|
20338
|
+
}
|
|
20339
|
+
const active = new Set(channels.flatMap((channel) => channel.releaseId ? [channel.releaseId] : []));
|
|
20340
|
+
const fallback = new Set(channels.flatMap((channel) => channel.fallbackReleaseId ? [channel.fallbackReleaseId] : []));
|
|
20341
|
+
const manifests = [];
|
|
20342
|
+
for (const releaseId of objectKeysByRelease.keys()) {
|
|
20343
|
+
const release = await readManifest(input.appId, releaseId);
|
|
20344
|
+
if (release)
|
|
20345
|
+
manifests.push(release.manifest);
|
|
20346
|
+
}
|
|
20347
|
+
const recent = new Set;
|
|
20348
|
+
const manifestsByChannel = new Map;
|
|
20349
|
+
for (const manifest of manifests)
|
|
20350
|
+
manifestsByChannel.set(manifest.channel, [
|
|
20351
|
+
...manifestsByChannel.get(manifest.channel) ?? [],
|
|
20352
|
+
manifest
|
|
20353
|
+
]);
|
|
20354
|
+
for (const releases2 of manifestsByChannel.values())
|
|
20355
|
+
for (const manifest of releases2.toSorted((left, right) => right.createdAt.localeCompare(left.createdAt)).slice(0, retainRecent))
|
|
20356
|
+
recent.add(manifest.releaseId);
|
|
20357
|
+
const now = clock().getTime();
|
|
20358
|
+
const releases = manifests.map((manifest) => {
|
|
20359
|
+
const protectedBy = [];
|
|
20360
|
+
if (active.has(manifest.releaseId))
|
|
20361
|
+
protectedBy.push("active");
|
|
20362
|
+
if (fallback.has(manifest.releaseId))
|
|
20363
|
+
protectedBy.push("fallback");
|
|
20364
|
+
if (recent.has(manifest.releaseId))
|
|
20365
|
+
protectedBy.push("recent");
|
|
20366
|
+
if (now - Date.parse(manifest.createdAt) < minAgeMs)
|
|
20367
|
+
protectedBy.push("age");
|
|
20368
|
+
return {
|
|
20369
|
+
bytes: objectBytesByRelease.get(manifest.releaseId) ?? 0,
|
|
20370
|
+
channel: manifest.channel,
|
|
20371
|
+
createdAt: manifest.createdAt,
|
|
20372
|
+
...markedAt.has(manifest.releaseId) ? { markedAt: markedAt.get(manifest.releaseId) } : {},
|
|
20373
|
+
objectCount: objectKeysByRelease.get(manifest.releaseId)?.length ?? 0,
|
|
20374
|
+
protectedBy,
|
|
20375
|
+
releaseId: manifest.releaseId
|
|
20376
|
+
};
|
|
20377
|
+
}).toSorted((left, right) => right.createdAt.localeCompare(left.createdAt));
|
|
20378
|
+
const protectedReleaseIds = new Set(releases.filter((release) => release.protectedBy.length > 0).map((release) => release.releaseId));
|
|
20379
|
+
const protectedDigests = new Set(manifests.filter((manifest) => protectedReleaseIds.has(manifest.releaseId)).flatMap((manifest) => manifest.files.map((file) => file.sha256)));
|
|
20380
|
+
const reclaimableContentBytes = [...objectBytesByDigest].filter(([sha2563]) => !protectedDigests.has(sha2563)).reduce((total, [, bytes]) => total + bytes, 0);
|
|
20381
|
+
const contentBlobBytes = [...objectBytesByDigest.values()].reduce((total, bytes) => total + bytes, 0);
|
|
20382
|
+
const releaseBytes = releases.reduce((total, release) => total + release.bytes, 0);
|
|
20383
|
+
const totalBytes = objects.reduce((total, object22) => total + object22.size, 0);
|
|
20384
|
+
return {
|
|
20385
|
+
objectKeysByRelease,
|
|
20386
|
+
objectKeysByDigest,
|
|
20387
|
+
report: {
|
|
20388
|
+
appId: input.appId,
|
|
20389
|
+
channelCount: channels.length,
|
|
20390
|
+
contentBlobBytes,
|
|
20391
|
+
contentBlobCount: objectKeysByDigest.size,
|
|
20392
|
+
reclaimableBytes: releases.filter((release) => release.protectedBy.length === 0).reduce((total, release) => total + release.bytes, 0) + reclaimableContentBytes,
|
|
20393
|
+
reclaimableContentBytes,
|
|
20394
|
+
releaseBytes,
|
|
20395
|
+
releaseCount: releases.length,
|
|
20396
|
+
releases,
|
|
20397
|
+
totalBytes,
|
|
20398
|
+
totalObjectCount: objects.length,
|
|
20399
|
+
untrackedBytes: totalBytes - releaseBytes - contentBlobBytes
|
|
20400
|
+
}
|
|
20401
|
+
};
|
|
20402
|
+
};
|
|
20403
|
+
const pruneUpdates = async (input) => {
|
|
20404
|
+
const initial = await inventory(input);
|
|
20405
|
+
const result = {
|
|
20406
|
+
...initial.report,
|
|
20407
|
+
dryRun: input.apply !== true,
|
|
20408
|
+
marked: [],
|
|
20409
|
+
reclaimedBytes: 0,
|
|
20410
|
+
restored: [],
|
|
20411
|
+
swept: [],
|
|
20412
|
+
sweptContentBlobs: []
|
|
20413
|
+
};
|
|
20414
|
+
if (input.apply !== true)
|
|
20415
|
+
return result;
|
|
20416
|
+
const remove = options.store.delete;
|
|
20417
|
+
if (!remove)
|
|
20418
|
+
throw new MobileUpdateRegistryError("Mobile update storage does not support lifecycle deletion");
|
|
20419
|
+
const gracePeriodMs = input.gracePeriodMs ?? DEFAULT_GRACE_PERIOD_MS;
|
|
20420
|
+
if (!Number.isSafeInteger(gracePeriodMs) || gracePeriodMs < 0)
|
|
20421
|
+
throw new MobileUpdateRegistryError("Mobile update collection grace period is invalid");
|
|
20422
|
+
const now = clock();
|
|
20423
|
+
for (const release of initial.report.releases) {
|
|
20424
|
+
input.signal?.throwIfAborted();
|
|
20425
|
+
const marker = tombstoneKey(input.appId, release.releaseId);
|
|
20426
|
+
if (release.protectedBy.length > 0) {
|
|
20427
|
+
if (release.markedAt) {
|
|
20428
|
+
await remove(marker);
|
|
20429
|
+
result.restored.push(release.releaseId);
|
|
20430
|
+
}
|
|
20431
|
+
continue;
|
|
20432
|
+
}
|
|
20433
|
+
if (!release.markedAt) {
|
|
20434
|
+
const bytes = json({
|
|
20435
|
+
appId: input.appId,
|
|
20436
|
+
format: 1,
|
|
20437
|
+
markedAt: now.toISOString(),
|
|
20438
|
+
releaseId: release.releaseId
|
|
20439
|
+
});
|
|
20440
|
+
await options.store.put(marker, bytes, {
|
|
20441
|
+
cacheControl: "no-cache",
|
|
20442
|
+
contentType: "application/json",
|
|
20443
|
+
maxBytes: bytes.byteLength,
|
|
20444
|
+
metadata: { releaseid: release.releaseId, sha256: digest(bytes) },
|
|
20445
|
+
signal: input.signal
|
|
20446
|
+
});
|
|
20447
|
+
result.marked.push(release.releaseId);
|
|
20448
|
+
}
|
|
20449
|
+
}
|
|
20450
|
+
const current = await inventory(input);
|
|
20451
|
+
for (const release of current.report.releases) {
|
|
20452
|
+
input.signal?.throwIfAborted();
|
|
20453
|
+
if (release.protectedBy.length > 0 || !release.markedAt || now.getTime() - Date.parse(release.markedAt) < gracePeriodMs || result.marked.includes(release.releaseId))
|
|
20454
|
+
continue;
|
|
20455
|
+
const checked = await inventory(input);
|
|
20456
|
+
const candidate = checked.report.releases.find((value) => value.releaseId === release.releaseId);
|
|
20457
|
+
if (!candidate || candidate.protectedBy.length > 0 || !candidate.markedAt)
|
|
20458
|
+
continue;
|
|
20459
|
+
const keys = checked.objectKeysByRelease.get(release.releaseId) ?? [];
|
|
20460
|
+
for (const key of keys.toSorted((left, right) => {
|
|
20461
|
+
const leftManifest = left.endsWith("/update.json");
|
|
20462
|
+
const rightManifest = right.endsWith("/update.json");
|
|
20463
|
+
return Number(leftManifest) - Number(rightManifest);
|
|
20464
|
+
}))
|
|
20465
|
+
await remove(key);
|
|
20466
|
+
await remove(tombstoneKey(input.appId, release.releaseId));
|
|
20467
|
+
result.reclaimedBytes += candidate.bytes;
|
|
20468
|
+
result.swept.push(release.releaseId);
|
|
20469
|
+
}
|
|
20470
|
+
const afterReleaseSweep = await inventory(input);
|
|
20471
|
+
const referencedDigests = new Set;
|
|
20472
|
+
for (const release of afterReleaseSweep.report.releases) {
|
|
20473
|
+
const manifest = await readManifest(input.appId, release.releaseId);
|
|
20474
|
+
for (const file of manifest?.manifest.files ?? [])
|
|
20475
|
+
referencedDigests.add(file.sha256);
|
|
20476
|
+
}
|
|
20477
|
+
for (const [sha2563, key] of afterReleaseSweep.objectKeysByDigest) {
|
|
20478
|
+
if (referencedDigests.has(sha2563))
|
|
20479
|
+
continue;
|
|
20480
|
+
const head = await options.store.head(key);
|
|
20481
|
+
await remove(key);
|
|
20482
|
+
result.reclaimedBytes += head?.size ?? 0;
|
|
20483
|
+
result.sweptContentBlobs.push(sha2563);
|
|
20484
|
+
}
|
|
20485
|
+
return result;
|
|
20486
|
+
};
|
|
20237
20487
|
return {
|
|
20488
|
+
inspectUpdateStorage: async (input) => (await inventory(input)).report,
|
|
20489
|
+
pruneUpdates,
|
|
20238
20490
|
publishUpdate: async (input) => {
|
|
20239
20491
|
input.signal?.throwIfAborted();
|
|
20240
20492
|
const manifest = parseMobileUpdateManifest(input.manifest);
|
|
20241
20493
|
verifyManifestSignature(manifest, options.publicKeys);
|
|
20494
|
+
await assertNotMarked(manifest.appId, manifest.releaseId);
|
|
20242
20495
|
const localRoot = path.resolve(input.releaseDirectory);
|
|
20243
20496
|
const localManifest = parseMobileUpdateManifest(JSON.parse(await readFile23(path.join(localRoot, "update.json"), "utf8")));
|
|
20244
20497
|
if (JSON.stringify(localManifest) !== JSON.stringify(manifest))
|
|
20245
20498
|
throw new MobileUpdateRegistryError("Local mobile update manifest changed");
|
|
20246
20499
|
const existing = await readManifest(manifest.appId, manifest.releaseId);
|
|
20247
20500
|
let reused = existing !== null;
|
|
20501
|
+
let storedBytes = 0;
|
|
20502
|
+
let storedFiles = 0;
|
|
20503
|
+
let reusedBytes = 0;
|
|
20504
|
+
let reusedFiles = 0;
|
|
20248
20505
|
if (existing && JSON.stringify(existing.manifest) !== JSON.stringify(manifest))
|
|
20249
20506
|
throw new MobileUpdateRegistryError("Published mobile update is immutable");
|
|
20507
|
+
if (existing) {
|
|
20508
|
+
reusedBytes = manifest.files.reduce((total, file) => total + file.bytes, 0);
|
|
20509
|
+
reusedFiles = manifest.files.length;
|
|
20510
|
+
}
|
|
20250
20511
|
if (!existing) {
|
|
20251
20512
|
for (const file of manifest.files) {
|
|
20252
20513
|
const local = path.join(localRoot, "files", file.path);
|
|
@@ -20255,18 +20516,24 @@ var MOBILE_UPDATE_REGISTRY_FORMAT = 1, DEFAULT_PREFIX = "absolutejs/mobile-updat
|
|
|
20255
20516
|
throw new MobileUpdateRegistryError(`Mobile update file ${file.path} size changed`);
|
|
20256
20517
|
if (await fileDigest(Bun.file(local)) !== file.sha256)
|
|
20257
20518
|
throw new MobileUpdateRegistryError(`Mobile update file ${file.path} integrity failed`);
|
|
20258
|
-
const key =
|
|
20519
|
+
const key = contentBlobKey(manifest.appId, file.sha256);
|
|
20259
20520
|
const stored = await options.store.head(key);
|
|
20260
20521
|
if (!stored) {
|
|
20261
20522
|
await options.store.put(key, Bun.file(local).stream(), {
|
|
20262
20523
|
cacheControl: "public, max-age=31536000, immutable",
|
|
20263
20524
|
contentType: "application/octet-stream",
|
|
20264
20525
|
maxBytes: file.bytes,
|
|
20265
|
-
metadata: {
|
|
20526
|
+
metadata: { sha256: file.sha256 },
|
|
20266
20527
|
signal: input.signal
|
|
20267
20528
|
});
|
|
20268
|
-
|
|
20269
|
-
|
|
20529
|
+
storedBytes += file.bytes;
|
|
20530
|
+
storedFiles += 1;
|
|
20531
|
+
} else if (stored.size !== file.bytes || stored.metadata?.sha256 !== file.sha256)
|
|
20532
|
+
throw new MobileUpdateRegistryError("Stored mobile update content identity changed");
|
|
20533
|
+
else {
|
|
20534
|
+
reusedBytes += file.bytes;
|
|
20535
|
+
reusedFiles += 1;
|
|
20536
|
+
}
|
|
20270
20537
|
}
|
|
20271
20538
|
const bytes = json(manifest);
|
|
20272
20539
|
await options.store.put(manifestKey(manifest), bytes, {
|
|
@@ -20290,8 +20557,12 @@ var MOBILE_UPDATE_REGISTRY_FORMAT = 1, DEFAULT_PREFIX = "absolutejs/mobile-updat
|
|
|
20290
20557
|
return {
|
|
20291
20558
|
appId: manifest.appId,
|
|
20292
20559
|
channel: manifest.channel,
|
|
20560
|
+
storedBytes,
|
|
20561
|
+
storedFiles,
|
|
20293
20562
|
releaseId: manifest.releaseId,
|
|
20294
20563
|
reused,
|
|
20564
|
+
reusedBytes,
|
|
20565
|
+
reusedFiles,
|
|
20295
20566
|
rollout: input.rollout,
|
|
20296
20567
|
stage: "published"
|
|
20297
20568
|
};
|
|
@@ -20303,6 +20574,7 @@ var MOBILE_UPDATE_REGISTRY_FORMAT = 1, DEFAULT_PREFIX = "absolutejs/mobile-updat
|
|
|
20303
20574
|
if (!existing)
|
|
20304
20575
|
throw new MobileUpdateRegistryError("Mobile update channel does not exist");
|
|
20305
20576
|
if (input.releaseId) {
|
|
20577
|
+
await assertNotMarked(input.appId, input.releaseId);
|
|
20306
20578
|
const release = await readManifest(input.appId, input.releaseId);
|
|
20307
20579
|
if (!release || release.manifest.channel !== input.channel)
|
|
20308
20580
|
throw new MobileUpdateRegistryError("Mobile rollback release was not published");
|
|
@@ -20339,14 +20611,16 @@ var MOBILE_UPDATE_REGISTRY_FORMAT = 1, DEFAULT_PREFIX = "absolutejs/mobile-updat
|
|
|
20339
20611
|
const file = release.manifest.files.find((candidate) => candidate.path === requested);
|
|
20340
20612
|
if (!file)
|
|
20341
20613
|
return null;
|
|
20342
|
-
const
|
|
20614
|
+
const legacyKey = fileKey(release.manifest, file);
|
|
20615
|
+
const legacyHead = await options.store.head(legacyKey);
|
|
20616
|
+
const key = legacyHead ? legacyKey : contentBlobKey(release.manifest.appId, file.sha256);
|
|
20343
20617
|
const [bytes, head] = await Promise.all([
|
|
20344
20618
|
options.store.get(key),
|
|
20345
|
-
options.store.head(key)
|
|
20619
|
+
legacyHead ? Promise.resolve(legacyHead) : options.store.head(key)
|
|
20346
20620
|
]);
|
|
20347
20621
|
if (!bytes || !head)
|
|
20348
20622
|
return null;
|
|
20349
|
-
if (bytes.byteLength !== file.bytes || head.size !== file.bytes || head.metadata?.sha256 !== file.sha256 || (head.metadata?.releaseid ?? head.metadata?.releaseId) !== release.manifest.releaseId || digest(bytes) !== file.sha256)
|
|
20623
|
+
if (bytes.byteLength !== file.bytes || head.size !== file.bytes || head.metadata?.sha256 !== file.sha256 || legacyHead && (head.metadata?.releaseid ?? head.metadata?.releaseId) !== release.manifest.releaseId || digest(bytes) !== file.sha256)
|
|
20350
20624
|
throw new MobileUpdateRegistryError("Stored mobile update file integrity failed");
|
|
20351
20625
|
return { bytes, file };
|
|
20352
20626
|
}
|
|
@@ -20635,6 +20909,9 @@ var MOBILE_UPDATE_REGISTRY_FORMAT = 1, DEFAULT_PREFIX = "absolutejs/mobile-updat
|
|
|
20635
20909
|
var init_mobileUpdate = __esm(() => {
|
|
20636
20910
|
MAX_FILE_BYTES = 32 * 1024 * 1024;
|
|
20637
20911
|
MAX_TOTAL_BYTES = 128 * 1024 * 1024;
|
|
20912
|
+
DAY_MS = 24 * 60 * 60 * 1000;
|
|
20913
|
+
DEFAULT_MIN_AGE_MS = 30 * DAY_MS;
|
|
20914
|
+
DEFAULT_GRACE_PERIOD_MS = 7 * DAY_MS;
|
|
20638
20915
|
HASH = /^[a-f0-9]{64}$/;
|
|
20639
20916
|
RELEASE = /^amu_[a-f0-9]{64}$/;
|
|
20640
20917
|
APP_ID = /^[A-Za-z][\w]*(?:\.[A-Za-z][\w]*)+$/;
|
|
@@ -38480,10 +38757,26 @@ var requireCompatible = (manifest, config) => {
|
|
|
38480
38757
|
var createAbsoluteMobileUpdateClient = (options) => {
|
|
38481
38758
|
const manifestUrl = exactManifestUrl(options.config.manifestUrl);
|
|
38482
38759
|
const request = options.fetch ?? globalThis.fetch;
|
|
38483
|
-
const downloadFiles = async (manifest, index = 0,
|
|
38760
|
+
const downloadFiles = async (manifest, index = 0, transfer = {
|
|
38761
|
+
downloadedBytes: 0,
|
|
38762
|
+
downloadedFiles: 0,
|
|
38763
|
+
reusedBytes: 0,
|
|
38764
|
+
reusedFiles: 0,
|
|
38765
|
+
totalBytes: manifest.files.reduce((total, file) => total + file.bytes, 0),
|
|
38766
|
+
totalFiles: manifest.files.length
|
|
38767
|
+
}) => {
|
|
38484
38768
|
const file = manifest.files[index];
|
|
38485
38769
|
if (!file)
|
|
38486
|
-
return
|
|
38770
|
+
return transfer;
|
|
38771
|
+
const reusable = await options.store.readReusable?.(file);
|
|
38772
|
+
if (reusable && reusable.byteLength === file.bytes && await options.verifier.digest(reusable) === file.sha256) {
|
|
38773
|
+
await options.store.write(file, reusable);
|
|
38774
|
+
return downloadFiles(manifest, index + 1, {
|
|
38775
|
+
...transfer,
|
|
38776
|
+
reusedBytes: transfer.reusedBytes + reusable.byteLength,
|
|
38777
|
+
reusedFiles: transfer.reusedFiles + 1
|
|
38778
|
+
});
|
|
38779
|
+
}
|
|
38487
38780
|
const asset2 = await request(fileUrl(manifestUrl, manifest.releaseId, file.path), {
|
|
38488
38781
|
cache: "no-store",
|
|
38489
38782
|
credentials: "omit",
|
|
@@ -38493,13 +38786,17 @@ var createAbsoluteMobileUpdateClient = (options) => {
|
|
|
38493
38786
|
if (!asset2.ok)
|
|
38494
38787
|
throw new TypeError(`Mobile update asset ${file.path} failed with HTTP ${asset2.status}.`);
|
|
38495
38788
|
const contents = await readBounded(asset2, file.bytes);
|
|
38496
|
-
const
|
|
38497
|
-
if (contents.byteLength !== file.bytes ||
|
|
38789
|
+
const downloadedBytes = transfer.downloadedBytes + contents.byteLength;
|
|
38790
|
+
if (contents.byteLength !== file.bytes || downloadedBytes > ABSOLUTE_MOBILE_UPDATE_MAX_TOTAL_BYTES)
|
|
38498
38791
|
throw new TypeError(`Mobile update asset ${file.path} has an invalid size.`);
|
|
38499
38792
|
if (await options.verifier.digest(contents) !== file.sha256)
|
|
38500
38793
|
throw new TypeError(`Mobile update asset ${file.path} failed integrity verification.`);
|
|
38501
38794
|
await options.store.write(file, contents);
|
|
38502
|
-
return downloadFiles(manifest, index + 1,
|
|
38795
|
+
return downloadFiles(manifest, index + 1, {
|
|
38796
|
+
...transfer,
|
|
38797
|
+
downloadedBytes,
|
|
38798
|
+
downloadedFiles: transfer.downloadedFiles + 1
|
|
38799
|
+
});
|
|
38503
38800
|
};
|
|
38504
38801
|
const check = async (download = false) => {
|
|
38505
38802
|
const response = await request(manifestUrl, {
|
|
@@ -38531,14 +38828,15 @@ var createAbsoluteMobileUpdateClient = (options) => {
|
|
|
38531
38828
|
if (!download)
|
|
38532
38829
|
return { kind: "update-available", manifest };
|
|
38533
38830
|
await options.store.begin(manifest);
|
|
38831
|
+
let transfer;
|
|
38534
38832
|
try {
|
|
38535
|
-
await downloadFiles(manifest);
|
|
38833
|
+
transfer = await downloadFiles(manifest);
|
|
38536
38834
|
await options.store.commit(manifest);
|
|
38537
38835
|
} catch (error) {
|
|
38538
38836
|
await options.store.abort(manifest.releaseId);
|
|
38539
38837
|
throw error;
|
|
38540
38838
|
}
|
|
38541
|
-
return { kind: "downloaded", manifest };
|
|
38839
|
+
return { kind: "downloaded", manifest, transfer };
|
|
38542
38840
|
};
|
|
38543
38841
|
return {
|
|
38544
38842
|
check,
|
|
@@ -38660,6 +38958,50 @@ var verifyAbsoluteMobileUpdateSignature = (manifestValue, publicKey) => {
|
|
|
38660
38958
|
};
|
|
38661
38959
|
|
|
38662
38960
|
// src/mobile/updatePublisher.ts
|
|
38961
|
+
var lifecycleMethod = (publisher, name) => {
|
|
38962
|
+
const method = publisher[name];
|
|
38963
|
+
if (typeof method !== "function")
|
|
38964
|
+
throw new TypeError(`Mobile update registry does not support ${name}. Re-run \`absolute mobile update provision --force\` after upgrading @absolutejs/deploy.`);
|
|
38965
|
+
return method;
|
|
38966
|
+
};
|
|
38967
|
+
var validOptionalCounters = (values) => values.every((value) => value === undefined) || values.every((value) => Number.isSafeInteger(value) && (value ?? -1) >= 0);
|
|
38968
|
+
var validateStorageIdentity = (result, appId) => {
|
|
38969
|
+
if (!object5(result) || result.appId !== appId || !Array.isArray(result.releases) || ![
|
|
38970
|
+
result.channelCount,
|
|
38971
|
+
result.reclaimableBytes,
|
|
38972
|
+
result.releaseBytes,
|
|
38973
|
+
result.releaseCount,
|
|
38974
|
+
result.totalBytes,
|
|
38975
|
+
result.totalObjectCount,
|
|
38976
|
+
result.untrackedBytes
|
|
38977
|
+
].every((value) => Number.isSafeInteger(value) && value >= 0) || !validOptionalCounters([
|
|
38978
|
+
result.contentBlobBytes,
|
|
38979
|
+
result.contentBlobCount,
|
|
38980
|
+
result.reclaimableContentBytes
|
|
38981
|
+
]))
|
|
38982
|
+
throw new TypeError("Mobile update registry returned an invalid storage report.");
|
|
38983
|
+
return result;
|
|
38984
|
+
};
|
|
38985
|
+
var inspectAbsoluteMobileUpdateStorage = async (options) => validateStorageIdentity(await lifecycleMethod(options.publisher, "inspectUpdateStorage")({
|
|
38986
|
+
appId: options.appId,
|
|
38987
|
+
...options.minAgeMs === undefined ? {} : { minAgeMs: options.minAgeMs },
|
|
38988
|
+
...options.retainRecent === undefined ? {} : { retainRecent: options.retainRecent },
|
|
38989
|
+
...options.signal ? { signal: options.signal } : {}
|
|
38990
|
+
}), options.appId);
|
|
38991
|
+
var pruneAbsoluteMobileUpdates = async (options) => {
|
|
38992
|
+
const result = await lifecycleMethod(options.publisher, "pruneUpdates")({
|
|
38993
|
+
appId: options.appId,
|
|
38994
|
+
...options.apply === undefined ? {} : { apply: options.apply },
|
|
38995
|
+
...options.gracePeriodMs === undefined ? {} : { gracePeriodMs: options.gracePeriodMs },
|
|
38996
|
+
...options.minAgeMs === undefined ? {} : { minAgeMs: options.minAgeMs },
|
|
38997
|
+
...options.retainRecent === undefined ? {} : { retainRecent: options.retainRecent },
|
|
38998
|
+
...options.signal ? { signal: options.signal } : {}
|
|
38999
|
+
});
|
|
39000
|
+
validateStorageIdentity(result, options.appId);
|
|
39001
|
+
if (!Array.isArray(result.marked) || !Array.isArray(result.restored) || !Array.isArray(result.swept) || result.sweptContentBlobs !== undefined && !Array.isArray(result.sweptContentBlobs) || typeof result.dryRun !== "boolean" || !Number.isSafeInteger(result.reclaimedBytes) || result.reclaimedBytes < 0)
|
|
39002
|
+
throw new TypeError("Mobile update registry returned an invalid collection report.");
|
|
39003
|
+
return result;
|
|
39004
|
+
};
|
|
38663
39005
|
var object5 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
38664
39006
|
var isPublisher2 = (value) => object5(value) && typeof value.publishUpdate === "function" && typeof value.promoteUpdate === "function" && typeof value.rollbackUpdate === "function";
|
|
38665
39007
|
var projectPath2 = (projectRoot, requested, label) => {
|
|
@@ -38702,7 +39044,12 @@ var publishAbsoluteMobileUpdate = async (options) => {
|
|
|
38702
39044
|
rollout: options.rollout,
|
|
38703
39045
|
signal: options.signal
|
|
38704
39046
|
});
|
|
38705
|
-
if (result.appId !== manifest.appId || result.channel !== manifest.channel || result.releaseId !== manifest.releaseId || result.rollout !== options.rollout || result.stage !== "published" || typeof result.reused !== "boolean"
|
|
39047
|
+
if (result.appId !== manifest.appId || result.channel !== manifest.channel || result.releaseId !== manifest.releaseId || result.rollout !== options.rollout || result.stage !== "published" || typeof result.reused !== "boolean" || !validOptionalCounters([
|
|
39048
|
+
result.storedBytes,
|
|
39049
|
+
result.storedFiles,
|
|
39050
|
+
result.reusedBytes,
|
|
39051
|
+
result.reusedFiles
|
|
39052
|
+
]))
|
|
38706
39053
|
throw new TypeError("Mobile update registry returned a different publication identity.");
|
|
38707
39054
|
return result;
|
|
38708
39055
|
};
|
|
@@ -38879,6 +39226,7 @@ export {
|
|
|
38879
39226
|
inspectAbsoluteAndroidInstalledApp,
|
|
38880
39227
|
inspectAbsoluteMobileRouteMetadata,
|
|
38881
39228
|
inspectAbsoluteMobileUpdateServer,
|
|
39229
|
+
inspectAbsoluteMobileUpdateStorage,
|
|
38882
39230
|
inspectAbsoluteRemoteMac,
|
|
38883
39231
|
inspectAbsoluteRemoteMacLanHost,
|
|
38884
39232
|
inspectAbsoluteRemoteMacWorkspace,
|
|
@@ -38936,6 +39284,7 @@ export {
|
|
|
38936
39284
|
projectUsesAbsoluteAuth,
|
|
38937
39285
|
projectUsesAbsoluteSync,
|
|
38938
39286
|
promoteAbsoluteMobileUpdate,
|
|
39287
|
+
pruneAbsoluteMobileUpdates,
|
|
38939
39288
|
publishAbsoluteAndroidRelease,
|
|
38940
39289
|
publishAbsoluteIosRelease,
|
|
38941
39290
|
publishAbsoluteMobileUpdate,
|
|
@@ -38984,5 +39333,5 @@ export {
|
|
|
38984
39333
|
writeAbsoluteMobileUpdateRegistry
|
|
38985
39334
|
};
|
|
38986
39335
|
|
|
38987
|
-
//# debugId=
|
|
39336
|
+
//# debugId=A051836D5390197D64756E2164756E21
|
|
38988
39337
|
//# sourceMappingURL=index.js.map
|