@hot-updater/react-native 1.0.0-rc.1 → 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 (227) hide show
  1. package/android/proguard-rules.pro +17 -2
  2. package/android/src/main/java/com/hotupdater/BuiltInAssetResolver.kt +286 -0
  3. package/android/src/main/java/com/hotupdater/BundleFileStorageService.kt +651 -637
  4. package/android/src/main/java/com/hotupdater/BundleMetadata.kt +10 -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/HotUpdaterRecoveryManager.kt +37 -54
  8. package/android/src/main/java/com/hotupdater/HotUpdaterRecoveryState.kt +64 -0
  9. package/android/src/main/java/com/hotupdater/OkHttpDownloadService.kt +19 -0
  10. package/android/src/main/java/com/hotupdater/TarArchiveInputStream.kt +214 -375
  11. package/android/src/main/java/com/hotupdater/TarBrArchiveExtractor.kt +119 -0
  12. package/android/src/newarch/HotUpdater.kt +7 -9
  13. package/android/src/newarch/HotUpdaterModule.kt +40 -20
  14. package/android/src/oldarch/HotUpdater.kt +7 -9
  15. package/android/src/oldarch/HotUpdaterModule.kt +40 -20
  16. package/android/src/test/java/com/hotupdater/BundleFileStorageServiceTest.kt +1126 -74
  17. package/android/src/test/java/com/hotupdater/HotUpdaterImplTest.kt +4 -5
  18. package/android/src/test/java/com/hotupdater/HotUpdaterRecoveryStateTest.kt +97 -0
  19. package/android/src/test/java/com/hotupdater/PendingCrashRecoveryTest.kt +44 -0
  20. package/android/src/test/java/com/hotupdater/TarBrArchiveExtractorTest.kt +206 -0
  21. package/ios/HotUpdater/Internal/BrotliFileDecompressor.swift +167 -0
  22. package/ios/HotUpdater/Internal/BuiltInAssetResolver.swift +196 -0
  23. package/ios/HotUpdater/Internal/BundleFileStorageService.swift +773 -1016
  24. package/ios/HotUpdater/Internal/BundleMetadata.swift +5 -0
  25. package/ios/HotUpdater/Internal/FileUtilities.swift +64 -0
  26. package/ios/HotUpdater/Internal/HotUpdater-Bridging-Header.h +0 -1
  27. package/ios/HotUpdater/Internal/HotUpdater.mm +5 -27
  28. package/ios/HotUpdater/Internal/HotUpdaterImpl.swift +149 -51
  29. package/ios/HotUpdater/Internal/NotificationExtension.swift +0 -2
  30. package/ios/HotUpdater/Internal/TarArchiveExtractor.swift +182 -311
  31. package/ios/HotUpdater/Internal/URLSessionDownloadService.swift +13 -72
  32. package/ios/HotUpdater/Recovery/HotUpdaterRecovery.h +16 -0
  33. package/ios/HotUpdater/Recovery/HotUpdaterRecovery.mm +83 -0
  34. package/ios/HotUpdater/Recovery/HotUpdaterRecoveryHooks.mm +49 -0
  35. package/lib/commonjs/checkForUpdate.js +32 -23
  36. package/lib/commonjs/checkForUpdate.js.map +1 -1
  37. package/lib/commonjs/checkForUpdate.releaseCatalog.spec.js +97 -28
  38. package/lib/commonjs/checkForUpdate.releaseCatalog.spec.js.map +1 -1
  39. package/lib/commonjs/error.js +4 -13
  40. package/lib/commonjs/error.js.map +1 -1
  41. package/lib/commonjs/fetchJSON.js +9 -2
  42. package/lib/commonjs/fetchJSON.js.map +1 -1
  43. package/lib/commonjs/httpClient.js +46 -29
  44. package/lib/commonjs/httpClient.js.map +1 -1
  45. package/lib/commonjs/httpClient.spec.js +57 -5
  46. package/lib/commonjs/httpClient.spec.js.map +1 -1
  47. package/lib/commonjs/index.js +31 -14
  48. package/lib/commonjs/index.js.map +1 -1
  49. package/lib/commonjs/index.spec.js +56 -11
  50. package/lib/commonjs/index.spec.js.map +1 -1
  51. package/lib/commonjs/native.js +15 -14
  52. package/lib/commonjs/native.js.map +1 -1
  53. package/lib/commonjs/native.spec.js +128 -22
  54. package/lib/commonjs/native.spec.js.map +1 -1
  55. package/lib/commonjs/{notifyAppReadyAnalytics.boundary.spec.js → notifyAppReadyInsights.boundary.spec.js} +28 -21
  56. package/lib/commonjs/notifyAppReadyInsights.boundary.spec.js.map +1 -0
  57. package/lib/commonjs/notifyAppReadyInsights.js +188 -0
  58. package/lib/commonjs/notifyAppReadyInsights.js.map +1 -0
  59. package/lib/commonjs/{notifyAppReadyAnalytics.spec.js → notifyAppReadyInsights.spec.js} +161 -29
  60. package/lib/commonjs/notifyAppReadyInsights.spec.js.map +1 -0
  61. package/lib/commonjs/{notifyAppReadyAnalytics.test-utils.js → notifyAppReadyInsights.test-utils.js} +3 -3
  62. package/lib/commonjs/notifyAppReadyInsights.test-utils.js.map +1 -0
  63. package/lib/commonjs/{notifyAppReadyAnalytics.transport.spec.js → notifyAppReadyInsights.transport.spec.js} +14 -7
  64. package/lib/commonjs/notifyAppReadyInsights.transport.spec.js.map +1 -0
  65. package/lib/commonjs/sdkVersion.js +1 -1
  66. package/lib/commonjs/sdkVersion.js.map +1 -1
  67. package/lib/commonjs/specs/NativeHotUpdater.js.map +1 -1
  68. package/lib/commonjs/store.js +3 -10
  69. package/lib/commonjs/store.js.map +1 -1
  70. package/lib/commonjs/store.notification.spec.js +12 -4
  71. package/lib/commonjs/store.notification.spec.js.map +1 -1
  72. package/lib/commonjs/store.spec.js +2 -136
  73. package/lib/commonjs/store.spec.js.map +1 -1
  74. package/lib/commonjs/wrap.js +11 -12
  75. package/lib/commonjs/wrap.js.map +1 -1
  76. package/lib/commonjs/wrap.spec.js +97 -25
  77. package/lib/commonjs/wrap.spec.js.map +1 -1
  78. package/lib/module/checkForUpdate.js +32 -23
  79. package/lib/module/checkForUpdate.js.map +1 -1
  80. package/lib/module/checkForUpdate.releaseCatalog.spec.js +97 -28
  81. package/lib/module/checkForUpdate.releaseCatalog.spec.js.map +1 -1
  82. package/lib/module/error.js +4 -13
  83. package/lib/module/error.js.map +1 -1
  84. package/lib/module/fetchJSON.js +7 -1
  85. package/lib/module/fetchJSON.js.map +1 -1
  86. package/lib/module/httpClient.js +48 -31
  87. package/lib/module/httpClient.js.map +1 -1
  88. package/lib/module/httpClient.spec.js +57 -5
  89. package/lib/module/httpClient.spec.js.map +1 -1
  90. package/lib/module/index.js +32 -15
  91. package/lib/module/index.js.map +1 -1
  92. package/lib/module/index.spec.js +56 -11
  93. package/lib/module/index.spec.js.map +1 -1
  94. package/lib/module/native.js +13 -12
  95. package/lib/module/native.js.map +1 -1
  96. package/lib/module/native.spec.js +128 -22
  97. package/lib/module/native.spec.js.map +1 -1
  98. package/lib/module/{notifyAppReadyAnalytics.boundary.spec.js → notifyAppReadyInsights.boundary.spec.js} +23 -16
  99. package/lib/module/notifyAppReadyInsights.boundary.spec.js.map +1 -0
  100. package/lib/module/notifyAppReadyInsights.js +181 -0
  101. package/lib/module/notifyAppReadyInsights.js.map +1 -0
  102. package/lib/module/{notifyAppReadyAnalytics.spec.js → notifyAppReadyInsights.spec.js} +153 -21
  103. package/lib/module/notifyAppReadyInsights.spec.js.map +1 -0
  104. package/lib/module/{notifyAppReadyAnalytics.test-utils.js → notifyAppReadyInsights.test-utils.js} +3 -3
  105. package/lib/module/notifyAppReadyInsights.test-utils.js.map +1 -0
  106. package/lib/module/{notifyAppReadyAnalytics.transport.spec.js → notifyAppReadyInsights.transport.spec.js} +11 -4
  107. package/lib/module/notifyAppReadyInsights.transport.spec.js.map +1 -0
  108. package/lib/module/sdkVersion.js +1 -1
  109. package/lib/module/sdkVersion.js.map +1 -1
  110. package/lib/module/specs/NativeHotUpdater.js.map +1 -1
  111. package/lib/module/store.js +3 -10
  112. package/lib/module/store.js.map +1 -1
  113. package/lib/module/store.notification.spec.js +12 -4
  114. package/lib/module/store.notification.spec.js.map +1 -1
  115. package/lib/module/store.spec.js +2 -136
  116. package/lib/module/store.spec.js.map +1 -1
  117. package/lib/module/wrap.js +11 -12
  118. package/lib/module/wrap.js.map +1 -1
  119. package/lib/module/wrap.spec.js +99 -25
  120. package/lib/module/wrap.spec.js.map +1 -1
  121. package/lib/typescript/commonjs/checkForUpdate.d.ts +2 -3
  122. package/lib/typescript/commonjs/checkForUpdate.d.ts.map +1 -1
  123. package/lib/typescript/commonjs/error.d.ts +4 -13
  124. package/lib/typescript/commonjs/error.d.ts.map +1 -1
  125. package/lib/typescript/commonjs/fetchJSON.d.ts +4 -0
  126. package/lib/typescript/commonjs/fetchJSON.d.ts.map +1 -1
  127. package/lib/typescript/commonjs/httpClient.d.ts +6 -6
  128. package/lib/typescript/commonjs/httpClient.d.ts.map +1 -1
  129. package/lib/typescript/commonjs/index.d.ts +4 -7
  130. package/lib/typescript/commonjs/index.d.ts.map +1 -1
  131. package/lib/typescript/commonjs/native.d.ts +5 -9
  132. package/lib/typescript/commonjs/native.d.ts.map +1 -1
  133. package/lib/typescript/commonjs/notifyAppReadyInsights.d.ts +25 -0
  134. package/lib/typescript/commonjs/notifyAppReadyInsights.d.ts.map +1 -0
  135. package/lib/typescript/commonjs/notifyAppReadyInsights.test-utils.d.ts +8 -0
  136. package/lib/typescript/commonjs/notifyAppReadyInsights.test-utils.d.ts.map +1 -0
  137. package/lib/typescript/commonjs/sdkVersion.d.ts +1 -1
  138. package/lib/typescript/commonjs/sdkVersion.d.ts.map +1 -1
  139. package/lib/typescript/commonjs/specs/NativeHotUpdater.d.ts +8 -35
  140. package/lib/typescript/commonjs/specs/NativeHotUpdater.d.ts.map +1 -1
  141. package/lib/typescript/commonjs/store.d.ts +1 -3
  142. package/lib/typescript/commonjs/store.d.ts.map +1 -1
  143. package/lib/typescript/commonjs/wrap.types.d.ts +5 -8
  144. package/lib/typescript/commonjs/wrap.types.d.ts.map +1 -1
  145. package/lib/typescript/module/checkForUpdate.d.ts +2 -3
  146. package/lib/typescript/module/checkForUpdate.d.ts.map +1 -1
  147. package/lib/typescript/module/error.d.ts +4 -13
  148. package/lib/typescript/module/error.d.ts.map +1 -1
  149. package/lib/typescript/module/fetchJSON.d.ts +4 -0
  150. package/lib/typescript/module/fetchJSON.d.ts.map +1 -1
  151. package/lib/typescript/module/httpClient.d.ts +6 -6
  152. package/lib/typescript/module/httpClient.d.ts.map +1 -1
  153. package/lib/typescript/module/index.d.ts +4 -7
  154. package/lib/typescript/module/index.d.ts.map +1 -1
  155. package/lib/typescript/module/native.d.ts +5 -9
  156. package/lib/typescript/module/native.d.ts.map +1 -1
  157. package/lib/typescript/module/notifyAppReadyInsights.d.ts +25 -0
  158. package/lib/typescript/module/notifyAppReadyInsights.d.ts.map +1 -0
  159. package/lib/typescript/module/notifyAppReadyInsights.test-utils.d.ts +8 -0
  160. package/lib/typescript/module/notifyAppReadyInsights.test-utils.d.ts.map +1 -0
  161. package/lib/typescript/module/sdkVersion.d.ts +1 -1
  162. package/lib/typescript/module/sdkVersion.d.ts.map +1 -1
  163. package/lib/typescript/module/specs/NativeHotUpdater.d.ts +8 -35
  164. package/lib/typescript/module/specs/NativeHotUpdater.d.ts.map +1 -1
  165. package/lib/typescript/module/store.d.ts +1 -3
  166. package/lib/typescript/module/store.d.ts.map +1 -1
  167. package/lib/typescript/module/wrap.types.d.ts +5 -8
  168. package/lib/typescript/module/wrap.types.d.ts.map +1 -1
  169. package/package.json +3 -3
  170. package/src/checkForUpdate.releaseCatalog.spec.ts +137 -61
  171. package/src/checkForUpdate.ts +35 -29
  172. package/src/error.ts +4 -14
  173. package/src/fetchJSON.ts +12 -1
  174. package/src/httpClient.spec.ts +70 -5
  175. package/src/httpClient.ts +78 -64
  176. package/src/index.spec.ts +59 -11
  177. package/src/index.ts +41 -17
  178. package/src/native.spec.ts +133 -22
  179. package/src/native.ts +21 -32
  180. package/src/{notifyAppReadyAnalytics.boundary.spec.ts → notifyAppReadyInsights.boundary.spec.ts} +25 -17
  181. package/src/{notifyAppReadyAnalytics.spec.ts → notifyAppReadyInsights.spec.ts} +141 -22
  182. package/src/{notifyAppReadyAnalytics.test-utils.ts → notifyAppReadyInsights.test-utils.ts} +4 -4
  183. package/src/{notifyAppReadyAnalytics.transport.spec.ts → notifyAppReadyInsights.transport.spec.ts} +13 -5
  184. package/src/notifyAppReadyInsights.ts +293 -0
  185. package/src/sdkVersion.ts +1 -1
  186. package/src/specs/NativeHotUpdater.ts +8 -36
  187. package/src/store.notification.spec.ts +13 -3
  188. package/src/store.spec.ts +0 -157
  189. package/src/store.ts +2 -23
  190. package/src/wrap.spec.ts +109 -31
  191. package/src/wrap.tsx +11 -11
  192. package/src/wrap.types.ts +5 -8
  193. package/android/src/main/java/com/hotupdater/DecompressService.kt +0 -89
  194. package/android/src/main/java/com/hotupdater/DecompressionStrategy.kt +0 -26
  195. package/android/src/main/java/com/hotupdater/TarBrDecompressionStrategy.kt +0 -117
  196. package/android/src/main/java/com/hotupdater/TarGzDecompressionStrategy.kt +0 -117
  197. package/android/src/main/java/com/hotupdater/ZipDecompressionStrategy.kt +0 -192
  198. package/android/src/test/java/com/hotupdater/TarArchiveInputStreamTest.kt +0 -68
  199. package/ios/HotUpdater/Internal/ArchiveExtractionUtilities.swift +0 -178
  200. package/ios/HotUpdater/Internal/DecompressService.swift +0 -112
  201. package/ios/HotUpdater/Internal/DecompressionStrategy.swift +0 -22
  202. package/ios/HotUpdater/Internal/StreamingTarArchiveExtractor.swift +0 -370
  203. package/ios/HotUpdater/Internal/TarBrDecompressionStrategy.swift +0 -39
  204. package/ios/HotUpdater/Internal/TarGzDecompressionStrategy.swift +0 -59
  205. package/ios/HotUpdater/Internal/ZipArchiveExtractor.swift +0 -482
  206. package/ios/HotUpdater/Internal/ZipDecompressionStrategy.swift +0 -56
  207. package/lib/commonjs/notifyAppReadyAnalytics.boundary.spec.js.map +0 -1
  208. package/lib/commonjs/notifyAppReadyAnalytics.js +0 -133
  209. package/lib/commonjs/notifyAppReadyAnalytics.js.map +0 -1
  210. package/lib/commonjs/notifyAppReadyAnalytics.spec.js.map +0 -1
  211. package/lib/commonjs/notifyAppReadyAnalytics.test-utils.js.map +0 -1
  212. package/lib/commonjs/notifyAppReadyAnalytics.transport.spec.js.map +0 -1
  213. package/lib/module/notifyAppReadyAnalytics.boundary.spec.js.map +0 -1
  214. package/lib/module/notifyAppReadyAnalytics.js +0 -128
  215. package/lib/module/notifyAppReadyAnalytics.js.map +0 -1
  216. package/lib/module/notifyAppReadyAnalytics.spec.js.map +0 -1
  217. package/lib/module/notifyAppReadyAnalytics.test-utils.js.map +0 -1
  218. package/lib/module/notifyAppReadyAnalytics.transport.spec.js.map +0 -1
  219. package/lib/typescript/commonjs/notifyAppReadyAnalytics.d.ts +0 -13
  220. package/lib/typescript/commonjs/notifyAppReadyAnalytics.d.ts.map +0 -1
  221. package/lib/typescript/commonjs/notifyAppReadyAnalytics.test-utils.d.ts +0 -8
  222. package/lib/typescript/commonjs/notifyAppReadyAnalytics.test-utils.d.ts.map +0 -1
  223. package/lib/typescript/module/notifyAppReadyAnalytics.d.ts +0 -13
  224. package/lib/typescript/module/notifyAppReadyAnalytics.d.ts.map +0 -1
  225. package/lib/typescript/module/notifyAppReadyAnalytics.test-utils.d.ts +0 -8
  226. package/lib/typescript/module/notifyAppReadyAnalytics.test-utils.d.ts.map +0 -1
  227. package/src/notifyAppReadyAnalytics.ts +0 -194
