@bravemobile/react-native-code-push 13.1.0-beta.3 → 13.1.0-beta.5

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.
Files changed (46) hide show
  1. package/CodePush.podspec +5 -0
  2. package/README.md +20 -145
  3. package/android/app/src/main/java/com/microsoft/codepush/react/ArchiveAttemptLog.java +181 -0
  4. package/android/app/src/main/java/com/microsoft/codepush/react/{BinaryPatchResult.java → ArchiveRestoreResult.java} +17 -7
  5. package/android/app/src/main/java/com/microsoft/codepush/react/CodePushBinaryPatch.java +20 -20
  6. package/android/app/src/main/java/com/microsoft/codepush/react/CodePushConstants.java +20 -1
  7. package/android/app/src/main/java/com/microsoft/codepush/react/CodePushErrorCode.java +86 -0
  8. package/android/app/src/main/java/com/microsoft/codepush/react/CodePushHttpException.java +29 -0
  9. package/android/app/src/main/java/com/microsoft/codepush/react/CodePushIncompleteDownloadException.java +17 -0
  10. package/android/app/src/main/java/com/microsoft/codepush/react/CodePushNativeModule.java +12 -7
  11. package/android/app/src/main/java/com/microsoft/codepush/react/CodePushUpdateManager.java +143 -43
  12. package/android/app/src/test/java/com/microsoft/codepush/react/CodePushBinaryPatchTest.java +31 -31
  13. package/android/app/src/test/java/com/microsoft/codepush/react/CodePushErrorCodeTest.java +90 -0
  14. package/android/app/src/test/java/com/microsoft/codepush/react/CodePushUpdateManagerBinaryPatchTest.java +159 -19
  15. package/android/app/src/test/java/com/microsoft/codepush/react/CodePushUpdateManagerDownloadTest.java +304 -15
  16. package/cli/commands/createHistoryCommand/createReleaseHistory.test.ts +133 -0
  17. package/cli/commands/createHistoryCommand/createReleaseHistory.ts +3 -11
  18. package/cli/commands/releaseCommand/addToReleaseHistory.ts +3 -11
  19. package/cli/commands/releaseCommand/release.test.ts +55 -13
  20. package/cli/commands/releaseCommand/release.ts +39 -6
  21. package/cli/commands/updateHistoryCommand/updateReleaseHistory.ts +3 -11
  22. package/cli/dist/commands/createHistoryCommand/createReleaseHistory.js +2 -8
  23. package/cli/dist/commands/createHistoryCommand/createReleaseHistory.test.js +95 -0
  24. package/cli/dist/commands/releaseCommand/addToReleaseHistory.js +2 -8
  25. package/cli/dist/commands/releaseCommand/release.js +23 -8
  26. package/cli/dist/commands/releaseCommand/release.test.js +37 -9
  27. package/cli/dist/commands/updateHistoryCommand/updateReleaseHistory.js +2 -8
  28. package/cli/dist/functions/resolveAssetDiffBases.js +7 -2
  29. package/cli/dist/functions/resolveAssetDiffBases.test.js +26 -12
  30. package/cli/dist/functions/stageReleaseHistoryFile.js +28 -0
  31. package/cli/functions/resolveAssetDiffBases.test.ts +28 -14
  32. package/cli/functions/resolveAssetDiffBases.ts +8 -1
  33. package/cli/functions/stageReleaseHistoryFile.ts +38 -0
  34. package/ios/CodePush/CodePush.h +6 -3
  35. package/ios/CodePush/CodePush.mm +10 -10
  36. package/ios/CodePush/CodePushBinaryPatch.h +18 -8
  37. package/ios/CodePush/CodePushBinaryPatch.m +23 -22
  38. package/ios/CodePush/CodePushDownloadHandler.m +2 -1
  39. package/ios/CodePush/CodePushErrorUtils.m +55 -0
  40. package/ios/CodePush/CodePushPackage.m +242 -71
  41. package/package.json +2 -1
  42. package/src/CodePush.js +28 -18
  43. package/src/CodePush.test.js +110 -55
  44. package/src/package-mixins.js +19 -10
  45. package/typings/react-native-code-push.d.ts +152 -41
  46. package/android/app/src/main/java/com/microsoft/codepush/react/BinaryPatchAttempt.java +0 -92
