@hot-updater/react-native 1.0.0-rc.14 → 1.0.0-rc.15

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 (181) hide show
  1. package/android/proguard-rules.pro +2 -2
  2. package/android/src/main/java/com/hotupdater/BuiltInAssetResolver.kt +286 -0
  3. package/android/src/main/java/com/hotupdater/BundleFileStorageService.kt +650 -636
  4. package/android/src/main/java/com/hotupdater/BundleMetadata.kt +3 -0
  5. package/android/src/main/java/com/hotupdater/HotUpdaterException.kt +0 -13
  6. package/android/src/main/java/com/hotupdater/HotUpdaterImpl.kt +10 -14
  7. package/android/src/main/java/com/hotupdater/OkHttpDownloadService.kt +19 -0
  8. package/android/src/main/java/com/hotupdater/TarArchiveInputStream.kt +214 -375
  9. package/android/src/main/java/com/hotupdater/TarBrArchiveExtractor.kt +119 -0
  10. package/android/src/newarch/HotUpdater.kt +7 -9
  11. package/android/src/newarch/HotUpdaterModule.kt +40 -20
  12. package/android/src/oldarch/HotUpdater.kt +7 -9
  13. package/android/src/oldarch/HotUpdaterModule.kt +40 -20
  14. package/android/src/test/java/com/hotupdater/BundleFileStorageServiceTest.kt +1126 -74
  15. package/android/src/test/java/com/hotupdater/HotUpdaterImplTest.kt +4 -5
  16. package/android/src/test/java/com/hotupdater/TarBrArchiveExtractorTest.kt +206 -0
  17. package/ios/HotUpdater/Internal/BrotliFileDecompressor.swift +167 -0
  18. package/ios/HotUpdater/Internal/BuiltInAssetResolver.swift +196 -0
  19. package/ios/HotUpdater/Internal/BundleFileStorageService.swift +773 -1016
  20. package/ios/HotUpdater/Internal/BundleMetadata.swift +5 -0
  21. package/ios/HotUpdater/Internal/FileUtilities.swift +64 -0
  22. package/ios/HotUpdater/Internal/HotUpdater-Bridging-Header.h +0 -1
  23. package/ios/HotUpdater/Internal/HotUpdater.mm +5 -27
  24. package/ios/HotUpdater/Internal/HotUpdaterImpl.swift +47 -31
  25. package/ios/HotUpdater/Internal/NotificationExtension.swift +0 -2
  26. package/ios/HotUpdater/Internal/TarArchiveExtractor.swift +182 -311
  27. package/ios/HotUpdater/Internal/URLSessionDownloadService.swift +13 -72
  28. package/lib/commonjs/checkForUpdate.js +7 -10
  29. package/lib/commonjs/checkForUpdate.js.map +1 -1
  30. package/lib/commonjs/checkForUpdate.releaseCatalog.spec.js +9 -4
  31. package/lib/commonjs/checkForUpdate.releaseCatalog.spec.js.map +1 -1
  32. package/lib/commonjs/error.js +4 -13
  33. package/lib/commonjs/error.js.map +1 -1
  34. package/lib/commonjs/fetchJSON.js +9 -2
  35. package/lib/commonjs/fetchJSON.js.map +1 -1
  36. package/lib/commonjs/httpClient.js +44 -27
  37. package/lib/commonjs/httpClient.js.map +1 -1
  38. package/lib/commonjs/httpClient.spec.js +57 -5
  39. package/lib/commonjs/httpClient.spec.js.map +1 -1
  40. package/lib/commonjs/index.js +2 -6
  41. package/lib/commonjs/index.js.map +1 -1
  42. package/lib/commonjs/native.js +7 -8
  43. package/lib/commonjs/native.js.map +1 -1
  44. package/lib/commonjs/native.spec.js +21 -16
  45. package/lib/commonjs/native.spec.js.map +1 -1
  46. package/lib/commonjs/notifyAppReadyInsights.boundary.spec.js +7 -0
  47. package/lib/commonjs/notifyAppReadyInsights.boundary.spec.js.map +1 -1
  48. package/lib/commonjs/notifyAppReadyInsights.js +4 -1
  49. package/lib/commonjs/notifyAppReadyInsights.js.map +1 -1
  50. package/lib/commonjs/notifyAppReadyInsights.spec.js +59 -0
  51. package/lib/commonjs/notifyAppReadyInsights.spec.js.map +1 -1
  52. package/lib/commonjs/notifyAppReadyInsights.transport.spec.js +7 -0
  53. package/lib/commonjs/notifyAppReadyInsights.transport.spec.js.map +1 -1
  54. package/lib/commonjs/sdkVersion.js +1 -1
  55. package/lib/commonjs/specs/NativeHotUpdater.js.map +1 -1
  56. package/lib/commonjs/store.js +3 -10
  57. package/lib/commonjs/store.js.map +1 -1
  58. package/lib/commonjs/store.notification.spec.js +12 -4
  59. package/lib/commonjs/store.notification.spec.js.map +1 -1
  60. package/lib/commonjs/store.spec.js +2 -136
  61. package/lib/commonjs/store.spec.js.map +1 -1
  62. package/lib/commonjs/wrap.js +1 -3
  63. package/lib/commonjs/wrap.js.map +1 -1
  64. package/lib/commonjs/wrap.spec.js +7 -0
  65. package/lib/commonjs/wrap.spec.js.map +1 -1
  66. package/lib/module/checkForUpdate.js +7 -10
  67. package/lib/module/checkForUpdate.js.map +1 -1
  68. package/lib/module/checkForUpdate.releaseCatalog.spec.js +9 -4
  69. package/lib/module/checkForUpdate.releaseCatalog.spec.js.map +1 -1
  70. package/lib/module/error.js +4 -13
  71. package/lib/module/error.js.map +1 -1
  72. package/lib/module/fetchJSON.js +7 -1
  73. package/lib/module/fetchJSON.js.map +1 -1
  74. package/lib/module/httpClient.js +46 -29
  75. package/lib/module/httpClient.js.map +1 -1
  76. package/lib/module/httpClient.spec.js +57 -5
  77. package/lib/module/httpClient.spec.js.map +1 -1
  78. package/lib/module/index.js +2 -6
  79. package/lib/module/index.js.map +1 -1
  80. package/lib/module/native.js +7 -8
  81. package/lib/module/native.js.map +1 -1
  82. package/lib/module/native.spec.js +21 -16
  83. package/lib/module/native.spec.js.map +1 -1
  84. package/lib/module/notifyAppReadyInsights.boundary.spec.js +7 -0
  85. package/lib/module/notifyAppReadyInsights.boundary.spec.js.map +1 -1
  86. package/lib/module/notifyAppReadyInsights.js +5 -2
  87. package/lib/module/notifyAppReadyInsights.js.map +1 -1
  88. package/lib/module/notifyAppReadyInsights.spec.js +59 -0
  89. package/lib/module/notifyAppReadyInsights.spec.js.map +1 -1
  90. package/lib/module/notifyAppReadyInsights.transport.spec.js +7 -0
  91. package/lib/module/notifyAppReadyInsights.transport.spec.js.map +1 -1
  92. package/lib/module/sdkVersion.js +1 -1
  93. package/lib/module/specs/NativeHotUpdater.js.map +1 -1
  94. package/lib/module/store.js +3 -10
  95. package/lib/module/store.js.map +1 -1
  96. package/lib/module/store.notification.spec.js +12 -4
  97. package/lib/module/store.notification.spec.js.map +1 -1
  98. package/lib/module/store.spec.js +2 -136
  99. package/lib/module/store.spec.js.map +1 -1
  100. package/lib/module/wrap.js +1 -3
  101. package/lib/module/wrap.js.map +1 -1
  102. package/lib/module/wrap.spec.js +7 -0
  103. package/lib/module/wrap.spec.js.map +1 -1
  104. package/lib/typescript/commonjs/checkForUpdate.d.ts +1 -2
  105. package/lib/typescript/commonjs/checkForUpdate.d.ts.map +1 -1
  106. package/lib/typescript/commonjs/error.d.ts +4 -13
  107. package/lib/typescript/commonjs/error.d.ts.map +1 -1
  108. package/lib/typescript/commonjs/fetchJSON.d.ts +4 -0
  109. package/lib/typescript/commonjs/fetchJSON.d.ts.map +1 -1
  110. package/lib/typescript/commonjs/httpClient.d.ts.map +1 -1
  111. package/lib/typescript/commonjs/index.d.ts +0 -2
  112. package/lib/typescript/commonjs/index.d.ts.map +1 -1
  113. package/lib/typescript/commonjs/native.d.ts +1 -6
  114. package/lib/typescript/commonjs/native.d.ts.map +1 -1
  115. package/lib/typescript/commonjs/notifyAppReadyInsights.d.ts +1 -1
  116. package/lib/typescript/commonjs/notifyAppReadyInsights.d.ts.map +1 -1
  117. package/lib/typescript/commonjs/sdkVersion.d.ts +1 -1
  118. package/lib/typescript/commonjs/specs/NativeHotUpdater.d.ts +8 -35
  119. package/lib/typescript/commonjs/specs/NativeHotUpdater.d.ts.map +1 -1
  120. package/lib/typescript/commonjs/store.d.ts +1 -3
  121. package/lib/typescript/commonjs/store.d.ts.map +1 -1
  122. package/lib/typescript/commonjs/wrap.d.ts.map +1 -1
  123. package/lib/typescript/commonjs/wrap.types.d.ts +0 -2
  124. package/lib/typescript/commonjs/wrap.types.d.ts.map +1 -1
  125. package/lib/typescript/module/checkForUpdate.d.ts +1 -2
  126. package/lib/typescript/module/checkForUpdate.d.ts.map +1 -1
  127. package/lib/typescript/module/error.d.ts +4 -13
  128. package/lib/typescript/module/error.d.ts.map +1 -1
  129. package/lib/typescript/module/fetchJSON.d.ts +4 -0
  130. package/lib/typescript/module/fetchJSON.d.ts.map +1 -1
  131. package/lib/typescript/module/httpClient.d.ts.map +1 -1
  132. package/lib/typescript/module/index.d.ts +0 -2
  133. package/lib/typescript/module/index.d.ts.map +1 -1
  134. package/lib/typescript/module/native.d.ts +1 -6
  135. package/lib/typescript/module/native.d.ts.map +1 -1
  136. package/lib/typescript/module/notifyAppReadyInsights.d.ts +1 -1
  137. package/lib/typescript/module/notifyAppReadyInsights.d.ts.map +1 -1
  138. package/lib/typescript/module/sdkVersion.d.ts +1 -1
  139. package/lib/typescript/module/specs/NativeHotUpdater.d.ts +8 -35
  140. package/lib/typescript/module/specs/NativeHotUpdater.d.ts.map +1 -1
  141. package/lib/typescript/module/store.d.ts +1 -3
  142. package/lib/typescript/module/store.d.ts.map +1 -1
  143. package/lib/typescript/module/wrap.d.ts.map +1 -1
  144. package/lib/typescript/module/wrap.types.d.ts +0 -2
  145. package/lib/typescript/module/wrap.types.d.ts.map +1 -1
  146. package/package.json +3 -3
  147. package/src/checkForUpdate.releaseCatalog.spec.ts +10 -4
  148. package/src/checkForUpdate.ts +5 -11
  149. package/src/error.ts +4 -14
  150. package/src/fetchJSON.ts +12 -1
  151. package/src/httpClient.spec.ts +70 -5
  152. package/src/httpClient.ts +67 -53
  153. package/src/index.ts +1 -5
  154. package/src/native.spec.ts +23 -16
  155. package/src/native.ts +12 -24
  156. package/src/notifyAppReadyInsights.boundary.spec.ts +8 -0
  157. package/src/notifyAppReadyInsights.spec.ts +60 -0
  158. package/src/notifyAppReadyInsights.transport.spec.ts +8 -0
  159. package/src/notifyAppReadyInsights.ts +13 -1
  160. package/src/sdkVersion.ts +1 -1
  161. package/src/specs/NativeHotUpdater.ts +8 -36
  162. package/src/store.notification.spec.ts +13 -3
  163. package/src/store.spec.ts +0 -157
  164. package/src/store.ts +2 -23
  165. package/src/wrap.spec.ts +8 -0
  166. package/src/wrap.tsx +0 -2
  167. package/src/wrap.types.ts +0 -2
  168. package/android/src/main/java/com/hotupdater/DecompressService.kt +0 -89
  169. package/android/src/main/java/com/hotupdater/DecompressionStrategy.kt +0 -26
  170. package/android/src/main/java/com/hotupdater/TarBrDecompressionStrategy.kt +0 -117
  171. package/android/src/main/java/com/hotupdater/TarGzDecompressionStrategy.kt +0 -117
  172. package/android/src/main/java/com/hotupdater/ZipDecompressionStrategy.kt +0 -192
  173. package/android/src/test/java/com/hotupdater/TarArchiveInputStreamTest.kt +0 -68
  174. package/ios/HotUpdater/Internal/ArchiveExtractionUtilities.swift +0 -178
  175. package/ios/HotUpdater/Internal/DecompressService.swift +0 -112
  176. package/ios/HotUpdater/Internal/DecompressionStrategy.swift +0 -22
  177. package/ios/HotUpdater/Internal/StreamingTarArchiveExtractor.swift +0 -370
  178. package/ios/HotUpdater/Internal/TarBrDecompressionStrategy.swift +0 -39
  179. package/ios/HotUpdater/Internal/TarGzDecompressionStrategy.swift +0 -59
  180. package/ios/HotUpdater/Internal/ZipArchiveExtractor.swift +0 -482
  181. package/ios/HotUpdater/Internal/ZipDecompressionStrategy.swift +0 -56
