@otaupdate/react-native 1.0.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 (57) hide show
  1. package/README.md +321 -0
  2. package/android/build.gradle +70 -0
  3. package/android/src/expo/java/com/otaupdate/OtaUpdateExpoPackage.kt +37 -0
  4. package/android/src/main/AndroidManifest.xml +3 -0
  5. package/android/src/main/java/com/otaupdate/OtaUpdate.kt +98 -0
  6. package/android/src/main/java/com/otaupdate/OtaUpdateInstaller.kt +211 -0
  7. package/android/src/main/java/com/otaupdate/OtaUpdateModule.kt +278 -0
  8. package/android/src/main/java/com/otaupdate/OtaUpdatePackage.kt +15 -0
  9. package/android/src/main/java/com/otaupdate/OtaUpdateStore.kt +268 -0
  10. package/app.plugin.js +3 -0
  11. package/expo-module.config.json +6 -0
  12. package/ios/OtaUpdate.h +46 -0
  13. package/ios/OtaUpdate.m +302 -0
  14. package/ios/OtaUpdateInstaller.h +25 -0
  15. package/ios/OtaUpdateInstaller.m +278 -0
  16. package/ios/OtaUpdateStore.h +69 -0
  17. package/ios/OtaUpdateStore.m +283 -0
  18. package/lib/OtaUpdate.d.ts +28 -0
  19. package/lib/OtaUpdate.d.ts.map +1 -0
  20. package/lib/OtaUpdate.js +254 -0
  21. package/lib/OtaUpdate.js.map +1 -0
  22. package/lib/api.d.ts +36 -0
  23. package/lib/api.d.ts.map +1 -0
  24. package/lib/api.js +89 -0
  25. package/lib/api.js.map +1 -0
  26. package/lib/index.d.ts +24 -0
  27. package/lib/index.d.ts.map +1 -0
  28. package/lib/index.js +37 -0
  29. package/lib/index.js.map +1 -0
  30. package/lib/native.d.ts +34 -0
  31. package/lib/native.d.ts.map +1 -0
  32. package/lib/native.js +34 -0
  33. package/lib/native.js.map +1 -0
  34. package/lib/types.d.ts +112 -0
  35. package/lib/types.d.ts.map +1 -0
  36. package/lib/types.js +27 -0
  37. package/lib/types.js.map +1 -0
  38. package/lib/useOtaUpdate.d.ts +17 -0
  39. package/lib/useOtaUpdate.d.ts.map +1 -0
  40. package/lib/useOtaUpdate.js +81 -0
  41. package/lib/useOtaUpdate.js.map +1 -0
  42. package/lib/withOtaUpdate.d.ts +10 -0
  43. package/lib/withOtaUpdate.d.ts.map +1 -0
  44. package/lib/withOtaUpdate.js +21 -0
  45. package/lib/withOtaUpdate.js.map +1 -0
  46. package/package.json +54 -0
  47. package/plugin/build/index.d.ts +11 -0
  48. package/plugin/build/index.js +97 -0
  49. package/react-native-ota-update.podspec +43 -0
  50. package/react-native.config.js +21 -0
  51. package/src/OtaUpdate.ts +293 -0
  52. package/src/api.ts +122 -0
  53. package/src/index.ts +55 -0
  54. package/src/native.ts +64 -0
  55. package/src/types.ts +125 -0
  56. package/src/useOtaUpdate.ts +96 -0
  57. package/src/withOtaUpdate.tsx +22 -0
