@vexor-push/react-native-code-push 0.2.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/LICENSE +20 -0
- package/README.md +61 -0
- package/android/build.gradle +129 -0
- package/android/generated/java/com/vexorpush/codepush/NativeVexorCodePushSpec.java +91 -0
- package/android/generated/jni/CMakeLists.txt +36 -0
- package/android/generated/jni/RNVexorCodePushSpec-generated.cpp +110 -0
- package/android/generated/jni/RNVexorCodePushSpec.h +31 -0
- package/android/generated/jni/react/renderer/components/RNVexorCodePushSpec/ComponentDescriptors.cpp +22 -0
- package/android/generated/jni/react/renderer/components/RNVexorCodePushSpec/ComponentDescriptors.h +24 -0
- package/android/generated/jni/react/renderer/components/RNVexorCodePushSpec/EventEmitters.cpp +16 -0
- package/android/generated/jni/react/renderer/components/RNVexorCodePushSpec/EventEmitters.h +17 -0
- package/android/generated/jni/react/renderer/components/RNVexorCodePushSpec/Props.cpp +19 -0
- package/android/generated/jni/react/renderer/components/RNVexorCodePushSpec/Props.h +18 -0
- package/android/generated/jni/react/renderer/components/RNVexorCodePushSpec/RNVexorCodePushSpecJSI-generated.cpp +129 -0
- package/android/generated/jni/react/renderer/components/RNVexorCodePushSpec/RNVexorCodePushSpecJSI.h +184 -0
- package/android/generated/jni/react/renderer/components/RNVexorCodePushSpec/ShadowNodes.cpp +17 -0
- package/android/generated/jni/react/renderer/components/RNVexorCodePushSpec/ShadowNodes.h +23 -0
- package/android/generated/jni/react/renderer/components/RNVexorCodePushSpec/States.cpp +16 -0
- package/android/generated/jni/react/renderer/components/RNVexorCodePushSpec/States.h +19 -0
- package/android/gradle.properties +5 -0
- package/android/src/main/AndroidManifest.xml +3 -0
- package/android/src/main/AndroidManifestNew.xml +2 -0
- package/android/src/main/java/com/vexorpush/codepush/CrashHandler.kt +78 -0
- package/android/src/main/java/com/vexorpush/codepush/SharedPrefs.kt +60 -0
- package/android/src/main/java/com/vexorpush/codepush/Utils.kt +105 -0
- package/android/src/main/java/com/vexorpush/codepush/VexorCodePush.kt +100 -0
- package/android/src/main/java/com/vexorpush/codepush/VexorCodePushModule.kt +698 -0
- package/android/src/newarch/VexorCodePushSpec.kt +7 -0
- package/android/src/oldarch/VexorCodePushSpec.kt +24 -0
- package/app.plugin.js +1 -0
- package/current-version.js +27 -0
- package/deployment-state.js +33 -0
- package/index.d.ts +101 -0
- package/index.js +535 -0
- package/ios/VexorCodePush.h +13 -0
- package/ios/VexorCodePush.mm +949 -0
- package/ios/generated/RNVexorCodePushSpec/ComponentDescriptors.cpp +22 -0
- package/ios/generated/RNVexorCodePushSpec/ComponentDescriptors.h +24 -0
- package/ios/generated/RNVexorCodePushSpec/EventEmitters.cpp +16 -0
- package/ios/generated/RNVexorCodePushSpec/EventEmitters.h +17 -0
- package/ios/generated/RNVexorCodePushSpec/Props.cpp +19 -0
- package/ios/generated/RNVexorCodePushSpec/Props.h +18 -0
- package/ios/generated/RNVexorCodePushSpec/RCTComponentViewHelpers.h +18 -0
- package/ios/generated/RNVexorCodePushSpec/RNVexorCodePushSpec-generated.mm +130 -0
- package/ios/generated/RNVexorCodePushSpec/RNVexorCodePushSpec.h +109 -0
- package/ios/generated/RNVexorCodePushSpec/ShadowNodes.cpp +17 -0
- package/ios/generated/RNVexorCodePushSpec/ShadowNodes.h +23 -0
- package/ios/generated/RNVexorCodePushSpec/States.cpp +16 -0
- package/ios/generated/RNVexorCodePushSpec/States.h +19 -0
- package/ios/generated/RNVexorCodePushSpecJSI-generated.cpp +129 -0
- package/ios/generated/RNVexorCodePushSpecJSI.h +184 -0
- package/package.json +51 -0
- package/plugin/src/index.ts +92 -0
- package/plugin/tsconfig.json +9 -0
- package/react-native-code-push.podspec +48 -0
- package/react-native.config.js +13 -0
- package/src/NativeVexorCodePush.ts +21 -0
package/index.js
ADDED
|
@@ -0,0 +1,535 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-require-imports */
|
|
2
|
+
const { NativeModules, Platform } = require("react-native");
|
|
3
|
+
const { makeScopedKey } = require("./deployment-state");
|
|
4
|
+
const { resolveCurrentVersion } = require("./current-version");
|
|
5
|
+
|
|
6
|
+
const defaultConfig = {
|
|
7
|
+
baseUrl: "https://app.vexor.one",
|
|
8
|
+
deployment: "Production",
|
|
9
|
+
binaryVersion: "*",
|
|
10
|
+
downloadRetryAttempts: 2,
|
|
11
|
+
downloadRetryDelayMs: 1500,
|
|
12
|
+
debug: false,
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
let runtimeConfig = { ...defaultConfig };
|
|
16
|
+
const clientIdKey = "vexorCodePushClientId";
|
|
17
|
+
|
|
18
|
+
function normalizeUpdateMode(value, fallback = "deferred") {
|
|
19
|
+
const normalized = String(value || "").trim().toLowerCase();
|
|
20
|
+
if (normalized === "force" || normalized === "background" || normalized === "deferred" || normalized === "prompt") {
|
|
21
|
+
return normalized;
|
|
22
|
+
}
|
|
23
|
+
return fallback;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function updateModeToMandatory(updateMode) {
|
|
27
|
+
return updateMode === "force" || updateMode === "background";
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function configure(config) {
|
|
31
|
+
runtimeConfig = normalizeConfig({ ...runtimeConfig, ...config });
|
|
32
|
+
return runtimeConfig;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function getConfig() {
|
|
36
|
+
return { ...runtimeConfig };
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
async function getCurrentVersion(config = runtimeConfig) {
|
|
40
|
+
const scopedConfig = normalizeConfig({ ...runtimeConfig, ...config });
|
|
41
|
+
const storedVersion = await readScopedVersion(scopedConfig);
|
|
42
|
+
if (Number.isFinite(storedVersion)) {
|
|
43
|
+
debugLog(scopedConfig, "getCurrentVersion", {
|
|
44
|
+
source: "stored",
|
|
45
|
+
storedVersion,
|
|
46
|
+
scope: makeScopedKey("vexorCodePushCurrentVersion", scopedConfig),
|
|
47
|
+
});
|
|
48
|
+
return storedVersion;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const pending = await readPendingUpdate(scopedConfig);
|
|
52
|
+
const pendingVersion = Number(pending?.version);
|
|
53
|
+
const nativeVersion = await readNativeCurrentVersion();
|
|
54
|
+
const resolvedVersion = resolveCurrentVersion({
|
|
55
|
+
pendingVersion,
|
|
56
|
+
nativeVersion,
|
|
57
|
+
});
|
|
58
|
+
debugLog(scopedConfig, "getCurrentVersion", {
|
|
59
|
+
source: Number.isFinite(pendingVersion) ? "pending" : Number.isFinite(nativeVersion) ? "native" : "empty",
|
|
60
|
+
pendingVersion,
|
|
61
|
+
nativeVersion,
|
|
62
|
+
resolvedVersion,
|
|
63
|
+
scope: makeScopedKey("vexorCodePushCurrentVersion", scopedConfig),
|
|
64
|
+
});
|
|
65
|
+
return resolvedVersion;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
async function checkUpdate(options = {}) {
|
|
69
|
+
const config = normalizeConfig({ ...runtimeConfig, ...options });
|
|
70
|
+
const currentVersion =
|
|
71
|
+
typeof options.currentVersion === "number" ? options.currentVersion : await getCurrentVersion(config);
|
|
72
|
+
const manifestUrl = buildManifestUrl(config, currentVersion);
|
|
73
|
+
debugLog(config, "checkUpdate:request", {
|
|
74
|
+
currentVersion,
|
|
75
|
+
manifestUrl,
|
|
76
|
+
platform: config.platform,
|
|
77
|
+
binaryVersion: config.binaryVersion,
|
|
78
|
+
deploymentKey: config.deploymentKey,
|
|
79
|
+
appName: config.appName,
|
|
80
|
+
deployment: config.deployment,
|
|
81
|
+
});
|
|
82
|
+
const response = await fetch(manifestUrl, {
|
|
83
|
+
headers: config.headers,
|
|
84
|
+
});
|
|
85
|
+
const manifest = await response.json();
|
|
86
|
+
|
|
87
|
+
if (!response.ok) {
|
|
88
|
+
throw new Error(manifest.message || manifest.errorMessage || `OTA manifest failed: ${response.status}`);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const version = Number(manifest.version || 0);
|
|
92
|
+
const updateMode = normalizeUpdateMode(manifest.updateMode || manifest.metadata?.updateMode, manifest.is_mandatory ? "force" : "deferred");
|
|
93
|
+
const downloadUrl = getDownloadUrl(manifest, config.platform);
|
|
94
|
+
const hasUpdate = version > currentVersion && Boolean(downloadUrl);
|
|
95
|
+
debugLog(config, "checkUpdate:response", {
|
|
96
|
+
status: response.status,
|
|
97
|
+
currentVersion,
|
|
98
|
+
version,
|
|
99
|
+
label: manifest.label,
|
|
100
|
+
hasUpdate,
|
|
101
|
+
downloadUrl,
|
|
102
|
+
updateMode,
|
|
103
|
+
metadata: manifest.metadata,
|
|
104
|
+
});
|
|
105
|
+
return {
|
|
106
|
+
currentVersion,
|
|
107
|
+
hasUpdate,
|
|
108
|
+
manifest,
|
|
109
|
+
manifestUrl,
|
|
110
|
+
version,
|
|
111
|
+
downloadUrl,
|
|
112
|
+
mandatory: updateModeToMandatory(updateMode),
|
|
113
|
+
updateMode,
|
|
114
|
+
metadata: manifest.metadata || {},
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
async function installUpdate(update, options = {}) {
|
|
119
|
+
const manifest = update.manifest || update;
|
|
120
|
+
const config = normalizeConfig({ ...runtimeConfig, ...options });
|
|
121
|
+
const downloadUrl = update.downloadUrl || getDownloadUrl(manifest, config.platform);
|
|
122
|
+
const version = Number(update.version || manifest.version || 0);
|
|
123
|
+
const reportPayload = await buildReportPayload(manifest, config);
|
|
124
|
+
const extensionBundle = resolveBundleExtension(options, config);
|
|
125
|
+
|
|
126
|
+
if (!downloadUrl || !version) {
|
|
127
|
+
throw new Error("No installable OTA update is available.");
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
debugLog(config, "installUpdate:start", {
|
|
131
|
+
version,
|
|
132
|
+
label: reportPayload.label,
|
|
133
|
+
downloadUrl,
|
|
134
|
+
extensionBundle,
|
|
135
|
+
restartAfterInstall: options.restartAfterInstall ?? true,
|
|
136
|
+
scope: makeScopedKey("vexorCodePushCurrentVersion", config),
|
|
137
|
+
});
|
|
138
|
+
await reportDownload(reportPayload, config).catch(() => undefined);
|
|
139
|
+
await savePendingUpdate({
|
|
140
|
+
...reportPayload,
|
|
141
|
+
version,
|
|
142
|
+
label: reportPayload.label,
|
|
143
|
+
installedAt: Date.now(),
|
|
144
|
+
}, config).catch(() => undefined);
|
|
145
|
+
debugLog(config, "installUpdate:pendingSaved", {
|
|
146
|
+
version,
|
|
147
|
+
label: reportPayload.label,
|
|
148
|
+
scope: makeScopedKey("vexorCodePushPendingUpdate", config),
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
try {
|
|
152
|
+
await downloadBundleWithRetry({
|
|
153
|
+
downloadUrl,
|
|
154
|
+
version,
|
|
155
|
+
manifest,
|
|
156
|
+
options,
|
|
157
|
+
config,
|
|
158
|
+
extensionBundle,
|
|
159
|
+
});
|
|
160
|
+
debugLog(config, "installUpdate:downloadSuccess", {
|
|
161
|
+
version,
|
|
162
|
+
label: reportPayload.label,
|
|
163
|
+
});
|
|
164
|
+
await writeScopedVersion(config, version);
|
|
165
|
+
debugLog(config, "installUpdate:versionSaved", {
|
|
166
|
+
version,
|
|
167
|
+
scope: makeScopedKey("vexorCodePushCurrentVersion", config),
|
|
168
|
+
});
|
|
169
|
+
} catch (error) {
|
|
170
|
+
debugLog(config, "installUpdate:error", {
|
|
171
|
+
version,
|
|
172
|
+
label: reportPayload.label,
|
|
173
|
+
message: error?.message || String(error),
|
|
174
|
+
});
|
|
175
|
+
await reportDeploy({ ...reportPayload, status: "DeploymentFailed" }, config).catch(() => undefined);
|
|
176
|
+
await clearPendingUpdate(config).catch(() => undefined);
|
|
177
|
+
if (options.onError) options.onError(error);
|
|
178
|
+
throw error;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
async function downloadBundleWithRetry({ downloadUrl, version, manifest, options, config, extensionBundle }) {
|
|
183
|
+
const attempts = positiveInteger(options.downloadRetryAttempts ?? config.downloadRetryAttempts, 1);
|
|
184
|
+
const delayMs = positiveInteger(options.downloadRetryDelayMs ?? config.downloadRetryDelayMs, 0);
|
|
185
|
+
let lastError;
|
|
186
|
+
|
|
187
|
+
for (let attempt = 1; attempt <= attempts; attempt += 1) {
|
|
188
|
+
let attemptFailedError;
|
|
189
|
+
if (options.onDownloadAttempt) {
|
|
190
|
+
options.onDownloadAttempt({ attempt, attempts, downloadUrl, version });
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
try {
|
|
194
|
+
await downloadAndInstallBundleNative({
|
|
195
|
+
downloadUrl,
|
|
196
|
+
version,
|
|
197
|
+
manifest,
|
|
198
|
+
options,
|
|
199
|
+
config,
|
|
200
|
+
extensionBundle,
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
if (options.onSuccess) options.onSuccess();
|
|
204
|
+
if (options.restartAfterInstall ?? true) {
|
|
205
|
+
setTimeout(() => {
|
|
206
|
+
NativeModules?.VexorCodePush?.restart?.();
|
|
207
|
+
}, options.restartDelay || 300);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
if (attemptFailedError) throw attemptFailedError;
|
|
211
|
+
return;
|
|
212
|
+
} catch (error) {
|
|
213
|
+
lastError = error;
|
|
214
|
+
if (attempt < attempts && delayMs > 0) {
|
|
215
|
+
await sleep(delayMs * attempt);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
throw lastError || new Error("OTA download failed");
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
async function downloadAndInstallBundleNative({ downloadUrl, version, manifest, options, config, extensionBundle }) {
|
|
224
|
+
const nativeModule = NativeModules?.VexorCodePush;
|
|
225
|
+
if (!nativeModule?.downloadAndInstallBundle) {
|
|
226
|
+
throw new Error(
|
|
227
|
+
"VexorCodePush native module does not expose downloadAndInstallBundle. Rebuild the app after installing @vexor-push/react-native-code-push.",
|
|
228
|
+
);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
const success = await nativeModule.downloadAndInstallBundle(
|
|
232
|
+
downloadUrl,
|
|
233
|
+
JSON.stringify(config.headers || {}),
|
|
234
|
+
extensionBundle,
|
|
235
|
+
version,
|
|
236
|
+
positiveInteger(options.maxBundleVersions ?? config.maxBundleVersions, 2),
|
|
237
|
+
JSON.stringify(manifest.metadata || {}),
|
|
238
|
+
);
|
|
239
|
+
|
|
240
|
+
if (!success) {
|
|
241
|
+
throw new Error("OTA download failed");
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
async function sync(options = {}) {
|
|
246
|
+
await checkPendingUpdate(options);
|
|
247
|
+
await notifyApplicationReady(options);
|
|
248
|
+
const update = await checkUpdate(options);
|
|
249
|
+
if (!update.hasUpdate) return update;
|
|
250
|
+
if (update.updateMode === "prompt") return update;
|
|
251
|
+
await installUpdate(update, {
|
|
252
|
+
...options,
|
|
253
|
+
restartAfterInstall: update.updateMode !== "deferred",
|
|
254
|
+
});
|
|
255
|
+
return update;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
function reset(restart = true) {
|
|
259
|
+
debugLog(runtimeConfig, "reset", { restart });
|
|
260
|
+
const nativeModule = NativeModules?.VexorCodePush;
|
|
261
|
+
if (nativeModule?.deleteBundle) {
|
|
262
|
+
nativeModule.deleteBundle(1).then((removed) => {
|
|
263
|
+
if (removed && restart) {
|
|
264
|
+
setTimeout(() => {
|
|
265
|
+
nativeModule.restart?.();
|
|
266
|
+
}, 300);
|
|
267
|
+
}
|
|
268
|
+
}).catch(() => undefined);
|
|
269
|
+
}
|
|
270
|
+
clearPendingUpdate(runtimeConfig).catch(() => undefined);
|
|
271
|
+
clearScopedVersion(runtimeConfig).catch(() => undefined);
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
async function notifyApplicationReady(options = {}) {
|
|
275
|
+
const config = normalizeConfig({ ...runtimeConfig, ...options });
|
|
276
|
+
const pending = await readPendingUpdate(config);
|
|
277
|
+
if (!pending) {
|
|
278
|
+
debugLog(config, "notifyApplicationReady", { status: "none" });
|
|
279
|
+
return { status: "none" };
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
await reportDeploy({ ...pending, status: "DeploymentSucceeded" }, config).catch(() => undefined);
|
|
283
|
+
await clearPendingUpdate(config);
|
|
284
|
+
debugLog(config, "notifyApplicationReady", {
|
|
285
|
+
status: "ready",
|
|
286
|
+
version: pending.version,
|
|
287
|
+
label: pending.label,
|
|
288
|
+
});
|
|
289
|
+
return { status: "ready", pending };
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
async function checkPendingUpdate(options = {}) {
|
|
293
|
+
const config = normalizeConfig({ ...runtimeConfig, ...options });
|
|
294
|
+
const pending = await readPendingUpdate(config);
|
|
295
|
+
if (!pending) {
|
|
296
|
+
debugLog(config, "checkPendingUpdate", { status: "none" });
|
|
297
|
+
return { status: "none" };
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
if (pending.launchSeenAt) {
|
|
301
|
+
await reportDeploy({ ...pending, status: "DeploymentFailed" }, config).catch(() => undefined);
|
|
302
|
+
await clearPendingUpdate(config).catch(() => undefined);
|
|
303
|
+
if (options.rollbackOnFailedPending !== false) {
|
|
304
|
+
hotUpdate.removeUpdate(options.restartAfterRollback ?? false);
|
|
305
|
+
}
|
|
306
|
+
debugLog(config, "checkPendingUpdate", {
|
|
307
|
+
status: "rolledBack",
|
|
308
|
+
version: pending.version,
|
|
309
|
+
label: pending.label,
|
|
310
|
+
});
|
|
311
|
+
return { status: "rolledBack", pending };
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
const nextPending = { ...pending, launchSeenAt: Date.now() };
|
|
315
|
+
await savePendingUpdate(nextPending, config).catch(() => undefined);
|
|
316
|
+
debugLog(config, "checkPendingUpdate", {
|
|
317
|
+
status: "pending",
|
|
318
|
+
version: nextPending.version,
|
|
319
|
+
label: nextPending.label,
|
|
320
|
+
});
|
|
321
|
+
return { status: "pending", pending: nextPending };
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
function buildManifestUrl(config, currentVersion) {
|
|
325
|
+
const baseUrl = String(config.baseUrl || defaultConfig.baseUrl).replace(/\/+$/, "");
|
|
326
|
+
const path = config.deploymentKey
|
|
327
|
+
? `/api/ota/key/${encodeURIComponent(config.deploymentKey)}/update.json`
|
|
328
|
+
: `/api/ota/${encodeURIComponent(required(config.appName, "appName"))}/${encodeURIComponent(
|
|
329
|
+
config.deployment || defaultConfig.deployment,
|
|
330
|
+
)}/update.json`;
|
|
331
|
+
const params = [
|
|
332
|
+
["currentVersion", String(currentVersion || 0)],
|
|
333
|
+
["platform", config.platform || Platform.OS],
|
|
334
|
+
["binaryVersion", config.binaryVersion || defaultConfig.binaryVersion],
|
|
335
|
+
];
|
|
336
|
+
if (config.clientId) {
|
|
337
|
+
params.push(["clientId", String(config.clientId)]);
|
|
338
|
+
}
|
|
339
|
+
return `${baseUrl}${path}?${toQueryString(params)}`;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
function toQueryString(params) {
|
|
343
|
+
return params
|
|
344
|
+
.map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(value)}`)
|
|
345
|
+
.join("&");
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
function getDownloadUrl(manifest, platform = Platform.OS) {
|
|
349
|
+
if (!manifest) return "";
|
|
350
|
+
if (platform === "android") {
|
|
351
|
+
return manifest.downloadAndroidUrl || manifest.download_url || "";
|
|
352
|
+
}
|
|
353
|
+
return manifest.downloadIosUrl || manifest.download_url || "";
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
async function reportDownload(payload, config = runtimeConfig) {
|
|
357
|
+
return postReport("download", payload, config);
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
async function reportDeploy(payload, config = runtimeConfig) {
|
|
361
|
+
return postReport("deploy", payload, config);
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
async function postReport(kind, payload, config) {
|
|
365
|
+
if (!payload.deploymentKey || !payload.label) return;
|
|
366
|
+
|
|
367
|
+
const clientUniqueId = payload.clientUniqueId || (await getClientUniqueId(config));
|
|
368
|
+
if (!clientUniqueId) return;
|
|
369
|
+
|
|
370
|
+
const baseUrl = String(config.baseUrl || defaultConfig.baseUrl).replace(/\/+$/, "");
|
|
371
|
+
const response = await fetch(`${baseUrl}/api/ota/report/${kind}`, {
|
|
372
|
+
method: "POST",
|
|
373
|
+
headers: {
|
|
374
|
+
"Content-Type": "application/json",
|
|
375
|
+
...(config.headers || {}),
|
|
376
|
+
},
|
|
377
|
+
body: JSON.stringify({ ...payload, clientUniqueId }),
|
|
378
|
+
});
|
|
379
|
+
|
|
380
|
+
if (!response.ok) {
|
|
381
|
+
throw new Error(`OTA report ${kind} failed: ${response.status}`);
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
async function buildReportPayload(manifest, config) {
|
|
386
|
+
return {
|
|
387
|
+
deploymentKey: config.deploymentKey || manifest.metadata?.deploymentKey || "",
|
|
388
|
+
label: manifest.label || manifest.metadata?.label || "",
|
|
389
|
+
clientUniqueId: await getClientUniqueId(config),
|
|
390
|
+
previousDeploymentKey: config.previousDeploymentKey,
|
|
391
|
+
previousLabelOrAppVersion: config.previousLabelOrAppVersion || config.binaryVersion,
|
|
392
|
+
};
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
async function getClientUniqueId(config = runtimeConfig) {
|
|
396
|
+
if (config.clientId) return String(config.clientId);
|
|
397
|
+
|
|
398
|
+
const existing = await storageGetItem(clientIdKey);
|
|
399
|
+
if (existing) return existing;
|
|
400
|
+
|
|
401
|
+
const generated = `${Platform.OS}-${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}`;
|
|
402
|
+
await storageSetItem(clientIdKey, generated).catch(() => undefined);
|
|
403
|
+
return generated;
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
async function savePendingUpdate(payload, config = runtimeConfig) {
|
|
407
|
+
await writeJson(makeScopedKey("vexorCodePushPendingUpdate", config), payload);
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
async function readPendingUpdate(config = runtimeConfig) {
|
|
411
|
+
return readJson(makeScopedKey("vexorCodePushPendingUpdate", config));
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
async function clearPendingUpdate(config = runtimeConfig) {
|
|
415
|
+
await removeItem(makeScopedKey("vexorCodePushPendingUpdate", config));
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
async function writeScopedVersion(config, version) {
|
|
419
|
+
await writeJson(makeScopedKey("vexorCodePushCurrentVersion", config), {
|
|
420
|
+
version: Number(version) || 0,
|
|
421
|
+
});
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
async function readScopedVersion(config) {
|
|
425
|
+
const stored = await readJson(makeScopedKey("vexorCodePushCurrentVersion", config));
|
|
426
|
+
const version = Number(stored?.version);
|
|
427
|
+
return Number.isFinite(version) ? version : undefined;
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
async function readNativeCurrentVersion() {
|
|
431
|
+
const nativeModule = NativeModules?.VexorCodePush;
|
|
432
|
+
|
|
433
|
+
if (!nativeModule?.getCurrentVersion) {
|
|
434
|
+
return undefined;
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
try {
|
|
438
|
+
const version = await nativeModule.getCurrentVersion(0);
|
|
439
|
+
const parsed = Number(version);
|
|
440
|
+
return Number.isFinite(parsed) ? parsed : undefined;
|
|
441
|
+
} catch {
|
|
442
|
+
return undefined;
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
async function clearScopedVersion(config = runtimeConfig) {
|
|
447
|
+
await removeItem(makeScopedKey("vexorCodePushCurrentVersion", config));
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
async function readJson(key) {
|
|
451
|
+
const value = await storageGetItem(key);
|
|
452
|
+
if (!value) return null;
|
|
453
|
+
try {
|
|
454
|
+
return JSON.parse(value);
|
|
455
|
+
} catch {
|
|
456
|
+
return null;
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
async function writeJson(key, value) {
|
|
461
|
+
await storageSetItem(key, JSON.stringify(value));
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
async function storageGetItem(key) {
|
|
465
|
+
if (runtimeConfig.storage?.getItem) return runtimeConfig.storage.getItem(key);
|
|
466
|
+
if (globalThis.localStorage?.getItem) return globalThis.localStorage.getItem(key);
|
|
467
|
+
return memoryStorage.get(key) ?? null;
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
async function storageSetItem(key, value) {
|
|
471
|
+
if (runtimeConfig.storage?.setItem) return runtimeConfig.storage.setItem(key, value);
|
|
472
|
+
if (globalThis.localStorage?.setItem) return globalThis.localStorage.setItem(key, value);
|
|
473
|
+
memoryStorage.set(key, value);
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
async function removeItem(key) {
|
|
477
|
+
if (runtimeConfig.storage?.removeItem) return runtimeConfig.storage.removeItem(key);
|
|
478
|
+
if (globalThis.localStorage?.removeItem) return globalThis.localStorage.removeItem(key);
|
|
479
|
+
memoryStorage.delete(key);
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
const memoryStorage = new Map();
|
|
483
|
+
|
|
484
|
+
function normalizeConfig(config) {
|
|
485
|
+
return {
|
|
486
|
+
...config,
|
|
487
|
+
baseUrl: config.baseUrl || defaultConfig.baseUrl,
|
|
488
|
+
deployment: config.deployment || defaultConfig.deployment,
|
|
489
|
+
binaryVersion: config.binaryVersion || defaultConfig.binaryVersion,
|
|
490
|
+
platform: config.platform || Platform.OS,
|
|
491
|
+
};
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
function resolveBundleExtension(options = {}, config = runtimeConfig) {
|
|
495
|
+
if (options.extensionBundle) return options.extensionBundle;
|
|
496
|
+
return config.platform === "android" ? ".bundle" : ".jsbundle";
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
function positiveInteger(value, fallback) {
|
|
500
|
+
const parsed = Number(value);
|
|
501
|
+
return Number.isFinite(parsed) && parsed > 0 ? Math.floor(parsed) : fallback;
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
function sleep(ms) {
|
|
505
|
+
return new Promise((resolve) => {
|
|
506
|
+
setTimeout(resolve, ms);
|
|
507
|
+
});
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
function required(value, name) {
|
|
511
|
+
if (!value) throw new Error(`OTA config "${name}" is required.`);
|
|
512
|
+
return value;
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
function debugLog(config, event, payload = {}) {
|
|
516
|
+
if (!config?.debug && !runtimeConfig.debug) return;
|
|
517
|
+
console.log("[VEXOR-CODEPUSH-DEBUG]", event, payload);
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
module.exports = {
|
|
521
|
+
configure,
|
|
522
|
+
getConfig,
|
|
523
|
+
getCurrentVersion,
|
|
524
|
+
getClientUniqueId,
|
|
525
|
+
checkUpdate,
|
|
526
|
+
installUpdate,
|
|
527
|
+
sync,
|
|
528
|
+
reset,
|
|
529
|
+
notifyApplicationReady,
|
|
530
|
+
checkPendingUpdate,
|
|
531
|
+
reportDownload,
|
|
532
|
+
reportDeploy,
|
|
533
|
+
buildManifestUrl,
|
|
534
|
+
getDownloadUrl,
|
|
535
|
+
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
#import <React/RCTReloadCommand.h>
|
|
2
|
+
|
|
3
|
+
#if defined(RCT_NEW_ARCH_ENABLED) && __has_include("RNVexorCodePushSpec.h")
|
|
4
|
+
#import "RNVexorCodePushSpec.h"
|
|
5
|
+
@interface VexorCodePush : NSObject <NativeVexorCodePushSpec>
|
|
6
|
+
#else
|
|
7
|
+
#import <React/RCTBridgeModule.h>
|
|
8
|
+
@interface VexorCodePush : NSObject <RCTBridgeModule>
|
|
9
|
+
#endif
|
|
10
|
+
|
|
11
|
+
+ (NSURL *)getBundle;
|
|
12
|
+
|
|
13
|
+
@end
|