@bravemobile/react-native-code-push 13.1.0-beta.4 → 13.1.0-beta.6
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/CodePush.podspec +5 -0
- package/README.md +15 -145
- package/android/app/src/main/java/com/microsoft/codepush/react/ArchiveAttemptLog.java +181 -0
- package/android/app/src/main/java/com/microsoft/codepush/react/{BinaryPatchResult.java → ArchiveRestoreResult.java} +17 -7
- package/android/app/src/main/java/com/microsoft/codepush/react/CodePushBinaryPatch.java +20 -20
- package/android/app/src/main/java/com/microsoft/codepush/react/CodePushConstants.java +20 -1
- package/android/app/src/main/java/com/microsoft/codepush/react/CodePushErrorCode.java +86 -0
- package/android/app/src/main/java/com/microsoft/codepush/react/CodePushHttpException.java +29 -0
- package/android/app/src/main/java/com/microsoft/codepush/react/CodePushIncompleteDownloadException.java +17 -0
- package/android/app/src/main/java/com/microsoft/codepush/react/CodePushNativeModule.java +12 -7
- package/android/app/src/main/java/com/microsoft/codepush/react/CodePushUpdateManager.java +143 -43
- package/android/app/src/test/java/com/microsoft/codepush/react/CodePushBinaryPatchTest.java +31 -31
- package/android/app/src/test/java/com/microsoft/codepush/react/CodePushErrorCodeTest.java +90 -0
- package/android/app/src/test/java/com/microsoft/codepush/react/CodePushUpdateManagerBinaryPatchTest.java +159 -19
- package/android/app/src/test/java/com/microsoft/codepush/react/CodePushUpdateManagerDownloadTest.java +304 -15
- package/cli/commands/buildPatchToolsCommand/buildPatchTools.test.ts +118 -0
- package/cli/commands/buildPatchToolsCommand/buildPatchTools.ts +46 -0
- package/cli/commands/buildPatchToolsCommand/index.ts +44 -0
- package/cli/commands/createHistoryCommand/createReleaseHistory.test.ts +133 -0
- package/cli/commands/createHistoryCommand/createReleaseHistory.ts +3 -11
- package/cli/commands/releaseCommand/addToReleaseHistory.ts +3 -11
- package/cli/commands/releaseCommand/release.test.ts +3 -3
- package/cli/commands/updateHistoryCommand/updateReleaseHistory.ts +3 -11
- package/cli/dist/commands/buildPatchToolsCommand/buildPatchTools.js +34 -0
- package/cli/dist/commands/buildPatchToolsCommand/buildPatchTools.test.js +88 -0
- package/cli/dist/commands/buildPatchToolsCommand/index.js +32 -0
- package/cli/dist/commands/createHistoryCommand/createReleaseHistory.js +2 -8
- package/cli/dist/commands/createHistoryCommand/createReleaseHistory.test.js +95 -0
- package/cli/dist/commands/releaseCommand/addToReleaseHistory.js +2 -8
- package/cli/dist/commands/releaseCommand/release.test.js +3 -3
- package/cli/dist/commands/updateHistoryCommand/updateReleaseHistory.js +2 -8
- package/cli/dist/functions/stageReleaseHistoryFile.js +28 -0
- package/cli/dist/index.js +4 -0
- package/cli/dist/utils/binaryPatch.js +8 -5
- package/cli/dist/utils/binaryPatch.test.js +1 -1
- package/cli/functions/stageReleaseHistoryFile.ts +38 -0
- package/cli/index.ts +5 -0
- package/cli/utils/binaryPatch.test.ts +1 -1
- package/cli/utils/binaryPatch.ts +8 -5
- package/ios/CodePush/CodePush.h +6 -3
- package/ios/CodePush/CodePush.mm +10 -10
- package/ios/CodePush/CodePushBinaryPatch.h +18 -8
- package/ios/CodePush/CodePushBinaryPatch.m +23 -22
- package/ios/CodePush/CodePushDownloadHandler.m +2 -1
- package/ios/CodePush/CodePushErrorUtils.m +55 -0
- package/ios/CodePush/CodePushPackage.m +242 -71
- package/package.json +2 -1
- package/src/CodePush.js +28 -18
- package/src/CodePush.test.js +110 -55
- package/src/package-mixins.js +19 -10
- package/typings/react-native-code-push.d.ts +110 -29
- package/android/app/src/main/java/com/microsoft/codepush/react/BinaryPatchAttempt.java +0 -92
package/src/CodePush.test.js
CHANGED
|
@@ -60,16 +60,16 @@ function diffRelease() {
|
|
|
60
60
|
}
|
|
61
61
|
|
|
62
62
|
/**
|
|
63
|
-
* @param
|
|
64
|
-
* only a download that had
|
|
63
|
+
* @param updateArchiveResult what the native side reports the archive attempts ended in,
|
|
64
|
+
* which only a download that had an archive to try comes back with.
|
|
65
65
|
* @param installedPackage the CodePush update the app is running, if any.
|
|
66
66
|
*/
|
|
67
|
-
function createNativeBridge({
|
|
67
|
+
function createNativeBridge({ updateArchiveResult, installedPackage } = {}) {
|
|
68
68
|
return {
|
|
69
69
|
addDownloadProgressListener: jest.fn(() => ({ remove: jest.fn() })),
|
|
70
70
|
downloadUpdate: jest.fn(async (updatePackage) => ({
|
|
71
71
|
...updatePackage,
|
|
72
|
-
...(
|
|
72
|
+
...(updateArchiveResult ? { updateArchiveResult } : {}),
|
|
73
73
|
})),
|
|
74
74
|
// Without an installed CodePush update, the app runs the bundle of its binary.
|
|
75
75
|
getUpdateMetadata: jest.fn(async () => installedPackage ?? null),
|
|
@@ -89,13 +89,13 @@ function createNativeBridge({ binaryPatchResult, installedPackage } = {}) {
|
|
|
89
89
|
* native bridge live on the module itself - configured the way an app configures it: the
|
|
90
90
|
* decorator registers the app-wide options, and every sync below is one the app asks for.
|
|
91
91
|
*
|
|
92
|
-
* @param
|
|
92
|
+
* @param onUpdateArchiveResult a callback the app registers on the decorator, as opposed to
|
|
93
93
|
* one it passes to a single `sync()` call.
|
|
94
94
|
*/
|
|
95
|
-
function loadCodePush({ releaseHistory = {}, updateChecker,
|
|
95
|
+
function loadCodePush({ releaseHistory = {}, updateChecker, updateArchiveResult, onUpdateArchiveResult, installedPackage } = {}) {
|
|
96
96
|
jest.resetModules();
|
|
97
97
|
const CodePush = require('./CodePush');
|
|
98
|
-
const nativeBridge = createNativeBridge({
|
|
98
|
+
const nativeBridge = createNativeBridge({ updateArchiveResult, installedPackage });
|
|
99
99
|
|
|
100
100
|
CodePush.setUpTestDependencies(
|
|
101
101
|
null,
|
|
@@ -106,7 +106,7 @@ function loadCodePush({ releaseHistory = {}, updateChecker, binaryPatchResult, o
|
|
|
106
106
|
checkFrequency: CodePush.CheckFrequency.MANUAL,
|
|
107
107
|
releaseHistoryFetcher: async () => releaseHistory,
|
|
108
108
|
updateChecker,
|
|
109
|
-
|
|
109
|
+
onUpdateArchiveResult,
|
|
110
110
|
});
|
|
111
111
|
|
|
112
112
|
return { CodePush, nativeBridge };
|
|
@@ -211,11 +211,13 @@ describe('checkForUpdate without a binary patch in the release history', () => {
|
|
|
211
211
|
});
|
|
212
212
|
|
|
213
213
|
/**
|
|
214
|
-
* A diff archive only applies to the one release it was computed against, so
|
|
215
|
-
*
|
|
214
|
+
* A diff archive only applies to the one release it was computed against, so whether the
|
|
215
|
+
* diff url is handed to the native side at all depends on what the app is running right
|
|
216
|
+
* now. The patch url is always handed over with it, because the diff falling back to the
|
|
217
|
+
* patch archive is the native side's decision to make.
|
|
216
218
|
*/
|
|
217
219
|
describe('checkForUpdate with asset diff packages in the release history', () => {
|
|
218
|
-
it('
|
|
220
|
+
it('carries the diff url next to the patch url when the installed update matches a diff base', async () => {
|
|
219
221
|
const { CodePush, nativeBridge } = loadCodePush({
|
|
220
222
|
releaseHistory: diffRelease(),
|
|
221
223
|
installedPackage: { packageHash: INSTALLED_HASH },
|
|
@@ -226,11 +228,12 @@ describe('checkForUpdate with asset diff packages in the release history', () =>
|
|
|
226
228
|
|
|
227
229
|
expect(downloadedPackageMetadata(nativeBridge)).toMatchObject({
|
|
228
230
|
downloadUrl: DOWNLOAD_URL,
|
|
229
|
-
binaryPatchDownloadUrl:
|
|
231
|
+
binaryPatchDownloadUrl: BINARY_PATCH_DOWNLOAD_URL,
|
|
232
|
+
assetDiffDownloadUrl: DIFF_URL,
|
|
230
233
|
});
|
|
231
234
|
});
|
|
232
235
|
|
|
233
|
-
it('
|
|
236
|
+
it('leaves the diff url out when the installed update matches no diff base', async () => {
|
|
234
237
|
const { CodePush, nativeBridge } = loadCodePush({
|
|
235
238
|
releaseHistory: diffRelease(),
|
|
236
239
|
installedPackage: { packageHash: 'other-hash' },
|
|
@@ -239,20 +242,20 @@ describe('checkForUpdate with asset diff packages in the release history', () =>
|
|
|
239
242
|
const remotePackage = await CodePush.checkForUpdate();
|
|
240
243
|
await remotePackage.download();
|
|
241
244
|
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
+
const metadata = downloadedPackageMetadata(nativeBridge);
|
|
246
|
+
expect(metadata).toMatchObject({ binaryPatchDownloadUrl: BINARY_PATCH_DOWNLOAD_URL });
|
|
247
|
+
expect(metadata).not.toHaveProperty('assetDiffDownloadUrl');
|
|
245
248
|
});
|
|
246
249
|
|
|
247
|
-
it('
|
|
250
|
+
it('leaves the diff url out when no update is installed', async () => {
|
|
248
251
|
const { CodePush, nativeBridge } = loadCodePush({ releaseHistory: diffRelease() });
|
|
249
252
|
|
|
250
253
|
const remotePackage = await CodePush.checkForUpdate();
|
|
251
254
|
await remotePackage.download();
|
|
252
255
|
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
+
const metadata = downloadedPackageMetadata(nativeBridge);
|
|
257
|
+
expect(metadata).toMatchObject({ binaryPatchDownloadUrl: BINARY_PATCH_DOWNLOAD_URL });
|
|
258
|
+
expect(metadata).not.toHaveProperty('assetDiffDownloadUrl');
|
|
256
259
|
});
|
|
257
260
|
|
|
258
261
|
it('does not send the diff package map across the bridge', async () => {
|
|
@@ -310,13 +313,33 @@ describe('checkForUpdate through the deprecated updateChecker', () => {
|
|
|
310
313
|
});
|
|
311
314
|
|
|
312
315
|
/**
|
|
313
|
-
* How
|
|
316
|
+
* How the archive attempts went is reported to the app that asked to hear about it, and to
|
|
314
317
|
* nobody else: it is not part of the update, and an app that asked for nothing gets the
|
|
315
318
|
* install it always got.
|
|
316
319
|
*/
|
|
317
|
-
describe('the
|
|
318
|
-
const APPLIED = {
|
|
319
|
-
|
|
320
|
+
describe('the update archive result of a sync', () => {
|
|
321
|
+
const APPLIED = {
|
|
322
|
+
status: 'applied',
|
|
323
|
+
archive: 'binary-patch',
|
|
324
|
+
totalDurationMs: 812,
|
|
325
|
+
attempts: [{ archive: 'binary-patch', durationMs: 812, applyDurationMs: 64 }],
|
|
326
|
+
};
|
|
327
|
+
const FELL_BACK = {
|
|
328
|
+
status: 'fallback',
|
|
329
|
+
archive: 'binary-patch',
|
|
330
|
+
fallbackReason: 'base_hash_mismatch',
|
|
331
|
+
totalDurationMs: 1503,
|
|
332
|
+
attempts: [{ archive: 'binary-patch', fallbackReason: 'base_hash_mismatch', durationMs: 1503 }],
|
|
333
|
+
};
|
|
334
|
+
const APPLIED_AFTER_DIFF_FELL_BACK = {
|
|
335
|
+
status: 'applied',
|
|
336
|
+
archive: 'binary-patch',
|
|
337
|
+
totalDurationMs: 1503,
|
|
338
|
+
attempts: [
|
|
339
|
+
{ archive: 'asset-diff', fallbackReason: 'asset_merge_failed', durationMs: 690, applyDurationMs: 41 },
|
|
340
|
+
{ archive: 'binary-patch', durationMs: 813, applyDurationMs: 58 },
|
|
341
|
+
],
|
|
342
|
+
};
|
|
320
343
|
|
|
321
344
|
/** The metadata the update is installed from, as the native module is given it. */
|
|
322
345
|
function installedPackageMetadata(nativeBridge) {
|
|
@@ -327,46 +350,62 @@ describe('the binary patch result of a sync', () => {
|
|
|
327
350
|
it('tells the app the update was installed from its patch, and how long that took', async () => {
|
|
328
351
|
const { CodePush, nativeBridge } = loadCodePush({
|
|
329
352
|
releaseHistory: patchedRelease(),
|
|
330
|
-
|
|
353
|
+
updateArchiveResult: APPLIED,
|
|
331
354
|
});
|
|
332
|
-
const
|
|
355
|
+
const onUpdateArchiveResult = jest.fn();
|
|
333
356
|
|
|
334
|
-
const syncStatus = await CodePush.sync({
|
|
357
|
+
const syncStatus = await CodePush.sync({ onUpdateArchiveResult });
|
|
335
358
|
|
|
336
359
|
expect(syncStatus).toBe(CodePush.SyncStatus.UPDATE_INSTALLED);
|
|
337
|
-
expect(
|
|
338
|
-
expect(
|
|
360
|
+
expect(onUpdateArchiveResult).toHaveBeenCalledTimes(1);
|
|
361
|
+
expect(onUpdateArchiveResult).toHaveBeenCalledWith(LABEL, APPLIED);
|
|
339
362
|
expect(nativeBridge.installUpdate).toHaveBeenCalledTimes(1);
|
|
340
363
|
});
|
|
341
364
|
|
|
342
365
|
it('tells the app why the update came from the full archive instead', async () => {
|
|
343
366
|
const { CodePush } = loadCodePush({
|
|
344
367
|
releaseHistory: patchedRelease(),
|
|
345
|
-
|
|
368
|
+
updateArchiveResult: FELL_BACK,
|
|
346
369
|
});
|
|
347
|
-
const
|
|
370
|
+
const onUpdateArchiveResult = jest.fn();
|
|
348
371
|
|
|
349
372
|
// A patch that could not be applied is not a failed update: the full archive installs.
|
|
350
|
-
const syncStatus = await CodePush.sync({
|
|
373
|
+
const syncStatus = await CodePush.sync({ onUpdateArchiveResult });
|
|
351
374
|
|
|
352
375
|
expect(syncStatus).toBe(CodePush.SyncStatus.UPDATE_INSTALLED);
|
|
353
|
-
expect(
|
|
376
|
+
expect(onUpdateArchiveResult).toHaveBeenCalledWith(LABEL, FELL_BACK);
|
|
377
|
+
});
|
|
378
|
+
|
|
379
|
+
it("the app is handed each archive's apply time and the whole path's total", async () => {
|
|
380
|
+
const { CodePush } = loadCodePush({
|
|
381
|
+
releaseHistory: diffRelease(),
|
|
382
|
+
installedPackage: { packageHash: INSTALLED_HASH },
|
|
383
|
+
updateArchiveResult: APPLIED_AFTER_DIFF_FELL_BACK,
|
|
384
|
+
});
|
|
385
|
+
const onUpdateArchiveResult = jest.fn();
|
|
386
|
+
|
|
387
|
+
await CodePush.sync({ onUpdateArchiveResult });
|
|
388
|
+
|
|
389
|
+
expect(onUpdateArchiveResult).toHaveBeenCalledWith(LABEL, APPLIED_AFTER_DIFF_FELL_BACK);
|
|
390
|
+
const [, result] = onUpdateArchiveResult.mock.calls[0];
|
|
391
|
+
expect(result.totalDurationMs).toBe(1503);
|
|
392
|
+
expect(result.attempts.map((attempt) => attempt.applyDurationMs)).toEqual([41, 58]);
|
|
354
393
|
});
|
|
355
394
|
|
|
356
395
|
it('tells the app that registered the callback on the decorator, even about a sync it asked for itself', async () => {
|
|
357
|
-
const
|
|
396
|
+
const onUpdateArchiveResult = jest.fn();
|
|
358
397
|
const { CodePush } = loadCodePush({
|
|
359
398
|
releaseHistory: patchedRelease(),
|
|
360
|
-
|
|
361
|
-
|
|
399
|
+
updateArchiveResult: APPLIED,
|
|
400
|
+
onUpdateArchiveResult,
|
|
362
401
|
});
|
|
363
402
|
|
|
364
403
|
// The decorator checks nothing on its own, so this sync is the app's own call.
|
|
365
404
|
const syncStatus = await CodePush.sync();
|
|
366
405
|
|
|
367
406
|
expect(syncStatus).toBe(CodePush.SyncStatus.UPDATE_INSTALLED);
|
|
368
|
-
expect(
|
|
369
|
-
expect(
|
|
407
|
+
expect(onUpdateArchiveResult).toHaveBeenCalledTimes(1);
|
|
408
|
+
expect(onUpdateArchiveResult).toHaveBeenCalledWith(LABEL, APPLIED);
|
|
370
409
|
});
|
|
371
410
|
|
|
372
411
|
it('tells only the callback of the sync call when one was passed to it', async () => {
|
|
@@ -374,11 +413,11 @@ describe('the binary patch result of a sync', () => {
|
|
|
374
413
|
const passedToTheSyncCall = jest.fn();
|
|
375
414
|
const { CodePush } = loadCodePush({
|
|
376
415
|
releaseHistory: patchedRelease(),
|
|
377
|
-
|
|
378
|
-
|
|
416
|
+
updateArchiveResult: APPLIED,
|
|
417
|
+
onUpdateArchiveResult: registeredOnTheDecorator,
|
|
379
418
|
});
|
|
380
419
|
|
|
381
|
-
const syncStatus = await CodePush.sync({
|
|
420
|
+
const syncStatus = await CodePush.sync({ onUpdateArchiveResult: passedToTheSyncCall });
|
|
382
421
|
|
|
383
422
|
expect(syncStatus).toBe(CodePush.SyncStatus.UPDATE_INSTALLED);
|
|
384
423
|
expect(passedToTheSyncCall).toHaveBeenCalledTimes(1);
|
|
@@ -388,65 +427,81 @@ describe('the binary patch result of a sync', () => {
|
|
|
388
427
|
|
|
389
428
|
it('says nothing about a download that had no patch to try', async () => {
|
|
390
429
|
const { CodePush } = loadCodePush({ releaseHistory: fullOnlyRelease() });
|
|
391
|
-
const
|
|
430
|
+
const onUpdateArchiveResult = jest.fn();
|
|
392
431
|
|
|
393
|
-
const syncStatus = await CodePush.sync({
|
|
432
|
+
const syncStatus = await CodePush.sync({ onUpdateArchiveResult });
|
|
394
433
|
|
|
395
434
|
expect(syncStatus).toBe(CodePush.SyncStatus.UPDATE_INSTALLED);
|
|
396
|
-
expect(
|
|
435
|
+
expect(onUpdateArchiveResult).not.toHaveBeenCalled();
|
|
397
436
|
});
|
|
398
437
|
|
|
399
438
|
it('installs the update even when the callback throws', async () => {
|
|
400
439
|
const { CodePush, nativeBridge } = loadCodePush({
|
|
401
440
|
releaseHistory: patchedRelease(),
|
|
402
|
-
|
|
441
|
+
updateArchiveResult: APPLIED,
|
|
403
442
|
});
|
|
404
|
-
const
|
|
443
|
+
const onUpdateArchiveResult = jest.fn(() => {
|
|
405
444
|
throw new Error('the telemetry the app sends the result to is down');
|
|
406
445
|
});
|
|
407
446
|
|
|
408
|
-
const syncStatus = await CodePush.sync({
|
|
447
|
+
const syncStatus = await CodePush.sync({ onUpdateArchiveResult });
|
|
409
448
|
|
|
410
449
|
expect(syncStatus).toBe(CodePush.SyncStatus.UPDATE_INSTALLED);
|
|
411
|
-
expect(
|
|
450
|
+
expect(onUpdateArchiveResult).toHaveBeenCalledTimes(1);
|
|
412
451
|
expect(nativeBridge.installUpdate).toHaveBeenCalledTimes(1);
|
|
413
452
|
});
|
|
414
453
|
|
|
415
454
|
it('keeps the result out of the package the update is installed from', async () => {
|
|
416
455
|
const { CodePush, nativeBridge } = loadCodePush({
|
|
417
456
|
releaseHistory: patchedRelease(),
|
|
418
|
-
|
|
457
|
+
updateArchiveResult: APPLIED,
|
|
419
458
|
});
|
|
420
459
|
|
|
421
|
-
await CodePush.sync({
|
|
460
|
+
await CodePush.sync({ onUpdateArchiveResult: jest.fn() });
|
|
422
461
|
|
|
423
462
|
const installedPackage = installedPackageMetadata(nativeBridge);
|
|
424
463
|
expect(installedPackage).toMatchObject({ label: LABEL, packageHash: PACKAGE_HASH });
|
|
425
|
-
expect(installedPackage).not.toHaveProperty('
|
|
464
|
+
expect(installedPackage).not.toHaveProperty('updateArchiveResult');
|
|
426
465
|
});
|
|
427
466
|
|
|
428
467
|
it('installs the same update, without the result, when no callback is registered', async () => {
|
|
429
468
|
const { CodePush, nativeBridge } = loadCodePush({
|
|
430
469
|
releaseHistory: patchedRelease(),
|
|
431
|
-
|
|
470
|
+
updateArchiveResult: FELL_BACK,
|
|
432
471
|
});
|
|
433
472
|
|
|
434
473
|
const syncStatus = await CodePush.sync();
|
|
435
474
|
|
|
436
475
|
expect(syncStatus).toBe(CodePush.SyncStatus.UPDATE_INSTALLED);
|
|
437
|
-
expect(installedPackageMetadata(nativeBridge)).not.toHaveProperty('
|
|
476
|
+
expect(installedPackageMetadata(nativeBridge)).not.toHaveProperty('updateArchiveResult');
|
|
477
|
+
});
|
|
478
|
+
|
|
479
|
+
it('resolves a direct download even when the callback passed to it throws', async () => {
|
|
480
|
+
const { CodePush } = loadCodePush({
|
|
481
|
+
releaseHistory: patchedRelease(),
|
|
482
|
+
updateArchiveResult: APPLIED,
|
|
483
|
+
});
|
|
484
|
+
const onUpdateArchiveResult = jest.fn(() => {
|
|
485
|
+
throw new Error('the telemetry the app sends the result to is down');
|
|
486
|
+
});
|
|
487
|
+
|
|
488
|
+
const remotePackage = await CodePush.checkForUpdate();
|
|
489
|
+
const localPackage = await remotePackage.download(undefined, onUpdateArchiveResult);
|
|
490
|
+
|
|
491
|
+
expect(onUpdateArchiveResult).toHaveBeenCalledTimes(1);
|
|
492
|
+
expect(localPackage).toMatchObject({ label: LABEL, packageHash: PACKAGE_HASH });
|
|
438
493
|
});
|
|
439
494
|
|
|
440
495
|
it('leaves the result off the package a download resolves with', async () => {
|
|
441
496
|
const { CodePush } = loadCodePush({
|
|
442
497
|
releaseHistory: patchedRelease(),
|
|
443
|
-
|
|
498
|
+
updateArchiveResult: APPLIED,
|
|
444
499
|
});
|
|
445
500
|
|
|
446
501
|
const remotePackage = await CodePush.checkForUpdate();
|
|
447
502
|
const localPackage = await remotePackage.download();
|
|
448
503
|
|
|
449
504
|
expect(localPackage).toMatchObject({ label: LABEL, packageHash: PACKAGE_HASH });
|
|
450
|
-
expect(localPackage).not.toHaveProperty('
|
|
505
|
+
expect(localPackage).not.toHaveProperty('updateArchiveResult');
|
|
451
506
|
});
|
|
452
507
|
});
|
package/src/package-mixins.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
const log = require("./logging");
|
|
2
|
+
|
|
1
3
|
// This function is used to augment remote and local
|
|
2
4
|
// package objects with additional functionality/properties
|
|
3
5
|
// beyond what is included in the metadata sent by the server.
|
|
@@ -6,13 +8,14 @@ module.exports = (NativeCodePush) => {
|
|
|
6
8
|
return {
|
|
7
9
|
/**
|
|
8
10
|
* @param downloadProgressCallback Called as the archive is received.
|
|
9
|
-
* @param
|
|
10
|
-
* when the update was published with a binary patch,
|
|
11
|
-
* how the
|
|
12
|
-
*
|
|
13
|
-
* in full - and it is the only place the result is
|
|
11
|
+
* @param updateArchiveResultCallback Called with `{ status, archive, fallbackReason?,
|
|
12
|
+
* totalDurationMs, attempts }` when the update was published with a binary patch,
|
|
13
|
+
* so that the app can observe how the archives went. It says nothing about whether
|
|
14
|
+
* the download succeeded - archives that could not be applied are reported here
|
|
15
|
+
* and the update is downloaded in full - and it is the only place the result is
|
|
16
|
+
* ever available.
|
|
14
17
|
*/
|
|
15
|
-
async download(downloadProgressCallback,
|
|
18
|
+
async download(downloadProgressCallback, updateArchiveResultCallback) {
|
|
16
19
|
if (!this.downloadUrl) {
|
|
17
20
|
throw new Error("Cannot download an update without a download url");
|
|
18
21
|
}
|
|
@@ -33,14 +36,20 @@ module.exports = (NativeCodePush) => {
|
|
|
33
36
|
|
|
34
37
|
const downloadResult = await NativeCodePush.downloadUpdate(updatePackageCopy, !!downloadProgressCallback);
|
|
35
38
|
|
|
36
|
-
// The
|
|
39
|
+
// The archive result describes the download that just happened, not the update it
|
|
37
40
|
// delivered, and the package is handed around as the update's metadata from here
|
|
38
41
|
// on - it is even written back to the native side on install. So the result is
|
|
39
42
|
// taken off the package and reported on its own, leaving the package exactly what
|
|
40
43
|
// the native side saved.
|
|
41
|
-
const {
|
|
42
|
-
if (
|
|
43
|
-
|
|
44
|
+
const { updateArchiveResult, ...downloadedPackage } = downloadResult ?? {};
|
|
45
|
+
if (updateArchiveResult && updateArchiveResultCallback) {
|
|
46
|
+
// The result is an observation, so an observer that throws must not turn a
|
|
47
|
+
// downloaded update into a failed one.
|
|
48
|
+
try {
|
|
49
|
+
updateArchiveResultCallback(updateArchiveResult);
|
|
50
|
+
} catch (error) {
|
|
51
|
+
log(`The update archive result callback threw: ${error?.message ?? error}`);
|
|
52
|
+
}
|
|
44
53
|
}
|
|
45
54
|
|
|
46
55
|
return { ...downloadedPackage, ...local };
|
|
@@ -36,9 +36,11 @@ export interface ReleaseInfo {
|
|
|
36
36
|
binaryPatchDownloadUrl?: string;
|
|
37
37
|
/**
|
|
38
38
|
* URLs of patch archives that also carry an asset diff, keyed by the packageHash of the
|
|
39
|
-
* release each archive was diffed against.
|
|
40
|
-
*
|
|
41
|
-
* ignores this field. Only present when the release was published with
|
|
39
|
+
* release each archive was diffed against. Give a key to every release worth diffing
|
|
40
|
+
* against: a client whose installed update matches one downloads that archive first, and
|
|
41
|
+
* every other client ignores this field. Only present when the release was published with
|
|
42
|
+
* asset diff archives. `UpdateArchiveResult` describes what a client does with one it
|
|
43
|
+
* cannot use.
|
|
42
44
|
*/
|
|
43
45
|
diffPackages?: Record<string, string>;
|
|
44
46
|
packageHash: string;
|
|
@@ -54,6 +56,13 @@ export interface UpdateCheckResponse {
|
|
|
54
56
|
* that cannot use it downloads the full update from `download_url` instead.
|
|
55
57
|
*/
|
|
56
58
|
binary_patch_download_url?: string;
|
|
59
|
+
/**
|
|
60
|
+
* The URL of the asset diff archive built against the update the client is running.
|
|
61
|
+
* It is only present when the release publishes a diff against exactly that update, and
|
|
62
|
+
* it accompanies `binary_patch_download_url` rather than replacing it: a response can
|
|
63
|
+
* carry both. `UpdateArchiveResult` describes the order the client tries them in.
|
|
64
|
+
*/
|
|
65
|
+
asset_diff_download_url?: string;
|
|
57
66
|
description?: string;
|
|
58
67
|
is_available: boolean;
|
|
59
68
|
is_disabled?: boolean;
|
|
@@ -71,7 +80,7 @@ export interface UpdateCheckResponse {
|
|
|
71
80
|
* instead. These are the words every platform's applier reports, so a rollout can be
|
|
72
81
|
* judged by them whichever platform it is running on.
|
|
73
82
|
*/
|
|
74
|
-
export type
|
|
83
|
+
export type ArchiveFallbackReason =
|
|
75
84
|
/** The bundle inside the app binary could not be opened or read. */
|
|
76
85
|
| "base_bundle_unavailable"
|
|
77
86
|
/** The bundle inside the app binary is not the one the patch was computed against. */
|
|
@@ -84,30 +93,88 @@ export type BinaryPatchFallbackReason =
|
|
|
84
93
|
| "patch_apply_failed"
|
|
85
94
|
/** The restored bundle is not the one the manifest promised. */
|
|
86
95
|
| "target_verification_failed"
|
|
96
|
+
/** The asset diff could not be merged with the installed update it was built against. */
|
|
97
|
+
| "asset_merge_failed"
|
|
87
98
|
/** The update restored from the patch did not pass the checks that follow the restore. */
|
|
88
99
|
| "package_verification_failed";
|
|
89
100
|
|
|
90
101
|
/**
|
|
91
|
-
*
|
|
102
|
+
* The archives on the patch path; the full archive is not one of them. There are two: the
|
|
103
|
+
* patch archive built against the app binary's bundle, and an asset diff archive that
|
|
104
|
+
* additionally leaves out the assets an installed base update already holds.
|
|
105
|
+
*/
|
|
106
|
+
export type UpdateArchive = "binary-patch" | "asset-diff";
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* One archive tried on the patch path: which archive it was, how long the try took, and,
|
|
110
|
+
* when it was given up on, why.
|
|
111
|
+
*/
|
|
112
|
+
export interface UpdateArchiveAttempt {
|
|
113
|
+
archive: UpdateArchive;
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Why this archive was given up on. Absent for the attempt the downloaded update came
|
|
117
|
+
* from, and also when the attempt ended in an error none of the appliers has a word
|
|
118
|
+
* for.
|
|
119
|
+
*/
|
|
120
|
+
fallbackReason?: ArchiveFallbackReason;
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* How long this attempt ran, in milliseconds, whichever way it ended: from the archive
|
|
124
|
+
* starting to download to the try being finished with.
|
|
125
|
+
*/
|
|
126
|
+
durationMs: number;
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* How long the applier took to rebuild the bundle from this archive's patch, in
|
|
130
|
+
* milliseconds. Absent when the attempt ended before the bundle was restored.
|
|
131
|
+
*/
|
|
132
|
+
applyDurationMs?: number;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Which of an update's patch archives the download came from, and what the patch path did
|
|
137
|
+
* on the way there. It is reported once the update has been downloaded and before the
|
|
138
|
+
* `LocalPackage` it resolved to is installed, so nothing here speaks to how installing goes.
|
|
92
139
|
*/
|
|
93
|
-
export interface
|
|
140
|
+
export interface UpdateArchiveResult {
|
|
94
141
|
/**
|
|
95
|
-
* Whether the update
|
|
96
|
-
*
|
|
142
|
+
* Whether one of the update's patch archives produced it, or the full archive had to be
|
|
143
|
+
* downloaded instead. A fallback is not an error: the update arrives either way.
|
|
97
144
|
*/
|
|
98
145
|
status: "applied" | "fallback";
|
|
99
146
|
|
|
100
147
|
/**
|
|
101
|
-
*
|
|
102
|
-
*
|
|
148
|
+
* The archive of the last attempt: the one the downloaded update came from when a patch
|
|
149
|
+
* archive produced it, and the last one given up on when none did.
|
|
150
|
+
*/
|
|
151
|
+
archive: UpdateArchive;
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Why the full archive had to be downloaded: the reason the last attempt ended in.
|
|
155
|
+
* Absent when a patch archive produced the update, and also when the attempt ended in
|
|
156
|
+
* an error none of the appliers has a word for.
|
|
103
157
|
*/
|
|
104
|
-
fallbackReason?:
|
|
158
|
+
fallbackReason?: ArchiveFallbackReason;
|
|
105
159
|
|
|
106
160
|
/**
|
|
107
|
-
* How long the patch
|
|
108
|
-
*
|
|
161
|
+
* How long the whole patch path took, in milliseconds: from the first archive starting
|
|
162
|
+
* to download to the last attempt being finished with. The full download that follows a
|
|
163
|
+
* fallback is not part of it, because that is not time the patch path spent.
|
|
109
164
|
*/
|
|
110
|
-
|
|
165
|
+
totalDurationMs: number;
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Every archive that was tried, in the order it was tried. The full archive is never
|
|
169
|
+
* among them, because it is downloaded only once the patch path has given up.
|
|
170
|
+
*
|
|
171
|
+
* Most downloads leave a single entry. A second one appears when the asset diff failed
|
|
172
|
+
* after its bundle was restored - an asset-side failure the patch archive is not
|
|
173
|
+
* implicated in - and the patch archive was tried in its place. A diff that fails before
|
|
174
|
+
* its bundle is restored skips the patch archive instead, because both archives carry the
|
|
175
|
+
* same bundle patch and it would fail the same way.
|
|
176
|
+
*/
|
|
177
|
+
attempts: UpdateArchiveAttempt[];
|
|
111
178
|
}
|
|
112
179
|
|
|
113
180
|
export interface CodePushOptions extends SyncOptions {
|
|
@@ -250,8 +317,12 @@ export interface RemotePackage extends Package {
|
|
|
250
317
|
* Downloads the available update from the CodePush service.
|
|
251
318
|
*
|
|
252
319
|
* @param downloadProgressCallback An optional callback that allows tracking the progress of the update while it is being downloaded.
|
|
320
|
+
* @param updateArchiveResultCallback An optional callback for observing which archive an update published with a binary patch was downloaded from. It is called once when the download had such an archive to try, and only after that download has succeeded; what it says nothing about is whether installing the resolved `LocalPackage` succeeds. An archive that could not be applied is reported here and the update is downloaded in full.
|
|
253
321
|
*/
|
|
254
|
-
download(
|
|
322
|
+
download(
|
|
323
|
+
downloadProgressCallback?: DownloadProgressCallback,
|
|
324
|
+
updateArchiveResultCallback?: (result: UpdateArchiveResult) => void,
|
|
325
|
+
): Promise<LocalPackage>;
|
|
255
326
|
|
|
256
327
|
/**
|
|
257
328
|
* The URL at which the package is available for download.
|
|
@@ -265,6 +336,15 @@ export interface RemotePackage extends Package {
|
|
|
265
336
|
* package is downloaded in full from `downloadUrl`.
|
|
266
337
|
*/
|
|
267
338
|
binaryPatchDownloadUrl?: string;
|
|
339
|
+
|
|
340
|
+
/**
|
|
341
|
+
* The URL of the asset diff archive built against the update this app is running.
|
|
342
|
+
* It is only present when the release publishes a diff against exactly that update, and
|
|
343
|
+
* it accompanies `binaryPatchDownloadUrl` rather than replacing it, so a package can hold
|
|
344
|
+
* both. `download` chooses between them and reports the choice as an
|
|
345
|
+
* `UpdateArchiveResult`.
|
|
346
|
+
*/
|
|
347
|
+
assetDiffDownloadUrl?: string;
|
|
268
348
|
}
|
|
269
349
|
|
|
270
350
|
export interface SyncOptions {
|
|
@@ -297,8 +377,8 @@ export interface SyncOptions {
|
|
|
297
377
|
|
|
298
378
|
/**
|
|
299
379
|
* An "options" object used to determine whether a confirmation dialog should be displayed to the end user when an update is available,
|
|
300
|
-
* and if so, what strings to use. Defaults to null, which has the effect of disabling the dialog completely. Setting this to
|
|
301
|
-
*
|
|
380
|
+
* and if so, what strings to use. Defaults to null, which has the effect of disabling the dialog completely. Setting this to `true`
|
|
381
|
+
* will enable the dialog with the default strings, and passing an object to this parameter allows enabling the dialog as well as
|
|
302
382
|
* overriding one or more of the default strings.
|
|
303
383
|
*/
|
|
304
384
|
updateDialog?: UpdateDialog | true;
|
|
@@ -306,24 +386,25 @@ export interface SyncOptions {
|
|
|
306
386
|
/**
|
|
307
387
|
* The rollback retry mechanism allows the application to attempt to reinstall an update that was previously rolled back (with the restrictions
|
|
308
388
|
* specified in the options). It is an "options" object used to determine whether a rollback retry should occur, and if so, what settings to use
|
|
309
|
-
* for the rollback retry. This defaults to null, which has the effect of disabling the retry mechanism.
|
|
310
|
-
* the retry
|
|
311
|
-
*
|
|
389
|
+
* for the rollback retry. This defaults to null, which has the effect of disabling the retry mechanism. Passing an object to this parameter enables
|
|
390
|
+
* the rollback retry: an empty object retries with the default settings, and each value the object specifies overrides the default for that
|
|
391
|
+
* setting.
|
|
312
392
|
*/
|
|
313
393
|
rollbackRetryOptions?: RollbackRetryOptions;
|
|
314
394
|
|
|
315
395
|
/**
|
|
316
|
-
* An optional callback for observing
|
|
317
|
-
*
|
|
318
|
-
* how long the patch work took. It is called once
|
|
319
|
-
*
|
|
396
|
+
* An optional callback for observing which archive an update published with a binary
|
|
397
|
+
* patch was downloaded from: whether it came from one of its patch archives, why each
|
|
398
|
+
* archive that was given up on was, and how long the patch work took. It is called once
|
|
399
|
+
* per download that had such an archive to try, after that download has succeeded and
|
|
400
|
+
* before the update is installed, with the label of the release it carries.
|
|
320
401
|
*
|
|
321
402
|
* Purely for observation. The library neither stores the result nor sends it anywhere -
|
|
322
403
|
* an app that wants it in its telemetry sends it itself - and nothing about the update
|
|
323
404
|
* depends on the callback: registering none changes nothing, and one that throws is
|
|
324
|
-
* logged and does not fail the
|
|
405
|
+
* logged and does not fail the download it is reporting on.
|
|
325
406
|
*/
|
|
326
|
-
|
|
407
|
+
onUpdateArchiveResult?: (label: string, result: UpdateArchiveResult) => void;
|
|
327
408
|
|
|
328
409
|
/**
|
|
329
410
|
* Specifies whether to ignore the update if the installation fails.
|
|
@@ -583,13 +664,13 @@ declare namespace CodePush {
|
|
|
583
664
|
RUNNING,
|
|
584
665
|
|
|
585
666
|
/**
|
|
586
|
-
* Indicates
|
|
667
|
+
* Indicates that an update has been installed, but the
|
|
587
668
|
* app hasn't been restarted yet in order to apply it.
|
|
588
669
|
*/
|
|
589
670
|
PENDING,
|
|
590
671
|
|
|
591
672
|
/**
|
|
592
|
-
* Indicates
|
|
673
|
+
* Indicates that an update represents the latest available
|
|
593
674
|
* release, and can be either currently running or pending.
|
|
594
675
|
*/
|
|
595
676
|
LATEST
|
|
@@ -625,7 +706,7 @@ declare namespace CodePush {
|
|
|
625
706
|
ON_APP_RESUME,
|
|
626
707
|
|
|
627
708
|
/**
|
|
628
|
-
* Don't automatically check for updates, but only do it when codePush.sync() is
|
|
709
|
+
* Don't automatically check for updates, but only do it when codePush.sync() is manually called inside app code.
|
|
629
710
|
*/
|
|
630
711
|
MANUAL
|
|
631
712
|
}
|