@hot-updater/react-native 1.0.6 → 1.0.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/android/src/newarch/HotUpdaterModule.kt +108 -57
  2. package/android/src/oldarch/HotUpdaterModule.kt +109 -57
  3. package/android/src/oldarch/HotUpdaterSpec.kt +5 -0
  4. package/ios/HotUpdater/Interface/HotUpdaterInterface.h +3 -0
  5. package/ios/HotUpdater/Internal/HotUpdater.mm +49 -0
  6. package/ios/HotUpdater/Internal/HotUpdaterImpl.swift +150 -62
  7. package/lib/commonjs/checkForUpdate.js +16 -9
  8. package/lib/commonjs/checkForUpdate.js.map +1 -1
  9. package/lib/commonjs/httpClient.js +14 -20
  10. package/lib/commonjs/httpClient.js.map +1 -1
  11. package/lib/commonjs/native.js +36 -10
  12. package/lib/commonjs/native.js.map +1 -1
  13. package/lib/commonjs/sdkVersion.js +1 -1
  14. package/lib/commonjs/specs/NativeHotUpdater.js +5 -0
  15. package/lib/commonjs/specs/NativeHotUpdater.js.map +1 -1
  16. package/lib/module/checkForUpdate.js +17 -10
  17. package/lib/module/checkForUpdate.js.map +1 -1
  18. package/lib/module/httpClient.js +14 -20
  19. package/lib/module/httpClient.js.map +1 -1
  20. package/lib/module/native.js +35 -10
  21. package/lib/module/native.js.map +1 -1
  22. package/lib/module/sdkVersion.js +1 -1
  23. package/lib/module/specs/NativeHotUpdater.js +7 -0
  24. package/lib/module/specs/NativeHotUpdater.js.map +1 -1
  25. package/lib/typescript/commonjs/checkForUpdate.d.ts.map +1 -1
  26. package/lib/typescript/commonjs/httpClient.d.ts +20 -12
  27. package/lib/typescript/commonjs/httpClient.d.ts.map +1 -1
  28. package/lib/typescript/commonjs/native.d.ts +17 -3
  29. package/lib/typescript/commonjs/native.d.ts.map +1 -1
  30. package/lib/typescript/commonjs/sdkVersion.d.ts +1 -1
  31. package/lib/typescript/commonjs/specs/NativeHotUpdater.d.ts +31 -7
  32. package/lib/typescript/commonjs/specs/NativeHotUpdater.d.ts.map +1 -1
  33. package/lib/typescript/module/checkForUpdate.d.ts.map +1 -1
  34. package/lib/typescript/module/httpClient.d.ts +20 -12
  35. package/lib/typescript/module/httpClient.d.ts.map +1 -1
  36. package/lib/typescript/module/native.d.ts +17 -3
  37. package/lib/typescript/module/native.d.ts.map +1 -1
  38. package/lib/typescript/module/sdkVersion.d.ts +1 -1
  39. package/lib/typescript/module/specs/NativeHotUpdater.d.ts +31 -7
  40. package/lib/typescript/module/specs/NativeHotUpdater.d.ts.map +1 -1
  41. package/package.json +4 -4
  42. package/src/checkForUpdate.ts +13 -8
  43. package/src/httpClient.ts +35 -31
  44. package/src/native.ts +53 -16
  45. package/src/sdkVersion.ts +1 -1
  46. package/src/specs/NativeHotUpdater.ts +33 -7
