@hot-updater/react-native 0.25.14 → 0.26.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/src/main/java/com/hotupdater/BundleFileStorageService.kt +42 -0
- package/android/src/main/java/com/hotupdater/HotUpdaterImpl.kt +25 -0
- package/android/src/newarch/HotUpdater.kt +4 -1
- package/android/src/newarch/HotUpdaterModule.kt +18 -1
- package/android/src/oldarch/HotUpdater.kt +4 -1
- package/android/src/oldarch/HotUpdaterModule.kt +19 -1
- package/android/src/oldarch/HotUpdaterSpec.kt +2 -0
- package/ios/HotUpdater/Internal/BundleFileStorageService.swift +44 -0
- package/ios/HotUpdater/Internal/HotUpdater.mm +17 -0
- package/ios/HotUpdater/Internal/HotUpdaterImpl.swift +44 -2
- package/lib/commonjs/checkForUpdate.js +29 -4
- package/lib/commonjs/checkForUpdate.js.map +1 -1
- package/lib/commonjs/index.js +35 -0
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/native.js +88 -21
- package/lib/commonjs/native.js.map +1 -1
- package/lib/commonjs/specs/NativeHotUpdater.js.map +1 -1
- package/lib/module/checkForUpdate.js +29 -5
- package/lib/module/checkForUpdate.js.map +1 -1
- package/lib/module/index.js +36 -1
- package/lib/module/index.js.map +1 -1
- package/lib/module/native.js +81 -16
- package/lib/module/native.js.map +1 -1
- package/lib/module/specs/NativeHotUpdater.js.map +1 -1
- package/lib/typescript/commonjs/checkForUpdate.d.ts +5 -0
- package/lib/typescript/commonjs/checkForUpdate.d.ts.map +1 -1
- package/lib/typescript/commonjs/index.d.ts +26 -0
- package/lib/typescript/commonjs/index.d.ts.map +1 -1
- package/lib/typescript/commonjs/native.d.ts +13 -0
- package/lib/typescript/commonjs/native.d.ts.map +1 -1
- package/lib/typescript/commonjs/specs/NativeHotUpdater.d.ts +8 -0
- package/lib/typescript/commonjs/specs/NativeHotUpdater.d.ts.map +1 -1
- package/lib/typescript/module/checkForUpdate.d.ts +5 -0
- package/lib/typescript/module/checkForUpdate.d.ts.map +1 -1
- package/lib/typescript/module/index.d.ts +26 -0
- package/lib/typescript/module/index.d.ts.map +1 -1
- package/lib/typescript/module/native.d.ts +13 -0
- package/lib/typescript/module/native.d.ts.map +1 -1
- package/lib/typescript/module/specs/NativeHotUpdater.d.ts +8 -0
- package/lib/typescript/module/specs/NativeHotUpdater.d.ts.map +1 -1
- package/package.json +6 -6
- package/src/checkForUpdate.ts +63 -3
- package/src/index.ts +41 -0
- package/src/native.ts +103 -16
- package/src/specs/NativeHotUpdater.ts +9 -0
package/src/native.ts
CHANGED
|
@@ -15,6 +15,62 @@ export const HotUpdaterConstants = {
|
|
|
15
15
|
HOT_UPDATER_BUNDLE_ID: __HOT_UPDATER_BUNDLE_ID || NIL_UUID,
|
|
16
16
|
};
|
|
17
17
|
|
|
18
|
+
class HotUpdaterSessionState {
|
|
19
|
+
private readonly defaultChannel: string;
|
|
20
|
+
private currentChannel: string;
|
|
21
|
+
private readonly inflightUpdates = new Map<string, Promise<boolean>>();
|
|
22
|
+
private lastInstalledBundleId: string | null = null;
|
|
23
|
+
|
|
24
|
+
constructor() {
|
|
25
|
+
const constants = HotUpdaterNative.getConstants();
|
|
26
|
+
this.defaultChannel = constants.DEFAULT_CHANNEL ?? constants.CHANNEL;
|
|
27
|
+
this.currentChannel = constants.CHANNEL;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
getChannel(): string {
|
|
31
|
+
return this.currentChannel;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
getDefaultChannel(): string {
|
|
35
|
+
return this.defaultChannel;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
isChannelSwitched(): boolean {
|
|
39
|
+
return this.currentChannel !== this.defaultChannel;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
hasInstalledBundle(bundleId: string): boolean {
|
|
43
|
+
return this.lastInstalledBundleId === bundleId;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
getInflightUpdate(bundleId: string): Promise<boolean> | undefined {
|
|
47
|
+
return this.inflightUpdates.get(bundleId);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
trackInflightUpdate(bundleId: string, promise: Promise<boolean>) {
|
|
51
|
+
this.inflightUpdates.set(bundleId, promise);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
clearInflightUpdate(bundleId: string) {
|
|
55
|
+
this.inflightUpdates.delete(bundleId);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
markBundleInstalled(bundleId: string, channel?: string) {
|
|
59
|
+
this.lastInstalledBundleId = bundleId;
|
|
60
|
+
if (channel) {
|
|
61
|
+
this.currentChannel = channel;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
resetChannelState() {
|
|
66
|
+
this.currentChannel = this.defaultChannel;
|
|
67
|
+
this.lastInstalledBundleId = null;
|
|
68
|
+
this.inflightUpdates.clear();
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const sessionState = new HotUpdaterSessionState();
|
|
73
|
+
|
|
18
74
|
export type HotUpdaterEvent = {
|
|
19
75
|
onProgress: {
|
|
20
76
|
progress: number;
|
|
@@ -36,13 +92,9 @@ export const addListener = <T extends keyof HotUpdaterEvent>(
|
|
|
36
92
|
|
|
37
93
|
export type UpdateParams = UpdateBundleParams & {
|
|
38
94
|
status: UpdateStatus;
|
|
95
|
+
shouldSkipCurrentBundleIdCheck?: boolean;
|
|
39
96
|
};
|
|
40
97
|
|
|
41
|
-
// In-flight update deduplication by bundleId (session-scoped).
|
|
42
|
-
const inflightUpdates = new Map<string, Promise<boolean>>();
|
|
43
|
-
// Tracks the last successfully installed bundleId for this session.
|
|
44
|
-
let lastInstalledBundleId: string | null = null;
|
|
45
|
-
|
|
46
98
|
/**
|
|
47
99
|
* Downloads files and applies them to the app.
|
|
48
100
|
*
|
|
@@ -71,24 +123,27 @@ export async function updateBundle(
|
|
|
71
123
|
typeof paramsOrBundleId === "string" ? "UPDATE" : paramsOrBundleId.status;
|
|
72
124
|
|
|
73
125
|
// If we have already installed this bundle in this session, skip re-download.
|
|
74
|
-
if (status === "UPDATE" &&
|
|
126
|
+
if (status === "UPDATE" && sessionState.hasInstalledBundle(updateBundleId)) {
|
|
75
127
|
return true;
|
|
76
128
|
}
|
|
77
129
|
|
|
78
|
-
const
|
|
130
|
+
const shouldSkipCurrentBundleIdCheck =
|
|
131
|
+
typeof paramsOrBundleId === "string"
|
|
132
|
+
? false
|
|
133
|
+
: paramsOrBundleId.shouldSkipCurrentBundleIdCheck === true;
|
|
79
134
|
|
|
80
|
-
// updateBundleId <= currentBundleId
|
|
81
135
|
if (
|
|
136
|
+
!shouldSkipCurrentBundleIdCheck &&
|
|
82
137
|
status === "UPDATE" &&
|
|
83
|
-
updateBundleId.localeCompare(
|
|
138
|
+
updateBundleId.localeCompare(getBundleId()) <= 0
|
|
84
139
|
) {
|
|
85
140
|
throw new Error(
|
|
86
|
-
"Update bundle id is
|
|
141
|
+
"Update bundle id is not newer than the current bundle id. Preventing infinite update loop.",
|
|
87
142
|
);
|
|
88
143
|
}
|
|
89
144
|
|
|
90
145
|
// In-flight guard: return the same promise if the same bundle is already updating.
|
|
91
|
-
const existing =
|
|
146
|
+
const existing = sessionState.getInflightUpdate(updateBundleId);
|
|
92
147
|
if (existing) return existing;
|
|
93
148
|
|
|
94
149
|
const targetFileUrl =
|
|
@@ -101,23 +156,27 @@ export async function updateBundle(
|
|
|
101
156
|
? undefined
|
|
102
157
|
: paramsOrBundleId.fileHash;
|
|
103
158
|
|
|
159
|
+
const targetChannel =
|
|
160
|
+
typeof paramsOrBundleId === "string" ? undefined : paramsOrBundleId.channel;
|
|
161
|
+
|
|
104
162
|
const promise = (async () => {
|
|
105
163
|
try {
|
|
106
164
|
const ok = await HotUpdaterNative.updateBundle({
|
|
107
165
|
bundleId: updateBundleId,
|
|
166
|
+
channel: targetChannel,
|
|
108
167
|
fileUrl: targetFileUrl,
|
|
109
168
|
fileHash: targetFileHash ?? null,
|
|
110
169
|
});
|
|
111
170
|
if (ok) {
|
|
112
|
-
|
|
171
|
+
sessionState.markBundleInstalled(updateBundleId, targetChannel);
|
|
113
172
|
}
|
|
114
173
|
return ok;
|
|
115
174
|
} finally {
|
|
116
|
-
|
|
175
|
+
sessionState.clearInflightUpdate(updateBundleId);
|
|
117
176
|
}
|
|
118
177
|
})();
|
|
119
178
|
|
|
120
|
-
|
|
179
|
+
sessionState.trackInflightUpdate(updateBundleId, promise);
|
|
121
180
|
return promise;
|
|
122
181
|
}
|
|
123
182
|
|
|
@@ -165,8 +224,21 @@ export const getBundleId = (): string => {
|
|
|
165
224
|
* @returns {string} Resolves with the channel or null if not available.
|
|
166
225
|
*/
|
|
167
226
|
export const getChannel = (): string => {
|
|
168
|
-
|
|
169
|
-
|
|
227
|
+
return sessionState.getChannel();
|
|
228
|
+
};
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* Fetches the build-time default channel for the app.
|
|
232
|
+
*/
|
|
233
|
+
export const getDefaultChannel = (): string => {
|
|
234
|
+
return sessionState.getDefaultChannel();
|
|
235
|
+
};
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Returns whether the app is currently using a runtime channel override.
|
|
239
|
+
*/
|
|
240
|
+
export const isChannelSwitched = (): boolean => {
|
|
241
|
+
return sessionState.isChannelSwitched();
|
|
170
242
|
};
|
|
171
243
|
|
|
172
244
|
/**
|
|
@@ -275,3 +347,18 @@ export const getBaseURL = (): string | null => {
|
|
|
275
347
|
}
|
|
276
348
|
return null;
|
|
277
349
|
};
|
|
350
|
+
|
|
351
|
+
/**
|
|
352
|
+
* Clears the runtime channel override and restores the original bundle.
|
|
353
|
+
*/
|
|
354
|
+
export const resetChannel = async (): Promise<boolean> => {
|
|
355
|
+
if (!sessionState.isChannelSwitched()) {
|
|
356
|
+
return true;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
const ok = await HotUpdaterNative.resetChannel();
|
|
360
|
+
if (ok) {
|
|
361
|
+
sessionState.resetChannelState();
|
|
362
|
+
}
|
|
363
|
+
return ok;
|
|
364
|
+
};
|
|
@@ -3,6 +3,7 @@ import { TurboModuleRegistry } from "react-native";
|
|
|
3
3
|
|
|
4
4
|
export interface UpdateBundleParams {
|
|
5
5
|
bundleId: string;
|
|
6
|
+
channel?: string;
|
|
6
7
|
fileUrl: string | null;
|
|
7
8
|
/**
|
|
8
9
|
* File hash for integrity/signature verification.
|
|
@@ -84,6 +85,13 @@ export interface Spec extends TurboModule {
|
|
|
84
85
|
*/
|
|
85
86
|
clearCrashHistory(): boolean;
|
|
86
87
|
|
|
88
|
+
/**
|
|
89
|
+
* Clears the runtime channel override and restores the original bundle.
|
|
90
|
+
*
|
|
91
|
+
* @returns Promise that resolves to true if successful
|
|
92
|
+
*/
|
|
93
|
+
resetChannel(): Promise<boolean>;
|
|
94
|
+
|
|
87
95
|
/**
|
|
88
96
|
* Gets the base URL for the current active bundle directory.
|
|
89
97
|
* Returns the file:// URL to the bundle directory without trailing slash.
|
|
@@ -100,6 +108,7 @@ export interface Spec extends TurboModule {
|
|
|
100
108
|
MIN_BUNDLE_ID: string;
|
|
101
109
|
APP_VERSION: string | null;
|
|
102
110
|
CHANNEL: string;
|
|
111
|
+
DEFAULT_CHANNEL: string;
|
|
103
112
|
FINGERPRINT_HASH: string | null;
|
|
104
113
|
};
|
|
105
114
|
}
|