package/src/CodePush.js CHANGED
@@ -189,9 +189,16 @@ async function checkForUpdate(handleBinaryVersionMismatchCallback = null) {
189
189
  download_url: latestReleaseInfo.downloadUrl,
190
190
  /**
191
191
  * Only released updates that were published with a binary patch carry this.
192
- * When it is missing, the update is downloaded in full from `download_url`.
192
+ * A release offering neither this nor a diff is downloaded in full from
193
+ * `download_url`.
193
194
  */
194
- binary_patch_download_url: diffPackageDownloadUrl || latestReleaseInfo.binaryPatchDownloadUrl,
195
+ binary_patch_download_url: latestReleaseInfo.binaryPatchDownloadUrl,
196
+ /**
197
+ * The diff stands next to the patch url rather than in its place, so that the
198
+ * native side can try the diff first and still hold the patch archive to fall
199
+ * back on when the diff fails on its asset side.
200
+ */
201
+ asset_diff_download_url: diffPackageDownloadUrl,
195
202
  // (`enabled` will always be true in the release information obtained from the previous process.)
196
203
  is_available: latestReleaseInfo.enabled,
197
204
  package_hash: latestReleaseInfo.packageHash,
@@ -293,11 +300,14 @@ function mapToRemotePackageMetadata(updateInfo) {
293
300
  packageHash: updateInfo.package_hash ?? '',
294
301
  packageSize: updateInfo.package_size ?? 0,
295
302
  downloadUrl: updateInfo.download_url ?? '',
296
- // The field stays out of the package unless the update really has a binary patch,
303
+ // The fields stay out of the package unless the update really has these archives,
297
304
  // so that the native side sees exactly what it saw before patches existed.
298
305
  ...(updateInfo.binary_patch_download_url
299
306
  ? { binaryPatchDownloadUrl: updateInfo.binary_patch_download_url }
300
307
  : {}),
308
+ ...(updateInfo.asset_diff_download_url
309
+ ? { assetDiffDownloadUrl: updateInfo.asset_diff_download_url }
310
+ : {}),
301
311
  };
302
312
  }
303
313
 
@@ -551,20 +561,20 @@ async function syncInternal(options = {}, syncStatusChangeCallback, downloadProg
551
561
  updateDialog: null,
552
562
  // The callback registered on the decorator is the default for every sync, including a
553
563
  // manual one; a callback passed to this call overrides it for this call.
554
- onBinaryPatchResult: sharedCodePushOptions.onBinaryPatchResult ?? null,
564
+ onUpdateArchiveResult: sharedCodePushOptions.onUpdateArchiveResult ?? null,
555
565
  ...options,
556
566
  };
557
567
 
558
568
  /*
559
- * A callback an app registered to observe how a binary patch went must not be able to
560
- * cost it an update, so it is isolated the way the other sync callbacks are: whatever it
561
- * throws is logged and the install carries on. An app that registered none leaves the
569
+ * A callback an app registered to observe how the update archives went must not be able
570
+ * to cost it an update, so it is isolated the way the other sync callbacks are: whatever
571
+ * it throws is logged and the install carries on. An app that registered none leaves the
562
572
  * download exactly as it was before this option existed.
563
573
  */
564
- const onBinaryPatchResult = typeof syncOptions.onBinaryPatchResult === "function"
574
+ const onUpdateArchiveResult = typeof syncOptions.onUpdateArchiveResult === "function"
565
575
  ? (label, result) => {
566
576
  try {
567
- syncOptions.onBinaryPatchResult(label, result);
577
+ syncOptions.onUpdateArchiveResult(label, result);
568
578
  } catch (error) {
569
579
  log(`An error has occurred : ${error.stack}`);
570
580
  }
@@ -624,7 +634,7 @@ async function syncInternal(options = {}, syncStatusChangeCallback, downloadProg
624
634
 
625
635
  const localPackage = await remotePackage.download(
626
636
  downloadProgressCallback,
627
- onBinaryPatchResult && ((result) => onBinaryPatchResult(remotePackageLabel, result)),
637
+ onUpdateArchiveResult && ((result) => onUpdateArchiveResult(remotePackageLabel, result)),
628
638
  );
629
639
 
630
640
  sharedCodePushOptions.onDownloadSuccess?.(remotePackageLabel);
@@ -760,8 +770,8 @@ let CodePush;
760
770
  * onRolloutSkipped: (label: string, error: Error) => void | undefined,
761
771
  * setOnRolloutSkipped(onRolloutSkippedFunction: (label: string, error: Error) => void | undefined): void,
762
772
  *
763
- * onBinaryPatchResult: (label: string, result: object) => void | undefined,
764
- * setOnBinaryPatchResult(onBinaryPatchResultFunction: (label: string, result: object) => void | undefined): void,
773
+ * onUpdateArchiveResult: (label: string, result: object) => void | undefined,
774
+ * setOnUpdateArchiveResult(onUpdateArchiveResultFunction: (label: string, result: object) => void | undefined): void,
765
775
  * }}
