@hot-updater/react-native 0.17.0 → 0.18.1

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 (138) hide show
  1. package/HotUpdater.podspec +7 -11
  2. package/android/src/main/java/com/hotupdater/BundleFileStorageService.kt +200 -0
  3. package/android/src/main/java/com/hotupdater/FileManagerService.kt +104 -0
  4. package/android/src/main/java/com/hotupdater/HotUpdater.kt +62 -305
  5. package/android/src/main/java/com/hotupdater/HotUpdaterFactory.kt +49 -0
  6. package/android/src/main/java/com/hotupdater/HotUpdaterImpl.kt +176 -0
  7. package/android/src/main/java/com/hotupdater/HttpDownloadService.kt +98 -0
  8. package/android/src/main/java/com/hotupdater/VersionedPreferencesService.kt +69 -0
  9. package/android/src/main/java/com/hotupdater/ZipFileUnzipService.kt +52 -0
  10. package/android/src/newarch/HotUpdaterModule.kt +31 -34
  11. package/android/src/oldarch/HotUpdaterModule.kt +32 -34
  12. package/android/src/oldarch/HotUpdaterSpec.kt +2 -9
  13. package/ios/HotUpdater/Internal/BundleFileStorageService.swift +593 -0
  14. package/ios/HotUpdater/Internal/FileManagerService.swift +97 -0
  15. package/ios/HotUpdater/Internal/HotUpdater-Bridging-Header.h +8 -0
  16. package/ios/HotUpdater/Internal/HotUpdater.mm +241 -0
  17. package/ios/HotUpdater/Internal/HotUpdaterFactory.swift +24 -0
  18. package/ios/HotUpdater/Internal/HotUpdaterImpl.swift +143 -0
  19. package/ios/HotUpdater/Internal/NotificationExtension.swift +6 -0
  20. package/ios/HotUpdater/Internal/SSZipArchiveUnzipService.swift +25 -0
  21. package/ios/HotUpdater/Internal/URLSessionDownloadService.swift +101 -0
  22. package/ios/HotUpdater/Internal/VersionedPreferencesService.swift +82 -0
  23. package/ios/HotUpdater/Public/HotUpdater.h +29 -0
  24. package/lib/commonjs/checkForUpdate.js +70 -0
  25. package/lib/commonjs/checkForUpdate.js.map +1 -0
  26. package/lib/commonjs/error.js +14 -0
  27. package/lib/commonjs/error.js.map +1 -0
  28. package/lib/commonjs/fetchUpdateInfo.js +74 -0
  29. package/lib/commonjs/fetchUpdateInfo.js.map +1 -0
  30. package/lib/commonjs/hooks/useEventCallback.js +17 -0
  31. package/lib/commonjs/hooks/useEventCallback.js.map +1 -0
  32. package/lib/commonjs/index.js +234 -0
  33. package/lib/commonjs/index.js.map +1 -0
  34. package/lib/commonjs/native.js +132 -0
  35. package/lib/commonjs/native.js.map +1 -0
  36. package/lib/commonjs/package.json +1 -0
  37. package/lib/commonjs/runUpdateProcess.js +69 -0
  38. package/lib/commonjs/runUpdateProcess.js.map +1 -0
  39. package/lib/commonjs/specs/NativeHotUpdater.js +9 -0
  40. package/lib/commonjs/specs/NativeHotUpdater.js.map +1 -0
  41. package/lib/commonjs/store.js +48 -0
  42. package/lib/commonjs/store.js.map +1 -0
  43. package/lib/commonjs/wrap.js +98 -0
  44. package/lib/commonjs/wrap.js.map +1 -0
  45. package/lib/module/checkForUpdate.js +64 -0
  46. package/lib/module/checkForUpdate.js.map +1 -0
  47. package/lib/module/error.js +9 -0
  48. package/lib/module/error.js.map +1 -0
  49. package/lib/module/fetchUpdateInfo.js +69 -0
  50. package/lib/module/fetchUpdateInfo.js.map +1 -0
  51. package/lib/module/hooks/useEventCallback.js +13 -0
  52. package/lib/module/hooks/useEventCallback.js.map +1 -0
  53. package/lib/module/index.js +211 -0
  54. package/lib/module/index.js.map +1 -0
  55. package/lib/module/native.js +119 -0
  56. package/lib/module/native.js.map +1 -0
  57. package/lib/module/package.json +1 -0
  58. package/lib/module/runUpdateProcess.js +64 -0
  59. package/lib/module/runUpdateProcess.js.map +1 -0
  60. package/lib/module/specs/NativeHotUpdater.js +5 -0
  61. package/lib/module/specs/NativeHotUpdater.js.map +1 -0
  62. package/lib/module/store.js +42 -0
  63. package/lib/module/store.js.map +1 -0
  64. package/lib/module/wrap.js +94 -0
  65. package/lib/module/wrap.js.map +1 -0
  66. package/lib/typescript/commonjs/checkForUpdate.d.ts +22 -0
  67. package/lib/typescript/commonjs/checkForUpdate.d.ts.map +1 -0
  68. package/{dist → lib/typescript/commonjs}/error.d.ts +1 -0
  69. package/lib/typescript/commonjs/error.d.ts.map +1 -0
  70. package/lib/typescript/commonjs/fetchUpdateInfo.d.ts +4 -0
  71. package/lib/typescript/commonjs/fetchUpdateInfo.d.ts.map +1 -0
  72. package/{dist → lib/typescript/commonjs}/hooks/useEventCallback.d.ts +1 -0
  73. package/lib/typescript/commonjs/hooks/useEventCallback.d.ts.map +1 -0
  74. package/{dist → lib/typescript/commonjs}/index.d.ts +38 -12
  75. package/lib/typescript/commonjs/index.d.ts.map +1 -0
  76. package/lib/typescript/commonjs/native.d.ts +64 -0
  77. package/lib/typescript/commonjs/native.d.ts.map +1 -0
  78. package/lib/typescript/commonjs/package.json +1 -0
  79. package/{dist → lib/typescript/commonjs}/runUpdateProcess.d.ts +1 -0
  80. package/lib/typescript/commonjs/runUpdateProcess.d.ts.map +1 -0
  81. package/{dist → lib/typescript/commonjs}/specs/NativeHotUpdater.d.ts +8 -9
  82. package/lib/typescript/commonjs/specs/NativeHotUpdater.d.ts.map +1 -0
  83. package/{dist → lib/typescript/commonjs}/store.d.ts +1 -0
  84. package/lib/typescript/commonjs/store.d.ts.map +1 -0
  85. package/{dist → lib/typescript/commonjs}/wrap.d.ts +3 -2
  86. package/lib/typescript/commonjs/wrap.d.ts.map +1 -0
  87. package/lib/typescript/module/checkForUpdate.d.ts +22 -0
  88. package/lib/typescript/module/checkForUpdate.d.ts.map +1 -0
  89. package/lib/typescript/module/error.d.ts +4 -0
  90. package/lib/typescript/module/error.d.ts.map +1 -0
  91. package/lib/typescript/module/fetchUpdateInfo.d.ts +4 -0
  92. package/lib/typescript/module/fetchUpdateInfo.d.ts.map +1 -0
  93. package/lib/typescript/module/hooks/useEventCallback.d.ts +5 -0
  94. package/lib/typescript/module/hooks/useEventCallback.d.ts.map +1 -0
  95. package/lib/typescript/module/index.d.ts +202 -0
  96. package/lib/typescript/module/index.d.ts.map +1 -0
  97. package/lib/typescript/module/native.d.ts +64 -0
  98. package/lib/typescript/module/native.d.ts.map +1 -0
  99. package/lib/typescript/module/package.json +1 -0
  100. package/lib/typescript/module/runUpdateProcess.d.ts +49 -0
  101. package/lib/typescript/module/runUpdateProcess.d.ts.map +1 -0
  102. package/lib/typescript/module/specs/NativeHotUpdater.d.ts +19 -0
  103. package/lib/typescript/module/specs/NativeHotUpdater.d.ts.map +1 -0
  104. package/lib/typescript/module/store.d.ts +11 -0
  105. package/lib/typescript/module/store.d.ts.map +1 -0
  106. package/lib/typescript/module/wrap.d.ts +51 -0
  107. package/lib/typescript/module/wrap.d.ts.map +1 -0
  108. package/package.json +59 -30
  109. package/src/checkForUpdate.ts +59 -9
  110. package/src/fetchUpdateInfo.ts +40 -12
  111. package/src/index.ts +37 -11
  112. package/src/native.ts +87 -41
  113. package/src/runUpdateProcess.ts +2 -2
  114. package/src/specs/NativeHotUpdater.ts +8 -10
  115. package/src/wrap.tsx +9 -13
  116. package/android/generated/java/com/hotupdater/NativeHotUpdaterSpec.java +0 -93
  117. package/android/generated/jni/CMakeLists.txt +0 -36
  118. package/android/generated/jni/HotUpdaterSpec-generated.cpp +0 -68
  119. package/android/generated/jni/HotUpdaterSpec.h +0 -31
  120. package/android/generated/jni/react/renderer/components/HotUpdaterSpec/HotUpdaterSpecJSI-generated.cpp +0 -70
  121. package/android/generated/jni/react/renderer/components/HotUpdaterSpec/HotUpdaterSpecJSI.h +0 -121
  122. package/android/src/main/java/com/hotupdater/HotUpdaterPrefs.kt +0 -42
  123. package/dist/checkForUpdate.d.ts +0 -12
  124. package/dist/fetchUpdateInfo.d.ts +0 -3
  125. package/dist/index.js +0 -341
  126. package/dist/index.mjs +0 -301
  127. package/dist/native.d.ts +0 -41
  128. package/ios/HotUpdater/HotUpdater.h +0 -15
  129. package/ios/HotUpdater/HotUpdater.mm +0 -468
  130. package/ios/HotUpdater/HotUpdater.modulemap +0 -6
  131. package/ios/HotUpdater/HotUpdaterPrefs.h +0 -9
  132. package/ios/HotUpdater/HotUpdaterPrefs.mm +0 -45
  133. package/ios/generated/HotUpdaterSpec/HotUpdaterSpec-generated.mm +0 -81
  134. package/ios/generated/HotUpdaterSpec/HotUpdaterSpec.h +0 -112
  135. package/ios/generated/HotUpdaterSpecJSI-generated.cpp +0 -70
  136. package/ios/generated/HotUpdaterSpecJSI.h +0 -121
  137. package/react-native.config.js +0 -12
  138. package/src/global.d.ts +0 -3
