@hot-updater/react-native 0.12.7 → 0.13.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.
- 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 +2 -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 +7 -1
- 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
package/dist/index.mjs
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import * as __WEBPACK_EXTERNAL_MODULE_react_native_4af9217e__ from "react-native";
|
|
2
|
-
import * as __WEBPACK_EXTERNAL_MODULE__hot_updater_js_db235456__ from "@hot-updater/js";
|
|
3
2
|
import * as __WEBPACK_EXTERNAL_MODULE_use_sync_external_store_shim_with_selector_83d70c15__ from "use-sync-external-store/shim/with-selector";
|
|
4
3
|
import * as __WEBPACK_EXTERNAL_MODULE_react__ from "react";
|
|
5
4
|
var __webpack_modules__ = {
|
|
@@ -36,33 +35,36 @@ function __webpack_require__(moduleId) {
|
|
|
36
35
|
__webpack_require__.o = (obj, prop)=>Object.prototype.hasOwnProperty.call(obj, prop);
|
|
37
36
|
})();
|
|
38
37
|
var external_react_native_ = __webpack_require__("react-native");
|
|
39
|
-
const ensureUpdateInfo = async (source, { appVersion, bundleId, platform }, requestHeaders)=>{
|
|
40
|
-
try {
|
|
41
|
-
let bundles = null;
|
|
42
|
-
if ("string" == typeof source) {
|
|
43
|
-
if (source.startsWith("http")) return await fetch(source, {
|
|
44
|
-
headers: {
|
|
45
|
-
"x-app-platform": platform,
|
|
46
|
-
"x-app-version": appVersion,
|
|
47
|
-
"x-bundle-id": bundleId,
|
|
48
|
-
...requestHeaders
|
|
49
|
-
}
|
|
50
|
-
}).then((res)=>res.json());
|
|
51
|
-
} else bundles = "function" == typeof source ? await source() : source;
|
|
52
|
-
return bundles ?? [];
|
|
53
|
-
} catch {
|
|
54
|
-
return [];
|
|
55
|
-
}
|
|
56
|
-
};
|
|
57
38
|
class HotUpdaterError extends Error {
|
|
58
39
|
constructor(message){
|
|
59
40
|
super(message);
|
|
60
41
|
this.name = "HotUpdaterError";
|
|
61
42
|
}
|
|
62
43
|
}
|
|
44
|
+
const fetchUpdateInfo = async (source, { appVersion, bundleId, platform, minBundleId, channel }, requestHeaders)=>{
|
|
45
|
+
try {
|
|
46
|
+
return fetch(source, {
|
|
47
|
+
headers: {
|
|
48
|
+
"x-app-platform": platform,
|
|
49
|
+
"x-app-version": appVersion,
|
|
50
|
+
"x-bundle-id": bundleId,
|
|
51
|
+
...minBundleId ? {
|
|
52
|
+
"x-min-bundle-id": minBundleId
|
|
53
|
+
} : {},
|
|
54
|
+
...channel ? {
|
|
55
|
+
"x-channel": channel
|
|
56
|
+
} : {},
|
|
57
|
+
...requestHeaders
|
|
58
|
+
}
|
|
59
|
+
}).then((res)=>200 === res.status ? res.json() : null);
|
|
60
|
+
} catch {
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
};
|
|
63
64
|
const NIL_UUID = "00000000-0000-0000-0000-000000000000";
|
|
64
65
|
const HotUpdater = {
|
|
65
|
-
HOT_UPDATER_BUNDLE_ID: NIL_UUID
|
|
66
|
+
HOT_UPDATER_BUNDLE_ID: NIL_UUID,
|
|
67
|
+
CHANNEL: "production"
|
|
66
68
|
};
|
|
67
69
|
const LINKING_ERROR = `The package '@hot-updater/react-native' doesn't seem to be linked. Make sure: \n\n` + external_react_native_.Platform.select({
|
|
68
70
|
ios: "- You have run 'pod install'\n",
|
|
@@ -89,7 +91,15 @@ const reload = ()=>{
|
|
|
89
91
|
HotUpdaterNative.reload();
|
|
90
92
|
});
|
|
91
93
|
};
|
|
92
|
-
const
|
|
94
|
+
const getMinBundleId = ()=>{
|
|
95
|
+
const constants = HotUpdaterNative.getConstants();
|
|
96
|
+
return constants.MIN_BUNDLE_ID;
|
|
97
|
+
};
|
|
98
|
+
const getBundleId = ()=>{
|
|
99
|
+
const minBundleId = getMinBundleId();
|
|
100
|
+
return minBundleId.localeCompare(HotUpdater.HOT_UPDATER_BUNDLE_ID) >= 0 ? minBundleId : HotUpdater.HOT_UPDATER_BUNDLE_ID;
|
|
101
|
+
};
|
|
102
|
+
const getChannel = ()=>HotUpdater.CHANNEL;
|
|
93
103
|
async function checkForUpdate(config) {
|
|
94
104
|
if (__DEV__) return null;
|
|
95
105
|
if (![
|
|
@@ -98,28 +108,25 @@ async function checkForUpdate(config) {
|
|
|
98
108
|
].includes(external_react_native_.Platform.OS)) throw new HotUpdaterError("HotUpdater is only supported on iOS and Android");
|
|
99
109
|
const currentAppVersion = await getAppVersion();
|
|
100
110
|
const platform = external_react_native_.Platform.OS;
|
|
101
|
-
const currentBundleId =
|
|
111
|
+
const currentBundleId = getBundleId();
|
|
112
|
+
const minBundleId = getMinBundleId();
|
|
113
|
+
const channel = getChannel();
|
|
102
114
|
if (!currentAppVersion) throw new HotUpdaterError("Failed to get app version");
|
|
103
|
-
|
|
115
|
+
return fetchUpdateInfo(config.source, {
|
|
104
116
|
appVersion: currentAppVersion,
|
|
105
117
|
bundleId: currentBundleId,
|
|
106
|
-
platform
|
|
118
|
+
platform,
|
|
119
|
+
minBundleId,
|
|
120
|
+
channel
|
|
107
121
|
}, config.requestHeaders);
|
|
108
|
-
let updateInfo = null;
|
|
109
|
-
if (Array.isArray(ensuredUpdateInfo)) {
|
|
110
|
-
const bundles = ensuredUpdateInfo;
|
|
111
|
-
updateInfo = await (0, __WEBPACK_EXTERNAL_MODULE__hot_updater_js_db235456__.getUpdateInfo)(bundles, {
|
|
112
|
-
appVersion: currentAppVersion,
|
|
113
|
-
bundleId: currentBundleId,
|
|
114
|
-
platform
|
|
115
|
-
});
|
|
116
|
-
} else updateInfo = ensuredUpdateInfo;
|
|
117
|
-
return updateInfo;
|
|
118
122
|
}
|
|
119
123
|
const runUpdateProcess = async ({ reloadOnForceUpdate = true, ...checkForUpdateConfig })=>{
|
|
120
124
|
const updateInfo = await checkForUpdate(checkForUpdateConfig);
|
|
121
125
|
if (!updateInfo) return {
|
|
122
|
-
status: "UP_TO_DATE"
|
|
126
|
+
status: "UP_TO_DATE",
|
|
127
|
+
shouldForceUpdate: false,
|
|
128
|
+
message: null,
|
|
129
|
+
id: getBundleId()
|
|
123
130
|
};
|
|
124
131
|
const isUpdated = await updateBundle(updateInfo.id, updateInfo.fileUrl);
|
|
125
132
|
if (isUpdated && updateInfo.shouldForceUpdate && reloadOnForceUpdate) reload();
|
|
@@ -127,7 +134,8 @@ const runUpdateProcess = async ({ reloadOnForceUpdate = true, ...checkForUpdateC
|
|
|
127
134
|
return {
|
|
128
135
|
status: updateInfo.status,
|
|
129
136
|
shouldForceUpdate: updateInfo.shouldForceUpdate,
|
|
130
|
-
id: updateInfo.id
|
|
137
|
+
id: updateInfo.id,
|
|
138
|
+
message: updateInfo.message
|
|
131
139
|
};
|
|
132
140
|
};
|
|
133
141
|
const { useSyncExternalStoreWithSelector } = __WEBPACK_EXTERNAL_MODULE_use_sync_external_store_shim_with_selector_83d70c15__["default"];
|
|
@@ -141,10 +149,10 @@ const createHotUpdaterStore = ()=>{
|
|
|
141
149
|
const emitChange = ()=>{
|
|
142
150
|
for (const listener of listeners)listener();
|
|
143
151
|
};
|
|
144
|
-
const
|
|
152
|
+
const setState = (newState)=>{
|
|
145
153
|
state = {
|
|
146
|
-
|
|
147
|
-
|
|
154
|
+
...state,
|
|
155
|
+
...newState
|
|
148
156
|
};
|
|
149
157
|
emitChange();
|
|
150
158
|
};
|
|
@@ -154,7 +162,7 @@ const createHotUpdaterStore = ()=>{
|
|
|
154
162
|
};
|
|
155
163
|
return {
|
|
156
164
|
getSnapshot,
|
|
157
|
-
|
|
165
|
+
setState,
|
|
158
166
|
subscribe
|
|
159
167
|
};
|
|
160
168
|
};
|
|
@@ -178,6 +186,7 @@ function wrap(config) {
|
|
|
178
186
|
return (WrappedComponent)=>{
|
|
179
187
|
const HotUpdaterHOC = ()=>{
|
|
180
188
|
const progress = useHotUpdaterStore((state)=>state.progress);
|
|
189
|
+
const [message, setMessage] = (0, __WEBPACK_EXTERNAL_MODULE_react__.useState)(null);
|
|
181
190
|
const [updateStatus, setUpdateStatus] = (0, __WEBPACK_EXTERNAL_MODULE_react__.useState)("CHECK_FOR_UPDATE");
|
|
182
191
|
const initHotUpdater = useEventCallback(async ()=>{
|
|
183
192
|
try {
|
|
@@ -186,9 +195,13 @@ function wrap(config) {
|
|
|
186
195
|
source: restConfig.source,
|
|
187
196
|
requestHeaders: restConfig.requestHeaders
|
|
188
197
|
});
|
|
198
|
+
setMessage(updateInfo?.message ?? null);
|
|
189
199
|
if (!updateInfo) {
|
|
190
200
|
restConfig.onUpdateProcessCompleted?.({
|
|
191
|
-
status: "UP_TO_DATE"
|
|
201
|
+
status: "UP_TO_DATE",
|
|
202
|
+
shouldForceUpdate: false,
|
|
203
|
+
message: null,
|
|
204
|
+
id: getBundleId()
|
|
192
205
|
});
|
|
193
206
|
setUpdateStatus("UPDATE_PROCESS_COMPLETED");
|
|
194
207
|
return;
|
|
@@ -198,7 +211,8 @@ function wrap(config) {
|
|
|
198
211
|
restConfig.onUpdateProcessCompleted?.({
|
|
199
212
|
id: updateInfo.id,
|
|
200
213
|
status: updateInfo.status,
|
|
201
|
-
shouldForceUpdate: updateInfo.shouldForceUpdate
|
|
214
|
+
shouldForceUpdate: updateInfo.shouldForceUpdate,
|
|
215
|
+
message: updateInfo.message
|
|
202
216
|
});
|
|
203
217
|
setUpdateStatus("UPDATE_PROCESS_COMPLETED");
|
|
204
218
|
return;
|
|
@@ -210,7 +224,8 @@ function wrap(config) {
|
|
|
210
224
|
restConfig.onUpdateProcessCompleted?.({
|
|
211
225
|
id: updateInfo.id,
|
|
212
226
|
status: updateInfo.status,
|
|
213
|
-
shouldForceUpdate: updateInfo.shouldForceUpdate
|
|
227
|
+
shouldForceUpdate: updateInfo.shouldForceUpdate,
|
|
228
|
+
message: updateInfo.message
|
|
214
229
|
});
|
|
215
230
|
setUpdateStatus("UPDATE_PROCESS_COMPLETED");
|
|
216
231
|
} catch (error) {
|
|
@@ -229,24 +244,29 @@ function wrap(config) {
|
|
|
229
244
|
}, []);
|
|
230
245
|
if (restConfig.fallbackComponent && "UPDATE_PROCESS_COMPLETED" !== updateStatus) {
|
|
231
246
|
const Fallback = restConfig.fallbackComponent;
|
|
232
|
-
return /*#__PURE__*/
|
|
247
|
+
return /*#__PURE__*/ __WEBPACK_EXTERNAL_MODULE_react__["default"].createElement(Fallback, {
|
|
233
248
|
progress: progress,
|
|
234
|
-
status: updateStatus
|
|
249
|
+
status: updateStatus,
|
|
250
|
+
message: message
|
|
235
251
|
});
|
|
236
252
|
}
|
|
237
|
-
return /*#__PURE__*/
|
|
253
|
+
return /*#__PURE__*/ __WEBPACK_EXTERNAL_MODULE_react__["default"].createElement(WrappedComponent, null);
|
|
238
254
|
};
|
|
239
255
|
return HotUpdaterHOC;
|
|
240
256
|
};
|
|
241
257
|
}
|
|
242
258
|
addListener("onProgress", ({ progress })=>{
|
|
243
|
-
hotUpdaterStore.
|
|
259
|
+
hotUpdaterStore.setState({
|
|
260
|
+
progress
|
|
261
|
+
});
|
|
244
262
|
});
|
|
245
263
|
const src_HotUpdater = {
|
|
246
264
|
wrap: wrap,
|
|
247
265
|
reload: reload,
|
|
248
266
|
getAppVersion: getAppVersion,
|
|
249
267
|
getBundleId: getBundleId,
|
|
268
|
+
getMinBundleId: getMinBundleId,
|
|
269
|
+
getChannel: getChannel,
|
|
250
270
|
addListener: addListener,
|
|
251
271
|
checkForUpdate: checkForUpdate,
|
|
252
272
|
runUpdateProcess: runUpdateProcess,
|
package/dist/native.d.ts
CHANGED
|
@@ -20,6 +20,13 @@ export declare const getAppVersion: () => Promise<string | null>;
|
|
|
20
20
|
* Reloads the app.
|
|
21
21
|
*/
|
|
22
22
|
export declare const reload: () => void;
|
|
23
|
+
/**
|
|
24
|
+
* Fetches the minimum bundle id, which represents the initial bundle of the app
|
|
25
|
+
* since it is created at build time.
|
|
26
|
+
*
|
|
27
|
+
* @returns {string} Resolves with the minimum bundle id or null if not available.
|
|
28
|
+
*/
|
|
29
|
+
export declare const getMinBundleId: () => string;
|
|
23
30
|
/**
|
|
24
31
|
* Fetches the current bundle version id.
|
|
25
32
|
*
|
|
@@ -27,3 +34,4 @@ export declare const reload: () => void;
|
|
|
27
34
|
* @returns {Promise<string>} Resolves with the current version id or null if not available.
|
|
28
35
|
*/
|
|
29
36
|
export declare const getBundleId: () => string;
|
|
37
|
+
export declare const getChannel: () => string;
|
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
import { type CheckForUpdateConfig } from "./checkForUpdate";
|
|
2
|
-
export
|
|
3
|
-
status: "ROLLBACK" | "UPDATE";
|
|
2
|
+
export interface RunUpdateProcessResponse {
|
|
3
|
+
status: "ROLLBACK" | "UPDATE" | "UP_TO_DATE";
|
|
4
4
|
shouldForceUpdate: boolean;
|
|
5
|
+
message: string | null;
|
|
5
6
|
id: string;
|
|
6
|
-
}
|
|
7
|
-
status: "UP_TO_DATE";
|
|
8
|
-
};
|
|
7
|
+
}
|
|
9
8
|
export interface RunUpdateProcessConfig extends CheckForUpdateConfig {
|
|
10
9
|
/**
|
|
11
10
|
* If `true`, the app will be reloaded when the downloaded bundle is a force update.
|
package/dist/store.d.ts
CHANGED
|
@@ -4,7 +4,7 @@ export type HotUpdaterState = {
|
|
|
4
4
|
};
|
|
5
5
|
export declare const hotUpdaterStore: {
|
|
6
6
|
getSnapshot: () => HotUpdaterState;
|
|
7
|
-
|
|
7
|
+
setState: (newState: Partial<HotUpdaterState>) => void;
|
|
8
8
|
subscribe: (listener: () => void) => () => boolean;
|
|
9
9
|
};
|
|
10
10
|
export declare const useHotUpdaterStore: <T = HotUpdaterState>(selector?: (snapshot: HotUpdaterState) => T) => T;
|
package/dist/wrap.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import React from "react";
|
|
2
2
|
import { type CheckForUpdateConfig } from "./checkForUpdate";
|
|
3
3
|
import { HotUpdaterError } from "./error";
|
|
4
4
|
import type { RunUpdateProcessResponse } from "./runUpdateProcess";
|
|
@@ -28,6 +28,7 @@ export interface HotUpdaterConfig extends CheckForUpdateConfig {
|
|
|
28
28
|
fallbackComponent?: React.FC<{
|
|
29
29
|
status: Exclude<UpdateStatus, "UPDATE_PROCESS_COMPLETED">;
|
|
30
30
|
progress: number;
|
|
31
|
+
message: string | null;
|
|
31
32
|
}>;
|
|
32
33
|
onError?: (error: HotUpdaterError) => void;
|
|
33
34
|
onProgress?: (progress: number) => void;
|
|
@@ -21,6 +21,73 @@
|
|
|
21
21
|
|
|
22
22
|
RCT_EXPORT_MODULE();
|
|
23
23
|
|
|
24
|
+
#pragma mark - React Native Constants
|
|
25
|
+
|
|
26
|
+
- (NSString *)getMinBundleId {
|
|
27
|
+
static NSString *uuid = nil;
|
|
28
|
+
static dispatch_once_t onceToken;
|
|
29
|
+
dispatch_once(&onceToken, ^{
|
|
30
|
+
NSDate *buildDate = nil;
|
|
31
|
+
NSURL *fallbackURL = nil;
|
|
32
|
+
#if DEBUG
|
|
33
|
+
uuid = @"00000000-0000-0000-0000-000000000000";
|
|
34
|
+
return;
|
|
35
|
+
#else
|
|
36
|
+
fallbackURL = [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"];
|
|
37
|
+
#endif
|
|
38
|
+
if (fallbackURL) {
|
|
39
|
+
NSDictionary *attributes = [[NSFileManager defaultManager] attributesOfItemAtPath:[fallbackURL path] error:nil];
|
|
40
|
+
buildDate = attributes[NSFileModificationDate];
|
|
41
|
+
}
|
|
42
|
+
if (!buildDate) {
|
|
43
|
+
uuid = @"00000000-0000-0000-0000-000000000000";
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
uint64_t buildTimestampMs = (uint64_t)([buildDate timeIntervalSince1970] * 1000.0);
|
|
48
|
+
|
|
49
|
+
unsigned char bytes[16];
|
|
50
|
+
bytes[0] = (buildTimestampMs >> 40) & 0xFF;
|
|
51
|
+
bytes[1] = (buildTimestampMs >> 32) & 0xFF;
|
|
52
|
+
bytes[2] = (buildTimestampMs >> 24) & 0xFF;
|
|
53
|
+
bytes[3] = (buildTimestampMs >> 16) & 0xFF;
|
|
54
|
+
bytes[4] = (buildTimestampMs >> 8) & 0xFF;
|
|
55
|
+
bytes[5] = buildTimestampMs & 0xFF;
|
|
56
|
+
|
|
57
|
+
bytes[6] = 0x70;
|
|
58
|
+
bytes[7] = 0x00;
|
|
59
|
+
|
|
60
|
+
bytes[8] = 0x80;
|
|
61
|
+
bytes[9] = 0x00;
|
|
62
|
+
|
|
63
|
+
bytes[10] = 0x00;
|
|
64
|
+
bytes[11] = 0x00;
|
|
65
|
+
bytes[12] = 0x00;
|
|
66
|
+
bytes[13] = 0x00;
|
|
67
|
+
bytes[14] = 0x00;
|
|
68
|
+
bytes[15] = 0x00;
|
|
69
|
+
|
|
70
|
+
uuid = [NSString stringWithFormat:
|
|
71
|
+
@"%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x",
|
|
72
|
+
bytes[0], bytes[1], bytes[2], bytes[3],
|
|
73
|
+
bytes[4], bytes[5],
|
|
74
|
+
bytes[6], bytes[7],
|
|
75
|
+
bytes[8], bytes[9],
|
|
76
|
+
bytes[10], bytes[11], bytes[12], bytes[13], bytes[14], bytes[15]];
|
|
77
|
+
});
|
|
78
|
+
return uuid;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
- (NSDictionary *)constantsToExport {
|
|
82
|
+
return @{ @"MIN_BUNDLE_ID": [self getMinBundleId] };
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
- (NSDictionary*) getConstants {
|
|
87
|
+
return [self constantsToExport];
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
|
|
24
91
|
#pragma mark - Bundle URL Management
|
|
25
92
|
|
|
26
93
|
- (NSString *)getAppVersion {
|
|
@@ -49,11 +116,7 @@ RCT_EXPORT_MODULE();
|
|
|
49
116
|
}
|
|
50
117
|
|
|
51
118
|
+ (NSURL *)fallbackURL {
|
|
52
|
-
#if DEBUG
|
|
53
|
-
return [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index"];
|
|
54
|
-
#else
|
|
55
119
|
return [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"];
|
|
56
|
-
#endif
|
|
57
120
|
}
|
|
58
121
|
|
|
59
122
|
+ (NSURL *)bundleURL {
|
|
@@ -170,7 +233,7 @@ RCT_EXPORT_MODULE();
|
|
|
170
233
|
}
|
|
171
234
|
[fileManager createDirectoryAtPath:tempDir withIntermediateDirectories:YES attributes:nil error:nil];
|
|
172
235
|
|
|
173
|
-
NSString *tempZipFile = [tempDir stringByAppendingPathComponent:@"
|
|
236
|
+
NSString *tempZipFile = [tempDir stringByAppendingPathComponent:@"bundle.zip"];
|
|
174
237
|
NSString *extractedDir = [tempDir stringByAppendingPathComponent:@"extracted"];
|
|
175
238
|
[fileManager createDirectoryAtPath:extractedDir withIntermediateDirectories:YES attributes:nil error:nil];
|
|
176
239
|
|
|
@@ -46,6 +46,10 @@ namespace facebook::react {
|
|
|
46
46
|
return static_cast<ObjCTurboModule&>(turboModule).invokeObjCMethod(rt, VoidKind, "removeListeners", @selector(removeListeners:), args, count);
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
+
static facebook::jsi::Value __hostFunction_NativeHotUpdaterSpecJSI_getConstants(facebook::jsi::Runtime& rt, TurboModule &turboModule, const facebook::jsi::Value* args, size_t count) {
|
|
50
|
+
return static_cast<ObjCTurboModule&>(turboModule).invokeObjCMethod(rt, ObjectKind, "getConstants", @selector(getConstants), args, count);
|
|
51
|
+
}
|
|
52
|
+
|
|
49
53
|
NativeHotUpdaterSpecJSI::NativeHotUpdaterSpecJSI(const ObjCTurboModule::InitParams ¶ms)
|
|
50
54
|
: ObjCTurboModule(params) {
|
|
51
55
|
|
|
@@ -63,5 +67,8 @@ namespace facebook::react {
|
|
|
63
67
|
|
|
64
68
|
methodMap_["removeListeners"] = MethodMetadata {1, __hostFunction_NativeHotUpdaterSpecJSI_removeListeners};
|
|
65
69
|
|
|
70
|
+
|
|
71
|
+
methodMap_["getConstants"] = MethodMetadata {0, __hostFunction_NativeHotUpdaterSpecJSI_getConstants};
|
|
72
|
+
|
|
66
73
|
}
|
|
67
74
|
} // namespace facebook::react
|
|
@@ -30,7 +30,33 @@
|
|
|
30
30
|
#import <optional>
|
|
31
31
|
#import <vector>
|
|
32
32
|
|
|
33
|
+
namespace JS {
|
|
34
|
+
namespace NativeHotUpdater {
|
|
35
|
+
struct Constants {
|
|
33
36
|
|
|
37
|
+
struct Builder {
|
|
38
|
+
struct Input {
|
|
39
|
+
RCTRequired<NSString *> MIN_BUNDLE_ID;
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
/** Initialize with a set of values */
|
|
43
|
+
Builder(const Input i);
|
|
44
|
+
/** Initialize with an existing Constants */
|
|
45
|
+
Builder(Constants i);
|
|
46
|
+
/** Builds the object. Generally used only by the infrastructure. */
|
|
47
|
+
NSDictionary *buildUnsafeRawValue() const { return _factory(); };
|
|
48
|
+
private:
|
|
49
|
+
NSDictionary *(^_factory)(void);
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
static Constants fromUnsafeRawValue(NSDictionary *const v) { return {v}; }
|
|
53
|
+
NSDictionary *unsafeRawValue() const { return _v; }
|
|
54
|
+
private:
|
|
55
|
+
Constants(NSDictionary *const v) : _v(v) {}
|
|
56
|
+
NSDictionary *_v;
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
}
|
|
34
60
|
@protocol NativeHotUpdaterSpec <RCTBridgeModule, RCTTurboModule>
|
|
35
61
|
|
|
36
62
|
- (void)reload;
|
|
@@ -42,6 +68,8 @@
|
|
|
42
68
|
reject:(RCTPromiseRejectBlock)reject;
|
|
43
69
|
- (void)addListener:(NSString *)eventName;
|
|
44
70
|
- (void)removeListeners:(double)count;
|
|
71
|
+
- (facebook::react::ModuleConstants<JS::NativeHotUpdater::Constants::Builder>)constantsToExport;
|
|
72
|
+
- (facebook::react::ModuleConstants<JS::NativeHotUpdater::Constants::Builder>)getConstants;
|
|
45
73
|
|
|
46
74
|
@end
|
|
47
75
|
|
|
@@ -63,5 +91,13 @@ namespace facebook::react {
|
|
|
63
91
|
NativeHotUpdaterSpecJSI(const ObjCTurboModule::InitParams ¶ms);
|
|
64
92
|
};
|
|
65
93
|
} // namespace facebook::react
|
|
66
|
-
|
|
94
|
+
inline JS::NativeHotUpdater::Constants::Builder::Builder(const Input i) : _factory(^{
|
|
95
|
+
NSMutableDictionary *d = [NSMutableDictionary new];
|
|
96
|
+
auto MIN_BUNDLE_ID = i.MIN_BUNDLE_ID.get();
|
|
97
|
+
d[@"MIN_BUNDLE_ID"] = MIN_BUNDLE_ID;
|
|
98
|
+
return d;
|
|
99
|
+
}) {}
|
|
100
|
+
inline JS::NativeHotUpdater::Constants::Builder::Builder(Constants i) : _factory(^{
|
|
101
|
+
return i.unsafeRawValue();
|
|
102
|
+
}) {}
|
|
67
103
|
#endif // HotUpdaterSpec_H
|
|
@@ -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;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hot-updater/react-native",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.13.0",
|
|
4
4
|
"description": "React Native OTA solution for self-hosted",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"module": "dist/index.mjs",
|
|
@@ -67,8 +67,8 @@
|
|
|
67
67
|
"includesGeneratedCode": true
|
|
68
68
|
},
|
|
69
69
|
"peerDependencies": {
|
|
70
|
-
"react
|
|
71
|
-
"react": "*"
|
|
70
|
+
"react": "*",
|
|
71
|
+
"react-native": "*"
|
|
72
72
|
},
|
|
73
73
|
"devDependencies": {
|
|
74
74
|
"@react-native-community/cli": "15.0.1",
|
|
@@ -81,8 +81,8 @@
|
|
|
81
81
|
},
|
|
82
82
|
"dependencies": {
|
|
83
83
|
"use-sync-external-store": "1.4.0",
|
|
84
|
-
"@hot-updater/js": "0.
|
|
85
|
-
"@hot-updater/core": "0.
|
|
84
|
+
"@hot-updater/js": "0.13.0",
|
|
85
|
+
"@hot-updater/core": "0.13.0"
|
|
86
86
|
},
|
|
87
87
|
"scripts": {
|
|
88
88
|
"build": "rslib build",
|
package/src/checkForUpdate.ts
CHANGED
|
@@ -1,12 +1,15 @@
|
|
|
1
|
-
import type { Bundle, BundleArg, UpdateInfo } from "@hot-updater/core";
|
|
2
|
-
import { getUpdateInfo } from "@hot-updater/js";
|
|
3
1
|
import { Platform } from "react-native";
|
|
4
|
-
import { ensureUpdateInfo } from "./ensureUpdateInfo";
|
|
5
2
|
import { HotUpdaterError } from "./error";
|
|
6
|
-
import {
|
|
3
|
+
import { fetchUpdateInfo } from "./fetchUpdateInfo";
|
|
4
|
+
import {
|
|
5
|
+
getAppVersion,
|
|
6
|
+
getBundleId,
|
|
7
|
+
getChannel,
|
|
8
|
+
getMinBundleId,
|
|
9
|
+
} from "./native";
|
|
7
10
|
|
|
8
11
|
export interface CheckForUpdateConfig {
|
|
9
|
-
source:
|
|
12
|
+
source: string;
|
|
10
13
|
requestHeaders?: Record<string, string>;
|
|
11
14
|
}
|
|
12
15
|
|
|
@@ -23,34 +26,23 @@ export async function checkForUpdate(config: CheckForUpdateConfig) {
|
|
|
23
26
|
|
|
24
27
|
const currentAppVersion = await getAppVersion();
|
|
25
28
|
const platform = Platform.OS as "ios" | "android";
|
|
26
|
-
const currentBundleId =
|
|
29
|
+
const currentBundleId = getBundleId();
|
|
30
|
+
const minBundleId = getMinBundleId();
|
|
31
|
+
const channel = getChannel();
|
|
27
32
|
|
|
28
33
|
if (!currentAppVersion) {
|
|
29
34
|
throw new HotUpdaterError("Failed to get app version");
|
|
30
35
|
}
|
|
31
36
|
|
|
32
|
-
|
|
37
|
+
return fetchUpdateInfo(
|
|
33
38
|
config.source,
|
|
34
39
|
{
|
|
35
40
|
appVersion: currentAppVersion,
|
|
36
41
|
bundleId: currentBundleId,
|
|
37
42
|
platform,
|
|
43
|
+
minBundleId,
|
|
44
|
+
channel,
|
|
38
45
|
},
|
|
39
46
|
config.requestHeaders,
|
|
40
47
|
);
|
|
41
|
-
|
|
42
|
-
let updateInfo: UpdateInfo | null = null;
|
|
43
|
-
if (Array.isArray(ensuredUpdateInfo)) {
|
|
44
|
-
const bundles: Bundle[] = ensuredUpdateInfo;
|
|
45
|
-
|
|
46
|
-
updateInfo = await getUpdateInfo(bundles, {
|
|
47
|
-
appVersion: currentAppVersion,
|
|
48
|
-
bundleId: currentBundleId,
|
|
49
|
-
platform,
|
|
50
|
-
});
|
|
51
|
-
} else {
|
|
52
|
-
updateInfo = ensuredUpdateInfo;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
return updateInfo;
|
|
56
48
|
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { AppUpdateInfo, GetBundlesArgs } from "@hot-updater/core";
|
|
2
|
+
|
|
3
|
+
export const fetchUpdateInfo = async (
|
|
4
|
+
source: string,
|
|
5
|
+
{ appVersion, bundleId, platform, minBundleId, channel }: GetBundlesArgs,
|
|
6
|
+
requestHeaders?: Record<string, string>,
|
|
7
|
+
): Promise<AppUpdateInfo | null> => {
|
|
8
|
+
try {
|
|
9
|
+
return fetch(source, {
|
|
10
|
+
headers: {
|
|
11
|
+
"x-app-platform": platform,
|
|
12
|
+
"x-app-version": appVersion,
|
|
13
|
+
"x-bundle-id": bundleId,
|
|
14
|
+
...(minBundleId ? { "x-min-bundle-id": minBundleId } : {}),
|
|
15
|
+
...(channel ? { "x-channel": channel } : {}),
|
|
16
|
+
...requestHeaders,
|
|
17
|
+
},
|
|
18
|
+
}).then((res) => (res.status === 200 ? res.json() : null));
|
|
19
|
+
} catch {
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
};
|
package/src/index.ts
CHANGED
|
@@ -3,6 +3,8 @@ import {
|
|
|
3
3
|
addListener,
|
|
4
4
|
getAppVersion,
|
|
5
5
|
getBundleId,
|
|
6
|
+
getChannel,
|
|
7
|
+
getMinBundleId,
|
|
6
8
|
reload,
|
|
7
9
|
updateBundle,
|
|
8
10
|
} from "./native";
|
|
@@ -16,7 +18,9 @@ export type { HotUpdaterEvent } from "./native";
|
|
|
16
18
|
export * from "./store";
|
|
17
19
|
|
|
18
20
|
addListener("onProgress", ({ progress }) => {
|
|
19
|
-
hotUpdaterStore.
|
|
21
|
+
hotUpdaterStore.setState({
|
|
22
|
+
progress,
|
|
23
|
+
});
|
|
20
24
|
});
|
|
21
25
|
|
|
22
26
|
export const HotUpdater = {
|
|
@@ -25,6 +29,8 @@ export const HotUpdater = {
|
|
|
25
29
|
reload,
|
|
26
30
|
getAppVersion,
|
|
27
31
|
getBundleId,
|
|
32
|
+
getMinBundleId,
|
|
33
|
+
getChannel,
|
|
28
34
|
addListener,
|
|
29
35
|
|
|
30
36
|
checkForUpdate,
|