@absolutejs/absolute 0.20.0-beta.83 → 0.20.0-beta.85
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 +104 -17
- package/dist/build.js.map +3 -3
- package/dist/cli/{compile-t73ac1zb.js → compile-d7g2fvqe.js} +1 -1
- package/dist/cli/index.js +2 -2
- package/dist/cli/{mobile-9dx39bd3.js → mobile-pdck6a35.js} +17 -3
- package/dist/index.js +104 -17
- package/dist/index.js.map +3 -3
- package/dist/mobile/index.js +294 -48
- package/dist/mobile/index.js.map +5 -5
- package/dist/mobile/shellUpdate.js +302 -31
- package/dist/src/mobile/updateClient.d.ts +31 -0
- package/dist/src/mobile/updatePublisher.d.ts +4 -0
- package/package.json +2 -2
|
@@ -157,17 +157,18 @@ var fileUrl = (manifestUrl, releaseId, path) => {
|
|
|
157
157
|
throw new TypeError("Mobile update asset escaped its signed release origin.");
|
|
158
158
|
return result;
|
|
159
159
|
};
|
|
160
|
-
var readChunks = async (reader, maximum, chunks = [], received = 0) => {
|
|
160
|
+
var readChunks = async (reader, maximum, onChunk, chunks = [], received = 0) => {
|
|
161
161
|
const result = await reader.read();
|
|
162
162
|
if (result.done)
|
|
163
163
|
return { chunks, received };
|
|
164
164
|
const total = received + result.value.byteLength;
|
|
165
165
|
if (total > maximum)
|
|
166
166
|
throw new TypeError("Mobile update response exceeds its signed size.");
|
|
167
|
+
await onChunk?.(result.value, received);
|
|
167
168
|
chunks.push(result.value);
|
|
168
|
-
return readChunks(reader, maximum, chunks, total);
|
|
169
|
+
return readChunks(reader, maximum, onChunk, chunks, total);
|
|
169
170
|
};
|
|
170
|
-
var readBounded = async (response, maximum) => {
|
|
171
|
+
var readBounded = async (response, maximum, onChunk) => {
|
|
171
172
|
const declared = Number(response.headers.get("content-length"));
|
|
172
173
|
if (Number.isFinite(declared) && declared > maximum)
|
|
173
174
|
throw new TypeError("Mobile update response exceeds its signed size.");
|
|
@@ -175,8 +176,9 @@ var readBounded = async (response, maximum) => {
|
|
|
175
176
|
return new Uint8Array;
|
|
176
177
|
const reader = response.body.getReader();
|
|
177
178
|
let result;
|
|
179
|
+
const chunks = [];
|
|
178
180
|
try {
|
|
179
|
-
result = await readChunks(reader, maximum);
|
|
181
|
+
result = await readChunks(reader, maximum, onChunk, chunks);
|
|
180
182
|
} catch (error) {
|
|
181
183
|
await reader.cancel().catch(() => {
|
|
182
184
|
return;
|
|
@@ -185,7 +187,7 @@ var readBounded = async (response, maximum) => {
|
|
|
185
187
|
}
|
|
186
188
|
const contents = new Uint8Array(result.received);
|
|
187
189
|
let offset = 0;
|
|
188
|
-
for (const chunk of
|
|
190
|
+
for (const chunk of chunks) {
|
|
189
191
|
contents.set(chunk, offset);
|
|
190
192
|
offset += chunk.byteLength;
|
|
191
193
|
}
|
|
@@ -206,29 +208,172 @@ var requireCompatible = (manifest, config) => {
|
|
|
206
208
|
if (manifest.runtimeFingerprint !== config.runtimeFingerprint)
|
|
207
209
|
throw new TypeError("Mobile update requires a different native runtime.");
|
|
208
210
|
};
|
|
211
|
+
var networkConcurrency = (requested) => {
|
|
212
|
+
const bounded = Math.max(1, Math.min(6, Math.floor(requested ?? 3)));
|
|
213
|
+
const navigatorValue = Reflect.get(globalThis, "navigator");
|
|
214
|
+
const connection = typeof navigatorValue === "object" && navigatorValue !== null ? Reflect.get(navigatorValue, "connection") : undefined;
|
|
215
|
+
if (typeof connection !== "object" || connection === null)
|
|
216
|
+
return bounded;
|
|
217
|
+
if (Reflect.get(connection, "saveData") === true)
|
|
218
|
+
return 1;
|
|
219
|
+
const effectiveType = Reflect.get(connection, "effectiveType");
|
|
220
|
+
if (effectiveType === "slow-2g" || effectiveType === "2g")
|
|
221
|
+
return 1;
|
|
222
|
+
if (effectiveType === "3g")
|
|
223
|
+
return Math.min(2, bounded);
|
|
224
|
+
return bounded;
|
|
225
|
+
};
|
|
226
|
+
var combine = (prefix, suffix) => {
|
|
227
|
+
const result = new Uint8Array(prefix.byteLength + suffix.byteLength);
|
|
228
|
+
result.set(prefix);
|
|
229
|
+
result.set(suffix, prefix.byteLength);
|
|
230
|
+
return result;
|
|
231
|
+
};
|
|
232
|
+
var validContentRange = (value, start, total) => value === `bytes ${start}-${total - 1}/${total}`;
|
|
233
|
+
var combineSignals = (signals) => {
|
|
234
|
+
const nativeAny = Reflect.get(AbortSignal, "any");
|
|
235
|
+
if (typeof nativeAny === "function")
|
|
236
|
+
return Reflect.apply(nativeAny, AbortSignal, [signals]);
|
|
237
|
+
const controller = new AbortController;
|
|
238
|
+
const abort = () => controller.abort();
|
|
239
|
+
if (signals.some((signal) => signal.aborted))
|
|
240
|
+
abort();
|
|
241
|
+
else
|
|
242
|
+
signals.forEach((signal) => signal.addEventListener("abort", abort, { once: true }));
|
|
243
|
+
return controller.signal;
|
|
244
|
+
};
|
|
209
245
|
var createAbsoluteMobileUpdateClient = (options) => {
|
|
210
246
|
const manifestUrl = exactManifestUrl(options.config.manifestUrl);
|
|
211
247
|
const request = options.fetch ?? globalThis.fetch;
|
|
212
|
-
const downloadFiles = async (manifest
|
|
213
|
-
const
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
248
|
+
const downloadFiles = async (manifest) => {
|
|
249
|
+
const startedAt = performance.now();
|
|
250
|
+
const transfer = {
|
|
251
|
+
avoidedBytes: 0,
|
|
252
|
+
completedFiles: 0,
|
|
253
|
+
downloadedBytes: 0,
|
|
254
|
+
downloadedFiles: 0,
|
|
255
|
+
durationMs: 0,
|
|
256
|
+
resumedBytes: 0,
|
|
257
|
+
resumedFiles: 0,
|
|
258
|
+
reusedBytes: 0,
|
|
259
|
+
reusedFiles: 0,
|
|
260
|
+
throughputBytesPerSecond: 0,
|
|
261
|
+
totalBytes: manifest.files.reduce((total, file) => total + file.bytes, 0),
|
|
262
|
+
totalFiles: manifest.files.length
|
|
263
|
+
};
|
|
264
|
+
const updateTiming = () => {
|
|
265
|
+
transfer.durationMs = Math.max(0, performance.now() - startedAt);
|
|
266
|
+
transfer.avoidedBytes = transfer.reusedBytes + transfer.resumedBytes;
|
|
267
|
+
transfer.throughputBytesPerSecond = transfer.durationMs > 0 ? Math.round(transfer.downloadedBytes * 1000 / transfer.durationMs) : transfer.downloadedBytes;
|
|
268
|
+
};
|
|
269
|
+
const progress = () => {
|
|
270
|
+
updateTiming();
|
|
271
|
+
try {
|
|
272
|
+
options.onProgress?.({
|
|
273
|
+
...transfer,
|
|
274
|
+
kind: "download-progress"
|
|
275
|
+
});
|
|
276
|
+
} catch {}
|
|
277
|
+
};
|
|
278
|
+
const controller = new AbortController;
|
|
279
|
+
let next = 0;
|
|
280
|
+
let firstError;
|
|
281
|
+
const downloadFile = async (file) => {
|
|
282
|
+
const staged = await options.store.readStaged?.(file);
|
|
283
|
+
if (staged?.byteLength === file.bytes && await options.verifier.digest(staged) === file.sha256) {
|
|
284
|
+
transfer.resumedBytes += staged.byteLength;
|
|
285
|
+
transfer.resumedFiles += 1;
|
|
286
|
+
transfer.completedFiles += 1;
|
|
287
|
+
progress();
|
|
288
|
+
return;
|
|
289
|
+
}
|
|
290
|
+
const reusable = await options.store.readReusable?.(file);
|
|
291
|
+
if (reusable?.byteLength === file.bytes && await options.verifier.digest(reusable) === file.sha256) {
|
|
292
|
+
await options.store.write(file, reusable);
|
|
293
|
+
transfer.reusedBytes += reusable.byteLength;
|
|
294
|
+
transfer.reusedFiles += 1;
|
|
295
|
+
transfer.completedFiles += 1;
|
|
296
|
+
progress();
|
|
297
|
+
return;
|
|
298
|
+
}
|
|
299
|
+
const candidate = await options.store.readPartial?.(file);
|
|
300
|
+
if (candidate?.byteLength === file.bytes && await options.verifier.digest(candidate) === file.sha256) {
|
|
301
|
+
await options.store.write(file, candidate);
|
|
302
|
+
transfer.resumedBytes += candidate.byteLength;
|
|
303
|
+
transfer.resumedFiles += 1;
|
|
304
|
+
transfer.completedFiles += 1;
|
|
305
|
+
progress();
|
|
306
|
+
return;
|
|
307
|
+
}
|
|
308
|
+
const partial = candidate && candidate.byteLength > 0 && candidate.byteLength < file.bytes ? candidate : new Uint8Array;
|
|
309
|
+
const headers = new Headers;
|
|
310
|
+
if (partial.byteLength > 0) {
|
|
311
|
+
headers.set("if-range", `"${file.sha256}"`);
|
|
312
|
+
headers.set("range", `bytes=${partial.byteLength}-`);
|
|
313
|
+
}
|
|
314
|
+
const asset = await request(fileUrl(manifestUrl, manifest.releaseId, file.path), {
|
|
315
|
+
cache: "no-store",
|
|
316
|
+
credentials: "omit",
|
|
317
|
+
headers,
|
|
318
|
+
redirect: "error",
|
|
319
|
+
signal: combineSignals([
|
|
320
|
+
controller.signal,
|
|
321
|
+
AbortSignal.timeout(30000)
|
|
322
|
+
])
|
|
323
|
+
});
|
|
324
|
+
if (!asset.ok)
|
|
325
|
+
throw new TypeError(`Mobile update asset ${file.path} failed with HTTP ${asset.status}.`);
|
|
326
|
+
const ranged = asset.status === 206;
|
|
327
|
+
if (ranged && (partial.byteLength === 0 || !validContentRange(asset.headers.get("content-range"), partial.byteLength, file.bytes)))
|
|
328
|
+
throw new TypeError(`Mobile update asset ${file.path} returned an invalid byte range.`);
|
|
329
|
+
const prefix = ranged ? partial : new Uint8Array;
|
|
330
|
+
if (ranged) {
|
|
331
|
+
transfer.resumedBytes += partial.byteLength;
|
|
332
|
+
transfer.resumedFiles += 1;
|
|
333
|
+
}
|
|
334
|
+
const downloaded = await readBounded(asset, file.bytes - prefix.byteLength, options.store.appendPartial ? async (chunk, offset) => {
|
|
335
|
+
await options.store.appendPartial?.(file, chunk, prefix.byteLength + offset);
|
|
336
|
+
transfer.downloadedBytes += chunk.byteLength;
|
|
337
|
+
progress();
|
|
338
|
+
} : undefined);
|
|
339
|
+
if (!options.store.appendPartial) {
|
|
340
|
+
transfer.downloadedBytes += downloaded.byteLength;
|
|
341
|
+
progress();
|
|
342
|
+
}
|
|
343
|
+
if (transfer.downloadedBytes > ABSOLUTE_MOBILE_UPDATE_MAX_TOTAL_BYTES)
|
|
344
|
+
throw new TypeError("Mobile update exceeds the maximum transfer size.");
|
|
345
|
+
const contents = combine(prefix, downloaded);
|
|
346
|
+
if (contents.byteLength !== file.bytes)
|
|
347
|
+
throw new TypeError(`Mobile update asset ${file.path} has an invalid size.`);
|
|
348
|
+
if (await options.verifier.digest(contents) !== file.sha256)
|
|
349
|
+
throw new TypeError(`Mobile update asset ${file.path} failed integrity verification.`);
|
|
350
|
+
await options.store.write(file, contents);
|
|
351
|
+
transfer.downloadedFiles += 1;
|
|
352
|
+
transfer.completedFiles += 1;
|
|
353
|
+
progress();
|
|
354
|
+
};
|
|
355
|
+
const worker = async () => {
|
|
356
|
+
if (firstError)
|
|
357
|
+
return;
|
|
358
|
+
const index = next++;
|
|
359
|
+
const file = manifest.files[index];
|
|
360
|
+
if (!file)
|
|
361
|
+
return;
|
|
362
|
+
try {
|
|
363
|
+
await downloadFile(file);
|
|
364
|
+
} catch (error) {
|
|
365
|
+
firstError ??= error;
|
|
366
|
+
controller.abort();
|
|
367
|
+
}
|
|
368
|
+
await worker();
|
|
369
|
+
};
|
|
370
|
+
await Promise.all(Array.from({
|
|
371
|
+
length: Math.min(networkConcurrency(options.concurrency), manifest.files.length)
|
|
372
|
+
}, () => worker()));
|
|
373
|
+
if (firstError)
|
|
374
|
+
throw firstError;
|
|
375
|
+
updateTiming();
|
|
376
|
+
return transfer;
|
|
232
377
|
};
|
|
233
378
|
const check = async (download = false) => {
|
|
234
379
|
const response = await request(manifestUrl, {
|
|
@@ -260,14 +405,18 @@ var createAbsoluteMobileUpdateClient = (options) => {
|
|
|
260
405
|
if (!download)
|
|
261
406
|
return { kind: "update-available", manifest };
|
|
262
407
|
await options.store.begin(manifest);
|
|
408
|
+
let transfer;
|
|
263
409
|
try {
|
|
264
|
-
await downloadFiles(manifest);
|
|
410
|
+
transfer = await downloadFiles(manifest);
|
|
265
411
|
await options.store.commit(manifest);
|
|
266
412
|
} catch (error) {
|
|
267
|
-
|
|
413
|
+
if (options.store.suspend)
|
|
414
|
+
await options.store.suspend(manifest.releaseId);
|
|
415
|
+
else
|
|
416
|
+
await options.store.abort(manifest.releaseId);
|
|
268
417
|
throw error;
|
|
269
418
|
}
|
|
270
|
-
return { kind: "downloaded", manifest };
|
|
419
|
+
return { kind: "downloaded", manifest, transfer };
|
|
271
420
|
};
|
|
272
421
|
return {
|
|
273
422
|
check,
|
|
@@ -278,10 +427,12 @@ var createAbsoluteMobileUpdateClient = (options) => {
|
|
|
278
427
|
|
|
279
428
|
// src/mobile/shellUpdate.ts
|
|
280
429
|
var STATE_KEY = "absolute.mobile.update.state.v1";
|
|
430
|
+
var STAGING_KEY = "absolute.mobile.update.staging.v1";
|
|
281
431
|
var INSTALLATION_KEY = "absolute.mobile.update.installation.v1";
|
|
282
432
|
var RESULT_KEY = Symbol.for("absolutejs.mobile.update.result");
|
|
283
433
|
var RESULTS_KEY = Symbol.for("absolutejs.mobile.update.results");
|
|
284
434
|
var ROOT = "NoCloud/ionic_built_snapshots";
|
|
435
|
+
var STAGING_ROOT = "NoCloud/absolute_update_staging";
|
|
285
436
|
var watchdog = registerPlugin("AbsoluteMobileUpdateWatchdog");
|
|
286
437
|
var base64Bytes = (value) => Uint8Array.from(atob(value), (character) => character.charCodeAt(0));
|
|
287
438
|
var arrayBuffer = (value) => new Uint8Array(value).buffer;
|
|
@@ -340,6 +491,25 @@ var readState = async () => {
|
|
|
340
491
|
}
|
|
341
492
|
};
|
|
342
493
|
var writeState = (state) => Preferences.set({ key: STATE_KEY, value: JSON.stringify(state) });
|
|
494
|
+
var clearStagingRoot = () => Filesystem.rmdir({
|
|
495
|
+
directory: Directory.Library,
|
|
496
|
+
path: STAGING_ROOT,
|
|
497
|
+
recursive: true
|
|
498
|
+
}).catch(() => {
|
|
499
|
+
return;
|
|
500
|
+
});
|
|
501
|
+
var readStaging = async () => {
|
|
502
|
+
const { value } = await Preferences.get({ key: STAGING_KEY });
|
|
503
|
+
if (!value)
|
|
504
|
+
return;
|
|
505
|
+
try {
|
|
506
|
+
return parseAbsoluteMobileUpdateManifest(JSON.parse(value));
|
|
507
|
+
} catch {
|
|
508
|
+
await Preferences.remove({ key: STAGING_KEY });
|
|
509
|
+
await clearStagingRoot();
|
|
510
|
+
return;
|
|
511
|
+
}
|
|
512
|
+
};
|
|
343
513
|
var installationId = async () => {
|
|
344
514
|
const existing = await Preferences.get({ key: INSTALLATION_KEY });
|
|
345
515
|
if (existing.value && /^[a-f0-9-]{36}$/u.test(existing.value))
|
|
@@ -358,6 +528,7 @@ var webView = () => {
|
|
|
358
528
|
};
|
|
359
529
|
var currentServerBasePath = () => new Promise((resolve) => webView().getServerBasePath(resolve));
|
|
360
530
|
var releasePath = (releaseId) => `${ROOT}/${releaseId}`;
|
|
531
|
+
var partialPath = (releaseId, file) => `${STAGING_ROOT}/${releaseId}/${file.path}.part`;
|
|
361
532
|
var releaseNativePath = async (releaseId) => {
|
|
362
533
|
const { uri } = await Filesystem.getUri({
|
|
363
534
|
directory: Directory.Library,
|
|
@@ -375,12 +546,26 @@ var removeRelease = async (releaseId) => {
|
|
|
375
546
|
return;
|
|
376
547
|
});
|
|
377
548
|
};
|
|
549
|
+
var removeStaging = async (releaseId) => {
|
|
550
|
+
await Filesystem.rmdir({
|
|
551
|
+
directory: Directory.Library,
|
|
552
|
+
path: `${STAGING_ROOT}/${releaseId}`,
|
|
553
|
+
recursive: true
|
|
554
|
+
}).catch(() => {
|
|
555
|
+
return;
|
|
556
|
+
});
|
|
557
|
+
};
|
|
558
|
+
var filesystemBytes = async (data) => typeof data === "string" ? base64Bytes(data) : new Uint8Array(await data.arrayBuffer());
|
|
378
559
|
var createStore = () => {
|
|
379
560
|
let staging;
|
|
380
561
|
return {
|
|
381
562
|
abort: async (releaseId) => {
|
|
382
563
|
staging = undefined;
|
|
383
564
|
await removeRelease(releaseId);
|
|
565
|
+
await removeStaging(releaseId);
|
|
566
|
+
const persisted = await readStaging();
|
|
567
|
+
if (persisted?.releaseId === releaseId)
|
|
568
|
+
await Preferences.remove({ key: STAGING_KEY });
|
|
384
569
|
const state = await readState();
|
|
385
570
|
if (state.readyRelease === releaseId || state.pendingRelease === releaseId)
|
|
386
571
|
await writeState({
|
|
@@ -417,18 +602,59 @@ var createStore = () => {
|
|
|
417
602
|
throw error;
|
|
418
603
|
}
|
|
419
604
|
},
|
|
605
|
+
appendPartial: async (file, contents, offset) => {
|
|
606
|
+
if (!staging || !staging.files.some((candidate) => candidate.path === file.path))
|
|
607
|
+
throw new TypeError("Mobile update partial write is outside its staging transaction.");
|
|
608
|
+
const path = partialPath(staging.releaseId, file);
|
|
609
|
+
if (offset === 0) {
|
|
610
|
+
await Filesystem.writeFile({
|
|
611
|
+
data: bytesBase64(contents),
|
|
612
|
+
directory: Directory.Library,
|
|
613
|
+
path,
|
|
614
|
+
recursive: true
|
|
615
|
+
});
|
|
616
|
+
return;
|
|
617
|
+
}
|
|
618
|
+
const stat = await Filesystem.stat({
|
|
619
|
+
directory: Directory.Library,
|
|
620
|
+
path
|
|
621
|
+
});
|
|
622
|
+
if (stat.size !== offset)
|
|
623
|
+
throw new TypeError("Mobile update partial checkpoint changed unexpectedly.");
|
|
624
|
+
await Filesystem.appendFile({
|
|
625
|
+
data: bytesBase64(contents),
|
|
626
|
+
directory: Directory.Library,
|
|
627
|
+
path
|
|
628
|
+
});
|
|
629
|
+
},
|
|
420
630
|
begin: async (manifest) => {
|
|
421
|
-
await
|
|
631
|
+
const persisted = await readStaging();
|
|
632
|
+
const resume = persisted?.releaseId === manifest.releaseId && persisted.signature.keyId === manifest.signature.keyId && persisted.signature.value === manifest.signature.value;
|
|
633
|
+
if (persisted && !resume)
|
|
634
|
+
await Promise.all([
|
|
635
|
+
removeRelease(persisted.releaseId),
|
|
636
|
+
removeStaging(persisted.releaseId)
|
|
637
|
+
]);
|
|
638
|
+
if (!resume) {
|
|
639
|
+
await removeRelease(manifest.releaseId);
|
|
640
|
+
await removeStaging(manifest.releaseId);
|
|
641
|
+
}
|
|
422
642
|
await Filesystem.mkdir({
|
|
423
643
|
directory: Directory.Library,
|
|
424
644
|
path: releasePath(manifest.releaseId),
|
|
425
645
|
recursive: true
|
|
426
646
|
});
|
|
647
|
+
await Preferences.set({
|
|
648
|
+
key: STAGING_KEY,
|
|
649
|
+
value: JSON.stringify(manifest)
|
|
650
|
+
});
|
|
427
651
|
staging = manifest;
|
|
428
652
|
},
|
|
429
653
|
commit: async (manifest) => {
|
|
430
654
|
if (staging?.releaseId !== manifest.releaseId)
|
|
431
655
|
throw new TypeError("Mobile update staging transaction changed identity.");
|
|
656
|
+
await removeStaging(manifest.releaseId);
|
|
657
|
+
await Preferences.remove({ key: STAGING_KEY });
|
|
432
658
|
const state = await readState();
|
|
433
659
|
await writeState({
|
|
434
660
|
activeRelease: state.activeRelease,
|
|
@@ -437,6 +663,38 @@ var createStore = () => {
|
|
|
437
663
|
});
|
|
438
664
|
staging = undefined;
|
|
439
665
|
},
|
|
666
|
+
readPartial: async (file) => {
|
|
667
|
+
if (!staging)
|
|
668
|
+
return null;
|
|
669
|
+
const result = await Filesystem.readFile({
|
|
670
|
+
directory: Directory.Library,
|
|
671
|
+
path: partialPath(staging.releaseId, file)
|
|
672
|
+
}).catch(() => null);
|
|
673
|
+
return result ? filesystemBytes(result.data).catch(() => null) : null;
|
|
674
|
+
},
|
|
675
|
+
readReusable: async (file) => {
|
|
676
|
+
const state = await readState();
|
|
677
|
+
if (!state.activeRelease)
|
|
678
|
+
return null;
|
|
679
|
+
const result = await Filesystem.readFile({
|
|
680
|
+
directory: Directory.Library,
|
|
681
|
+
path: `${releasePath(state.activeRelease)}/${file.path}`
|
|
682
|
+
}).catch(() => null);
|
|
683
|
+
return result ? filesystemBytes(result.data).catch(() => null) : null;
|
|
684
|
+
},
|
|
685
|
+
readStaged: async (file) => {
|
|
686
|
+
if (!staging)
|
|
687
|
+
return null;
|
|
688
|
+
const result = await Filesystem.readFile({
|
|
689
|
+
directory: Directory.Library,
|
|
690
|
+
path: `${releasePath(staging.releaseId)}/${file.path}`
|
|
691
|
+
}).catch(() => null);
|
|
692
|
+
return result ? filesystemBytes(result.data).catch(() => null) : null;
|
|
693
|
+
},
|
|
694
|
+
suspend: async (releaseId) => {
|
|
695
|
+
if (staging?.releaseId === releaseId)
|
|
696
|
+
staging = undefined;
|
|
697
|
+
},
|
|
440
698
|
write: async (file, contents) => {
|
|
441
699
|
if (!staging || !staging.files.some((candidate) => candidate.path === file.path))
|
|
442
700
|
throw new TypeError("Mobile update write is outside its staging transaction.");
|
|
@@ -529,14 +787,27 @@ var installAbsoluteMobileShellUpdates = async (manifest) => {
|
|
|
529
787
|
runtimeFingerprint: manifest.nativeRuntime
|
|
530
788
|
},
|
|
531
789
|
store,
|
|
532
|
-
verifier: createVerifier(manifest.updates.publicKeys)
|
|
790
|
+
verifier: createVerifier(manifest.updates.publicKeys),
|
|
791
|
+
onProgress: (progress) => emitUpdateResult(progress)
|
|
533
792
|
});
|
|
534
793
|
const activateDownloaded = async (result) => {
|
|
535
794
|
if (result.kind !== "downloaded")
|
|
536
795
|
return;
|
|
537
796
|
emitUpdateResult({
|
|
797
|
+
avoidedBytes: result.transfer.avoidedBytes,
|
|
798
|
+
completedFiles: result.transfer.completedFiles,
|
|
799
|
+
downloadedBytes: result.transfer.downloadedBytes,
|
|
800
|
+
downloadedFiles: result.transfer.downloadedFiles,
|
|
801
|
+
durationMs: Math.round(result.transfer.durationMs),
|
|
538
802
|
kind: "downloaded",
|
|
539
|
-
releaseId: result.manifest.releaseId
|
|
803
|
+
releaseId: result.manifest.releaseId,
|
|
804
|
+
resumedBytes: result.transfer.resumedBytes,
|
|
805
|
+
resumedFiles: result.transfer.resumedFiles,
|
|
806
|
+
reusedBytes: result.transfer.reusedBytes,
|
|
807
|
+
reusedFiles: result.transfer.reusedFiles,
|
|
808
|
+
throughputBytesPerSecond: result.transfer.throughputBytesPerSecond,
|
|
809
|
+
totalBytes: result.transfer.totalBytes,
|
|
810
|
+
totalFiles: result.transfer.totalFiles
|
|
540
811
|
});
|
|
541
812
|
await client.activate(result.manifest.releaseId);
|
|
542
813
|
};
|
|
@@ -13,6 +13,16 @@ export type AbsoluteMobileUpdateStore = {
|
|
|
13
13
|
activate(releaseId: string): Promise<void>;
|
|
14
14
|
begin(manifest: AbsoluteMobileUpdateManifest): Promise<void>;
|
|
15
15
|
commit(manifest: AbsoluteMobileUpdateManifest): Promise<void>;
|
|
16
|
+
/** Append a response chunk to persistent staging at the expected offset. */
|
|
17
|
+
appendPartial?(file: AbsoluteMobileUpdateFile, contents: Uint8Array, offset: number): Promise<void>;
|
|
18
|
+
/** Return an unverified, incomplete download from persistent staging. */
|
|
19
|
+
readPartial?(file: AbsoluteMobileUpdateFile): Promise<Uint8Array | null>;
|
|
20
|
+
/** Return a locally cached candidate for this exact path, when available. */
|
|
21
|
+
readReusable?(file: AbsoluteMobileUpdateFile): Promise<Uint8Array | null>;
|
|
22
|
+
/** Return an unverified completed file from a prior staging attempt. */
|
|
23
|
+
readStaged?(file: AbsoluteMobileUpdateFile): Promise<Uint8Array | null>;
|
|
24
|
+
/** End this attempt without deleting persistent staging. */
|
|
25
|
+
suspend?(releaseId: string): Promise<void>;
|
|
16
26
|
write(file: AbsoluteMobileUpdateFile, contents: Uint8Array): Promise<void>;
|
|
17
27
|
};
|
|
18
28
|
export type AbsoluteMobileUpdateVerifier = {
|
|
@@ -21,7 +31,10 @@ export type AbsoluteMobileUpdateVerifier = {
|
|
|
21
31
|
};
|
|
22
32
|
export type AbsoluteMobileUpdateClientOptions = {
|
|
23
33
|
config: AbsoluteMobileUpdateClientConfig;
|
|
34
|
+
/** Maximum parallel asset requests. Network conditions may reduce this. */
|
|
35
|
+
concurrency?: number;
|
|
24
36
|
fetch?: typeof globalThis.fetch;
|
|
37
|
+
onProgress?: (progress: AbsoluteMobileUpdateProgress) => void;
|
|
25
38
|
store: AbsoluteMobileUpdateStore;
|
|
26
39
|
verifier: AbsoluteMobileUpdateVerifier;
|
|
27
40
|
};
|
|
@@ -30,6 +43,7 @@ export type AbsoluteMobileUpdateCheckResult = {
|
|
|
30
43
|
} | {
|
|
31
44
|
kind: 'downloaded';
|
|
32
45
|
manifest: AbsoluteMobileUpdateManifest;
|
|
46
|
+
transfer: AbsoluteMobileUpdateTransfer;
|
|
33
47
|
} | {
|
|
34
48
|
kind: 'quarantined';
|
|
35
49
|
releaseId: string;
|
|
@@ -37,6 +51,23 @@ export type AbsoluteMobileUpdateCheckResult = {
|
|
|
37
51
|
kind: 'update-available';
|
|
38
52
|
manifest: AbsoluteMobileUpdateManifest;
|
|
39
53
|
};
|
|
54
|
+
export type AbsoluteMobileUpdateTransfer = {
|
|
55
|
+
avoidedBytes: number;
|
|
56
|
+
completedFiles: number;
|
|
57
|
+
downloadedBytes: number;
|
|
58
|
+
downloadedFiles: number;
|
|
59
|
+
durationMs: number;
|
|
60
|
+
resumedBytes: number;
|
|
61
|
+
resumedFiles: number;
|
|
62
|
+
reusedBytes: number;
|
|
63
|
+
reusedFiles: number;
|
|
64
|
+
throughputBytesPerSecond: number;
|
|
65
|
+
totalBytes: number;
|
|
66
|
+
totalFiles: number;
|
|
67
|
+
};
|
|
68
|
+
export type AbsoluteMobileUpdateProgress = AbsoluteMobileUpdateTransfer & {
|
|
69
|
+
kind: 'download-progress';
|
|
70
|
+
};
|
|
40
71
|
export declare const createAbsoluteMobileUpdateClient: (options: AbsoluteMobileUpdateClientOptions) => {
|
|
41
72
|
check: (download?: boolean) => Promise<AbsoluteMobileUpdateCheckResult>;
|
|
42
73
|
activate: (releaseId: string) => Promise<void>;
|
|
@@ -3,8 +3,12 @@ import { readAbsoluteMobileUpdate } from './updateSigning';
|
|
|
3
3
|
export type AbsoluteMobileUpdatePublication = {
|
|
4
4
|
appId: string;
|
|
5
5
|
channel: string;
|
|
6
|
+
storedBytes?: number;
|
|
7
|
+
storedFiles?: number;
|
|
6
8
|
releaseId: string;
|
|
7
9
|
reused: boolean;
|
|
10
|
+
reusedBytes?: number;
|
|
11
|
+
reusedFiles?: number;
|
|
8
12
|
rollout: number;
|
|
9
13
|
stage: 'published';
|
|
10
14
|
};
|
package/package.json
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
"dependencies": {
|
|
10
10
|
"@absolutejs/auth": ">=0.75.0 <0.77.0",
|
|
11
11
|
"@absolutejs/beacon": ">=0.7.0-beta.4 <0.8.0",
|
|
12
|
-
"@absolutejs/deploy": "0.25.
|
|
12
|
+
"@absolutejs/deploy": "0.25.9",
|
|
13
13
|
"@absolutejs/devices": "0.7.0",
|
|
14
14
|
"@absolutejs/devices-capacitor": "0.8.0",
|
|
15
15
|
"@absolutejs/devices-expo": "0.0.2",
|
|
@@ -523,7 +523,7 @@
|
|
|
523
523
|
]
|
|
524
524
|
}
|
|
525
525
|
},
|
|
526
|
-
"version": "0.20.0-beta.
|
|
526
|
+
"version": "0.20.0-beta.85",
|
|
527
527
|
"workspaces": [
|
|
528
528
|
"tests/fixtures/*",
|
|
529
529
|
"tests/fixtures/_packages/*"
|