@hot-updater/react-native 1.0.0-rc.4 → 1.0.0-rc.41

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 (317) hide show
  1. package/android/build.gradle +13 -2
  2. package/android/proguard-rules.pro +8 -2
  3. package/android/src/bridge/BridgeReactInstance.kt +38 -0
  4. package/android/src/bridgeless/BridgeReactInstance.kt +24 -0
  5. package/android/src/main/java/com/hotupdater/ActivityStartGate.kt +94 -0
  6. package/android/src/main/java/com/hotupdater/BuiltInAssetResolver.kt +286 -0
  7. package/android/src/main/java/com/hotupdater/BundleFileStorageService.kt +1064 -844
  8. package/android/src/main/java/com/hotupdater/BundleMetadata.kt +50 -11
  9. package/android/src/main/java/com/hotupdater/FileManagerService.kt +45 -0
  10. package/android/src/main/java/com/hotupdater/HotUpdaterException.kt +287 -22
  11. package/android/src/main/java/com/hotupdater/HotUpdaterImpl.kt +46 -30
  12. package/android/src/main/java/com/hotupdater/HotUpdaterRecoveryManager.kt +75 -192
  13. package/android/src/main/java/com/hotupdater/HotUpdaterRecoveryState.kt +64 -0
  14. package/android/src/main/java/com/hotupdater/InstallIdentityService.kt +37 -0
  15. package/android/src/main/java/com/hotupdater/KeyValueStorageService.kt +86 -0
  16. package/android/src/main/java/com/hotupdater/NetworkState.kt +31 -0
  17. package/android/src/main/java/com/hotupdater/NoBackupStorage.kt +17 -0
  18. package/android/src/main/java/com/hotupdater/OkHttpDownloadService.kt +127 -50
  19. package/android/src/main/java/com/hotupdater/ReactNativeValueConverters.kt +34 -0
  20. package/android/src/main/java/com/hotupdater/TarArchiveInputStream.kt +214 -375
  21. package/android/src/main/java/com/hotupdater/TarBrArchiveExtractor.kt +119 -0
  22. package/android/src/newarch/HotUpdater.kt +7 -9
  23. package/android/src/newarch/HotUpdaterModule.kt +109 -84
  24. package/android/src/newarch/JavaScriptExceptionHooks.kt +48 -0
  25. package/android/src/newarch/ReactIntegrationManager.kt +6 -32
  26. package/android/src/oldarch/HotUpdater.kt +7 -9
  27. package/android/src/oldarch/HotUpdaterModule.kt +98 -74
  28. package/android/src/oldarch/HotUpdaterSpec.kt +4 -6
  29. package/android/src/oldarch/JavaScriptExceptionHooks.kt +71 -0
  30. package/ios/HotUpdater/Internal/BrotliFileDecompressor.swift +167 -0
  31. package/ios/HotUpdater/Internal/BuiltInAssetResolver.swift +196 -0
  32. package/ios/HotUpdater/Internal/BundleFileStorageService.swift +1032 -1116
  33. package/ios/HotUpdater/Internal/BundleMetadata.swift +61 -54
  34. package/ios/HotUpdater/Internal/FileManagerService.swift +48 -0
  35. package/ios/HotUpdater/Internal/FileUtilities.swift +64 -0
  36. package/ios/HotUpdater/Internal/HotUpdater-Bridging-Header.h +0 -1
  37. package/ios/HotUpdater/Internal/HotUpdater.mm +32 -67
  38. package/ios/HotUpdater/Internal/HotUpdaterImpl.swift +229 -73
  39. package/ios/HotUpdater/Internal/KeyValueStorageService.swift +71 -0
  40. package/ios/HotUpdater/Internal/NotificationExtension.swift +0 -2
  41. package/ios/HotUpdater/Internal/TarArchiveExtractor.swift +182 -311
  42. package/ios/HotUpdater/Internal/URLSessionDownloadService.swift +119 -71
  43. package/ios/HotUpdater/Internal/UpdateFailure.swift +390 -0
  44. package/ios/HotUpdater/Recovery/HotUpdaterRecovery.h +16 -0
  45. package/ios/HotUpdater/Recovery/HotUpdaterRecovery.mm +83 -0
  46. package/ios/HotUpdater/Recovery/HotUpdaterRecoveryHooks.mm +49 -0
  47. package/lib/commonjs/appReady.js +96 -0
  48. package/lib/commonjs/appReady.js.map +1 -0
  49. package/lib/commonjs/checkForUpdate.js +121 -69
  50. package/lib/commonjs/checkForUpdate.js.map +1 -1
  51. package/lib/commonjs/clientPlugin.js +13 -0
  52. package/lib/commonjs/clientPlugin.js.map +1 -0
  53. package/lib/commonjs/error.js +5 -14
  54. package/lib/commonjs/error.js.map +1 -1
  55. package/lib/commonjs/fetchJSON.js +16 -27
  56. package/lib/commonjs/fetchJSON.js.map +1 -1
  57. package/lib/commonjs/fetchUpdateResponse.js +60 -0
  58. package/lib/commonjs/fetchUpdateResponse.js.map +1 -0
  59. package/lib/commonjs/httpClient.js +64 -95
  60. package/lib/commonjs/httpClient.js.map +1 -1
  61. package/lib/commonjs/index.js +355 -324
  62. package/lib/commonjs/index.js.map +1 -1
  63. package/lib/commonjs/init.types.js +6 -0
  64. package/lib/commonjs/init.types.js.map +1 -0
  65. package/lib/commonjs/native.js +77 -63
  66. package/lib/commonjs/native.js.map +1 -1
  67. package/lib/commonjs/pluginHost.js +41 -0
  68. package/lib/commonjs/pluginHost.js.map +1 -0
  69. package/lib/commonjs/releaseCatalogCache.js +36 -87
  70. package/lib/commonjs/releaseCatalogCache.js.map +1 -1
  71. package/lib/commonjs/sdkVersion.js +1 -1
  72. package/lib/commonjs/sdkVersion.js.map +1 -1
  73. package/lib/commonjs/specs/NativeHotUpdater.js.map +1 -1
  74. package/lib/commonjs/store.js +3 -10
  75. package/lib/commonjs/store.js.map +1 -1
  76. package/lib/commonjs/updateError.js +145 -0
  77. package/lib/commonjs/updateError.js.map +1 -0
  78. package/lib/commonjs/wrap.js +46 -38
  79. package/lib/commonjs/wrap.js.map +1 -1
  80. package/lib/module/appReady.js +90 -0
  81. package/lib/module/appReady.js.map +1 -0
  82. package/lib/module/checkForUpdate.js +114 -64
  83. package/lib/module/checkForUpdate.js.map +1 -1
  84. package/lib/module/clientPlugin.js +4 -0
  85. package/lib/module/clientPlugin.js.map +1 -0
  86. package/lib/module/error.js +5 -14
  87. package/lib/module/error.js.map +1 -1
  88. package/lib/module/fetchJSON.js +14 -26
  89. package/lib/module/fetchJSON.js.map +1 -1
  90. package/lib/module/fetchUpdateResponse.js +55 -0
  91. package/lib/module/fetchUpdateResponse.js.map +1 -0
  92. package/lib/module/httpClient.js +63 -94
  93. package/lib/module/httpClient.js.map +1 -1
  94. package/lib/module/index.js +335 -327
  95. package/lib/module/index.js.map +1 -1
  96. package/lib/module/init.types.js +4 -0
  97. package/lib/module/init.types.js.map +1 -0
  98. package/lib/module/native.js +70 -56
  99. package/lib/module/native.js.map +1 -1
  100. package/lib/module/pluginHost.js +36 -0
  101. package/lib/module/pluginHost.js.map +1 -0
  102. package/lib/module/releaseCatalogCache.js +32 -82
  103. package/lib/module/releaseCatalogCache.js.map +1 -1
  104. package/lib/module/sdkVersion.js +1 -1
  105. package/lib/module/sdkVersion.js.map +1 -1
  106. package/lib/module/specs/NativeHotUpdater.js.map +1 -1
  107. package/lib/module/store.js +3 -10
  108. package/lib/module/store.js.map +1 -1
  109. package/lib/module/updateError.js +139 -0
  110. package/lib/module/updateError.js.map +1 -0
  111. package/lib/module/wrap.js +47 -38
  112. package/lib/module/wrap.js.map +1 -1
  113. package/lib/typescript/commonjs/appReady.d.ts +30 -0
  114. package/lib/typescript/commonjs/appReady.d.ts.map +1 -0
  115. package/lib/typescript/commonjs/checkForUpdate.d.ts +20 -6
  116. package/lib/typescript/commonjs/checkForUpdate.d.ts.map +1 -1
  117. package/lib/typescript/commonjs/clientPlugin.d.ts +2 -0
  118. package/lib/typescript/commonjs/clientPlugin.d.ts.map +1 -0
  119. package/lib/typescript/commonjs/error.d.ts +5 -14
  120. package/lib/typescript/commonjs/error.d.ts.map +1 -1
  121. package/lib/typescript/commonjs/fetchJSON.d.ts +5 -5
  122. package/lib/typescript/commonjs/fetchJSON.d.ts.map +1 -1
  123. package/lib/typescript/commonjs/fetchUpdateResponse.d.ts +14 -0
  124. package/lib/typescript/commonjs/fetchUpdateResponse.d.ts.map +1 -0
  125. package/lib/typescript/commonjs/httpClient.d.ts +5 -34
  126. package/lib/typescript/commonjs/httpClient.d.ts.map +1 -1
  127. package/lib/typescript/commonjs/index.d.ts +124 -116
  128. package/lib/typescript/commonjs/index.d.ts.map +1 -1
  129. package/lib/typescript/commonjs/init.types.d.ts +21 -0
  130. package/lib/typescript/commonjs/init.types.d.ts.map +1 -0
  131. package/lib/typescript/commonjs/native.d.ts +32 -25
  132. package/lib/typescript/commonjs/native.d.ts.map +1 -1
  133. package/lib/typescript/commonjs/pluginHost.d.ts +8 -0
  134. package/lib/typescript/commonjs/pluginHost.d.ts.map +1 -0
  135. package/lib/typescript/commonjs/releaseCatalogCache.d.ts +8 -14
  136. package/lib/typescript/commonjs/releaseCatalogCache.d.ts.map +1 -1
  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 +35 -47
  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/types.d.ts +1 -1
  144. package/lib/typescript/commonjs/types.d.ts.map +1 -1
  145. package/lib/typescript/commonjs/updateError.d.ts +26 -0
  146. package/lib/typescript/commonjs/updateError.d.ts.map +1 -0
  147. package/lib/typescript/commonjs/wrap.d.ts +2 -3
  148. package/lib/typescript/commonjs/wrap.d.ts.map +1 -1
  149. package/lib/typescript/commonjs/wrap.types.d.ts +13 -41
  150. package/lib/typescript/commonjs/wrap.types.d.ts.map +1 -1
  151. package/lib/typescript/module/appReady.d.ts +30 -0
  152. package/lib/typescript/module/appReady.d.ts.map +1 -0
  153. package/lib/typescript/module/checkForUpdate.d.ts +20 -6
  154. package/lib/typescript/module/checkForUpdate.d.ts.map +1 -1
  155. package/lib/typescript/module/clientPlugin.d.ts +2 -0
  156. package/lib/typescript/module/clientPlugin.d.ts.map +1 -0
  157. package/lib/typescript/module/error.d.ts +5 -14
  158. package/lib/typescript/module/error.d.ts.map +1 -1
  159. package/lib/typescript/module/fetchJSON.d.ts +5 -5
  160. package/lib/typescript/module/fetchJSON.d.ts.map +1 -1
  161. package/lib/typescript/module/fetchUpdateResponse.d.ts +14 -0
  162. package/lib/typescript/module/fetchUpdateResponse.d.ts.map +1 -0
  163. package/lib/typescript/module/httpClient.d.ts +5 -34
  164. package/lib/typescript/module/httpClient.d.ts.map +1 -1
  165. package/lib/typescript/module/index.d.ts +124 -116
  166. package/lib/typescript/module/index.d.ts.map +1 -1
  167. package/lib/typescript/module/init.types.d.ts +21 -0
  168. package/lib/typescript/module/init.types.d.ts.map +1 -0
  169. package/lib/typescript/module/native.d.ts +32 -25
  170. package/lib/typescript/module/native.d.ts.map +1 -1
  171. package/lib/typescript/module/pluginHost.d.ts +8 -0
  172. package/lib/typescript/module/pluginHost.d.ts.map +1 -0
  173. package/lib/typescript/module/releaseCatalogCache.d.ts +8 -14
  174. package/lib/typescript/module/releaseCatalogCache.d.ts.map +1 -1
  175. package/lib/typescript/module/sdkVersion.d.ts +1 -1
  176. package/lib/typescript/module/sdkVersion.d.ts.map +1 -1
  177. package/lib/typescript/module/specs/NativeHotUpdater.d.ts +35 -47
  178. package/lib/typescript/module/specs/NativeHotUpdater.d.ts.map +1 -1
  179. package/lib/typescript/module/store.d.ts +1 -3
  180. package/lib/typescript/module/store.d.ts.map +1 -1
  181. package/lib/typescript/module/types.d.ts +1 -1
  182. package/lib/typescript/module/types.d.ts.map +1 -1
  183. package/lib/typescript/module/updateError.d.ts +26 -0
  184. package/lib/typescript/module/updateError.d.ts.map +1 -0
  185. package/lib/typescript/module/wrap.d.ts +2 -3
  186. package/lib/typescript/module/wrap.d.ts.map +1 -1
  187. package/lib/typescript/module/wrap.types.d.ts +13 -41
  188. package/lib/typescript/module/wrap.types.d.ts.map +1 -1
  189. package/package.json +8 -4
  190. package/src/appReady.ts +147 -0
  191. package/src/checkForUpdate.ts +156 -89
  192. package/src/clientPlugin.ts +21 -0
  193. package/src/error.ts +5 -15
  194. package/src/fetchJSON.ts +14 -30
  195. package/src/fetchUpdateResponse.ts +60 -0
  196. package/src/httpClient.ts +89 -160
  197. package/src/index.ts +413 -425
  198. package/src/init.types.ts +24 -0
  199. package/src/native.ts +101 -105
  200. package/src/pluginHost.ts +52 -0
  201. package/src/releaseCatalogCache.ts +43 -172
  202. package/src/sdkVersion.ts +1 -1
  203. package/src/specs/NativeHotUpdater.ts +34 -48
  204. package/src/store.ts +2 -23
  205. package/src/types.ts +1 -1
  206. package/src/updateError.ts +167 -0
  207. package/src/wrap.tsx +60 -43
  208. package/src/wrap.types.ts +18 -46
  209. package/android/src/main/java/com/hotupdater/DecompressService.kt +0 -89
  210. package/android/src/main/java/com/hotupdater/DecompressionStrategy.kt +0 -26
  211. package/android/src/main/java/com/hotupdater/TarBrDecompressionStrategy.kt +0 -117
  212. package/android/src/main/java/com/hotupdater/TarGzDecompressionStrategy.kt +0 -117
  213. package/android/src/main/java/com/hotupdater/ZipDecompressionStrategy.kt +0 -192
  214. package/android/src/test/java/com/hotupdater/BsdiffPatchTest.kt +0 -46
  215. package/android/src/test/java/com/hotupdater/BundleFileStorageServiceTest.kt +0 -799
  216. package/android/src/test/java/com/hotupdater/HotUpdaterImplTest.kt +0 -273
  217. package/android/src/test/java/com/hotupdater/NativeConfigUtilsTest.kt +0 -20
  218. package/android/src/test/java/com/hotupdater/OkHttpDownloadServiceTest.kt +0 -111
  219. package/android/src/test/java/com/hotupdater/RelativePathResolverTest.kt +0 -41
  220. package/android/src/test/java/com/hotupdater/ReleaseCatalogCacheServiceTest.kt +0 -118
  221. package/android/src/test/java/com/hotupdater/TarArchiveInputStreamTest.kt +0 -68
  222. package/ios/HotUpdater/Internal/ArchiveExtractionUtilities.swift +0 -178
  223. package/ios/HotUpdater/Internal/DecompressService.swift +0 -112
  224. package/ios/HotUpdater/Internal/DecompressionStrategy.swift +0 -22
  225. package/ios/HotUpdater/Internal/StreamingTarArchiveExtractor.swift +0 -370
  226. package/ios/HotUpdater/Internal/TarBrDecompressionStrategy.swift +0 -39
  227. package/ios/HotUpdater/Internal/TarGzDecompressionStrategy.swift +0 -59
  228. package/ios/HotUpdater/Internal/ZipArchiveExtractor.swift +0 -482
  229. package/ios/HotUpdater/Internal/ZipDecompressionStrategy.swift +0 -56
  230. package/lib/commonjs/catalogCacheNative.spec.js +0 -31
  231. package/lib/commonjs/catalogCacheNative.spec.js.map +0 -1
  232. package/lib/commonjs/checkForUpdate.releaseCatalog.spec.js +0 -616
  233. package/lib/commonjs/checkForUpdate.releaseCatalog.spec.js.map +0 -1
  234. package/lib/commonjs/httpClient.spec.js +0 -102
  235. package/lib/commonjs/httpClient.spec.js.map +0 -1
  236. package/lib/commonjs/index.spec.js +0 -302
  237. package/lib/commonjs/index.spec.js.map +0 -1
  238. package/lib/commonjs/iosInterop.spec.js +0 -18
  239. package/lib/commonjs/iosInterop.spec.js.map +0 -1
  240. package/lib/commonjs/native.spec.js +0 -883
  241. package/lib/commonjs/native.spec.js.map +0 -1
  242. package/lib/commonjs/notifyAppReadyInsights.boundary.spec.js +0 -218
  243. package/lib/commonjs/notifyAppReadyInsights.boundary.spec.js.map +0 -1
  244. package/lib/commonjs/notifyAppReadyInsights.js +0 -133
  245. package/lib/commonjs/notifyAppReadyInsights.js.map +0 -1
  246. package/lib/commonjs/notifyAppReadyInsights.spec.js +0 -246
  247. package/lib/commonjs/notifyAppReadyInsights.spec.js.map +0 -1
  248. package/lib/commonjs/notifyAppReadyInsights.test-utils.js +0 -24
  249. package/lib/commonjs/notifyAppReadyInsights.test-utils.js.map +0 -1
  250. package/lib/commonjs/notifyAppReadyInsights.transport.spec.js +0 -117
  251. package/lib/commonjs/notifyAppReadyInsights.transport.spec.js.map +0 -1
  252. package/lib/commonjs/releaseCatalogCache.spec.js +0 -195
  253. package/lib/commonjs/releaseCatalogCache.spec.js.map +0 -1
  254. package/lib/commonjs/releaseCatalogCacheNativeContract.spec.js +0 -64
  255. package/lib/commonjs/releaseCatalogCacheNativeContract.spec.js.map +0 -1
  256. package/lib/commonjs/store.notification.spec.js +0 -76
  257. package/lib/commonjs/store.notification.spec.js.map +0 -1
  258. package/lib/commonjs/store.spec.js +0 -316
  259. package/lib/commonjs/store.spec.js.map +0 -1
  260. package/lib/commonjs/wrap.spec.js +0 -329
  261. package/lib/commonjs/wrap.spec.js.map +0 -1
  262. package/lib/module/catalogCacheNative.spec.js +0 -31
  263. package/lib/module/catalogCacheNative.spec.js.map +0 -1
  264. package/lib/module/checkForUpdate.releaseCatalog.spec.js +0 -615
  265. package/lib/module/checkForUpdate.releaseCatalog.spec.js.map +0 -1
  266. package/lib/module/httpClient.spec.js +0 -102
  267. package/lib/module/httpClient.spec.js.map +0 -1
  268. package/lib/module/index.spec.js +0 -301
  269. package/lib/module/index.spec.js.map +0 -1
  270. package/lib/module/iosInterop.spec.js +0 -18
  271. package/lib/module/iosInterop.spec.js.map +0 -1
  272. package/lib/module/native.spec.js +0 -882
  273. package/lib/module/native.spec.js.map +0 -1
  274. package/lib/module/notifyAppReadyInsights.boundary.spec.js +0 -217
  275. package/lib/module/notifyAppReadyInsights.boundary.spec.js.map +0 -1
  276. package/lib/module/notifyAppReadyInsights.js +0 -128
  277. package/lib/module/notifyAppReadyInsights.js.map +0 -1
  278. package/lib/module/notifyAppReadyInsights.spec.js +0 -245
  279. package/lib/module/notifyAppReadyInsights.spec.js.map +0 -1
  280. package/lib/module/notifyAppReadyInsights.test-utils.js +0 -18
  281. package/lib/module/notifyAppReadyInsights.test-utils.js.map +0 -1
  282. package/lib/module/notifyAppReadyInsights.transport.spec.js +0 -116
  283. package/lib/module/notifyAppReadyInsights.transport.spec.js.map +0 -1
  284. package/lib/module/releaseCatalogCache.spec.js +0 -195
  285. package/lib/module/releaseCatalogCache.spec.js.map +0 -1
  286. package/lib/module/releaseCatalogCacheNativeContract.spec.js +0 -64
  287. package/lib/module/releaseCatalogCacheNativeContract.spec.js.map +0 -1
  288. package/lib/module/store.notification.spec.js +0 -77
  289. package/lib/module/store.notification.spec.js.map +0 -1
  290. package/lib/module/store.spec.js +0 -315
  291. package/lib/module/store.spec.js.map +0 -1
  292. package/lib/module/wrap.spec.js +0 -330
  293. package/lib/module/wrap.spec.js.map +0 -1
  294. package/lib/typescript/commonjs/notifyAppReadyInsights.d.ts +0 -13
  295. package/lib/typescript/commonjs/notifyAppReadyInsights.d.ts.map +0 -1
  296. package/lib/typescript/commonjs/notifyAppReadyInsights.test-utils.d.ts +0 -8
  297. package/lib/typescript/commonjs/notifyAppReadyInsights.test-utils.d.ts.map +0 -1
  298. package/lib/typescript/module/notifyAppReadyInsights.d.ts +0 -13
  299. package/lib/typescript/module/notifyAppReadyInsights.d.ts.map +0 -1
  300. package/lib/typescript/module/notifyAppReadyInsights.test-utils.d.ts +0 -8
  301. package/lib/typescript/module/notifyAppReadyInsights.test-utils.d.ts.map +0 -1
  302. package/src/catalogCacheNative.spec.ts +0 -63
  303. package/src/checkForUpdate.releaseCatalog.spec.ts +0 -612
  304. package/src/httpClient.spec.ts +0 -132
  305. package/src/index.spec.ts +0 -337
  306. package/src/iosInterop.spec.ts +0 -30
  307. package/src/native.spec.ts +0 -945
  308. package/src/notifyAppReadyInsights.boundary.spec.ts +0 -234
  309. package/src/notifyAppReadyInsights.spec.ts +0 -243
  310. package/src/notifyAppReadyInsights.test-utils.ts +0 -31
  311. package/src/notifyAppReadyInsights.transport.spec.ts +0 -130
  312. package/src/notifyAppReadyInsights.ts +0 -194
  313. package/src/releaseCatalogCache.spec.ts +0 -282
  314. package/src/releaseCatalogCacheNativeContract.spec.ts +0 -80
  315. package/src/store.notification.spec.ts +0 -92
  316. package/src/store.spec.ts +0 -372
  317. package/src/wrap.spec.ts +0 -406