766
776
  */
767
777
  const sharedCodePushOptions = {
@@ -812,11 +822,11 @@ const sharedCodePushOptions = {
812
822
  if (typeof onRolloutSkippedFunction !== 'function') throw new Error('Please pass a function to onRolloutSkipped');
813
823
  this.onRolloutSkipped = onRolloutSkippedFunction;
814
824
  },
815
- onBinaryPatchResult: undefined,
816
- setOnBinaryPatchResult(onBinaryPatchResultFunction) {
817
- if (!onBinaryPatchResultFunction) return;
818
- if (typeof onBinaryPatchResultFunction !== 'function') throw new Error('Please pass a function to onBinaryPatchResult');
819
- this.onBinaryPatchResult = onBinaryPatchResultFunction;
825
+ onUpdateArchiveResult: undefined,
826
+ setOnUpdateArchiveResult(onUpdateArchiveResultFunction) {
827
+ if (!onUpdateArchiveResultFunction) return;
828
+ if (typeof onUpdateArchiveResultFunction !== 'function') throw new Error('Please pass a function to onUpdateArchiveResult');
829
+ this.onUpdateArchiveResult = onUpdateArchiveResultFunction;
820
830
  },
821
831
  }
822
832
 
@@ -854,7 +864,7 @@ function codePushify(options = {}) {
854
864
  sharedCodePushOptions.setOnDownloadSuccess(options.onDownloadSuccess);
855
865
  sharedCodePushOptions.setOnSyncError(options.onSyncError);
856
866
  sharedCodePushOptions.setOnRolloutSkipped(options.onRolloutSkipped);
857
- sharedCodePushOptions.setOnBinaryPatchResult(options.onBinaryPatchResult);
867
+ sharedCodePushOptions.setOnUpdateArchiveResult(options.onUpdateArchiveResult);
858
868
 
859
869
  const decorator = (RootComponent) => {
860
870
  class CodePushComponent extends React.Component {
@@ -60,16 +60,16 @@ function diffRelease() {
60
60
  }
61
61
 
62
62
  /**
63
- * @param binaryPatchResult what the native side reports the patch attempt ended in, which
64
- * only a download that had a patch to try comes back with.
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({ binaryPatchResult, installedPackage } = {}) {
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
- ...(binaryPatchResult ? { binaryPatchResult } : {}),
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 onBinaryPatchResult a callback the app registers on the decorator, as opposed to
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, binaryPatchResult, onBinaryPatchResult, installedPackage } = {}) {
95
+ function loadCodePush({ releaseHistory = {}, updateChecker, updateArchiveResult, onUpdateArchiveResult, installedPackage } = {}) {
96
96
  jest.resetModules();
97
97
  const CodePush = require('./CodePush');
98
- const nativeBridge = createNativeBridge({ binaryPatchResult, installedPackage });
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
- onBinaryPatchResult,
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 which of the
215
- * two patch urls of a release is downloaded depends on what the app is running right now.
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('downloads the diff archive when the installed update matches a diff base', async () => {
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: DIFF_URL,
231
+ binaryPatchDownloadUrl: BINARY_PATCH_DOWNLOAD_URL,
232
+ assetDiffDownloadUrl: DIFF_URL,
230
233
  });
231
234
  });
232
235
 
233
- it('downloads the plain patch archive when the installed update matches no diff base', async () => {
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
- expect(downloadedPackageMetadata(nativeBridge)).toMatchObject({
243
- binaryPatchDownloadUrl: BINARY_PATCH_DOWNLOAD_URL,
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('downloads the plain patch archive when no update is installed', async () => {
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
- expect(downloadedPackageMetadata(nativeBridge)).toMatchObject({
254
- binaryPatchDownloadUrl: BINARY_PATCH_DOWNLOAD_URL,
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 a patch attempt went is reported to the app that asked to hear about it, and to
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 binary patch result of a sync', () => {
318
- const APPLIED = { status: 'applied', applyDurationMs: 812 };
319
- const FELL_BACK = { status: 'fallback', fallbackReason: 'base_hash_mismatch', applyDurationMs: 1503 };
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
- binaryPatchResult: APPLIED,
353
+ updateArchiveResult: APPLIED,
331
354
  });
332
- const onBinaryPatchResult = jest.fn();
355
+ const onUpdateArchiveResult = jest.fn();
333
356
 
334
- const syncStatus = await CodePush.sync({ onBinaryPatchResult });
357
+ const syncStatus = await CodePush.sync({ onUpdateArchiveResult });
335
358
 
336
359
  expect(syncStatus).toBe(CodePush.SyncStatus.UPDATE_INSTALLED);
337
- expect(onBinaryPatchResult).toHaveBeenCalledTimes(1);
338
- expect(onBinaryPatchResult).toHaveBeenCalledWith(LABEL, APPLIED);
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
- binaryPatchResult: FELL_BACK,
368
+ updateArchiveResult: FELL_BACK,
346
369
  });
347
- const onBinaryPatchResult = jest.fn();
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({ onBinaryPatchResult });
373
+ const syncStatus = await CodePush.sync({ onUpdateArchiveResult });
351
374
 
352
375
  expect(syncStatus).toBe(CodePush.SyncStatus.UPDATE_INSTALLED);
353
- expect(onBinaryPatchResult).toHaveBeenCalledWith(LABEL, FELL_BACK);
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 onBinaryPatchResult = jest.fn();
396
+ const onUpdateArchiveResult = jest.fn();
358
397
  const { CodePush } = loadCodePush({
359
398
  releaseHistory: patchedRelease(),
360
- binaryPatchResult: APPLIED,
361
- onBinaryPatchResult,
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(onBinaryPatchResult).toHaveBeenCalledTimes(1);
369
- expect(onBinaryPatchResult).toHaveBeenCalledWith(LABEL, APPLIED);
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
- binaryPatchResult: APPLIED,
378
- onBinaryPatchResult: registeredOnTheDecorator,
416
+ updateArchiveResult: APPLIED,
417
+ onUpdateArchiveResult: registeredOnTheDecorator,
379
418
  });
380
419
 
381
- const syncStatus = await CodePush.sync({ onBinaryPatchResult: passedToTheSyncCall });
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 onBinaryPatchResult = jest.fn();
430
+ const onUpdateArchiveResult = jest.fn();
392
431
 
393
- const syncStatus = await CodePush.sync({ onBinaryPatchResult });
432
+ const syncStatus = await CodePush.sync({ onUpdateArchiveResult });
394
433
 
395
434
  expect(syncStatus).toBe(CodePush.SyncStatus.UPDATE_INSTALLED);
396
- expect(onBinaryPatchResult).not.toHaveBeenCalled();
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
- binaryPatchResult: APPLIED,
441
+ updateArchiveResult: APPLIED,
403
442
  });
404
- const onBinaryPatchResult = jest.fn(() => {
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({ onBinaryPatchResult });
447
+ const syncStatus = await CodePush.sync({ onUpdateArchiveResult });
409
448
 
410
449
  expect(syncStatus).toBe(CodePush.SyncStatus.UPDATE_INSTALLED);
411
- expect(onBinaryPatchResult).toHaveBeenCalledTimes(1);
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
- binaryPatchResult: APPLIED,
457
+ updateArchiveResult: APPLIED,
419
458
  });
420
459
 
421
- await CodePush.sync({ onBinaryPatchResult: jest.fn() });
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('binaryPatchResult');
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
- binaryPatchResult: FELL_BACK,
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('binaryPatchResult');
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
- binaryPatchResult: APPLIED,
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('binaryPatchResult');
505
+ expect(localPackage).not.toHaveProperty('updateArchiveResult');
451
506
  });
452
507
  });
@@ -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 binaryPatchResultCallback Called with `{ status, fallbackReason?, applyDurationMs }`
10
- * when the update was published with a binary patch, so that the app can observe
11
- * how the patch went. It says nothing about whether the download succeeded - a
12
- * patch that could not be applied is reported here and the update is downloaded
13
- * in full - and it is the only place the result is ever available.
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, binaryPatchResultCallback) {
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 patch result describes the download that just happened, not the update it
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 { binaryPatchResult, ...downloadedPackage } = downloadResult ?? {};
42
- if (binaryPatchResult) {
43
- binaryPatchResultCallback?.(binaryPatchResult);
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 };