@viettelpost/react-native-ota 0.1.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/README.md +38 -0
- package/android/build.gradle +48 -0
- package/android/src/main/java/com/viettelpost/otakit/OTAHashUtils.kt +21 -0
- package/android/src/main/java/com/viettelpost/otakit/OTATestReceiver.kt +51 -0
- package/android/src/main/java/com/viettelpost/otakit/OTAUpdateBundleResolver.kt +405 -0
- package/android/src/main/java/com/viettelpost/otakit/OTAUpdateCleanup.kt +186 -0
- package/android/src/main/java/com/viettelpost/otakit/OTAUpdateDownloader.kt +649 -0
- package/android/src/main/java/com/viettelpost/otakit/OTAUpdateMetadata.kt +72 -0
- package/android/src/main/java/com/viettelpost/otakit/OTAUpdateModule.kt +140 -0
- package/android/src/main/java/com/viettelpost/otakit/OTAUpdatePackage.kt +30 -0
- package/android/src/main/java/com/viettelpost/otakit/OTAUpdateSignatureVerifier.kt +63 -0
- package/android/src/main/java/com/viettelpost/otakit/OTAUpdateStorage.kt +62 -0
- package/android/src/main/java/com/viettelpost/otakit/OTAZipUtils.kt +100 -0
- package/android/src/main/res/raw/ota_public_key.pem +9 -0
- package/bin/cli/assets-zip.js +77 -0
- package/bin/cli/bundle.js +72 -0
- package/bin/cli/deploy.js +224 -0
- package/bin/cli/sign.js +97 -0
- package/bin/cli/upload.js +109 -0
- package/bin/ota.js +200 -0
- package/docs/BACKEND_CONTRACT.md +93 -0
- package/docs/DEPLOY_CLI.md +39 -0
- package/docs/INTEGRATION_ANDROID.md +20 -0
- package/docs/INTEGRATION_IOS.md +21 -0
- package/docs/RELEASE_WORKFLOW.md +14 -0
- package/ios/OTAHashUtils.swift +22 -0
- package/ios/OTAUpdateBundleResolver.swift +359 -0
- package/ios/OTAUpdateCleanup.swift +269 -0
- package/ios/OTAUpdateDownloader.swift +709 -0
- package/ios/OTAUpdateMetadata.swift +47 -0
- package/ios/OTAUpdateModule.mm +190 -0
- package/ios/OTAUpdateSignatureVerifier.swift +81 -0
- package/ios/OTAUpdateStorage.swift +83 -0
- package/ios/OTAZipUtils.swift +103 -0
- package/ios/ota_public_key.pem +9 -0
- package/lib/NativeOTAUpdate.d.ts +77 -0
- package/lib/NativeOTAUpdate.js +59 -0
- package/lib/OTAClient.d.ts +27 -0
- package/lib/OTAClient.js +101 -0
- package/lib/config.d.ts +14 -0
- package/lib/config.js +29 -0
- package/lib/devtools.d.ts +10 -0
- package/lib/devtools.js +54 -0
- package/lib/index.d.ts +15 -0
- package/lib/index.js +32 -0
- package/lib/spec/NativeOTAUpdate.d.ts +16 -0
- package/lib/spec/NativeOTAUpdate.js +4 -0
- package/package.json +82 -0
- package/react-native-ota.podspec +21 -0
- package/scripts/run-bin.js +67 -0
- package/src/NativeOTAUpdate.ts +144 -0
- package/src/OTAClient.ts +151 -0
- package/src/config.ts +41 -0
- package/src/devtools.ts +64 -0
- package/src/index.ts +69 -0
- package/src/spec/NativeOTAUpdate.ts +21 -0
package/lib/OTAClient.js
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getCurrentOTAStatus = exports.sync = exports.checkAndInstallOTA = void 0;
|
|
4
|
+
const react_native_1 = require("react-native");
|
|
5
|
+
const NativeOTAUpdate_1 = require("./NativeOTAUpdate");
|
|
6
|
+
const config_1 = require("./config");
|
|
7
|
+
function buildCheckUpdateURL(baseURL, currentVersion, checkUpdatePath = '/api/ota/check-update') {
|
|
8
|
+
const platform = react_native_1.Platform.OS === 'ios' ? 'ios' : 'android';
|
|
9
|
+
const normalizedBaseURL = baseURL.replace(/\/+$/, '');
|
|
10
|
+
const normalizedPath = checkUpdatePath.startsWith('/')
|
|
11
|
+
? checkUpdatePath
|
|
12
|
+
: `/${checkUpdatePath}`;
|
|
13
|
+
return `${normalizedBaseURL}${normalizedPath}?platform=${platform}¤tVersion=${encodeURIComponent(currentVersion)}`;
|
|
14
|
+
}
|
|
15
|
+
async function fetchUpdateMetadata(url, headers) {
|
|
16
|
+
const response = await fetch(url, {
|
|
17
|
+
method: 'GET',
|
|
18
|
+
headers: { 'Content-Type': 'application/json', ...headers },
|
|
19
|
+
});
|
|
20
|
+
if (response.status === 204) {
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
if (!response.ok) {
|
|
24
|
+
throw new Error(`OTA check-update failed: HTTP ${response.status}`);
|
|
25
|
+
}
|
|
26
|
+
const data = (await response.json());
|
|
27
|
+
const required = ['version', 'bundleUrl', 'fileName', 'platform', 'sha256', 'signature'];
|
|
28
|
+
for (const field of required) {
|
|
29
|
+
if (typeof data[field] !== 'string' || !data[field].trim()) {
|
|
30
|
+
throw new Error(`OTA check-update response missing required field: ${field}`);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
return data;
|
|
34
|
+
}
|
|
35
|
+
function toPayload(r) {
|
|
36
|
+
return {
|
|
37
|
+
version: r.version,
|
|
38
|
+
bundleUrl: r.bundleUrl,
|
|
39
|
+
platform: r.platform,
|
|
40
|
+
fileName: r.fileName,
|
|
41
|
+
sha256: r.sha256,
|
|
42
|
+
signature: r.signature,
|
|
43
|
+
assetsUrl: r.assetsUrl,
|
|
44
|
+
assetsSha256: r.assetsSha256,
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
async function checkAndInstallOTA(otaBaseURL, options = {}) {
|
|
48
|
+
try {
|
|
49
|
+
const metadata = await (0, NativeOTAUpdate_1.getOTAMetadata)();
|
|
50
|
+
const currentVersion = metadata.activeBundleVersion || metadata.embeddedBundleVersion;
|
|
51
|
+
const url = buildCheckUpdateURL(otaBaseURL, currentVersion, options.checkUpdatePath);
|
|
52
|
+
const update = await fetchUpdateMetadata(url, await (0, config_1.resolveHeaders)(options.headers));
|
|
53
|
+
if (!update) {
|
|
54
|
+
return { success: true, version: currentVersion, alreadyUpToDate: true };
|
|
55
|
+
}
|
|
56
|
+
const expectedPlatform = react_native_1.Platform.OS === 'ios' ? 'ios' : 'android';
|
|
57
|
+
if (update.platform !== expectedPlatform) {
|
|
58
|
+
return {
|
|
59
|
+
success: false,
|
|
60
|
+
code: 'OTA_PLATFORM_MISMATCH',
|
|
61
|
+
message: `Server returned platform=${update.platform}, expected ${expectedPlatform}`,
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
if (metadata.activeBundleVersion === update.version) {
|
|
65
|
+
return { success: true, version: update.version, alreadyUpToDate: true };
|
|
66
|
+
}
|
|
67
|
+
const didInstall = await (0, NativeOTAUpdate_1.downloadAndInstallBundle)(toPayload(update));
|
|
68
|
+
if (!didInstall) {
|
|
69
|
+
return {
|
|
70
|
+
success: false,
|
|
71
|
+
code: 'OTA_INSTALL_FAILED',
|
|
72
|
+
message: `Failed to install OTA update version ${update.version}`,
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
return { success: true, version: update.version };
|
|
76
|
+
}
|
|
77
|
+
catch (error) {
|
|
78
|
+
return {
|
|
79
|
+
success: false,
|
|
80
|
+
code: 'OTA_SERVICE_ERROR',
|
|
81
|
+
message: error instanceof Error ? error.message : String(error),
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
exports.checkAndInstallOTA = checkAndInstallOTA;
|
|
86
|
+
async function sync() {
|
|
87
|
+
const config = (0, config_1.getConfig)();
|
|
88
|
+
return checkAndInstallOTA(config.baseURL, {
|
|
89
|
+
headers: config.headers,
|
|
90
|
+
checkUpdatePath: config.checkUpdatePath,
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
exports.sync = sync;
|
|
94
|
+
async function getCurrentOTAStatus() {
|
|
95
|
+
const [metadata, bundleInfo] = await Promise.all([
|
|
96
|
+
(0, NativeOTAUpdate_1.getOTAMetadata)(),
|
|
97
|
+
(0, NativeOTAUpdate_1.getCurrentBundleInfo)(),
|
|
98
|
+
]);
|
|
99
|
+
return { metadata, bundleInfo };
|
|
100
|
+
}
|
|
101
|
+
exports.getCurrentOTAStatus = getCurrentOTAStatus;
|
package/lib/config.d.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export type OTAHeaders = Record<string, string> | (() => Record<string, string> | Promise<Record<string, string>>);
|
|
2
|
+
export type OTAConfig = {
|
|
3
|
+
baseURL: string;
|
|
4
|
+
headers?: OTAHeaders;
|
|
5
|
+
checkUpdatePath?: string;
|
|
6
|
+
publicKey?: string;
|
|
7
|
+
policy?: {
|
|
8
|
+
autoInstall?: boolean;
|
|
9
|
+
restart?: 'manual' | 'immediate';
|
|
10
|
+
};
|
|
11
|
+
};
|
|
12
|
+
export declare function configure(config: OTAConfig): void;
|
|
13
|
+
export declare function getConfig(): OTAConfig;
|
|
14
|
+
export declare function resolveHeaders(headers?: OTAHeaders): Promise<Record<string, string>>;
|
package/lib/config.js
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.resolveHeaders = exports.getConfig = exports.configure = void 0;
|
|
4
|
+
let currentConfig = null;
|
|
5
|
+
function configure(config) {
|
|
6
|
+
if (!config.baseURL || !config.baseURL.trim()) {
|
|
7
|
+
throw new Error('OTA.configure requires a non-empty baseURL');
|
|
8
|
+
}
|
|
9
|
+
currentConfig = {
|
|
10
|
+
...config,
|
|
11
|
+
baseURL: config.baseURL.replace(/\/+$/, ''),
|
|
12
|
+
checkUpdatePath: config.checkUpdatePath || '/api/ota/check-update',
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
exports.configure = configure;
|
|
16
|
+
function getConfig() {
|
|
17
|
+
if (!currentConfig) {
|
|
18
|
+
throw new Error('OTA is not configured. Call OTA.configure({ baseURL }) before syncing.');
|
|
19
|
+
}
|
|
20
|
+
return currentConfig;
|
|
21
|
+
}
|
|
22
|
+
exports.getConfig = getConfig;
|
|
23
|
+
async function resolveHeaders(headers) {
|
|
24
|
+
if (!headers) {
|
|
25
|
+
return {};
|
|
26
|
+
}
|
|
27
|
+
return typeof headers === 'function' ? headers() : headers;
|
|
28
|
+
}
|
|
29
|
+
exports.resolveHeaders = resolveHeaders;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { type OTAUpdatePayload } from './NativeOTAUpdate';
|
|
2
|
+
export declare function logOTAInfo(): Promise<{
|
|
3
|
+
directory: string;
|
|
4
|
+
metadata: import("./NativeOTAUpdate").OTAMetadata;
|
|
5
|
+
bundleInfo: import("./NativeOTAUpdate").OTABundleInfo;
|
|
6
|
+
}>;
|
|
7
|
+
export declare function confirmOTASuccess(): Promise<boolean>;
|
|
8
|
+
export declare function downloadAndInstallOTA(update: OTAUpdatePayload): Promise<boolean>;
|
|
9
|
+
export declare function logOTADiskUsage(): Promise<import("./NativeOTAUpdate").OTADiskUsage>;
|
|
10
|
+
export declare function cleanupOTADevStorage(): Promise<boolean>;
|
package/lib/devtools.js
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.cleanupOTADevStorage = exports.logOTADiskUsage = exports.downloadAndInstallOTA = exports.confirmOTASuccess = exports.logOTAInfo = void 0;
|
|
4
|
+
const NativeOTAUpdate_1 = require("./NativeOTAUpdate");
|
|
5
|
+
function warnIfNotDev() {
|
|
6
|
+
if (!__DEV__) {
|
|
7
|
+
console.warn('OTADevTools is intended only for manual OTA testing.');
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
async function logOTAInfo() {
|
|
11
|
+
warnIfNotDev();
|
|
12
|
+
const [directory, metadata, bundleInfo] = await Promise.all([
|
|
13
|
+
(0, NativeOTAUpdate_1.getOTADirectory)(),
|
|
14
|
+
(0, NativeOTAUpdate_1.getOTAMetadata)(),
|
|
15
|
+
(0, NativeOTAUpdate_1.getCurrentBundleInfo)(),
|
|
16
|
+
]);
|
|
17
|
+
console.log('OTA directory:', directory);
|
|
18
|
+
console.log('OTA metadata:', metadata);
|
|
19
|
+
console.log('OTA bundle info:', bundleInfo);
|
|
20
|
+
return { directory, metadata, bundleInfo };
|
|
21
|
+
}
|
|
22
|
+
exports.logOTAInfo = logOTAInfo;
|
|
23
|
+
async function confirmOTASuccess() {
|
|
24
|
+
warnIfNotDev();
|
|
25
|
+
const didMarkSuccess = await (0, NativeOTAUpdate_1.markOTASuccess)();
|
|
26
|
+
console.log('OTA success marked:', didMarkSuccess);
|
|
27
|
+
return didMarkSuccess;
|
|
28
|
+
}
|
|
29
|
+
exports.confirmOTASuccess = confirmOTASuccess;
|
|
30
|
+
async function downloadAndInstallOTA(update) {
|
|
31
|
+
warnIfNotDev();
|
|
32
|
+
const didInstall = await (0, NativeOTAUpdate_1.downloadAndInstallBundle)(update);
|
|
33
|
+
const downloadInfo = await (0, NativeOTAUpdate_1.getDownloadInfo)(update.version);
|
|
34
|
+
console.log('OTA downloaded install prepared:', didInstall, update.version);
|
|
35
|
+
console.log('OTA download info:', downloadInfo);
|
|
36
|
+
return didInstall;
|
|
37
|
+
}
|
|
38
|
+
exports.downloadAndInstallOTA = downloadAndInstallOTA;
|
|
39
|
+
async function logOTADiskUsage() {
|
|
40
|
+
warnIfNotDev();
|
|
41
|
+
const diskUsage = await (0, NativeOTAUpdate_1.getOTADiskUsage)();
|
|
42
|
+
console.log('OTA disk usage:', diskUsage);
|
|
43
|
+
return diskUsage;
|
|
44
|
+
}
|
|
45
|
+
exports.logOTADiskUsage = logOTADiskUsage;
|
|
46
|
+
async function cleanupOTADevStorage() {
|
|
47
|
+
warnIfNotDev();
|
|
48
|
+
const didCleanup = await (0, NativeOTAUpdate_1.cleanupOTAStorage)();
|
|
49
|
+
const diskUsage = await (0, NativeOTAUpdate_1.getOTADiskUsage)();
|
|
50
|
+
console.log('OTA cleanup completed:', didCleanup);
|
|
51
|
+
console.log('OTA disk usage after cleanup:', diskUsage);
|
|
52
|
+
return didCleanup;
|
|
53
|
+
}
|
|
54
|
+
exports.cleanupOTADevStorage = cleanupOTADevStorage;
|
package/lib/index.d.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { configure, getConfig, type OTAConfig } from './config';
|
|
2
|
+
import { checkAndInstallOTA, getCurrentOTAStatus, sync, type OTACheckUpdateResponse, type OTAUpdateResult } from './OTAClient';
|
|
3
|
+
import { cleanupOTAStorage, copyBundleFromDocuments, downloadAndInstallBundle, getCurrentBundleInfo, getDownloadInfo, getOTADirectory, getOTADiskUsage, getOTAMetadata, markOTASuccess, prepareManualInstall, resetOTAMetadata, type OTABundleInfo, type OTADiskUsage, type OTADownloadInfo, type OTAMetadata, type OTAStatus, type OTAUpdatePayload } from './NativeOTAUpdate';
|
|
4
|
+
export type { OTAConfig, OTACheckUpdateResponse, OTAUpdateResult, OTABundleInfo, OTADiskUsage, OTADownloadInfo, OTAMetadata, OTAStatus, OTAUpdatePayload, };
|
|
5
|
+
export { checkAndInstallOTA, cleanupOTAStorage, configure, copyBundleFromDocuments, downloadAndInstallBundle, getConfig, getCurrentBundleInfo, getCurrentOTAStatus, getDownloadInfo, getOTADirectory, getOTADiskUsage, getOTAMetadata, markOTASuccess, prepareManualInstall, resetOTAMetadata, sync, };
|
|
6
|
+
export declare const OTA: {
|
|
7
|
+
configure: typeof configure;
|
|
8
|
+
sync: typeof sync;
|
|
9
|
+
checkAndInstall: typeof checkAndInstallOTA;
|
|
10
|
+
markSuccess: typeof markOTASuccess;
|
|
11
|
+
getMetadata: typeof getOTAMetadata;
|
|
12
|
+
getCurrentBundleInfo: typeof getCurrentBundleInfo;
|
|
13
|
+
getCurrentStatus: typeof getCurrentOTAStatus;
|
|
14
|
+
cleanupStorage: typeof cleanupOTAStorage;
|
|
15
|
+
};
|
package/lib/index.js
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.OTA = exports.sync = exports.resetOTAMetadata = exports.prepareManualInstall = exports.markOTASuccess = exports.getOTAMetadata = exports.getOTADiskUsage = exports.getOTADirectory = exports.getDownloadInfo = exports.getCurrentOTAStatus = exports.getCurrentBundleInfo = exports.getConfig = exports.downloadAndInstallBundle = exports.copyBundleFromDocuments = exports.configure = exports.cleanupOTAStorage = exports.checkAndInstallOTA = void 0;
|
|
4
|
+
const config_1 = require("./config");
|
|
5
|
+
Object.defineProperty(exports, "configure", { enumerable: true, get: function () { return config_1.configure; } });
|
|
6
|
+
Object.defineProperty(exports, "getConfig", { enumerable: true, get: function () { return config_1.getConfig; } });
|
|
7
|
+
const OTAClient_1 = require("./OTAClient");
|
|
8
|
+
Object.defineProperty(exports, "checkAndInstallOTA", { enumerable: true, get: function () { return OTAClient_1.checkAndInstallOTA; } });
|
|
9
|
+
Object.defineProperty(exports, "getCurrentOTAStatus", { enumerable: true, get: function () { return OTAClient_1.getCurrentOTAStatus; } });
|
|
10
|
+
Object.defineProperty(exports, "sync", { enumerable: true, get: function () { return OTAClient_1.sync; } });
|
|
11
|
+
const NativeOTAUpdate_1 = require("./NativeOTAUpdate");
|
|
12
|
+
Object.defineProperty(exports, "cleanupOTAStorage", { enumerable: true, get: function () { return NativeOTAUpdate_1.cleanupOTAStorage; } });
|
|
13
|
+
Object.defineProperty(exports, "copyBundleFromDocuments", { enumerable: true, get: function () { return NativeOTAUpdate_1.copyBundleFromDocuments; } });
|
|
14
|
+
Object.defineProperty(exports, "downloadAndInstallBundle", { enumerable: true, get: function () { return NativeOTAUpdate_1.downloadAndInstallBundle; } });
|
|
15
|
+
Object.defineProperty(exports, "getCurrentBundleInfo", { enumerable: true, get: function () { return NativeOTAUpdate_1.getCurrentBundleInfo; } });
|
|
16
|
+
Object.defineProperty(exports, "getDownloadInfo", { enumerable: true, get: function () { return NativeOTAUpdate_1.getDownloadInfo; } });
|
|
17
|
+
Object.defineProperty(exports, "getOTADirectory", { enumerable: true, get: function () { return NativeOTAUpdate_1.getOTADirectory; } });
|
|
18
|
+
Object.defineProperty(exports, "getOTADiskUsage", { enumerable: true, get: function () { return NativeOTAUpdate_1.getOTADiskUsage; } });
|
|
19
|
+
Object.defineProperty(exports, "getOTAMetadata", { enumerable: true, get: function () { return NativeOTAUpdate_1.getOTAMetadata; } });
|
|
20
|
+
Object.defineProperty(exports, "markOTASuccess", { enumerable: true, get: function () { return NativeOTAUpdate_1.markOTASuccess; } });
|
|
21
|
+
Object.defineProperty(exports, "prepareManualInstall", { enumerable: true, get: function () { return NativeOTAUpdate_1.prepareManualInstall; } });
|
|
22
|
+
Object.defineProperty(exports, "resetOTAMetadata", { enumerable: true, get: function () { return NativeOTAUpdate_1.resetOTAMetadata; } });
|
|
23
|
+
exports.OTA = {
|
|
24
|
+
configure: config_1.configure,
|
|
25
|
+
sync: OTAClient_1.sync,
|
|
26
|
+
checkAndInstall: OTAClient_1.checkAndInstallOTA,
|
|
27
|
+
markSuccess: NativeOTAUpdate_1.markOTASuccess,
|
|
28
|
+
getMetadata: NativeOTAUpdate_1.getOTAMetadata,
|
|
29
|
+
getCurrentBundleInfo: NativeOTAUpdate_1.getCurrentBundleInfo,
|
|
30
|
+
getCurrentStatus: OTAClient_1.getCurrentOTAStatus,
|
|
31
|
+
cleanupStorage: NativeOTAUpdate_1.cleanupOTAStorage,
|
|
32
|
+
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { TurboModule } from 'react-native';
|
|
2
|
+
export interface Spec extends TurboModule {
|
|
3
|
+
getMetadata(): Promise<string>;
|
|
4
|
+
resetMetadata(): Promise<boolean>;
|
|
5
|
+
getOTADirectory(): Promise<string>;
|
|
6
|
+
prepareManualInstall(bundleVersion: string): Promise<boolean>;
|
|
7
|
+
markSuccess(): Promise<boolean>;
|
|
8
|
+
copyBundleFromDocuments(bundleVersion: string, fileName: string): Promise<boolean>;
|
|
9
|
+
getCurrentBundleInfo(): Promise<string>;
|
|
10
|
+
downloadAndInstallBundle(updateJson: string): Promise<boolean>;
|
|
11
|
+
getDownloadInfo(version: string): Promise<string>;
|
|
12
|
+
getOTADiskUsage(): Promise<string>;
|
|
13
|
+
cleanupOTAStorage(): Promise<boolean>;
|
|
14
|
+
}
|
|
15
|
+
declare const _default: Spec;
|
|
16
|
+
export default _default;
|
package/package.json
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@viettelpost/react-native-ota",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "ViettelPost React Native OTA runtime and deploy CLI",
|
|
5
|
+
"main": "lib/index.js",
|
|
6
|
+
"types": "lib/index.d.ts",
|
|
7
|
+
"react-native": "src/index.ts",
|
|
8
|
+
"publishConfig": {
|
|
9
|
+
"access": "public"
|
|
10
|
+
},
|
|
11
|
+
"files": [
|
|
12
|
+
"android",
|
|
13
|
+
"bin",
|
|
14
|
+
"docs",
|
|
15
|
+
"ios",
|
|
16
|
+
"lib",
|
|
17
|
+
"scripts",
|
|
18
|
+
"src",
|
|
19
|
+
"react-native-ota.podspec",
|
|
20
|
+
"README.md"
|
|
21
|
+
],
|
|
22
|
+
"bin": {
|
|
23
|
+
"ota": "bin/ota.js"
|
|
24
|
+
},
|
|
25
|
+
"scripts": {
|
|
26
|
+
"build": "node scripts/run-bin.js tsc -p tsconfig.build.json",
|
|
27
|
+
"prepack": "yarn build",
|
|
28
|
+
"test": "node scripts/run-bin.js jest",
|
|
29
|
+
"typecheck": "node scripts/run-bin.js tsc --noEmit",
|
|
30
|
+
"ota": "node bin/ota.js"
|
|
31
|
+
},
|
|
32
|
+
"dependencies": {
|
|
33
|
+
"archiver": "^8.0.0"
|
|
34
|
+
},
|
|
35
|
+
"peerDependencies": {
|
|
36
|
+
"react": ">=18.0.0",
|
|
37
|
+
"react-native": ">=0.77.0"
|
|
38
|
+
},
|
|
39
|
+
"devDependencies": {
|
|
40
|
+
"@types/jest": "^29.5.13",
|
|
41
|
+
"@types/react": "^18.2.6",
|
|
42
|
+
"@types/react-native": "^0.72.8",
|
|
43
|
+
"jest": "^29.6.3",
|
|
44
|
+
"typescript": "5.0.4"
|
|
45
|
+
},
|
|
46
|
+
"codegenConfig": {
|
|
47
|
+
"name": "ReactNativeOtaSpec",
|
|
48
|
+
"type": "modules",
|
|
49
|
+
"jsSrcsDir": "src/spec",
|
|
50
|
+
"android": {
|
|
51
|
+
"javaPackageName": "com.viettelpost.otakit"
|
|
52
|
+
},
|
|
53
|
+
"ios": {
|
|
54
|
+
"modulesProvider": {
|
|
55
|
+
"OTAUpdate": "OTAUpdateModule"
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
},
|
|
59
|
+
"engines": {
|
|
60
|
+
"node": ">=18"
|
|
61
|
+
},
|
|
62
|
+
"license": "UNLICENSED",
|
|
63
|
+
"directories": {
|
|
64
|
+
"doc": "docs",
|
|
65
|
+
"example": "example",
|
|
66
|
+
"lib": "lib"
|
|
67
|
+
},
|
|
68
|
+
"repository": {
|
|
69
|
+
"type": "git",
|
|
70
|
+
"url": "git+https://github.com/InfinityX-Solutions/react-native-ota.git"
|
|
71
|
+
},
|
|
72
|
+
"keywords": [
|
|
73
|
+
"react-native-ota",
|
|
74
|
+
"viettelpost",
|
|
75
|
+
"ota-update"
|
|
76
|
+
],
|
|
77
|
+
"author": "tranduc.250901@gmail.com",
|
|
78
|
+
"bugs": {
|
|
79
|
+
"url": "https://github.com/InfinityX-Solutions/react-native-ota/issues"
|
|
80
|
+
},
|
|
81
|
+
"homepage": "https://github.com/InfinityX-Solutions/react-native-ota#readme"
|
|
82
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
require "json"
|
|
2
|
+
|
|
3
|
+
package = JSON.parse(File.read(File.join(__dir__, "package.json")))
|
|
4
|
+
|
|
5
|
+
Pod::Spec.new do |s|
|
|
6
|
+
s.name = "react-native-ota"
|
|
7
|
+
s.module_name = "ReactNativeOta"
|
|
8
|
+
s.version = package["version"]
|
|
9
|
+
s.summary = package["description"]
|
|
10
|
+
s.homepage = "https://www.npmjs.com/package/@viettelpost/react-native-ota"
|
|
11
|
+
s.license = { :type => "UNLICENSED" }
|
|
12
|
+
s.author = { "ViettelPost" => "dev@viettelpost.vn" }
|
|
13
|
+
s.platforms = { :ios => "13.4" }
|
|
14
|
+
s.source = { :http => "https://registry.npmjs.org/@viettelpost/react-native-ota/-/react-native-ota-#{s.version}.tgz" }
|
|
15
|
+
s.source_files = "ios/**/*.{h,m,mm,swift}"
|
|
16
|
+
s.resources = "ios/ota_public_key.pem"
|
|
17
|
+
s.swift_version = "5.0"
|
|
18
|
+
s.requires_arc = true
|
|
19
|
+
|
|
20
|
+
install_modules_dependencies(s)
|
|
21
|
+
end
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
const { spawnSync } = require('child_process');
|
|
5
|
+
const path = require('path');
|
|
6
|
+
|
|
7
|
+
const BIN_MODULES = {
|
|
8
|
+
jest: 'jest/bin/jest.js',
|
|
9
|
+
tsc: 'typescript/bin/tsc',
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
function resolveBinExecutable(command, base) {
|
|
13
|
+
const binName = process.platform === 'win32' ? `${command}.cmd` : command;
|
|
14
|
+
const binPath = path.join(base, 'node_modules', '.bin', binName);
|
|
15
|
+
try {
|
|
16
|
+
require('fs').accessSync(binPath);
|
|
17
|
+
return binPath;
|
|
18
|
+
} catch {
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function resolveBin(command) {
|
|
24
|
+
const modulePath = BIN_MODULES[command];
|
|
25
|
+
if (!modulePath) {
|
|
26
|
+
throw new Error(`Unsupported package script command: ${command}`);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const searchPaths = [
|
|
30
|
+
process.cwd(),
|
|
31
|
+
path.resolve(process.cwd(), '..'),
|
|
32
|
+
];
|
|
33
|
+
|
|
34
|
+
for (const base of searchPaths) {
|
|
35
|
+
const binPath = resolveBinExecutable(command, base);
|
|
36
|
+
if (binPath) {
|
|
37
|
+
return { command: binPath, args: [] };
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
try {
|
|
41
|
+
return {
|
|
42
|
+
command: process.execPath,
|
|
43
|
+
args: [require.resolve(modulePath, { paths: [base] })],
|
|
44
|
+
};
|
|
45
|
+
} catch {
|
|
46
|
+
// Continue searching. This package is built both standalone and inside the app repo.
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
throw new Error(`Unable to resolve ${modulePath}. Run yarn install before executing this script.`);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const [, , command, ...args] = process.argv;
|
|
54
|
+
|
|
55
|
+
if (!command) {
|
|
56
|
+
console.error('Usage: node scripts/run-bin.js <jest|tsc> [...args]');
|
|
57
|
+
process.exit(1);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const resolved = resolveBin(command);
|
|
61
|
+
const result = spawnSync(
|
|
62
|
+
resolved.command,
|
|
63
|
+
[...resolved.args, ...args],
|
|
64
|
+
{ cwd: process.cwd(), stdio: 'inherit' },
|
|
65
|
+
);
|
|
66
|
+
|
|
67
|
+
process.exit(result.status === null ? 1 : result.status);
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import { Platform } from 'react-native';
|
|
2
|
+
import NativeOTAUpdate from './spec/NativeOTAUpdate';
|
|
3
|
+
|
|
4
|
+
export type OTAStatus =
|
|
5
|
+
| 'active'
|
|
6
|
+
| 'downloading'
|
|
7
|
+
| 'download_failed'
|
|
8
|
+
| 'downloaded'
|
|
9
|
+
| 'verifying'
|
|
10
|
+
| 'verified'
|
|
11
|
+
| 'verify_failed'
|
|
12
|
+
| 'pending'
|
|
13
|
+
| 'failed'
|
|
14
|
+
| 'rolled_back';
|
|
15
|
+
|
|
16
|
+
export type OTAMetadata = {
|
|
17
|
+
schemaVersion: number;
|
|
18
|
+
embeddedBundleVersion: string;
|
|
19
|
+
activeBundleVersion: string;
|
|
20
|
+
previousBundleVersion: string | null;
|
|
21
|
+
pendingBundleVersion: string | null;
|
|
22
|
+
failedBundleVersion: string | null;
|
|
23
|
+
runningBundleVersion: string;
|
|
24
|
+
status: OTAStatus;
|
|
25
|
+
launchCountForPending: number;
|
|
26
|
+
lastSuccessfulLaunchAt: string | null;
|
|
27
|
+
lastFailureReason: string | null;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
export type OTABundleInfo = {
|
|
31
|
+
runningBundleVersion: string;
|
|
32
|
+
activeBundleVersion: string;
|
|
33
|
+
pendingBundleVersion: string | null;
|
|
34
|
+
status: OTAStatus;
|
|
35
|
+
bundlePath: string | null;
|
|
36
|
+
assetsDirectoryPath?: string | null;
|
|
37
|
+
assetsDirectoryExists?: boolean;
|
|
38
|
+
isEmbedded: boolean;
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
export type OTAUpdatePayload = {
|
|
42
|
+
version: string;
|
|
43
|
+
bundleUrl: string;
|
|
44
|
+
platform: 'android' | 'ios';
|
|
45
|
+
fileName: string;
|
|
46
|
+
sha256: string;
|
|
47
|
+
signature: string;
|
|
48
|
+
assetsUrl?: string;
|
|
49
|
+
assetsSha256?: string;
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
export type OTADownloadInfo = {
|
|
53
|
+
version: string;
|
|
54
|
+
tempBundlePath: string;
|
|
55
|
+
finalBundlePath: string;
|
|
56
|
+
tempAssetsZipPath: string;
|
|
57
|
+
finalAssetsDirectoryPath: string;
|
|
58
|
+
tempExists: boolean;
|
|
59
|
+
finalExists: boolean;
|
|
60
|
+
tempAssetsZipExists: boolean;
|
|
61
|
+
finalAssetFileCount: number;
|
|
62
|
+
tempSize: number;
|
|
63
|
+
finalSize: number;
|
|
64
|
+
tempAssetsZipSize: number;
|
|
65
|
+
finalAssetsSize: number;
|
|
66
|
+
tempSha256: string | null;
|
|
67
|
+
finalSha256: string | null;
|
|
68
|
+
tempAssetsSha256: string | null;
|
|
69
|
+
signatureRequired: boolean;
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
export type OTADiskUsage = {
|
|
73
|
+
otaRootPath: string;
|
|
74
|
+
totalBytes: number;
|
|
75
|
+
tmpBytes: number;
|
|
76
|
+
bundlesBytes: number;
|
|
77
|
+
bundles: Array<{
|
|
78
|
+
version: string;
|
|
79
|
+
bytes: number;
|
|
80
|
+
isActive: boolean;
|
|
81
|
+
isPending: boolean;
|
|
82
|
+
isFailed: boolean;
|
|
83
|
+
}>;
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
export async function getOTAMetadata(): Promise<OTAMetadata> {
|
|
87
|
+
const metadata = await NativeOTAUpdate.getMetadata();
|
|
88
|
+
return JSON.parse(metadata) as OTAMetadata;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export async function resetOTAMetadata(): Promise<boolean> {
|
|
92
|
+
return NativeOTAUpdate.resetMetadata();
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export async function getOTADirectory(): Promise<string> {
|
|
96
|
+
return NativeOTAUpdate.getOTADirectory();
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export async function prepareManualInstall(
|
|
100
|
+
bundleVersion: string,
|
|
101
|
+
): Promise<boolean> {
|
|
102
|
+
return NativeOTAUpdate.prepareManualInstall(bundleVersion);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export async function markOTASuccess(): Promise<boolean> {
|
|
106
|
+
return NativeOTAUpdate.markSuccess();
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export async function copyBundleFromDocuments(
|
|
110
|
+
bundleVersion: string,
|
|
111
|
+
fileName: string,
|
|
112
|
+
): Promise<boolean> {
|
|
113
|
+
if (Platform.OS !== 'ios') {
|
|
114
|
+
return false;
|
|
115
|
+
}
|
|
116
|
+
return NativeOTAUpdate.copyBundleFromDocuments(bundleVersion, fileName);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export async function getCurrentBundleInfo(): Promise<OTABundleInfo> {
|
|
120
|
+
const bundleInfo = await NativeOTAUpdate.getCurrentBundleInfo();
|
|
121
|
+
return JSON.parse(bundleInfo) as OTABundleInfo;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export async function downloadAndInstallBundle(
|
|
125
|
+
update: OTAUpdatePayload,
|
|
126
|
+
): Promise<boolean> {
|
|
127
|
+
return NativeOTAUpdate.downloadAndInstallBundle(JSON.stringify(update));
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export async function getDownloadInfo(
|
|
131
|
+
version: string,
|
|
132
|
+
): Promise<OTADownloadInfo> {
|
|
133
|
+
const downloadInfo = await NativeOTAUpdate.getDownloadInfo(version);
|
|
134
|
+
return JSON.parse(downloadInfo) as OTADownloadInfo;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
export async function getOTADiskUsage(): Promise<OTADiskUsage> {
|
|
138
|
+
const diskUsage = await NativeOTAUpdate.getOTADiskUsage();
|
|
139
|
+
return JSON.parse(diskUsage) as OTADiskUsage;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
export async function cleanupOTAStorage(): Promise<boolean> {
|
|
143
|
+
return NativeOTAUpdate.cleanupOTAStorage();
|
|
144
|
+
}
|