@hot-updater/react-native 0.36.6 → 1.0.0-rc.0

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 (239) hide show
  1. package/android/src/main/java/com/hotupdater/BundleFileStorageService.kt +357 -6
  2. package/android/src/main/java/com/hotupdater/BundleMetadata.kt +268 -34
  3. package/android/src/main/java/com/hotupdater/HotUpdaterImpl.kt +75 -9
  4. package/android/src/main/java/com/hotupdater/NativeConfigUtils.kt +2 -16
  5. package/android/src/main/java/com/hotupdater/ReleaseCatalogCacheService.kt +166 -0
  6. package/android/src/main/java/com/hotupdater/SignatureVerifier.kt +1 -2
  7. package/android/src/newarch/HotUpdater.kt +1 -1
  8. package/android/src/newarch/HotUpdaterModule.kt +116 -0
  9. package/android/src/oldarch/HotUpdater.kt +1 -1
  10. package/android/src/oldarch/HotUpdaterModule.kt +128 -0
  11. package/android/src/oldarch/HotUpdaterSpec.kt +38 -0
  12. package/android/src/test/java/com/hotupdater/BundleFileStorageServiceTest.kt +280 -2
  13. package/android/src/test/java/com/hotupdater/HotUpdaterImplTest.kt +57 -2
  14. package/android/src/test/java/com/hotupdater/ReleaseCatalogCacheServiceTest.kt +118 -0
  15. package/ios/HotUpdater/Internal/BundleFileStorageService.swift +491 -30
  16. package/ios/HotUpdater/Internal/BundleMetadata.swift +229 -6
  17. package/ios/HotUpdater/Internal/HotUpdater.mm +122 -0
  18. package/ios/HotUpdater/Internal/HotUpdaterImpl.swift +176 -7
  19. package/ios/HotUpdater/Internal/ReleaseCatalogCacheService.swift +213 -0
  20. package/lib/commonjs/catalogCacheNative.js +44 -0
  21. package/lib/commonjs/catalogCacheNative.js.map +1 -0
  22. package/lib/commonjs/catalogCacheNative.spec.js +31 -0
  23. package/lib/commonjs/catalogCacheNative.spec.js.map +1 -0
  24. package/lib/commonjs/checkForUpdate.js +231 -60
  25. package/lib/commonjs/checkForUpdate.js.map +1 -1
  26. package/lib/commonjs/checkForUpdate.releaseCatalog.spec.js +604 -0
  27. package/lib/commonjs/checkForUpdate.releaseCatalog.spec.js.map +1 -0
  28. package/lib/commonjs/error.js +8 -1
  29. package/lib/commonjs/error.js.map +1 -1
  30. package/lib/commonjs/{fetchUpdateInfo.js → fetchJSON.js} +11 -14
  31. package/lib/commonjs/fetchJSON.js.map +1 -0
  32. package/lib/commonjs/httpClient.js +153 -0
  33. package/lib/commonjs/httpClient.js.map +1 -0
  34. package/lib/commonjs/httpClient.spec.js +102 -0
  35. package/lib/commonjs/httpClient.spec.js.map +1 -0
  36. package/lib/commonjs/index.js +48 -71
  37. package/lib/commonjs/index.js.map +1 -1
  38. package/lib/commonjs/index.spec.js +117 -161
  39. package/lib/commonjs/index.spec.js.map +1 -1
  40. package/lib/commonjs/iosInterop.spec.js +18 -0
  41. package/lib/commonjs/iosInterop.spec.js.map +1 -0
  42. package/lib/commonjs/native.js +222 -55
  43. package/lib/commonjs/native.js.map +1 -1
  44. package/lib/commonjs/native.spec.js +226 -12
  45. package/lib/commonjs/native.spec.js.map +1 -1
  46. package/lib/commonjs/notifyAppReadyAnalytics.boundary.spec.js +218 -0
  47. package/lib/commonjs/notifyAppReadyAnalytics.boundary.spec.js.map +1 -0
  48. package/lib/commonjs/notifyAppReadyAnalytics.js +133 -0
  49. package/lib/commonjs/notifyAppReadyAnalytics.js.map +1 -0
  50. package/lib/commonjs/notifyAppReadyAnalytics.spec.js +246 -0
  51. package/lib/commonjs/notifyAppReadyAnalytics.spec.js.map +1 -0
  52. package/lib/commonjs/notifyAppReadyAnalytics.test-utils.js +24 -0
  53. package/lib/commonjs/notifyAppReadyAnalytics.test-utils.js.map +1 -0
  54. package/lib/commonjs/notifyAppReadyAnalytics.transport.spec.js +117 -0
  55. package/lib/commonjs/notifyAppReadyAnalytics.transport.spec.js.map +1 -0
  56. package/lib/commonjs/releaseCatalogCache.js +179 -0
  57. package/lib/commonjs/releaseCatalogCache.js.map +1 -0
  58. package/lib/commonjs/releaseCatalogCache.spec.js +195 -0
  59. package/lib/commonjs/releaseCatalogCache.spec.js.map +1 -0
  60. package/lib/commonjs/releaseCatalogCacheNativeContract.spec.js +64 -0
  61. package/lib/commonjs/releaseCatalogCacheNativeContract.spec.js.map +1 -0
  62. package/lib/commonjs/sdkVersion.js +1 -1
  63. package/lib/commonjs/sdkVersion.js.map +1 -1
  64. package/lib/commonjs/specs/NativeHotUpdater.js.map +1 -1
  65. package/lib/commonjs/types.js +0 -12
  66. package/lib/commonjs/types.js.map +1 -1
  67. package/lib/commonjs/wrap.js +7 -82
  68. package/lib/commonjs/wrap.js.map +1 -1
  69. package/lib/commonjs/wrap.spec.js +203 -56
  70. package/lib/commonjs/wrap.spec.js.map +1 -1
  71. package/lib/commonjs/wrap.types.js +6 -0
  72. package/lib/commonjs/wrap.types.js.map +1 -0
  73. package/lib/module/catalogCacheNative.js +36 -0
  74. package/lib/module/catalogCacheNative.js.map +1 -0
  75. package/lib/module/catalogCacheNative.spec.js +31 -0
  76. package/lib/module/catalogCacheNative.spec.js.map +1 -0
  77. package/lib/module/checkForUpdate.js +233 -62
  78. package/lib/module/checkForUpdate.js.map +1 -1
  79. package/lib/module/checkForUpdate.releaseCatalog.spec.js +603 -0
  80. package/lib/module/checkForUpdate.releaseCatalog.spec.js.map +1 -0
  81. package/lib/module/error.js +6 -0
  82. package/lib/module/error.js.map +1 -1
  83. package/lib/module/{fetchUpdateInfo.js → fetchJSON.js} +9 -12
  84. package/lib/module/fetchJSON.js.map +1 -0
  85. package/lib/module/httpClient.js +148 -0
  86. package/lib/module/httpClient.js.map +1 -0
  87. package/lib/module/httpClient.spec.js +102 -0
  88. package/lib/module/httpClient.spec.js.map +1 -0
  89. package/lib/module/index.js +49 -66
  90. package/lib/module/index.js.map +1 -1
  91. package/lib/module/index.spec.js +117 -161
  92. package/lib/module/index.spec.js.map +1 -1
  93. package/lib/module/iosInterop.spec.js +18 -0
  94. package/lib/module/iosInterop.spec.js.map +1 -0
  95. package/lib/module/native.js +206 -51
  96. package/lib/module/native.js.map +1 -1
  97. package/lib/module/native.spec.js +226 -12
  98. package/lib/module/native.spec.js.map +1 -1
  99. package/lib/module/notifyAppReadyAnalytics.boundary.spec.js +217 -0
  100. package/lib/module/notifyAppReadyAnalytics.boundary.spec.js.map +1 -0
  101. package/lib/module/notifyAppReadyAnalytics.js +128 -0
  102. package/lib/module/notifyAppReadyAnalytics.js.map +1 -0
  103. package/lib/module/notifyAppReadyAnalytics.spec.js +245 -0
  104. package/lib/module/notifyAppReadyAnalytics.spec.js.map +1 -0
  105. package/lib/module/notifyAppReadyAnalytics.test-utils.js +18 -0
  106. package/lib/module/notifyAppReadyAnalytics.test-utils.js.map +1 -0
  107. package/lib/module/notifyAppReadyAnalytics.transport.spec.js +116 -0
  108. package/lib/module/notifyAppReadyAnalytics.transport.spec.js.map +1 -0
  109. package/lib/module/releaseCatalogCache.js +171 -0
  110. package/lib/module/releaseCatalogCache.js.map +1 -0
  111. package/lib/module/releaseCatalogCache.spec.js +195 -0
  112. package/lib/module/releaseCatalogCache.spec.js.map +1 -0
  113. package/lib/module/releaseCatalogCacheNativeContract.spec.js +64 -0
  114. package/lib/module/releaseCatalogCacheNativeContract.spec.js.map +1 -0
  115. package/lib/module/sdkVersion.js +1 -1
  116. package/lib/module/sdkVersion.js.map +1 -1
  117. package/lib/module/specs/NativeHotUpdater.js.map +1 -1
  118. package/lib/module/types.js +0 -12
  119. package/lib/module/types.js.map +1 -1
  120. package/lib/module/wrap.js +6 -81
  121. package/lib/module/wrap.js.map +1 -1
  122. package/lib/module/wrap.spec.js +203 -56
  123. package/lib/module/wrap.spec.js.map +1 -1
  124. package/lib/module/wrap.types.js +4 -0
  125. package/lib/module/wrap.types.js.map +1 -0
  126. package/lib/typescript/commonjs/catalogCacheNative.d.ts +4 -0
  127. package/lib/typescript/commonjs/catalogCacheNative.d.ts.map +1 -0
  128. package/lib/typescript/commonjs/checkForUpdate.d.ts +14 -4
  129. package/lib/typescript/commonjs/checkForUpdate.d.ts.map +1 -1
  130. package/lib/typescript/commonjs/error.d.ts +3 -0
  131. package/lib/typescript/commonjs/error.d.ts.map +1 -1
  132. package/lib/typescript/commonjs/fetchJSON.d.ts +6 -0
  133. package/lib/typescript/commonjs/fetchJSON.d.ts.map +1 -0
  134. package/lib/typescript/commonjs/httpClient.d.ts +56 -0
  135. package/lib/typescript/commonjs/httpClient.d.ts.map +1 -0
  136. package/lib/typescript/commonjs/index.d.ts +32 -41
  137. package/lib/typescript/commonjs/index.d.ts.map +1 -1
  138. package/lib/typescript/commonjs/native.d.ts +83 -18
  139. package/lib/typescript/commonjs/native.d.ts.map +1 -1
  140. package/lib/typescript/commonjs/notifyAppReadyAnalytics.d.ts +13 -0
  141. package/lib/typescript/commonjs/notifyAppReadyAnalytics.d.ts.map +1 -0
  142. package/lib/typescript/commonjs/notifyAppReadyAnalytics.test-utils.d.ts +8 -0
  143. package/lib/typescript/commonjs/notifyAppReadyAnalytics.test-utils.d.ts.map +1 -0
  144. package/lib/typescript/commonjs/releaseCatalogCache.d.ts +25 -0
  145. package/lib/typescript/commonjs/releaseCatalogCache.d.ts.map +1 -0
  146. package/lib/typescript/commonjs/sdkVersion.d.ts +1 -1
  147. package/lib/typescript/commonjs/sdkVersion.d.ts.map +1 -1
  148. package/lib/typescript/commonjs/specs/NativeHotUpdater.d.ts +40 -5
  149. package/lib/typescript/commonjs/specs/NativeHotUpdater.d.ts.map +1 -1
  150. package/lib/typescript/commonjs/types.d.ts +0 -118
  151. package/lib/typescript/commonjs/types.d.ts.map +1 -1
  152. package/lib/typescript/commonjs/wrap.d.ts +2 -191
  153. package/lib/typescript/commonjs/wrap.d.ts.map +1 -1
  154. package/lib/typescript/commonjs/wrap.types.d.ts +65 -0
  155. package/lib/typescript/commonjs/wrap.types.d.ts.map +1 -0
  156. package/lib/typescript/module/catalogCacheNative.d.ts +4 -0
  157. package/lib/typescript/module/catalogCacheNative.d.ts.map +1 -0
  158. package/lib/typescript/module/checkForUpdate.d.ts +14 -4
  159. package/lib/typescript/module/checkForUpdate.d.ts.map +1 -1
  160. package/lib/typescript/module/error.d.ts +3 -0
  161. package/lib/typescript/module/error.d.ts.map +1 -1
  162. package/lib/typescript/module/fetchJSON.d.ts +6 -0
  163. package/lib/typescript/module/fetchJSON.d.ts.map +1 -0
  164. package/lib/typescript/module/httpClient.d.ts +56 -0
  165. package/lib/typescript/module/httpClient.d.ts.map +1 -0
  166. package/lib/typescript/module/index.d.ts +32 -41
  167. package/lib/typescript/module/index.d.ts.map +1 -1
  168. package/lib/typescript/module/native.d.ts +83 -18
  169. package/lib/typescript/module/native.d.ts.map +1 -1
  170. package/lib/typescript/module/notifyAppReadyAnalytics.d.ts +13 -0
  171. package/lib/typescript/module/notifyAppReadyAnalytics.d.ts.map +1 -0
  172. package/lib/typescript/module/notifyAppReadyAnalytics.test-utils.d.ts +8 -0
  173. package/lib/typescript/module/notifyAppReadyAnalytics.test-utils.d.ts.map +1 -0
  174. package/lib/typescript/module/releaseCatalogCache.d.ts +25 -0
  175. package/lib/typescript/module/releaseCatalogCache.d.ts.map +1 -0
  176. package/lib/typescript/module/sdkVersion.d.ts +1 -1
  177. package/lib/typescript/module/sdkVersion.d.ts.map +1 -1
  178. package/lib/typescript/module/specs/NativeHotUpdater.d.ts +40 -5
  179. package/lib/typescript/module/specs/NativeHotUpdater.d.ts.map +1 -1
  180. package/lib/typescript/module/types.d.ts +0 -118
  181. package/lib/typescript/module/types.d.ts.map +1 -1
  182. package/lib/typescript/module/wrap.d.ts +2 -191
  183. package/lib/typescript/module/wrap.d.ts.map +1 -1
  184. package/lib/typescript/module/wrap.types.d.ts +65 -0
  185. package/lib/typescript/module/wrap.types.d.ts.map +1 -0
  186. package/package.json +6 -24
  187. package/src/catalogCacheNative.spec.ts +63 -0
  188. package/src/catalogCacheNative.ts +74 -0
  189. package/src/checkForUpdate.releaseCatalog.spec.ts +596 -0
  190. package/src/checkForUpdate.ts +368 -97
  191. package/src/error.ts +7 -0
  192. package/src/{fetchUpdateInfo.ts → fetchJSON.ts} +8 -15
  193. package/src/httpClient.spec.ts +132 -0
  194. package/src/httpClient.ts +261 -0
  195. package/src/index.spec.ts +110 -203
  196. package/src/index.ts +70 -93
  197. package/src/iosInterop.spec.ts +30 -0
  198. package/src/native.spec.ts +226 -10
  199. package/src/native.ts +385 -83
  200. package/src/notifyAppReadyAnalytics.boundary.spec.ts +234 -0
  201. package/src/notifyAppReadyAnalytics.spec.ts +243 -0
  202. package/src/notifyAppReadyAnalytics.test-utils.ts +31 -0
  203. package/src/notifyAppReadyAnalytics.transport.spec.ts +130 -0
  204. package/src/notifyAppReadyAnalytics.ts +194 -0
  205. package/src/releaseCatalogCache.spec.ts +282 -0
  206. package/src/releaseCatalogCache.ts +341 -0
  207. package/src/releaseCatalogCacheNativeContract.spec.ts +80 -0
  208. package/src/sdkVersion.ts +1 -1
  209. package/src/specs/NativeHotUpdater.ts +51 -5
  210. package/src/types.ts +0 -140
  211. package/src/wrap.spec.ts +251 -56
  212. package/src/wrap.tsx +23 -318
  213. package/src/wrap.types.ts +78 -0
  214. package/android/src/main/java/com/hotupdater/StringResourceUtils.kt +0 -45
  215. package/app.plugin.js +0 -1
  216. package/ios/HotUpdater/Internal/HotUpdaterCrashHandler.h +0 -7
  217. package/ios/HotUpdater/Internal/HotUpdaterCrashHandler.mm +0 -4
  218. package/lib/commonjs/DefaultResolver.js +0 -50
  219. package/lib/commonjs/DefaultResolver.js.map +0 -1
  220. package/lib/commonjs/DefaultResolver.spec.js +0 -144
  221. package/lib/commonjs/DefaultResolver.spec.js.map +0 -1
  222. package/lib/commonjs/fetchUpdateInfo.js.map +0 -1
  223. package/lib/module/DefaultResolver.js +0 -46
  224. package/lib/module/DefaultResolver.js.map +0 -1
  225. package/lib/module/DefaultResolver.spec.js +0 -144
  226. package/lib/module/DefaultResolver.spec.js.map +0 -1
  227. package/lib/module/fetchUpdateInfo.js.map +0 -1
  228. package/lib/typescript/commonjs/DefaultResolver.d.ts +0 -11
  229. package/lib/typescript/commonjs/DefaultResolver.d.ts.map +0 -1
  230. package/lib/typescript/commonjs/fetchUpdateInfo.d.ts +0 -7
  231. package/lib/typescript/commonjs/fetchUpdateInfo.d.ts.map +0 -1
  232. package/lib/typescript/module/DefaultResolver.d.ts +0 -11
  233. package/lib/typescript/module/DefaultResolver.d.ts.map +0 -1
  234. package/lib/typescript/module/fetchUpdateInfo.d.ts +0 -7
  235. package/lib/typescript/module/fetchUpdateInfo.d.ts.map +0 -1
  236. package/plugin/build/transformers.js +0 -314
  237. package/plugin/build/withHotUpdater.js +0 -228
  238. package/src/DefaultResolver.spec.ts +0 -193
  239. package/src/DefaultResolver.ts +0 -63
