@vanikya/ota-react-native 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 +223 -0
- package/android/build.gradle +58 -0
- package/android/src/main/AndroidManifest.xml +4 -0
- package/android/src/main/java/com/otaupdate/OTAUpdateModule.kt +185 -0
- package/android/src/main/java/com/otaupdate/OTAUpdatePackage.kt +16 -0
- package/ios/OTAUpdate.m +61 -0
- package/ios/OTAUpdate.swift +194 -0
- package/lib/commonjs/OTAProvider.js +113 -0
- package/lib/commonjs/OTAProvider.js.map +1 -0
- package/lib/commonjs/hooks/useOTAUpdate.js +272 -0
- package/lib/commonjs/hooks/useOTAUpdate.js.map +1 -0
- package/lib/commonjs/index.js +98 -0
- package/lib/commonjs/index.js.map +1 -0
- package/lib/commonjs/utils/api.js +60 -0
- package/lib/commonjs/utils/api.js.map +1 -0
- package/lib/commonjs/utils/storage.js +209 -0
- package/lib/commonjs/utils/storage.js.map +1 -0
- package/lib/commonjs/utils/verification.js +145 -0
- package/lib/commonjs/utils/verification.js.map +1 -0
- package/lib/module/OTAProvider.js +104 -0
- package/lib/module/OTAProvider.js.map +1 -0
- package/lib/module/hooks/useOTAUpdate.js +266 -0
- package/lib/module/hooks/useOTAUpdate.js.map +1 -0
- package/lib/module/index.js +11 -0
- package/lib/module/index.js.map +1 -0
- package/lib/module/utils/api.js +52 -0
- package/lib/module/utils/api.js.map +1 -0
- package/lib/module/utils/storage.js +202 -0
- package/lib/module/utils/storage.js.map +1 -0
- package/lib/module/utils/verification.js +137 -0
- package/lib/module/utils/verification.js.map +1 -0
- package/lib/typescript/OTAProvider.d.ts +28 -0
- package/lib/typescript/OTAProvider.d.ts.map +1 -0
- package/lib/typescript/hooks/useOTAUpdate.d.ts +35 -0
- package/lib/typescript/hooks/useOTAUpdate.d.ts.map +1 -0
- package/lib/typescript/index.d.ts +12 -0
- package/lib/typescript/index.d.ts.map +1 -0
- package/lib/typescript/utils/api.d.ts +47 -0
- package/lib/typescript/utils/api.d.ts.map +1 -0
- package/lib/typescript/utils/storage.d.ts +32 -0
- package/lib/typescript/utils/storage.d.ts.map +1 -0
- package/lib/typescript/utils/verification.d.ts +11 -0
- package/lib/typescript/utils/verification.d.ts.map +1 -0
- package/ota-update.podspec +21 -0
- package/package.json +83 -0
- package/src/OTAProvider.tsx +160 -0
- package/src/hooks/useOTAUpdate.ts +344 -0
- package/src/index.ts +36 -0
- package/src/utils/api.ts +99 -0
- package/src/utils/storage.ts +249 -0
- package/src/utils/verification.ts +167 -0
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import { NativeModules } from 'react-native';
|
|
2
|
+
|
|
3
|
+
// Try to use Expo Crypto if available
|
|
4
|
+
let ExpoCrypto = null;
|
|
5
|
+
try {
|
|
6
|
+
ExpoCrypto = require('expo-crypto');
|
|
7
|
+
} catch {
|
|
8
|
+
// Expo not available
|
|
9
|
+
}
|
|
10
|
+
const OTAUpdateNative = NativeModules.OTAUpdate;
|
|
11
|
+
|
|
12
|
+
// Convert ArrayBuffer to hex string
|
|
13
|
+
function bufferToHex(buffer) {
|
|
14
|
+
const bytes = new Uint8Array(buffer);
|
|
15
|
+
return Array.from(bytes).map(b => b.toString(16).padStart(2, '0')).join('');
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// Convert hex string to Uint8Array
|
|
19
|
+
function hexToBytes(hex) {
|
|
20
|
+
const bytes = new Uint8Array(hex.length / 2);
|
|
21
|
+
for (let i = 0; i < hex.length; i += 2) {
|
|
22
|
+
bytes[i / 2] = parseInt(hex.substr(i, 2), 16);
|
|
23
|
+
}
|
|
24
|
+
return bytes;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Calculate SHA-256 hash of data
|
|
28
|
+
export async function calculateHash(data) {
|
|
29
|
+
if (ExpoCrypto) {
|
|
30
|
+
// Use Expo Crypto
|
|
31
|
+
const hash = await ExpoCrypto.digestStringAsync(ExpoCrypto.CryptoDigestAlgorithm.SHA256, bufferToHex(data), {
|
|
32
|
+
encoding: ExpoCrypto.CryptoEncoding.HEX
|
|
33
|
+
});
|
|
34
|
+
return 'sha256:' + hash;
|
|
35
|
+
}
|
|
36
|
+
if (OTAUpdateNative?.calculateSHA256) {
|
|
37
|
+
// Use native module
|
|
38
|
+
const bytes = new Uint8Array(data);
|
|
39
|
+
let binary = '';
|
|
40
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
41
|
+
binary += String.fromCharCode(bytes[i]);
|
|
42
|
+
}
|
|
43
|
+
const base64 = btoa(binary);
|
|
44
|
+
const hash = await OTAUpdateNative.calculateSHA256(base64);
|
|
45
|
+
return 'sha256:' + hash;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Fallback: Use SubtleCrypto (not available in all RN environments)
|
|
49
|
+
if (typeof crypto !== 'undefined' && crypto.subtle) {
|
|
50
|
+
const hashBuffer = await crypto.subtle.digest('SHA-256', data);
|
|
51
|
+
return 'sha256:' + bufferToHex(hashBuffer);
|
|
52
|
+
}
|
|
53
|
+
throw new Error('No crypto implementation available');
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Verify bundle hash
|
|
57
|
+
export async function verifyBundleHash(data, expectedHash) {
|
|
58
|
+
const actualHash = await calculateHash(data);
|
|
59
|
+
return actualHash === expectedHash;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Verify Ed25519 signature
|
|
63
|
+
export async function verifySignature(data, signatureHex, publicKeyHex) {
|
|
64
|
+
// Ed25519 verification is complex in JS
|
|
65
|
+
// We rely on native modules or skip if not available
|
|
66
|
+
|
|
67
|
+
if (OTAUpdateNative?.verifySignature) {
|
|
68
|
+
const bytes = new Uint8Array(data);
|
|
69
|
+
let binary = '';
|
|
70
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
71
|
+
binary += String.fromCharCode(bytes[i]);
|
|
72
|
+
}
|
|
73
|
+
const base64 = btoa(binary);
|
|
74
|
+
return OTAUpdateNative.verifySignature(base64, signatureHex, publicKeyHex);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// If no native module, we can't verify signature
|
|
78
|
+
// In production, you might want to require this
|
|
79
|
+
if (__DEV__) {
|
|
80
|
+
console.warn('[OTAUpdate] Signature verification skipped: native module not available');
|
|
81
|
+
}
|
|
82
|
+
return true; // Skip verification if not available
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Full bundle verification
|
|
86
|
+
|
|
87
|
+
export async function verifyBundle(data, expectedHash, signature, publicKey) {
|
|
88
|
+
// Verify hash
|
|
89
|
+
let hashValid = false;
|
|
90
|
+
try {
|
|
91
|
+
hashValid = await verifyBundleHash(data, expectedHash);
|
|
92
|
+
} catch (error) {
|
|
93
|
+
return {
|
|
94
|
+
valid: false,
|
|
95
|
+
hashValid: false,
|
|
96
|
+
signatureValid: false,
|
|
97
|
+
error: `Hash verification failed: ${error}`
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
if (!hashValid) {
|
|
101
|
+
return {
|
|
102
|
+
valid: false,
|
|
103
|
+
hashValid: false,
|
|
104
|
+
signatureValid: false,
|
|
105
|
+
error: 'Bundle hash mismatch'
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Verify signature if both signature and public key are provided
|
|
110
|
+
let signatureValid = true;
|
|
111
|
+
if (signature && publicKey) {
|
|
112
|
+
try {
|
|
113
|
+
signatureValid = await verifySignature(data, signature, publicKey);
|
|
114
|
+
} catch (error) {
|
|
115
|
+
return {
|
|
116
|
+
valid: false,
|
|
117
|
+
hashValid: true,
|
|
118
|
+
signatureValid: false,
|
|
119
|
+
error: `Signature verification failed: ${error}`
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
if (!signatureValid) {
|
|
123
|
+
return {
|
|
124
|
+
valid: false,
|
|
125
|
+
hashValid: true,
|
|
126
|
+
signatureValid: false,
|
|
127
|
+
error: 'Invalid bundle signature'
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
return {
|
|
132
|
+
valid: true,
|
|
133
|
+
hashValid: true,
|
|
134
|
+
signatureValid
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
//# sourceMappingURL=verification.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"names":["NativeModules","ExpoCrypto","require","OTAUpdateNative","OTAUpdate","bufferToHex","buffer","bytes","Uint8Array","Array","from","map","b","toString","padStart","join","hexToBytes","hex","length","i","parseInt","substr","calculateHash","data","hash","digestStringAsync","CryptoDigestAlgorithm","SHA256","encoding","CryptoEncoding","HEX","calculateSHA256","binary","String","fromCharCode","base64","btoa","crypto","subtle","hashBuffer","digest","Error","verifyBundleHash","expectedHash","actualHash","verifySignature","signatureHex","publicKeyHex","__DEV__","console","warn","verifyBundle","signature","publicKey","hashValid","error","valid","signatureValid"],"sourceRoot":"../../../src","sources":["utils/verification.ts"],"mappings":"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","ignoreList":[]}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import React, { ReactNode } from 'react';
|
|
2
|
+
import { OTAUpdateConfig, UseOTAUpdateResult, UpdateInfo } from './hooks/useOTAUpdate';
|
|
3
|
+
interface OTAContextValue extends UseOTAUpdateResult {
|
|
4
|
+
config: OTAUpdateConfig;
|
|
5
|
+
}
|
|
6
|
+
export interface OTAProviderProps {
|
|
7
|
+
children: ReactNode;
|
|
8
|
+
config: OTAUpdateConfig;
|
|
9
|
+
onUpdateAvailable?: (info: UpdateInfo) => void;
|
|
10
|
+
onUpdateDownloaded?: () => void;
|
|
11
|
+
onError?: (error: Error) => void;
|
|
12
|
+
showMandatoryUpdateAlert?: boolean;
|
|
13
|
+
mandatoryUpdateAlertTitle?: string;
|
|
14
|
+
mandatoryUpdateAlertMessage?: string;
|
|
15
|
+
}
|
|
16
|
+
export declare function OTAProvider({ children, config, onUpdateAvailable, onUpdateDownloaded, onError, showMandatoryUpdateAlert, mandatoryUpdateAlertTitle, mandatoryUpdateAlertMessage, }: OTAProviderProps): React.JSX.Element;
|
|
17
|
+
export declare function useOTA(): OTAContextValue;
|
|
18
|
+
export declare function withOTA<P extends object>(Component: React.ComponentType<P & {
|
|
19
|
+
ota: OTAContextValue;
|
|
20
|
+
}>): React.FC<P>;
|
|
21
|
+
export interface UpdateBannerProps {
|
|
22
|
+
renderAvailable?: (info: UpdateInfo, download: () => void) => ReactNode;
|
|
23
|
+
renderDownloading?: (progress: number) => ReactNode;
|
|
24
|
+
renderReady?: (apply: () => void) => ReactNode;
|
|
25
|
+
}
|
|
26
|
+
export declare function UpdateBanner({ renderAvailable, renderDownloading, renderReady, }: UpdateBannerProps): React.JSX.Element | null;
|
|
27
|
+
export {};
|
|
28
|
+
//# sourceMappingURL=OTAProvider.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"OTAProvider.d.ts","sourceRoot":"","sources":["../../src/OTAProvider.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,EAMZ,SAAS,EACV,MAAM,OAAO,CAAC;AAEf,OAAO,EAEL,eAAe,EACf,kBAAkB,EAElB,UAAU,EACX,MAAM,sBAAsB,CAAC;AAG9B,UAAU,eAAgB,SAAQ,kBAAkB;IAClD,MAAM,EAAE,eAAe,CAAC;CACzB;AAKD,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,EAAE,SAAS,CAAC;IACpB,MAAM,EAAE,eAAe,CAAC;IACxB,iBAAiB,CAAC,EAAE,CAAC,IAAI,EAAE,UAAU,KAAK,IAAI,CAAC;IAC/C,kBAAkB,CAAC,EAAE,MAAM,IAAI,CAAC;IAChC,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;IACjC,wBAAwB,CAAC,EAAE,OAAO,CAAC;IACnC,yBAAyB,CAAC,EAAE,MAAM,CAAC;IACnC,2BAA2B,CAAC,EAAE,MAAM,CAAC;CACtC;AAED,wBAAgB,WAAW,CAAC,EAC1B,QAAQ,EACR,MAAM,EACN,iBAAiB,EACjB,kBAAkB,EAClB,OAAO,EACP,wBAA+B,EAC/B,yBAA6C,EAC7C,2BAA6F,GAC9F,EAAE,gBAAgB,qBAgElB;AAGD,wBAAgB,MAAM,IAAI,eAAe,CAQxC;AAGD,wBAAgB,OAAO,CAAC,CAAC,SAAS,MAAM,EACtC,SAAS,EAAE,KAAK,CAAC,aAAa,CAAC,CAAC,GAAG;IAAE,GAAG,EAAE,eAAe,CAAA;CAAE,CAAC,GAC3D,KAAK,CAAC,EAAE,CAAC,CAAC,CAAC,CAKb;AAGD,MAAM,WAAW,iBAAiB;IAChC,eAAe,CAAC,EAAE,CAAC,IAAI,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,IAAI,KAAK,SAAS,CAAC;IACxE,iBAAiB,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,SAAS,CAAC;IACpD,WAAW,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,IAAI,KAAK,SAAS,CAAC;CAChD;AAED,wBAAgB,YAAY,CAAC,EAC3B,eAAe,EACf,iBAAiB,EACjB,WAAW,GACZ,EAAE,iBAAiB,4BAgBnB"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
export interface OTAUpdateConfig {
|
|
2
|
+
serverUrl: string;
|
|
3
|
+
appSlug: string;
|
|
4
|
+
channel?: string;
|
|
5
|
+
appVersion: string;
|
|
6
|
+
publicKey?: string;
|
|
7
|
+
checkOnMount?: boolean;
|
|
8
|
+
checkOnForeground?: boolean;
|
|
9
|
+
}
|
|
10
|
+
export interface UpdateInfo {
|
|
11
|
+
version: string;
|
|
12
|
+
releaseId: string;
|
|
13
|
+
bundleSize: number;
|
|
14
|
+
isMandatory: boolean;
|
|
15
|
+
releaseNotes: string | null;
|
|
16
|
+
}
|
|
17
|
+
export interface DownloadProgress {
|
|
18
|
+
downloadedBytes: number;
|
|
19
|
+
totalBytes: number;
|
|
20
|
+
percentage: number;
|
|
21
|
+
}
|
|
22
|
+
export type UpdateStatus = 'idle' | 'checking' | 'available' | 'downloading' | 'verifying' | 'ready' | 'applying' | 'error' | 'up-to-date';
|
|
23
|
+
export interface UseOTAUpdateResult {
|
|
24
|
+
status: UpdateStatus;
|
|
25
|
+
updateInfo: UpdateInfo | null;
|
|
26
|
+
downloadProgress: DownloadProgress | null;
|
|
27
|
+
error: Error | null;
|
|
28
|
+
currentVersion: string | null;
|
|
29
|
+
checkForUpdate: () => Promise<UpdateInfo | null>;
|
|
30
|
+
downloadUpdate: () => Promise<void>;
|
|
31
|
+
applyUpdate: (restartApp?: boolean) => Promise<void>;
|
|
32
|
+
clearPendingUpdate: () => Promise<void>;
|
|
33
|
+
}
|
|
34
|
+
export declare function useOTAUpdate(config: OTAUpdateConfig): UseOTAUpdateResult;
|
|
35
|
+
//# sourceMappingURL=useOTAUpdate.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useOTAUpdate.d.ts","sourceRoot":"","sources":["../../../src/hooks/useOTAUpdate.ts"],"names":[],"mappings":"AAQA,MAAM,WAAW,eAAe;IAC9B,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,iBAAiB,CAAC,EAAE,OAAO,CAAC;CAC7B;AAED,MAAM,WAAW,UAAU;IACzB,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,OAAO,CAAC;IACrB,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;CAC7B;AAED,MAAM,WAAW,gBAAgB;IAC/B,eAAe,EAAE,MAAM,CAAC;IACxB,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,MAAM,YAAY,GACpB,MAAM,GACN,UAAU,GACV,WAAW,GACX,aAAa,GACb,WAAW,GACX,OAAO,GACP,UAAU,GACV,OAAO,GACP,YAAY,CAAC;AAEjB,MAAM,WAAW,kBAAkB;IACjC,MAAM,EAAE,YAAY,CAAC;IACrB,UAAU,EAAE,UAAU,GAAG,IAAI,CAAC;IAC9B,gBAAgB,EAAE,gBAAgB,GAAG,IAAI,CAAC;IAC1C,KAAK,EAAE,KAAK,GAAG,IAAI,CAAC;IACpB,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,cAAc,EAAE,MAAM,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC,CAAC;IACjD,cAAc,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IACpC,WAAW,EAAE,CAAC,UAAU,CAAC,EAAE,OAAO,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACrD,kBAAkB,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CACzC;AAWD,wBAAgB,YAAY,CAAC,MAAM,EAAE,eAAe,GAAG,kBAAkB,CAuRxE"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export { OTAProvider, useOTA, withOTA, UpdateBanner } from './OTAProvider';
|
|
2
|
+
export type { OTAProviderProps, UpdateBannerProps } from './OTAProvider';
|
|
3
|
+
export { useOTAUpdate } from './hooks/useOTAUpdate';
|
|
4
|
+
export type { OTAUpdateConfig, UpdateInfo, DownloadProgress, UpdateStatus, UseOTAUpdateResult, } from './hooks/useOTAUpdate';
|
|
5
|
+
export { OTAApiClient, getDeviceInfo } from './utils/api';
|
|
6
|
+
export type { CheckUpdateRequest, CheckUpdateResponse, ReleaseInfo, ReportEventRequest, } from './utils/api';
|
|
7
|
+
export { UpdateStorage, getStorageAdapter } from './utils/storage';
|
|
8
|
+
export type { StoredUpdate, StorageAdapter } from './utils/storage';
|
|
9
|
+
export { calculateHash, verifyBundleHash, verifySignature, verifyBundle, } from './utils/verification';
|
|
10
|
+
export type { VerificationResult } from './utils/verification';
|
|
11
|
+
export declare const VERSION = "0.1.0";
|
|
12
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,WAAW,EAAE,MAAM,EAAE,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAC3E,YAAY,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAGzE,OAAO,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AACpD,YAAY,EACV,eAAe,EACf,UAAU,EACV,gBAAgB,EAChB,YAAY,EACZ,kBAAkB,GACnB,MAAM,sBAAsB,CAAC;AAG9B,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC1D,YAAY,EACV,kBAAkB,EAClB,mBAAmB,EACnB,WAAW,EACX,kBAAkB,GACnB,MAAM,aAAa,CAAC;AAErB,OAAO,EAAE,aAAa,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AACnE,YAAY,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAEpE,OAAO,EACL,aAAa,EACb,gBAAgB,EAChB,eAAe,EACf,YAAY,GACb,MAAM,sBAAsB,CAAC;AAC9B,YAAY,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAG/D,eAAO,MAAM,OAAO,UAAU,CAAC"}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
export interface CheckUpdateRequest {
|
|
2
|
+
appSlug: string;
|
|
3
|
+
channel: string;
|
|
4
|
+
platform: 'ios' | 'android';
|
|
5
|
+
currentVersion: string | null;
|
|
6
|
+
appVersion: string;
|
|
7
|
+
deviceId: string;
|
|
8
|
+
}
|
|
9
|
+
export interface ReleaseInfo {
|
|
10
|
+
id: string;
|
|
11
|
+
version: string;
|
|
12
|
+
bundleUrl: string;
|
|
13
|
+
bundleHash: string;
|
|
14
|
+
bundleSignature: string | null;
|
|
15
|
+
bundleSize: number;
|
|
16
|
+
isMandatory: boolean;
|
|
17
|
+
releaseNotes: string | null;
|
|
18
|
+
}
|
|
19
|
+
export interface CheckUpdateResponse {
|
|
20
|
+
updateAvailable: boolean;
|
|
21
|
+
release?: ReleaseInfo;
|
|
22
|
+
}
|
|
23
|
+
export interface ReportEventRequest {
|
|
24
|
+
appSlug: string;
|
|
25
|
+
releaseId: string | null;
|
|
26
|
+
deviceId: string;
|
|
27
|
+
eventType: 'download' | 'apply' | 'success' | 'failure' | 'rollback';
|
|
28
|
+
errorMessage?: string;
|
|
29
|
+
appVersion?: string;
|
|
30
|
+
deviceInfo?: {
|
|
31
|
+
os: string;
|
|
32
|
+
osVersion: string;
|
|
33
|
+
[key: string]: unknown;
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
export declare class OTAApiClient {
|
|
37
|
+
private serverUrl;
|
|
38
|
+
constructor(serverUrl: string);
|
|
39
|
+
checkUpdate(request: CheckUpdateRequest): Promise<CheckUpdateResponse>;
|
|
40
|
+
reportEvent(request: ReportEventRequest): Promise<void>;
|
|
41
|
+
downloadBundle(bundleUrl: string): Promise<ArrayBuffer>;
|
|
42
|
+
}
|
|
43
|
+
export declare function getDeviceInfo(): {
|
|
44
|
+
os: string;
|
|
45
|
+
osVersion: string;
|
|
46
|
+
};
|
|
47
|
+
//# sourceMappingURL=api.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"api.d.ts","sourceRoot":"","sources":["../../../src/utils/api.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,kBAAkB;IACjC,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,KAAK,GAAG,SAAS,CAAC;IAC5B,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,WAAW;IAC1B,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,OAAO,CAAC;IACrB,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;CAC7B;AAED,MAAM,WAAW,mBAAmB;IAClC,eAAe,EAAE,OAAO,CAAC;IACzB,OAAO,CAAC,EAAE,WAAW,CAAC;CACvB;AAED,MAAM,WAAW,kBAAkB;IACjC,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,UAAU,GAAG,OAAO,GAAG,SAAS,GAAG,SAAS,GAAG,UAAU,CAAC;IACrE,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE;QACX,EAAE,EAAE,MAAM,CAAC;QACX,SAAS,EAAE,MAAM,CAAC;QAClB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;KACxB,CAAC;CACH;AAED,qBAAa,YAAY;IACvB,OAAO,CAAC,SAAS,CAAS;gBAEd,SAAS,EAAE,MAAM;IAIvB,WAAW,CAAC,OAAO,EAAE,kBAAkB,GAAG,OAAO,CAAC,mBAAmB,CAAC;IAiBtE,WAAW,CAAC,OAAO,EAAE,kBAAkB,GAAG,OAAO,CAAC,IAAI,CAAC;IAiBvD,cAAc,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC;CAS9D;AAED,wBAAgB,aAAa,IAAI;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,CAKjE"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
export interface StoredUpdate {
|
|
2
|
+
releaseId: string;
|
|
3
|
+
version: string;
|
|
4
|
+
bundlePath: string;
|
|
5
|
+
bundleHash: string;
|
|
6
|
+
downloadedAt: number;
|
|
7
|
+
}
|
|
8
|
+
export interface StorageAdapter {
|
|
9
|
+
getDocumentDirectory(): string;
|
|
10
|
+
writeFile(path: string, data: string | ArrayBuffer): Promise<void>;
|
|
11
|
+
readFile(path: string): Promise<string>;
|
|
12
|
+
readFileAsBuffer(path: string): Promise<ArrayBuffer>;
|
|
13
|
+
deleteFile(path: string): Promise<void>;
|
|
14
|
+
exists(path: string): Promise<boolean>;
|
|
15
|
+
makeDirectory(path: string): Promise<void>;
|
|
16
|
+
}
|
|
17
|
+
export declare function getStorageAdapter(): StorageAdapter;
|
|
18
|
+
export declare class UpdateStorage {
|
|
19
|
+
private storage;
|
|
20
|
+
private baseDir;
|
|
21
|
+
constructor();
|
|
22
|
+
private ensureDirectory;
|
|
23
|
+
saveBundle(releaseId: string, data: ArrayBuffer): Promise<string>;
|
|
24
|
+
getBundlePath(releaseId: string): Promise<string | null>;
|
|
25
|
+
readBundle(releaseId: string): Promise<ArrayBuffer | null>;
|
|
26
|
+
deleteBundle(releaseId: string): Promise<void>;
|
|
27
|
+
saveMetadata(update: StoredUpdate): Promise<void>;
|
|
28
|
+
getMetadata(): Promise<StoredUpdate | null>;
|
|
29
|
+
clearMetadata(): Promise<void>;
|
|
30
|
+
cleanOldBundles(keepReleaseId: string): Promise<void>;
|
|
31
|
+
}
|
|
32
|
+
//# sourceMappingURL=storage.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"storage.d.ts","sourceRoot":"","sources":["../../../src/utils/storage.ts"],"names":[],"mappings":"AAGA,MAAM,WAAW,YAAY;IAC3B,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,cAAc;IAC7B,oBAAoB,IAAI,MAAM,CAAC;IAC/B,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACnE,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IACxC,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC,CAAC;IACrD,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACxC,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IACvC,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC5C;AAwID,wBAAgB,iBAAiB,IAAI,cAAc,CAYlD;AAGD,qBAAa,aAAa;IACxB,OAAO,CAAC,OAAO,CAAiB;IAChC,OAAO,CAAC,OAAO,CAAS;;YAOV,eAAe;IAOvB,UAAU,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,WAAW,GAAG,OAAO,CAAC,MAAM,CAAC;IASjE,aAAa,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;IAMxD,UAAU,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC;IAO1D,YAAY,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAO9C,YAAY,CAAC,MAAM,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC;IAOjD,WAAW,IAAI,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC;IAe3C,aAAa,IAAI,OAAO,CAAC,IAAI,CAAC;IAO9B,eAAe,CAAC,aAAa,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;CAI5D"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export declare function calculateHash(data: ArrayBuffer): Promise<string>;
|
|
2
|
+
export declare function verifyBundleHash(data: ArrayBuffer, expectedHash: string): Promise<boolean>;
|
|
3
|
+
export declare function verifySignature(data: ArrayBuffer, signatureHex: string, publicKeyHex: string): Promise<boolean>;
|
|
4
|
+
export interface VerificationResult {
|
|
5
|
+
valid: boolean;
|
|
6
|
+
hashValid: boolean;
|
|
7
|
+
signatureValid: boolean;
|
|
8
|
+
error?: string;
|
|
9
|
+
}
|
|
10
|
+
export declare function verifyBundle(data: ArrayBuffer, expectedHash: string, signature: string | null, publicKey: string | null): Promise<VerificationResult>;
|
|
11
|
+
//# sourceMappingURL=verification.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"verification.d.ts","sourceRoot":"","sources":["../../../src/utils/verification.ts"],"names":[],"mappings":"AA8BA,wBAAsB,aAAa,CAAC,IAAI,EAAE,WAAW,GAAG,OAAO,CAAC,MAAM,CAAC,CA8BtE;AAGD,wBAAsB,gBAAgB,CACpC,IAAI,EAAE,WAAW,EACjB,YAAY,EAAE,MAAM,GACnB,OAAO,CAAC,OAAO,CAAC,CAGlB;AAGD,wBAAsB,eAAe,CACnC,IAAI,EAAE,WAAW,EACjB,YAAY,EAAE,MAAM,EACpB,YAAY,EAAE,MAAM,GACnB,OAAO,CAAC,OAAO,CAAC,CAuBlB;AAGD,MAAM,WAAW,kBAAkB;IACjC,KAAK,EAAE,OAAO,CAAC;IACf,SAAS,EAAE,OAAO,CAAC;IACnB,cAAc,EAAE,OAAO,CAAC;IACxB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,wBAAsB,YAAY,CAChC,IAAI,EAAE,WAAW,EACjB,YAAY,EAAE,MAAM,EACpB,SAAS,EAAE,MAAM,GAAG,IAAI,EACxB,SAAS,EAAE,MAAM,GAAG,IAAI,GACvB,OAAO,CAAC,kBAAkB,CAAC,CAoD7B"}
|
|
@@ -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 = "ota-update"
|
|
7
|
+
s.version = package["version"]
|
|
8
|
+
s.summary = package["description"]
|
|
9
|
+
s.homepage = package["repository"]["url"]
|
|
10
|
+
s.license = package["license"]
|
|
11
|
+
s.authors = { "OTA Update" => "support@ota-update.dev" }
|
|
12
|
+
|
|
13
|
+
s.platforms = { :ios => "13.0" }
|
|
14
|
+
s.source = { :git => "https://github.com/your-org/ota-update.git", :tag => "#{s.version}" }
|
|
15
|
+
|
|
16
|
+
s.source_files = "ios/**/*.{h,m,mm,swift}"
|
|
17
|
+
|
|
18
|
+
s.dependency "React-Core"
|
|
19
|
+
|
|
20
|
+
s.swift_version = "5.0"
|
|
21
|
+
end
|
package/package.json
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@vanikya/ota-react-native",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "OTA Update SDK for React Native apps - self-hosted CodePush/EAS Updates alternative",
|
|
5
|
+
"main": "lib/commonjs/index.js",
|
|
6
|
+
"module": "lib/module/index.js",
|
|
7
|
+
"types": "lib/typescript/index.d.ts",
|
|
8
|
+
"react-native": "src/index.ts",
|
|
9
|
+
"source": "src/index.ts",
|
|
10
|
+
"files": [
|
|
11
|
+
"src",
|
|
12
|
+
"lib",
|
|
13
|
+
"ios",
|
|
14
|
+
"android",
|
|
15
|
+
"*.podspec",
|
|
16
|
+
"README.md"
|
|
17
|
+
],
|
|
18
|
+
"scripts": {
|
|
19
|
+
"build": "bob build",
|
|
20
|
+
"typecheck": "tsc --noEmit",
|
|
21
|
+
"lint": "eslint src --ext .ts,.tsx",
|
|
22
|
+
"prepare": "bob build",
|
|
23
|
+
"prepublishOnly": "npm run build"
|
|
24
|
+
},
|
|
25
|
+
"keywords": [
|
|
26
|
+
"react-native",
|
|
27
|
+
"ota",
|
|
28
|
+
"update",
|
|
29
|
+
"codepush",
|
|
30
|
+
"eas-updates",
|
|
31
|
+
"expo",
|
|
32
|
+
"over-the-air",
|
|
33
|
+
"hot-update"
|
|
34
|
+
],
|
|
35
|
+
"author": "",
|
|
36
|
+
"repository": {
|
|
37
|
+
"type": "git",
|
|
38
|
+
"url": "git+https://github.com/aniruddha-ota/ota-update.git",
|
|
39
|
+
"directory": "packages/react-native"
|
|
40
|
+
},
|
|
41
|
+
"homepage": "https://github.com/aniruddha-ota/ota-update#readme",
|
|
42
|
+
"bugs": {
|
|
43
|
+
"url": "https://github.com/aniruddha-ota/ota-update/issues"
|
|
44
|
+
},
|
|
45
|
+
"publishConfig": {
|
|
46
|
+
"access": "public"
|
|
47
|
+
},
|
|
48
|
+
"license": "MIT",
|
|
49
|
+
"devDependencies": {
|
|
50
|
+
"@types/react": "^18.2.0",
|
|
51
|
+
"@types/react-native": "^0.72.0",
|
|
52
|
+
"react": "^18.2.0",
|
|
53
|
+
"react-native": "^0.73.0",
|
|
54
|
+
"react-native-builder-bob": "^0.23.0",
|
|
55
|
+
"typescript": "^5.3.0"
|
|
56
|
+
},
|
|
57
|
+
"peerDependencies": {
|
|
58
|
+
"react": ">=17.0.0",
|
|
59
|
+
"react-native": ">=0.70.0"
|
|
60
|
+
},
|
|
61
|
+
"peerDependenciesMeta": {
|
|
62
|
+
"expo-file-system": {
|
|
63
|
+
"optional": true
|
|
64
|
+
},
|
|
65
|
+
"expo-crypto": {
|
|
66
|
+
"optional": true
|
|
67
|
+
}
|
|
68
|
+
},
|
|
69
|
+
"react-native-builder-bob": {
|
|
70
|
+
"source": "src",
|
|
71
|
+
"output": "lib",
|
|
72
|
+
"targets": [
|
|
73
|
+
"commonjs",
|
|
74
|
+
"module",
|
|
75
|
+
"typescript"
|
|
76
|
+
]
|
|
77
|
+
},
|
|
78
|
+
"codegenConfig": {
|
|
79
|
+
"name": "OTAUpdateSpec",
|
|
80
|
+
"type": "modules",
|
|
81
|
+
"jsSrcsDir": "src"
|
|
82
|
+
}
|
|
83
|
+
}
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import React, {
|
|
2
|
+
createContext,
|
|
3
|
+
useContext,
|
|
4
|
+
useCallback,
|
|
5
|
+
useEffect,
|
|
6
|
+
useState,
|
|
7
|
+
ReactNode,
|
|
8
|
+
} from 'react';
|
|
9
|
+
import { Alert, Platform } from 'react-native';
|
|
10
|
+
import {
|
|
11
|
+
useOTAUpdate,
|
|
12
|
+
OTAUpdateConfig,
|
|
13
|
+
UseOTAUpdateResult,
|
|
14
|
+
UpdateStatus,
|
|
15
|
+
UpdateInfo,
|
|
16
|
+
} from './hooks/useOTAUpdate';
|
|
17
|
+
|
|
18
|
+
// Context type
|
|
19
|
+
interface OTAContextValue extends UseOTAUpdateResult {
|
|
20
|
+
config: OTAUpdateConfig;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const OTAContext = createContext<OTAContextValue | null>(null);
|
|
24
|
+
|
|
25
|
+
// Provider props
|
|
26
|
+
export interface OTAProviderProps {
|
|
27
|
+
children: ReactNode;
|
|
28
|
+
config: OTAUpdateConfig;
|
|
29
|
+
onUpdateAvailable?: (info: UpdateInfo) => void;
|
|
30
|
+
onUpdateDownloaded?: () => void;
|
|
31
|
+
onError?: (error: Error) => void;
|
|
32
|
+
showMandatoryUpdateAlert?: boolean;
|
|
33
|
+
mandatoryUpdateAlertTitle?: string;
|
|
34
|
+
mandatoryUpdateAlertMessage?: string;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function OTAProvider({
|
|
38
|
+
children,
|
|
39
|
+
config,
|
|
40
|
+
onUpdateAvailable,
|
|
41
|
+
onUpdateDownloaded,
|
|
42
|
+
onError,
|
|
43
|
+
showMandatoryUpdateAlert = true,
|
|
44
|
+
mandatoryUpdateAlertTitle = 'Update Required',
|
|
45
|
+
mandatoryUpdateAlertMessage = 'A new version is available and must be installed to continue.',
|
|
46
|
+
}: OTAProviderProps) {
|
|
47
|
+
const ota = useOTAUpdate(config);
|
|
48
|
+
const [handledMandatory, setHandledMandatory] = useState(false);
|
|
49
|
+
|
|
50
|
+
// Handle callbacks
|
|
51
|
+
useEffect(() => {
|
|
52
|
+
if (ota.status === 'available' && ota.updateInfo) {
|
|
53
|
+
onUpdateAvailable?.(ota.updateInfo);
|
|
54
|
+
|
|
55
|
+
// Handle mandatory updates
|
|
56
|
+
if (
|
|
57
|
+
showMandatoryUpdateAlert &&
|
|
58
|
+
ota.updateInfo.isMandatory &&
|
|
59
|
+
!handledMandatory
|
|
60
|
+
) {
|
|
61
|
+
setHandledMandatory(true);
|
|
62
|
+
|
|
63
|
+
Alert.alert(mandatoryUpdateAlertTitle, mandatoryUpdateAlertMessage, [
|
|
64
|
+
{
|
|
65
|
+
text: 'Update Now',
|
|
66
|
+
onPress: async () => {
|
|
67
|
+
try {
|
|
68
|
+
await ota.downloadUpdate();
|
|
69
|
+
await ota.applyUpdate(true);
|
|
70
|
+
} catch (error) {
|
|
71
|
+
// Error is handled by the hook
|
|
72
|
+
}
|
|
73
|
+
},
|
|
74
|
+
},
|
|
75
|
+
]);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}, [
|
|
79
|
+
ota.status,
|
|
80
|
+
ota.updateInfo,
|
|
81
|
+
onUpdateAvailable,
|
|
82
|
+
showMandatoryUpdateAlert,
|
|
83
|
+
handledMandatory,
|
|
84
|
+
mandatoryUpdateAlertTitle,
|
|
85
|
+
mandatoryUpdateAlertMessage,
|
|
86
|
+
ota.downloadUpdate,
|
|
87
|
+
ota.applyUpdate,
|
|
88
|
+
]);
|
|
89
|
+
|
|
90
|
+
useEffect(() => {
|
|
91
|
+
if (ota.status === 'ready') {
|
|
92
|
+
onUpdateDownloaded?.();
|
|
93
|
+
}
|
|
94
|
+
}, [ota.status, onUpdateDownloaded]);
|
|
95
|
+
|
|
96
|
+
useEffect(() => {
|
|
97
|
+
if (ota.error) {
|
|
98
|
+
onError?.(ota.error);
|
|
99
|
+
}
|
|
100
|
+
}, [ota.error, onError]);
|
|
101
|
+
|
|
102
|
+
const contextValue: OTAContextValue = {
|
|
103
|
+
...ota,
|
|
104
|
+
config,
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
return (
|
|
108
|
+
<OTAContext.Provider value={contextValue}>{children}</OTAContext.Provider>
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Hook to use OTA context
|
|
113
|
+
export function useOTA(): OTAContextValue {
|
|
114
|
+
const context = useContext(OTAContext);
|
|
115
|
+
|
|
116
|
+
if (!context) {
|
|
117
|
+
throw new Error('useOTA must be used within an OTAProvider');
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
return context;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Higher-order component
|
|
124
|
+
export function withOTA<P extends object>(
|
|
125
|
+
Component: React.ComponentType<P & { ota: OTAContextValue }>
|
|
126
|
+
): React.FC<P> {
|
|
127
|
+
return function WithOTA(props: P) {
|
|
128
|
+
const ota = useOTA();
|
|
129
|
+
return <Component {...props} ota={ota} />;
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Utility component for update banner
|
|
134
|
+
export interface UpdateBannerProps {
|
|
135
|
+
renderAvailable?: (info: UpdateInfo, download: () => void) => ReactNode;
|
|
136
|
+
renderDownloading?: (progress: number) => ReactNode;
|
|
137
|
+
renderReady?: (apply: () => void) => ReactNode;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
export function UpdateBanner({
|
|
141
|
+
renderAvailable,
|
|
142
|
+
renderDownloading,
|
|
143
|
+
renderReady,
|
|
144
|
+
}: UpdateBannerProps) {
|
|
145
|
+
const ota = useOTA();
|
|
146
|
+
|
|
147
|
+
if (ota.status === 'available' && ota.updateInfo && renderAvailable) {
|
|
148
|
+
return <>{renderAvailable(ota.updateInfo, ota.downloadUpdate)}</>;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
if (ota.status === 'downloading' && ota.downloadProgress && renderDownloading) {
|
|
152
|
+
return <>{renderDownloading(ota.downloadProgress.percentage)}</>;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
if (ota.status === 'ready' && renderReady) {
|
|
156
|
+
return <>{renderReady(() => ota.applyUpdate(true))}</>;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
return null;
|
|
160
|
+
}
|