@hot-updater/react-native 0.23.1 → 0.24.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 (109) hide show
  1. package/android/src/main/java/com/hotupdater/BundleFileStorageService.kt +393 -49
  2. package/android/src/main/java/com/hotupdater/BundleMetadata.kt +204 -0
  3. package/android/src/main/java/com/hotupdater/HotUpdater.kt +48 -36
  4. package/android/src/main/java/com/hotupdater/HotUpdaterException.kt +134 -0
  5. package/android/src/main/java/com/hotupdater/HotUpdaterImpl.kt +168 -95
  6. package/android/src/main/java/com/hotupdater/OkHttpDownloadService.kt +15 -3
  7. package/android/src/main/java/com/hotupdater/SignatureVerifier.kt +17 -12
  8. package/android/src/newarch/HotUpdaterModule.kt +88 -23
  9. package/android/src/oldarch/HotUpdaterModule.kt +89 -22
  10. package/android/src/oldarch/HotUpdaterSpec.kt +6 -0
  11. package/ios/HotUpdater/Internal/BundleFileStorageService.swift +401 -77
  12. package/ios/HotUpdater/Internal/BundleMetadata.swift +177 -0
  13. package/ios/HotUpdater/Internal/HotUpdater.mm +213 -47
  14. package/ios/HotUpdater/Internal/HotUpdaterImpl.swift +96 -25
  15. package/ios/HotUpdater/Internal/SignatureVerifier.swift +35 -29
  16. package/ios/HotUpdater/Internal/URLSessionDownloadService.swift +2 -2
  17. package/ios/HotUpdater/Public/HotUpdater.h +8 -2
  18. package/lib/commonjs/DefaultResolver.js +38 -0
  19. package/lib/commonjs/DefaultResolver.js.map +1 -0
  20. package/lib/commonjs/checkForUpdate.js +33 -45
  21. package/lib/commonjs/checkForUpdate.js.map +1 -1
  22. package/lib/commonjs/error.js +45 -1
  23. package/lib/commonjs/error.js.map +1 -1
  24. package/lib/commonjs/fetchUpdateInfo.js +7 -45
  25. package/lib/commonjs/fetchUpdateInfo.js.map +1 -1
  26. package/lib/commonjs/index.js +249 -208
  27. package/lib/commonjs/index.js.map +1 -1
  28. package/lib/commonjs/native.js +103 -3
  29. package/lib/commonjs/native.js.map +1 -1
  30. package/lib/commonjs/specs/NativeHotUpdater.js.map +1 -1
  31. package/lib/commonjs/types.js +12 -0
  32. package/lib/commonjs/types.js.map +1 -1
  33. package/lib/commonjs/wrap.js +70 -1
  34. package/lib/commonjs/wrap.js.map +1 -1
  35. package/lib/module/DefaultResolver.js +34 -0
  36. package/lib/module/DefaultResolver.js.map +1 -0
  37. package/lib/module/checkForUpdate.js +34 -43
  38. package/lib/module/checkForUpdate.js.map +1 -1
  39. package/lib/module/error.js +45 -0
  40. package/lib/module/error.js.map +1 -1
  41. package/lib/module/fetchUpdateInfo.js +7 -45
  42. package/lib/module/fetchUpdateInfo.js.map +1 -1
  43. package/lib/module/index.js +250 -203
  44. package/lib/module/index.js.map +1 -1
  45. package/lib/module/native.js +87 -2
  46. package/lib/module/native.js.map +1 -1
  47. package/lib/module/specs/NativeHotUpdater.js.map +1 -1
  48. package/lib/module/types.js +12 -0
  49. package/lib/module/types.js.map +1 -1
  50. package/lib/module/wrap.js +71 -2
  51. package/lib/module/wrap.js.map +1 -1
  52. package/lib/typescript/commonjs/DefaultResolver.d.ts +10 -0
  53. package/lib/typescript/commonjs/DefaultResolver.d.ts.map +1 -0
  54. package/lib/typescript/commonjs/checkForUpdate.d.ts +12 -13
  55. package/lib/typescript/commonjs/checkForUpdate.d.ts.map +1 -1
  56. package/lib/typescript/commonjs/error.d.ts +120 -0
  57. package/lib/typescript/commonjs/error.d.ts.map +1 -1
  58. package/lib/typescript/commonjs/fetchUpdateInfo.d.ts +3 -5
  59. package/lib/typescript/commonjs/fetchUpdateInfo.d.ts.map +1 -1
  60. package/lib/typescript/commonjs/index.d.ts +38 -44
  61. package/lib/typescript/commonjs/index.d.ts.map +1 -1
  62. package/lib/typescript/commonjs/native.d.ts +58 -2
  63. package/lib/typescript/commonjs/native.d.ts.map +1 -1
  64. package/lib/typescript/commonjs/specs/NativeHotUpdater.d.ts +62 -0
  65. package/lib/typescript/commonjs/specs/NativeHotUpdater.d.ts.map +1 -1
  66. package/lib/typescript/commonjs/types.d.ts +115 -0
  67. package/lib/typescript/commonjs/types.d.ts.map +1 -1
  68. package/lib/typescript/commonjs/wrap.d.ts +132 -7
  69. package/lib/typescript/commonjs/wrap.d.ts.map +1 -1
  70. package/lib/typescript/module/DefaultResolver.d.ts +10 -0
  71. package/lib/typescript/module/DefaultResolver.d.ts.map +1 -0
  72. package/lib/typescript/module/checkForUpdate.d.ts +12 -13
  73. package/lib/typescript/module/checkForUpdate.d.ts.map +1 -1
  74. package/lib/typescript/module/error.d.ts +120 -0
  75. package/lib/typescript/module/error.d.ts.map +1 -1
  76. package/lib/typescript/module/fetchUpdateInfo.d.ts +3 -5
  77. package/lib/typescript/module/fetchUpdateInfo.d.ts.map +1 -1
  78. package/lib/typescript/module/index.d.ts +38 -44
  79. package/lib/typescript/module/index.d.ts.map +1 -1
  80. package/lib/typescript/module/native.d.ts +58 -2
  81. package/lib/typescript/module/native.d.ts.map +1 -1
  82. package/lib/typescript/module/specs/NativeHotUpdater.d.ts +62 -0
  83. package/lib/typescript/module/specs/NativeHotUpdater.d.ts.map +1 -1
  84. package/lib/typescript/module/types.d.ts +115 -0
  85. package/lib/typescript/module/types.d.ts.map +1 -1
  86. package/lib/typescript/module/wrap.d.ts +132 -7
  87. package/lib/typescript/module/wrap.d.ts.map +1 -1
  88. package/package.json +6 -6
  89. package/plugin/build/withHotUpdater.js +3 -3
  90. package/src/DefaultResolver.ts +36 -0
  91. package/src/checkForUpdate.ts +51 -56
  92. package/src/error.ts +153 -0
  93. package/src/fetchUpdateInfo.ts +10 -58
  94. package/src/index.ts +315 -206
  95. package/src/native.ts +88 -2
  96. package/src/specs/NativeHotUpdater.ts +63 -0
  97. package/src/types.ts +135 -0
  98. package/src/wrap.tsx +245 -34
  99. package/android/src/main/java/com/hotupdater/HotUpdaterFactory.kt +0 -52
  100. package/ios/HotUpdater/Internal/HotUpdaterFactory.swift +0 -24
  101. package/lib/commonjs/runUpdateProcess.js +0 -69
  102. package/lib/commonjs/runUpdateProcess.js.map +0 -1
  103. package/lib/module/runUpdateProcess.js +0 -64
  104. package/lib/module/runUpdateProcess.js.map +0 -1
  105. package/lib/typescript/commonjs/runUpdateProcess.d.ts +0 -49
  106. package/lib/typescript/commonjs/runUpdateProcess.d.ts.map +0 -1
  107. package/lib/typescript/module/runUpdateProcess.d.ts +0 -49
  108. package/lib/typescript/module/runUpdateProcess.d.ts.map +0 -1
  109. package/src/runUpdateProcess.ts +0 -80