@@ -2,16 +2,92 @@ import Foundation
2
2
 
3
3
  // MARK: - BundleMetadata
4
4
 
5
+ /// Strategy used to derive update identities for OTA updates.
6
+ public enum UpdateStrategy: String, Codable {
7
+ case appVersion
8
+ case fingerprint
9
+ }
10
+
11
+ public struct PendingBundleTransition: Codable {
12
+ let fromBundleId: String
13
+ let toBundleId: String
14
+ let updateStrategy: UpdateStrategy
15
+
16
+ init(fromBundleId: String, toBundleId: String, updateStrategy: UpdateStrategy) {
17
+ self.fromBundleId = fromBundleId
18
+ self.toBundleId = toBundleId
19
+ self.updateStrategy = updateStrategy
20
+ }
21
+ }
22
+
23
+ public struct CatalogHighWater: Codable, Equatable {
24
+ let generation: Int64
25
+ let catalogHash: String
26
+ }
27
+
28
+ public struct PersistedSelection: Codable {
29
+ let kind: String
30
+ let releaseId: String?
31
+ let bundleId: String
32
+ let catalogId: String?
33
+ let scopeKey: String?
34
+ let generation: Int64?
35
+ let catalogHash: String?
36
+ let channel: String
37
+ let selectionContextHash: String?
38
+
39
+ static func legacyBundle(_ bundleId: String) -> PersistedSelection {
40
+ PersistedSelection(
41
+ kind: "BUNDLE",
42
+ releaseId: nil,
43
+ bundleId: bundleId,
44
+ catalogId: nil,
45
+ scopeKey: nil,
46
+ generation: nil,
47
+ catalogHash: nil,
48
+ channel: "",
49
+ selectionContextHash: nil
50
+ )
51
+ }
52
+
53
+ var dictionary: [String: Any] {
54
+ [
55
+ "kind": kind,
56
+ "releaseId": releaseId ?? NSNull(),
57
+ "bundleId": bundleId,
58
+ "catalogId": catalogId ?? NSNull(),
59
+ "scopeKey": scopeKey ?? NSNull(),
60
+ "generation": generation.map(NSNumber.init(value:)) ?? NSNull(),
61
+ "catalogHash": catalogHash ?? NSNull(),
62
+ "channel": channel,
63
+ "selectionContextHash": selectionContextHash ?? NSNull()
64
+ ]
65
+ }
66
+ }
67
+
68
+ public struct PendingSelectionTransition: Codable {
69
+ let fromReleaseId: String?
70
+ let fromBundleId: String
71
+ let toReleaseId: String?
72
+ let toBundleId: String
73
+ }
74
+
5
75
  /// Bundle metadata for managing stable/staging bundles and verification state
