@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.
- package/CodePush.podspec +5 -0
- package/README.md +20 -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/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 +55 -13
- package/cli/commands/releaseCommand/release.ts +39 -6
- package/cli/commands/updateHistoryCommand/updateReleaseHistory.ts +3 -11
- 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.js +23 -8
- package/cli/dist/commands/releaseCommand/release.test.js +37 -9
- package/cli/dist/commands/updateHistoryCommand/updateReleaseHistory.js +2 -8
- package/cli/dist/functions/resolveAssetDiffBases.js +7 -2
- package/cli/dist/functions/resolveAssetDiffBases.test.js +26 -12
- package/cli/dist/functions/stageReleaseHistoryFile.js +28 -0
- package/cli/functions/resolveAssetDiffBases.test.ts +28 -14
- package/cli/functions/resolveAssetDiffBases.ts +8 -1
- package/cli/functions/stageReleaseHistoryFile.ts +38 -0
- 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 +152 -41
- 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
|
-
*
|
|
192
|
+
* A release offering neither this nor a diff is downloaded in full from
|
|
193
|
+
* `download_url`.
|
|
193
194
|
*/
|
|
194
|
-
binary_patch_download_url:
|
|
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
|
|
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
|
-
|
|
564
|
+
onUpdateArchiveResult: sharedCodePushOptions.onUpdateArchiveResult ?? null,
|
|
555
565
|
...options,
|
|
556
566
|
};
|
|
557
567
|
|
|
558
568
|
/*
|
|
559
|
-
* A callback an app registered to observe how
|
|
560
|
-
* cost it an update, so it is isolated the way the other sync callbacks are: whatever
|
|
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
|
|
574
|
+
const onUpdateArchiveResult = typeof syncOptions.onUpdateArchiveResult === "function"
|
|
565
575
|
? (label, result) => {
|
|
566
576
|
try {
|
|
567
|
-
syncOptions.
|
|
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
|
-
|
|
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
|
-
*
|
|
764
|
-
*
|
|
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
|
-
|
|
816
|
-
|
|
817
|
-
if (!
|
|
818
|
-
if (typeof
|
|
819
|
-
this.
|
|
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.
|
|
867
|
+
sharedCodePushOptions.setOnUpdateArchiveResult(options.onUpdateArchiveResult);
|
|
858
868
|
|
|
859
869
|
const decorator = (RootComponent) => {
|
|
860
870
|
class CodePushComponent extends React.Component {
|
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 };
|