relay-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.
Files changed (56) hide show
  1. package/README.md +376 -0
  2. package/dist/client.d.ts +3 -0
  3. package/dist/client.d.ts.map +1 -0
  4. package/dist/client.js +16 -0
  5. package/dist/client.js.map +1 -0
  6. package/dist/downloader.d.ts +6 -0
  7. package/dist/downloader.d.ts.map +1 -0
  8. package/dist/downloader.js +65 -0
  9. package/dist/downloader.js.map +1 -0
  10. package/dist/filesystem.d.ts +4 -0
  11. package/dist/filesystem.d.ts.map +1 -0
  12. package/dist/filesystem.js +63 -0
  13. package/dist/filesystem.js.map +1 -0
  14. package/dist/hooks.d.ts +15 -0
  15. package/dist/hooks.d.ts.map +1 -0
  16. package/dist/hooks.js +34 -0
  17. package/dist/hooks.js.map +1 -0
  18. package/dist/index.d.ts +9 -0
  19. package/dist/index.d.ts.map +1 -0
  20. package/dist/index.js +25 -0
  21. package/dist/index.js.map +1 -0
  22. package/dist/installer.d.ts +6 -0
  23. package/dist/installer.d.ts.map +1 -0
  24. package/dist/installer.js +46 -0
  25. package/dist/installer.js.map +1 -0
  26. package/dist/provider.d.ts +18 -0
  27. package/dist/provider.d.ts.map +1 -0
  28. package/dist/provider.js +82 -0
  29. package/dist/provider.js.map +1 -0
  30. package/dist/storage.d.ts +12 -0
  31. package/dist/storage.d.ts.map +1 -0
  32. package/dist/storage.js +44 -0
  33. package/dist/storage.js.map +1 -0
  34. package/dist/types.d.ts +55 -0
  35. package/dist/types.d.ts.map +1 -0
  36. package/dist/types.js +3 -0
  37. package/dist/types.js.map +1 -0
  38. package/dist/updater.d.ts +17 -0
  39. package/dist/updater.d.ts.map +1 -0
  40. package/dist/updater.js +98 -0
  41. package/dist/updater.js.map +1 -0
  42. package/package.json +60 -0
  43. package/src/client.ts +19 -0
  44. package/src/downloader.ts +79 -0
  45. package/src/filesystem.ts +62 -0
  46. package/src/hooks.ts +46 -0
  47. package/src/index.ts +21 -0
  48. package/src/installer.ts +42 -0
  49. package/src/native/OtaBundleUpdater.h +5 -0
  50. package/src/native/OtaBundleUpdater.m +56 -0
  51. package/src/native/OtaBundleUpdaterModule.kt +73 -0
  52. package/src/native/OtaBundleUpdaterPackage.kt +14 -0
  53. package/src/provider.tsx +77 -0
  54. package/src/storage.ts +42 -0
  55. package/src/types.ts +68 -0
  56. package/src/updater.ts +115 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;AAAA,uCAAwD;AAA/C,uGAAA,WAAW,OAAA;AAAE,yGAAA,aAAa,OAAA;AACnC,iCAAsD;AAA7C,qGAAA,YAAY,OAAA;AAAE,sGAAA,aAAa,OAAA;AACpC,qCAAuC;AAA9B,qGAAA,UAAU,OAAA;AACnB,mCAA0C;AAAjC,wGAAA,cAAc,OAAA;AACvB,2CAA8C;AAArC,4GAAA,cAAc,OAAA;AACvB,2CAAwF;AAA/E,yHAAA,2BAA2B,OAAA;AAAE,yHAAA,2BAA2B,OAAA;AACjE,yCAMqB;AALnB,oHAAA,uBAAuB,OAAA;AACvB,gHAAA,mBAAmB,OAAA;AACnB,gHAAA,mBAAmB,OAAA;AACnB,kHAAA,qBAAqB,OAAA;AACrB,mGAAA,MAAM,OAAA"}
