@hot-updater/react-native 0.12.7 → 0.13.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/android/generated/java/com/hotupdater/NativeHotUpdaterSpec.java +32 -0
- package/android/generated/jni/HotUpdaterSpec-generated.cpp +6 -0
- package/android/generated/jni/react/renderer/components/HotUpdaterSpec/HotUpdaterSpecJSI-generated.cpp +6 -0
- package/android/generated/jni/react/renderer/components/HotUpdaterSpec/HotUpdaterSpecJSI.h +9 -0
- package/android/src/main/java/com/hotupdater/HotUpdater.kt +47 -1
- package/android/src/newarch/HotUpdaterModule.kt +6 -0
- package/android/src/newarch/ReactIntegrationManager.kt +28 -6
- package/android/src/oldarch/HotUpdaterModule.kt +16 -0
- package/android/src/oldarch/ReactIntegrationManager.kt +15 -1
- package/dist/checkForUpdate.d.ts +2 -3
- package/dist/fetchUpdateInfo.d.ts +2 -0
- package/dist/index.d.ts +124 -0
- package/dist/index.js +68 -47
- package/dist/index.mjs +67 -47
- package/dist/native.d.ts +8 -0
- package/dist/runUpdateProcess.d.ts +4 -5
- package/dist/store.d.ts +1 -1
- package/dist/wrap.d.ts +2 -1
- package/ios/HotUpdater/HotUpdater.mm +68 -5
- package/ios/generated/HotUpdaterSpec/HotUpdaterSpec-generated.mm +7 -0
- package/ios/generated/HotUpdaterSpec/HotUpdaterSpec.h +37 -1
- package/ios/generated/HotUpdaterSpecJSI-generated.cpp +6 -0
- package/ios/generated/HotUpdaterSpecJSI.h +9 -0
- package/package.json +5 -5
- package/src/checkForUpdate.ts +14 -22
- package/src/fetchUpdateInfo.ts +22 -0
- package/src/index.ts +129 -3
- package/src/native.ts +21 -1
- package/src/runUpdateProcess.ts +11 -10
- package/src/specs/NativeHotUpdater.ts +3 -0
- package/src/store.ts +4 -4
- package/src/wrap.tsx +19 -4
- package/dist/ensureUpdateInfo.d.ts +0 -2
- package/src/ensureUpdateInfo.ts +0 -36
|
@@ -17,8 +17,14 @@ import com.facebook.react.bridge.Promise;
|
|
|
17
17
|
import com.facebook.react.bridge.ReactApplicationContext;
|
|
18
18
|
import com.facebook.react.bridge.ReactContextBaseJavaModule;
|
|
19
19
|
import com.facebook.react.bridge.ReactMethod;
|
|
20
|
+
import com.facebook.react.common.build.ReactBuildConfig;
|
|
20
21
|
import com.facebook.react.turbomodule.core.interfaces.TurboModule;
|
|
22
|
+
import java.util.Arrays;
|
|
23
|
+
import java.util.HashSet;
|
|
24
|
+
import java.util.Map;
|
|
25
|
+
import java.util.Set;
|
|
21
26
|
import javax.annotation.Nonnull;
|
|
27
|
+
import javax.annotation.Nullable;
|
|
22
28
|
|
|
23
29
|
public abstract class NativeHotUpdaterSpec extends ReactContextBaseJavaModule implements TurboModule {
|
|
24
30
|
public static final String NAME = "HotUpdater";
|
|
@@ -51,4 +57,30 @@ public abstract class NativeHotUpdaterSpec extends ReactContextBaseJavaModule im
|
|
|
51
57
|
@ReactMethod
|
|
52
58
|
@DoNotStrip
|
|
53
59
|
public abstract void removeListeners(double count);
|
|
60
|
+
|
|
61
|
+
protected abstract Map<String, Object> getTypedExportedConstants();
|
|
62
|
+
|
|
63
|
+
@Override
|
|
64
|
+
@DoNotStrip
|
|
65
|
+
public final @Nullable Map<String, Object> getConstants() {
|
|
66
|
+
Map<String, Object> constants = getTypedExportedConstants();
|
|
67
|
+
if (ReactBuildConfig.DEBUG || ReactBuildConfig.IS_INTERNAL_BUILD) {
|
|
68
|
+
Set<String> obligatoryFlowConstants = new HashSet<>(Arrays.asList(
|
|
69
|
+
"MIN_BUNDLE_ID"
|
|
70
|
+
));
|
|
71
|
+
Set<String> optionalFlowConstants = new HashSet<>();
|
|
72
|
+
Set<String> undeclaredConstants = new HashSet<>(constants.keySet());
|
|
73
|
+
undeclaredConstants.removeAll(obligatoryFlowConstants);
|
|
74
|
+
undeclaredConstants.removeAll(optionalFlowConstants);
|
|
75
|
+
if (!undeclaredConstants.isEmpty()) {
|
|
76
|
+
throw new IllegalStateException(String.format("Native Module Flow doesn't declare constants: %s", undeclaredConstants));
|
|
77
|
+
}
|
|
78
|
+
undeclaredConstants = obligatoryFlowConstants;
|
|
79
|
+
undeclaredConstants.removeAll(constants.keySet());
|
|
80
|
+
if (!undeclaredConstants.isEmpty()) {
|
|
81
|
+
throw new IllegalStateException(String.format("Native Module doesn't fill in constants: %s", undeclaredConstants));
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
return constants;
|
|
85
|
+
}
|
|
54
86
|
}
|
|
@@ -37,6 +37,11 @@ static facebook::jsi::Value __hostFunction_NativeHotUpdaterSpecJSI_removeListene
|
|
|
37
37
|
return static_cast<JavaTurboModule &>(turboModule).invokeJavaMethod(rt, VoidKind, "removeListeners", "(D)V", args, count, cachedMethodId);
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
+
static facebook::jsi::Value __hostFunction_NativeHotUpdaterSpecJSI_getConstants(facebook::jsi::Runtime& rt, TurboModule &turboModule, const facebook::jsi::Value* args, size_t count) {
|
|
41
|
+
static jmethodID cachedMethodId = nullptr;
|
|
42
|
+
return static_cast<JavaTurboModule &>(turboModule).invokeJavaMethod(rt, ObjectKind, "getConstants", "()Ljava/util/Map;", args, count, cachedMethodId);
|
|
43
|
+
}
|
|
44
|
+
|
|
40
45
|
NativeHotUpdaterSpecJSI::NativeHotUpdaterSpecJSI(const JavaTurboModule::InitParams ¶ms)
|
|
41
46
|
: JavaTurboModule(params) {
|
|
42
47
|
methodMap_["reload"] = MethodMetadata {0, __hostFunction_NativeHotUpdaterSpecJSI_reload};
|
|
@@ -44,6 +49,7 @@ NativeHotUpdaterSpecJSI::NativeHotUpdaterSpecJSI(const JavaTurboModule::InitPara
|
|
|
44
49
|
methodMap_["getAppVersion"] = MethodMetadata {0, __hostFunction_NativeHotUpdaterSpecJSI_getAppVersion};
|
|
45
50
|
methodMap_["addListener"] = MethodMetadata {1, __hostFunction_NativeHotUpdaterSpecJSI_addListener};
|
|
46
51
|
methodMap_["removeListeners"] = MethodMetadata {1, __hostFunction_NativeHotUpdaterSpecJSI_removeListeners};
|
|
52
|
+
methodMap_["getConstants"] = MethodMetadata {0, __hostFunction_NativeHotUpdaterSpecJSI_getConstants};
|
|
47
53
|
}
|
|
48
54
|
|
|
49
55
|
std::shared_ptr<TurboModule> HotUpdaterSpec_ModuleProvider(const std::string &moduleName, const JavaTurboModule::InitParams ¶ms) {
|
|
@@ -43,6 +43,11 @@ static jsi::Value __hostFunction_NativeHotUpdaterCxxSpecJSI_removeListeners(jsi:
|
|
|
43
43
|
);
|
|
44
44
|
return jsi::Value::undefined();
|
|
45
45
|
}
|
|
46
|
+
static jsi::Value __hostFunction_NativeHotUpdaterCxxSpecJSI_getConstants(jsi::Runtime &rt, TurboModule &turboModule, const jsi::Value* args, size_t count) {
|
|
47
|
+
return static_cast<NativeHotUpdaterCxxSpecJSI *>(&turboModule)->getConstants(
|
|
48
|
+
rt
|
|
49
|
+
);
|
|
50
|
+
}
|
|
46
51
|
|
|
47
52
|
NativeHotUpdaterCxxSpecJSI::NativeHotUpdaterCxxSpecJSI(std::shared_ptr<CallInvoker> jsInvoker)
|
|
48
53
|
: TurboModule("HotUpdater", jsInvoker) {
|
|
@@ -51,6 +56,7 @@ NativeHotUpdaterCxxSpecJSI::NativeHotUpdaterCxxSpecJSI(std::shared_ptr<CallInvok
|
|
|
51
56
|
methodMap_["getAppVersion"] = MethodMetadata {0, __hostFunction_NativeHotUpdaterCxxSpecJSI_getAppVersion};
|
|
52
57
|
methodMap_["addListener"] = MethodMetadata {1, __hostFunction_NativeHotUpdaterCxxSpecJSI_addListener};
|
|
53
58
|
methodMap_["removeListeners"] = MethodMetadata {1, __hostFunction_NativeHotUpdaterCxxSpecJSI_removeListeners};
|
|
59
|
+
methodMap_["getConstants"] = MethodMetadata {0, __hostFunction_NativeHotUpdaterCxxSpecJSI_getConstants};
|
|
54
60
|
}
|
|
55
61
|
|
|
56
62
|
|
|
@@ -25,6 +25,7 @@ public:
|
|
|
25
25
|
virtual jsi::Value getAppVersion(jsi::Runtime &rt) = 0;
|
|
26
26
|
virtual void addListener(jsi::Runtime &rt, jsi::String eventName) = 0;
|
|
27
27
|
virtual void removeListeners(jsi::Runtime &rt, double count) = 0;
|
|
28
|
+
virtual jsi::Object getConstants(jsi::Runtime &rt) = 0;
|
|
28
29
|
|
|
29
30
|
};
|
|
30
31
|
|
|
@@ -91,6 +92,14 @@ private:
|
|
|
91
92
|
return bridging::callFromJs<void>(
|
|
92
93
|
rt, &T::removeListeners, jsInvoker_, instance_, std::move(count));
|
|
93
94
|
}
|
|
95
|
+
jsi::Object getConstants(jsi::Runtime &rt) override {
|
|
96
|
+
static_assert(
|
|
97
|
+
bridging::getParameterCount(&T::getConstants) == 1,
|
|
98
|
+
"Expected getConstants(...) to have 1 parameters");
|
|
99
|
+
|
|
100
|
+
return bridging::callFromJs<jsi::Object>(
|
|
101
|
+
rt, &T::getConstants, jsInvoker_, instance_);
|
|
102
|
+
}
|
|
94
103
|
|
|
95
104
|
private:
|
|
96
105
|
friend class NativeHotUpdaterCxxSpec;
|
|
@@ -153,7 +153,7 @@ class HotUpdater : ReactPackage {
|
|
|
153
153
|
}
|
|
154
154
|
tempDir.mkdirs()
|
|
155
155
|
|
|
156
|
-
val tempZipFile = File(tempDir, "
|
|
156
|
+
val tempZipFile = File(tempDir, "bundle.zip")
|
|
157
157
|
val extractedDir = File(tempDir, "extracted")
|
|
158
158
|
extractedDir.mkdirs()
|
|
159
159
|
|
|
@@ -274,5 +274,51 @@ class HotUpdater : ReactPackage {
|
|
|
274
274
|
}
|
|
275
275
|
}
|
|
276
276
|
}
|
|
277
|
+
|
|
278
|
+
fun getMinBundleId(context: Context): String =
|
|
279
|
+
try {
|
|
280
|
+
val apkFile = File(context.applicationInfo.sourceDir)
|
|
281
|
+
val buildTimestampMs = apkFile.lastModified()
|
|
282
|
+
val bytes =
|
|
283
|
+
ByteArray(16).apply {
|
|
284
|
+
this[0] = ((buildTimestampMs shr 40) and 0xFF).toByte()
|
|
285
|
+
this[1] = ((buildTimestampMs shr 32) and 0xFF).toByte()
|
|
286
|
+
this[2] = ((buildTimestampMs shr 24) and 0xFF).toByte()
|
|
287
|
+
this[3] = ((buildTimestampMs shr 16) and 0xFF).toByte()
|
|
288
|
+
this[4] = ((buildTimestampMs shr 8) and 0xFF).toByte()
|
|
289
|
+
this[5] = (buildTimestampMs and 0xFF).toByte()
|
|
290
|
+
this[6] = 0x70.toByte()
|
|
291
|
+
this[7] = 0x00.toByte()
|
|
292
|
+
this[8] = 0x80.toByte()
|
|
293
|
+
this[9] = 0x00.toByte()
|
|
294
|
+
this[10] = 0x00.toByte()
|
|
295
|
+
this[11] = 0x00.toByte()
|
|
296
|
+
this[12] = 0x00.toByte()
|
|
297
|
+
this[13] = 0x00.toByte()
|
|
298
|
+
this[14] = 0x00.toByte()
|
|
299
|
+
this[15] = 0x00.toByte()
|
|
300
|
+
}
|
|
301
|
+
String.format(
|
|
302
|
+
"%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x",
|
|
303
|
+
bytes[0].toInt() and 0xFF,
|
|
304
|
+
bytes[1].toInt() and 0xFF,
|
|
305
|
+
bytes[2].toInt() and 0xFF,
|
|
306
|
+
bytes[3].toInt() and 0xFF,
|
|
307
|
+
bytes[4].toInt() and 0xFF,
|
|
308
|
+
bytes[5].toInt() and 0xFF,
|
|
309
|
+
bytes[6].toInt() and 0xFF,
|
|
310
|
+
bytes[7].toInt() and 0xFF,
|
|
311
|
+
bytes[8].toInt() and 0xFF,
|
|
312
|
+
bytes[9].toInt() and 0xFF,
|
|
313
|
+
bytes[10].toInt() and 0xFF,
|
|
314
|
+
bytes[11].toInt() and 0xFF,
|
|
315
|
+
bytes[12].toInt() and 0xFF,
|
|
316
|
+
bytes[13].toInt() and 0xFF,
|
|
317
|
+
bytes[14].toInt() and 0xFF,
|
|
318
|
+
bytes[15].toInt() and 0xFF,
|
|
319
|
+
)
|
|
320
|
+
} catch (e: Exception) {
|
|
321
|
+
"00000000-0000-0000-0000-000000000000"
|
|
322
|
+
}
|
|
277
323
|
}
|
|
278
324
|
}
|
|
@@ -54,6 +54,12 @@ class HotUpdaterModule internal constructor(
|
|
|
54
54
|
}
|
|
55
55
|
}
|
|
56
56
|
|
|
57
|
+
override fun getTypedExportedConstants(): Map<String, Any?> {
|
|
58
|
+
val constants: MutableMap<String, Any?> = HashMap()
|
|
59
|
+
constants["MIN_BUNDLE_ID"] = HotUpdater.getMinBundleId(mReactApplicationContext)
|
|
60
|
+
return constants
|
|
61
|
+
}
|
|
62
|
+
|
|
57
63
|
override fun addListener(eventName: String?) {
|
|
58
64
|
// No-op
|
|
59
65
|
}
|
|
@@ -34,13 +34,35 @@ class ReactIntegrationManager(
|
|
|
34
34
|
* Reload the React Native application.
|
|
35
35
|
*/
|
|
36
36
|
public fun reload(application: ReactApplication) {
|
|
37
|
-
|
|
38
|
-
|
|
37
|
+
try {
|
|
38
|
+
val reactHost = application.reactHost
|
|
39
|
+
if (reactHost != null) {
|
|
40
|
+
val activity = reactHost.currentReactContext?.currentActivity
|
|
41
|
+
if (reactHost.lifecycleState != LifecycleState.RESUMED && activity != null) {
|
|
42
|
+
reactHost.onHostResume(activity)
|
|
43
|
+
}
|
|
44
|
+
reactHost.reload("Requested by HotUpdater")
|
|
45
|
+
} else {
|
|
46
|
+
val reactNativeHost = application.reactNativeHost
|
|
47
|
+
try {
|
|
48
|
+
reactNativeHost.reactInstanceManager.recreateReactContextInBackground()
|
|
49
|
+
} catch (e: Exception) {
|
|
50
|
+
val currentActivity = reactNativeHost.reactInstanceManager.currentReactContext?.currentActivity
|
|
51
|
+
if (currentActivity == null) {
|
|
52
|
+
return
|
|
53
|
+
}
|
|
39
54
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
55
|
+
currentActivity.runOnUiThread {
|
|
56
|
+
currentActivity.recreate()
|
|
57
|
+
}
|
|
58
|
+
} catch (e: Exception) {
|
|
59
|
+
Log.d("HotUpdater", "Failed to reload: ${e.message}")
|
|
60
|
+
throw e
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
} catch (e: Exception) {
|
|
64
|
+
Log.d("HotUpdater", "Failed to reload: ${e.message}")
|
|
65
|
+
throw e
|
|
43
66
|
}
|
|
44
|
-
reactHost.reload("Requested by HotUpdater")
|
|
45
67
|
}
|
|
46
68
|
}
|
|
@@ -54,6 +54,22 @@ class HotUpdaterModule internal constructor(
|
|
|
54
54
|
}
|
|
55
55
|
}
|
|
56
56
|
|
|
57
|
+
@ReactMethod
|
|
58
|
+
fun addListener(eventName: String?) {
|
|
59
|
+
// No-op
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
@ReactMethod
|
|
63
|
+
fun removeListeners(count: Double) {
|
|
64
|
+
// No-op
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
override fun getConstants(): Map<String, Any?> {
|
|
68
|
+
val constants: MutableMap<String, Any?> = HashMap()
|
|
69
|
+
constants["MIN_BUNDLE_ID"] = HotUpdater.getMinBundleId(mReactApplicationContext)
|
|
70
|
+
return constants
|
|
71
|
+
}
|
|
72
|
+
|
|
57
73
|
companion object {
|
|
58
74
|
const val NAME = "HotUpdater"
|
|
59
75
|
}
|
|
@@ -36,6 +36,20 @@ class ReactIntegrationManager(
|
|
|
36
36
|
*/
|
|
37
37
|
public fun reload(application: ReactApplication) {
|
|
38
38
|
val reactNativeHost = application.reactNativeHost
|
|
39
|
-
|
|
39
|
+
try {
|
|
40
|
+
reactNativeHost.reactInstanceManager.recreateReactContextInBackground()
|
|
41
|
+
} catch (e: Exception) {
|
|
42
|
+
val currentActivity = reactNativeHost.reactInstanceManager.currentReactContext?.currentActivity
|
|
43
|
+
if (currentActivity == null) {
|
|
44
|
+
return
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
currentActivity.runOnUiThread {
|
|
48
|
+
currentActivity.recreate()
|
|
49
|
+
}
|
|
50
|
+
} catch (e: Exception) {
|
|
51
|
+
Log.d("HotUpdater", "Failed to reload: ${e.message}")
|
|
52
|
+
throw e
|
|
53
|
+
}
|
|
40
54
|
}
|
|
41
55
|
}
|
package/dist/checkForUpdate.d.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
import type { BundleArg, UpdateInfo } from "@hot-updater/core";
|
|
2
1
|
export interface CheckForUpdateConfig {
|
|
3
|
-
source:
|
|
2
|
+
source: string;
|
|
4
3
|
requestHeaders?: Record<string, string>;
|
|
5
4
|
}
|
|
6
|
-
export declare function checkForUpdate(config: CheckForUpdateConfig): Promise<
|
|
5
|
+
export declare function checkForUpdate(config: CheckForUpdateConfig): Promise<import("@hot-updater/core").AppUpdateInfo | null>;
|
package/dist/index.d.ts
CHANGED
|
@@ -4,11 +4,106 @@ export type { HotUpdaterConfig } from "./wrap";
|
|
|
4
4
|
export type { HotUpdaterEvent } from "./native";
|
|
5
5
|
export * from "./store";
|
|
6
6
|
export declare const HotUpdater: {
|
|
7
|
+
/**
|
|
8
|
+
* `HotUpdater.wrap` checks for updates at the entry point, and if there is a bundle to update, it downloads the bundle and applies the update strategy.
|
|
9
|
+
*
|
|
10
|
+
* @param {object} options - Configuration options
|
|
11
|
+
* @param {string} options.source - Update server URL
|
|
12
|
+
* @param {object} [options.requestHeaders] - Request headers
|
|
13
|
+
* @param {React.ComponentType} [options.fallbackComponent] - Component to display during updates
|
|
14
|
+
* @param {boolean} [options.reloadOnForceUpdate=true] - Whether to automatically reload the app on force updates
|
|
15
|
+
* @param {Function} [options.onUpdateProcessCompleted] - Callback after update process completes
|
|
16
|
+
* @param {Function} [options.onProgress] - Callback to track bundle download progress
|
|
17
|
+
* @returns {Function} Higher-order component that wraps the app component
|
|
18
|
+
*
|
|
19
|
+
* @example
|
|
20
|
+
* ```tsx
|
|
21
|
+
* export default HotUpdater.wrap({
|
|
22
|
+
* source: "<your-update-server-url>",
|
|
23
|
+
* requestHeaders: {
|
|
24
|
+
* "Authorization": "Bearer <your-access-token>",
|
|
25
|
+
* },
|
|
26
|
+
* })(App);
|
|
27
|
+
* ```
|
|
28
|
+
*/
|
|
7
29
|
wrap: typeof wrap;
|
|
30
|
+
/**
|
|
31
|
+
* Reloads the app.
|
|
32
|
+
*/
|
|
8
33
|
reload: () => void;
|
|
34
|
+
/**
|
|
35
|
+
* Fetches the current app version.
|
|
36
|
+
*/
|
|
9
37
|
getAppVersion: () => Promise<string | null>;
|
|
38
|
+
/**
|
|
39
|
+
* Fetches the current bundle ID of the app.
|
|
40
|
+
*/
|
|
10
41
|
getBundleId: () => string;
|
|
42
|
+
/**
|
|
43
|
+
* Retrieves the initial bundle ID based on the build time of the native app.
|
|
44
|
+
*/
|
|
45
|
+
getMinBundleId: () => string;
|
|
46
|
+
/**
|
|
47
|
+
* Fetches the current release channel of the app.
|
|
48
|
+
*
|
|
49
|
+
* By default, if no channel is specified, the app is assigned to the 'production' channel.
|
|
50
|
+
*
|
|
51
|
+
* @returns {string} The current release channel of the app
|
|
52
|
+
*
|
|
53
|
+
* @example
|
|
54
|
+
* ```ts
|
|
55
|
+
* const channel = HotUpdater.getChannel();
|
|
56
|
+
* console.log(`Current channel: ${channel}`);
|
|
57
|
+
* ```
|
|
58
|
+
*/
|
|
59
|
+
getChannel: () => string;
|
|
60
|
+
/**
|
|
61
|
+
* Adds a listener to HotUpdater events.
|
|
62
|
+
*
|
|
63
|
+
* @param {keyof HotUpdaterEvent} eventName - The name of the event to listen for
|
|
64
|
+
* @param {(event: HotUpdaterEvent[T]) => void} listener - The callback function to handle the event
|
|
65
|
+
* @returns {() => void} A cleanup function that removes the event listener
|
|
66
|
+
*
|
|
67
|
+
* @example
|
|
68
|
+
* ```ts
|
|
69
|
+
* const unsubscribe = HotUpdater.addListener("onProgress", ({ progress }) => {
|
|
70
|
+
* console.log(`Update progress: ${progress * 100}%`);
|
|
71
|
+
* });
|
|
72
|
+
*
|
|
73
|
+
* // Unsubscribe when no longer needed
|
|
74
|
+
* unsubscribe();
|
|
75
|
+
* ```
|
|
76
|
+
*/
|
|
11
77
|
addListener: <T extends keyof import("./native").HotUpdaterEvent>(eventName: T, listener: (event: import("./native").HotUpdaterEvent[T]) => void) => () => void;
|
|
78
|
+
/**
|
|
79
|
+
* Manually checks for updates.
|
|
80
|
+
*
|
|
81
|
+
* @param {Object} config - Update check configuration
|
|
82
|
+
* @param {string} config.source - Update server URL
|
|
83
|
+
* @param {Record<string, string>} [config.requestHeaders] - Request headers
|
|
84
|
+
*
|
|
85
|
+
* @returns {Promise<UpdateInfo | null>} Update information or null if up to date
|
|
86
|
+
*
|
|
87
|
+
* @example
|
|
88
|
+
* ```ts
|
|
89
|
+
* const updateInfo = await HotUpdater.checkForUpdate({
|
|
90
|
+
* source: "<your-update-server-url>",
|
|
91
|
+
* requestHeaders: {
|
|
92
|
+
* Authorization: "Bearer <your-access-token>",
|
|
93
|
+
* },
|
|
94
|
+
* });
|
|
95
|
+
*
|
|
96
|
+
* if (!updateInfo) {
|
|
97
|
+
* console.log("App is up to date");
|
|
98
|
+
* return;
|
|
99
|
+
* }
|
|
100
|
+
*
|
|
101
|
+
* await HotUpdater.updateBundle(updateInfo.id, updateInfo.fileUrl);
|
|
102
|
+
* if (updateInfo.shouldForceUpdate) {
|
|
103
|
+
* HotUpdater.reload();
|
|
104
|
+
* }
|
|
105
|
+
* ```
|
|
106
|
+
*/
|
|
12
107
|
checkForUpdate: typeof checkForUpdate;
|
|
13
108
|
/**
|
|
14
109
|
* Manually checks and applies updates for the application.
|
|
@@ -43,5 +138,34 @@ export declare const HotUpdater: {
|
|
|
43
138
|
* @returns {Promise<RunUpdateProcessResponse>} The result of the update process
|
|
44
139
|
*/
|
|
45
140
|
runUpdateProcess: ({ reloadOnForceUpdate, ...checkForUpdateConfig }: import("./runUpdateProcess").RunUpdateProcessConfig) => Promise<import("./runUpdateProcess").RunUpdateProcessResponse>;
|
|
141
|
+
/**
|
|
142
|
+
* Updates the bundle of the app.
|
|
143
|
+
*
|
|
144
|
+
* @param {string} bundleId - The bundle ID of the app
|
|
145
|
+
* @param {string} zipUrl - The URL of the zip file
|
|
146
|
+
*
|
|
147
|
+
* @returns {Promise<boolean>} Whether the update was successful
|
|
148
|
+
*
|
|
149
|
+
* @example
|
|
150
|
+
* ```ts
|
|
151
|
+
* const updateInfo = await HotUpdater.checkForUpdate({
|
|
152
|
+
* source: "<your-update-server-url>",
|
|
153
|
+
* requestHeaders: {
|
|
154
|
+
* Authorization: "Bearer <your-access-token>",
|
|
155
|
+
* },
|
|
156
|
+
* });
|
|
157
|
+
*
|
|
158
|
+
* if (!updateInfo) {
|
|
159
|
+
* return {
|
|
160
|
+
* status: "UP_TO_DATE",
|
|
161
|
+
* };
|
|
162
|
+
* }
|
|
163
|
+
*
|
|
164
|
+
* await HotUpdater.updateBundle(updateInfo.id, updateInfo.fileUrl);
|
|
165
|
+
* if (updateInfo.shouldForceUpdate) {
|
|
166
|
+
* HotUpdater.reload();
|
|
167
|
+
* }
|
|
168
|
+
* ```
|
|
169
|
+
*/
|
|
46
170
|
updateBundle: (bundleId: string, zipUrl: string | null) => Promise<boolean>;
|
|
47
171
|
};
|
package/dist/index.js
CHANGED
|
@@ -59,35 +59,37 @@ var __webpack_exports__ = {};
|
|
|
59
59
|
useHotUpdaterStore: ()=>useHotUpdaterStore,
|
|
60
60
|
hotUpdaterStore: ()=>hotUpdaterStore
|
|
61
61
|
});
|
|
62
|
-
const js_namespaceObject = require("@hot-updater/js");
|
|
63
62
|
var external_react_native_ = __webpack_require__("react-native");
|
|
64
|
-
const ensureUpdateInfo = async (source, { appVersion, bundleId, platform }, requestHeaders)=>{
|
|
65
|
-
try {
|
|
66
|
-
let bundles = null;
|
|
67
|
-
if ("string" == typeof source) {
|
|
68
|
-
if (source.startsWith("http")) return await fetch(source, {
|
|
69
|
-
headers: {
|
|
70
|
-
"x-app-platform": platform,
|
|
71
|
-
"x-app-version": appVersion,
|
|
72
|
-
"x-bundle-id": bundleId,
|
|
73
|
-
...requestHeaders
|
|
74
|
-
}
|
|
75
|
-
}).then((res)=>res.json());
|
|
76
|
-
} else bundles = "function" == typeof source ? await source() : source;
|
|
77
|
-
return bundles ?? [];
|
|
78
|
-
} catch {
|
|
79
|
-
return [];
|
|
80
|
-
}
|
|
81
|
-
};
|
|
82
63
|
class HotUpdaterError extends Error {
|
|
83
64
|
constructor(message){
|
|
84
65
|
super(message);
|
|
85
66
|
this.name = "HotUpdaterError";
|
|
86
67
|
}
|
|
87
68
|
}
|
|
69
|
+
const fetchUpdateInfo = async (source, { appVersion, bundleId, platform, minBundleId, channel }, requestHeaders)=>{
|
|
70
|
+
try {
|
|
71
|
+
return fetch(source, {
|
|
72
|
+
headers: {
|
|
73
|
+
"x-app-platform": platform,
|
|
74
|
+
"x-app-version": appVersion,
|
|
75
|
+
"x-bundle-id": bundleId,
|
|
76
|
+
...minBundleId ? {
|
|
77
|
+
"x-min-bundle-id": minBundleId
|
|
78
|
+
} : {},
|
|
79
|
+
...channel ? {
|
|
80
|
+
"x-channel": channel
|
|
81
|
+
} : {},
|
|
82
|
+
...requestHeaders
|
|
83
|
+
}
|
|
84
|
+
}).then((res)=>200 === res.status ? res.json() : null);
|
|
85
|
+
} catch {
|
|
86
|
+
return null;
|
|
87
|
+
}
|
|
88
|
+
};
|
|
88
89
|
const NIL_UUID = "00000000-0000-0000-0000-000000000000";
|
|
89
90
|
const HotUpdater = {
|
|
90
|
-
HOT_UPDATER_BUNDLE_ID: NIL_UUID
|
|
91
|
+
HOT_UPDATER_BUNDLE_ID: NIL_UUID,
|
|
92
|
+
CHANNEL: "production"
|
|
91
93
|
};
|
|
92
94
|
const LINKING_ERROR = `The package '@hot-updater/react-native' doesn't seem to be linked. Make sure: \n\n` + external_react_native_.Platform.select({
|
|
93
95
|
ios: "- You have run 'pod install'\n",
|
|
@@ -114,7 +116,15 @@ var __webpack_exports__ = {};
|
|
|
114
116
|
HotUpdaterNative.reload();
|
|
115
117
|
});
|
|
116
118
|
};
|
|
117
|
-
const
|
|
119
|
+
const getMinBundleId = ()=>{
|
|
120
|
+
const constants = HotUpdaterNative.getConstants();
|
|
121
|
+
return constants.MIN_BUNDLE_ID;
|
|
122
|
+
};
|
|
123
|
+
const getBundleId = ()=>{
|
|
124
|
+
const minBundleId = getMinBundleId();
|
|
125
|
+
return minBundleId.localeCompare(HotUpdater.HOT_UPDATER_BUNDLE_ID) >= 0 ? minBundleId : HotUpdater.HOT_UPDATER_BUNDLE_ID;
|
|
126
|
+
};
|
|
127
|
+
const getChannel = ()=>HotUpdater.CHANNEL;
|
|
118
128
|
async function checkForUpdate(config) {
|
|
119
129
|
if (__DEV__) return null;
|
|
120
130
|
if (![
|
|
@@ -123,28 +133,25 @@ var __webpack_exports__ = {};
|
|
|
123
133
|
].includes(external_react_native_.Platform.OS)) throw new HotUpdaterError("HotUpdater is only supported on iOS and Android");
|
|
124
134
|
const currentAppVersion = await getAppVersion();
|
|
125
135
|
const platform = external_react_native_.Platform.OS;
|
|
126
|
-
const currentBundleId =
|
|
136
|
+
const currentBundleId = getBundleId();
|
|
137
|
+
const minBundleId = getMinBundleId();
|
|
138
|
+
const channel = getChannel();
|
|
127
139
|
if (!currentAppVersion) throw new HotUpdaterError("Failed to get app version");
|
|
128
|
-
|
|
140
|
+
return fetchUpdateInfo(config.source, {
|
|
129
141
|
appVersion: currentAppVersion,
|
|
130
142
|
bundleId: currentBundleId,
|
|
131
|
-
platform
|
|
143
|
+
platform,
|
|
144
|
+
minBundleId,
|
|
145
|
+
channel
|
|
132
146
|
}, config.requestHeaders);
|
|
133
|
-
let updateInfo = null;
|
|
134
|
-
if (Array.isArray(ensuredUpdateInfo)) {
|
|
135
|
-
const bundles = ensuredUpdateInfo;
|
|
136
|
-
updateInfo = await (0, js_namespaceObject.getUpdateInfo)(bundles, {
|
|
137
|
-
appVersion: currentAppVersion,
|
|
138
|
-
bundleId: currentBundleId,
|
|
139
|
-
platform
|
|
140
|
-
});
|
|
141
|
-
} else updateInfo = ensuredUpdateInfo;
|
|
142
|
-
return updateInfo;
|
|
143
147
|
}
|
|
144
148
|
const runUpdateProcess = async ({ reloadOnForceUpdate = true, ...checkForUpdateConfig })=>{
|
|
145
149
|
const updateInfo = await checkForUpdate(checkForUpdateConfig);
|
|
146
150
|
if (!updateInfo) return {
|
|
147
|
-
status: "UP_TO_DATE"
|
|
151
|
+
status: "UP_TO_DATE",
|
|
152
|
+
shouldForceUpdate: false,
|
|
153
|
+
message: null,
|
|
154
|
+
id: getBundleId()
|
|
148
155
|
};
|
|
149
156
|
const isUpdated = await updateBundle(updateInfo.id, updateInfo.fileUrl);
|
|
150
157
|
if (isUpdated && updateInfo.shouldForceUpdate && reloadOnForceUpdate) reload();
|
|
@@ -152,7 +159,8 @@ var __webpack_exports__ = {};
|
|
|
152
159
|
return {
|
|
153
160
|
status: updateInfo.status,
|
|
154
161
|
shouldForceUpdate: updateInfo.shouldForceUpdate,
|
|
155
|
-
id: updateInfo.id
|
|
162
|
+
id: updateInfo.id,
|
|
163
|
+
message: updateInfo.message
|
|
156
164
|
};
|
|
157
165
|
};
|
|
158
166
|
const with_selector_namespaceObject = require("use-sync-external-store/shim/with-selector");
|
|
@@ -168,10 +176,10 @@ var __webpack_exports__ = {};
|
|
|
168
176
|
const emitChange = ()=>{
|
|
169
177
|
for (const listener of listeners)listener();
|
|
170
178
|
};
|
|
171
|
-
const
|
|
179
|
+
const setState = (newState)=>{
|
|
172
180
|
state = {
|
|
173
|
-
|
|
174
|
-
|
|
181
|
+
...state,
|
|
182
|
+
...newState
|
|
175
183
|
};
|
|
176
184
|
emitChange();
|
|
177
185
|
};
|
|
@@ -181,13 +189,14 @@ var __webpack_exports__ = {};
|
|
|
181
189
|
};
|
|
182
190
|
return {
|
|
183
191
|
getSnapshot,
|
|
184
|
-
|
|
192
|
+
setState,
|
|
185
193
|
subscribe
|
|
186
194
|
};
|
|
187
195
|
};
|
|
188
196
|
const hotUpdaterStore = createHotUpdaterStore();
|
|
189
197
|
const useHotUpdaterStore = (selector = (snapshot)=>snapshot)=>useSyncExternalStoreWithSelector(hotUpdaterStore.subscribe, hotUpdaterStore.getSnapshot, hotUpdaterStore.getSnapshot, selector);
|
|
190
198
|
const external_react_namespaceObject = require("react");
|
|
199
|
+
var external_react_default = /*#__PURE__*/ __webpack_require__.n(external_react_namespaceObject);
|
|
191
200
|
function useEventCallback(fn) {
|
|
192
201
|
const callbackRef = (0, external_react_namespaceObject.useRef)(()=>{
|
|
193
202
|
throw new Error("Cannot call an event handler while rendering.");
|
|
@@ -206,6 +215,7 @@ var __webpack_exports__ = {};
|
|
|
206
215
|
return (WrappedComponent)=>{
|
|
207
216
|
const HotUpdaterHOC = ()=>{
|
|
208
217
|
const progress = useHotUpdaterStore((state)=>state.progress);
|
|
218
|
+
const [message, setMessage] = (0, external_react_namespaceObject.useState)(null);
|
|
209
219
|
const [updateStatus, setUpdateStatus] = (0, external_react_namespaceObject.useState)("CHECK_FOR_UPDATE");
|
|
210
220
|
const initHotUpdater = useEventCallback(async ()=>{
|
|
211
221
|
try {
|
|
@@ -214,9 +224,13 @@ var __webpack_exports__ = {};
|
|
|
214
224
|
source: restConfig.source,
|
|
215
225
|
requestHeaders: restConfig.requestHeaders
|
|
216
226
|
});
|
|
227
|
+
setMessage(updateInfo?.message ?? null);
|
|
217
228
|
if (!updateInfo) {
|
|
218
229
|
restConfig.onUpdateProcessCompleted?.({
|
|
219
|
-
status: "UP_TO_DATE"
|
|
230
|
+
status: "UP_TO_DATE",
|
|
231
|
+
shouldForceUpdate: false,
|
|
232
|
+
message: null,
|
|
233
|
+
id: getBundleId()
|
|
220
234
|
});
|
|
221
235
|
setUpdateStatus("UPDATE_PROCESS_COMPLETED");
|
|
222
236
|
return;
|
|
@@ -226,7 +240,8 @@ var __webpack_exports__ = {};
|
|
|
226
240
|
restConfig.onUpdateProcessCompleted?.({
|
|
227
241
|
id: updateInfo.id,
|
|
228
242
|
status: updateInfo.status,
|
|
229
|
-
shouldForceUpdate: updateInfo.shouldForceUpdate
|
|
243
|
+
shouldForceUpdate: updateInfo.shouldForceUpdate,
|
|
244
|
+
message: updateInfo.message
|
|
230
245
|
});
|
|
231
246
|
setUpdateStatus("UPDATE_PROCESS_COMPLETED");
|
|
232
247
|
return;
|
|
@@ -238,7 +253,8 @@ var __webpack_exports__ = {};
|
|
|
238
253
|
restConfig.onUpdateProcessCompleted?.({
|
|
239
254
|
id: updateInfo.id,
|
|
240
255
|
status: updateInfo.status,
|
|
241
|
-
shouldForceUpdate: updateInfo.shouldForceUpdate
|
|
256
|
+
shouldForceUpdate: updateInfo.shouldForceUpdate,
|
|
257
|
+
message: updateInfo.message
|
|
242
258
|
});
|
|
243
259
|
setUpdateStatus("UPDATE_PROCESS_COMPLETED");
|
|
244
260
|
} catch (error) {
|
|
@@ -257,24 +273,29 @@ var __webpack_exports__ = {};
|
|
|
257
273
|
}, []);
|
|
258
274
|
if (restConfig.fallbackComponent && "UPDATE_PROCESS_COMPLETED" !== updateStatus) {
|
|
259
275
|
const Fallback = restConfig.fallbackComponent;
|
|
260
|
-
return /*#__PURE__*/
|
|
276
|
+
return /*#__PURE__*/ external_react_default().createElement(Fallback, {
|
|
261
277
|
progress: progress,
|
|
262
|
-
status: updateStatus
|
|
278
|
+
status: updateStatus,
|
|
279
|
+
message: message
|
|
263
280
|
});
|
|
264
281
|
}
|
|
265
|
-
return /*#__PURE__*/
|
|
282
|
+
return /*#__PURE__*/ external_react_default().createElement(WrappedComponent, null);
|
|
266
283
|
};
|
|
267
284
|
return HotUpdaterHOC;
|
|
268
285
|
};
|
|
269
286
|
}
|
|
270
287
|
addListener("onProgress", ({ progress })=>{
|
|
271
|
-
hotUpdaterStore.
|
|
288
|
+
hotUpdaterStore.setState({
|
|
289
|
+
progress
|
|
290
|
+
});
|
|
272
291
|
});
|
|
273
292
|
const src_HotUpdater = {
|
|
274
293
|
wrap: wrap,
|
|
275
294
|
reload: reload,
|
|
276
295
|
getAppVersion: getAppVersion,
|
|
277
296
|
getBundleId: getBundleId,
|
|
297
|
+
getMinBundleId: getMinBundleId,
|
|
298
|
+
getChannel: getChannel,
|
|
278
299
|
addListener: addListener,
|
|
279
300
|
checkForUpdate: checkForUpdate,
|
|
280
301
|
runUpdateProcess: runUpdateProcess,
|