@@ -1,17 +1,16 @@
1
1
  import {
2
- MAX_TARGET_COHORTS_PER_RELEASE,
3
- MAX_COMPILED_CATALOG_BYTES,
4
- MAX_DISTINCT_TARGET_COHORTS_PER_SCOPE,
5
- NUMERIC_COHORT_SIZE,
6
- isUUIDv7,
7
- parseReleaseCatalogScopeKey,
2
+ type ExpectedReleaseCatalogScope,
3
+ getUtf8ByteLength,
4
+ MAX_RELEASE_CATALOG_WIRE_BYTES,
5
+ parseReleaseCatalog,
8
6
  type ReleaseCatalog,
9
- } from "@hot-updater/core";
7
+ } from "@hot-updater/protocol";
8
+
9
+ import { fetchUpdateResponse, type UpdateRequest } from "./fetchUpdateResponse";
10
+ import { InvalidUpdateResponseError, UpdateHttpError } from "./updateError";
10
11
 
11
12
  const CACHE_FORMAT_VERSION = "1";
12
13
  const MAX_ETAG_BYTES = 1024;
13
- export const MAX_RELEASE_CATALOG_WIRE_BYTES =
14
- MAX_COMPILED_CATALOG_BYTES * 2 + 4 * 1024;
15
14
  export const MAX_RELEASE_CATALOG_CACHE_ENTRY_BYTES =