@@ -47,7 +47,14 @@ class HotUpdaterModule internal constructor(
47
47
  */
48
48
  private fun getInstance(): HotUpdaterImpl = HotUpdater.getInstance(mReactApplicationContext)
49
49
 
50
- private fun parseAssets(params: ReadableMap): Map<String, ChangedAssetDescriptor>? {
50
+ /**
51
+ * Every changed file with its hash and URL or Hermes patch; with
52
+ * [requireFileHash] false, also entries that hold only the Hermes patch.
53
+ */
54
+ private fun parseAssets(
55
+ params: ReadableMap,
56
+ requireFileHash: Boolean,
57
+ ): Map<String, ChangedAssetDescriptor>? {
51
58
  if (!params.hasKey("assets") || params.isNull("assets")) {
52
59
  return null
53
60
  }
@@ -59,39 +66,10 @@ class HotUpdaterModule internal constructor(
59
66
  while (iterator.hasNextKey()) {
60
67
  val assetPath = iterator.nextKey()
61
68
  val assetMap = changedAssetsMap.getMap(assetPath) ?: continue
62
- // In artifact protocol v2, an entry holds only the Hermes patch.
63
69
  val assetHash = if (assetMap.hasKey("fileHash")) assetMap.getString("fileHash") else null
70
+ if (assetHash == null && requireFileHash) continue
64
71
  val patchMap = assetMap.getMap("patch")
65
- val patch =
66
- if (patchMap != null) {
67
- val algorithm = patchMap.getString("algorithm")
68
- val baseBundleId = patchMap.getString("baseBundleId")
69
- val baseFileHash = patchMap.getString("baseFileHash")
70
- val patchFileHash = patchMap.getString("patchFileHash")
71
- val patchUrl = patchMap.getString("patchUrl")
72
- val byteSize = readSafeInteger(patchMap, "byteSize")
73
-
74
- if (
75
- algorithm != null &&
76
- baseBundleId != null &&
77
- baseFileHash != null &&
78
- patchFileHash != null &&
79
- patchUrl != null
80
- ) {
81
- BsdiffPatchDescriptor(
82
- algorithm = algorithm,
83
- baseBundleId = baseBundleId,
84
- baseFileHash = baseFileHash,
85
- patchFileHash = patchFileHash,
86
- patchUrl = patchUrl,
87
- byteSize = byteSize,
88
- )
89
- } else {
90
- null
91
- }
92
- } else {
93
- null
94
- }
72
+ val patch = parsePatch(patchMap)
95
73
  val fileMap =
96
74
  if (assetMap.hasKey("file") && !assetMap.isNull("file")) {
97
75
  assetMap.getMap("file")
@@ -138,6 +116,79 @@ class HotUpdaterModule internal constructor(
138
116
  return parsed
139
117
  }
140
118
 
119
+ private fun parsePatch(patchMap: ReadableMap?): BsdiffPatchDescriptor? {
120
+ if (patchMap == null) return null
121
+ val algorithm = patchMap.getString("algorithm")
122
+ val baseBundleId = patchMap.getString("baseBundleId")
123
+ val baseFileHash = patchMap.getString("baseFileHash")
124
+ val patchFileHash = patchMap.getString("patchFileHash")
125
+ val patchUrl = patchMap.getString("patchUrl")
126
+ val byteSize = readSafeInteger(patchMap, "byteSize")
127
+
128
+ return if (
129
+ algorithm != null &&
130
+ baseBundleId != null &&
131
+ baseFileHash != null &&
132
+ patchFileHash != null &&
133
+ patchUrl != null
134
+ ) {
135
+ BsdiffPatchDescriptor(
136
+ algorithm = algorithm,
137
+ baseBundleId = baseBundleId,
138
+ baseFileHash = baseFileHash,
139
+ patchFileHash = patchFileHash,
140
+ patchUrl = patchUrl,
141
+ byteSize = byteSize,
142
+ )
143
+ } else {
144
+ null
145
+ }
146
+ }
147
+
148
+ /**
149
+ * The resolver that asks `filesUrl` for the files native downloads one by
150
+ * one; null when `filesUrl` is missing.
151
+ */
152
+ private fun parseFileResolver(params: ReadableMap): ArtifactFileResolver? {
153
+ val filesUrl =
154
+ if (params.hasKey("filesUrl") && !params.isNull("filesUrl")) {
155
+ params.getString("filesUrl")?.takeIf { it.isNotEmpty() }
156
+ } else {
157
+ null
158
+ } ?: return null
159
+ try {
160
+ java.net.URL(filesUrl)
161
+ } catch (e: java.net.MalformedURLException) {
162
+ throw HotUpdaterException.invalidFileUrl("filesUrl", filesUrl, UpdateFailureResource.FILE)
163
+ }
164
+ val requestTimeoutMs =
165
+ if (params.hasKey("requestTimeout") && !params.isNull("requestTimeout")) {
166
+ params.getDouble("requestTimeout").toLong().takeIf { it > 0 }
167
+ } else {
168
+ null
169
+ } ?: HttpArtifactFileResolver.DEFAULT_TIMEOUT_MS
170
+ return HttpArtifactFileResolver(filesUrl, parseRequestHeaders(params), requestTimeoutMs)
171
+ }
172
+
173
+ /**
174
+ * Artifact protocol v2: the Hermes patch as the only planned asset, and
175
+ * the resolver that asks `filesUrl` for the files native downloads one by
176
+ * one; null when `filesUrl` is missing.
177
+ */
178
+ private fun parseArtifactInstall(params: ReadableMap): Pair<Map<String, ChangedAssetDescriptor>, ArtifactFileResolver?>? {
179
+ val fileResolver = parseFileResolver(params) ?: return null
180
+ val patchMap = if (params.hasKey("patch") && !params.isNull("patch")) params.getMap("patch") else null
181
+ val assetPath = patchMap?.getString("assetPath")
182
+ val patch = parsePatch(patchMap)
183
+ val assets =
184
+ if (assetPath != null && patch != null) {
185
+ mapOf(assetPath to ChangedAssetDescriptor(fileUrl = null, fileHash = "", patch = patch))
186
+ } else {
187
+ emptyMap()
188
+ }
189
+ return assets to fileResolver
190
+ }
191
+
141
192
  private fun parseSelection(params: ReadableMap): PersistedSelection? {
142
193
  if (!params.hasKey("selection") || params.isNull("selection")) return null
143
194
  val selection = params.getMap("selection") ?: return null
@@ -188,6 +239,28 @@ class HotUpdaterModule internal constructor(
188
239
  override fun updateBundle(
189
240
  params: ReadableMap,
190
241
  promise: Promise,
242
+ ) {
243
+ // Legacy: artifact protocol v1, where every file comes with its URL.
244
+ // JavaScript from the first protocol v2 SDK also sends `filesUrl`
245
+ // here, with only the Hermes patch in `assets`; its OTA Bundles must
246
+ // still install.
247
+ stage(params, promise) {
248
+ val fileResolver = parseFileResolver(params)
249
+ parseAssets(params, requireFileHash = fileResolver == null)?.let { it to fileResolver }
250
+ }
251
+ }
252
+
253
+ override fun installArtifact(
254
+ params: ReadableMap,
255
+ promise: Promise,
256
+ ) {
257
+ stage(params, promise) { parseArtifactInstall(params) }
258
+ }
259
+
260
+ private fun stage(
261
+ params: ReadableMap,
262
+ promise: Promise,
263
+ plan: () -> Pair<Map<String, ChangedAssetDescriptor>, ArtifactFileResolver?>?,
191
264
  ) {
192
265
  moduleScope.launch {
193
266
  try {
@@ -199,7 +272,8 @@ class HotUpdaterModule internal constructor(
199
272
 
200
273
  val manifestUrl = params.getString("manifestUrl")
201
274
  val manifestFileHash = params.getString("manifestFileHash")
202
- val assets = parseAssets(params)
275
+ val planned = plan()
276
+ val assets = planned?.first
203
277
  if (manifestUrl.isNullOrEmpty() || manifestFileHash.isNullOrEmpty() || assets == null) {
204
278
  promise.rejectUpdateBundle(HotUpdaterException.invalidManifestParams())
205
279
  return@launch
@@ -230,30 +304,7 @@ class HotUpdaterModule internal constructor(
230
304
  }
231
305
  }
232
306
  val selection = parseSelection(params)
233
- val filesUrl =
234
- if (params.hasKey("filesUrl") && !params.isNull("filesUrl")) {
235
- params.getString("filesUrl")
236
- } else {
237
- null
238
- }
239
- if (filesUrl != null) {
240
- try {
241
- java.net.URL(filesUrl)
242
- } catch (e: java.net.MalformedURLException) {
243
- promise.rejectUpdateBundle(
244
- HotUpdaterException.invalidFileUrl("filesUrl", filesUrl, UpdateFailureResource.FILE),
245
- )
246
- return@launch
247
- }
248
- }
249
- val requestTimeoutMs =
250
- if (params.hasKey("requestTimeout") && !params.isNull("requestTimeout")) {
251
- params.getDouble("requestTimeout").toLong().takeIf { it > 0 }
252
- } else {
253
- null
254
- } ?: HttpArtifactFileResolver.DEFAULT_TIMEOUT_MS
255
- val fileResolver =
256
- filesUrl?.let { HttpArtifactFileResolver(it, parseRequestHeaders(params), requestTimeoutMs) }
307
+ val fileResolver = planned.second
257
308
 
258
309
  val impl = getInstance()
259
310
 
@@ -47,7 +47,14 @@ class HotUpdaterModule internal constructor(
47
47
  */
48
48
  private fun getInstance(): HotUpdaterImpl = HotUpdater.getInstance(mReactApplicationContext)
49
49
 
50
- private fun parseAssets(params: ReadableMap): Map<String, ChangedAssetDescriptor>? {
50
+ /**
51
+ * Every changed file with its hash and URL or Hermes patch; with
52
+ * [requireFileHash] false, also entries that hold only the Hermes patch.
53
+ */
54
+ private fun parseAssets(
55
+ params: ReadableMap,
56
+ requireFileHash: Boolean,
57
+ ): Map<String, ChangedAssetDescriptor>? {
51
58
  if (!params.hasKey("assets") || params.isNull("assets")) {
52
59
  return null
53
60
  }
@@ -59,39 +66,10 @@ class HotUpdaterModule internal constructor(
59
66
  while (iterator.hasNextKey()) {
60
67
  val assetPath = iterator.nextKey()
61
68
  val assetMap = changedAssetsMap.getMap(assetPath) ?: continue
62
- // In artifact protocol v2, an entry holds only the Hermes patch.
63
69
  val assetHash = if (assetMap.hasKey("fileHash")) assetMap.getString("fileHash") else null
70
+ if (assetHash == null && requireFileHash) continue
64
71
  val patchMap = assetMap.getMap("patch")
65
- val patch =
66
- if (patchMap != null) {
67
- val algorithm = patchMap.getString("algorithm")
68
- val baseBundleId = patchMap.getString("baseBundleId")
69
- val baseFileHash = patchMap.getString("baseFileHash")
70
- val patchFileHash = patchMap.getString("patchFileHash")
71
- val patchUrl = patchMap.getString("patchUrl")
72
- val byteSize = readSafeInteger(patchMap, "byteSize")
73
-
74
- if (
75
- algorithm != null &&
76
- baseBundleId != null &&
77
- baseFileHash != null &&
78
- patchFileHash != null &&
79
- patchUrl != null
80
- ) {
81
- BsdiffPatchDescriptor(
82
- algorithm = algorithm,
83
- baseBundleId = baseBundleId,
84
- baseFileHash = baseFileHash,
85
- patchFileHash = patchFileHash,
86
- patchUrl = patchUrl,
87
- byteSize = byteSize,
88
- )
89
- } else {
90
- null
91
- }
92
- } else {
93
- null
94
- }
72
+ val patch = parsePatch(patchMap)
95
73
  val fileMap =
96
74
  if (assetMap.hasKey("file") && !assetMap.isNull("file")) {
97
75
  assetMap.getMap("file")
@@ -138,6 +116,79 @@ class HotUpdaterModule internal constructor(
138
116
  return parsed
139
117
  }
140
118
 
119
+ private fun parsePatch(patchMap: ReadableMap?): BsdiffPatchDescriptor? {
120
+ if (patchMap == null) return null
121
+ val algorithm = patchMap.getString("algorithm")
122
+ val baseBundleId = patchMap.getString("baseBundleId")
123
+ val baseFileHash = patchMap.getString("baseFileHash")
124
+ val patchFileHash = patchMap.getString("patchFileHash")
125
+ val patchUrl = patchMap.getString("patchUrl")
126
+ val byteSize = readSafeInteger(patchMap, "byteSize")
127
+
128
+ return if (
129
+ algorithm != null &&
130
+ baseBundleId != null &&
131
+ baseFileHash != null &&
132
+ patchFileHash != null &&
133
+ patchUrl != null
134
+ ) {
135
+ BsdiffPatchDescriptor(
136
+ algorithm = algorithm,
137
+ baseBundleId = baseBundleId,
138
+ baseFileHash = baseFileHash,
139
+ patchFileHash = patchFileHash,
140
+ patchUrl = patchUrl,
141
+ byteSize = byteSize,
142
+ )
143
+ } else {
144
+ null
145
+ }
146
+ }
147
+
148
+ /**
149
+ * The resolver that asks `filesUrl` for the files native downloads one by
150
+ * one; null when `filesUrl` is missing.
151
+ */
152
+ private fun parseFileResolver(params: ReadableMap): ArtifactFileResolver? {
153
+ val filesUrl =
154
+ if (params.hasKey("filesUrl") && !params.isNull("filesUrl")) {
155
+ params.getString("filesUrl")?.takeIf { it.isNotEmpty() }
156
+ } else {
157
+ null
158
+ } ?: return null
159
+ try {
160
+ java.net.URL(filesUrl)
161
+ } catch (e: java.net.MalformedURLException) {
162
+ throw HotUpdaterException.invalidFileUrl("filesUrl", filesUrl, UpdateFailureResource.FILE)
163
+ }
164
+ val requestTimeoutMs =
165
+ if (params.hasKey("requestTimeout") && !params.isNull("requestTimeout")) {
166
+ params.getDouble("requestTimeout").toLong().takeIf { it > 0 }
167
+ } else {
168
+ null
169
+ } ?: HttpArtifactFileResolver.DEFAULT_TIMEOUT_MS
170
+ return HttpArtifactFileResolver(filesUrl, parseRequestHeaders(params), requestTimeoutMs)
171
+ }
172
+
173
+ /**
174
+ * Artifact protocol v2: the Hermes patch as the only planned asset, and
175
+ * the resolver that asks `filesUrl` for the files native downloads one by
176
+ * one; null when `filesUrl` is missing.
177
+ */
178
+ private fun parseArtifactInstall(params: ReadableMap): Pair<Map<String, ChangedAssetDescriptor>, ArtifactFileResolver?>? {
179
+ val fileResolver = parseFileResolver(params) ?: return null
180
+ val patchMap = if (params.hasKey("patch") && !params.isNull("patch")) params.getMap("patch") else null
181
+ val assetPath = patchMap?.getString("assetPath")
182
+ val patch = parsePatch(patchMap)
183
+ val assets =
184
+ if (assetPath != null && patch != null) {
185
+ mapOf(assetPath to ChangedAssetDescriptor(fileUrl = null, fileHash = "", patch = patch))
186
+ } else {
187
+ emptyMap()
188
+ }
189
+ return assets to fileResolver
190
+ }
191
+
141
192
  private fun parseSelection(params: ReadableMap): PersistedSelection? {
142
193
  if (!params.hasKey("selection") || params.isNull("selection")) return null
143
194
  val selection = params.getMap("selection") ?: return null
@@ -192,6 +243,29 @@ class HotUpdaterModule internal constructor(
192
243
  override fun updateBundle(
193
244
  params: ReadableMap,
194
245
  promise: Promise,
246
+ ) {
247
+ // Legacy: artifact protocol v1, where every file comes with its URL.
248
+ // JavaScript from the first protocol v2 SDK also sends `filesUrl`
249
+ // here, with only the Hermes patch in `assets`; its OTA Bundles must
250
+ // still install.
251
+ stage(params, promise) {
252
+ val fileResolver = parseFileResolver(params)
253
+ parseAssets(params, requireFileHash = fileResolver == null)?.let { it to fileResolver }
254
+ }
255
+ }
256
+
257
+ @ReactMethod
258
+ override fun installArtifact(
259
+ params: ReadableMap,
260
+ promise: Promise,
261
+ ) {
262
+ stage(params, promise) { parseArtifactInstall(params) }
263
+ }
264
+
265
+ private fun stage(
266
+ params: ReadableMap,
267
+ promise: Promise,
268
+ plan: () -> Pair<Map<String, ChangedAssetDescriptor>, ArtifactFileResolver?>?,
195
269
  ) {
196
270
  moduleScope.launch {
197
271
  try {
@@ -203,7 +277,8 @@ class HotUpdaterModule internal constructor(
203
277
 
204
278
  val manifestUrl = params.getString("manifestUrl")
205
279
  val manifestFileHash = params.getString("manifestFileHash")
206
- val assets = parseAssets(params)
280
+ val planned = plan()
281
+ val assets = planned?.first
207
282
  if (manifestUrl.isNullOrEmpty() || manifestFileHash.isNullOrEmpty() || assets == null) {
208
283
  promise.rejectUpdateBundle(HotUpdaterException.invalidManifestParams())
209
284
  return@launch
@@ -234,30 +309,7 @@ class HotUpdaterModule internal constructor(
234
309
  }
235
310
  }
236
311
  val selection = parseSelection(params)
237
- val filesUrl =
238
- if (params.hasKey("filesUrl") && !params.isNull("filesUrl")) {
239
- params.getString("filesUrl")
240
- } else {
241
- null
242
- }
243
- if (filesUrl != null) {
244
- try {
245
- java.net.URL(filesUrl)
246
- } catch (e: java.net.MalformedURLException) {
247
- promise.rejectUpdateBundle(
248
- HotUpdaterException.invalidFileUrl("filesUrl", filesUrl, UpdateFailureResource.FILE),
249
- )
250
- return@launch
251
- }
252
- }
253
- val requestTimeoutMs =
254
- if (params.hasKey("requestTimeout") && !params.isNull("requestTimeout")) {
255
- params.getDouble("requestTimeout").toLong().takeIf { it > 0 }
256
- } else {
257
- null
258
- } ?: HttpArtifactFileResolver.DEFAULT_TIMEOUT_MS
259
- val fileResolver =
260
- filesUrl?.let { HttpArtifactFileResolver(it, parseRequestHeaders(params), requestTimeoutMs) }
312
+ val fileResolver = planned.second
261
313
 
262
314
  val impl = getInstance()
263
315
 
@@ -13,6 +13,11 @@ abstract class HotUpdaterSpec internal constructor(
13
13
  promise: Promise,
14
14
  )
15
15
 
16
+ abstract fun installArtifact(
17
+ params: ReadableMap,
18
+ promise: Promise,
19
+ )
20
+
16
21
  abstract fun acceptReleaseCatalog(params: ReadableMap): Boolean
17
22
 
18
23
  abstract fun getActiveUpdateState(): String
@@ -21,6 +21,9 @@ NS_ASSUME_NONNULL_BEGIN
21
21
  - (void)updateBundle:(nullable NSDictionary *)params
22
22
  resolver:(RCTPromiseResolveBlock)resolve
23
23
  rejecter:(RCTPromiseRejectBlock)reject;
24
+ - (void)installArtifact:(nullable NSDictionary *)params
25
+ resolver:(RCTPromiseResolveBlock)resolve
26
+ rejecter:(RCTPromiseRejectBlock)reject;
24
27
  - (BOOL)acceptReleaseCatalog:(nullable NSDictionary *)params;
25
28
  - (NSDictionary<NSString *, id> *)getActiveUpdateState;
26
29
  - (void)getReleaseCatalogCache:(NSString *)partition
@@ -532,6 +532,7 @@ RCT_EXPORT_MODULE();
532
532
  if (params.selection()) {
533
533
  paramDict[@"selection"] = params.selection();
534
534
  }
535
+ // Legacy: what JavaScript from the first artifact protocol v2 SDK sends.
535
536
  if (params.filesUrl()) {
536
537
  paramDict[@"filesUrl"] = params.filesUrl();
537
538
  }
@@ -546,6 +547,46 @@ RCT_EXPORT_MODULE();
546
547
  [impl updateBundle:paramDict resolver:resolve rejecter:reject];
547
548
  }
548
549
 
550
+ - (void)installArtifact:(JS::NativeHotUpdater::InstallArtifactParams &)params
551
+ resolve:(RCTPromiseResolveBlock)resolve
552
+ reject:(RCTPromiseRejectBlock)reject {
553
+ NSLog(@"[HotUpdater.mm] installArtifact called.");
554
+ NSMutableDictionary *paramDict = [NSMutableDictionary dictionary];
555
+ if (params.bundleId()) {
556
+ paramDict[@"bundleId"] = params.bundleId();
557
+ }
558
+ if (params.manifestUrl()) {
559
+ paramDict[@"manifestUrl"] = params.manifestUrl();
560
+ }
561
+ if (params.manifestFileHash()) {
562
+ paramDict[@"manifestFileHash"] = params.manifestFileHash();
563
+ }
564
+ if (params.archiveUrl()) {
565
+ paramDict[@"archiveUrl"] = params.archiveUrl();
566
+ }
567
+ if (params.patch()) {
568
+ paramDict[@"patch"] = params.patch();
569
+ }
570
+ if (params.filesUrl()) {
571
+ paramDict[@"filesUrl"] = params.filesUrl();
572
+ }
573
+ if (params.requestHeaders()) {
574
+ paramDict[@"requestHeaders"] = params.requestHeaders();
575
+ }
576
+ if (params.requestTimeout().has_value()) {
577
+ paramDict[@"requestTimeout"] = @(params.requestTimeout().value());
578
+ }
579
+ if (params.channel()) {
580
+ paramDict[@"channel"] = params.channel();
581
+ }
582
+ if (params.selection()) {
583
+ paramDict[@"selection"] = params.selection();
584
+ }
585
+
586
+ HotUpdaterImpl *impl = [HotUpdater sharedImpl];
587
+ [impl installArtifact:paramDict resolver:resolve rejecter:reject];
588
+ }
589
+
549
590
  - (NSNumber *)acceptReleaseCatalog:(NSDictionary *)params {
550
591
  return @([[HotUpdater sharedImpl] acceptReleaseCatalog:params]);
551
592
  }
@@ -711,6 +752,14 @@ RCT_EXPORT_METHOD(updateBundle:(NSDictionary *)params
711
752
  [impl updateBundle:params resolver:resolve rejecter:reject];
712
753
  }
713
754
 
755
+ RCT_EXPORT_METHOD(installArtifact:(NSDictionary *)params
756
+ resolve:(RCTPromiseResolveBlock)resolve
757
+ reject:(RCTPromiseRejectBlock)reject) {
758
+ NSLog(@"[HotUpdater.mm] installArtifact called.");
759
+ HotUpdaterImpl *impl = [HotUpdater sharedImpl];
760
+ [impl installArtifact:params resolver:resolve rejecter:reject];
761
+ }
762
+
714
763
  RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD(acceptReleaseCatalog:(NSDictionary *)params) {
715
764
  return @([[HotUpdater sharedImpl] acceptReleaseCatalog:params]);
716
765
  }