@@ -1,11 +1,14 @@
1
+ import type { AppUpdateInfo, GetBundlesArgs } from "@hot-updater/core";
1
2
  import { Platform } from "react-native";
2
3
  import { HotUpdaterError } from "./error";
3
4
  import { type UpdateSource, fetchUpdateInfo } from "./fetchUpdateInfo";
4
5
  import {
6
+ HotUpdaterConstants,
5
7
  getAppVersion,
6
8
  getBundleId,
7
9
  getChannel,
8
10
  getMinBundleId,
11
+ updateBundle,
9
12
  } from "./native";
10
13
 
11
14
  export interface CheckForUpdateOptions {
@@ -19,7 +22,17 @@ export interface CheckForUpdateOptions {
19
22
  requestTimeout?: number;
20
23
  }
21
24
 
22
- export async function checkForUpdate(options: CheckForUpdateOptions) {
25
+ export type CheckForUpdateResult = AppUpdateInfo & {
26
+ /**
27
+ * Updates the bundle.
28
+ * This method is equivalent to `HotUpdater.updateBundle()` but with all required arguments pre-filled.
29
+ */
30
+ updateBundle: () => Promise<boolean>;
31
+ };
32
+
33
+ export async function checkForUpdate(
34
+ options: CheckForUpdateOptions,
35
+ ): Promise<CheckForUpdateResult | null> {
23
36
  if (__DEV__) {
24
37
  return null;
25
38
  }
@@ -42,17 +55,54 @@ export async function checkForUpdate(options: CheckForUpdateOptions) {
42
55
  return null;
43
56
  }
44
57
 
58
+ const baseArgs = {
59
+ bundleId: currentBundleId,
60
+ platform,
61
+ minBundleId,
62
+ channel: channel ?? undefined,
63
+ };
64
+
45
65
  return fetchUpdateInfo(
46
66
  options.source,
47
- {
48
- appVersion: currentAppVersion,
49
- bundleId: currentBundleId,
50
- platform,
51
- minBundleId,
52
- channel: channel ?? undefined,
53
- },
67
+ HotUpdaterConstants.UPDATE_STRATEGY === "appVersion"
68
+ ? {
69
+ _updateStrategy: HotUpdaterConstants.UPDATE_STRATEGY,
70
+ appVersion: currentAppVersion,
71
+ ...baseArgs,
72
+ }
73
+ : {
74
+ _updateStrategy: HotUpdaterConstants.UPDATE_STRATEGY,
75
+ fingerprintHash: HotUpdaterConstants.FINGERPRINT_HASH!,
76
+ ...baseArgs,
77
+ },
54
78
  options.requestHeaders,
55
79
  options.onError,
56
80
  options.requestTimeout,
57
- );
81
+ ).then((updateInfo) => {
82
+ if (!updateInfo) {
83
+ return null;
84
+ }
85
+
86
+ return {
87
+ ...updateInfo,
88
+ updateBundle: async () => {
89
+ return updateBundle({
90
+ bundleId: updateInfo.id,
91
+ fileUrl: updateInfo.fileUrl,
92
+ status: updateInfo.status,
93
+ });
94
+ },
95
+ };
96
+ });
58
97
  }
98
+
99
+ export const getUpdateSource = (baseUrl: string) => (args: GetBundlesArgs) => {
100
+ switch (args._updateStrategy) {
101
+ case "appVersion":
102
+ return `${baseUrl}/app-version/${args.platform}/${args.appVersion}/${args.channel}/${args.minBundleId}/${args.bundleId}`;
103
+ case "fingerprint":
104
+ return `${baseUrl}/fingerprint/${args.platform}/${args.fingerprintHash}/${args.channel}/${args.minBundleId}/${args.bundleId}`;
105
+ default:
106
+ return baseUrl;
107
+ }
108
+ };
@@ -1,16 +1,23 @@
1
1
  import type { AppUpdateInfo, GetBundlesArgs } from "@hot-updater/core";
2
2
 
3
- export type UpdateSource = string | (() => Promise<AppUpdateInfo | null>);
3
+ export type UpdateSource =
4
+ | string
5
+ | ((args: GetBundlesArgs) => Promise<AppUpdateInfo | null>)
6
+ | ((args: GetBundlesArgs) => string);
4
7
 
5
8
  export const fetchUpdateInfo = async (
6
9
  source: UpdateSource,
7
- { appVersion, bundleId, platform, minBundleId, channel }: GetBundlesArgs,
10
+ args: GetBundlesArgs,
8
11
  requestHeaders?: Record<string, string>,
9
12
  onError?: (error: Error) => void,
10
13
  requestTimeout = 5000,
11
14
  ): Promise<AppUpdateInfo | null> => {
12
15
  if (typeof source === "function") {
13
- return source();
16
+ const url = source(args);
17
+ if (typeof url !== "string") {
18
+ return null;
19
+ }
20
+ source = url;
14
21
  }
15
22
 
16
23
  const controller = new AbortController();
@@ -19,17 +26,38 @@ export const fetchUpdateInfo = async (
19
26
  }, requestTimeout);
20
27
 
21
28
  try {
29
+ let headers: Record<string, string> = {};
30
+
31
+ switch (args._updateStrategy) {
32
+ case "fingerprint":
33
+ headers = {
34
+ "Content-Type": "application/json",
35
+ "x-app-platform": args.platform,
36
+ "x-bundle-id": args.bundleId,
37
+ "x-fingerprint-hash": args.fingerprintHash,
38
+ ...(args.minBundleId ? { "x-min-bundle-id": args.minBundleId } : {}),
39
+ ...(args.channel ? { "x-channel": args.channel } : {}),
40
+ ...requestHeaders,
41
+ };
42
+ break;
43
+ case "appVersion":
44
+ headers = {
45
+ "Content-Type": "application/json",
46
+ "x-app-platform": args.platform,
47
+ "x-bundle-id": args.bundleId,
48
+ "x-app-version": args.appVersion,
49
+ ...(args.minBundleId ? { "x-min-bundle-id": args.minBundleId } : {}),
50
+ ...(args.channel ? { "x-channel": args.channel } : {}),
51
+ ...requestHeaders,
52
+ };
53
+ break;
54
+ default:
55
+ throw new Error("Invalid update strategy");
56
+ }
57
+
22
58
  const response = await fetch(source, {
23
59
  signal: controller.signal,
24
- headers: {
25
- "Content-Type": "application/json",
26
- "x-app-platform": platform,
27
- "x-app-version": appVersion,
28
- "x-bundle-id": bundleId,
29
- ...(minBundleId ? { "x-min-bundle-id": minBundleId } : {}),
30
- ...(channel ? { "x-channel": channel } : {}),
31
- ...requestHeaders,
32
- },
60
+ headers,
33
61
  });
34
62
 
35
63
  clearTimeout(timeoutId);
package/src/index.ts CHANGED
@@ -4,9 +4,10 @@ import {
4
4
  getAppVersion,
5
5
  getBundleId,
6
6
  getChannel,
7
+ getFingerprintHash,
7
8
  getMinBundleId,
9
+ getReleaseChannel,
8
10
  reload,
9
- setChannel,
10
11
  updateBundle,
11
12
  } from "./native";
12
13
  import { runUpdateProcess } from "./runUpdateProcess";
@@ -65,13 +66,12 @@ export const HotUpdater = {
65
66
  */
66
67
  getMinBundleId,
67
68
  /**
68
- * Fetches the current release channel of the app.
69
+ * Fetches the current channel of the app.
69
70
  *
70
- * By default, if no channel is specified, the app is assigned to the 'production' channel.
71
- * If the app is running in development mode, the channel will be `null`.
72
- *
73
- * @returns {string | null} The current release channel of the app
71
+ * If no channel is specified, the app is assigned to the 'production' channel.
74
72
  *
73
+ * @returns {string} The current release channel of the app
74
+ * @default "production"
75
75
  * @example
76
76
  * ```ts
77
77
  * const channel = HotUpdater.getChannel();
@@ -80,9 +80,17 @@ export const HotUpdater = {
80
80
  */
81
81
  getChannel,
82
82
  /**
83
- * Sets the channel for the app.
83
+ * The initial channel of the native app.
84
+ *
85
+ * @returns {string} The current release channel of the app
86
+ * @default "production"
87
+ * @example
88
+ * ```ts
89
+ * const channel = HotUpdater.getReleaseChannel();
90
+ * console.log(`Current release channel: ${channel}`);
91
+ * ```
84
92
  */
85
- setChannel,
93
+ getReleaseChannel,
86
94
  /**
87
95
  * Adds a listener to HotUpdater events.
88
96
  *
@@ -167,8 +175,9 @@ export const HotUpdater = {
167
175
  /**
168
176
  * Updates the bundle of the app.
169
177
  *
170
- * @param {string} bundleId - The bundle ID of the app
171
- * @param {string} zipUrl - The URL of the zip file
178
+ * @param {UpdateBundleParams} params - Parameters object required for bundle update
179
+ * @param {string} params.bundleId - The bundle ID of the app
180
+ * @param {string|null} params.fileUrl - The URL of the zip file
172
181
  *
173
182
  * @returns {Promise<boolean>} Whether the update was successful
174
183
  *
@@ -187,11 +196,28 @@ export const HotUpdater = {
187
196
  * };
188
197
  * }
189
198
  *
190
- * await HotUpdater.updateBundle(updateInfo.id, updateInfo.fileUrl);
199
+ * await HotUpdater.updateBundle({
200
+ * bundleId: updateInfo.id,
201
+ * fileUrl: updateInfo.fileUrl
202
+ * });
191
203
  * if (updateInfo.shouldForceUpdate) {
192
204
  * HotUpdater.reload();
193
205
  * }
194
206
  * ```
195
207
  */
196
208
  updateBundle,
209
+ /**
210
+ * Fetches the fingerprint of the app.
211
+ *
212
+ * @returns {string} The fingerprint of the app
213
+ *
214
+ * @example
215
+ * ```ts
216
+ * const fingerprint = HotUpdater.getFingerprintHash();
217
+ * console.log(`Fingerprint: ${fingerprint}`);
218
+ * ```
219
+ */
220
+ getFingerprintHash,
197
221
  };
222
+
223
+ export { getUpdateSource } from "./checkForUpdate";
package/src/native.ts CHANGED
@@ -1,37 +1,28 @@
1
+ import type { UpdateStatus, UpdateStrategy } from "@hot-updater/core";
1
2
  import { NativeEventEmitter, Platform } from "react-native";
2
- import type { Spec } from "./specs/NativeHotUpdater";
3
+ import HotUpdaterNative, {
4
+ type UpdateBundleParams,
5
+ } from "./specs/NativeHotUpdater";
6
+
3
7
  const NIL_UUID = "00000000-0000-0000-0000-000000000000";
4
8
 
5
9
  declare const __HOT_UPDATER_BUNDLE_ID: string | undefined;
6
- declare const __HOT_UPDATER_CHANNEL: string | undefined;
10
+ declare const __HOT_UPDATER_FINGERPRINT_HASH_IOS: string | null;
11
+ declare const __HOT_UPDATER_FINGERPRINT_HASH_ANDROID: string | null;
12
+ declare const __HOT_UPDATER_UPDATE_STRATEGY: UpdateStrategy;
13
+ declare const __HOT_UPDATER_CHANNEL: string | null;
7
14
 
8
- const HotUpdater = {
15
+ export const HotUpdaterConstants = {
16
+ OVER_THE_AIR_CHANNEL: __HOT_UPDATER_CHANNEL,
9
17
  HOT_UPDATER_BUNDLE_ID: __HOT_UPDATER_BUNDLE_ID || NIL_UUID,
10
- CHANNEL: __HOT_UPDATER_CHANNEL || (!__DEV__ ? "production" : null),
18
+ FINGERPRINT_HASH: Platform.select({
19
+ ios: __HOT_UPDATER_FINGERPRINT_HASH_IOS,
20
+ android: __HOT_UPDATER_FINGERPRINT_HASH_ANDROID,
21
+ default: null,
22
+ }),
23
+ UPDATE_STRATEGY: __HOT_UPDATER_UPDATE_STRATEGY,
11
24
  };
12
25
 
13
- const RCTNativeHotUpdater = require("./specs/NativeHotUpdater").default;
14
-
15
- const LINKING_ERROR =
16
- // biome-ignore lint/style/useTemplate: <explanation>
17
- `The package '@hot-updater/react-native' doesn't seem to be linked. Make sure: \n\n` +
18
- Platform.select({ ios: "- You have run 'pod install'\n", default: "" }) +
19
- "- You rebuilt the app after installing the package\n" +
20
- "- You are not using Expo Go\n";
21
-
22
- const HotUpdaterNative = (
23
- RCTNativeHotUpdater
24
- ? RCTNativeHotUpdater
25
- : new Proxy(
26
- {},
27
- {
28
- get() {
29
- throw new Error(LINKING_ERROR);
30
- },
31
- },
32
- )
33
- ) as Spec;
34
-
35
26
  export type HotUpdaterEvent = {
36
27
  onProgress: {
37
28
  progress: number;
@@ -50,19 +41,59 @@ export const addListener = <T extends keyof HotUpdaterEvent>(
50
41
  };
51
42
  };
52
43
 
44
+ export type UpdateParams = UpdateBundleParams & {
45
+ status: UpdateStatus;
46
+ };
47
+
53
48
  /**
54
- * Downloads files from given URLs.
49
+ * Downloads files and applies them to the app.
55
50
  *
56
- * @param {string} bundleId - identifier for the bundle id.
57
- * @param {string | null} zipUrl - zip file URL. If null, it means rolling back to the built-in bundle
51
+ * @param {UpdateParams} params - Parameters object required for bundle update
58
52
  * @returns {Promise<boolean>} Resolves with true if download was successful, otherwise rejects with an error.
59
53
  */
60
- export const updateBundle = (
54
+ export async function updateBundle(params: UpdateParams): Promise<boolean>;
55
+ /**
56
+ * @deprecated Use updateBundle(params: UpdateBundleParamsWithStatus) instead
57
+ */
58
+ export async function updateBundle(
61
59
  bundleId: string,
62
- zipUrl: string | null,
63
- ): Promise<boolean> => {
64
- return HotUpdaterNative.updateBundle(bundleId, zipUrl);
65
- };
60
+ fileUrl: string | null,
61
+ ): Promise<boolean>;
62
+ export async function updateBundle(
63
+ paramsOrBundleId: UpdateParams | string,
64
+ fileUrl?: string | null,
65
+ ): Promise<boolean> {
66
+ const updateBundleId =
67
+ typeof paramsOrBundleId === "string"
68
+ ? paramsOrBundleId
69
+ : paramsOrBundleId.bundleId;
70
+
71
+ const status =
72
+ typeof paramsOrBundleId === "string" ? "UPDATE" : paramsOrBundleId.status;
73
+
74
+ const currentBundleId = getBundleId();
75
+
76
+ // updateBundleId <= currentBundleId
77
+ if (
78
+ status === "UPDATE" &&
79
+ updateBundleId.localeCompare(currentBundleId) <= 0
80
+ ) {
81
+ throw new Error(
82
+ "Update bundle id is the same as the current bundle id. Preventing infinite update loop.",
83
+ );
84
+ }
85
+
86
+ if (typeof paramsOrBundleId === "string") {
87
+ return HotUpdaterNative.updateBundle({
88
+ bundleId: updateBundleId,
89
+ fileUrl: fileUrl || null,
90
+ });
91
+ }
92
+ return HotUpdaterNative.updateBundle({
93
+ bundleId: updateBundleId,
94
+ fileUrl: paramsOrBundleId.fileUrl,
95
+ });
96
+ }
66
97
 
67
98
  /**
68
99
  * Fetches the current app version.
@@ -99,19 +130,34 @@ export const getMinBundleId = (): string => {
99
130
  * @returns {Promise<string>} Resolves with the current version id or null if not available.
100
131
  */
101
132
  export const getBundleId = (): string => {
102
- return HotUpdater.HOT_UPDATER_BUNDLE_ID === NIL_UUID
133
+ return HotUpdaterConstants.HOT_UPDATER_BUNDLE_ID === NIL_UUID
103
134
  ? getMinBundleId()
104
- : HotUpdater.HOT_UPDATER_BUNDLE_ID;
135
+ : HotUpdaterConstants.HOT_UPDATER_BUNDLE_ID;
105
136
  };
106
137
 
107
138
  /**
108
- * Sets the channel for the app.
139
+ * Fetches the channel for the app.
140
+ *
141
+ * @returns {string} Resolves with the channel or null if not available.
109
142
  */
110
- export const setChannel = async (channel: string) => {
111
- return HotUpdaterNative.setChannel(channel);
143
+ export const getChannel = (): string => {
144
+ if (HotUpdaterConstants.OVER_THE_AIR_CHANNEL) {
145
+ return HotUpdaterConstants.OVER_THE_AIR_CHANNEL;
146
+ }
147
+ const constants = HotUpdaterNative.getConstants();
148
+ return constants.CHANNEL;
112
149
  };
113
150
 
114
- export const getChannel = (): string | null => {
151
+ export const getReleaseChannel = (): string => {
115
152
  const constants = HotUpdaterNative.getConstants();
116
- return constants?.CHANNEL ?? HotUpdater.CHANNEL ?? null;
153
+ return constants.CHANNEL;
154
+ };
155
+
156
+ /**
157
+ * Fetches the fingerprint for the app.
158
+ *
159
+ * @returns {string | null} Resolves with the fingerprint hash
160
+ */
161
+ export const getFingerprintHash = (): string | null => {
162
+ return HotUpdaterConstants.FINGERPRINT_HASH;
117
163
  };
@@ -1,5 +1,5 @@
1
1
  import { type CheckForUpdateOptions, checkForUpdate } from "./checkForUpdate";
2
- import { getBundleId, reload, updateBundle } from "./native";
2
+ import { getBundleId, reload } from "./native";
3
3
 
4
4
  export interface RunUpdateProcessResponse {
5
5
  status: "ROLLBACK" | "UPDATE" | "UP_TO_DATE";
@@ -63,7 +63,7 @@ export const runUpdateProcess = async ({
63
63
  };
64
64
  }
65
65
 
66
- const isUpdated = await updateBundle(updateInfo.id, updateInfo.fileUrl);
66
+ const isUpdated = await updateInfo.updateBundle();
67
67
  if (isUpdated && updateInfo.shouldForceUpdate && reloadOnForceUpdate) {
68
68
  reload();
69
69
  }
@@ -1,17 +1,15 @@
1
1
  import type { TurboModule } from "react-native";
2
2
  import { TurboModuleRegistry } from "react-native";
3
3
 
4
+ export interface UpdateBundleParams {
5
+ bundleId: string;
6
+ fileUrl: string | null;
7
+ }
8
+
4
9
  export interface Spec extends TurboModule {
5
10
  // Methods
6
11
  reload(): void;
7
- updateBundle(bundleId: string, zipUrl: string | null): Promise<boolean>;
8
- /**
9
- * @deprecated
10
- * use getConstants().APP_VERSION instead
11
- */
12
- getAppVersion(): Promise<string | null>;
13
-
14
- setChannel(channel: string): Promise<void>;
12
+ updateBundle(params: UpdateBundleParams): Promise<boolean>;
15
13
 
16
14
  // EventEmitter
17
15
  addListener(eventName: string): void;
@@ -19,8 +17,8 @@ export interface Spec extends TurboModule {
19
17
  readonly getConstants: () => {
20
18
  MIN_BUNDLE_ID: string;
21
19
  APP_VERSION: string | null;
22
- CHANNEL: string | null;
20
+ CHANNEL: string;
23
21
  };
24
22
  }
25
23
 
26
- export default TurboModuleRegistry.get<Spec>("HotUpdater");
24
+ export default TurboModuleRegistry.getEnforcing<Spec>("HotUpdater");
package/src/wrap.tsx CHANGED
@@ -1,9 +1,9 @@
1
1
  import React from "react";
2
2
  import { useEffect, useLayoutEffect, useState } from "react";
3
3
  import { type CheckForUpdateOptions, checkForUpdate } from "./checkForUpdate";
4
- import { HotUpdaterError } from "./error";
4
+ import type { HotUpdaterError } from "./error";
5
5
  import { useEventCallback } from "./hooks/useEventCallback";
6
- import { getBundleId, reload, updateBundle } from "./native";
6
+ import { getBundleId, reload } from "./native";
7
7
  import type { RunUpdateProcessResponse } from "./runUpdateProcess";
8
8
  import { useHotUpdaterStore } from "./store";
9
9
 
@@ -39,7 +39,7 @@ export interface HotUpdaterOptions extends CheckForUpdateOptions {
39
39
  progress: number;
40
40
  message: string | null;
41
41
  }>;
42
- onError?: (error: HotUpdaterError) => void;
42
+ onError?: (error: HotUpdaterError | Error | unknown) => void;
43
43
  onProgress?: (progress: number) => void;
44
44
  /**
45
45
  * When a force update exists, the app will automatically reload.
@@ -93,7 +93,10 @@ export function wrap<P extends React.JSX.IntrinsicAttributes = object>(
93
93
  }
94
94
 
95
95
  if (updateInfo.shouldForceUpdate === false) {
96
- void updateBundle(updateInfo.id, updateInfo.fileUrl);
96
+ void updateInfo.updateBundle().catch((error) => {
97
+ restOptions.onError?.(error);
98
+ });
99
+
97
100
  restOptions.onUpdateProcessCompleted?.({
98
101
  id: updateInfo.id,
99
102
  status: updateInfo.status,
@@ -103,13 +106,9 @@ export function wrap<P extends React.JSX.IntrinsicAttributes = object>(
103
106
  setUpdateStatus("UPDATE_PROCESS_COMPLETED");
104
107
  return;
105
108
  }
106
-
107
109
  // Force Update Scenario
108
110
  setUpdateStatus("UPDATING");
109
- const isSuccess = await updateBundle(
110
- updateInfo.id,
111
- updateInfo.fileUrl,
112
- );
111
+ const isSuccess = await updateInfo.updateBundle();
113
112
 
114
113
  if (!isSuccess) {
115
114
  throw new Error(
@@ -130,11 +129,8 @@ export function wrap<P extends React.JSX.IntrinsicAttributes = object>(
130
129
 
131
130
  setUpdateStatus("UPDATE_PROCESS_COMPLETED");
132
131
  } catch (error) {
133
- if (error instanceof HotUpdaterError) {
134
- restOptions.onError?.(error);
135
- }
132
+ restOptions.onError?.(error);
136
133
  setUpdateStatus("UPDATE_PROCESS_COMPLETED");
137
- throw error;
138
134
  }
139
135
  });
140
136
 
@@ -1,93 +0,0 @@
1
-
2
- /**
3
- * This code was generated by [react-native-codegen](https://www.npmjs.com/package/react-native-codegen).
4
- *
5
- * Do not edit this file as changes may cause incorrect behavior and will be lost
6
- * once the code is regenerated.
7
- *
8
- * @generated by codegen project: GenerateModuleJavaSpec.js
9
- *
10
- * @nolint
11
- */
12
-
13
- package com.hotupdater;
14
-
15
- import com.facebook.proguard.annotations.DoNotStrip;
16
- import com.facebook.react.bridge.Promise;
17
- import com.facebook.react.bridge.ReactApplicationContext;
18
- import com.facebook.react.bridge.ReactContextBaseJavaModule;
19
- import com.facebook.react.bridge.ReactMethod;
20
- import com.facebook.react.common.build.ReactBuildConfig;
21
- import com.facebook.react.turbomodule.core.interfaces.TurboModule;
22
- import java.util.Arrays;
23
- import java.util.HashSet;
24
- import java.util.Map;
25
- import java.util.Set;
26
- import javax.annotation.Nonnull;
27
- import javax.annotation.Nullable;
28
-
29
- public abstract class NativeHotUpdaterSpec extends ReactContextBaseJavaModule implements TurboModule {
30
- public static final String NAME = "HotUpdater";
31
-
32
- public NativeHotUpdaterSpec(ReactApplicationContext reactContext) {
33
- super(reactContext);
34
- }
35
-
36
- @Override
37
- public @Nonnull String getName() {
38
- return NAME;
39
- }
40
-
41
- @ReactMethod
42
- @DoNotStrip
43
- public abstract void reload();
44
-
45
- @ReactMethod
46
- @DoNotStrip
47
- public abstract void updateBundle(String bundleId, @Nullable String zipUrl, Promise promise);
48
-
49
- @ReactMethod
50
- @DoNotStrip
51
- public abstract void getAppVersion(Promise promise);
52
-
53
- @ReactMethod
54
- @DoNotStrip
55
- public abstract void setChannel(String channel, Promise promise);
56
-
57
- @ReactMethod
58
- @DoNotStrip
59
- public abstract void addListener(String eventName);
60
-
61
- @ReactMethod
62
- @DoNotStrip
63
- public abstract void removeListeners(double count);
64
-
65
- protected abstract Map<String, Object> getTypedExportedConstants();
66
-
67
- @Override
68
- @DoNotStrip
69
- public final @Nullable Map<String, Object> getConstants() {
70
- Map<String, Object> constants = getTypedExportedConstants();
71
- if (ReactBuildConfig.DEBUG || ReactBuildConfig.IS_INTERNAL_BUILD) {
72
- Set<String> obligatoryFlowConstants = new HashSet<>(Arrays.asList(
73
- "MIN_BUNDLE_ID"
74
- ));
75
- Set<String> optionalFlowConstants = new HashSet<>(Arrays.asList(
76
- "APP_VERSION",
77
- "CHANNEL"
78
- ));
79
- Set<String> undeclaredConstants = new HashSet<>(constants.keySet());
80
- undeclaredConstants.removeAll(obligatoryFlowConstants);
81
- undeclaredConstants.removeAll(optionalFlowConstants);
82
- if (!undeclaredConstants.isEmpty()) {
83
- throw new IllegalStateException(String.format("Native Module Flow doesn't declare constants: %s", undeclaredConstants));
84
- }
85
- undeclaredConstants = obligatoryFlowConstants;
86
- undeclaredConstants.removeAll(constants.keySet());
87
- if (!undeclaredConstants.isEmpty()) {
88
- throw new IllegalStateException(String.format("Native Module doesn't fill in constants: %s", undeclaredConstants));
89
- }
90
- }
91
- return constants;
92
- }
93
- }
@@ -1,36 +0,0 @@
1
- # Copyright (c) Meta Platforms, Inc. and affiliates.
2
- #
3
- # This source code is licensed under the MIT license found in the
4
- # LICENSE file in the root directory of this source tree.
5
-
6
- cmake_minimum_required(VERSION 3.13)
7
- set(CMAKE_VERBOSE_MAKEFILE on)
8
-
9
- file(GLOB react_codegen_SRCS CONFIGURE_DEPENDS *.cpp react/renderer/components/HotUpdaterSpec/*.cpp)
10
-
11
- add_library(
12
- react_codegen_HotUpdaterSpec
13
- OBJECT
14
- ${react_codegen_SRCS}
15
- )
16
-
17
- target_include_directories(react_codegen_HotUpdaterSpec PUBLIC . react/renderer/components/HotUpdaterSpec)
18
-
19
- target_link_libraries(
20
- react_codegen_HotUpdaterSpec
21
- fbjni
22
- jsi
23
- # We need to link different libraries based on whether we are building rncore or not, that's necessary
24
- # because we want to break a circular dependency between react_codegen_rncore and reactnative
25
- reactnative
26
- )
27
-
28
- target_compile_options(
29
- react_codegen_HotUpdaterSpec
30
- PRIVATE
31
- -DLOG_TAG=\"ReactNative\"
32
- -fexceptions
33
- -frtti
34
- -std=c++20
35
- -Wall
36
- )