16
15
  MAX_RELEASE_CATALOG_WIRE_BYTES + MAX_ETAG_BYTES + 3;
17
16
 
@@ -46,137 +45,13 @@ let lastParsedCache:
46
45
  })
47
46
  | null = null;
48
47
 
49
- export type ExpectedReleaseCatalogScope = {
50
- readonly channelKey: string;
51
- readonly platform: "ios" | "android";
52
- } & (
53
- | {
54
- readonly strategy: "APP_VERSION";
55
- readonly fingerprintHash?: never;
56
- }
57
- | {
58
- readonly strategy: "FINGERPRINT";
59
- readonly fingerprintHash: string;
60
- }
61
- );
62
-
63
48
  type FetchReleaseCatalogInput = {
64
49
  readonly baseURL: string;
65
50
  readonly expectedScope: ExpectedReleaseCatalogScope;
66
51
  readonly requestHeaders?: Record<string, string>;
67
52
  readonly requestTimeout?: number;
68
53
  readonly url: string;
69
- };
70
-
71
- const getUtf8ByteLength = (value: string): number => {
72
- let bytes = 0;
73
- for (let index = 0; index < value.length; index += 1) {
74
- const code = value.charCodeAt(index);
75
- if (code <= 0x7f) {
76
- bytes += 1;
77
- } else if (code <= 0x7ff) {
78
- bytes += 2;
79
- } else if (
80
- code >= 0xd800 &&
81
- code <= 0xdbff &&
82
- index + 1 < value.length &&
83
- value.charCodeAt(index + 1) >= 0xdc00 &&
84
- value.charCodeAt(index + 1) <= 0xdfff
85
- ) {
86
- bytes += 4;
87
- index += 1;
88
- } else {
89
- bytes += 3;
90
- }
91
- }
92
- return bytes;
93
- };
94
-
95
- const isStringArray = (value: unknown): value is readonly string[] =>
96
- Array.isArray(value) && value.every((entry) => typeof entry === "string");
97
-
98
- const isValidReleaseDescriptor = (value: unknown): boolean => {
99
- if (value === null || typeof value !== "object") return false;
100
- const descriptor = value as Record<string, unknown>;
101
-
102
- return (
103
- isUUIDv7(descriptor.releaseId) &&
104
- (descriptor.kind === "BUNDLE" || descriptor.kind === "EMBEDDED") &&
105
- ((descriptor.kind === "BUNDLE" &&
106
- typeof descriptor.bundleId === "string") ||
107
- (descriptor.kind === "EMBEDDED" && descriptor.bundleId === null)) &&
108
- Number.isSafeInteger(descriptor.rolloutCohortCount) &&
109
- (descriptor.rolloutCohortCount as number) >= 0 &&
110
- (descriptor.rolloutCohortCount as number) <= NUMERIC_COHORT_SIZE &&
111
- isStringArray(descriptor.targetCohorts) &&
112
- descriptor.targetCohorts.length <= MAX_TARGET_COHORTS_PER_RELEASE &&
113
- typeof descriptor.shouldForceUpdate === "boolean" &&
114
- (descriptor.message === null || typeof descriptor.message === "string")
115
- );
116
- };
117
-
118
- export const hasExpectedReleaseCatalogScope = (
119
- catalog: ReleaseCatalog,
120
- expected: ExpectedReleaseCatalogScope,
121
- ): boolean => {
122
- try {
123
- const parsed = parseReleaseCatalogScopeKey(catalog.scopeKey);
124
- return (
125
- parsed.channelKey === expected.channelKey &&
126
- parsed.platform === expected.platform &&
127
- parsed.strategy === expected.strategy &&
128
- (parsed.strategy === "APP_VERSION" ||
129
- (expected.strategy === "FINGERPRINT" &&
130
- parsed.fingerprintHash === expected.fingerprintHash))
131
- );
132
- } catch {
133
- return false;
134
- }
135
- };
136
-
137
- const parseValidatedCatalog = (
138
- value: string,
139
- expectedScope: ExpectedReleaseCatalogScope,
140
- ): ReleaseCatalog | null => {
141
- if (getUtf8ByteLength(value) > MAX_RELEASE_CATALOG_WIRE_BYTES) return null;
142
-
143
- try {
144
- const catalog = JSON.parse(value) as Partial<ReleaseCatalog>;
145
- if (
146
- catalog.schemaVersion !== 1 ||
147
- typeof catalog.catalogId !== "string" ||
148
- catalog.catalogId.length === 0 ||
149
- typeof catalog.scopeKey !== "string" ||
150
- !Number.isSafeInteger(catalog.generation) ||
151
- (catalog.generation ?? 0) < 1 ||
152
- typeof catalog.catalogHash !== "string" ||
153
- !/^sha256:[0-9a-f]{64}$/.test(catalog.catalogHash) ||
154
- catalog.fallbackPolicy !== "BUILTIN_IF_ACTIVE_INELIGIBLE" ||
155
- !Array.isArray(catalog.releases) ||
156
- !catalog.releases.every(isValidReleaseDescriptor) ||
157
- (catalog.rollbackReleases !== undefined &&
158
- (!Array.isArray(catalog.rollbackReleases) ||
159
- !catalog.rollbackReleases.every(isValidReleaseDescriptor)))
160
- ) {
161
- return null;
162
- }
163
- if (
164
- !hasExpectedReleaseCatalogScope(catalog as ReleaseCatalog, expectedScope)
165
- ) {
166
- return null;
167
- }
168
- const distinctTargetCohorts = new Set(
169
- [...catalog.releases, ...(catalog.rollbackReleases ?? [])].flatMap(
170
- (release) => release.targetCohorts,
171
- ),
172
- );
173
- if (distinctTargetCohorts.size > MAX_DISTINCT_TARGET_COHORTS_PER_SCOPE) {
174
- return null;
175
- }
176
- return catalog as ReleaseCatalog;
177
- } catch {
178
- return null;
179
- }
54
+ readonly onResponse?: UpdateRequest["onResponse"];
180
55
  };
