@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
@@ -13,6 +13,7 @@ import { resolveAssetDiffBases } from "./resolveAssetDiffBases.js";
13
13
  const CONTENTS_DIR_NAME = 'CodePush';
14
14
  /** Where the resolver unpacks a base for verification, so leftovers are recognizable. */
15
15
  const TEMP_DIR_PREFIX = 'codepush-asset-diff-base-';
16
+ const TARGET_BINARY_VERSION = '1.0.0';
16
17
  let workDir;
17
18
  let bundleDirectory;
18
19
  /** Releases one full archive the way the CLI does, and returns what the history records. */
@@ -49,16 +50,16 @@ function releaseHistoryOf(releases) {
49
50
  /** Stands in for the consumer's downloader: hands back a local archive per download url. */
50
51
  function fakeDownloader(archives) {
51
52
  const calls = [];
52
- const download = async (downloadUrl, platform, identifier) => {
53
- calls.push([downloadUrl, platform, identifier]);
54
- const archive = archives[downloadUrl];
55
- if (archive instanceof Error) {
56
- throw archive;
53
+ const download = async (downloadInfo, platform, identifier) => {
54
+ calls.push([downloadInfo, platform, identifier]);
55
+ const downloadedArchive = archives[downloadInfo.downloadUrl];
56
+ if (downloadedArchive instanceof Error) {
57
+ throw downloadedArchive;
57
58
  }
58
- if (!archive) {
59
- throw new Error(`the downloader was called with an unexpected url: ${downloadUrl}`);
59
+ if (!downloadedArchive) {
60
+ throw new Error(`the downloader was called with an unexpected url: ${downloadInfo.downloadUrl}`);
60
61
  }
61
- return { downloadedFilePath: archive };
62
+ return { downloadedFilePath: downloadedArchive };
62
63
  };
63
64
  return { download, calls };
64
65
  }
@@ -86,14 +87,15 @@ describe("resolveAssetDiffBases", () => {
86
87
  bundleDownloader: download,
87
88
  platform: 'android',
88
89
  identifier: 'my-app',
90
+ targetBinaryVersion: TARGET_BINARY_VERSION,
89
91
  });
90
92
  expect(bases).toEqual([
91
93
  { basePackageHash: newest.packageHash, baseBundleFilePath: newest.archivePath },
92
94
  { basePackageHash: second.packageHash, baseBundleFilePath: second.archivePath },
93
95
  ]);
94
96
  expect(calls).toEqual([
95
- [newest.downloadUrl, 'android', 'my-app'],
96
- [second.downloadUrl, 'android', 'my-app'],
97
+ [{ downloadUrl: newest.downloadUrl, targetBinaryVersion: TARGET_BINARY_VERSION, releaseVersion: newest.version, packageHash: newest.packageHash }, 'android', 'my-app'],
98
+ [{ downloadUrl: second.downloadUrl, targetBinaryVersion: TARGET_BINARY_VERSION, releaseVersion: second.version, packageHash: second.packageHash }, 'android', 'my-app'],
97
99
  ]);
98
100
  });
99
101
  it("skips history entries that lack a download url or a package hash", async () => {
@@ -109,11 +111,16 @@ describe("resolveAssetDiffBases", () => {
109
111
  diffBaseCount: 1,
110
112
  bundleDownloader: download,
111
113
  platform: 'ios',
114
+ targetBinaryVersion: TARGET_BINARY_VERSION,
112
115
  });
113
116
  expect(bases).toEqual([
114
117
  { basePackageHash: released.packageHash, baseBundleFilePath: released.archivePath },
115
118
  ]);
116
- expect(calls).toEqual([[released.downloadUrl, 'ios', undefined]]);
119
+ expect(calls).toEqual([[
120
+ { downloadUrl: released.downloadUrl, targetBinaryVersion: TARGET_BINARY_VERSION, releaseVersion: released.version, packageHash: released.packageHash },
121
+ 'ios',
122
+ undefined,
123
+ ]]);
117
124
  });
118
125
  it("ignores a history entry whose key is not a version, and resolves the rest", async () => {
119
126
  const [released] = await stageReleases(['1.4.0']);
@@ -131,11 +138,16 @@ describe("resolveAssetDiffBases", () => {
131
138
  diffBaseCount: 2,
132
139
  bundleDownloader: download,
133
140
  platform: 'ios',
141
+ targetBinaryVersion: TARGET_BINARY_VERSION,
134
142
  });
135
143
  expect(bases).toEqual([
136
144
  { basePackageHash: released.packageHash, baseBundleFilePath: released.archivePath },
137
145
  ]);
138
- expect(calls).toEqual([[released.downloadUrl, 'ios', undefined]]);
146
+ expect(calls).toEqual([[
147
+ { downloadUrl: released.downloadUrl, targetBinaryVersion: TARGET_BINARY_VERSION, releaseVersion: released.version, packageHash: released.packageHash },
148
+ 'ios',
149
+ undefined,
150
+ ]]);
139
151
  });
140
152
  it("drops a base whose downloaded bytes do not match its recorded package hash", async () => {
141
153
  const [older, newer] = await stageReleases(['1.2.0', '1.2.1']);
@@ -150,6 +162,7 @@ describe("resolveAssetDiffBases", () => {
150
162
  diffBaseCount: 2,
151
163
  bundleDownloader: download,
152
164
  platform: 'android',
165
+ targetBinaryVersion: TARGET_BINARY_VERSION,
153
166
  });
154
167
  expect(bases).toEqual([{ basePackageHash: older.packageHash, baseBundleFilePath: older.archivePath }]);
155
168
  expect(warn.mock.calls.join('\n')).toMatch(/1\.2\.1/);
@@ -167,6 +180,7 @@ describe("resolveAssetDiffBases", () => {
167
180
  diffBaseCount: 2,
168
181
  bundleDownloader: download,
169
182
  platform: 'android',
183
+ targetBinaryVersion: TARGET_BINARY_VERSION,
170
184
  });
171
185
  expect(bases).toEqual([{ basePackageHash: older.packageHash, baseBundleFilePath: older.archivePath }]);
172
186
  expect(warn.mock.calls.join('\n')).toMatch(/1\.3\.1.*the network is down/s);
@@ -0,0 +1,28 @@
1
+ import fs from "fs";
2
+ import path from "path";
3
+ const STAGING_DIR_NAME = 'codepush-release-history';
4
+ /**
5
+ * Writes the release history to a file for the config to read, and takes it away again
6
+ * once the config has stored it.
7
+ *
8
+ * Written under a directory of the platform's own rather than straight into the directory
9
+ * the command ran in. The name follows from the binary version alone, so two commands
10
+ * releasing the same version of one app for different platforms shared one path: whichever
11
+ * finished first took the file away while the other was still about to read it, and before
12
+ * that each had overwritten the other's contents. The name itself is kept as it was,
13
+ * because the name is part of what the config is handed.
14
+ *
15
+ * A file whose history was never stored is left where it was written, so a release that
16
+ * failed can be read back from a path that is the same on every run.
17
+ */
18
+ export async function stageReleaseHistoryFile(binaryVersion, releaseHistory, platform, store) {
19
+ const jsonFileName = `${binaryVersion}.json`;
20
+ const stagingDir = path.resolve(process.cwd(), STAGING_DIR_NAME, platform);
21
+ const jsonFilePath = path.join(stagingDir, jsonFileName);
22
+ fs.mkdirSync(stagingDir, { recursive: true });
23
+ console.log(`log: creating JSON file... ("${jsonFileName}")\n`, JSON.stringify(releaseHistory, null, 2));
24
+ fs.writeFileSync(jsonFilePath, JSON.stringify(releaseHistory));
25
+ const stored = await store(jsonFilePath);
26
+ fs.rmSync(jsonFilePath, { force: true });
27
+ return stored;
28
+ }
@@ -4,7 +4,7 @@ import path from "path";
4
4
  import { afterAll, afterEach, beforeAll, describe, expect, it, jest } from "@jest/globals";
5
5
  import { makeCodePushBundle } from "./makeCodePushBundle.js";
6
6
  import { resolveAssetDiffBases } from "./resolveAssetDiffBases.js";
7
- import type { CliConfigInterface, ReleaseHistoryInterface } from "../../typings/react-native-code-push.d.ts";
7
+ import type { BundleDownloadInfo, CliConfigInterface, ReleaseHistoryInterface } from "../../typings/react-native-code-push.d.ts";
8
8
 
9
9
  /**
10
10
  * Exercises base acquisition against real archives and real hashes: a base is only usable
@@ -17,6 +17,7 @@ const CONTENTS_DIR_NAME = 'CodePush';
17
17
 
18
18
  /** Where the resolver unpacks a base for verification, so leftovers are recognizable. */
19
19
  const TEMP_DIR_PREFIX = 'codepush-asset-diff-base-';
20
+ const TARGET_BINARY_VERSION = '1.0.0';
20
21
 
21
22
  type StagedRelease = {
22
23
  version: string;
@@ -68,22 +69,22 @@ function releaseHistoryOf(releases: StagedRelease[]): ReleaseHistoryInterface {
68
69
 
69
70
  /** Stands in for the consumer's downloader: hands back a local archive per download url. */
70
71
  function fakeDownloader(archives: Record<string, string | Error>) {
71
- const calls: Array<[string, string, string | undefined]> = [];
72
+ const calls: Array<[BundleDownloadInfo, string, string | undefined]> = [];
72
73
  const download: NonNullable<CliConfigInterface['bundleDownloader']> = async (
73
- downloadUrl,
74
+ downloadInfo,
74
75
  platform,
75
76
  identifier,
76
77
  ) => {
77
- calls.push([downloadUrl, platform, identifier]);
78
+ calls.push([downloadInfo, platform, identifier]);
78
79
 
79
- const archive = archives[downloadUrl];
80
- if (archive instanceof Error) {
81
- throw archive;
80
+ const downloadedArchive = archives[downloadInfo.downloadUrl];
81
+ if (downloadedArchive instanceof Error) {
82
+ throw downloadedArchive;
82
83
  }
83
- if (!archive) {
84
- throw new Error(`the downloader was called with an unexpected url: ${downloadUrl}`);
84
+ if (!downloadedArchive) {
85
+ throw new Error(`the downloader was called with an unexpected url: ${downloadInfo.downloadUrl}`);
85
86
  }
86
- return { downloadedFilePath: archive };
87
+ return { downloadedFilePath: downloadedArchive };
87
88
  };
88
89
  return { download, calls };
89
90
  }
@@ -119,6 +120,7 @@ describe("resolveAssetDiffBases", () => {
119
120
  bundleDownloader: download,
120
121
  platform: 'android',
121
122
  identifier: 'my-app',
123
+ targetBinaryVersion: TARGET_BINARY_VERSION,
122
124
  });
123
125
 
124
126
  expect(bases).toEqual([
@@ -126,8 +128,8 @@ describe("resolveAssetDiffBases", () => {
126
128
  { basePackageHash: second.packageHash, baseBundleFilePath: second.archivePath },
127
129
  ]);
128
130
  expect(calls).toEqual([
129
- [newest.downloadUrl, 'android', 'my-app'],
130
- [second.downloadUrl, 'android', 'my-app'],
131
+ [{ downloadUrl: newest.downloadUrl, targetBinaryVersion: TARGET_BINARY_VERSION, releaseVersion: newest.version, packageHash: newest.packageHash }, 'android', 'my-app'],
132
+ [{ downloadUrl: second.downloadUrl, targetBinaryVersion: TARGET_BINARY_VERSION, releaseVersion: second.version, packageHash: second.packageHash }, 'android', 'my-app'],
131
133
  ]);
132
134
  });
133
135
 
@@ -145,12 +147,17 @@ describe("resolveAssetDiffBases", () => {
145
147
  diffBaseCount: 1,
146
148
  bundleDownloader: download,
147
149
  platform: 'ios',
150
+ targetBinaryVersion: TARGET_BINARY_VERSION,
148
151
  });
149
152
 
150
153
  expect(bases).toEqual([
151
154
  { basePackageHash: released.packageHash, baseBundleFilePath: released.archivePath },
152
155
  ]);
153
- expect(calls).toEqual([[released.downloadUrl, 'ios', undefined]]);
156
+ expect(calls).toEqual([[
157
+ { downloadUrl: released.downloadUrl, targetBinaryVersion: TARGET_BINARY_VERSION, releaseVersion: released.version, packageHash: released.packageHash },
158
+ 'ios',
159
+ undefined,
160
+ ]]);
154
161
  });
155
162
 
156
163
  it("ignores a history entry whose key is not a version, and resolves the rest", async () => {
@@ -170,12 +177,17 @@ describe("resolveAssetDiffBases", () => {
170
177
  diffBaseCount: 2,
171
178
  bundleDownloader: download,
172
179
  platform: 'ios',
180
+ targetBinaryVersion: TARGET_BINARY_VERSION,
173
181
  });
174
182
 
175
183
  expect(bases).toEqual([
176
184
  { basePackageHash: released.packageHash, baseBundleFilePath: released.archivePath },
177
185
  ]);
178
- expect(calls).toEqual([[released.downloadUrl, 'ios', undefined]]);
186
+ expect(calls).toEqual([[
187
+ { downloadUrl: released.downloadUrl, targetBinaryVersion: TARGET_BINARY_VERSION, releaseVersion: released.version, packageHash: released.packageHash },
188
+ 'ios',
189
+ undefined,
190
+ ]]);
179
191
  });
180
192
 
181
193
  it("drops a base whose downloaded bytes do not match its recorded package hash", async () => {
@@ -192,6 +204,7 @@ describe("resolveAssetDiffBases", () => {
192
204
  diffBaseCount: 2,
193
205
  bundleDownloader: download,
194
206
  platform: 'android',
207
+ targetBinaryVersion: TARGET_BINARY_VERSION,
195
208
  });
196
209
 
197
210
  expect(bases).toEqual([{ basePackageHash: older.packageHash, baseBundleFilePath: older.archivePath }]);
@@ -212,6 +225,7 @@ describe("resolveAssetDiffBases", () => {
212
225
  diffBaseCount: 2,
213
226
  bundleDownloader: download,
214
227
  platform: 'android',
228
+ targetBinaryVersion: TARGET_BINARY_VERSION,
215
229
  });
216
230
 
217
231
  expect(bases).toEqual([{ basePackageHash: older.packageHash, baseBundleFilePath: older.archivePath }]);
@@ -40,12 +40,14 @@ export async function resolveAssetDiffBases({
40
40
  bundleDownloader,
41
41
  platform,
42
42
  identifier,
43
+ targetBinaryVersion,
43
44
  }: {
44
45
  releaseHistory: ReleaseHistoryInterface;
45
46
  diffBaseCount: number;
46
47
  bundleDownloader: NonNullable<CliConfigInterface['bundleDownloader']>;
47
48
  platform: 'ios' | 'android';
48
49
  identifier?: string;
50
+ targetBinaryVersion: string;
49
51
  }): Promise<AssetDiffBase[]> {
50
52
  // The seeded entry for the update inside the app binary has neither a url nor a hash,
51
53
  // and nothing can be diffed against a package that was never published. A key that is
@@ -60,7 +62,12 @@ export async function resolveAssetDiffBases({
60
62
  const bases: AssetDiffBase[] = [];
61
63
  for (const [version, releaseInfo] of candidates) {
62
64
  try {
63
- const { downloadedFilePath } = await bundleDownloader(releaseInfo.downloadUrl, platform, identifier);
65
+ const { downloadedFilePath } = await bundleDownloader({
66
+ downloadUrl: releaseInfo.downloadUrl,
67
+ targetBinaryVersion,
68
+ releaseVersion: version,
69
+ packageHash: releaseInfo.packageHash,
70
+ }, platform, identifier);
64
71
 
65
72
  if (!(await matchesPackageHash(downloadedFilePath, releaseInfo.packageHash))) {
66
73
  console.warn(
@@ -0,0 +1,38 @@
1
+ import fs from "fs";
2
+ import path from "path";
3
+ import type { ReleaseHistoryInterface } from "../../typings/react-native-code-push.d.ts";
4
+
5
+ const STAGING_DIR_NAME = 'codepush-release-history';
6
+
7
+ /**
8
+ * Writes the release history to a file for the config to read, and takes it away again
9
+ * once the config has stored it.
10
+ *
11
+ * Written under a directory of the platform's own rather than straight into the directory
12
+ * the command ran in. The name follows from the binary version alone, so two commands
13
+ * releasing the same version of one app for different platforms shared one path: whichever
14
+ * finished first took the file away while the other was still about to read it, and before
15
+ * that each had overwritten the other's contents. The name itself is kept as it was,
16
+ * because the name is part of what the config is handed.
17
+ *
18
+ * A file whose history was never stored is left where it was written, so a release that
19
+ * failed can be read back from a path that is the same on every run.
20
+ */
21
+ export async function stageReleaseHistoryFile<T>(
22
+ binaryVersion: string,
23
+ releaseHistory: ReleaseHistoryInterface,
24
+ platform: 'ios' | 'android',
25
+ store: (jsonFilePath: string) => Promise<T>,
26
+ ): Promise<T> {
27
+ const jsonFileName = `${binaryVersion}.json`;
28
+ const stagingDir = path.resolve(process.cwd(), STAGING_DIR_NAME, platform);
29
+ const jsonFilePath = path.join(stagingDir, jsonFileName);
30
+
31
+ fs.mkdirSync(stagingDir, { recursive: true });
32
+ console.log(`log: creating JSON file... ("${jsonFileName}")\n`, JSON.stringify(releaseHistory, null, 2));
33
+ fs.writeFileSync(jsonFilePath, JSON.stringify(releaseHistory));
34
+
35
+ const stored = await store(jsonFilePath);
36
+ fs.rmSync(jsonFilePath, { force: true });
37
+ return stored;
38
+ }
@@ -157,22 +157,25 @@ failCallback:(void (^)(NSError *err))failCallback;
157
157
  @interface CodePushErrorUtils : NSObject
158
158
 
159
159
  + (NSError *)errorWithMessage:(NSString *)errorMessage;
160
+ + (NSError *)errorWithMessage:(NSString *)errorMessage httpStatusCode:(NSInteger)statusCode;
160
161
  + (BOOL)isCodePushError:(NSError *)error;
162
+ + (BOOL)isNetworkFailure:(NSError *)error;
163
+ + (BOOL)isHttpStatusError:(NSError *)error;
161
164
 
162
165
  @end
163
166
 
164
167
  @interface CodePushPackage : NSObject
165
168
 
166
169
  /*
167
- * doneCallback is given what the attempt at installing the update from its binary patch
168
- * archive ended in, or nil when the update had no patch archive to attempt. The result
170
+ * doneCallback is given what the attempts at installing the update from its update
171
+ * archives ended in, or nil when the update had no archive to attempt. The result
169
172
  * describes the download and not the update: it is never part of the update's metadata.
170
173
  */
171
174
  + (void)downloadPackage:(NSDictionary *)updatePackage
172
175
  expectedBundleFileName:(NSString *)expectedBundleFileName
173
176
  operationQueue:(dispatch_queue_t)operationQueue
174
177
  progressCallback:(void (^)(long long, long long))progressCallback
175
- doneCallback:(void (^)(NSDictionary *binaryPatchResult))doneCallback
178
+ doneCallback:(void (^)(NSDictionary *updateArchiveResult))doneCallback
176
179
  failCallback:(void (^)(NSError *err))failCallback;
177
180
 
178
181
  + (NSDictionary *)getCurrentPackage:(NSError **)error;
@@ -63,7 +63,7 @@ static NSString *const PackageIsPendingKey = @"isPending";
63
63
 
64
64
  // Not one of those keys: the field a download that tried a binary patch reports its outcome
65
65
  // under, on the package it resolves with and on no package it stores.
66
- static NSString *const BinaryPatchResultKey = @"binaryPatchResult";
66
+ static NSString *const UpdateArchiveResultKey = @"updateArchiveResult";
67
67
 
68
68
  #pragma mark - Static variables
69
69
 
@@ -740,8 +740,8 @@ RCT_EXPORT_METHOD(downloadUpdate:(NSDictionary*)updatePackage
740
740
  // The download is progressing forward
741
741
  progressCallback:^(long long expectedContentLength, long long receivedContentLength) {
742
742
  // Received bytes only run backwards when a new download starts a stream of its
743
- // own - the full archive after a binary patch fallback. The pause left over
744
- // from the finished patch download would swallow that whole stream, so frame
743
+ // own - the next archive after a binary patch fallback. The pause left over
744
+ // from the finished download would swallow that whole stream, so frame
745
745
  // observation resumes here and the throttle forgets the previous stream.
746
746
  if (notifyProgress && receivedContentLength < _latestReceivedConentLength) {
747
747
  self.paused = NO;
@@ -767,21 +767,21 @@ RCT_EXPORT_METHOD(downloadUpdate:(NSDictionary*)updatePackage
767
767
  }
768
768
  }
769
769
  // The download completed
770
- doneCallback:^(NSDictionary *binaryPatchResult) {
770
+ doneCallback:^(NSDictionary *updateArchiveResult) {
771
771
  NSError *err;
772
772
  NSDictionary *newPackage = [CodePushPackage getPackage:mutableUpdatePackage[PackageHashKey] error:&err];
773
773
 
774
774
  if (err) {
775
- return reject([NSString stringWithFormat: @"%lu", (long)err.code], err.localizedDescription, err);
775
+ return reject([NSString stringWithFormat: @"%ld", (long)err.code], err.localizedDescription, err);
776
776
  }
777
777
 
778
- if (binaryPatchResult) {
778
+ if (updateArchiveResult) {
779
779
  // The result describes this download rather than the update, so it is put on
780
780
  // what this call resolves with and nowhere else: the metadata that was
781
781
  // written for the update knows nothing about it, and whoever asked for the
782
782
  // download decides what the result is worth.
783
783
  NSMutableDictionary *downloadedPackage = [newPackage mutableCopy];
784
- downloadedPackage[BinaryPatchResultKey] = binaryPatchResult;
784
+ downloadedPackage[UpdateArchiveResultKey] = updateArchiveResult;
785
785
  return resolve(downloadedPackage);
786
786
  }
787
787
 
@@ -796,7 +796,7 @@ RCT_EXPORT_METHOD(downloadUpdate:(NSDictionary*)updatePackage
796
796
  // Stop observing frame updates if the download fails.
797
797
  _didUpdateProgress = NO;
798
798
  self.paused = YES;
799
- reject([NSString stringWithFormat: @"%lu", (long)err.code], err.localizedDescription, err);
799
+ reject([NSString stringWithFormat: @"%ld", (long)err.code], err.localizedDescription, err);
800
800
  }];
801
801
  }
802
802
 
@@ -876,7 +876,7 @@ RCT_EXPORT_METHOD(getUpdateMetadata:(double)updateStateValue
876
876
  NSMutableDictionary *package = [[CodePushPackage getCurrentPackage:&error] mutableCopy];
877
877
 
878
878
  if (error) {
879
- return reject([NSString stringWithFormat: @"%lu", (long)error.code], error.localizedDescription, error);
879
+ return reject([NSString stringWithFormat: @"%ld", (long)error.code], error.localizedDescription, error);
880
880
  } else if (package == nil) {
881
881
  // The app hasn't downloaded any CodePush updates yet,
882
882
  // so we simply return nil regardless if the user
@@ -930,7 +930,7 @@ RCT_EXPORT_METHOD(installUpdate:(NSDictionary*)updatePackage
930
930
  error:&error];
931
931
 
932
932
  if (error) {
933
- reject([NSString stringWithFormat: @"%lu", (long)error.code], error.localizedDescription, error);
933
+ reject([NSString stringWithFormat: @"%ld", (long)error.code], error.localizedDescription, error);
934
934
  } else {
935
935
  [self savePendingUpdate:updatePackage[PackageHashKey]
936
936
  isLoading:NO];
@@ -1,7 +1,7 @@
1
1
  #import <Foundation/Foundation.h>
2
2
 
3
3
  /*
4
- * Why an update that was downloaded as a binary patch has to be downloaded in full
4
+ * Why an update that was downloaded as one of its archives has to be downloaded in full
5
5
  * instead.
6
6
  *
7
7
  * A failed restore is not an error the user ever hears about: it is the signal to
@@ -11,22 +11,32 @@
11
11
  */
12
12
 
13
13
  /** The bundle inside the app binary could not be opened or read. */
14
- extern NSString *const CodePushBinaryPatchReasonBaseBundleUnavailable;
14
+ extern NSString *const CodePushArchiveFallbackReasonBaseBundleUnavailable;
15
15
 
16
16
  /** The bundle inside the app binary is not the one the patch was computed against. */
17
- extern NSString *const CodePushBinaryPatchReasonBaseHashMismatch;
17
+ extern NSString *const CodePushArchiveFallbackReasonBaseHashMismatch;
18
18
 
19
19
  /** The manifest is missing, malformed, points outside the archive, or asks for too much. */
20
- extern NSString *const CodePushBinaryPatchReasonInvalidManifest;
20
+ extern NSString *const CodePushArchiveFallbackReasonInvalidManifest;
21
21
 
22
22
  /** The patch was produced by a format or a codec this client cannot apply. */
23
- extern NSString *const CodePushBinaryPatchReasonUnsupportedFormat;
23
+ extern NSString *const CodePushArchiveFallbackReasonUnsupportedFormat;
24
24
 
25
25
  /** The applier refused the patch, or the restored bundle could not be written. */
26
- extern NSString *const CodePushBinaryPatchReasonPatchApplyFailed;
26
+ extern NSString *const CodePushArchiveFallbackReasonPatchApplyFailed;
27
27
 
28
28
  /** The restored bundle is not the one the manifest promised. */
29
- extern NSString *const CodePushBinaryPatchReasonTargetVerificationFailed;
29
+ extern NSString *const CodePushArchiveFallbackReasonTargetVerificationFailed;
30
+
31
+ /**
32
+ * The asset diff could not be merged with the installed update it was built against.
33
+ *
34
+ * Reported by the caller too, from the merge that follows the restore: the diff archive
35
+ * only carries what the installed update does not, so this covers reading the installed
36
+ * update's files, applying the deletion manifest, and everything else that stitches the
37
+ * two back together.
38
+ */
39
+ extern NSString *const CodePushArchiveFallbackReasonAssetMergeFailed;
30
40
 
31
41
  /**
32
42
  * The update restored from the patch did not pass the checks every update passes before
@@ -35,7 +45,7 @@ extern NSString *const CodePushBinaryPatchReasonTargetVerificationFailed;
35
45
  * This one is reported by the caller rather than by the applier: by the time it applies,
36
46
  * the applier has already handed back a bundle it was happy with.
37
47
  */
38
- extern NSString *const CodePushBinaryPatchReasonPackageVerificationFailed;
48
+ extern NSString *const CodePushArchiveFallbackReasonPackageVerificationFailed;
39
49
 
40
50
  /**
41
51
  * Rebuilds the JS bundle of an update that was downloaded as a binary patch archive.
@@ -14,13 +14,14 @@
14
14
  #include "binarypatch_zstd_decompressor.h"
15
15
  #include "libHDiffPatch/HPatch/patch.h"
16
16
 
17
- NSString *const CodePushBinaryPatchReasonBaseBundleUnavailable = @"base_bundle_unavailable";
18
- NSString *const CodePushBinaryPatchReasonBaseHashMismatch = @"base_hash_mismatch";
19
- NSString *const CodePushBinaryPatchReasonInvalidManifest = @"invalid_manifest";
20
- NSString *const CodePushBinaryPatchReasonUnsupportedFormat = @"unsupported_format";
21
- NSString *const CodePushBinaryPatchReasonPatchApplyFailed = @"patch_apply_failed";
22
- NSString *const CodePushBinaryPatchReasonTargetVerificationFailed = @"target_verification_failed";
23
- NSString *const CodePushBinaryPatchReasonPackageVerificationFailed = @"package_verification_failed";
17
+ NSString *const CodePushArchiveFallbackReasonBaseBundleUnavailable = @"base_bundle_unavailable";
18
+ NSString *const CodePushArchiveFallbackReasonBaseHashMismatch = @"base_hash_mismatch";
19
+ NSString *const CodePushArchiveFallbackReasonInvalidManifest = @"invalid_manifest";
20
+ NSString *const CodePushArchiveFallbackReasonUnsupportedFormat = @"unsupported_format";
21
+ NSString *const CodePushArchiveFallbackReasonPatchApplyFailed = @"patch_apply_failed";
22
+ NSString *const CodePushArchiveFallbackReasonTargetVerificationFailed = @"target_verification_failed";
23
+ NSString *const CodePushArchiveFallbackReasonAssetMergeFailed = @"asset_merge_failed";
24
+ NSString *const CodePushArchiveFallbackReasonPackageVerificationFailed = @"package_verification_failed";
24
25
 
25
26
  #pragma mark - Private constants
26
27
 
@@ -205,7 +206,7 @@ static CodePushBinaryPatchApplyResult CodePushApplyBinaryPatch(const unsigned ch
205
206
  NSString *contentsFolderPath = [self contentsFolderInUnzippedFolder:unzippedFolderPath];
206
207
  NSString *manifestFilePath = [contentsFolderPath stringByAppendingPathComponent:BinaryPatchManifestFileName];
207
208
  if (![self isFileAtPath:manifestFilePath]) {
208
- return [self failWithReason:CodePushBinaryPatchReasonInvalidManifest
209
+ return [self failWithReason:CodePushArchiveFallbackReasonInvalidManifest
209
210
  outFailureReason:failureReason];
210
211
  }
211
212
 
@@ -219,13 +220,13 @@ static CodePushBinaryPatchApplyResult CodePushApplyBinaryPatch(const unsigned ch
219
220
  : nil;
220
221
  if (![manifest isKindOfClass:[NSDictionary class]]) {
221
222
  CPLog(@"The binary patch manifest could not be read: %@", error);
222
- return [self failWithReason:CodePushBinaryPatchReasonInvalidManifest
223
+ return [self failWithReason:CodePushArchiveFallbackReasonInvalidManifest
223
224
  outFailureReason:failureReason];
224
225
  }
225
226
 
226
227
  if ([[self numberInManifest:manifest forKey:BinaryPatchFormatVersionKey] integerValue] != BinaryPatchFormatVersion
227
228
  || ![BinaryPatchAlgorithm isEqualToString:[self stringInManifest:manifest forKey:BinaryPatchAlgorithmKey]]) {
228
- return [self failWithReason:CodePushBinaryPatchReasonUnsupportedFormat
229
+ return [self failWithReason:CodePushArchiveFallbackReasonUnsupportedFormat
229
230
  outFailureReason:failureReason];
230
231
  }
231
232
 
@@ -239,7 +240,7 @@ static CodePushBinaryPatchApplyResult CodePushApplyBinaryPatch(const unsigned ch
239
240
  if (targetBundleFilePath == nil || patchFilePath == nil || ![self isFileAtPath:patchFilePath]
240
241
  || baseBundleHash.length == 0 || targetBundleHash.length == 0
241
242
  || targetBundleSize <= 0 || targetBundleSize > BinaryPatchMaxTargetBundleSize) {
242
- return [self failWithReason:CodePushBinaryPatchReasonInvalidManifest
243
+ return [self failWithReason:CodePushArchiveFallbackReasonInvalidManifest
243
244
  outFailureReason:failureReason];
244
245
  }
245
246
 
@@ -250,7 +251,7 @@ static CodePushBinaryPatchApplyResult CodePushApplyBinaryPatch(const unsigned ch
250
251
  attributes:nil
251
252
  error:&error]) {
252
253
  CPLog(@"Unable to create the binary patch working directory at %@: %@", workingFolderPath, error);
253
- return [self failWithReason:CodePushBinaryPatchReasonPatchApplyFailed
254
+ return [self failWithReason:CodePushArchiveFallbackReasonPatchApplyFailed
254
255
  outFailureReason:failureReason];
255
256
  }
256
257
 
@@ -296,13 +297,13 @@ static CodePushBinaryPatchApplyResult CodePushApplyBinaryPatch(const unsigned ch
296
297
  error:nil][NSFileSystemFreeSize];
297
298
  if (freeSize != nil && freeSize.longLongValue < targetBundleSize) {
298
299
  CPLog(@"Not enough free space to restore a %lld byte bundle.", targetBundleSize);
299
- return CodePushBinaryPatchReasonPatchApplyFailed;
300
+ return CodePushArchiveFallbackReasonPatchApplyFailed;
300
301
  }
301
302
 
302
303
  NSString *baseBundlePath = [baseBundleURL path];
303
304
  if (baseBundlePath == nil) {
304
305
  CPLog(@"The app binary carries no JS bundle to patch against.");
305
- return CodePushBinaryPatchReasonBaseBundleUnavailable;
306
+ return CodePushArchiveFallbackReasonBaseBundleUnavailable;
306
307
  }
307
308
 
308
309
  NSError *error = nil;
@@ -318,10 +319,10 @@ static CodePushBinaryPatchApplyResult CodePushApplyBinaryPatch(const unsigned ch
318
319
  error:&error];
319
320
  if (baseBundle == nil) {
320
321
  CPLog(@"Unable to read the JS bundle inside the app binary: %@", error);
321
- return CodePushBinaryPatchReasonBaseBundleUnavailable;
322
+ return CodePushArchiveFallbackReasonBaseBundleUnavailable;
322
323
  }
323
324
  if (![baseBundleHash isEqualToString:[CodePushUpdateUtils computeHashForData:baseBundle]]) {
324
- return CodePushBinaryPatchReasonBaseHashMismatch;
325
+ return CodePushArchiveFallbackReasonBaseHashMismatch;
325
326
  }
326
327
 
327
328
  __attribute__((objc_precise_lifetime)) NSData *patch = [NSData dataWithContentsOfFile:patchFilePath
@@ -329,7 +330,7 @@ static CodePushBinaryPatchApplyResult CodePushApplyBinaryPatch(const unsigned ch
329
330
  error:&error];
330
331
  if (patch == nil) {
331
332
  CPLog(@"Unable to read the binary patch: %@", error);
332
- return CodePushBinaryPatchReasonPatchApplyFailed;
333
+ return CodePushArchiveFallbackReasonPatchApplyFailed;
333
334
  }
334
335
 
335
336
  NSString *restoredBundleFilePath = [workingFolderPath stringByAppendingPathComponent:BinaryPatchTargetFileName];
@@ -342,20 +343,20 @@ static CodePushBinaryPatchApplyResult CodePushApplyBinaryPatch(const unsigned ch
342
343
  if (applyResult != CodePushBinaryPatchApplyResultOK) {
343
344
  CPLog(@"The binary patch applier returned %ld.", (long)applyResult);
344
345
  return applyResult == CodePushBinaryPatchApplyResultUnsupportedCompression
345
- ? CodePushBinaryPatchReasonUnsupportedFormat
346
- : CodePushBinaryPatchReasonPatchApplyFailed;
346
+ ? CodePushArchiveFallbackReasonUnsupportedFormat
347
+ : CodePushArchiveFallbackReasonPatchApplyFailed;
347
348
  }
348
349
 
349
350
  NSDictionary *restoredBundleAttributes = [fileManager attributesOfItemAtPath:restoredBundleFilePath
350
351
  error:&error];
351
352
  if (restoredBundleAttributes == nil || (long long)[restoredBundleAttributes fileSize] != targetBundleSize) {
352
353
  CPLog(@"The restored bundle is not %lld bytes long: %@", targetBundleSize, error);
353
- return CodePushBinaryPatchReasonTargetVerificationFailed;
354
+ return CodePushArchiveFallbackReasonTargetVerificationFailed;
354
355
  }
355
356
 
356
357
  NSString *restoredBundleHash = [CodePushUpdateUtils computeHashForFileAtPath:restoredBundleFilePath];
357
358
  if (restoredBundleHash == nil || ![targetBundleHash isEqualToString:restoredBundleHash]) {
358
- return CodePushBinaryPatchReasonTargetVerificationFailed;
359
+ return CodePushArchiveFallbackReasonTargetVerificationFailed;
359
360
  }
360
361
 
361
362
  if (![self moveItemAtPath:restoredBundleFilePath toPath:targetBundleFilePath]
@@ -365,7 +366,7 @@ static CodePushBinaryPatchApplyResult CodePushApplyBinaryPatch(const unsigned ch
365
366
  // that follows empties the unzipped archive before it replaces it, which is what
366
367
  // clears them.
367
368
  CPLog(@"Unable to put the restored bundle in place of the patch: %@", error);
368
- return CodePushBinaryPatchReasonPatchApplyFailed;
369
+ return CodePushArchiveFallbackReasonPatchApplyFailed;
369
370
  }
370
371
 
371
372
  return nil;
@@ -54,7 +54,8 @@ failCallback:(void (^)(NSError *err))failCallback {
54
54
  if (statusCode >= 400) {
55
55
  [self.outputFileStream close];
56
56
  [connection cancel];
57
- NSError *err = [CodePushErrorUtils errorWithMessage:[NSString stringWithFormat: @"Received %ld response from %@", (long)statusCode, self.downloadUrl]];
57
+ NSError *err = [CodePushErrorUtils errorWithMessage:[NSString stringWithFormat: @"Received %ld response from %@", (long)statusCode, self.downloadUrl]
58
+ httpStatusCode:statusCode];
58
59
  self.failCallback(err);
59
60
  return;
60
61
  }