package/src/index.ts CHANGED
@@ -1,26 +1,37 @@
1
- import { checkForUpdate } from "./checkForUpdate";
1
+ import {
2
+ type CheckForUpdateOptions,
3
+ checkForUpdate,
4
+ type InternalCheckForUpdateOptions,
5
+ } from "./checkForUpdate";
6
+ import { createDefaultResolver } from "./DefaultResolver";
2
7
  import {
3
8
  addListener,
9
+ clearCrashHistory,
4
10
  getAppVersion,
5
11
  getBundleId,
6
12
  getChannel,
13
+ getCrashHistory,
7
14
  getFingerprintHash,
8
15
  getMinBundleId,
9
16
  reload,
17
+ type UpdateParams,
10
18
  updateBundle,
11
19
  } from "./native";
12
- import { runUpdateProcess } from "./runUpdateProcess";
13
20
  import { hotUpdaterStore } from "./store";
14
- import { wrap } from "./wrap";
21
+ import type { HotUpdaterResolver } from "./types";
22
+ import { type HotUpdaterOptions, type InternalWrapOptions, wrap } from "./wrap";
15
23
 
16
- export type { HotUpdaterEvent } from "./native";
24
+ export type { HotUpdaterEvent, NotifyAppReadyResult } from "./native";
17
25
  export * from "./store";