package/lib/types.d.ts ADDED
@@ -0,0 +1,112 @@
1
+ /** When a downloaded update actually replaces the running JS bundle. */
2
+ export declare enum InstallMode {
3
+ /** Swap in on the next natural app start. Safest; the default. */
4
+ ON_NEXT_RESTART = 0,
5
+ /** Swap in the next time the app returns from the background. */
6
+ ON_NEXT_RESUME = 1,
7
+ /** Reload the JS bundle right now. Interrupts the user — use for mandatory fixes. */
8
+ IMMEDIATE = 2
9
+ }
10
+ /** Progress of a `sync()` call, reported through `onSyncStatusChange`. */
11
+ export declare enum SyncStatus {
12
+ UP_TO_DATE = 0,
13
+ UPDATE_INSTALLED = 1,
14
+ UPDATE_IGNORED = 2,
15
+ UNKNOWN_ERROR = 3,
16
+ SYNC_IN_PROGRESS = 4,
17
+ CHECKING_FOR_UPDATE = 5,
18
+ AWAITING_USER_ACTION = 6,
19
+ DOWNLOADING_PACKAGE = 7,
20
+ INSTALLING_UPDATE = 8
21
+ }
22
+ export interface DownloadProgress {
23
+ totalBytes: number;
24
+ receivedBytes: number;
25
+ }
26
+ /** Metadata about an update that exists on the server but not yet on device. */
27
+ export interface RemotePackage {
28
+ label: string;
29
+ packageHash: string;
30
+ downloadUrl: string;
31
+ size: number;
32
+ isMandatory: boolean;
33
+ description: string | null;
34
+ targetBinaryVersion: string;
35
+ rollout: number;
36
+ isRollback: boolean;
37
+ deployment: string;
38
+ /** Fetches and verifies the bundle, returning the on-device package. */
39
+ download(onProgress?: (progress: DownloadProgress) => void): Promise<LocalPackage>;
40
+ }
41
+ /** An update that has been downloaded and verified onto the device. */
42
+ export interface LocalPackage {
43
+ label: string;
44
+ packageHash: string;
45
+ isMandatory: boolean;
46
+ description: string | null;
47
+ bundlePath: string;
48
+ size: number;
49
+ /** Applies the update according to `mode`. */
50
+ install(mode?: InstallMode, minimumBackgroundDuration?: number): Promise<void>;
51
+ }
52
+ /** The bundle currently running, if it came from an OTA update. */
53
+ export interface CurrentPackage {
54
+ label: string | null;
55
+ packageHash: string | null;
56
+ description: string | null;
57
+ isMandatory: boolean;
58
+ bundlePath: string | null;
59
+ appVersion: string;
60
+ /** True until `notifyAppReady()` confirms this update booted successfully. */
61
+ isPending: boolean;
62
+ /** True on the first run after this update was applied. */
63
+ isFirstRun: boolean;
64
+ }
65
+ export type NoUpdateReason = 'up_to_date' | 'not_in_rollout' | 'no_matching_binary_version' | 'update_app_version';
66
+ export interface CheckResult {
67
+ update: RemotePackage | null;
68
+ reason?: NoUpdateReason;
69
+ /** Set when a newer *binary* is required — the fix is a store update, not OTA. */
70
+ updateAppVersion?: boolean;
71
+ }
72
+ export interface OtaConfiguration {
73
+ deploymentKey: string;
74
+ serverUrl: string;
75
+ appVersion: string;
76
+ clientUniqueId: string;
77
+ /** Hash/label of the bundle currently running, if it is an OTA package. */
78
+ packageHash: string | null;
79
+ label: string | null;
80
+ /**
81
+ * The app's real native bundle identifier (iOS `CFBundleIdentifier` /
82
+ * Android package name), read from the native runtime — not settable from
83
+ * JS. Sent on every update-check so the server can reject a deployment key
84
+ * used against an app it was not issued for.
85
+ */
86
+ bundleIdentifier: string;
87
+ }
88
+ export interface SyncOptions {
89
+ installMode?: InstallMode;
90
+ /** Install mode used when the release is flagged mandatory. */
91
+ mandatoryInstallMode?: InstallMode;
92
+ /** Seconds the app must stay backgrounded before an ON_NEXT_RESUME install applies. */
93
+ minimumBackgroundDuration?: number;
94
+ deploymentKey?: string;
95
+ onSyncStatusChange?: (status: SyncStatus) => void;
96
+ onDownloadProgress?: (progress: DownloadProgress) => void;
97
+ /**
98
+ * Return false to skip an update (e.g. after asking the user).
99
+ * Mandatory updates ignore the result.
100
+ */
101
+ shouldInstall?: (update: RemotePackage) => boolean | Promise<boolean>;
102
+ }
103
+ export interface OtaOptions {
104
+ /** Run a sync when the app starts. Default: true. */
105
+ checkOnAppStart?: boolean;
106
+ /** Run a sync each time the app returns to the foreground. Default: true. */
107
+ checkOnResume?: boolean;
108
+ /** Minimum seconds between automatic syncs. Default: 60. */
109
+ minimumSyncInterval?: number;
110
+ sync?: SyncOptions;
111
+ }
112
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,wEAAwE;AACxE,oBAAY,WAAW;IACrB,kEAAkE;IAClE,eAAe,IAAI;IACnB,iEAAiE;IACjE,cAAc,IAAI;IAClB,qFAAqF;IACrF,SAAS,IAAI;CACd;AAED,0EAA0E;AAC1E,oBAAY,UAAU;IACpB,UAAU,IAAI;IACd,gBAAgB,IAAI;IACpB,cAAc,IAAI;IAClB,aAAa,IAAI;IACjB,gBAAgB,IAAI;IACpB,mBAAmB,IAAI;IACvB,oBAAoB,IAAI;IACxB,mBAAmB,IAAI;IACvB,iBAAiB,IAAI;CACtB;AAED,MAAM,WAAW,gBAAgB;IAC/B,UAAU,EAAE,MAAM,CAAC;IACnB,aAAa,EAAE,MAAM,CAAC;CACvB;AAED,gFAAgF;AAChF,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,OAAO,CAAC;IACrB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,mBAAmB,EAAE,MAAM,CAAC;IAC5B,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,OAAO,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;IACnB,wEAAwE;IACxE,QAAQ,CAAC,UAAU,CAAC,EAAE,CAAC,QAAQ,EAAE,gBAAgB,KAAK,IAAI,GAAG,OAAO,CAAC,YAAY,CAAC,CAAC;CACpF;AAED,uEAAuE;AACvE,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,OAAO,CAAC;IACrB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,8CAA8C;IAC9C,OAAO,CAAC,IAAI,CAAC,EAAE,WAAW,EAAE,yBAAyB,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAChF;AAED,mEAAmE;AACnE,MAAM,WAAW,cAAc;IAC7B,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,WAAW,EAAE,OAAO,CAAC;IACrB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,UAAU,EAAE,MAAM,CAAC;IACnB,8EAA8E;IAC9E,SAAS,EAAE,OAAO,CAAC;IACnB,2DAA2D;IAC3D,UAAU,EAAE,OAAO,CAAC;CACrB;AAED,MAAM,MAAM,cAAc,GACtB,YAAY,GACZ,gBAAgB,GAChB,4BAA4B,GAC5B,oBAAoB,CAAC;AAEzB,MAAM,WAAW,WAAW;IAC1B,MAAM,EAAE,aAAa,GAAG,IAAI,CAAC;IAC7B,MAAM,CAAC,EAAE,cAAc,CAAC;IACxB,kFAAkF;IAClF,gBAAgB,CAAC,EAAE,OAAO,CAAC;CAC5B;AAED,MAAM,WAAW,gBAAgB;IAC/B,aAAa,EAAE,MAAM,CAAC;IACtB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,cAAc,EAAE,MAAM,CAAC;IACvB,2EAA2E;IAC3E,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB;;;;;OAKG;IACH,gBAAgB,EAAE,MAAM,CAAC;CAC1B;AAED,MAAM,WAAW,WAAW;IAC1B,WAAW,CAAC,EAAE,WAAW,CAAC;IAC1B,+DAA+D;IAC/D,oBAAoB,CAAC,EAAE,WAAW,CAAC;IACnC,uFAAuF;IACvF,yBAAyB,CAAC,EAAE,MAAM,CAAC;IACnC,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,kBAAkB,CAAC,EAAE,CAAC,MAAM,EAAE,UAAU,KAAK,IAAI,CAAC;IAClD,kBAAkB,CAAC,EAAE,CAAC,QAAQ,EAAE,gBAAgB,KAAK,IAAI,CAAC;IAC1D;;;OAGG;IACH,aAAa,CAAC,EAAE,CAAC,MAAM,EAAE,aAAa,KAAK,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;CACvE;AAED,MAAM,WAAW,UAAU;IACzB,qDAAqD;IACrD,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,6EAA6E;IAC7E,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,4DAA4D;IAC5D,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,IAAI,CAAC,EAAE,WAAW,CAAC;CACpB"}
package/lib/types.js ADDED
@@ -0,0 +1,27 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.SyncStatus = exports.InstallMode = void 0;
4
+ /** When a downloaded update actually replaces the running JS bundle. */
5
+ var InstallMode;
6
+ (function (InstallMode) {
7
+ /** Swap in on the next natural app start. Safest; the default. */
8
+ InstallMode[InstallMode["ON_NEXT_RESTART"] = 0] = "ON_NEXT_RESTART";
9
+ /** Swap in the next time the app returns from the background. */
10
+ InstallMode[InstallMode["ON_NEXT_RESUME"] = 1] = "ON_NEXT_RESUME";
11
+ /** Reload the JS bundle right now. Interrupts the user — use for mandatory fixes. */
12
+ InstallMode[InstallMode["IMMEDIATE"] = 2] = "IMMEDIATE";
13
+ })(InstallMode || (exports.InstallMode = InstallMode = {}));
14
+ /** Progress of a `sync()` call, reported through `onSyncStatusChange`. */
15
+ var SyncStatus;
16
+ (function (SyncStatus) {
17
+ SyncStatus[SyncStatus["UP_TO_DATE"] = 0] = "UP_TO_DATE";
18
+ SyncStatus[SyncStatus["UPDATE_INSTALLED"] = 1] = "UPDATE_INSTALLED";
19
+ SyncStatus[SyncStatus["UPDATE_IGNORED"] = 2] = "UPDATE_IGNORED";
20
+ SyncStatus[SyncStatus["UNKNOWN_ERROR"] = 3] = "UNKNOWN_ERROR";
21
+ SyncStatus[SyncStatus["SYNC_IN_PROGRESS"] = 4] = "SYNC_IN_PROGRESS";
22
+ SyncStatus[SyncStatus["CHECKING_FOR_UPDATE"] = 5] = "CHECKING_FOR_UPDATE";
23
+ SyncStatus[SyncStatus["AWAITING_USER_ACTION"] = 6] = "AWAITING_USER_ACTION";
24
+ SyncStatus[SyncStatus["DOWNLOADING_PACKAGE"] = 7] = "DOWNLOADING_PACKAGE";
25
+ SyncStatus[SyncStatus["INSTALLING_UPDATE"] = 8] = "INSTALLING_UPDATE";
26
+ })(SyncStatus || (exports.SyncStatus = SyncStatus = {}));
27
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":";;;AAAA,wEAAwE;AACxE,IAAY,WAOX;AAPD,WAAY,WAAW;IACrB,kEAAkE;IAClE,mEAAmB,CAAA;IACnB,iEAAiE;IACjE,iEAAkB,CAAA;IAClB,qFAAqF;IACrF,uDAAa,CAAA;AACf,CAAC,EAPW,WAAW,2BAAX,WAAW,QAOtB;AAED,0EAA0E;AAC1E,IAAY,UAUX;AAVD,WAAY,UAAU;IACpB,uDAAc,CAAA;IACd,mEAAoB,CAAA;IACpB,+DAAkB,CAAA;IAClB,6DAAiB,CAAA;IACjB,mEAAoB,CAAA;IACpB,yEAAuB,CAAA;IACvB,2EAAwB,CAAA;IACxB,yEAAuB,CAAA;IACvB,qEAAqB,CAAA;AACvB,CAAC,EAVW,UAAU,0BAAV,UAAU,QAUrB"}
@@ -0,0 +1,17 @@
1
+ import { SyncStatus, type DownloadProgress, type RemotePackage, type SyncOptions } from './types';
2
+ export interface UseOtaUpdateState {
3
+ status: SyncStatus;
4
+ progress: DownloadProgress | null;
5
+ /** Populated when a check found an update that has not been installed yet. */
6
+ available: RemotePackage | null;
7
+ error: Error | null;
8
+ isSyncing: boolean;
9
+ check(): Promise<void>;
10
+ update(options?: SyncOptions): Promise<SyncStatus>;
11
+ }
12
+ /**
13
+ * Hook for update UI — a "New version available" banner, a download progress
14
+ * bar, or a settings screen with a manual "Check for updates" button.
15
+ */
16
+ export declare function useOtaUpdate(defaultOptions?: SyncOptions): UseOtaUpdateState;
17
+ //# sourceMappingURL=useOtaUpdate.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useOtaUpdate.d.ts","sourceRoot":"","sources":["../src/useOtaUpdate.ts"],"names":[],"mappings":"AAEA,OAAO,EAEL,UAAU,EACV,KAAK,gBAAgB,EACrB,KAAK,aAAa,EAClB,KAAK,WAAW,EACjB,MAAM,SAAS,CAAC;AAEjB,MAAM,WAAW,iBAAiB;IAChC,MAAM,EAAE,UAAU,CAAC;IACnB,QAAQ,EAAE,gBAAgB,GAAG,IAAI,CAAC;IAClC,8EAA8E;IAC9E,SAAS,EAAE,aAAa,GAAG,IAAI,CAAC;IAChC,KAAK,EAAE,KAAK,GAAG,IAAI,CAAC;IACpB,SAAS,EAAE,OAAO,CAAC;IACnB,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACvB,MAAM,CAAC,OAAO,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC;CACpD;AAED;;;GAGG;AACH,wBAAgB,YAAY,CAAC,cAAc,GAAE,WAAgB,GAAG,iBAAiB,CAsEhF"}
@@ -0,0 +1,81 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.useOtaUpdate = useOtaUpdate;
4
+ const react_1 = require("react");
5
+ const OtaUpdate_1 = require("./OtaUpdate");
6
+ const types_1 = require("./types");
7
+ /**
8
+ * Hook for update UI — a "New version available" banner, a download progress
9
+ * bar, or a settings screen with a manual "Check for updates" button.
10
+ */
11
+ function useOtaUpdate(defaultOptions = {}) {
12
+ const [status, setStatus] = (0, react_1.useState)(types_1.SyncStatus.UP_TO_DATE);
13
+ const [progress, setProgress] = (0, react_1.useState)(null);
14
+ const [available, setAvailable] = (0, react_1.useState)(null);
15
+ const [error, setError] = (0, react_1.useState)(null);
16
+ const mounted = (0, react_1.useRef)(true);
17
+ (0, react_1.useEffect)(() => {
18
+ mounted.current = true;
19
+ return () => {
20
+ mounted.current = false;
21
+ };
22
+ }, []);
23
+ const check = (0, react_1.useCallback)(async () => {
24
+ try {
25
+ setError(null);
26
+ setStatus(types_1.SyncStatus.CHECKING_FOR_UPDATE);
27
+ const result = await (0, OtaUpdate_1.checkForUpdate)(defaultOptions.deploymentKey);
28
+ if (!mounted.current)
29
+ return;
30
+ setAvailable(result.update);
31
+ setStatus(result.update ? types_1.SyncStatus.AWAITING_USER_ACTION : types_1.SyncStatus.UP_TO_DATE);
32
+ }
33
+ catch (err) {
34
+ if (!mounted.current)
35
+ return;
36
+ setError(err);
37
+ setStatus(types_1.SyncStatus.UNKNOWN_ERROR);
38
+ }
39
+ // defaultOptions is intentionally read fresh on each call rather than
40
+ // captured as a dependency — callers routinely pass an inline object.
41
+ // eslint-disable-next-line react-hooks/exhaustive-deps
42
+ }, [defaultOptions.deploymentKey]);
43
+ const update = (0, react_1.useCallback)(async (options = {}) => {
44
+ setError(null);
45
+ const result = await (0, OtaUpdate_1.sync)({
46
+ installMode: types_1.InstallMode.ON_NEXT_RESTART,
47
+ ...defaultOptions,
48
+ ...options,
49
+ onSyncStatusChange: (s) => {
50
+ if (mounted.current)
51
+ setStatus(s);
52
+ defaultOptions.onSyncStatusChange?.(s);
53
+ options.onSyncStatusChange?.(s);
54
+ },
55
+ onDownloadProgress: (p) => {
56
+ if (mounted.current)
57
+ setProgress(p);
58
+ defaultOptions.onDownloadProgress?.(p);
59
+ options.onDownloadProgress?.(p);
60
+ },
61
+ });
62
+ if (mounted.current && result === types_1.SyncStatus.UPDATE_INSTALLED)
63
+ setAvailable(null);
64
+ return result;
65
+ },
66
+ // eslint-disable-next-line react-hooks/exhaustive-deps
67
+ []);
68
+ return {
69
+ status,
70
+ progress,
71
+ available,
72
+ error,
73
+ isSyncing: status === types_1.SyncStatus.CHECKING_FOR_UPDATE ||
74
+ status === types_1.SyncStatus.DOWNLOADING_PACKAGE ||
75
+ status === types_1.SyncStatus.INSTALLING_UPDATE ||
76
+ status === types_1.SyncStatus.SYNC_IN_PROGRESS,
77
+ check,
78
+ update,
79
+ };
80
+ }
81
+ //# sourceMappingURL=useOtaUpdate.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useOtaUpdate.js","sourceRoot":"","sources":["../src/useOtaUpdate.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,10 @@
1
+ import React from 'react';
2
+ import type { OtaOptions } from './types';
3
+ /**
4
+ * Wraps the root component so updates are checked on start and on resume, and
5
+ * the running bundle is confirmed (rollback-protection) as soon as it mounts.
6
+ *
7
+ * export default withOtaUpdate(App, { sync: { installMode: InstallMode.ON_NEXT_RESUME } })
8
+ */
9
+ export declare function withOtaUpdate<P extends object>(Component: React.ComponentType<P>, options?: OtaOptions): React.ComponentType<P>;
10
+ //# sourceMappingURL=withOtaUpdate.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"withOtaUpdate.d.ts","sourceRoot":"","sources":["../src/withOtaUpdate.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAoB,MAAM,OAAO,CAAC;AAEzC,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAE1C;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,CAAC,SAAS,MAAM,EAC5C,SAAS,EAAE,KAAK,CAAC,aAAa,CAAC,CAAC,CAAC,EACjC,OAAO,GAAE,UAAe,GACvB,KAAK,CAAC,aAAa,CAAC,CAAC,CAAC,CAQxB"}
@@ -0,0 +1,21 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.withOtaUpdate = withOtaUpdate;
4
+ const jsx_runtime_1 = require("react/jsx-runtime");
5
+ const react_1 = require("react");
6
+ const OtaUpdate_1 = require("./OtaUpdate");
7
+ /**
8
+ * Wraps the root component so updates are checked on start and on resume, and
9
+ * the running bundle is confirmed (rollback-protection) as soon as it mounts.
10
+ *
11
+ * export default withOtaUpdate(App, { sync: { installMode: InstallMode.ON_NEXT_RESUME } })
12
+ */
13
+ function withOtaUpdate(Component, options = {}) {
14
+ const Wrapped = (props) => {
15
+ (0, react_1.useEffect)(() => (0, OtaUpdate_1.startAutoSync)(options), []);
16
+ return (0, jsx_runtime_1.jsx)(Component, { ...props });
17
+ };
18
+ Wrapped.displayName = `withOtaUpdate(${Component.displayName ?? Component.name ?? 'Component'})`;
19
+ return Wrapped;
20
+ }
21
+ //# sourceMappingURL=withOtaUpdate.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"withOtaUpdate.js","sourceRoot":"","sources":["../src/withOtaUpdate.tsx"],"names":[],"mappings":";;AAUA,sCAWC;;AArBD,iCAAyC;AACzC,2CAA4C;AAG5C;;;;;GAKG;AACH,SAAgB,aAAa,CAC3B,SAAiC,EACjC,UAAsB,EAAE;IAExB,MAAM,OAAO,GAAgB,CAAC,KAAK,EAAE,EAAE;QACrC,IAAA,iBAAS,EAAC,GAAG,EAAE,CAAC,IAAA,yBAAa,EAAC,OAAO,CAAC,EAAE,EAAE,CAAC,CAAC;QAC5C,OAAO,uBAAC,SAAS,OAAK,KAAK,GAAI,CAAC;IAClC,CAAC,CAAC;IAEF,OAAO,CAAC,WAAW,GAAG,iBAAiB,SAAS,CAAC,WAAW,IAAI,SAAS,CAAC,IAAI,IAAI,WAAW,GAAG,CAAC;IACjG,OAAO,OAAO,CAAC;AACjB,CAAC"}
package/package.json ADDED
@@ -0,0 +1,54 @@
1
+ {
2
+ "name": "@otaupdate/react-native",
3
+ "version": "1.0.0",
4
+ "description": "Over-the-air JS bundle updates for React Native \u2014 bare and Expo",
5
+ "license": "MIT",
6
+ "publishConfig": {
7
+ "access": "public"
8
+ },
9
+ "main": "lib/index.js",
10
+ "types": "lib/index.d.ts",
11
+ "react-native": "src/index.ts",
12
+ "source": "src/index.ts",
13
+ "app.plugin.js": "app.plugin.js",
14
+ "files": [
15
+ "lib",
16
+ "src",
17
+ "android",
18
+ "ios",
19
+ "plugin/build",
20
+ "expo-module.config.json",
21
+ "react-native.config.js",
22
+ "app.plugin.js",
23
+ "react-native-ota-update.podspec",
24
+ "README.md"
25
+ ],
26
+ "scripts": {
27
+ "build": "tsc -p tsconfig.json && tsc -p plugin/tsconfig.json",
28
+ "typecheck": "tsc -p tsconfig.json --noEmit && tsc -p plugin/tsconfig.json --noEmit",
29
+ "prepublishOnly": "npm run build"
30
+ },
31
+ "keywords": [
32
+ "react-native",
33
+ "ota",
34
+ "codepush",
35
+ "hot-update",
36
+ "expo"
37
+ ],
38
+ "peerDependencies": {
39
+ "react": ">=18.0.0",
40
+ "react-native": ">=0.71.0"
41
+ },
42
+ "devDependencies": {
43
+ "@expo/config-plugins": "^9.0.0",
44
+ "@types/react": "^18.3.18",
45
+ "react": "18.3.1",
46
+ "react-native": "0.76.6",
47
+ "typescript": "^5.7.3"
48
+ },
49
+ "peerDependenciesMeta": {
50
+ "expo": {
51
+ "optional": true
52
+ }
53
+ }
54
+ }
@@ -0,0 +1,11 @@
1
+ import { ConfigPlugin } from '@expo/config-plugins';
2
+ export interface OtaPluginOptions {
3
+ /** Server that serves updates, e.g. https://ota.example.com */
4
+ serverUrl: string;
5
+ /** Deployment key used on both platforms unless overridden below. */
6
+ deploymentKey?: string;
7
+ iosDeploymentKey?: string;
8
+ androidDeploymentKey?: string;
9
+ }
10
+ declare const _default: ConfigPlugin<OtaPluginOptions>;
11
+ export default _default;
@@ -0,0 +1,97 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const config_plugins_1 = require("@expo/config-plugins");
4
+ const PACKAGE_NAME = '@otaupdate/react-native';
5
+ function keysFor(options) {
6
+ const ios = options.iosDeploymentKey ?? options.deploymentKey;
7
+ const android = options.androidDeploymentKey ?? options.deploymentKey;
8
+ if (!ios || !android) {
9
+ throw new Error(`[${PACKAGE_NAME}] Missing deployment key. Provide "deploymentKey", or both ` +
10
+ '"iosDeploymentKey" and "androidDeploymentKey", in the plugin options.\n\n' +
11
+ ' ["@otaupdate/react-native", { "serverUrl": "https://ota.example.com", ' +
12
+ '"iosDeploymentKey": "…", "androidDeploymentKey": "…" }]');
13
+ }
14
+ return { ios, android };
15
+ }
16
+ // --- iOS ---------------------------------------------------------------------
17
+ const withOtaInfoPlist = (config, options) => (0, config_plugins_1.withInfoPlist)(config, (mod) => {
18
+ mod.modResults.OtaDeploymentKey = keysFor(options).ios;
19
+ mod.modResults.OtaServerUrl = options.serverUrl;
20
+ return mod;
21
+ });
22
+ /**
23
+ * Points React Native at the OTA bundle. Handles both AppDelegate flavours:
24
+ * Swift (Expo SDK 52+) and Objective-C++ (SDK 51 and earlier).
25
+ */
26
+ const withOtaAppDelegate = (config) => (0, config_plugins_1.withAppDelegate)(config, (mod) => {
27
+ const { language } = mod.modResults;
28
+ let contents = mod.modResults.contents;
29
+ if (language === 'swift') {
30
+ if (!contents.includes('import OtaUpdate')) {
31
+ contents = contents.replace(/(import\s+React[^\n]*\n)/, `$1import OtaUpdate\n`);
32
+ if (!contents.includes('import OtaUpdate')) {
33
+ // Fall back to inserting after the first import of any kind.
34
+ contents = contents.replace(/^(import [^\n]+\n)/m, `$1import OtaUpdate\n`);
35
+ }
36
+ }
37
+ if (!contents.includes('OtaUpdate.bundleURL()')) {
38
+ const replaced = contents.replace(/Bundle\.main\.url\(forResource:\s*"main",\s*withExtension:\s*"jsbundle"\)/g, 'OtaUpdate.bundleURL()');
39
+ if (replaced === contents) {
40
+ throw new Error(`[${PACKAGE_NAME}] Could not patch AppDelegate.swift — the release bundle lookup ` +
41
+ 'was not found. Add `return OtaUpdate.bundleURL()` to bundleURL() manually.');
42
+ }
43
+ contents = replaced;
44
+ }
45
+ }
46
+ else {
47
+ if (!contents.includes('#import <OtaUpdate/OtaUpdate.h>')) {
48
+ contents = contents.replace(/(#import\s+"AppDelegate\.h"\n)/, `$1#import <OtaUpdate/OtaUpdate.h>\n`);
49
+ if (!contents.includes('#import <OtaUpdate/OtaUpdate.h>')) {
50
+ contents = `#import <OtaUpdate/OtaUpdate.h>\n${contents}`;
51
+ }
52
+ }
53
+ if (!contents.includes('[OtaUpdate bundleURL]')) {
54
+ const replaced = contents.replace(/\[\[NSBundle mainBundle\] URLForResource:@"main" withExtension:@"jsbundle"\]/g, '[OtaUpdate bundleURL]');
55
+ if (replaced === contents) {
56
+ throw new Error(`[${PACKAGE_NAME}] Could not patch AppDelegate.mm — the release bundle lookup ` +
57
+ 'was not found. Return `[OtaUpdate bundleURL]` from getBundleURL manually.');
58
+ }
59
+ contents = replaced;
60
+ }
61
+ }
62
+ mod.modResults.contents = contents;
63
+ return mod;
64
+ });
65
+ // --- Android -----------------------------------------------------------------
66
+ const withOtaStrings = (config, options) => (0, config_plugins_1.withStringsXml)(config, (mod) => {
67
+ const { android } = keysFor(options);
68
+ mod.modResults = config_plugins_1.AndroidConfig.Strings.setStringItem([
69
+ // translatable=false keeps these out of localisation exports.
70
+ { $: { name: 'ota_deployment_key', translatable: 'false' }, _: android },
71
+ { $: { name: 'ota_server_url', translatable: 'false' }, _: options.serverUrl },
72
+ ], mod.modResults);
73
+ return mod;
74
+ });
75
+ // Note: there is deliberately no MainApplication patch on Android.
76
+ //
77
+ // Expo apps are bridgeless — MainApplication builds a ReactHost directly from
78
+ // ExpoReactHostFactory and exposes no ReactNativeHost to override. The bundle
79
+ // path is instead supplied by OtaUpdateExpoPackage, which registers a
80
+ // ReactNativeHostHandler (Expo's own extension point, the same one
81
+ // expo-updates uses) and is picked up by Expo autolinking.
82
+ //
83
+ // Rewriting Kotlin source here would break every time the RN/Expo template
84
+ // changes — it already did, against the RN 0.86 template.
85
+ // --- Entry point --------------------------------------------------------------
86
+ const withOtaUpdate = (config, options) => {
87
+ if (!options?.serverUrl) {
88
+ throw new Error(`[${PACKAGE_NAME}] "serverUrl" is required.\n\n` +
89
+ ' ["@otaupdate/react-native", { "serverUrl": "https://ota.example.com", "deploymentKey": "…" }]');
90
+ }
91
+ keysFor(options); // fail fast at config time rather than at build time
92
+ config = withOtaInfoPlist(config, options);
93
+ config = withOtaAppDelegate(config);
94
+ config = withOtaStrings(config, options);
95
+ return config;
96
+ };
97
+ exports.default = (0, config_plugins_1.createRunOncePlugin)(withOtaUpdate, PACKAGE_NAME, '1.0.0');
@@ -0,0 +1,43 @@
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-update"
7
+ # module_name is what `import OtaUpdate` (Swift) and
8
+ # `#import <OtaUpdate/OtaUpdate.h>` (ObjC) resolve to in the host app.
9
+ s.module_name = "OtaUpdate"
10
+ s.header_dir = "OtaUpdate"
11
+ s.version = package["version"]
12
+ s.summary = package["description"]
13
+ s.license = package["license"]
14
+ s.authors = { "OTA Platform" => "noreply@example.com" }
15
+ s.homepage = "https://github.com/your-org/ota-platform"
16
+ s.platforms = { :ios => "13.4" }
17
+ s.source = { :git => "https://github.com/your-org/ota-platform.git", :tag => "v#{s.version}" }
18
+
19
+ s.source_files = "ios/**/*.{h,m,mm}"
20
+ s.public_header_files = "ios/OtaUpdate.h"
21
+ s.requires_arc = true
22
+
23
+ # Required for `import OtaUpdate` from a Swift AppDelegate.
24
+ #
25
+ # React Native and Expo build pods as static libraries by default, and an
26
+ # Objective-C static library is NOT importable as a Swift module unless
27
+ # CocoaPods emits a module map for it. DEFINES_MODULE makes it do that,
28
+ # generating `module OtaUpdate` from `module_name` above. Without this the
29
+ # host app fails to compile with "no such module 'OtaUpdate'" — even though
30
+ # the pod links fine and the ObjC side is completely healthy.
31
+ s.pod_target_xcconfig = { "DEFINES_MODULE" => "YES" }
32
+
33
+ # Unzipping the release package. SSZipArchive is the same dependency
34
+ # react-native-code-push uses, so most projects already resolve it cleanly.
35
+ s.dependency "SSZipArchive", "~> 2.4"
36
+
37
+ if respond_to?(:install_modules_dependencies, true)
38
+ # RN >= 0.71 helper: wires up React-Core plus the new-architecture deps.
39
+ install_modules_dependencies(s)
40
+ else
41
+ s.dependency "React-Core"
42
+ end
43
+ end
@@ -0,0 +1,21 @@
1
+ // This package is linked by BOTH linkers, and needs to be:
2
+ //
3
+ // - React Native autolinking registers `OtaUpdatePackage`, the ReactPackage
4
+ // that exposes the `OtaUpdate` native module to JS.
5
+ // - Expo autolinking registers `OtaUpdateExpoPackage`, which supplies the
6
+ // downloaded bundle path via `ReactNativeHostHandler`.
7
+ //
8
+ // Expo's resolver deliberately drops a package from React Native autolinking
9
+ // when it is *also* an Expo module with its own Gradle file and no explicit
10
+ // React Native config — the two would otherwise race to link the same Gradle
11
+ // project. Declaring the config here opts out of that skip, so the native
12
+ // module still reaches JS. Without this file, `NativeModules.OtaUpdate` is
13
+ // undefined in Expo apps and every SDK call throws the linking error.
14
+ module.exports = {
15
+ dependency: {
16
+ platforms: {
17
+ android: {},
18
+ ios: {},
19
+ },
20
+ },
21
+ };