181
56
 
182
57
  const isValidETag = (value: string | null): value is string =>
@@ -214,7 +89,7 @@ const parseCache = (
214
89
  const etag = value.slice(versionEnd + 1, etagEnd);
215
90
  const body = value.slice(etagEnd + 1);
216
91
  if (!isValidETag(etag)) return null;
217
- const catalog = parseValidatedCatalog(body, expectedScope);
92
+ const catalog = parseReleaseCatalog(body, expectedScope);
218
93
  if (catalog === null) return null;
219
94
  const parsed = { catalog, etag, partition, serialized: value };
220
95
  lastParsedCache = parsed;
@@ -240,36 +115,24 @@ export const createReleaseCatalogCachePartition = (
240
115
  version: 2,
241
116
  });
242
117
 
243
- const fetchCatalogResponse = async (
118
+ const isEmptyCatalogResponse = (response: Response) =>
119
+ response.status === 404 &&
120
+ response.headers.get("x-hot-updater-catalog")?.trim().toLowerCase() ===
121
+ "none";
122
+
123
+ const fetchCatalogResponse = (
244
124
  input: FetchReleaseCatalogInput,
245
125
  etag?: string,
246
- ): Promise<Response> => {
247
- const controller = new AbortController();
248
- const timeoutId = setTimeout(
249
- () => controller.abort(),
250
- input.requestTimeout ?? 5000,
251
- );
252
-
253
- try {
254
- const headers = new Headers(input.requestHeaders);
255
- headers.set("Accept", "application/json");
256
- if (etag === undefined) {
257
- headers.delete("If-None-Match");
258
- } else {
259
- headers.set("If-None-Match", etag);
260
- }
261
- return await fetch(input.url, {
262
- headers,
263
- signal: controller.signal,
264
- });
265
- } catch (error: unknown) {
266
- if (error instanceof Error && error.name === "AbortError") {
267
- throw new Error("Request timed out");
268
- }
269
- throw error;
270
- } finally {
271
- clearTimeout(timeoutId);
272
- }
126
+ ) => {
127
+ const headers = new Headers(input.requestHeaders);
128
+ headers.set("Accept", "application/json");
129
+ if (etag === undefined) headers.delete("If-None-Match");
130
+ else headers.set("If-None-Match", etag);
131
+ return fetchUpdateResponse({
132
+ ...input,
133
+ resource: "catalog",
134
+ requestHeaders: Object.fromEntries(headers.entries()),
135
+ });
273
136
  };
274
137
 
275
138
  const readValidatedCache = async (
@@ -287,24 +150,28 @@ const readValidatedCache = async (
287
150
  };
288
151
 
289
152
  const consumeSuccessfulResponse = async (
290
- response: Response,
153
+ { response, body }: Awaited<ReturnType<typeof fetchCatalogResponse>>,
291
154
  input: FetchReleaseCatalogInput,
292
155
  partition: string,
293
- ): Promise<ReleaseCatalog> => {
156
+ ): Promise<ReleaseCatalog | null> => {
157
+ // The server marks the 404 of a scope that has no catalog yet: no update,
158
+ // not a failure. Any other 404 is a wrong baseURL or route.
159
+ if (isEmptyCatalogResponse(response)) {
160
+ return null;
161
+ }
294
162
  if (response.status !== 200) {
295
- throw new Error(response.statusText);
163
+ throw new UpdateHttpError(response.status, response.statusText);
296
164
  }
297
165
 
298
- const body = await response.text();
299
- const catalog = parseValidatedCatalog(body, input.expectedScope);
166
+ const catalog = parseReleaseCatalog(body!, input.expectedScope);
300
167
  if (catalog === null) {
301
168
  await removeNativeReleaseCatalogCache(partition);
302
- throw new Error("Received an invalid Release catalog");
169
+ throw new InvalidUpdateResponseError("Received an invalid Release catalog");
303
170
  }
304
171
 
305
172
  const etag = response.headers.get("etag");
306
173
  if (isValidETag(etag)) {
307
- const serialized = serializeCache(etag, body);
174
+ const serialized = serializeCache(etag, body!);
308
175
  if (getUtf8ByteLength(serialized) > MAX_RELEASE_CATALOG_CACHE_ENTRY_BYTES) {
309
176
  await removeNativeReleaseCatalogCache(partition);
310
177
  return catalog;
@@ -323,14 +190,18 @@ const consumeSuccessfulResponse = async (
323
190
  return catalog;
324
191
  };
325
192
 
193
+ /**
194
+ * Fetches a scope's catalog, or null when the server answers that the scope
195
+ * has none (a 404 with `x-hot-updater-catalog: none`).
196
+ */
326
197
  export const fetchReleaseCatalogWithCache = async (
327
198
  input: FetchReleaseCatalogInput,
328
- ): Promise<ReleaseCatalog> => {
199
+ ): Promise<ReleaseCatalog | null> => {
329
200
  const partition = createReleaseCatalogCachePartition(input);
330
201
  const cached = await readValidatedCache(partition, input.expectedScope);
331
202
  const response = await fetchCatalogResponse(input, cached?.etag);
332
203
 
333
- if (response.status === 304) {
204
+ if (response.response.status === 304) {
334
205
  if (cached !== null) return cached.catalog;
335
206
 
336
207
  const repairResponse = await fetchCatalogResponse(input);
package/src/sdkVersion.ts CHANGED
@@ -1 +1 @@
1
- export const HOT_UPDATER_SDK_VERSION = "1.0.0-rc.4";
1
+ export const HOT_UPDATER_SDK_VERSION = "1.0.0-rc.41";
@@ -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
8
  /**
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.
9
+ * Signed manifest URL for installation.
32
10
  */
33
- fileHash: string | null;
34
- /**
35
- * Optional signed manifest URL for manifest-driven installation.
36
- */
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
  }
@@ -61,18 +34,21 @@ export interface Spec extends TurboModule {
61
34
  * Downloads and applies a bundle update.
62
35
  *
63
36
  * @param params - Update bundle parameters
64
- * @returns Promise that resolves to true if successful
37
+ * @returns Promise that resolves, once the bundle is staged, to how it
38
+ * arrived: `{ delivery, patchFallback }`. `delivery` is "patch" when a
39
+ * bsdiff patch produced a file, "manifest" when only changed files were
40
+ * downloaded, or "archive" for the full archive; `patchFallback` is true
41
+ * when a patch was tried but the file or archive was downloaded instead.
65
42
  * @throws {HotUpdaterErrorCode} Rejects with one of the following error codes:
66
43
  *
67
44
  * Parameter validation:
68
45
  * - MISSING_BUNDLE_ID: Missing or empty bundleId
69
- * - INVALID_FILE_URL: Invalid fileUrl provided
46
+ * - INVALID_FILE_URL: Invalid manifest or asset URL provided
70
47
  *
71
48
  * Bundle storage:
72
49
  * - DIRECTORY_CREATION_FAILED: Failed to create bundle directory
73
50
  * - DOWNLOAD_FAILED: Failed to download bundle
74
51
  * - INCOMPLETE_DOWNLOAD: Download incomplete (size mismatch)
75
- * - EXTRACTION_FORMAT_ERROR: Invalid or corrupted archive format
76
52
  * - INVALID_BUNDLE: Bundle missing required platform files
77
53
  * - INSUFFICIENT_DISK_SPACE: Insufficient disk space
78
54
  * - MOVE_OPERATION_FAILED: Failed to move bundle files
@@ -87,8 +63,20 @@ export interface Spec extends TurboModule {
87
63
  *
88
64
  * Note: iOS normalizes rare signature/storage errors to SIGNATURE_VERIFICATION_FAILED
89
65
  * or UNKNOWN_ERROR to keep the JS error surface small.
66
+ *
67
+ * A rejection from the download or install also carries `userInfo`
68
+ * `{ stage, reason, resource?, httpStatus?, transport?, originCode? }`:
69
+ * `stage` is "download" (transfer and verification) or "install" (patch,
70
+ * extract, move into place); `reason` is one of "network", "http",
71
+ * "invalid_response", "hash_mismatch", "signature", "patch", "extract",
72
+ * "storage", or "unknown"; `resource` is what was being fetched or
73
+ * applied: "manifest", "file", "patch", or "archive". `httpStatus` is
74
+ * present when `reason` is "http", with `originCode`, the storage
75
+ * origin's XML error `<Code>` when the body names one. `transport` is
76
+ * present when no response arrived: "timeout", "dns", "tls",
77
+ * "connection", "offline", or "cancelled".
90
78
  */
91
- updateBundle(params: UpdateBundleParams): Promise<boolean>;
79
+ updateBundle(params: UpdateBundleParams): Promise<UnsafeObject>;
92
80
 
93
81
  /** Accepts and durably advances the catalog high-water for a scope. */
94
82
  acceptReleaseCatalog(params: UnsafeObject): boolean;
@@ -192,24 +180,22 @@ export interface Spec extends TurboModule {
192
180
  getCohort: () => string;
193
181
 
194
182
  /**
195
- * Gets the persisted install id for this app installation.
183
+ * Gets the install id: a random id native creates once per app
184
+ * installation and keeps out of device backups.
196
185
  */
197
186
  getInstallId: () => string;
198
187
 
199
188
  /**
200
- * Gets the persisted nullable user id associated with this installation.
201
- */
202
- getUserId: () => string | null;
203
-
204
- /**
205
- * Gets the persisted nullable username associated with this installation.
189
+ * Reads a value from the SDK's persistent key-value store, which native
190
+ * keeps out of device backups. Returns null when the key has no value.
206
191
  */
207
- getUsername: () => string | null;
192
+ getStorageItem: (key: string) => string | null;
208
193
 
209
194
  /**
210
- * Persists nullable user identity fields associated with this installation.
195
+ * Writes a value to the SDK's persistent key-value store; null removes the
196
+ * key.
211
197
  */
212
- setUser: (userId: string | null, username: string | null) => void;
198
+ setStorageItem: (key: string, value: string | null) => void;
213
199
 
214
200
  // EventEmitter
215
201
  addListener(eventName: string): void;
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/types.ts CHANGED
@@ -1,4 +1,4 @@
1
- export type HotUpdaterBaseURL = string | (() => string | Promise<string>);
1
+ export type { HotUpdaterBaseURL } from "@hot-updater/protocol";
2
2
 
3
3
  /**
4
4
  * Information about a signature verification failure.
@@ -0,0 +1,167 @@
1
+ import type {
2
+ UpdateError,
3
+ UpdateErrorReason,
4
+ UpdateErrorResource,
5
+ UpdateErrorStage,
6
+ UpdateErrorTransport,
7
+ } from "./clientPlugin";
8
+ import { HotUpdaterError } from "./error";
9
+
10
+ /**
11
+ * A response the SDK cannot use: a malformed or stale catalog, an artifact
12
+ * in another protocol, or an invalid URL.
13
+ */
14
+ export class InvalidUpdateResponseError extends HotUpdaterError {
15
+ constructor(message: string, options?: { cause?: unknown }) {
16
+ super(message);
17
+ this.name = "InvalidUpdateResponseError";
18
+ if (options?.cause !== undefined) this.cause = options.cause;
19
+ }
20
+ }
21
+
22
+ /** A request the server answered with a status the SDK does not accept. */
23
+ export class UpdateHttpError extends HotUpdaterError {
24
+ constructor(
25
+ readonly status: number,
26
+ statusText: string,
27
+ ) {
28
+ super(
29
+ `Request failed with HTTP ${status}${statusText ? ` ${statusText}` : ""}`,
30
+ );
31
+ this.name = "UpdateHttpError";
32
+ }
33
+ }
34
+
35
+ const STAGES: readonly UpdateErrorStage[] = ["check", "download", "install"];
36
+ const REASONS: readonly UpdateErrorReason[] = [
37
+ "network",
38
+ "http",
39
+ "invalid_response",
40
+ "hash_mismatch",
41
+ "signature",
42
+ "patch",
43
+ "extract",
44
+ "storage",
45
+ "unknown",
46
+ ];
47
+ const RESOURCES: readonly UpdateErrorResource[] = [
48
+ "catalog",
49
+ "artifact",
50
+ "manifest",
51
+ "file",
52
+ "patch",
53
+ "archive",
54
+ ];
55
+ const TRANSPORTS: readonly UpdateErrorTransport[] = [
56
+ "timeout",
57
+ "dns",
58
+ "tls",
59
+ "connection",
60
+ "offline",
61
+ "cancelled",
62
+ ];
63
+ /** An S3, R2, or GCS error code: letters, digits, and punctuation only. */
64
+ const ORIGIN_CODE = /^[A-Za-z0-9._-]{1,64}$/;
65
+ /** Expo's iOS and Android names for a fetch canceled before its response. */
66
+ const EXPO_FETCH_CANCELED =
67
+ /FetchRequestCanceledException|Fetch request has been canceled|The operation was aborted/;
68
+
69
+ type Classification = Pick<
70
+ UpdateError,
71
+ "stage" | "reason" | "resource" | "httpStatus" | "originCode" | "transport"
72
+ >;
73
+
74
+ const oneOf = <T extends string>(
75
+ values: readonly T[],
76
+ value: unknown,
77
+ ): value is T => values.includes(value as T);
78
+
79
+ const readNativeClassification = (
80
+ error: object,
81
+ ): Classification | null | undefined => {
82
+ if (!("code" in error) || typeof error.code !== "string") return undefined;
83
+ const userInfo =
84
+ "userInfo" in error && typeof error.userInfo === "object"
85
+ ? (error.userInfo as Record<string, unknown> | null)
86
+ : null;
87
+ const { stage, reason, resource, transport, originCode } = userInfo ?? {};
88
+ // Native classifies every failure of its download and install; a rejection
89
+ // without it, such as a stale selection, is not an update failure.
90
+ if (!oneOf(STAGES, stage) || !oneOf(REASONS, reason)) return null;
91
+ const httpStatus = Number(userInfo?.httpStatus);
92
+ return {
93
+ stage,
94
+ reason,
95
+ ...(oneOf(RESOURCES, resource) ? { resource } : {}),
96
+ ...(reason === "http" && Number.isSafeInteger(httpStatus)
97
+ ? { httpStatus }
98
+ : {}),
99
+ ...(reason === "http" &&
100
+ typeof originCode === "string" &&
101
+ ORIGIN_CODE.test(originCode)
102
+ ? { originCode }
103
+ : {}),
104
+ ...(reason === "network" && oneOf(TRANSPORTS, transport)
105
+ ? { transport }
106
+ : {}),
107
+ };
108
+ };
109
+
110
+ /**
111
+ * Where and why an update failed, or null when the error is not an update
112
+ * failure. Native rejections carry their own classification; an error the
113
+ * SDK's JavaScript raised happened in `stage`, fetching `resource` when one
114
+ * is given.
115
+ */
116
+ export const classifyUpdateError = (
117
+ error: unknown,
118
+ stage: UpdateErrorStage,
119
+ resource?: UpdateErrorResource,
120
+ ): Classification | null => {
121
+ if (typeof error === "object" && error !== null) {
122
+ const native = readNativeClassification(error);
123
+ if (native !== undefined) return native;
124
+ }
125
+ const scope = {
126
+ stage,
127
+ ...(resource === undefined ? {} : { resource }),
128
+ };
129
+ // Names, not classes, so an error from another copy of a module still
130
+ // classifies, as it does across test module resets.
131
+ const name = error instanceof Error ? error.name : null;
132
+ // A selection that became stale is a race between checks, not a failure.
133
+ if (name === "StaleReleaseCatalogError") return null;
134
+ if (name === "UpdateHttpError") {
135
+ return {
136
+ ...scope,
137
+ reason: "http",
138
+ httpStatus: (error as UpdateHttpError).status,
139
+ };
140
+ }
141
+ if (name === "InvalidUpdateResponseError" || name === "SyntaxError") {
142
+ return { ...scope, reason: "invalid_response" };
143
+ }
144
+ if (error instanceof Error && error.message === "Request timed out") {
145
+ return { ...scope, reason: "network", transport: "timeout" };
146
+ }
147
+ if (name === "AbortError") {
148
+ return { ...scope, reason: "network", transport: "cancelled" };
149
+ }
150
+ // Expo's fetch rejects with "fetch failed: <native exception>" when no
151
+ // response arrives, and names a canceled request.
152
+ if (error instanceof Error && error.message.startsWith("fetch failed:")) {
153
+ return {
154
+ ...scope,
155
+ reason: "network",
156
+ ...(EXPO_FETCH_CANCELED.test(error.message)
157
+ ? { transport: "cancelled" }
158
+ : {}),
159
+ };
160
+ }
161
+ if (name === "TypeError") {
162
+ // fetch rejects with a TypeError when no response arrives, without
163
+ // saying why.
164
+ return { ...scope, reason: "network" };
165
+ }
166
+ return { ...scope, reason: "unknown" };
167
+ };