18
26
  export {
19
27
  extractSignatureFailure,
28
+ type HotUpdaterResolver,
20
29
  isSignatureVerificationError,
30
+ type ResolverCheckUpdateParams,
31
+ type ResolverNotifyAppReadyParams,
21
32
  type SignatureVerificationFailure,
22
33
  } from "./types";
23
- export type { HotUpdaterOptions } from "./wrap";
34
+ export type { HotUpdaterOptions, RunUpdateProcessResponse } from "./wrap";
24
35
 
25
36
  addListener("onProgress", ({ progress }) => {
26
37
  hotUpdaterStore.setState({
@@ -28,205 +39,303 @@ addListener("onProgress", ({ progress }) => {
28
39
  });
29
40
  });
30
41
 
31
- export const HotUpdater = {
32
- /**
33
- * `HotUpdater.wrap` checks for updates at the entry point, and if there is a bundle to update, it downloads the bundle and applies the update strategy.
34
- *
35
- * @param {object} options - Configuration options
36
- * @param {string} options.source - Update server URL
37
- * @param {object} [options.requestHeaders] - Request headers
38
- * @param {React.ComponentType} [options.fallbackComponent] - Component to display during updates
39
- * @param {boolean} [options.reloadOnForceUpdate=true] - Whether to automatically reload the app on force updates
40
- * @param {Function} [options.onUpdateProcessCompleted] - Callback after update process completes
41
- * @param {Function} [options.onProgress] - Callback to track bundle download progress
42
- * @returns {Function} Higher-order component that wraps the app component
43
- *
44
- * @example
45
- * ```tsx
46
- * export default HotUpdater.wrap({
47
- * source: "<your-update-server-url>",
48
- * requestHeaders: {
49
- * "Authorization": "Bearer <your-access-token>",
50
- * },
51
- * })(App);
52
- * ```
53
- */
54
- wrap,
55
- /**
56
- * Reloads the app.
57
- */
58
- reload,
59
- /**
60
- * Returns whether an update has finished downloading in this app session.
61
- *
62
- * When it returns true, calling `HotUpdater.reload()` (or restarting the app)
63
- * will apply the downloaded update bundle.
64
- *
65
- * - Derived from `progress` reaching 1.0
66
- * - Resets to false when a new download starts (progress < 1)
67
- *
68
- * @returns {boolean} True if a downloaded update is ready to apply
69
- * @example
70
- * ```ts
71
- * if (HotUpdater.isUpdateDownloaded()) {
72
- * await HotUpdater.reload();
73
- * }
74
- * ```
75
- */
76
- isUpdateDownloaded: () => hotUpdaterStore.getSnapshot().isUpdateDownloaded,
77
- /**
78
- * Fetches the current app version.
79
- */
80
- getAppVersion,
81
- /**
82
- * Fetches the current bundle ID of the app.
83
- */
84
- getBundleId,
85
- /**
86
- * Retrieves the initial bundle ID based on the build time of the native app.
87
- */
88
- getMinBundleId,
89
- /**
90
- * Fetches the current channel of the app.
91
- *
92
- * If no channel is specified, the app is assigned to the 'production' channel.
93
- *
94
- * @returns {string} The current release channel of the app
95
- * @default "production"
96
- * @example
97
- * ```ts
98
- * const channel = HotUpdater.getChannel();
99
- * console.log(`Current channel: ${channel}`);
100
- * ```
101
- */
102
- getChannel,
103
- /**
104
- * Adds a listener to HotUpdater events.
105
- *
106
- * @param {keyof HotUpdaterEvent} eventName - The name of the event to listen for
107
- * @param {(event: HotUpdaterEvent[T]) => void} listener - The callback function to handle the event
108
- * @returns {() => void} A cleanup function that removes the event listener
109
- *
110
- * @example
111
- * ```ts
112
- * const unsubscribe = HotUpdater.addListener("onProgress", ({ progress }) => {
113
- * console.log(`Update progress: ${progress * 100}%`);
114
- * });
115
- *
116
- * // Unsubscribe when no longer needed
117
- * unsubscribe();
118
- * ```
119
- */
120
- addListener,
121
- /**
122
- * Manually checks for updates.
123
- *
124
- * @param {Object} config - Update check configuration
125
- * @param {string} config.source - Update server URL
126
- * @param {Record<string, string>} [config.requestHeaders] - Request headers
127
- *
128
- * @returns {Promise<UpdateInfo | null>} Update information or null if up to date
129
- *
130
- * @example
131
- * ```ts
132
- * const updateInfo = await HotUpdater.checkForUpdate({
133
- * source: "<your-update-server-url>",
134
- * requestHeaders: {
135
- * Authorization: "Bearer <your-access-token>",
136
- * },
137
- * });
138
- *
139
- * if (!updateInfo) {
140
- * console.log("App is up to date");
141
- * return;
142
- * }
143
- *
144
- * await HotUpdater.updateBundle(updateInfo.id, updateInfo.fileUrl);
145
- * if (updateInfo.shouldForceUpdate) {
146
- * await HotUpdater.reload();
147
- * }
148
- * ```
149
- */
150
- checkForUpdate,
151
- /**
152
- * Manually checks and applies updates for the application.
153
- *
154
- * @param {RunUpdateProcessConfig} config - Update process configuration
155
- * @param {string} config.source - Update server URL
156
- * @param {Record<string, string>} [config.requestHeaders] - Request headers
157
- * @param {boolean} [config.reloadOnForceUpdate=false] - Whether to automatically reload on force update
158
- *
159
- * @example
160
- * ```ts
161
- * // Auto reload on force update
162
- * const result = await HotUpdater.runUpdateProcess({
163
- * source: "<your-update-server-url>",
164
- * requestHeaders: {
165
- * // Add necessary headers
166
- * },
167
- * reloadOnForceUpdate: true
168
- * });
169
- *
170
- * // Manually handle reload on force update
171
- * const result = await HotUpdater.runUpdateProcess({
172
- * source: "<your-update-server-url>",
173
- * reloadOnForceUpdate: false
174
- * });
175
- *
176
- * if(result.status !== "UP_TO_DATE" && result.shouldForceUpdate) {
177
- * await HotUpdater.reload();
178
- * }
179
- * ```
180
- *
181
- * @returns {Promise<RunUpdateProcessResponse>} The result of the update process
182
- */
183
- runUpdateProcess,
184
- /**
185
- * Updates the bundle of the app.
186
- *
187
- * @param {UpdateBundleParams} params - Parameters object required for bundle update
188
- * @param {string} params.bundleId - The bundle ID of the app
189
- * @param {string|null} params.fileUrl - The URL of the zip file
190
- *
191
- * @returns {Promise<boolean>} Whether the update was successful
192
- *
193
- * @example
194
- * ```ts
195
- * const updateInfo = await HotUpdater.checkForUpdate({
196
- * source: "<your-update-server-url>",
197
- * requestHeaders: {
198
- * Authorization: "Bearer <your-access-token>",
199
- * },
200
- * });
201
- *
202
- * if (!updateInfo) {
203
- * return {
204
- * status: "UP_TO_DATE",
205
- * };
206
- * }
207
- *
208
- * await HotUpdater.updateBundle({
209
- * bundleId: updateInfo.id,
210
- * fileUrl: updateInfo.fileUrl
211
- * });
212
- * if (updateInfo.shouldForceUpdate) {
213
- * await HotUpdater.reload();
214
- * }
215
- * ```
216
- */
217
- updateBundle,
218
- /**
219
- * Fetches the fingerprint of the app.
220
- *
221
- * @returns {string} The fingerprint of the app
222
- *
223
- * @example
224
- * ```ts
225
- * const fingerprint = HotUpdater.getFingerprintHash();
226
- * console.log(`Fingerprint: ${fingerprint}`);
227
- * ```
228
- */
229
- getFingerprintHash,
230
- };
42
+ /**
43
+ * Creates a HotUpdater client instance with all update management methods.
44
+ * This function is called once on module initialization to create a singleton instance.
45
+ */
46
+ function createHotUpdaterClient() {
47
+ // Global configuration stored from wrap
48
+ const globalConfig: {
49
+ resolver: HotUpdaterResolver | null;
50
+ requestHeaders?: Record<string, string>;
51
+ requestTimeout?: number;
52
+ } = {
53
+ resolver: null,
54
+ };
55
+
56
+ const ensureGlobalResolver = (methodName: string) => {
57
+ if (!globalConfig.resolver) {
58
+ throw new Error(
59
+ `[HotUpdater] ${methodName} requires HotUpdater.wrap() to be used.\n\n` +
60
+ `To fix this issue, wrap your root component with HotUpdater.wrap():\n\n` +
61
+ `Option 1: With automatic updates\n` +
62
+ ` export default HotUpdater.wrap({\n` +
63
+ ` baseURL: "<your-update-server-url>",\n` +
64
+ ` updateStrategy: "appVersion",\n` +
65
+ ` updateMode: "auto"\n` +
66
+ ` })(App);\n\n` +
67
+ `Option 2: Manual updates only (custom flow)\n` +
68
+ ` export default HotUpdater.wrap({\n` +
69
+ ` baseURL: "<your-update-server-url>",\n` +
70
+ ` updateMode: "manual"\n` +
71
+ ` })(App);\n\n` +
72
+ `For more information, visit: https://hot-updater.dev/docs/react-native-api/wrap`,
73
+ );
74
+ }
75
+ return globalConfig.resolver;
76
+ };
77
+
78
+ return {
79
+ /**
80
+ * `HotUpdater.wrap` checks for updates at the entry point, and if there is a bundle to update, it downloads the bundle and applies the update strategy.
81
+ *
82
+ * @param {object} options - Configuration options
83
+ * @param {string} options.source - Update server URL
84
+ * @param {object} [options.requestHeaders] - Request headers
85
+ * @param {React.ComponentType} [options.fallbackComponent] - Component to display during updates
86
+ * @param {boolean} [options.reloadOnForceUpdate=true] - Whether to automatically reload the app on force updates
87
+ * @param {Function} [options.onUpdateProcessCompleted] - Callback after update process completes
88
+ * @param {Function} [options.onProgress] - Callback to track bundle download progress
89
+ * @returns {Function} Higher-order component that wraps the app component
90
+ *
91
+ * @example
92
+ * ```tsx
93
+ * export default HotUpdater.wrap({
94
+ * baseURL: "<your-update-server-url>",
95
+ * updateStrategy: "appVersion",
96
+ * updateMode: "auto",
97
+ * requestHeaders: {
98
+ * "Authorization": "Bearer <your-access-token>",
99
+ * },
100
+ * })(App);
101
+ * ```
102
+ */
103
+ wrap: (options: HotUpdaterOptions) => {
104
+ let normalizedOptions: InternalWrapOptions;
105
+
106
+ if ("baseURL" in options && options.baseURL) {
107
+ const { baseURL, ...rest } = options;
108
+ normalizedOptions = {
109
+ ...rest,
110
+ resolver: createDefaultResolver(baseURL),
111
+ };
112
+ } else if ("resolver" in options && options.resolver) {
113
+ normalizedOptions = options;
114
+ } else {
115
+ throw new Error(
116
+ `[HotUpdater] Either baseURL or resolver must be provided.\n\n` +
117
+ `Option 1: Using baseURL (recommended for most cases)\n` +
118
+ ` export default HotUpdater.wrap({\n` +
119
+ ` baseURL: "<your-update-server-url>",\n` +
120
+ ` updateStrategy: "appVersion",\n` +
121
+ ` updateMode: "auto"\n` +
122
+ ` })(App);\n\n` +
123
+ `Option 2: Using custom resolver (advanced)\n` +
124
+ ` export default HotUpdater.wrap({\n` +
125
+ ` resolver: {\n` +
126
+ ` checkUpdate: async (params) => { /* custom logic */ },\n` +
127
+ ` notifyAppReady: async (params) => { /* custom logic */ }\n` +
128
+ ` },\n` +
129
+ ` updateMode: "manual"\n` +
130
+ ` })(App);\n\n` +
131
+ `For more information, visit: https://hot-updater.dev/docs/react-native-api/wrap`,
132
+ );
133
+ }
134
+
135
+ globalConfig.resolver = normalizedOptions.resolver;
136
+ globalConfig.requestHeaders = options.requestHeaders;
137
+ globalConfig.requestTimeout = options.requestTimeout;
138
+
139
+ return wrap(normalizedOptions);
140
+ },
141
+
142
+ /**
143
+ * Reloads the app.
144
+ */
145
+ reload,
146
+
147
+ /**
148
+ * Returns whether an update has finished downloading in this app session.
149
+ *
150
+ * When it returns true, calling `HotUpdater.reload()` (or restarting the app)
151
+ * will apply the downloaded update bundle.
152
+ *
153
+ * - Derived from `progress` reaching 1.0
154
+ * - Resets to false when a new download starts (progress < 1)
155
+ *
156
+ * @returns {boolean} True if a downloaded update is ready to apply
157
+ * @example
158
+ * ```ts
159
+ * if (HotUpdater.isUpdateDownloaded()) {
160
+ * await HotUpdater.reload();
161
+ * }
162
+ * ```
163
+ */
164
+ isUpdateDownloaded: () => hotUpdaterStore.getSnapshot().isUpdateDownloaded,
165
+
166
+ /**
167
+ * Fetches the current app version.
168
+ */
169
+ getAppVersion,
170
+
171
+ /**
172
+ * Fetches the current bundle ID of the app.
173
+ */
174
+ getBundleId,
175
+
176
+ /**
177
+ * Retrieves the initial bundle ID based on the build time of the native app.
178
+ */
179
+ getMinBundleId,
180
+
181
+ /**
182
+ * Fetches the current channel of the app.
183
+ *
184
+ * If no channel is specified, the app is assigned to the 'production' channel.
185
+ *
186
+ * @returns {string} The current release channel of the app
187
+ * @default "production"
188
+ * @example
189
+ * ```ts
190
+ * const channel = HotUpdater.getChannel();
191
+ * console.log(`Current channel: ${channel}`);
192
+ * ```
193
+ */
194
+ getChannel,
195
+
196
+ /**
197
+ * Adds a listener to HotUpdater events.
198
+ *
199
+ * @param {keyof HotUpdaterEvent} eventName - The name of the event to listen for
200
+ * @param {(event: HotUpdaterEvent[T]) => void} listener - The callback function to handle the event
201
+ * @returns {() => void} A cleanup function that removes the event listener
202
+ *
203
+ * @example
204
+ * ```ts
205
+ * const unsubscribe = HotUpdater.addListener("onProgress", ({ progress }) => {
206
+ * console.log(`Update progress: ${progress * 100}%`);
207
+ * });
208
+ *
209
+ * // Unsubscribe when no longer needed
210
+ * unsubscribe();
211
+ * ```
212
+ */
213
+ addListener,
214
+
215
+ /**
216
+ * Manually checks for updates.
217
+ *
218
+ * @param {Object} config - Update check configuration
219
+ * @param {string} config.source - Update server URL
220
+ * @param {Record<string, string>} [config.requestHeaders] - Request headers
221
+ *
222
+ * @returns {Promise<UpdateInfo | null>} Update information or null if up to date
223
+ *
224
+ * @example
225
+ * ```ts
226
+ * const updateInfo = await HotUpdater.checkForUpdate({
227
+ * source: "<your-update-server-url>",
228
+ * requestHeaders: {
229
+ * Authorization: "Bearer <your-access-token>",
230
+ * },
231
+ * });
232
+ *
233
+ * if (!updateInfo) {
234
+ * console.log("App is up to date");
235
+ * return;
236
+ * }
237
+ *
238
+ * await HotUpdater.updateBundle(updateInfo.id, updateInfo.fileUrl);
239
+ * if (updateInfo.shouldForceUpdate) {
240
+ * await HotUpdater.reload();
241
+ * }
242
+ * ```
243
+ */
244
+ checkForUpdate: (config: CheckForUpdateOptions) => {
245
+ const resolver = ensureGlobalResolver("checkForUpdate");
246
+
247
+ const mergedConfig: InternalCheckForUpdateOptions = {
248
+ ...config,
249
+ resolver,
250
+ requestHeaders: {
251
+ ...globalConfig.requestHeaders,
252
+ ...config.requestHeaders,
253
+ },
254
+ requestTimeout: config.requestTimeout ?? globalConfig.requestTimeout,
255
+ };
256
+
257
+ return checkForUpdate(mergedConfig);
258
+ },
259
+
260
+ /**
261
+ * Updates the bundle of the app.
262
+ *
263
+ * @param {UpdateBundleParams} params - Parameters object required for bundle update
264
+ * @param {string} params.bundleId - The bundle ID of the app
265
+ * @param {string|null} params.fileUrl - The URL of the zip file
266
+ *
267
+ * @returns {Promise<boolean>} Whether the update was successful
268
+ *
269
+ * @example
270
+ * ```ts
271
+ * const updateInfo = await HotUpdater.checkForUpdate({
272
+ * source: "<your-update-server-url>",
273
+ * requestHeaders: {
274
+ * Authorization: "Bearer <your-access-token>",
275
+ * },
276
+ * });
277
+ *
278
+ * if (!updateInfo) {
279
+ * return {
280
+ * status: "UP_TO_DATE",
281
+ * };
282
+ * }
283
+ *
284
+ * await HotUpdater.updateBundle({
285
+ * bundleId: updateInfo.id,
286
+ * fileUrl: updateInfo.fileUrl
287
+ * });
288
+ * if (updateInfo.shouldForceUpdate) {
289
+ * await HotUpdater.reload();
290
+ * }
291
+ * ```
292
+ */
293
+ updateBundle: (params: UpdateParams) => {
294
+ ensureGlobalResolver("updateBundle");
295
+ return updateBundle(params);
296
+ },
297
+
298
+ /**
299
+ * Fetches the fingerprint of the app.
300
+ *
301
+ * @returns {string} The fingerprint of the app
302
+ *
303
+ * @example
304
+ * ```ts
305
+ * const fingerprint = HotUpdater.getFingerprintHash();
306
+ * console.log(`Fingerprint: ${fingerprint}`);
307
+ * ```
308
+ */
309
+ getFingerprintHash,
310
+
311
+ /**
312
+ * Gets the list of bundle IDs that have been marked as crashed.
313
+ * These bundles will be rejected if attempted to install again.
314
+ *
315
+ * @returns {string[]} Array of crashed bundle IDs
316
+ *
317
+ * @example
318
+ * ```ts
319
+ * const crashedBundles = HotUpdater.getCrashHistory();
320
+ * console.log("Crashed bundles:", crashedBundles);
321
+ * ```
322
+ */
323
+ getCrashHistory,
324
+
325
+ /**
326
+ * Clears the crashed bundle history, allowing previously crashed bundles
327
+ * to be installed again.
328
+ *
329
+ * @returns {boolean} true if clearing was successful
330
+ *
331
+ * @example
332
+ * ```ts
333
+ * // Clear crash history to allow retrying a previously failed bundle
334
+ * HotUpdater.clearCrashHistory();
335
+ * ```
336
+ */
337
+ clearCrashHistory,
338
+ };
339
+ }
231
340
 
232
- export { getUpdateSource } from "./checkForUpdate";
341
+ export const HotUpdater = createHotUpdaterClient();
package/src/native.ts CHANGED
@@ -1,9 +1,12 @@
1
1
  import type { UpdateStatus } from "@hot-updater/core";
2
2
  import { NativeEventEmitter } from "react-native";
3
+ import { HotUpdaterErrorCode, isHotUpdaterError } from "./error";
3
4
  import HotUpdaterNative, {
4
5
  type UpdateBundleParams,
5
6
  } from "./specs/NativeHotUpdater";
6
7
 
8
+ export { HotUpdaterErrorCode, isHotUpdaterError };
9
+
7
10
  const NIL_UUID = "00000000-0000-0000-0000-000000000000";
8
11
 
9
12
  declare const __HOT_UPDATER_BUNDLE_ID: string | undefined;
@@ -43,7 +46,8 @@ let lastInstalledBundleId: string | null = null;
43
46
  * Downloads files and applies them to the app.
44
47
  *
45
48
  * @param {UpdateParams} params - Parameters object required for bundle update
46
- * @returns {Promise<boolean>} Resolves with true if download was successful, otherwise rejects with an error.
49
+ * @returns {Promise<boolean>} Resolves with true if download was successful
50
+ * @throws {Error} Rejects with error.code from HotUpdaterErrorCode enum and error.message
47
51
  */
48
52
  export async function updateBundle(params: UpdateParams): Promise<boolean>;
49
53
  /**
@@ -146,7 +150,7 @@ export const getMinBundleId = (): string => {
146
150
  * Fetches the current bundle version id.
147
151
  *
148
152
  * @async
149
- * @returns {Promise<string>} Resolves with the current version id or null if not available.
153
+ * @returns {string} Resolves with the current version id or null if not available.
150
154
  */
151
155
  export const getBundleId = (): string => {
152
156
  return HotUpdaterConstants.HOT_UPDATER_BUNDLE_ID === NIL_UUID
@@ -173,3 +177,85 @@ export const getFingerprintHash = (): string | null => {
173
177
  const constants = HotUpdaterNative.getConstants();
174
178
  return constants.FINGERPRINT_HASH;
175
179
  };
180
+
181
+ /**
182
+ * Result returned by notifyAppReady()
183
+ */
184
+ export type NotifyAppReadyResult = {
185
+ status: "PROMOTED" | "RECOVERED" | "STABLE";
186
+ crashedBundleId?: string;
187
+ };
188
+
189
+ /**
190
+ * Notifies the native side that the app has successfully started with the current bundle.
191
+ * If the bundle matches the staging bundle, it promotes to stable.
192
+ *
193
+ * This function is called automatically when the module loads.
194
+ *
195
+ * @returns {NotifyAppReadyResult} Bundle state information
196
+ * - `status: "PROMOTED"` - Staging bundle was promoted to stable (ACTIVE event)
197
+ * - `status: "RECOVERED"` - App recovered from crash, rollback occurred (ROLLBACK event)
198
+ * - `status: "STABLE"` - No changes, already stable
199
+ * - `crashedBundleId` - Present only when status is "RECOVERED"
200
+ *
201
+ * @example
202
+ * ```ts
203
+ * const result = HotUpdater.notifyAppReady();
204
+ *
205
+ * switch (result.status) {
206
+ * case "PROMOTED":
207
+ * // Send ACTIVE analytics event
208
+ * analytics.track('bundle_active', { bundleId: HotUpdater.getBundleId() });
209
+ * break;
210
+ * case "RECOVERED":
211
+ * // Send ROLLBACK analytics event
212
+ * analytics.track('bundle_rollback', { crashedBundleId: result.crashedBundleId });
213
+ * break;
214
+ * case "STABLE":
215
+ * // No special action needed
216
+ * break;
217
+ * }
218
+ * ```
219
+ */
220
+ export const notifyAppReady = (): NotifyAppReadyResult => {
221
+ const bundleId = getBundleId();
222
+ const result = HotUpdaterNative.notifyAppReady({ bundleId });
223
+ // Oldarch returns JSON string, newarch returns array
224
+ if (typeof result === "string") {
225
+ try {
226
+ return JSON.parse(result);
227
+ } catch {
228
+ return { status: "STABLE" };
229
+ }
230
+ }
231
+ return result;
232
+ };
233
+
234
+ /**
235
+ * Gets the list of bundle IDs that have been marked as crashed.
236
+ * These bundles will be rejected if attempted to install again.
237
+ *
238
+ * @returns {string[]} Array of crashed bundle IDs
239
+ */
240
+ export const getCrashHistory = (): string[] => {
241
+ const result = HotUpdaterNative.getCrashHistory();
242
+ // Oldarch returns JSON string, newarch returns array
243
+ if (typeof result === "string") {
244
+ try {
245
+ return JSON.parse(result);
246
+ } catch {
247
+ return [];
248
+ }
249
+ }
250
+ return result;
251
+ };
252
+
253
+ /**
254
+ * Clears the crashed bundle history, allowing previously crashed bundles
255
+ * to be installed again.
256
+ *
257
+ * @returns {boolean} true if clearing was successful
258
+ */
259
+ export const clearCrashHistory = (): boolean => {
260
+ return HotUpdaterNative.clearCrashHistory();
261
+ };