@@ -0,0 +1,6 @@
1
+ export declare function isNativeModuleAvailable(): boolean;
2
+ export declare function setStagedBundlePath(path: string): Promise<void>;
3
+ export declare function getStagedBundlePath(): Promise<string | null>;
4
+ export declare function clearStagedBundlePath(): Promise<void>;
5
+ export declare function reload(): void;
6
+ //# sourceMappingURL=installer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"installer.d.ts","sourceRoot":"","sources":["../src/installer.ts"],"names":[],"mappings":"AAUA,wBAAgB,uBAAuB,IAAI,OAAO,CAEjD;AAED,wBAAsB,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAIrE;AAED,wBAAsB,mBAAmB,IAAI,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAIlE;AAED,wBAAsB,qBAAqB,IAAI,OAAO,CAAC,IAAI,CAAC,CAI3D;AAED,wBAAgB,MAAM,IAAI,IAAI,CAS7B"}
@@ -0,0 +1,46 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.isNativeModuleAvailable = isNativeModuleAvailable;
4
+ exports.setStagedBundlePath = setStagedBundlePath;
5
+ exports.getStagedBundlePath = getStagedBundlePath;
6
+ exports.clearStagedBundlePath = clearStagedBundlePath;
7
+ exports.reload = reload;
8
+ const react_native_1 = require("react-native");
9
+ // User must register their native module under this name
10
+ const NATIVE_MODULE_NAME = 'OtaBundleUpdater';
11
+ function getNativeModule() {
12
+ return react_native_1.NativeModules[NATIVE_MODULE_NAME] ?? null;
13
+ }
14
+ function isNativeModuleAvailable() {
15
+ return getNativeModule() !== null;
16
+ }
17
+ async function setStagedBundlePath(path) {
18
+ const mod = getNativeModule();
19
+ if (!mod)
20
+ throw new Error(`Native module '${NATIVE_MODULE_NAME}' not found. See SDK setup docs.`);
21
+ await mod.setBundlePath(path);
22
+ }
23
+ async function getStagedBundlePath() {
24
+ const mod = getNativeModule();
25
+ if (!mod)
26
+ return null;
27
+ return mod.getBundlePath();
28
+ }
29
+ async function clearStagedBundlePath() {
30
+ const mod = getNativeModule();
31
+ if (!mod)
32
+ return;
33
+ await mod.clearBundlePath();
34
+ }
35
+ function reload() {
36
+ const mod = getNativeModule();
37
+ if (mod) {
38
+ mod.reload();
39
+ }
40
+ else {
41
+ // Dev fallback
42
+ const devSettings = react_native_1.NativeModules['DevSettings'];
43
+ devSettings?.reload?.();
44
+ }
45
+ }
46
+ //# sourceMappingURL=installer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"installer.js","sourceRoot":"","sources":["../src/installer.ts"],"names":[],"mappings":";;AAUA,0DAEC;AAED,kDAIC;AAED,kDAIC;AAED,sDAIC;AAED,wBASC;AAzCD,+CAA6C;AAG7C,yDAAyD;AACzD,MAAM,kBAAkB,GAAG,kBAAkB,CAAC;AAE9C,SAAS,eAAe;IACtB,OAAQ,4BAAa,CAAC,kBAAkB,CAA0B,IAAI,IAAI,CAAC;AAC7E,CAAC;AAED,SAAgB,uBAAuB;IACrC,OAAO,eAAe,EAAE,KAAK,IAAI,CAAC;AACpC,CAAC;AAEM,KAAK,UAAU,mBAAmB,CAAC,IAAY;IACpD,MAAM,GAAG,GAAG,eAAe,EAAE,CAAC;IAC9B,IAAI,CAAC,GAAG;QAAE,MAAM,IAAI,KAAK,CAAC,kBAAkB,kBAAkB,kCAAkC,CAAC,CAAC;IAClG,MAAM,GAAG,CAAC,aAAa,CAAC,IAAI,CAAC,CAAC;AAChC,CAAC;AAEM,KAAK,UAAU,mBAAmB;IACvC,MAAM,GAAG,GAAG,eAAe,EAAE,CAAC;IAC9B,IAAI,CAAC,GAAG;QAAE,OAAO,IAAI,CAAC;IACtB,OAAO,GAAG,CAAC,aAAa,EAAE,CAAC;AAC7B,CAAC;AAEM,KAAK,UAAU,qBAAqB;IACzC,MAAM,GAAG,GAAG,eAAe,EAAE,CAAC;IAC9B,IAAI,CAAC,GAAG;QAAE,OAAO;IACjB,MAAM,GAAG,CAAC,eAAe,EAAE,CAAC;AAC9B,CAAC;AAED,SAAgB,MAAM;IACpB,MAAM,GAAG,GAAG,eAAe,EAAE,CAAC;IAC9B,IAAI,GAAG,EAAE,CAAC;QACR,GAAG,CAAC,MAAM,EAAE,CAAC;IACf,CAAC;SAAM,CAAC;QACN,eAAe;QACf,MAAM,WAAW,GAAG,4BAAa,CAAC,aAAa,CAAwC,CAAC;QACxF,WAAW,EAAE,MAAM,EAAE,EAAE,CAAC;IAC1B,CAAC;AACH,CAAC"}
@@ -0,0 +1,18 @@
1
+ import React, { type ReactNode } from 'react';
2
+ import type { OtaConfig, UpdateState, IFileSystemAdapter } from './types';
3
+ interface OtaContextValue {
4
+ state: UpdateState;
5
+ checkForUpdates: () => Promise<void>;
6
+ downloadUpdate: () => Promise<void>;
7
+ applyUpdate: () => Promise<void>;
8
+ rollback: () => Promise<void>;
9
+ }
10
+ interface OtaProviderProps {
11
+ config: OtaConfig;
12
+ fileSystem: IFileSystemAdapter;
13
+ children: ReactNode;
14
+ }
15
+ export declare function OtaProvider({ config, fileSystem, children }: OtaProviderProps): React.JSX.Element;
16
+ export declare function useOtaContext(): OtaContextValue;
17
+ export {};
18
+ //# sourceMappingURL=provider.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"provider.d.ts","sourceRoot":"","sources":["../src/provider.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,EAMZ,KAAK,SAAS,EACf,MAAM,OAAO,CAAC;AAGf,OAAO,KAAK,EAAE,SAAS,EAAE,WAAW,EAAE,kBAAkB,EAAE,MAAM,SAAS,CAAC;AAE1E,UAAU,eAAe;IACvB,KAAK,EAAE,WAAW,CAAC;IACnB,eAAe,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IACrC,cAAc,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IACpC,WAAW,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IACjC,QAAQ,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAC/B;AAID,UAAU,gBAAgB;IACxB,MAAM,EAAE,SAAS,CAAC;IAClB,UAAU,EAAE,kBAAkB,CAAC;IAC/B,QAAQ,EAAE,SAAS,CAAC;CACrB;AAED,wBAAgB,WAAW,CAAC,EAAE,MAAM,EAAE,UAAU,EAAE,QAAQ,EAAE,EAAE,gBAAgB,qBA0C7E;AAED,wBAAgB,aAAa,IAAI,eAAe,CAI/C"}
@@ -0,0 +1,82 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.OtaProvider = OtaProvider;
37
+ exports.useOtaContext = useOtaContext;
38
+ const react_1 = __importStar(require("react"));
39
+ const react_native_1 = require("react-native");
40
+ const updater_1 = require("./updater");
41
+ const OtaContext = (0, react_1.createContext)(null);
42
+ function OtaProvider({ config, fileSystem, children }) {
43
+ const updaterRef = (0, react_1.useRef)(null);
44
+ if (!updaterRef.current) {
45
+ updaterRef.current = new updater_1.OtaUpdater(config, fileSystem);
46
+ }
47
+ const updater = updaterRef.current;
48
+ const [state, setState] = (0, react_1.useState)(updater.getState());
49
+ (0, react_1.useEffect)(() => {
50
+ const unsub = updater.subscribe(setState);
51
+ return unsub;
52
+ }, [updater]);
53
+ // Check on foreground (AppState active)
54
+ (0, react_1.useEffect)(() => {
55
+ if (config.checkOnForeground === false)
56
+ return;
57
+ const handler = (nextState) => {
58
+ if (nextState === 'active') {
59
+ updater.checkForUpdates();
60
+ }
61
+ };
62
+ const sub = react_native_1.AppState.addEventListener('change', handler);
63
+ // Initial check on mount
64
+ updater.checkForUpdates();
65
+ return () => sub.remove();
66
+ }, [updater, config.checkOnForeground]);
67
+ const value = {
68
+ state,
69
+ checkForUpdates: () => updater.checkForUpdates(),
70
+ downloadUpdate: () => updater.downloadUpdate(),
71
+ applyUpdate: () => updater.applyUpdate(),
72
+ rollback: () => updater.rollback(),
73
+ };
74
+ return <OtaContext.Provider value={value}>{children}</OtaContext.Provider>;
75
+ }
76
+ function useOtaContext() {
77
+ const ctx = (0, react_1.useContext)(OtaContext);
78
+ if (!ctx)
79
+ throw new Error('useOtaContext must be used inside <OtaProvider>');
80
+ return ctx;
81
+ }
82
+ //# sourceMappingURL=provider.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"provider.js","sourceRoot":"","sources":["../src/provider.tsx"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA4BA,kCA0CC;AAED,sCAIC;AA5ED,+CAOe;AACf,+CAA6D;AAC7D,uCAAuC;AAWvC,MAAM,UAAU,GAAG,IAAA,qBAAa,EAAyB,IAAI,CAAC,CAAC;AAQ/D,SAAgB,WAAW,CAAC,EAAE,MAAM,EAAE,UAAU,EAAE,QAAQ,EAAoB;IAC5E,MAAM,UAAU,GAAG,IAAA,cAAM,EAAoB,IAAI,CAAC,CAAC;IAEnD,IAAI,CAAC,UAAU,CAAC,OAAO,EAAE,CAAC;QACxB,UAAU,CAAC,OAAO,GAAG,IAAI,oBAAU,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;IAC1D,CAAC;IAED,MAAM,OAAO,GAAG,UAAU,CAAC,OAAO,CAAC;IACnC,MAAM,CAAC,KAAK,EAAE,QAAQ,CAAC,GAAG,IAAA,gBAAQ,EAAc,OAAO,CAAC,QAAQ,EAAE,CAAC,CAAC;IAEpE,IAAA,iBAAS,EAAC,GAAG,EAAE;QACb,MAAM,KAAK,GAAG,OAAO,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC;QAC1C,OAAO,KAAK,CAAC;IACf,CAAC,EAAE,CAAC,OAAO,CAAC,CAAC,CAAC;IAEd,wCAAwC;IACxC,IAAA,iBAAS,EAAC,GAAG,EAAE;QACb,IAAI,MAAM,CAAC,iBAAiB,KAAK,KAAK;YAAE,OAAO;QAE/C,MAAM,OAAO,GAAG,CAAC,SAAyB,EAAE,EAAE;YAC5C,IAAI,SAAS,KAAK,QAAQ,EAAE,CAAC;gBAC3B,OAAO,CAAC,eAAe,EAAE,CAAC;YAC5B,CAAC;QACH,CAAC,CAAC;QAEF,MAAM,GAAG,GAAG,uBAAQ,CAAC,gBAAgB,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;QAEzD,yBAAyB;QACzB,OAAO,CAAC,eAAe,EAAE,CAAC;QAE1B,OAAO,GAAG,EAAE,CAAC,GAAG,CAAC,MAAM,EAAE,CAAC;IAC5B,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,CAAC,iBAAiB,CAAC,CAAC,CAAC;IAExC,MAAM,KAAK,GAAoB;QAC7B,KAAK;QACL,eAAe,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,eAAe,EAAE;QAChD,cAAc,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,cAAc,EAAE;QAC9C,WAAW,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,WAAW,EAAE;QACxC,QAAQ,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,QAAQ,EAAE;KACnC,CAAC;IAEF,OAAO,CAAC,UAAU,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,QAAQ,CAAC,EAAE,UAAU,CAAC,QAAQ,CAAC,CAAC;AAC7E,CAAC;AAED,SAAgB,aAAa;IAC3B,MAAM,GAAG,GAAG,IAAA,kBAAU,EAAC,UAAU,CAAC,CAAC;IACnC,IAAI,CAAC,GAAG;QAAE,MAAM,IAAI,KAAK,CAAC,iDAAiD,CAAC,CAAC;IAC7E,OAAO,GAAG,CAAC;AACb,CAAC"}
@@ -0,0 +1,12 @@
1
+ import type { OtaCheckResponse } from './types';
2
+ type StoredRelease = NonNullable<OtaCheckResponse['release']>;
3
+ export declare function storePendingRelease(release: StoredRelease): Promise<void>;
4
+ export declare function getPendingRelease(): Promise<StoredRelease | null>;
5
+ export declare function clearPendingRelease(): Promise<void>;
6
+ export declare function storeAppliedRelease(release: StoredRelease): Promise<void>;
7
+ export declare function getAppliedRelease(): Promise<StoredRelease | null>;
8
+ export declare function storeBundlePath(path: string): Promise<void>;
9
+ export declare function getBundlePath(): Promise<string | null>;
10
+ export declare function clearBundlePath(): Promise<void>;
11
+ export {};
12
+ //# sourceMappingURL=storage.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"storage.d.ts","sourceRoot":"","sources":["../src/storage.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAC;AAMhD,KAAK,aAAa,GAAG,WAAW,CAAC,gBAAgB,CAAC,SAAS,CAAC,CAAC,CAAC;AAE9D,wBAAsB,mBAAmB,CAAC,OAAO,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC,CAE/E;AAED,wBAAsB,iBAAiB,IAAI,OAAO,CAAC,aAAa,GAAG,IAAI,CAAC,CAGvE;AAED,wBAAsB,mBAAmB,IAAI,OAAO,CAAC,IAAI,CAAC,CAEzD;AAED,wBAAsB,mBAAmB,CAAC,OAAO,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC,CAE/E;AAED,wBAAsB,iBAAiB,IAAI,OAAO,CAAC,aAAa,GAAG,IAAI,CAAC,CAGvE;AAED,wBAAsB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAEjE;AAED,wBAAsB,aAAa,IAAI,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAE5D;AAED,wBAAsB,eAAe,IAAI,OAAO,CAAC,IAAI,CAAC,CAErD"}
@@ -0,0 +1,44 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.storePendingRelease = storePendingRelease;
7
+ exports.getPendingRelease = getPendingRelease;
8
+ exports.clearPendingRelease = clearPendingRelease;
9
+ exports.storeAppliedRelease = storeAppliedRelease;
10
+ exports.getAppliedRelease = getAppliedRelease;
11
+ exports.storeBundlePath = storeBundlePath;
12
+ exports.getBundlePath = getBundlePath;
13
+ exports.clearBundlePath = clearBundlePath;
14
+ const async_storage_1 = __importDefault(require("@react-native-async-storage/async-storage"));
15
+ const KEY_PENDING_RELEASE = '@ota/pending_release';
16
+ const KEY_APPLIED_RELEASE = '@ota/applied_release';
17
+ const KEY_BUNDLE_PATH = '@ota/bundle_path';
18
+ async function storePendingRelease(release) {
19
+ await async_storage_1.default.setItem(KEY_PENDING_RELEASE, JSON.stringify(release));
20
+ }
21
+ async function getPendingRelease() {
22
+ const raw = await async_storage_1.default.getItem(KEY_PENDING_RELEASE);
23
+ return raw ? JSON.parse(raw) : null;
24
+ }
25
+ async function clearPendingRelease() {
26
+ await async_storage_1.default.removeItem(KEY_PENDING_RELEASE);
27
+ }
28
+ async function storeAppliedRelease(release) {
29
+ await async_storage_1.default.setItem(KEY_APPLIED_RELEASE, JSON.stringify(release));
30
+ }
31
+ async function getAppliedRelease() {
32
+ const raw = await async_storage_1.default.getItem(KEY_APPLIED_RELEASE);
33
+ return raw ? JSON.parse(raw) : null;
34
+ }
35
+ async function storeBundlePath(path) {
36
+ await async_storage_1.default.setItem(KEY_BUNDLE_PATH, path);
37
+ }
38
+ async function getBundlePath() {
39
+ return async_storage_1.default.getItem(KEY_BUNDLE_PATH);
40
+ }
41
+ async function clearBundlePath() {
42
+ await async_storage_1.default.removeItem(KEY_BUNDLE_PATH);
43
+ }
44
+ //# sourceMappingURL=storage.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"storage.js","sourceRoot":"","sources":["../src/storage.ts"],"names":[],"mappings":";;;;;AASA,kDAEC;AAED,8CAGC;AAED,kDAEC;AAED,kDAEC;AAED,8CAGC;AAED,0CAEC;AAED,sCAEC;AAED,0CAEC;AAzCD,8FAAqE;AAGrE,MAAM,mBAAmB,GAAG,sBAAsB,CAAC;AACnD,MAAM,mBAAmB,GAAG,sBAAsB,CAAC;AACnD,MAAM,eAAe,GAAG,kBAAkB,CAAC;AAIpC,KAAK,UAAU,mBAAmB,CAAC,OAAsB;IAC9D,MAAM,uBAAY,CAAC,OAAO,CAAC,mBAAmB,EAAE,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,CAAC;AAC3E,CAAC;AAEM,KAAK,UAAU,iBAAiB;IACrC,MAAM,GAAG,GAAG,MAAM,uBAAY,CAAC,OAAO,CAAC,mBAAmB,CAAC,CAAC;IAC5D,OAAO,GAAG,CAAC,CAAC,CAAE,IAAI,CAAC,KAAK,CAAC,GAAG,CAAmB,CAAC,CAAC,CAAC,IAAI,CAAC;AACzD,CAAC;AAEM,KAAK,UAAU,mBAAmB;IACvC,MAAM,uBAAY,CAAC,UAAU,CAAC,mBAAmB,CAAC,CAAC;AACrD,CAAC;AAEM,KAAK,UAAU,mBAAmB,CAAC,OAAsB;IAC9D,MAAM,uBAAY,CAAC,OAAO,CAAC,mBAAmB,EAAE,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,CAAC;AAC3E,CAAC;AAEM,KAAK,UAAU,iBAAiB;IACrC,MAAM,GAAG,GAAG,MAAM,uBAAY,CAAC,OAAO,CAAC,mBAAmB,CAAC,CAAC;IAC5D,OAAO,GAAG,CAAC,CAAC,CAAE,IAAI,CAAC,KAAK,CAAC,GAAG,CAAmB,CAAC,CAAC,CAAC,IAAI,CAAC;AACzD,CAAC;AAEM,KAAK,UAAU,eAAe,CAAC,IAAY;IAChD,MAAM,uBAAY,CAAC,OAAO,CAAC,eAAe,EAAE,IAAI,CAAC,CAAC;AACpD,CAAC;AAEM,KAAK,UAAU,aAAa;IACjC,OAAO,uBAAY,CAAC,OAAO,CAAC,eAAe,CAAC,CAAC;AAC/C,CAAC;AAEM,KAAK,UAAU,eAAe;IACnC,MAAM,uBAAY,CAAC,UAAU,CAAC,eAAe,CAAC,CAAC;AACjD,CAAC"}
@@ -0,0 +1,55 @@
1
+ export type Platform = 'IOS' | 'ANDROID';
2
+ export interface OtaCheckRequest {
3
+ appId: string;
4
+ platform: Platform;
5
+ currentVersion: string;
6
+ runtimeVersion: string;
7
+ channel: string;
8
+ }
9
+ export interface OtaCheckResponse {
10
+ updateAvailable: boolean;
11
+ release?: {
12
+ id: string;
13
+ version: string;
14
+ downloadUrl: string;
15
+ checksum: string;
16
+ fileSize: number;
17
+ isMandatory: boolean;
18
+ releaseNotes?: string;
19
+ metadata?: Record<string, unknown>;
20
+ };
21
+ }
22
+ export interface OtaConfig {
23
+ serverUrl: string;
24
+ appId: string;
25
+ channel: string;
26
+ platform: Platform;
27
+ currentVersion: string;
28
+ runtimeVersion: string;
29
+ checkOnForeground?: boolean;
30
+ onUpdate?: (release: NonNullable<OtaCheckResponse['release']>) => void;
31
+ onError?: (error: Error) => void;
32
+ }
33
+ export type UpdateStatus = 'idle' | 'checking' | 'up_to_date' | 'update_available' | 'downloading' | 'ready' | 'error';
34
+ export interface UpdateState {
35
+ status: UpdateStatus;
36
+ progress: number;
37
+ release: NonNullable<OtaCheckResponse['release']> | null;
38
+ error: Error | null;
39
+ localBundlePath: string | null;
40
+ }
41
+ export interface IFileSystemAdapter {
42
+ getDocumentDirectory(): string;
43
+ exists(path: string): Promise<boolean>;
44
+ readAsString(path: string, encoding: 'utf8' | 'base64'): Promise<string>;
45
+ writeAsString(path: string, content: string, encoding: 'utf8' | 'base64'): Promise<void>;
46
+ deleteFile(path: string): Promise<void>;
47
+ makeDirectory(path: string): Promise<void>;
48
+ }
49
+ export interface INativeBundleUpdater {
50
+ setBundlePath(path: string): Promise<void>;
51
+ getBundlePath(): Promise<string | null>;
52
+ clearBundlePath(): Promise<void>;
53
+ reload(): void;
54
+ }
55
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,QAAQ,GAAG,KAAK,GAAG,SAAS,CAAC;AAEzC,MAAM,WAAW,eAAe;IAC9B,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,QAAQ,CAAC;IACnB,cAAc,EAAE,MAAM,CAAC;IACvB,cAAc,EAAE,MAAM,CAAC;IACvB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,gBAAgB;IAC/B,eAAe,EAAE,OAAO,CAAC;IACzB,OAAO,CAAC,EAAE;QACR,EAAE,EAAE,MAAM,CAAC;QACX,OAAO,EAAE,MAAM,CAAC;QAChB,WAAW,EAAE,MAAM,CAAC;QACpB,QAAQ,EAAE,MAAM,CAAC;QACjB,QAAQ,EAAE,MAAM,CAAC;QACjB,WAAW,EAAE,OAAO,CAAC;QACrB,YAAY,CAAC,EAAE,MAAM,CAAC;QACtB,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;KACpC,CAAC;CACH;AAED,MAAM,WAAW,SAAS;IACxB,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,QAAQ,CAAC;IACnB,cAAc,EAAE,MAAM,CAAC;IACvB,cAAc,EAAE,MAAM,CAAC;IACvB,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B,QAAQ,CAAC,EAAE,CAAC,OAAO,EAAE,WAAW,CAAC,gBAAgB,CAAC,SAAS,CAAC,CAAC,KAAK,IAAI,CAAC;IACvE,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;CAClC;AAED,MAAM,MAAM,YAAY,GACpB,MAAM,GACN,UAAU,GACV,YAAY,GACZ,kBAAkB,GAClB,aAAa,GACb,OAAO,GACP,OAAO,CAAC;AAEZ,MAAM,WAAW,WAAW;IAC1B,MAAM,EAAE,YAAY,CAAC;IACrB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,WAAW,CAAC,gBAAgB,CAAC,SAAS,CAAC,CAAC,GAAG,IAAI,CAAC;IACzD,KAAK,EAAE,KAAK,GAAG,IAAI,CAAC;IACpB,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;CAChC;AAED,MAAM,WAAW,kBAAkB;IACjC,oBAAoB,IAAI,MAAM,CAAC;IAC/B,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IACvC,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,QAAQ,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IACzE,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACzF,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACxC,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC5C;AAED,MAAM,WAAW,oBAAoB;IACnC,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC3C,aAAa,IAAI,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IACxC,eAAe,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACjC,MAAM,IAAI,IAAI,CAAC;CAChB"}
package/dist/types.js ADDED
@@ -0,0 +1,3 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
@@ -0,0 +1,17 @@
1
+ import type { OtaConfig, UpdateState, IFileSystemAdapter } from './types';
2
+ export type StateListener = (state: UpdateState) => void;
3
+ export declare class OtaUpdater {
4
+ private state;
5
+ private listeners;
6
+ private config;
7
+ private fs;
8
+ constructor(config: OtaConfig, fs: IFileSystemAdapter);
9
+ getState(): UpdateState;
10
+ subscribe(listener: StateListener): () => void;
11
+ private emit;
12
+ checkForUpdates(): Promise<void>;
13
+ downloadUpdate(): Promise<void>;
14
+ applyUpdate(): Promise<void>;
15
+ rollback(): Promise<void>;
16
+ }
17
+ //# sourceMappingURL=updater.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"updater.d.ts","sourceRoot":"","sources":["../src/updater.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,SAAS,EAAE,WAAW,EAAE,kBAAkB,EAAE,MAAM,SAAS,CAAC;AAE1E,MAAM,MAAM,aAAa,GAAG,CAAC,KAAK,EAAE,WAAW,KAAK,IAAI,CAAC;AAUzD,qBAAa,UAAU;IACrB,OAAO,CAAC,KAAK,CAAoC;IACjD,OAAO,CAAC,SAAS,CAAiC;IAClD,OAAO,CAAC,MAAM,CAAY;IAC1B,OAAO,CAAC,EAAE,CAAqB;gBAEnB,MAAM,EAAE,SAAS,EAAE,EAAE,EAAE,kBAAkB;IAKrD,QAAQ,IAAI,WAAW;IAIvB,SAAS,CAAC,QAAQ,EAAE,aAAa,GAAG,MAAM,IAAI;IAK9C,OAAO,CAAC,IAAI;IAKN,eAAe,IAAI,OAAO,CAAC,IAAI,CAAC;IA8BhC,cAAc,IAAI,OAAO,CAAC,IAAI,CAAC;IAsB/B,WAAW,IAAI,OAAO,CAAC,IAAI,CAAC;IAO5B,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;CAOhC"}
@@ -0,0 +1,98 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.OtaUpdater = void 0;
4
+ const client_1 = require("./client");
5
+ const downloader_1 = require("./downloader");
6
+ const installer_1 = require("./installer");
7
+ const storage_1 = require("./storage");
8
+ const initialState = {
9
+ status: 'idle',
10
+ progress: 0,
11
+ release: null,
12
+ error: null,
13
+ localBundlePath: null,
14
+ };
15
+ class OtaUpdater {
16
+ state = { ...initialState };
17
+ listeners = new Set();
18
+ config;
19
+ fs;
20
+ constructor(config, fs) {
21
+ this.config = config;
22
+ this.fs = fs;
23
+ }
24
+ getState() {
25
+ return this.state;
26
+ }
27
+ subscribe(listener) {
28
+ this.listeners.add(listener);
29
+ return () => this.listeners.delete(listener);
30
+ }
31
+ emit(patch) {
32
+ this.state = { ...this.state, ...patch };
33
+ this.listeners.forEach((l) => l(this.state));
34
+ }
35
+ async checkForUpdates() {
36
+ if (this.state.status === 'checking' || this.state.status === 'downloading')
37
+ return;
38
+ this.emit({ status: 'checking', error: null });
39
+ try {
40
+ const applied = await (0, storage_1.getAppliedRelease)();
41
+ const currentVersion = applied?.version ?? this.config.currentVersion;
42
+ const result = await (0, client_1.checkForUpdate)(this.config.serverUrl, {
43
+ appId: this.config.appId,
44
+ channel: this.config.channel,
45
+ platform: this.config.platform,
46
+ currentVersion,
47
+ runtimeVersion: this.config.runtimeVersion,
48
+ });
49
+ if (!result.updateAvailable || !result.release) {
50
+ this.emit({ status: 'up_to_date', release: null });
51
+ return;
52
+ }
53
+ this.emit({ status: 'update_available', release: result.release });
54
+ await (0, storage_1.storePendingRelease)(result.release);
55
+ this.config.onUpdate?.(result.release);
56
+ }
57
+ catch (err) {
58
+ const error = err instanceof Error ? err : new Error(String(err));
59
+ this.emit({ status: 'error', error });
60
+ this.config.onError?.(error);
61
+ }
62
+ }
63
+ async downloadUpdate() {
64
+ if (!this.state.release || this.state.status !== 'update_available')
65
+ return;
66
+ const release = this.state.release;
67
+ this.emit({ status: 'downloading', progress: 0 });
68
+ try {
69
+ const bundlePath = await (0, downloader_1.downloadBundle)(release, this.fs, (progress) => {
70
+ this.emit({ progress });
71
+ });
72
+ await (0, installer_1.setStagedBundlePath)(bundlePath);
73
+ await (0, storage_1.storeBundlePath)(bundlePath);
74
+ this.emit({ status: 'ready', localBundlePath: bundlePath, progress: 100 });
75
+ }
76
+ catch (err) {
77
+ const error = err instanceof Error ? err : new Error(String(err));
78
+ this.emit({ status: 'error', error });
79
+ this.config.onError?.(error);
80
+ }
81
+ }
82
+ async applyUpdate() {
83
+ if (this.state.status !== 'ready' || !this.state.release)
84
+ return;
85
+ await (0, storage_1.storeAppliedRelease)(this.state.release);
86
+ await (0, storage_1.clearPendingRelease)();
87
+ (0, installer_1.reload)();
88
+ }
89
+ async rollback() {
90
+ await (0, installer_1.clearStagedBundlePath)();
91
+ await (0, storage_1.clearBundlePath)();
92
+ await (0, storage_1.clearPendingRelease)();
93
+ this.emit({ ...initialState, status: 'idle' });
94
+ (0, installer_1.reload)();
95
+ }
96
+ }
97
+ exports.OtaUpdater = OtaUpdater;
98
+ //# sourceMappingURL=updater.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"updater.js","sourceRoot":"","sources":["../src/updater.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,60 @@
1
+ {
2
+ "name": "relay-ota-react-native",
3
+ "version": "0.1.0",
4
+ "description": "Relay OTA SDK for React Native — ship updates without app store review",
5
+ "main": "./dist/index.js",
6
+ "module": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "default": "./dist/index.js"
12
+ }
13
+ },
14
+ "files": [
15
+ "dist",
16
+ "src"
17
+ ],
18
+ "scripts": {
19
+ "build": "tsc",
20
+ "type-check": "tsc --noEmit",
21
+ "prepublishOnly": "npm run build"
22
+ },
23
+ "keywords": [
24
+ "react-native",
25
+ "ota",
26
+ "over-the-air",
27
+ "codepush",
28
+ "expo",
29
+ "update",
30
+ "sdk"
31
+ ],
32
+ "license": "MIT",
33
+ "homepage": "https://relayota.com",
34
+ "repository": {
35
+ "type": "git",
36
+ "url": "https://github.com/aglaonematech/relay-ota-react-native.git"
37
+ },
38
+ "bugs": {
39
+ "url": "https://github.com/aglaonematech/relay-ota-react-native/issues"
40
+ },
41
+ "publishConfig": {
42
+ "access": "public"
43
+ },
44
+ "peerDependencies": {
45
+ "react": ">=18.0.0",
46
+ "react-native": ">=0.72.0",
47
+ "@react-native-async-storage/async-storage": ">=1.0.0"
48
+ },
49
+ "peerDependenciesMeta": {
50
+ "@react-native-async-storage/async-storage": {
51
+ "optional": false
52
+ }
53
+ },
54
+ "devDependencies": {
55
+ "@react-native-async-storage/async-storage": "^2.1.2",
56
+ "@types/react": "^18.3.1",
57
+ "@types/react-native": "^0.73.0",
58
+ "typescript": "^5.7.3"
59
+ }
60
+ }
package/src/client.ts ADDED
@@ -0,0 +1,19 @@
1
+ import type { OtaCheckRequest, OtaCheckResponse } from './types';
2
+
3
+ export async function checkForUpdate(
4
+ serverUrl: string,
5
+ request: OtaCheckRequest,
6
+ ): Promise<OtaCheckResponse> {
7
+ const url = `${serverUrl.replace(/\/$/, '')}/api/v1/ota/check`;
8
+ const response = await fetch(url, {
9
+ method: 'POST',
10
+ headers: { 'Content-Type': 'application/json' },
11
+ body: JSON.stringify(request),
12
+ });
13
+
14
+ if (!response.ok) {
15
+ throw new Error(`OTA check failed: HTTP ${response.status}`);
16
+ }
17
+
18
+ return response.json() as Promise<OtaCheckResponse>;
19
+ }
@@ -0,0 +1,79 @@
1
+ import type { IFileSystemAdapter } from './types';
2
+ import type { OtaCheckResponse } from './types';
3
+
4
+ type Release = NonNullable<OtaCheckResponse['release']>;
5
+
6
+ export async function downloadBundle(
7
+ release: Release,
8
+ fs: IFileSystemAdapter,
9
+ onProgress?: (progress: number) => void,
10
+ ): Promise<string> {
11
+ const bundleDir = `${fs.getDocumentDirectory()}ota-bundles/${release.id}`;
12
+ const bundlePath = `${bundleDir}/bundle.js`;
13
+
14
+ await fs.makeDirectory(bundleDir);
15
+
16
+ const response = await fetch(release.downloadUrl);
17
+ if (!response.ok) {
18
+ throw new Error(`Download failed: HTTP ${response.status}`);
19
+ }
20
+
21
+ const contentLength = parseInt(response.headers.get('content-length') ?? '0', 10);
22
+ const reader = response.body?.getReader();
23
+
24
+ if (!reader) {
25
+ // Fallback: read entire body at once (no streaming progress)
26
+ const buffer = await response.arrayBuffer();
27
+ const bytes = new Uint8Array(buffer);
28
+ const b64 = uint8ArrayToBase64(bytes);
29
+ await fs.writeAsString(bundlePath, b64, 'base64');
30
+ onProgress?.(100);
31
+ } else {
32
+ const chunks: Uint8Array[] = [];
33
+ let received = 0;
34
+
35
+ while (true) {
36
+ const { done, value } = await reader.read();
37
+ if (done) break;
38
+ chunks.push(value);
39
+ received += value.length;
40
+ if (contentLength > 0) {
41
+ onProgress?.(Math.round((received / contentLength) * 100));
42
+ }
43
+ }
44
+
45
+ const total = chunks.reduce((acc, c) => acc + c.length, 0);
46
+ const merged = new Uint8Array(total);
47
+ let offset = 0;
48
+ for (const chunk of chunks) {
49
+ merged.set(chunk, offset);
50
+ offset += chunk.length;
51
+ }
52
+
53
+ const checksum = await sha256Hex(merged);
54
+ if (checksum !== release.checksum) {
55
+ throw new Error(`Checksum mismatch: expected ${release.checksum}, got ${checksum}`);
56
+ }
57
+
58
+ const b64 = uint8ArrayToBase64(merged);
59
+ await fs.writeAsString(bundlePath, b64, 'base64');
60
+ onProgress?.(100);
61
+ }
62
+
63
+ return bundlePath;
64
+ }
65
+
66
+ async function sha256Hex(data: Uint8Array): Promise<string> {
67
+ const hashBuffer = await crypto.subtle.digest('SHA-256', data as unknown as ArrayBuffer);
68
+ const hashArray = Array.from(new Uint8Array(hashBuffer));
69
+ return hashArray.map((b) => b.toString(16).padStart(2, '0')).join('');
70
+ }
71
+
72
+ function uint8ArrayToBase64(bytes: Uint8Array): string {
73
+ let binary = '';
74
+ const len = bytes.byteLength;
75
+ for (let i = 0; i < len; i++) {
76
+ binary += String.fromCharCode(bytes[i]);
77
+ }
78
+ return btoa(binary);
79
+ }