@@ -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
@@ -1,8 +1,12 @@
1
- import type React from "react";
2
- import { beforeEach, describe, expect, it, vi } from "vitest";
1
+ // @vitest-environment jsdom
2
+
3
+ import { cleanup, render, waitFor } from "@testing-library/react";
4
+ import { createElement, type ComponentType } from "react";
5
+ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
3
6
 
4
7
  import type {
5
- NotifyAppReadyAnalyticsEvent,
8
+ ActiveUpdateState,
9
+ NotifyAppReadyInsightsEvent,
6
10
  NotifyAppReadyResult,
7
11
  } from "./native";
8
12
  import type { HotUpdaterOptions } from "./wrap";
@@ -15,14 +19,14 @@ vi.mock("react-native", () => ({
15
19
 
16
20
  const createNotifyReadResult = (
17
21
  result: NotifyAppReadyResult = { status: "UNCHANGED" },
18
- analyticsEvent: NotifyAppReadyAnalyticsEvent | null = null,
22
+ insightsEvent: NotifyAppReadyInsightsEvent | null = null,
19
23
  pending = false,
20
24
  ): {
21
- analyticsEvent: NotifyAppReadyAnalyticsEvent | null;
25
+ insightsEvent: NotifyAppReadyInsightsEvent | null;
22
26
  pending: boolean;
23
27
  result: NotifyAppReadyResult;
24
28
  } => ({
25
- analyticsEvent,
29
+ insightsEvent,
26
30
  pending,
27
31
  result,
28
32
  });
@@ -32,14 +36,16 @@ const mocks = vi.hoisted(() => ({
32
36
  checkForUpdate: vi.fn(),
33
37
  getAppVersion: vi.fn(() => "1.0.0"),
34
38
  getBundleId: vi.fn(() => "bundle-id"),
39
+ getUpdateId: vi.fn(() => "release-id"),
35
40
  getChannel: vi.fn(() => "production"),
41
+ getActiveUpdateState: vi.fn<() => ActiveUpdateState>(),
36
42
  getCohort: vi.fn(() => "123"),
37
43
  getFingerprintHash: vi.fn(() => "fingerprint-hash"),
38
44
  getInstallId: vi.fn(() => "install-id"),
39
45
  getPersistedUserIdentity: vi.fn(() => ({})),
40
46
  readNotifyAppReady: vi.fn<
41
47
  () => {
42
- analyticsEvent: NotifyAppReadyAnalyticsEvent | null;
48
+ insightsEvent: NotifyAppReadyInsightsEvent | null;
43
49
  pending: boolean;
44
50
  result: NotifyAppReadyResult;
45
51
  }
@@ -55,7 +61,9 @@ vi.mock("./native", () => ({
55
61
  addListener: mocks.addListener,
56
62
  getAppVersion: mocks.getAppVersion,
57
63
  getBundleId: mocks.getBundleId,
64
+ getUpdateId: mocks.getUpdateId,
58
65
  getChannel: mocks.getChannel,
66
+ getActiveUpdateState: mocks.getActiveUpdateState,
59
67
  getCohort: mocks.getCohort,
60
68
  getFingerprintHash: mocks.getFingerprintHash,
61
69
  getInstallId: mocks.getInstallId,
@@ -65,16 +73,16 @@ vi.mock("./native", () => ({
65
73
  }));
66
74
 
67
75
  const createClient = (
68
- sendAnalyticsEvent = vi.fn().mockResolvedValue(undefined),
76
+ sendInsightsEvent = vi.fn().mockResolvedValue(undefined),
69
77
  ) => ({
70
78
  client: {
71
79
  createSession: vi.fn(async () => ({
72
80
  fetchReleaseCatalog: vi.fn(),
73
81
  resolveArtifact: vi.fn(),
74
- sendAnalyticsEvent,
82
+ sendInsightsEvent,
75
83
  })),
76
84
  },
77
- sendAnalyticsEvent,
85
+ sendInsightsEvent,
78
86
  });
79
87
 
80
88
  describe("HotUpdater wrap initialization", () => {
@@ -92,13 +100,83 @@ describe("HotUpdater wrap initialization", () => {
92
100
  mocks.getAppVersion.mockReturnValue("1.0.0");
93
101
  mocks.getPersistedUserIdentity.mockReturnValue({});
94
102
  mocks.getBundleId.mockReturnValue("bundle-id");
103
+ mocks.getUpdateId.mockReturnValue("release-id");
95
104
  mocks.getChannel.mockReturnValue("production");
105
+ mocks.getActiveUpdateState.mockReturnValue({
106
+ activeSelection: null,
107
+ stableSelection: null,
108
+ verificationPending: false,
109
+ });
96
110
  mocks.getCohort.mockReturnValue("123");
97
111
  mocks.getFingerprintHash.mockReturnValue("fingerprint-hash");
98
112
  mocks.getInstallId.mockReturnValue("install-id");
99
113
  mocks.readNotifyAppReady.mockReturnValue(createNotifyReadResult());
100
114
  });
101
115
 
116
+ afterEach(cleanup);
117
+
118
+ it("does not report No change while an optional download is in progress", async () => {
119
+ const { client, sendInsightsEvent } = createClient();
120
+ let complete!: (success: boolean) => void;
121
+ const download = vi.fn(
122
+ () =>
123
+ new Promise<boolean>((resolve) => {
124
+ complete = resolve;
125
+ }),
126
+ );
127
+ mocks.checkForUpdate.mockResolvedValue({
128
+ id: "next",
129
+ status: "UPDATE",
130
+ shouldForceUpdate: false,
131
+ message: null,
132
+ updateBundle: download,
133
+ });
134
+ const { wrap } = await import("./wrap");
135
+ const onUpdateProcessCompleted = vi.fn();
136
+ const Wrapped = wrap({
137
+ client,
138
+ insights: true,
139
+ updateStrategy: "appVersion",
140
+ onUpdateProcessCompleted,
141
+ })(() => null);
142
+ render(createElement(Wrapped));
143
+ await waitFor(() => expect(download).toHaveBeenCalledOnce());
144
+ expect(onUpdateProcessCompleted).toHaveBeenCalledOnce();
145
+ expect(sendInsightsEvent).not.toHaveBeenCalled();
146
+ complete(true);
147
+ });
148
+
149
+ it("reports the console ID when up to date while retaining file IDs in insights", async () => {
150
+ const onUpdateProcessCompleted = vi.fn();
151
+ const { client, sendInsightsEvent } = createClient();
152
+ const { wrap } = await import("./wrap");
153
+ const WrappedComponent = wrap({
154
+ insights: true,
155
+ client,
156
+ onUpdateProcessCompleted,
157
+ updateStrategy: "appVersion",
158
+ })(() => null);
159
+
160
+ render(createElement(WrappedComponent));
161
+
162
+ await waitFor(() =>
163
+ expect(onUpdateProcessCompleted).toHaveBeenCalledWith({
164
+ id: "release-id",
165
+ message: null,
166
+ shouldForceUpdate: false,
167
+ status: "UP_TO_DATE",
168
+ }),
169
+ );
170
+ await waitFor(() =>
171
+ expect(sendInsightsEvent).toHaveBeenCalledWith(
172
+ expect.objectContaining({
173
+ toBundleId: "bundle-id",
174
+ type: "UNCHANGED",
175
+ }),
176
+ ),
177
+ );
178
+ });
179
+
102
180
  it("returns void from init and defers notifyAppReady to the next frame", async () => {
103
181
  vi.useFakeTimers();
104
182
 
@@ -110,7 +188,7 @@ describe("HotUpdater wrap initialization", () => {
110
188
  );
111
189
  vi.stubGlobal("requestAnimationFrame", requestAnimationFrame);
112
190
 
113
- const { client, sendAnalyticsEvent } = createClient();
191
+ const { client, sendInsightsEvent } = createClient();
114
192
  const { init } = await import("./wrap");
115
193
 
116
194
  const result = init({
@@ -123,17 +201,17 @@ describe("HotUpdater wrap initialization", () => {
123
201
 
124
202
  expect(result).toBeUndefined();
125
203
  expect(mocks.readNotifyAppReady).not.toHaveBeenCalled();
126
- expect(sendAnalyticsEvent).not.toHaveBeenCalled();
204
+ expect(sendInsightsEvent).not.toHaveBeenCalled();
127
205
 
128
206
  expect(requestAnimationFrame).toHaveBeenCalled();
129
207
 
130
208
  await vi.runOnlyPendingTimersAsync();
131
209
 
132
210
  expect(mocks.readNotifyAppReady).toHaveBeenCalledWith();
133
- expect(sendAnalyticsEvent).not.toHaveBeenCalled();
211
+ expect(sendInsightsEvent).not.toHaveBeenCalled();
134
212
  });
135
213
 
136
- it("waits for native launch verification before sending analytics", async () => {
214
+ it("waits for native launch verification before sending insights", async () => {
137
215
  vi.useFakeTimers();
138
216
 
139
217
  vi.stubGlobal(
@@ -162,18 +240,18 @@ describe("HotUpdater wrap initialization", () => {
162
240
  ),
163
241
  );
164
242
 
165
- const { client, sendAnalyticsEvent } = createClient();
243
+ const { client, sendInsightsEvent } = createClient();
166
244
  const { init } = await import("./wrap");
167
245
 
168
- init({ analytics: true, client });
246
+ init({ insights: true, client });
169
247
 
170
248
  await vi.runAllTimersAsync();
171
249
 
172
250
  expect(mocks.readNotifyAppReady).toHaveBeenCalledTimes(2);
173
- expect(sendAnalyticsEvent).toHaveBeenCalledTimes(1);
251
+ expect(sendInsightsEvent).toHaveBeenCalledTimes(1);
174
252
  });
175
253
 
176
- it("sends automatic analytics only from init when enabled", async () => {
254
+ it("sends automatic insights only from init when enabled", async () => {
177
255
  vi.useFakeTimers();
178
256
 
179
257
  vi.stubGlobal(
@@ -205,11 +283,11 @@ describe("HotUpdater wrap initialization", () => {
205
283
  username: "alice",
206
284
  });
207
285
 
208
- const { client, sendAnalyticsEvent } = createClient();
286
+ const { client, sendInsightsEvent } = createClient();
209
287
  const { init } = await import("./wrap");
210
288
 
211
289
  init({
212
- analytics: true,
290
+ insights: true,
213
291
  requestHeaders: {
214
292
  Authorization: "Bearer token",
215
293
  },
@@ -219,7 +297,7 @@ describe("HotUpdater wrap initialization", () => {
219
297
 
220
298
  await vi.runOnlyPendingTimersAsync();
221
299
 
222
- expect(sendAnalyticsEvent).toHaveBeenCalledWith({
300
+ expect(sendInsightsEvent).toHaveBeenCalledWith({
223
301
  appVersion: "1.0.0",
224
302
  channel: "production",
225
303
  cohort: "123",
@@ -241,7 +319,7 @@ describe("HotUpdater wrap initialization", () => {
241
319
  });
242
320
  });
243
321
 
244
- it("guards automatic analytics to a single delivery attempt per runtime", async () => {
322
+ it("guards automatic insights to a single delivery attempt per runtime", async () => {
245
323
  vi.useFakeTimers();
246
324
 
247
325
  vi.stubGlobal(
@@ -268,18 +346,18 @@ describe("HotUpdater wrap initialization", () => {
268
346
  ),
269
347
  );
270
348
 
271
- const { client, sendAnalyticsEvent } = createClient();
349
+ const { client, sendInsightsEvent } = createClient();
272
350
  const { init } = await import("./wrap");
273
351
 
274
- init({ analytics: true, client });
275
- init({ analytics: true, client });
352
+ init({ insights: true, client });
353
+ init({ insights: true, client });
276
354
 
277
355
  await vi.runOnlyPendingTimersAsync();
278
356
 
279
- expect(sendAnalyticsEvent).toHaveBeenCalledTimes(1);
357
+ expect(sendInsightsEvent).toHaveBeenCalledTimes(1);
280
358
  });
281
359
 
282
- it("warns without interrupting app readiness when analytics transport fails", async () => {
360
+ it("warns without interrupting app readiness when insights transport fails", async () => {
283
361
  vi.useFakeTimers();
284
362
 
285
363
  const warn = vi.spyOn(console, "warn").mockImplementation(() => {});
@@ -312,7 +390,7 @@ describe("HotUpdater wrap initialization", () => {
312
390
  const { init } = await import("./wrap");
313
391
 
314
392
  init({
315
- analytics: true,
393
+ insights: true,
316
394
  onError,
317
395
  onNotifyAppReady,
318
396
  client,
@@ -322,7 +400,7 @@ describe("HotUpdater wrap initialization", () => {
322
400
 
323
401
  expect(onError).not.toHaveBeenCalled();
324
402
  expect(warn).toHaveBeenCalledWith(
325
- "[HotUpdater] Automatic notifyAppReady analytics failed:",
403
+ "[HotUpdater] Automatic notifyAppReady insights failed:",
326
404
  error,
327
405
  );
328
406
  expect(onNotifyAppReady).toHaveBeenCalledWith({
@@ -344,14 +422,14 @@ describe("HotUpdater wrap initialization", () => {
344
422
 
345
423
  it("preserves wrapped component prop inference", async () => {
346
424
  const { wrap } = await import("./wrap");
347
- const Component: React.ComponentType<{ title: string }> = () => null;
425
+ const Component: ComponentType<{ title: string }> = () => null;
348
426
 
349
427
  const WrappedComponent = wrap({
350
428
  client: createClient().client,
351
429
  updateStrategy: "appVersion",
352
430
  })(Component);
353
431
 
354
- const acceptsTitleProps: React.ComponentType<{ title: string }> =
432
+ const acceptsTitleProps: ComponentType<{ title: string }> =
355
433
  WrappedComponent;
356
434
  expect(acceptsTitleProps).toBe(WrappedComponent);
357
435
  });