package/src/native.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import {
2
- type ChangedAsset,
2
+ type ArtifactAsset,
3
3
  type CatalogHighWater,
4
4
  INVALID_COHORT_ERROR_MESSAGE,
5
5
  isValidCohort,
@@ -234,7 +234,7 @@ const getReloadProcess = (): (() => Promise<void>) | null => {
234
234
  : null;
235
235
  };
236
236
 
237
- export type HotUpdaterProgressArtifactType = "archive" | "diff";
237
+ export type HotUpdaterProgressArtifactType = "diff";
238
238
 
239
239
  export type HotUpdaterDiffFileStatus =
240
240
  | "pending"
@@ -286,18 +286,11 @@ export interface HotUpdaterDiffProgressDetails {
286
286
  files: HotUpdaterDiffFileSnapshot[];
287
287
  }
288
288
 
289
- export type HotUpdaterProgressEvent =
290
- | {
291
- progress: number;
292
- artifactType: "archive";
293
- downloadedBytes?: number;
294
- totalBytes?: number;
295
- }
296
- | {
297
- progress: number;
298
- artifactType: "diff";
299
- details: HotUpdaterDiffProgressDetails;
300
- };
289
+ export type HotUpdaterProgressEvent = {
290
+ progress: number;
291
+ artifactType: "diff";
292
+ details: HotUpdaterDiffProgressDetails;
293
+ };
301
294
 
302
295
  export type HotUpdaterEvent = {
303
296
  onProgress: HotUpdaterProgressEvent;
@@ -434,8 +427,6 @@ export const commitReleaseSelection = async (input: {
434
427
  export async function updateBundle(params: UpdateParams): Promise<boolean> {
435
428
  const updateBundleId = params.bundleId;
436
429
  const status = params.status;
437
- const targetFileUrl = params.fileUrl;
438
-
439
430
  const currentBundleId = status === "UPDATE" ? getFreshBundleId() : undefined;
440
431
 
441
432
  // If native is still on the same bundle we installed in this session,
@@ -467,11 +458,10 @@ export async function updateBundle(params: UpdateParams): Promise<boolean> {
467
458
  const existing = sessionState.getInflightUpdate(updateBundleId);
468
459
  if (existing) return existing;
469
460
 
470
- const targetFileHash = params.fileHash;
471
461
  const targetChannel = params.channel;
472
462
  const targetManifestUrl = params.manifestUrl;
473
463
  const targetManifestFileHash = params.manifestFileHash;
474
- const targetChangedAssets = params.changedAssets;
464
+ const targetAssets = params.assets;
475
465
 
476
466
  const promise = (async () => {
477
467
  try {
@@ -479,12 +469,10 @@ export async function updateBundle(params: UpdateParams): Promise<boolean> {
479
469
  const ok = await HotUpdaterNative.updateBundle({
480
470
  bundleId: updateBundleId,
481
471
  channel: targetChannel,
482
- changedAssets:
483
- (targetChangedAssets as Record<string, ChangedAsset> | null) ?? null,
484
- fileUrl: targetFileUrl,
485
- fileHash: targetFileHash ?? null,
486
- manifestFileHash: targetManifestFileHash ?? null,
487
- manifestUrl: targetManifestUrl ?? null,
472
+ assets: targetAssets as Record<string, ArtifactAsset>,
473
+ manifestFileHash: targetManifestFileHash,
474
+ manifestUrl: targetManifestUrl,
475
+ ...(params.archiveUrl ? { archiveUrl: params.archiveUrl } : {}),
488
476
  ...(selection === undefined ? {} : { selection }),
489
477
  });
490
478
  if (ok) {
@@ -1,6 +1,7 @@
1
1
  import { beforeEach, describe, expect, it, vi } from "vitest";
2
2
 
3
3
  import type {
4
+ ActiveUpdateState,
4
5
  NotifyAppReadyInsightsEvent,
5
6
  NotifyAppReadyResult,
6
7
  } from "./native";
@@ -42,6 +43,7 @@ const mocks = vi.hoisted(() => ({
42
43
  getAppVersion: vi.fn<() => string | undefined>(() => "1.0.0"),
43
44
  getBundleId: vi.fn<() => string | undefined>(() => "bundle-id"),
44
45
  getChannel: vi.fn(() => "production"),
46
+ getActiveUpdateState: vi.fn<() => ActiveUpdateState>(),
45
47
  getCohort: vi.fn(() => "123"),
46
48
  getFingerprintHash: vi.fn(() => "fingerprint-hash"),
47
49
  getInstallId: vi.fn<() => string | undefined>(() => "install-id"),
@@ -65,6 +67,7 @@ vi.mock("./native", () => ({
65
67
  getAppVersion: mocks.getAppVersion,
66
68
  getBundleId: mocks.getBundleId,
67
69
  getChannel: mocks.getChannel,
70
+ getActiveUpdateState: mocks.getActiveUpdateState,
68
71
  getCohort: mocks.getCohort,
69
72
  getFingerprintHash: mocks.getFingerprintHash,
70
73
  getInstallId: mocks.getInstallId,
@@ -106,6 +109,11 @@ describe("automatic notifyAppReady insights boundaries", () => {
106
109
  mocks.getAppVersion.mockReturnValue("1.0.0");
107
110
  mocks.getBundleId.mockReturnValue("bundle-id");
108
111
  mocks.getChannel.mockReturnValue("production");
112
+ mocks.getActiveUpdateState.mockReturnValue({
113
+ activeSelection: null,
114
+ stableSelection: null,
115
+ verificationPending: false,
116
+ });
109
117
  mocks.getCohort.mockReturnValue("123");
110
118
  mocks.getFingerprintHash.mockReturnValue("fingerprint-hash");
111
119
  mocks.getInstallId.mockReturnValue("install-id");
@@ -1,6 +1,7 @@
1
1
  import { beforeEach, describe, expect, it, vi } from "vitest";
2
2
 
3
3
  import type {
4
+ ActiveUpdateState,
4
5
  NotifyAppReadyInsightsEvent,
5
6
  NotifyAppReadyResult,
6
7
  } from "./native";
@@ -19,6 +20,7 @@ const mocks = vi.hoisted(() => ({
19
20
  getAppVersion: vi.fn(() => "1.0.0"),
20
21
  getBundleId: vi.fn(() => "bundle-id"),
21
22
  getChannel: vi.fn(() => "production"),
23
+ getActiveUpdateState: vi.fn<() => ActiveUpdateState>(),
22
24
  getCohort: vi.fn(() => "123"),
23
25
  getFingerprintHash: vi.fn(() => "fingerprint-hash"),
24
26
  getInstallId: vi.fn(() => "install-id"),
@@ -42,6 +44,7 @@ vi.mock("./native", () => ({
42
44
  getAppVersion: mocks.getAppVersion,
43
45
  getBundleId: mocks.getBundleId,
44
46
  getChannel: mocks.getChannel,
47
+ getActiveUpdateState: mocks.getActiveUpdateState,
45
48
  getCohort: mocks.getCohort,
46
49
  getFingerprintHash: mocks.getFingerprintHash,
47
50
  getInstallId: mocks.getInstallId,
@@ -78,6 +81,11 @@ describe("automatic notifyAppReady insights", () => {
78
81
  mocks.addListener.mockReturnValue(() => {});
79
82
  mocks.getBundleId.mockReturnValue("bundle-id");
80
83
  mocks.getChannel.mockReturnValue("production");
84
+ mocks.getActiveUpdateState.mockReturnValue({
85
+ activeSelection: null,
86
+ stableSelection: null,
87
+ verificationPending: false,
88
+ });
81
89
  mocks.getCohort.mockReturnValue("123");
82
90
  mocks.getFingerprintHash.mockReturnValue("fingerprint-hash");
83
91
  mocks.getInstallId.mockReturnValue("install-id");
@@ -85,6 +93,58 @@ describe("automatic notifyAppReady insights", () => {
85
93
  mocks.readNotifyAppReady.mockReturnValue(createNotifyReadResult());
86
94
  });
87
95
 
96
+ it.each([
97
+ {
98
+ label: "running selection",
99
+ pending: false,
100
+ channel: "production",
101
+ expected: "release-running",
102
+ },
103
+ {
104
+ label: "staged selection",
105
+ pending: true,
106
+ channel: "production",
107
+ expected: "release-running",
108
+ },
109
+ {
110
+ label: "another channel",
111
+ pending: false,
112
+ channel: "preview",
113
+ expected: null,
114
+ },
115
+ ])(
116
+ "attributes an unchanged launch with $label",
117
+ async ({ pending, channel, expected }) => {
118
+ const running = {
119
+ kind: "BUNDLE" as const,
120
+ releaseId: "release-running",
121
+ bundleId: "bundle-id",
122
+ channel,
123
+ };
124
+ mocks.getActiveUpdateState.mockReturnValue({
125
+ activeSelection: pending
126
+ ? {
127
+ ...running,
128
+ releaseId: "release-pending",
129
+ bundleId: "bundle-pending",
130
+ }
131
+ : running,
132
+ stableSelection: pending ? running : null,
133
+ verificationPending: pending,
134
+ });
135
+ const { reportNoChange } = await import("./notifyAppReadyInsights");
136
+ const { client, sendInsightsEvent } = createClient();
137
+ await reportNoChange({ client, insights: true });
138
+ expect(sendInsightsEvent).toHaveBeenCalledWith(
139
+ expect.objectContaining({
140
+ type: "UNCHANGED",
141
+ toBundleId: "bundle-id",
142
+ toReleaseId: expected,
143
+ }),
144
+ );
145
+ },
146
+ );
147
+
88
148
  it("orders download after in-flight startup reporting and suppresses a later no-change report", async () => {
89
149
  const { handleNotifyAppReady, reportBundleDownloaded, reportNoChange } =
90
150
  await import("./notifyAppReadyInsights");
@@ -1,6 +1,7 @@
1
1
  import { beforeEach, describe, expect, it, vi } from "vitest";
2
2
 
3
3
  import type {
4
+ ActiveUpdateState,
4
5
  NotifyAppReadyInsightsEvent,
5
6
  NotifyAppReadyResult,
6
7
  } from "./native";
@@ -30,6 +31,7 @@ const mocks = vi.hoisted(() => ({
30
31
  getAppVersion: vi.fn(() => "1.0.0"),
31
32
  getBundleId: vi.fn(() => "bundle-id"),
32
33
  getChannel: vi.fn(() => "production"),
34
+ getActiveUpdateState: vi.fn<() => ActiveUpdateState>(),
33
35
  getCohort: vi.fn(() => "123"),
34
36
  getFingerprintHash: vi.fn(() => "fingerprint-hash"),
35
37
  getInstallId: vi.fn(() => "install-id"),
@@ -53,6 +55,7 @@ vi.mock("./native", () => ({
53
55
  getAppVersion: mocks.getAppVersion,
54
56
  getBundleId: mocks.getBundleId,
55
57
  getChannel: mocks.getChannel,
58
+ getActiveUpdateState: mocks.getActiveUpdateState,
56
59
  getCohort: mocks.getCohort,
57
60
  getFingerprintHash: mocks.getFingerprintHash,
58
61
  getInstallId: mocks.getInstallId,
@@ -75,6 +78,11 @@ describe("automatic notifyAppReady transport failures", () => {
75
78
  mocks.getAppVersion.mockReturnValue("1.0.0");
76
79
  mocks.getBundleId.mockReturnValue("bundle-id");
77
80
  mocks.getChannel.mockReturnValue("production");
81
+ mocks.getActiveUpdateState.mockReturnValue({
82
+ activeSelection: null,
83
+ stableSelection: null,
84
+ verificationPending: false,
85
+ });
78
86
  mocks.getCohort.mockReturnValue("123");
79
87
  mocks.getFingerprintHash.mockReturnValue("fingerprint-hash");
80
88
  mocks.getInstallId.mockReturnValue("install-id");
@@ -3,6 +3,7 @@ import { Platform } from "react-native";
3
3
  import type { HotUpdaterError } from "./error";
4
4
  import type { InsightsEventParams, HotUpdaterHttpClient } from "./httpClient";
5
5
  import {
6
+ getActiveUpdateState,
6
7
  getAppVersion,
7
8
  getBundleId,
8
9
  getChannel,
@@ -101,10 +102,21 @@ const buildNotifyAppReadyInsightsParams = (
101
102
  );
102
103
  }
103
104
 
105
+ const state = getActiveUpdateState();
106
+ const runningSelection = [
107
+ state.activeSelection,
108
+ state.stableSelection,
109
+ ].find(
110
+ (selection) =>
111
+ selection?.bundleId === bundleId &&
112
+ selection.channel === commonParams.channel,
113
+ );
114
+
104
115
  return {
105
116
  ...commonParams,
106
117
  fromBundleId: null,
107
118
  toBundleId: bundleId,
119
+ toReleaseId: runningSelection?.releaseId ?? null,
108
120
  type: "UNCHANGED",
109
121
  updateStrategy: null,
110
122
  };
@@ -237,7 +249,7 @@ export const reportNoChange = async (
237
249
 
238
250
  let lastDownloadedSelection: string | undefined;
239
251
 
240
- /** Report only after the archive/diff has been verified and staged successfully. */
252
+ /** Report only after the manifest update has been verified and staged successfully. */
241
253
  export const reportBundleDownloaded = async (
242
254
  options: Pick<
243
255
  NotifyAppReadyOptions,
package/src/sdkVersion.ts CHANGED
@@ -1 +1 @@
1
- export const HOT_UPDATER_SDK_VERSION = "1.0.0-rc.14";
1
+ export const HOT_UPDATER_SDK_VERSION = "1.0.0-rc.15";
@@ -2,48 +2,21 @@ import type { TurboModule } from "react-native";
2
2
  import { TurboModuleRegistry } from "react-native";
3
3
  import type { UnsafeObject } from "react-native/Libraries/Types/CodegenTypes";
4
4
 
5
- export interface ChangedAsset {
6
- file?: {
7
- compression?: "br" | null;
8
- url: string;
9
- } | null;
10
- fileHash: string;
11
- patch?: {
12
- algorithm: "bsdiff";
13
- baseBundleId: string;
14
- baseFileHash: string;
15
- patchFileHash: string;
16
- patchUrl: string;
17
- } | null;
18
- }
19
-
20
5
  export interface UpdateBundleParams {
21
6
  bundleId: string;
22
7
  channel?: string;
23
- fileUrl: string | null;
24
- /**
25
- * File hash for integrity/signature verification.
26
- *
27
- * Format depends on signing configuration:
28
- * - Signed: `sig:<base64_signature>` - Native will verify signature (and implicitly hash)
29
- * - Unsigned: `<hex_hash>` - Native will verify SHA256 hash only
30
- *
31
- * Native determines verification mode by checking for "sig:" prefix.
32
- */
33
- fileHash: string | null;
34
8
  /**
35
- * Optional signed manifest URL for manifest-driven installation.
9
+ * Signed manifest URL for installation.
36
10
  */
37
- manifestUrl?: string | null;
11
+ manifestUrl: string;
38
12
  /**
39
13
  * File hash/signature for the manifest file itself.
40
14
  */
41
- manifestFileHash?: string | null;
42
- /**
43
- * Per-file URLs for assets that must be downloaded instead of reused from
44
- * the currently active bundle.
45
- */
46
- changedAssets?: UnsafeObject | null;
15
+ manifestFileHash: string;
16
+ /** Optional tar.br URL; integrity and sizes come from the verified manifest. */
17
+ archiveUrl?: string | null;
18
+ /** Full protocol v1 target file descriptor map. */
19
+ assets: UnsafeObject;
47
20
  /** Full Release Catalog selection receipt committed with the staged Bundle. */
48
21
  selection?: UnsafeObject | null;
49
22
  }
@@ -66,13 +39,12 @@ export interface Spec extends TurboModule {
66
39
  *
67
40
  * Parameter validation:
68
41
  * - MISSING_BUNDLE_ID: Missing or empty bundleId
69
- * - INVALID_FILE_URL: Invalid fileUrl provided
42
+ * - INVALID_FILE_URL: Invalid manifest or asset URL provided
70
43
  *
71
44
  * Bundle storage:
72
45
  * - DIRECTORY_CREATION_FAILED: Failed to create bundle directory
73
46
  * - DOWNLOAD_FAILED: Failed to download bundle
74
47
  * - INCOMPLETE_DOWNLOAD: Download incomplete (size mismatch)
75
- * - EXTRACTION_FORMAT_ERROR: Invalid or corrupted archive format
76
48
  * - INVALID_BUNDLE: Bundle missing required platform files
77
49
  * - INSUFFICIENT_DISK_SPACE: Insufficient disk space
78
50
  * - MOVE_OPERATION_FAILED: Failed to move bundle files
@@ -61,10 +61,20 @@ describe("hotUpdaterStore notification scheduling", () => {
61
61
 
62
62
  const nextProgress = Math.min(1, progress + 0.01);
63
63
  emitProgress?.({
64
- artifactType: "archive",
65
- downloadedBytes: nextProgress * 1_000_000,
64
+ artifactType: "diff",
65
+ details: {
66
+ completedFilesCount: nextProgress === 1 ? 1 : 0,
67
+ files: [
68
+ {
69
+ order: 0,
70
+ path: "index.ios.bundle",
71
+ progress: nextProgress,
72
+ status: nextProgress === 1 ? "downloaded" : "downloading",
73
+ },
74
+ ],
75
+ totalFilesCount: 1,
76
+ },
66
77
  progress: nextProgress,
67
- totalBytes: 1_000_000,
68
78
  });
69
79
  }, [progress]);
70
80
 
package/src/store.spec.ts CHANGED
@@ -41,75 +41,6 @@ describe("hotUpdaterStore", () => {
41
41
  unsubscribe();
42
42
  });
43
43
 
44
- it("notifies subscribers only when progress events change the snapshot", async () => {
45
- const store = await importStore();
46
- const listener = vi.fn();
47
- const emitProgress = listeners.get("onProgress");
48
-
49
- expect(emitProgress).toBeTypeOf("function");
50
-
51
- const unsubscribe = store.subscribe(listener);
52
-
53
- emitProgress?.({
54
- artifactType: "archive",
55
- progress: 0.5,
56
- });
57
- emitProgress?.({
58
- artifactType: "archive",
59
- progress: 0.5,
60
- });
61
- emitProgress?.({
62
- artifactType: "archive",
63
- progress: 1,
64
- });
65
- store.setState({ isUpdateDownloaded: true });
66
-
67
- expect(listener).toHaveBeenCalledTimes(2);
68
- expect(store.getSnapshot()).toEqual({
69
- artifactType: "archive",
70
- details: null,
71
- downloadedBytes: undefined,
72
- isUpdateDownloaded: true,
73
- progress: 1,
74
- totalBytes: undefined,
75
- });
76
-
77
- unsubscribe();
78
- });
79
-
80
- it("stores archive byte metadata and notifies when only bytes change", async () => {
81
- const store = await importStore();
82
- const listener = vi.fn();
83
- const emitProgress = listeners.get("onProgress");
84
-
85
- const unsubscribe = store.subscribe(listener);
86
-
87
- emitProgress?.({
88
- artifactType: "archive",
89
- downloadedBytes: 100.4,
90
- progress: 0.5,
91
- totalBytes: 1_000,
92
- });
93
- emitProgress?.({
94
- artifactType: "archive",
95
- downloadedBytes: 250,
96
- progress: 0.5,
97
- totalBytes: 1_000,
98
- });
99
-
100
- expect(listener).toHaveBeenCalledTimes(2);
101
- expect(store.getSnapshot()).toEqual({
102
- artifactType: "archive",
103
- details: null,
104
- downloadedBytes: 250,
105
- isUpdateDownloaded: false,
106
- progress: 0.5,
107
- totalBytes: 1_000,
108
- });
109
-
110
- unsubscribe();
111
- });
112
-
113
44
  it("stores manifest progress metadata while keeping overall progress", async () => {
114
45
  const store = await importStore();
115
46
  const emitProgress = listeners.get("onProgress");
@@ -163,10 +94,8 @@ describe("hotUpdaterStore", () => {
163
94
  ],
164
95
  totalFilesCount: 2,
165
96
  },
166
- downloadedBytes: undefined,
167
97
  isUpdateDownloaded: false,
168
98
  progress: 0.42,
169
- totalBytes: undefined,
170
99
  });
171
100
  });
172
101
 
@@ -279,94 +208,8 @@ describe("hotUpdaterStore", () => {
279
208
  ],
280
209
  totalFilesCount: 2,
281
210
  },
282
- downloadedBytes: undefined,
283
211
  isUpdateDownloaded: false,
284
212
  progress: 0.6,
285
- totalBytes: undefined,
286
- });
287
- });
288
-
289
- it("clears manifest metadata when archive progress events arrive", async () => {
290
- const store = await importStore();
291
- const emitProgress = listeners.get("onProgress");
292
-
293
- emitProgress?.({
294
- artifactType: "diff",
295
- details: {
296
- completedFilesCount: 2,
297
- files: [
298
- {
299
- order: 0,
300
- path: "index.ios.bundle",
301
- progress: 1,
302
- status: "downloaded",
303
- },
304
- {
305
- order: 1,
306
- path: "assets/logo.png",
307
- progress: 1,
308
- status: "downloaded",
309
- },
310
- {
311
- order: 2,
312
- path: "assets/bg.png",
313
- progress: 0,
314
- status: "pending",
315
- },
316
- ],
317
- totalFilesCount: 3,
318
- },
319
- progress: 0.9,
320
- });
321
-
322
- emitProgress?.({
323
- artifactType: "archive",
324
- progress: 0.25,
325
- });
326
-
327
- expect(store.getSnapshot()).toEqual({
328
- artifactType: "archive",
329
- details: null,
330
- downloadedBytes: undefined,
331
- isUpdateDownloaded: false,
332
- progress: 0.25,
333
- totalBytes: undefined,
334
- });
335
- });
336
-
337
- it("clears stale manifest metadata even when archive events omit optional fields", async () => {
338
- const store = await importStore();
339
- const emitProgress = listeners.get("onProgress");
340
-
341
- emitProgress?.({
342
- artifactType: "diff",
343
- details: {
344
- completedFilesCount: 0,
345
- files: [
346
- {
347
- order: 0,
348
- path: "manifest.asset.png",
349
- progress: 0,
350
- status: "failed",
351
- },
352
- ],
353
- totalFilesCount: 1,
354
- },
355
- progress: 0.2,
356
- });
357
-
358
- emitProgress?.({
359
- artifactType: "archive",
360
- progress: 0.4,
361
- });
362
-
363
- expect(store.getSnapshot()).toEqual({
364
- artifactType: "archive",
365
- details: null,
366
- downloadedBytes: undefined,
367
- isUpdateDownloaded: false,
368
- progress: 0.4,
369
- totalBytes: undefined,
370
213
  });
371
214
  });
372
215
  });
package/src/store.ts CHANGED
@@ -9,10 +9,8 @@ import { addListener } from "./native";
9
9
 
10
10
  export type HotUpdaterState = {
11
11
  progress: number;
12
- downloadedBytes: number | undefined;
13
- totalBytes: number | undefined;
14
12
  isUpdateDownloaded: boolean;
15
- artifactType: "archive" | "diff" | null;
13
+ artifactType: "diff" | null;
16
14
  details: HotUpdaterDiffProgressDetails | null;
17
15
  };
18
16
 
@@ -56,8 +54,6 @@ const areDiffDetailsEqual = (
56
54
  const areStatesEqual = (left: HotUpdaterState, right: HotUpdaterState) => {
57
55
  return (
58
56
  left.progress === right.progress &&
59
- left.downloadedBytes === right.downloadedBytes &&
60
- left.totalBytes === right.totalBytes &&
61
57
  left.isUpdateDownloaded === right.isUpdateDownloaded &&
62
58
  left.artifactType === right.artifactType &&
63
59
  areDiffDetailsEqual(left.details, right.details)
@@ -78,8 +74,6 @@ const normalizeDownloadPath = (value: string | undefined) => {
78
74
  const createHotUpdaterStore = () => {
79
75
  let state: HotUpdaterState = {
80
76
  progress: 0,
81
- downloadedBytes: undefined,
82
- totalBytes: undefined,
83
77
  isUpdateDownloaded: false,
84
78
  artifactType: null,
85
79
  details: null,
@@ -165,10 +159,6 @@ const createHotUpdaterStore = () => {
165
159
  nextState.details = state.details;
166
160
  }
167
161
 
168
- if (nextState.artifactType !== "diff") {
169
- nextState.details = null;
170
- }
171
-
172
162
  if (areStatesEqual(state, nextState)) {
173
163
  return;
174
164
  }
@@ -180,19 +170,8 @@ const createHotUpdaterStore = () => {
180
170
  const applyProgressEvent = (event: HotUpdaterProgressEvent) => {
181
171
  setState({
182
172
  artifactType: event.artifactType,
183
- details:
184
- event.artifactType === "diff"
185
- ? normalizeDiffDetails(event.details)
186
- : null,
187
- downloadedBytes:
188
- event.artifactType === "archive"
189
- ? normalizeByteCount(event.downloadedBytes)
190
- : undefined,
173
+ details: normalizeDiffDetails(event.details),
191
174
  progress: event.progress,
192
- totalBytes:
193
- event.artifactType === "archive"
194
- ? normalizeByteCount(event.totalBytes)
195
- : undefined,
196
175
  });
197
176
  };
198
177
 
package/src/wrap.spec.ts CHANGED
@@ -5,6 +5,7 @@ import { createElement, type ComponentType } from "react";
5
5
  import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
6
6
 
7
7
  import type {
8
+ ActiveUpdateState,
8
9
  NotifyAppReadyInsightsEvent,
9
10
  NotifyAppReadyResult,
10
11
  } from "./native";
@@ -37,6 +38,7 @@ const mocks = vi.hoisted(() => ({
37
38
  getBundleId: vi.fn(() => "bundle-id"),
38
39
  getUpdateId: vi.fn(() => "release-id"),
39
40
  getChannel: vi.fn(() => "production"),
41
+ getActiveUpdateState: vi.fn<() => ActiveUpdateState>(),
40
42
  getCohort: vi.fn(() => "123"),
41
43
  getFingerprintHash: vi.fn(() => "fingerprint-hash"),
42
44
  getInstallId: vi.fn(() => "install-id"),
@@ -61,6 +63,7 @@ vi.mock("./native", () => ({
61
63
  getBundleId: mocks.getBundleId,
62
64
  getUpdateId: mocks.getUpdateId,
63
65
  getChannel: mocks.getChannel,
66
+ getActiveUpdateState: mocks.getActiveUpdateState,
64
67
  getCohort: mocks.getCohort,
65
68
  getFingerprintHash: mocks.getFingerprintHash,
66
69
  getInstallId: mocks.getInstallId,
@@ -99,6 +102,11 @@ describe("HotUpdater wrap initialization", () => {
99
102
  mocks.getBundleId.mockReturnValue("bundle-id");
100
103
  mocks.getUpdateId.mockReturnValue("release-id");
101
104
  mocks.getChannel.mockReturnValue("production");
105
+ mocks.getActiveUpdateState.mockReturnValue({
106
+ activeSelection: null,
107
+ stableSelection: null,
108
+ verificationPending: false,
109
+ });
102
110
  mocks.getCohort.mockReturnValue("123");
103
111
  mocks.getFingerprintHash.mockReturnValue("fingerprint-hash");
104
112
  mocks.getInstallId.mockReturnValue("install-id");
package/src/wrap.tsx CHANGED
@@ -136,11 +136,9 @@ export function wrap(
136
136
  <Fallback
137
137
  artifactType={progressState.artifactType}
138
138
  details={progressState.details}
139
- downloadedBytes={progressState.downloadedBytes}
140
139
  progress={progress}
141
140
  status={updateStatus}
142
141
  message={message}
143
- totalBytes={progressState.totalBytes}
144
142
  />
145
143
  );
146
144
  }
package/src/wrap.types.ts CHANGED
@@ -21,8 +21,6 @@ export type UpdateStatus =
21
21
  export type HotUpdaterFallbackComponentProps = {
22
22
  status: Exclude<UpdateStatus, "UPDATE_PROCESS_COMPLETED">;
23
23
  progress: number;
24
- downloadedBytes: HotUpdaterState["downloadedBytes"];
25
- totalBytes: HotUpdaterState["totalBytes"];
26
24
  message: string | null;
27
25
  artifactType: HotUpdaterState["artifactType"];
28
26
  details: HotUpdaterState["details"];