@hot-updater/react-native 1.0.8 → 1.1.1
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.
- package/HotUpdater.podspec +14 -2
- package/Package.swift +24 -3
- package/android/build.gradle +2 -6
- package/android/hot-updater-fingerprint.gradle +135 -0
- package/android/src/main/cpp/HotUpdaterRecovery.cpp +291 -21
- package/android/src/main/java/com/hotupdater/ActivityStartGate.kt +176 -15
- package/android/src/main/java/com/hotupdater/ArtifactFileResolver.kt +20 -8
- package/android/src/main/java/com/hotupdater/BundleFileStorageService.kt +145 -31
- package/android/src/main/java/com/hotupdater/CohortService.kt +10 -2
- package/android/src/main/java/com/hotupdater/FileManagerService.kt +3 -1
- package/android/src/main/java/com/hotupdater/HotUpdaterConfig.kt +249 -21
- package/android/src/main/java/com/hotupdater/HotUpdaterException.kt +8 -4
- package/android/src/main/java/com/hotupdater/HotUpdaterHeldContent.kt +92 -0
- package/android/src/main/java/com/hotupdater/HotUpdaterImpl.kt +319 -113
- package/android/src/main/java/com/hotupdater/HotUpdaterInstance.kt +87 -0
- package/android/src/main/java/com/hotupdater/HotUpdaterInstanceEnvironment.kt +323 -0
- package/android/src/main/java/com/hotupdater/HotUpdaterMainThread.kt +46 -0
- package/android/src/main/java/com/hotupdater/HotUpdaterProcess.kt +30 -0
- package/android/src/main/java/com/hotupdater/HotUpdaterRecoveryAttribution.kt +493 -0
- package/android/src/main/java/com/hotupdater/HotUpdaterRecoveryManager.kt +1541 -159
- package/android/src/main/java/com/hotupdater/HotUpdaterRecoverySlots.kt +137 -0
- package/android/src/main/java/com/hotupdater/HotUpdaterRecoveryState.kt +4 -0
- package/android/src/main/java/com/hotupdater/HotUpdaterRuntimeInstance.kt +53 -0
- package/android/src/main/java/com/hotupdater/HotUpdaterUpdateCheck.kt +149 -0
- package/android/src/main/java/com/hotupdater/InstallIdentityService.kt +12 -2
- package/android/src/main/java/com/hotupdater/NativeConfigUtils.kt +5 -6
- package/android/src/main/java/com/hotupdater/NativeUpdateCheck.kt +172 -0
- package/android/src/main/java/com/hotupdater/NativeUpdateChecker.kt +1007 -0
- package/android/src/main/java/com/hotupdater/NativeUpdateEventQueue.kt +111 -0
- package/android/src/main/java/com/hotupdater/OkHttpUpdateCheckTransport.kt +88 -0
- package/android/src/main/java/com/hotupdater/ReleaseCatalog.kt +491 -0
- package/android/src/main/java/com/hotupdater/ReleaseCatalogClient.kt +405 -0
- package/android/src/main/java/com/hotupdater/ReleaseSelection.kt +474 -0
- package/android/src/main/java/com/hotupdater/SignatureVerifier.kt +32 -10
- package/android/src/main/java/com/hotupdater/StrictJson.kt +392 -0
- package/android/src/main/java/com/hotupdater/VersionedPreferencesService.kt +24 -9
- package/android/src/main/java/com/hotupdater/WeakIdentitySet.kt +76 -0
- package/android/src/main/java/com/hotupdater/vendor/brotli/dec/BitReader.java +314 -0
- package/android/src/main/java/com/hotupdater/vendor/brotli/dec/BrotliError.java +47 -0
- package/android/src/main/java/com/hotupdater/vendor/brotli/dec/BrotliInputStream.java +174 -0
- package/android/src/main/java/com/hotupdater/vendor/brotli/dec/BrotliRuntimeException.java +21 -0
- package/android/src/main/java/com/hotupdater/vendor/brotli/dec/Context.java +58 -0
- package/android/src/main/java/com/hotupdater/vendor/brotli/dec/Decode.java +1574 -0
- package/android/src/main/java/com/hotupdater/vendor/brotli/dec/Decoder.java +72 -0
- package/android/src/main/java/com/hotupdater/vendor/brotli/dec/Dictionary.java +103 -0
- package/android/src/main/java/com/hotupdater/vendor/brotli/dec/DictionaryData.java +82 -0
- package/android/src/main/java/com/hotupdater/vendor/brotli/dec/Huffman.java +143 -0
- package/{LICENSE → android/src/main/java/com/hotupdater/vendor/brotli/dec/LICENSE} +6 -20
- package/android/src/main/java/com/hotupdater/vendor/brotli/dec/README.md +39 -0
- package/android/src/main/java/com/hotupdater/vendor/brotli/dec/State.java +100 -0
- package/android/src/main/java/com/hotupdater/vendor/brotli/dec/Transform.java +240 -0
- package/android/src/main/java/com/hotupdater/vendor/brotli/dec/Utils.java +163 -0
- package/android/src/main/resources/META-INF/LICENSE-brotli +19 -0
- package/android/src/newarch/HotUpdater.kt +53 -15
- package/android/src/newarch/HotUpdaterModule.kt +42 -17
- package/android/src/newarch/JavaScriptExceptionHooks.kt +45 -8
- package/android/src/oldarch/HotUpdater.kt +51 -12
- package/android/src/oldarch/HotUpdaterModule.kt +43 -17
- package/android/src/oldarch/HotUpdaterSpec.kt +2 -0
- package/android/src/oldarch/JavaScriptExceptionHooks.kt +25 -16
- package/ios/HotUpdater/Interface/HotUpdaterInterface.h +13 -0
- package/ios/HotUpdater/Internal/ArtifactFileResolver.swift +38 -7
- package/ios/HotUpdater/Internal/BuiltInAssetResolver.swift +10 -4
- package/ios/HotUpdater/Internal/BundleFileStorageService.swift +186 -48
- package/ios/HotUpdater/Internal/CohortService.swift +8 -0
- package/ios/HotUpdater/Internal/HotUpdater.mm +821 -71
- package/ios/HotUpdater/Internal/HotUpdaterAsync.swift +7 -0
- package/ios/HotUpdater/Internal/HotUpdaterConfig.swift +208 -25
- package/ios/HotUpdater/Internal/HotUpdaterFingerprintHash.m +10 -0
- package/ios/HotUpdater/Internal/HotUpdaterImpl.swift +940 -261
- package/ios/HotUpdater/Internal/HotUpdaterInstanceEnvironment.swift +492 -0
- package/ios/HotUpdater/Internal/HotUpdaterInstanceRouter.swift +83 -0
- package/ios/HotUpdater/Internal/HotUpdaterInterface.swift +28 -1
- package/ios/HotUpdater/Internal/HotUpdaterLaunchRecovery.swift +1553 -0
- package/ios/HotUpdater/Internal/HotUpdaterNativeUpdates.swift +353 -0
- package/ios/HotUpdater/Internal/LaunchPreparationLock.swift +54 -0
- package/ios/HotUpdater/Internal/NativeUpdateCheck.swift +2051 -0
- package/ios/HotUpdater/Internal/ReleaseCatalog.swift +634 -0
- package/ios/HotUpdater/Internal/ReleaseCatalogCacheService.swift +1 -1
- package/ios/HotUpdater/Internal/ReleaseSelection.swift +482 -0
- package/ios/HotUpdater/Internal/SignatureVerifier.swift +27 -10
- package/ios/HotUpdater/Internal/StrictJson.swift +363 -0
- package/ios/HotUpdater/Internal/URLSessionDownloadService.swift +4 -2
- package/ios/HotUpdater/Internal/UpdateFailure.swift +2 -0
- package/ios/HotUpdater/Package.swift +11 -0
- package/ios/HotUpdater/Public/HotUpdater/HotUpdater.h +4 -0
- package/ios/HotUpdater/Public/HotUpdater.h +21 -2
- package/ios/HotUpdater/Recovery/HotUpdaterRecoveryHooks.mm +66 -0
- package/ios/Plugins/HotUpdaterFingerprintPlugin/HotUpdaterFingerprintPlugin.swift +45 -0
- package/ios/scripts/generate-fingerprint-hash.zsh +85 -0
- package/lib/commonjs/checkForUpdate.js +3 -1
- package/lib/commonjs/checkForUpdate.js.map +1 -1
- package/lib/commonjs/index.js +9 -12
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/native.js +24 -79
- package/lib/commonjs/native.js.map +1 -1
- package/lib/commonjs/nativeUpdateEvents.js +66 -0
- package/lib/commonjs/nativeUpdateEvents.js.map +1 -0
- package/lib/commonjs/sdkVersion.js +1 -1
- package/lib/commonjs/specs/NativeHotUpdater.js.map +1 -1
- package/lib/module/checkForUpdate.js +3 -1
- package/lib/module/checkForUpdate.js.map +1 -1
- package/lib/module/index.js +10 -13
- package/lib/module/index.js.map +1 -1
- package/lib/module/native.js +19 -73
- package/lib/module/native.js.map +1 -1
- package/lib/module/nativeUpdateEvents.js +61 -0
- package/lib/module/nativeUpdateEvents.js.map +1 -0
- package/lib/module/sdkVersion.js +1 -1
- package/lib/module/specs/NativeHotUpdater.js.map +1 -1
- package/lib/typescript/commonjs/checkForUpdate.d.ts.map +1 -1
- package/lib/typescript/commonjs/index.d.ts +4 -14
- package/lib/typescript/commonjs/index.d.ts.map +1 -1
- package/lib/typescript/commonjs/native.d.ts +9 -46
- package/lib/typescript/commonjs/native.d.ts.map +1 -1
- package/lib/typescript/commonjs/nativeUpdateEvents.d.ts +12 -0
- package/lib/typescript/commonjs/nativeUpdateEvents.d.ts.map +1 -0
- package/lib/typescript/commonjs/sdkVersion.d.ts +1 -1
- package/lib/typescript/commonjs/specs/NativeHotUpdater.d.ts +19 -3
- package/lib/typescript/commonjs/specs/NativeHotUpdater.d.ts.map +1 -1
- package/lib/typescript/module/checkForUpdate.d.ts.map +1 -1
- package/lib/typescript/module/index.d.ts +4 -14
- package/lib/typescript/module/index.d.ts.map +1 -1
- package/lib/typescript/module/native.d.ts +9 -46
- package/lib/typescript/module/native.d.ts.map +1 -1
- package/lib/typescript/module/nativeUpdateEvents.d.ts +12 -0
- package/lib/typescript/module/nativeUpdateEvents.d.ts.map +1 -0
- package/lib/typescript/module/sdkVersion.d.ts +1 -1
- package/lib/typescript/module/specs/NativeHotUpdater.d.ts +19 -3
- package/lib/typescript/module/specs/NativeHotUpdater.d.ts.map +1 -1
- package/package.json +4 -4
- package/src/checkForUpdate.ts +7 -1
- package/src/index.ts +9 -17
- package/src/native.ts +19 -91
- package/src/nativeUpdateEvents.ts +77 -0
- package/src/sdkVersion.ts +1 -1
- package/src/specs/NativeHotUpdater.ts +20 -3
- package/android/libs/README.md +0 -50
- package/android/libs/hot-updater-brotli-dec-1.2.0.jar +0 -0
- package/android/src/bridge/BridgeReactInstance.kt +0 -38
- package/android/src/bridgeless/BridgeReactInstance.kt +0 -24
- package/android/src/main/java/com/hotupdater/ContentAppearedLog.kt +0 -31
- package/android/src/main/java/com/hotupdater/ReactIntegrationManagerBase.kt +0 -34
- package/android/src/newarch/ReactIntegrationManager.kt +0 -212
- package/android/src/oldarch/ReactIntegrationManager.kt +0 -120
|
@@ -36,16 +36,39 @@ class HotUpdaterModule internal constructor(
|
|
|
36
36
|
return value.toLong().takeIf { it in 0..9_007_199_254_740_991L }
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
+
override fun initialize() {
|
|
40
|
+
super.initialize()
|
|
41
|
+
// Each runtime has its own module, so this reaches a named instance's
|
|
42
|
+
// runtime, whose host the app doesn't expose, and main's, whose root
|
|
43
|
+
// views are looked for too; main's errors are left alone.
|
|
44
|
+
registeredInstanceName = HotUpdaterRecoveryManager.registerRuntime(mReactApplicationContext)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// The named instance whose runtime this module registered; the runtime,
|
|
48
|
+
// whichever instance's, is unregistered when it is torn down.
|
|
49
|
+
private var registeredInstanceName: String? = null
|
|
50
|
+
|
|
51
|
+
private fun unregisterRuntime() {
|
|
52
|
+
HotUpdaterRecoveryManager.unregisterRuntime(mReactApplicationContext, registeredInstanceName)
|
|
53
|
+
registeredInstanceName = null
|
|
54
|
+
}
|
|
55
|
+
|
|
39
56
|
override fun onCatalystInstanceDestroy() {
|
|
40
57
|
super.onCatalystInstanceDestroy()
|
|
58
|
+
unregisterRuntime()
|
|
41
59
|
// Cancel all ongoing coroutines when module is destroyed
|
|
42
60
|
moduleScope.cancel()
|
|
43
61
|
}
|
|
44
62
|
|
|
63
|
+
// The instance whose bundle this module's runtime runs, read from the
|
|
64
|
+
// URL React Native loaded it from.
|
|
65
|
+
private val runtimeInstance = HotUpdaterRuntimeInstance(mReactApplicationContext, { mReactApplicationContext.sourceURL })
|
|
66
|
+
|
|
45
67
|
/**
|
|
46
|
-
*
|
|
68
|
+
* The impl of this runtime's instance: main's, or a named instance's
|
|
69
|
+
* when the runtime runs its bundle
|
|
47
70
|
*/
|
|
48
|
-
private fun getInstance(): HotUpdaterImpl =
|
|
71
|
+
private fun getInstance(): HotUpdaterImpl = runtimeInstance.impl
|
|
49
72
|
|
|
50
73
|
/**
|
|
51
74
|
* Every changed file with its hash and URL or Hermes patch; with
|
|
@@ -211,8 +234,15 @@ class HotUpdaterModule internal constructor(
|
|
|
211
234
|
)
|
|
212
235
|
}
|
|
213
236
|
|
|
237
|
+
// Legacy: released JavaScript calls reload() on Android when the app chose
|
|
238
|
+
// setReloadBehavior("reload"). The in-place React Native reload is gone, so
|
|
239
|
+
// reload() restarts the process, and reloadProcess() runs the same code.
|
|
214
240
|
@ReactMethod
|
|
215
241
|
override fun reload(promise: Promise) {
|
|
242
|
+
runtimeInstance.reloadUnsupportedReason()?.let { reason ->
|
|
243
|
+
promise.reject(RELOAD_UNSUPPORTED_CODE, reason)
|
|
244
|
+
return
|
|
245
|
+
}
|
|
216
246
|
moduleScope.launch {
|
|
217
247
|
try {
|
|
218
248
|
val impl = getInstance()
|
|
@@ -225,19 +255,10 @@ class HotUpdaterModule internal constructor(
|
|
|
225
255
|
}
|
|
226
256
|
}
|
|
227
257
|
|
|
258
|
+
// Deprecated: JavaScript calls reloadProcess() on Android, both released
|
|
259
|
+
// JavaScript and this SDK's, so it stays until the next major release.
|
|
228
260
|
@ReactMethod
|
|
229
|
-
override fun reloadProcess(promise: Promise)
|
|
230
|
-
moduleScope.launch {
|
|
231
|
-
try {
|
|
232
|
-
val impl = getInstance()
|
|
233
|
-
impl.reloadProcess(mReactApplicationContext)
|
|
234
|
-
promise.resolve(null)
|
|
235
|
-
} catch (e: Exception) {
|
|
236
|
-
Log.d("HotUpdater", "Failed to restart process", e)
|
|
237
|
-
promise.reject("reloadProcess", e)
|
|
238
|
-
}
|
|
239
|
-
}
|
|
240
|
-
}
|
|
261
|
+
override fun reloadProcess(promise: Promise) = reload(promise)
|
|
241
262
|
|
|
242
263
|
@ReactMethod
|
|
243
264
|
override fun updateBundle(
|
|
@@ -485,7 +506,7 @@ class HotUpdaterModule internal constructor(
|
|
|
485
506
|
constants["APP_VERSION"] = HotUpdater.getAppVersion(mReactApplicationContext)
|
|
486
507
|
constants["CHANNEL"] = getInstance().getChannel()
|
|
487
508
|
constants["DEFAULT_CHANNEL"] = getInstance().getDefaultChannel()
|
|
488
|
-
constants["FINGERPRINT_HASH"] =
|
|
509
|
+
constants["FINGERPRINT_HASH"] = getInstance().getFingerprintHash()
|
|
489
510
|
return constants
|
|
490
511
|
}
|
|
491
512
|
|
|
@@ -547,12 +568,17 @@ class HotUpdaterModule internal constructor(
|
|
|
547
568
|
getInstance().setStorageItem(key, value)
|
|
548
569
|
}
|
|
549
570
|
|
|
571
|
+
@ReactMethod(isBlockingSynchronousMethod = true)
|
|
572
|
+
override fun takeNativeUpdateEvents(): String? = getInstance().takeNativeUpdateEvents()
|
|
573
|
+
|
|
550
574
|
@ReactMethod
|
|
551
575
|
override fun resetChannel(promise: Promise) {
|
|
552
576
|
moduleScope.launch {
|
|
553
577
|
try {
|
|
554
|
-
|
|
555
|
-
|
|
578
|
+
// Waits for a native check or download, which would save its
|
|
579
|
+
// channel again after the reset.
|
|
580
|
+
val name = runtimeInstance.name
|
|
581
|
+
val success = NativeUpdateCheck.resetChannel(mReactApplicationContext, name) { runtimeInstance.impl(name) }
|
|
556
582
|
promise.resolve(success)
|
|
557
583
|
} catch (e: HotUpdaterException) {
|
|
558
584
|
promise.reject(e.code, e.message)
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
package com.hotupdater
|
|
2
2
|
|
|
3
|
+
import android.content.Context
|
|
3
4
|
import com.facebook.react.ReactApplication
|
|
4
5
|
import com.facebook.react.bridge.JSExceptionHandler
|
|
5
6
|
import com.facebook.react.bridge.ReactContext
|
|
@@ -7,20 +8,22 @@ import com.facebook.react.bridge.ReactContext
|
|
|
7
8
|
/**
|
|
8
9
|
* Routes JavaScript errors of the running React instance to crash recovery.
|
|
9
10
|
* The old architecture reaches the instance through ReactNativeHost, which only
|
|
10
|
-
* an old architecture build compiles
|
|
11
|
+
* an old architecture build compiles, or through the context of a runtime
|
|
12
|
+
* whose HotUpdater module was created. An error belongs to the instance whose
|
|
13
|
+
* bundle the runtime runs.
|
|
11
14
|
*/
|
|
12
15
|
internal object JavaScriptExceptionHooks {
|
|
13
|
-
private val
|
|
14
|
-
private val
|
|
16
|
+
private val patchedInstanceManagers = WeakIdentitySet<Any>()
|
|
17
|
+
private val patchedCatalystInstances = WeakIdentitySet<Any>()
|
|
15
18
|
|
|
16
|
-
fun install(
|
|
19
|
+
fun install(appContext: Context) {
|
|
20
|
+
val application = appContext as? ReactApplication ?: return
|
|
17
21
|
val instanceManager = application.reactNativeHost.reactInstanceManager
|
|
18
|
-
|
|
19
|
-
if (patchedInstanceManagerIds.add(managerIdentity)) {
|
|
22
|
+
if (patchedInstanceManagers.add(instanceManager)) {
|
|
20
23
|
val exceptionHandlerField =
|
|
21
24
|
HotUpdaterRecoveryManager.findField(instanceManager.javaClass, "mJSExceptionHandler")
|
|
22
25
|
if (exceptionHandlerField == null) {
|
|
23
|
-
|
|
26
|
+
patchedInstanceManagers.remove(instanceManager)
|
|
24
27
|
return
|
|
25
28
|
}
|
|
26
29
|
|
|
@@ -28,14 +31,21 @@ internal object JavaScriptExceptionHooks {
|
|
|
28
31
|
val previousHandler = exceptionHandlerField.get(instanceManager) as? JSExceptionHandler
|
|
29
32
|
exceptionHandlerField.set(
|
|
30
33
|
instanceManager,
|
|
31
|
-
HotUpdaterRecoveryManager.RecoveryJSExceptionHandler(
|
|
34
|
+
HotUpdaterRecoveryManager.RecoveryJSExceptionHandler(
|
|
35
|
+
previousHandler,
|
|
36
|
+
{ instanceManager.currentReactContext?.sourceURL },
|
|
37
|
+
{ instanceManager.currentReactContext },
|
|
38
|
+
),
|
|
32
39
|
)
|
|
33
40
|
}
|
|
34
41
|
|
|
35
|
-
instanceManager.currentReactContext?.let
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
42
|
+
instanceManager.currentReactContext?.let(::installForRuntime)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** Hooks the catalyst instance of [reactContext]'s runtime, which a named instance's runtime is reached through. */
|
|
46
|
+
fun installForRuntime(reactContext: ReactContext) {
|
|
47
|
+
HotUpdaterRecoveryManager.patchReactContextExceptionHandler(reactContext)
|
|
48
|
+
patchCatalystInstanceExceptionHandler(reactContext)
|
|
39
49
|
}
|
|
40
50
|
|
|
41
51
|
private fun patchCatalystInstanceExceptionHandler(reactContext: ReactContext) {
|
|
@@ -46,15 +56,14 @@ internal object JavaScriptExceptionHooks {
|
|
|
46
56
|
null
|
|
47
57
|
} ?: return
|
|
48
58
|
|
|
49
|
-
|
|
50
|
-
if (!patchedCatalystInstanceIds.add(catalystIdentity)) {
|
|
59
|
+
if (!patchedCatalystInstances.add(catalystInstance)) {
|
|
51
60
|
return
|
|
52
61
|
}
|
|
53
62
|
|
|
54
63
|
val exceptionHandlerField =
|
|
55
64
|
HotUpdaterRecoveryManager.findField(catalystInstance.javaClass, "mJSExceptionHandler")
|
|
56
65
|
if (exceptionHandlerField == null) {
|
|
57
|
-
|
|
66
|
+
patchedCatalystInstances.remove(catalystInstance)
|
|
58
67
|
return
|
|
59
68
|
}
|
|
60
69
|
|
|
@@ -65,7 +74,7 @@ internal object JavaScriptExceptionHooks {
|
|
|
65
74
|
}
|
|
66
75
|
exceptionHandlerField.set(
|
|
67
76
|
catalystInstance,
|
|
68
|
-
HotUpdaterRecoveryManager.RecoveryJSExceptionHandler(previousHandler),
|
|
77
|
+
HotUpdaterRecoveryManager.RecoveryJSExceptionHandler(previousHandler, { catalystInstance.sourceURL }, { reactContext }),
|
|
69
78
|
)
|
|
70
79
|
}
|
|
71
80
|
}
|
|
@@ -54,6 +54,7 @@ NS_ASSUME_NONNULL_BEGIN
|
|
|
54
54
|
@end
|
|
55
55
|
|
|
56
56
|
@protocol HotUpdaterConfigInterface <NSObject>
|
|
57
|
+
@property (nonatomic, strong) NSBundle *bundle;
|
|
57
58
|
- (void)configureWithFingerprintHash:(nullable NSString *)fingerprintHash
|
|
58
59
|
publicKey:(nullable NSString *)publicKey
|
|
59
60
|
channel:(nullable NSString *)channel
|
|
@@ -61,9 +62,21 @@ NS_ASSUME_NONNULL_BEGIN
|
|
|
61
62
|
NS_SWIFT_NAME(configureWithFingerprintHash(_:publicKey:channel:isolationKey:));
|
|
62
63
|
@end
|
|
63
64
|
|
|
65
|
+
// The TurboModule's channel reset and event replay, which go through the
|
|
66
|
+
// Swift native update check, and the impl of the runtime that runs a bundle.
|
|
67
|
+
@protocol HotUpdaterNativeUpdatesInterface <NSObject>
|
|
68
|
+
- (void)resetChannelWithImpl:(id<HotUpdaterImplInterface>)impl
|
|
69
|
+
resolver:(RCTPromiseResolveBlock)resolve
|
|
70
|
+
rejecter:(RCTPromiseRejectBlock)reject;
|
|
71
|
+
- (nullable NSString *)takeEventsWithImpl:(id<HotUpdaterImplInterface>)impl;
|
|
72
|
+
- (id<HotUpdaterImplInterface>)implForBundleURL:(nullable NSURL *)bundleURL
|
|
73
|
+
NS_SWIFT_NAME(impl(forBundleURL:));
|
|
74
|
+
@end
|
|
75
|
+
|
|
64
76
|
// Implemented in HotUpdaterInterface.swift.
|
|
65
77
|
FOUNDATION_EXTERN id<HotUpdaterImplInterface> HotUpdaterMakeImpl(void);
|
|
66
78
|
FOUNDATION_EXTERN NSString *_Nullable HotUpdaterAppVersion(void);
|
|
67
79
|
FOUNDATION_EXTERN id<HotUpdaterConfigInterface> HotUpdaterSharedConfig(void);
|
|
80
|
+
FOUNDATION_EXTERN id<HotUpdaterNativeUpdatesInterface> HotUpdaterNativeUpdates(void);
|
|
68
81
|
|
|
69
82
|
NS_ASSUME_NONNULL_END
|
|
@@ -26,7 +26,8 @@ enum ArtifactFileResolverError: Error {
|
|
|
26
26
|
}
|
|
27
27
|
|
|
28
28
|
/// POSTs `{ paths }` to the files URL the SDK composed, with the update
|
|
29
|
-
/// check's headers and request timeout.
|
|
29
|
+
/// check's headers and request timeout. `onResponse` receives each
|
|
30
|
+
/// response's status and body, or a nil body when it could not be read.
|
|
30
31
|
final class HTTPArtifactFileResolver: ArtifactFileResolver {
|
|
31
32
|
/// The SDK's request timeout when the app sets none.
|
|
32
33
|
static let defaultTimeout: TimeInterval = 5
|
|
@@ -34,13 +35,23 @@ final class HTTPArtifactFileResolver: ArtifactFileResolver {
|
|
|
34
35
|
private let filesUrl: URL
|
|
35
36
|
private let headers: [String: String]
|
|
36
37
|
private let timeout: TimeInterval
|
|
37
|
-
private let
|
|
38
|
+
private let protocolClasses: [AnyClass]?
|
|
39
|
+
private let onResponse: ((Int, String?) -> Void)?
|
|
38
40
|
|
|
39
|
-
|
|
41
|
+
/// `protocolClasses` replaces the session's URL protocols; tests stub
|
|
42
|
+
/// the network with it.
|
|
43
|
+
init(
|
|
44
|
+
filesUrl: URL,
|
|
45
|
+
headers: [String: String],
|
|
46
|
+
timeout: TimeInterval = defaultTimeout,
|
|
47
|
+
protocolClasses: [AnyClass]? = nil,
|
|
48
|
+
onResponse: ((Int, String?) -> Void)? = nil
|
|
49
|
+
) {
|
|
40
50
|
self.filesUrl = filesUrl
|
|
41
51
|
self.headers = headers
|
|
42
52
|
self.timeout = timeout
|
|
43
|
-
self.
|
|
53
|
+
self.protocolClasses = protocolClasses
|
|
54
|
+
self.onResponse = onResponse
|
|
44
55
|
}
|
|
45
56
|
|
|
46
57
|
func resolve(paths: [String]) throws -> [String: ResolvedArtifactFile] {
|
|
@@ -53,15 +64,33 @@ final class HTTPArtifactFileResolver: ArtifactFileResolver {
|
|
|
53
64
|
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
|
|
54
65
|
request.httpBody = try JSONSerialization.data(withJSONObject: ["paths": paths])
|
|
55
66
|
|
|
67
|
+
// The resource timeout bounds the whole request, as Android's call
|
|
68
|
+
// timeout does; the request's own interval only bounds idle time.
|
|
69
|
+
let configuration = URLSessionConfiguration.ephemeral
|
|
70
|
+
configuration.urlCache = nil
|
|
71
|
+
configuration.timeoutIntervalForRequest = timeout
|
|
72
|
+
configuration.timeoutIntervalForResource = timeout
|
|
73
|
+
if let protocolClasses {
|
|
74
|
+
configuration.protocolClasses = protocolClasses
|
|
75
|
+
}
|
|
76
|
+
let session = URLSession(configuration: configuration)
|
|
77
|
+
defer { session.finishTasksAndInvalidate() }
|
|
78
|
+
|
|
56
79
|
let finished = DispatchSemaphore(value: 0)
|
|
57
80
|
var result: Result<Data, Error> = .failure(ArtifactFileResolverError.invalidResponse)
|
|
81
|
+
let onResponse = self.onResponse
|
|
58
82
|
let task = session.dataTask(with: request) { data, response, error in
|
|
59
83
|
defer { finished.signal() }
|
|
60
84
|
if let error {
|
|
85
|
+
// The status arrived but the body broke: still reported.
|
|
86
|
+
if let httpResponse = response as? HTTPURLResponse {
|
|
87
|
+
onResponse?(httpResponse.statusCode, nil)
|
|
88
|
+
}
|
|
61
89
|
result = .failure(error)
|
|
62
90
|
return
|
|
63
91
|
}
|
|
64
92
|
guard let httpResponse = response as? HTTPURLResponse else { return }
|
|
93
|
+
onResponse?(httpResponse.statusCode, data.flatMap { String(data: $0, encoding: .utf8) } ?? "")
|
|
65
94
|
guard (200..<300).contains(httpResponse.statusCode) else {
|
|
66
95
|
result = .failure(DownloadError.httpStatus(httpResponse.statusCode))
|
|
67
96
|
return
|
|
@@ -71,12 +100,14 @@ final class HTTPArtifactFileResolver: ArtifactFileResolver {
|
|
|
71
100
|
task.resume()
|
|
72
101
|
finished.wait()
|
|
73
102
|
|
|
74
|
-
|
|
75
|
-
|
|
103
|
+
// As JSON.parse reads it, which JSONSerialization does not.
|
|
104
|
+
let body = String(decoding: try result.get(), as: UTF8.self)
|
|
105
|
+
guard let json = StrictJson.parse(body) as? [String: Any],
|
|
106
|
+
let files = json["files"] as? [String: Any] else {
|
|
76
107
|
throw ArtifactFileResolverError.invalidResponse
|
|
77
108
|
}
|
|
78
109
|
return try paths.reduce(into: [:]) { resolved, path in
|
|
79
|
-
guard let file = files[path],
|
|
110
|
+
guard let file = files[StrictJson.storedKey(path)] as? [String: Any],
|
|
80
111
|
let urlString = file["url"] as? String,
|
|
81
112
|
let url = URL(string: urlString) else {
|
|
82
113
|
throw ArtifactFileResolverError.invalidResponse
|
|
@@ -14,15 +14,21 @@ final class IOSBuiltInAssetResolver: BuiltInAssetResolver {
|
|
|
14
14
|
|
|
15
15
|
private let cacheURL: URL
|
|
16
16
|
private let lock = NSLock()
|
|
17
|
-
private var
|
|
18
|
-
|
|
19
|
-
|
|
17
|
+
private var launchBundle: Bundle?
|
|
18
|
+
private let defaultBundle: () -> Bundle
|
|
19
|
+
/// The launch's bundle, or before a launch, the one the app registered
|
|
20
|
+
/// (`HotUpdaterConfig.bundle`), so an update staged before React Native
|
|
21
|
+
/// starts still finds the embedded files.
|
|
22
|
+
private var bundle: Bundle { launchBundle ?? defaultBundle() }
|
|
23
|
+
|
|
24
|
+
init(cacheURL: URL, defaultBundle: @escaping () -> Bundle = { HotUpdaterConfig.shared.bundle }) {
|
|
20
25
|
self.cacheURL = cacheURL
|
|
26
|
+
self.defaultBundle = defaultBundle
|
|
21
27
|
}
|
|
22
28
|
|
|
23
29
|
func use(bundle: Bundle) {
|
|
24
30
|
lock.lock()
|
|
25
|
-
self.
|
|
31
|
+
self.launchBundle = bundle
|
|
26
32
|
lock.unlock()
|
|
27
33
|
}
|
|
28
34
|
|