6
76
  public struct BundleMetadata: Codable {
7
- static let schemaVersion = "metadata-v1"
77
+ static let schemaVersion = "metadata-v2"
8
78
  static let metadataFilename = "metadata.json"
9
79
 
10
80
  let schema: String
11
81
  var isolationKey: String?
12
82
  var stableBundleId: String?
13
83
  var stagingBundleId: String?
84
+ var stableSelection: PersistedSelection?
85
+ var stagingSelection: PersistedSelection?
14
86
  var verificationPending: Bool
87
+ var pendingTransition: PendingBundleTransition?
88
+ var pendingSelectionTransition: PendingSelectionTransition?
89
+ var highestSeenCatalogs: [String: CatalogHighWater]
90
+ var currentSelectionContexts: [String: String]
15
91
  var updatedAt: Double
16
92
 
17
93
  enum CodingKeys: String, CodingKey {
@@ -19,7 +95,13 @@ public struct BundleMetadata: Codable {
19
95
  case isolationKey = "isolation_key"
20
96
  case stableBundleId = "stable_bundle_id"
21
97
  case stagingBundleId = "staging_bundle_id"
98
+ case stableSelection = "stable_selection"
99
+ case stagingSelection = "staging_selection"
22
100
  case verificationPending = "verification_pending"
101
+ case pendingTransition = "pending_transition"
102
+ case pendingSelectionTransition = "pending_selection_transition"
103
+ case highestSeenCatalogs = "highest_seen_catalogs"
104
+ case currentSelectionContexts = "current_selection_contexts"
23
105
  case updatedAt = "updated_at"
24
106
  }
25
107
 
@@ -28,17 +110,47 @@ public struct BundleMetadata: Codable {
28
110
  isolationKey: String? = nil,
29
111
  stableBundleId: String? = nil,
30
112
  stagingBundleId: String? = nil,
113
+ stableSelection: PersistedSelection? = nil,
114
+ stagingSelection: PersistedSelection? = nil,
31
115
  verificationPending: Bool = false,
116
+ pendingTransition: PendingBundleTransition? = nil,
117
+ pendingSelectionTransition: PendingSelectionTransition? = nil,
118
+ highestSeenCatalogs: [String: CatalogHighWater] = [:],
119
+ currentSelectionContexts: [String: String] = [:],
32
120
  updatedAt: Double = Date().timeIntervalSince1970 * 1000
33
121
  ) {
34
122
  self.schema = schema
35
123
  self.isolationKey = isolationKey
36
124
  self.stableBundleId = stableBundleId
37
125
  self.stagingBundleId = stagingBundleId
126
+ self.stableSelection = stableSelection
127
+ self.stagingSelection = stagingSelection
38
128
  self.verificationPending = verificationPending
129
+ self.pendingTransition = pendingTransition
130
+ self.pendingSelectionTransition = pendingSelectionTransition
131
+ self.highestSeenCatalogs = highestSeenCatalogs
132
+ self.currentSelectionContexts = currentSelectionContexts
39
133
  self.updatedAt = updatedAt
40
134
  }
41
135
 
136
+ public init(from decoder: Decoder) throws {
137
+ let values = try decoder.container(keyedBy: CodingKeys.self)
138
+ schema = BundleMetadata.schemaVersion
139
+ isolationKey = try values.decodeIfPresent(String.self, forKey: .isolationKey)
140
+ stableBundleId = try values.decodeIfPresent(String.self, forKey: .stableBundleId)
141
+ stagingBundleId = try values.decodeIfPresent(String.self, forKey: .stagingBundleId)
142
+ stableSelection = try values.decodeIfPresent(PersistedSelection.self, forKey: .stableSelection)
143
+ ?? stableBundleId.map(PersistedSelection.legacyBundle)
144
+ stagingSelection = try values.decodeIfPresent(PersistedSelection.self, forKey: .stagingSelection)
145
+ ?? stagingBundleId.map(PersistedSelection.legacyBundle)
146
+ verificationPending = try values.decodeIfPresent(Bool.self, forKey: .verificationPending) ?? false
147
+ pendingTransition = try values.decodeIfPresent(PendingBundleTransition.self, forKey: .pendingTransition)
148
+ pendingSelectionTransition = try values.decodeIfPresent(PendingSelectionTransition.self, forKey: .pendingSelectionTransition)
149
+ highestSeenCatalogs = try values.decodeIfPresent([String: CatalogHighWater].self, forKey: .highestSeenCatalogs) ?? [:]
150
+ currentSelectionContexts = try values.decodeIfPresent([String: String].self, forKey: .currentSelectionContexts) ?? [:]
151
+ updatedAt = try values.decodeIfPresent(Double.self, forKey: .updatedAt) ?? Date().timeIntervalSince1970 * 1000
152
+ }
153
+
42
154
  static func load(from file: URL, expectedIsolationKey: String) -> BundleMetadata? {
43
155
  guard FileManager.default.fileExists(atPath: file.path) else {
44
156
  print("[BundleMetadata] Metadata file does not exist: \(file.path)")
@@ -80,7 +192,7 @@ public struct BundleMetadata: Codable {
80
192
  try FileManager.default.createDirectory(at: directory, withIntermediateDirectories: true)
81
193
  }
82
194
 
83
- try data.write(to: file)
195
+ try data.write(to: file, options: .atomic)
84
196
  print("[BundleMetadata] Saved metadata to file: \(file.path)")
85
197
  return true
86
198
  } catch {
@@ -202,15 +314,36 @@ public struct LaunchSelection {
202
314
  let shouldRollbackOnCrash: Bool
203
315
  }
204
316
 
317
+ public enum LaunchReportStatus: String, Codable {
318
+ case unchanged = "UNCHANGED"
319
+ case updateApplied = "UPDATE_APPLIED"
320
+ case recovered = "RECOVERED"
321
+ }
322
+
205
323
  public struct LaunchReport: Codable {
206
324
  static let launchReportFilename = "launch-report.json"
207
325
 
208
- let status: String
209
- let crashedBundleId: String?
326
+ let status: LaunchReportStatus
327
+ let fromReleaseId: String?
328
+ let fromBundleId: String?
329
+ let toReleaseId: String?
330
+ let toBundleId: String?
331
+ let updateStrategy: UpdateStrategy?
210
332
 
211
- init(status: String = "STABLE", crashedBundleId: String? = nil) {
333
+ init(
334
+ status: LaunchReportStatus = .unchanged,
335
+ fromReleaseId: String? = nil,
336
+ fromBundleId: String? = nil,
337
+ toReleaseId: String? = nil,
338
+ toBundleId: String? = nil,
339
+ updateStrategy: UpdateStrategy? = nil
340
+ ) {
212
341
  self.status = status
213
- self.crashedBundleId = crashedBundleId
342
+ self.fromReleaseId = fromReleaseId
343
+ self.fromBundleId = fromBundleId
344
+ self.toReleaseId = toReleaseId
345
+ self.toBundleId = toBundleId
346
+ self.updateStrategy = updateStrategy
214
347
  }
215
348
 
216
349
  static func load(from file: URL) -> LaunchReport? {
@@ -244,3 +377,93 @@ public struct LaunchReport: Codable {
244
377
  }
245
378
  }
246
379
  }
380
+
381
+ public struct InstallIdentity: Codable {
382
+ static let installIdentityFilename = "install-identity.json"
383
+
384
+ let installId: String
385
+
386
+ init(installId: String = UUID().uuidString) {
387
+ self.installId = installId
388
+ }
389
+
390
+ static func load(from file: URL) -> InstallIdentity? {
391
+ guard FileManager.default.fileExists(atPath: file.path) else {
392
+ return nil
393
+ }
394
+
395
+ do {
396
+ let data = try Data(contentsOf: file)
397
+ let identity = try JSONDecoder().decode(InstallIdentity.self, from: data)
398
+ return identity.installId.isEmpty ? nil : identity
399
+ } catch {
400
+ print("[InstallIdentity] Failed to load install identity: \(error)")
401
+ return nil
402
+ }
403
+ }
404
+
405
+ func save(to file: URL) -> Bool {
406
+ do {
407
+ let encoder = JSONEncoder()
408
+ encoder.outputFormatting = .prettyPrinted
409
+ let data = try encoder.encode(self)
410
+ let directory = file.deletingLastPathComponent()
411
+ if !FileManager.default.fileExists(atPath: directory.path) {
412
+ try FileManager.default.createDirectory(at: directory, withIntermediateDirectories: true)
413
+ }
414
+ try data.write(to: file)
415
+ return true
416
+ } catch {
417
+ print("[InstallIdentity] Failed to save install identity: \(error)")
418
+ return false
419
+ }
420
+ }
421
+ }
422
+
423
+ public struct UserIdentity: Codable {
424
+ static let userIdentityFilename = "user-identity.json"
425
+
426
+ let userId: String?
427
+ let username: String?
428
+
429
+ init(userId: String?, username: String?) {
430
+ self.userId = userId
431
+ self.username = username
432
+ }
433
+
434
+ var isEmpty: Bool {
435
+ userId == nil && username == nil
436
+ }
437
+
438
+ static func load(from file: URL) -> UserIdentity? {
439
+ guard FileManager.default.fileExists(atPath: file.path) else {
440
+ return nil
441
+ }
442
+
443
+ do {
444
+ let data = try Data(contentsOf: file)
445
+ let identity = try JSONDecoder().decode(UserIdentity.self, from: data)
446
+ return identity.isEmpty ? nil : identity
447
+ } catch {
448
+ print("[UserIdentity] Failed to load user identity: \(error)")
449
+ return nil
450
+ }
451
+ }
452
+
453
+ func save(to file: URL) -> Bool {
454
+ do {
455
+ let encoder = JSONEncoder()
456
+ encoder.outputFormatting = .prettyPrinted
457
+ let data = try encoder.encode(self)
458
+ let directory = file.deletingLastPathComponent()
459
+ if !FileManager.default.fileExists(atPath: directory.path) {
460
+ try FileManager.default.createDirectory(at: directory, withIntermediateDirectories: true)
461
+ }
462
+ try data.write(to: file)
463
+ return true
464
+ } catch {
465
+ print("[UserIdentity] Failed to save user identity: \(error)")
466
+ return false
467
+ }
468
+ }
469
+ }
@@ -267,6 +267,11 @@ extern "C" NSString *HotUpdaterGetMinBundleId(void)
267
267
  return uuid;
268
268
  }
269
269
 
270
+ extern "C" void *HotUpdaterCopyMinBundleId(void)
271
+ {
272
+ return (__bridge_retained void *)HotUpdaterGetMinBundleId();
273
+ }
274
+
270
275
 
271
276
  // Define Notification names used for observing Swift Core
272
277
  NSNotificationName const HotUpdaterDownloadProgressUpdateNotification = @"HotUpdaterDownloadProgressUpdate";
@@ -607,11 +612,51 @@ RCT_EXPORT_MODULE();
607
612
  if (params.channel()) {
608
613
  paramDict[@"channel"] = params.channel();
609
614
  }
615
+ if (params.selection()) {
616
+ paramDict[@"selection"] = params.selection();
617
+ }
610
618
 
611
619
  HotUpdaterImpl *impl = [HotUpdater sharedImpl];
612
620
  [impl updateBundle:paramDict resolver:resolve rejecter:reject];
613
621
  }
614
622
 
623
+ - (NSNumber *)acceptReleaseCatalog:(NSDictionary *)params {
624
+ return @([[HotUpdater sharedImpl] acceptReleaseCatalog:params]);
625
+ }
626
+
627
+ - (NSDictionary *)getActiveUpdateState {
628
+ return [[HotUpdater sharedImpl] getActiveUpdateState];
629
+ }
630
+
631
+ - (void)getReleaseCatalogCache:(NSString *)partition
632
+ resolve:(RCTPromiseResolveBlock)resolve
633
+ reject:(RCTPromiseRejectBlock)reject {
634
+ [[HotUpdater sharedImpl] getReleaseCatalogCache:partition resolver:resolve rejecter:reject];
635
+ }
636
+
637
+ - (void)setReleaseCatalogCache:(NSString *)partition
638
+ payload:(NSString *)payload
639
+ resolve:(RCTPromiseResolveBlock)resolve
640
+ reject:(RCTPromiseRejectBlock)reject {
641
+ [[HotUpdater sharedImpl] setReleaseCatalogCache:partition payload:payload resolver:resolve rejecter:reject];
642
+ }
643
+
644
+ - (void)removeReleaseCatalogCache:(NSString *)partition
645
+ resolve:(RCTPromiseResolveBlock)resolve
646
+ reject:(RCTPromiseRejectBlock)reject {
647
+ [[HotUpdater sharedImpl] removeReleaseCatalogCache:partition resolver:resolve rejecter:reject];
648
+ }
649
+
650
+ - (NSNumber *)isReleaseSelectionCurrent:(NSDictionary *)params {
651
+ return @([[HotUpdater sharedImpl] isReleaseSelectionCurrent:params]);
652
+ }
653
+
654
+ - (void)commitReleaseSelection:(NSDictionary *)params
655
+ resolve:(RCTPromiseResolveBlock)resolve
656
+ reject:(RCTPromiseRejectBlock)reject {
657
+ [[HotUpdater sharedImpl] commitReleaseSelection:params resolver:resolve rejecter:reject];
658
+ }
659
+
615
660
  - (NSDictionary *)notifyAppReady {
616
661
  NSLog(@"[HotUpdater.mm] notifyAppReady called");
617
662
  HotUpdaterImpl *impl = [HotUpdater sharedImpl];
@@ -632,6 +677,26 @@ RCT_EXPORT_MODULE();
632
677
  return @(result);
633
678
  }
634
679
 
680
+ - (NSString *)getInstallId {
681
+ HotUpdaterImpl *impl = [HotUpdater sharedImpl];
682
+ return [impl getInstallId];
683
+ }
684
+
685
+ - (NSString * _Nullable)getUserId {
686
+ HotUpdaterImpl *impl = [HotUpdater sharedImpl];
687
+ return [impl getUserId];
688
+ }
689
+
690
+ - (NSString * _Nullable)getUsername {
691
+ HotUpdaterImpl *impl = [HotUpdater sharedImpl];
692
+ return [impl getUsername];
693
+ }
694
+
695
+ - (void)setUser:(NSString * _Nullable)userId username:(NSString * _Nullable)username {
696
+ HotUpdaterImpl *impl = [HotUpdater sharedImpl];
697
+ [impl setUser:userId username:username];
698
+ }
699
+
635
700
  - (NSString * _Nullable)getBaseURL {
636
701
  NSLog(@"[HotUpdater.mm] getBaseURL called");
637
702
  HotUpdaterImpl *impl = [HotUpdater sharedImpl];
@@ -725,6 +790,43 @@ RCT_EXPORT_METHOD(updateBundle:(NSDictionary *)params
725
790
  [impl updateBundle:params resolver:resolve rejecter:reject];
726
791
  }
727
792
 
793
+ RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD(acceptReleaseCatalog:(NSDictionary *)params) {
794
+ return @([[HotUpdater sharedImpl] acceptReleaseCatalog:params]);
795
+ }
796
+
797
+ RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD(getActiveUpdateState) {
798
+ return [[HotUpdater sharedImpl] getActiveUpdateState];
799
+ }
800
+
801
+ RCT_EXPORT_METHOD(getReleaseCatalogCache:(NSString *)partition
802
+ resolve:(RCTPromiseResolveBlock)resolve
803
+ reject:(RCTPromiseRejectBlock)reject) {
804
+ [[HotUpdater sharedImpl] getReleaseCatalogCache:partition resolver:resolve rejecter:reject];
805
+ }
806
+
807
+ RCT_EXPORT_METHOD(setReleaseCatalogCache:(NSString *)partition
808
+ payload:(NSString *)payload
809
+ resolve:(RCTPromiseResolveBlock)resolve
810
+ reject:(RCTPromiseRejectBlock)reject) {
811
+ [[HotUpdater sharedImpl] setReleaseCatalogCache:partition payload:payload resolver:resolve rejecter:reject];
812
+ }
813
+
814
+ RCT_EXPORT_METHOD(removeReleaseCatalogCache:(NSString *)partition
815
+ resolve:(RCTPromiseResolveBlock)resolve
816
+ reject:(RCTPromiseRejectBlock)reject) {
817
+ [[HotUpdater sharedImpl] removeReleaseCatalogCache:partition resolver:resolve rejecter:reject];
818
+ }
819
+
820
+ RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD(isReleaseSelectionCurrent:(NSDictionary *)params) {
821
+ return @([[HotUpdater sharedImpl] isReleaseSelectionCurrent:params]);
822
+ }
823
+
824
+ RCT_EXPORT_METHOD(commitReleaseSelection:(NSDictionary *)params
825
+ resolve:(RCTPromiseResolveBlock)resolve
826
+ reject:(RCTPromiseRejectBlock)reject) {
827
+ [[HotUpdater sharedImpl] commitReleaseSelection:params resolver:resolve rejecter:reject];
828
+ }
829
+
728
830
  RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD(notifyAppReady) {
729
831
  NSLog(@"[HotUpdater.mm] notifyAppReady called");
730
832
  HotUpdaterImpl *impl = [HotUpdater sharedImpl];
@@ -745,6 +847,26 @@ RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD(clearCrashHistory) {
745
847
  return @(result);
746
848
  }
747
849
 
850
+ RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD(getInstallId) {
851
+ HotUpdaterImpl *impl = [HotUpdater sharedImpl];
852
+ return [impl getInstallId];
853
+ }
854
+
855
+ RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD(getUserId) {
856
+ HotUpdaterImpl *impl = [HotUpdater sharedImpl];
857
+ return [impl getUserId];
858
+ }
859
+
860
+ RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD(getUsername) {
861
+ HotUpdaterImpl *impl = [HotUpdater sharedImpl];
862
+ return [impl getUsername];
863
+ }
864
+
865
+ RCT_EXPORT_METHOD(setUser:(NSString * _Nullable)userId username:(NSString * _Nullable)username) {
866
+ HotUpdaterImpl *impl = [HotUpdater sharedImpl];
867
+ [impl setUser:userId username:username];
868
+ }
869
+
748
870
  RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD(getBaseURL) {
749
871
  NSLog(@"[HotUpdater.mm] getBaseURL called");
750
872
  HotUpdaterImpl *impl = [HotUpdater sharedImpl];