@hot-updater/react-native 1.0.0-rc.40 → 1.0.0-rc.42

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 (88) hide show
  1. package/lib/commonjs/appReady.js +25 -31
  2. package/lib/commonjs/appReady.js.map +1 -1
  3. package/lib/commonjs/checkForUpdate.js +9 -9
  4. package/lib/commonjs/checkForUpdate.js.map +1 -1
  5. package/lib/commonjs/error.js +1 -1
  6. package/lib/commonjs/httpClient.js +1 -1
  7. package/lib/commonjs/index.js +304 -326
  8. package/lib/commonjs/index.js.map +1 -1
  9. package/lib/commonjs/init.types.js +6 -0
  10. package/lib/commonjs/init.types.js.map +1 -0
  11. package/lib/commonjs/native.js +3 -3
  12. package/lib/commonjs/pluginHost.js +11 -15
  13. package/lib/commonjs/pluginHost.js.map +1 -1
  14. package/lib/commonjs/sdkVersion.js +1 -1
  15. package/lib/commonjs/wrap.js +7 -16
  16. package/lib/commonjs/wrap.js.map +1 -1
  17. package/lib/module/appReady.js +23 -28
  18. package/lib/module/appReady.js.map +1 -1
  19. package/lib/module/checkForUpdate.js +10 -10
  20. package/lib/module/checkForUpdate.js.map +1 -1
  21. package/lib/module/clientPlugin.js.map +1 -1
  22. package/lib/module/error.js +1 -1
  23. package/lib/module/httpClient.js +1 -1
  24. package/lib/module/index.js +299 -329
  25. package/lib/module/index.js.map +1 -1
  26. package/lib/module/init.types.js +4 -0
  27. package/lib/module/init.types.js.map +1 -0
  28. package/lib/module/native.js +3 -3
  29. package/lib/module/pluginHost.js +9 -9
  30. package/lib/module/pluginHost.js.map +1 -1
  31. package/lib/module/sdkVersion.js +1 -1
  32. package/lib/module/wrap.js +7 -15
  33. package/lib/module/wrap.js.map +1 -1
  34. package/lib/typescript/commonjs/appReady.d.ts +18 -9
  35. package/lib/typescript/commonjs/appReady.d.ts.map +1 -1
  36. package/lib/typescript/commonjs/checkForUpdate.d.ts +5 -3
  37. package/lib/typescript/commonjs/checkForUpdate.d.ts.map +1 -1
  38. package/lib/typescript/commonjs/clientPlugin.d.ts +1 -1
  39. package/lib/typescript/commonjs/clientPlugin.d.ts.map +1 -1
  40. package/lib/typescript/commonjs/error.d.ts +1 -1
  41. package/lib/typescript/commonjs/httpClient.d.ts +1 -1
  42. package/lib/typescript/commonjs/httpClient.d.ts.map +1 -1
  43. package/lib/typescript/commonjs/index.d.ts +122 -110
  44. package/lib/typescript/commonjs/index.d.ts.map +1 -1
  45. package/lib/typescript/commonjs/init.types.d.ts +21 -0
  46. package/lib/typescript/commonjs/init.types.d.ts.map +1 -0
  47. package/lib/typescript/commonjs/native.d.ts +3 -3
  48. package/lib/typescript/commonjs/pluginHost.d.ts +7 -3
  49. package/lib/typescript/commonjs/pluginHost.d.ts.map +1 -1
  50. package/lib/typescript/commonjs/sdkVersion.d.ts +1 -1
  51. package/lib/typescript/commonjs/wrap.d.ts +2 -3
  52. package/lib/typescript/commonjs/wrap.d.ts.map +1 -1
  53. package/lib/typescript/commonjs/wrap.types.d.ts +13 -40
  54. package/lib/typescript/commonjs/wrap.types.d.ts.map +1 -1
  55. package/lib/typescript/module/appReady.d.ts +18 -9
  56. package/lib/typescript/module/appReady.d.ts.map +1 -1
  57. package/lib/typescript/module/checkForUpdate.d.ts +5 -3
  58. package/lib/typescript/module/checkForUpdate.d.ts.map +1 -1
  59. package/lib/typescript/module/clientPlugin.d.ts +1 -1
  60. package/lib/typescript/module/clientPlugin.d.ts.map +1 -1
  61. package/lib/typescript/module/error.d.ts +1 -1
  62. package/lib/typescript/module/httpClient.d.ts +1 -1
  63. package/lib/typescript/module/httpClient.d.ts.map +1 -1
  64. package/lib/typescript/module/index.d.ts +122 -110
  65. package/lib/typescript/module/index.d.ts.map +1 -1
  66. package/lib/typescript/module/init.types.d.ts +21 -0
  67. package/lib/typescript/module/init.types.d.ts.map +1 -0
  68. package/lib/typescript/module/native.d.ts +3 -3
  69. package/lib/typescript/module/pluginHost.d.ts +7 -3
  70. package/lib/typescript/module/pluginHost.d.ts.map +1 -1
  71. package/lib/typescript/module/sdkVersion.d.ts +1 -1
  72. package/lib/typescript/module/wrap.d.ts +2 -3
  73. package/lib/typescript/module/wrap.d.ts.map +1 -1
  74. package/lib/typescript/module/wrap.types.d.ts +13 -40
  75. package/lib/typescript/module/wrap.types.d.ts.map +1 -1
  76. package/package.json +4 -3
  77. package/src/appReady.ts +43 -37
  78. package/src/checkForUpdate.ts +19 -11
  79. package/src/clientPlugin.ts +3 -0
  80. package/src/error.ts +1 -1
  81. package/src/httpClient.ts +1 -1
  82. package/src/index.ts +344 -422
  83. package/src/init.types.ts +24 -0
  84. package/src/native.ts +3 -3
  85. package/src/pluginHost.ts +16 -11
  86. package/src/sdkVersion.ts +1 -1
  87. package/src/wrap.tsx +6 -20
  88. package/src/wrap.types.ts +18 -45
@@ -1,15 +1,17 @@
1
1
  "use strict";
2
2
 
3
- import { emitAfterAppReady } from "./appReady.js";
3
+ import { createLaunchReporter } from "./appReady.js";
4
4
  import { checkForUpdate, reportUpdateError } from "./checkForUpdate.js";
5
5
  import { createHttpClient } from "./httpClient.js";
6
6
  import { addListener, getPublicActiveUpdateState, getActiveUpdateState, getBundleId, clearCrashHistory, getAppVersion, getBaseURL, getUpdateId, getChannel, getCohort, getCrashHistory, getDefaultChannel, getFingerprintHash, getInstallId, getManifest, getMinBundleId, isChannelSwitched, notifyAppReady, reload, resetChannel, setCohort, setReloadBehavior, stageBundle } from "./native.js";
7
- import { configurePlugins } from "./pluginHost.js";
7
+ import { createAppPluginHost } from "./pluginHost.js";
8
8
  import { hotUpdaterStore } from "./store.js";
9
- import { init, wrap } from "./wrap.js";
9
+ import { wrap } from "./wrap.js";
10
10
  export { defineClientPlugin } from "./clientPlugin.js";
11
11
  // The built-in Insights client plugin, for HotUpdater.init({ plugins }).
12
12
  export { insights } from "@hot-updater/plugin-insights/client";
13
+ // The built-in Remote Config client plugin, for HotUpdater.init({ plugins }).
14
+ export { remoteConfig } from "@hot-updater/plugin-remote-config/client";
13
15
  export * from "./store.js";
14
16
  export { extractSignatureFailure, isSignatureVerificationError } from "./types.js";
15
17
  /**
@@ -29,255 +31,204 @@ const registerGlobalGetBaseURL = () => {
29
31
 
30
32
  // Call registration immediately on module load
31
33
  registerGlobalGetBaseURL();
32
- /**
33
- * Creates a HotUpdater client instance with all update management methods.
34
- * This function is called once on module initialization to create a singleton instance.
35
- */
36
- function createHotUpdaterClient() {
37
- let configurationAPI = null;
38
- let mixedConfigurationReported = false;
34
+ const createMissingNetworkConfigError = () => new Error(`[HotUpdater] baseURL must be provided.\n\n` + `Configure HotUpdater with the standard baseURL setup:\n\n` + ` export const hotUpdater = HotUpdater.init({\n` + ` baseURL: "<your-update-server-url>",\n` + ` });\n\n` + `See https://hot-updater.dev/docs/react-native-api/init`);
39
35
 
40
- // Global configuration stored from wrap
41
- const globalConfig = {
42
- client: null
43
- };
44
- const createMissingNetworkConfigError = apiName => {
45
- if (apiName === "init") {
46
- return new Error(`[HotUpdater] baseURL must be provided.\n\n` + `Configure HotUpdater before calling update APIs with the standard baseURL setup:\n\n` + ` HotUpdater.init({\n` + ` baseURL: "<your-update-server-url>",\n` + ` });\n\n` + `For manual update flows, visit: https://hot-updater.dev/docs/guides/custom-update`);
47
- }
48
- const baseURLExample = ` export default HotUpdater.wrap({\n` + ` baseURL: "<your-update-server-url>",\n` + ` updateStrategy: "appVersion"\n` + ` })(App);\n\n`;
49
- return new Error(`[HotUpdater] baseURL must be provided.\n\n` + `Configure HotUpdater.wrap with the standard baseURL setup:\n\n` + baseURLExample + `For manual update flows, use HotUpdater.init() and visit: ` + `https://hot-updater.dev/docs/guides/custom-update`);
50
- };
51
- const normalizeOptions = options => {
52
- const incoming = options;
53
- if (incoming.updateMode === "manual") {
54
- throw new Error('[HotUpdater] HotUpdater.wrap({ updateMode: "manual" }) was removed. ' + "Call HotUpdater.init({ ... }) instead, export your root component " + "directly, and use HotUpdater.checkForUpdate(...) for the manual " + "update flow. See https://hot-updater.dev/docs/guides/custom-update");
55
- }
56
- const autoOptions = incoming;
57
- if (autoOptions.baseURL) {
58
- const {
59
- baseURL,
60
- plugins: _plugins,
61
- ...rest
62
- } = autoOptions;
63
- return {
64
- ...rest,
65
- client: createHttpClient(baseURL, response => {
66
- emitAfterAppReady("onHttpResponse", () => response);
67
- })
68
- };
69
- }
70
- throw createMissingNetworkConfigError("wrap");
71
- };
72
- const normalizeInitOptions = options => {
73
- const {
74
- updateMode: _updateMode,
75
- ...rest
76
- } = options;
77
- if (rest.baseURL) {
78
- const {
79
- baseURL,
80
- plugins: _plugins,
81
- ...baseURLRest
82
- } = rest;
83
- return {
84
- ...baseURLRest,
85
- client: createHttpClient(baseURL, response => {
86
- emitAfterAppReady("onHttpResponse", () => response);
87
- })
88
- };
89
- }
90
- throw createMissingNetworkConfigError("init");
91
- };
92
- const configureGlobal = (normalizedOptions, options) => {
93
- // Plugins are set up before init or wrap reads the launch they observe.
94
- configurePlugins(options.plugins, {
95
- baseURL: options.baseURL,
96
- requestHeaders: options.requestHeaders,
97
- requestTimeout: options.requestTimeout,
98
- onError: options.onError
99
- });
100
- globalConfig.client = normalizedOptions.client;
101
- globalConfig.requestHeaders = options.requestHeaders;
102
- globalConfig.requestTimeout = options.requestTimeout;
103
- globalConfig.onError = options.onError;
104
- };
105
- const reportMixedConfiguration = nextAPI => {
106
- if (configurationAPI !== null && configurationAPI !== nextAPI && !mixedConfigurationReported) {
107
- mixedConfigurationReported = true;
108
- console.error("[HotUpdater] HotUpdater.init() and HotUpdater.wrap() must not be used together. " + "For custom or manual update flows, use HotUpdater.init() with " + "HotUpdater.checkForUpdate(). For the automatic HOC flow, use " + "HotUpdater.wrap().");
109
- }
110
- configurationAPI ??= nextAPI;
111
- };
112
- const ensureGlobalClient = methodName => {
113
- if (!globalConfig.client) {
114
- throw new Error(`[HotUpdater] ${methodName} requires HotUpdater.wrap() or HotUpdater.init() to be used.\n\n` + `To fix this issue, configure HotUpdater before calling ${methodName}:\n\n` + `Option 1: With HotUpdater.wrap()\n` + ` export default HotUpdater.wrap({\n` + ` baseURL: "<your-update-server-url>",\n` + ` updateStrategy: "appVersion"\n` + ` })(App);\n\n` + `Option 2: With HotUpdater.init() for custom runtimes\n` + ` HotUpdater.init({\n` + ` baseURL: "<your-update-server-url>",\n` + ` });\n\n` + `For manual update flows, visit: https://hot-updater.dev/docs/guides/custom-update`);
36
+ /** The methods that read or change native state, the same on every instance. */
37
+ const nativeMethods = {
38
+ /**
39
+ * Reloads the app.
40
+ */
41
+ reload,
42
+ /**
43
+ * Configures how `hotUpdater.reload()` behaves.
44
+ *
45
+ * This can be called unconditionally on both platforms.
46
+ * The default is `processRestart`.
47
+ *
48
+ * - `reload`: built-in React Native reload on both platforms
49
+ * - `processRestart`: Android process restart, iOS behaves like normal reload
50
+ * - `custom`: run a custom JS handler on both platforms
51
+ */
52
+ setReloadBehavior,
53
+ /**
54
+ * Returns whether an update has finished downloading in this app session.
55
+ *
56
+ * When it returns true, calling `hotUpdater.reload()` (or restarting the app)
57
+ * will apply the downloaded update bundle.
58
+ *
59
+ * - Derived from `progress` reaching 1.0
60
+ * - Resets to false when a new download starts (progress < 1)
61
+ *
62
+ * @returns {boolean} True if a downloaded update is ready to apply
63
+ * @example
64
+ * ```ts
65
+ * if (hotUpdater.isUpdateDownloaded()) {
66
+ * await hotUpdater.reload();
67
+ * }
68
+ * ```
69
+ */
70
+ isUpdateDownloaded: () => hotUpdaterStore.getSnapshot().isUpdateDownloaded,
71
+ /**
72
+ * Fetches the current app version.
73
+ */
74
+ getAppVersion,
75
+ /** Reads the active and stable Release/Bundle state. */
76
+ getActiveUpdateState: async () => getPublicActiveUpdateState(),
77
+ /**
78
+ * Returns the selected update ID, matching the ID shown in the console.
79
+ * A staged update can change this ID before the app reloads.
80
+ */
81
+ getBundleId: getUpdateId,
82
+ /** Returns the minimum bundle ID based on the native app build time. */
83
+ getMinBundleId,
84
+ /**
85
+ * Fetches the current manifest for the active bundle.
86
+ */
87
+ getManifest,
88
+ /**
89
+ * Fetches the current channel of the app.
90
+ *
91
+ * If no channel is specified, the app is assigned to the 'production' channel.
92
+ *
93
+ * @returns {string} The current release channel of the app
94
+ * @default "production"
95
+ * @example
96
+ * ```ts
97
+ * const channel = hotUpdater.getChannel();
98
+ * console.log(`Current channel: ${channel}`);
99
+ * ```
100
+ */
101
+ getChannel,
102
+ /**
103
+ * Fetches the build-time default channel of the app.
104
+ *
105
+ * This value does not change when a runtime channel override is active.
106
+ *
107
+ * @returns {string} The default release channel embedded in the app
108
+ * @example
109
+ * ```ts
110
+ * const defaultChannel = hotUpdater.getDefaultChannel();
111
+ * console.log(`Default channel: ${defaultChannel}`);
112
+ * ```
113
+ */
114
+ getDefaultChannel,
115
+ /**
116
+ * Returns whether the app is currently using a runtime channel override.
117
+ *
118
+ * @returns {boolean} true when a non-default channel has been applied
119
+ */
120
+ isChannelSwitched,
121
+ /**
122
+ * Sets the persisted cohort used for rollout calculations.
123
+ * Call `getCohort()` first if you need to restore the initial value later.
124
+ */
125
+ setCohort,
126
+ /**
127
+ * Gets the persisted cohort used for rollout calculations.
128
+ */
129
+ getCohort,
130
+ /**
131
+ * Adds a listener to HotUpdater events.
132
+ *
133
+ * @param {keyof HotUpdaterEvent} eventName - The name of the event to listen for
134
+ * @param {(event: HotUpdaterEvent[T]) => void} listener - The callback function to handle the event
135
+ * @returns {() => void} A cleanup function that removes the event listener
136
+ *
137
+ * @example
138
+ * ```ts
139
+ * const unsubscribe = hotUpdater.addListener("onProgress", ({ progress }) => {
140
+ * console.log(`Update progress: ${progress * 100}%`);
141
+ * });
142
+ *
143
+ * // Unsubscribe when no longer needed
144
+ * unsubscribe();
145
+ * ```
146
+ */
147
+ addListener,
148
+ /**
149
+ * Clears the runtime channel override and restores the original bundle.
150
+ *
151
+ * @returns {Promise<boolean>} Resolves with true if reset was successful
152
+ */
153
+ resetChannel: async () => {
154
+ const ok = await resetChannel();
155
+ if (ok) {
156
+ hotUpdaterStore.setState({
157
+ artifactType: null,
158
+ details: null,
159
+ isUpdateDownloaded: false,
160
+ progress: 0
161
+ });
115
162
  }
116
- return globalConfig.client;
117
- };
118
- return {
119
- /**
120
- * `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.
121
- *
122
- * @param {object} options - Configuration options
123
- * @param {string} options.source - Update server URL
124
- * @param {object} [options.requestHeaders] - Request headers
125
- * @param {React.ComponentType} [options.fallbackComponent] - Component to display during updates
126
- * @param {boolean} [options.reloadOnForceUpdate=true] - Whether to automatically reload the app on force updates
127
- * @param {Function} [options.onUpdateProcessCompleted] - Callback after update process completes
128
- * @param {Function} [options.onProgress] - Callback to track bundle download progress
129
- * @returns {Function} Higher-order component that wraps the app component
130
- *
131
- * @example
132
- * ```tsx
133
- * export default HotUpdater.wrap({
134
- * baseURL: "<your-update-server-url>",
135
- * updateStrategy: "appVersion",
136
- * requestHeaders: {
137
- * "x-api-key": "<your-api-key>",
138
- * },
139
- * })(App);
140
- * ```
141
- */
142
- wrap: options => {
143
- const normalizedOptions = normalizeOptions(options);
144
- reportMixedConfiguration("wrap");
145
- configureGlobal(normalizedOptions, options);
146
- return wrap(normalizedOptions);
147
- },
148
- /**
149
- * Initializes HotUpdater without wrapping a React component.
150
- *
151
- * Use this for manual update flows in runtimes where a root component HOC
152
- * is not convenient. Use this instead of wrapping the root component.
153
- *
154
- * @example
155
- * ```tsx
156
- * HotUpdater.init({
157
- * baseURL: "<your-update-server-url>",
158
- * });
159
- *
160
- * export default App;
161
- * ```
162
- */
163
- init: options => {
164
- const normalizedOptions = normalizeInitOptions(options);
165
- reportMixedConfiguration("init");
166
- configureGlobal(normalizedOptions, options);
167
- init(normalizedOptions);
163
+ return ok;
164
+ },
165
+ /**
166
+ * Fetches the fingerprint of the app.
167
+ *
168
+ * @returns {string} The fingerprint of the app
169
+ *
170
+ * @example
171
+ * ```ts
172
+ * const fingerprint = hotUpdater.getFingerprintHash();
173
+ * console.log(`Fingerprint: ${fingerprint}`);
174
+ * ```
175
+ */
176
+ getFingerprintHash,
177
+ /**
178
+ * Fetches the persisted install id for this app installation.
179
+ */
180
+ getInstallId,
181
+ /**
182
+ * Reads the native launch report for the current process.
183
+ */
184
+ notifyAppReady,
185
+ /**
186
+ * Gets the list of bundle IDs that have been marked as crashed.
187
+ * These bundles will be rejected if attempted to install again.
188
+ *
189
+ * @returns {string[]} Array of crashed bundle IDs
190
+ *
191
+ * @example
192
+ * ```ts
193
+ * const crashedBundles = hotUpdater.getCrashHistory();
194
+ * console.log("Crashed bundles:", crashedBundles);
195
+ * ```
196
+ */
197
+ getCrashHistory,
198
+ /**
199
+ * Clears the crashed bundle history, allowing previously crashed bundles
200
+ * to be installed again.
201
+ *
202
+ * @returns {boolean} true if clearing was successful
203
+ *
204
+ * @example
205
+ * ```ts
206
+ * // Clear crash history to allow retrying a previously failed bundle
207
+ * hotUpdater.clearCrashHistory();
208
+ * ```
209
+ */
210
+ clearCrashHistory
211
+ };
212
+
213
+ /** What an instance's own methods use: its server, settings, and launch. */
214
+
215
+ /** The methods that use the instance's own configuration and plugins. */
216
+ const createConfiguredMethods = config => {
217
+ const check = options => checkForUpdate({
218
+ ...options,
219
+ client: config.client,
220
+ emit: config.launch.emit,
221
+ requestHeaders: {
222
+ ...config.requestHeaders,
223
+ ...options.requestHeaders
168
224
  },
225
+ requestTimeout: options.requestTimeout ?? config.requestTimeout,
226
+ onError: options.onError ?? config.onError
227
+ });
228
+ return {
169
229
  /**
170
- * Reloads the app.
171
- */
172
- reload,
173
- /**
174
- * Configures how `HotUpdater.reload()` behaves.
175
- *
176
- * This can be called unconditionally on both platforms.
177
- * The default is `processRestart`.
178
- *
179
- * - `reload`: built-in React Native reload on both platforms
180
- * - `processRestart`: Android process restart, iOS behaves like normal reload
181
- * - `custom`: run a custom JS handler on both platforms
182
- */
183
- setReloadBehavior,
184
- /**
185
- * Returns whether an update has finished downloading in this app session.
186
- *
187
- * When it returns true, calling `HotUpdater.reload()` (or restarting the app)
188
- * will apply the downloaded update bundle.
189
- *
190
- * - Derived from `progress` reaching 1.0
191
- * - Resets to false when a new download starts (progress < 1)
192
- *
193
- * @returns {boolean} True if a downloaded update is ready to apply
194
- * @example
195
- * ```ts
196
- * if (HotUpdater.isUpdateDownloaded()) {
197
- * await HotUpdater.reload();
198
- * }
199
- * ```
200
- */
201
- isUpdateDownloaded: () => hotUpdaterStore.getSnapshot().isUpdateDownloaded,
202
- /**
203
- * Fetches the current app version.
204
- */
205
- getAppVersion,
206
- /** Reads the active and stable Release/Bundle state. */
207
- getActiveUpdateState: async () => getPublicActiveUpdateState(),
208
- /**
209
- * Returns the selected update ID, matching the ID shown in the console.
210
- * A staged update can change this ID before the app reloads.
211
- */
212
- getBundleId: getUpdateId,
213
- /** Returns the minimum bundle ID based on the native app build time. */
214
- getMinBundleId,
215
- /**
216
- * Fetches the current manifest for the active bundle.
217
- */
218
- getManifest,
219
- /**
220
- * Fetches the current channel of the app.
221
- *
222
- * If no channel is specified, the app is assigned to the 'production' channel.
223
- *
224
- * @returns {string} The current release channel of the app
225
- * @default "production"
226
- * @example
227
- * ```ts
228
- * const channel = HotUpdater.getChannel();
229
- * console.log(`Current channel: ${channel}`);
230
- * ```
231
- */
232
- getChannel,
233
- /**
234
- * Fetches the build-time default channel of the app.
235
- *
236
- * This value does not change when a runtime channel override is active.
237
- *
238
- * @returns {string} The default release channel embedded in the app
239
- * @example
240
- * ```ts
241
- * const defaultChannel = HotUpdater.getDefaultChannel();
242
- * console.log(`Default channel: ${defaultChannel}`);
243
- * ```
244
- */
245
- getDefaultChannel,
246
- /**
247
- * Returns whether the app is currently using a runtime channel override.
248
- *
249
- * @returns {boolean} true when a non-default channel has been applied
250
- */
251
- isChannelSwitched,
252
- /**
253
- * Sets the persisted cohort used for rollout calculations.
254
- * Call `getCohort()` first if you need to restore the initial value later.
255
- */
256
- setCohort,
257
- /**
258
- * Gets the persisted cohort used for rollout calculations.
259
- */
260
- getCohort,
261
- /**
262
- * Adds a listener to HotUpdater events.
263
- *
264
- * @param {keyof HotUpdaterEvent} eventName - The name of the event to listen for
265
- * @param {(event: HotUpdaterEvent[T]) => void} listener - The callback function to handle the event
266
- * @returns {() => void} A cleanup function that removes the event listener
267
- *
268
- * @example
269
- * ```ts
270
- * const unsubscribe = HotUpdater.addListener("onProgress", ({ progress }) => {
271
- * console.log(`Update progress: ${progress * 100}%`);
272
- * });
273
- *
274
- * // Unsubscribe when no longer needed
275
- * unsubscribe();
276
- * ```
277
- */
278
- addListener,
279
- /**
280
- * Manually checks for updates.
230
+ * Checks for an update, with this instance's server and request
231
+ * settings.
281
232
  *
282
233
  * @param {Object} config - Update check configuration
283
234
  * @param {string} [config.channel] - Optional channel override for this update check
@@ -287,7 +238,7 @@ function createHotUpdaterClient() {
287
238
  *
288
239
  * @example
289
240
  * ```ts
290
- * const updateInfo = await HotUpdater.checkForUpdate({
241
+ * const updateInfo = await hotUpdater.checkForUpdate({
291
242
  * updateStrategy: "appVersion",
292
243
  * requestHeaders: {
293
244
  * "x-api-key": "<your-api-key>",
@@ -301,24 +252,11 @@ function createHotUpdaterClient() {
301
252
  *
302
253
  * await updateInfo.updateBundle();
303
254
  * if (updateInfo.shouldForceUpdate) {
304
- * await HotUpdater.reload();
255
+ * await hotUpdater.reload();
305
256
  * }
306
257
  * ```
307
258
  */
308
- checkForUpdate: config => {
309
- const client = ensureGlobalClient("checkForUpdate");
310
- const mergedConfig = {
311
- ...config,
312
- client,
313
- requestHeaders: {
314
- ...globalConfig.requestHeaders,
315
- ...config.requestHeaders
316
- },
317
- requestTimeout: config.requestTimeout ?? globalConfig.requestTimeout,
318
- onError: config.onError ?? globalConfig.onError
319
- };
320
- return checkForUpdate(mergedConfig);
321
- },
259
+ checkForUpdate: check,
322
260
  /**
323
261
  * Updates the bundle of the app.
324
262
  *
@@ -328,7 +266,7 @@ function createHotUpdaterClient() {
328
266
  *
329
267
  * @example
330
268
  * ```ts
331
- * const updateInfo = await HotUpdater.checkForUpdate({
269
+ * const updateInfo = await hotUpdater.checkForUpdate({
332
270
  * updateStrategy: "appVersion",
333
271
  * requestHeaders: {
334
272
  * "x-api-key": "<your-api-key>",
@@ -343,12 +281,11 @@ function createHotUpdaterClient() {
343
281
  *
344
282
  * await updateInfo.updateBundle();
345
283
  * if (updateInfo.shouldForceUpdate) {
346
- * await HotUpdater.reload();
284
+ * await hotUpdater.reload();
347
285
  * }
348
286
  * ```
349
287
  */
350
288
  updateBundle: async params => {
351
- ensureGlobalClient("updateBundle");
352
289
  const fromBundleId = getBundleId();
353
290
  const state = getActiveUpdateState();
354
291
  const active = state.activeSelection;
@@ -359,7 +296,7 @@ function createHotUpdaterClient() {
359
296
  try {
360
297
  delivery = await stageBundle(params);
361
298
  } catch (error) {
362
- reportUpdateError(error, "download", undefined, {
299
+ reportUpdateError(config.launch.emit, error, "download", undefined, {
363
300
  bundleId: fromBundleId,
364
301
  channel,
365
302
  targetBundleId: params.bundleId,
@@ -369,7 +306,7 @@ function createHotUpdaterClient() {
369
306
  throw error;
370
307
  }
371
308
  if (delivery !== null && params.bundleId !== fromBundleId) {
372
- emitAfterAppReady("onBundleDownloaded", () => {
309
+ config.launch.emit("onBundleDownloaded", () => {
373
310
  const selection = getActiveUpdateState().activeSelection;
374
311
  return {
375
312
  channel,
@@ -385,69 +322,102 @@ function createHotUpdaterClient() {
385
322
  return true;
386
323
  },
387
324
  /**
388
- * Clears the runtime channel override and restores the original bundle.
389
- *
390
- * @returns {Promise<boolean>} Resolves with true if reset was successful
391
- */
392
- resetChannel: async () => {
393
- const ok = await resetChannel();
394
- if (ok) {
395
- hotUpdaterStore.setState({
396
- artifactType: null,
397
- details: null,
398
- isUpdateDownloaded: false,
399
- progress: 0
400
- });
401
- }
402
- return ok;
403
- },
404
- /**
405
- * Fetches the fingerprint of the app.
406
- *
407
- * @returns {string} The fingerprint of the app
408
- *
409
- * @example
410
- * ```ts
411
- * const fingerprint = HotUpdater.getFingerprintHash();
412
- * console.log(`Fingerprint: ${fingerprint}`);
413
- * ```
414
- */
415
- getFingerprintHash,
416
- /**
417
- * Fetches the persisted install id for this app installation.
418
- */
419
- getInstallId,
420
- /**
421
- * Reads the native launch report for the current process.
422
- */
423
- notifyAppReady,
424
- /**
425
- * Gets the list of bundle IDs that have been marked as crashed.
426
- * These bundles will be rejected if attempted to install again.
427
- *
428
- * @returns {string[]} Array of crashed bundle IDs
429
- *
430
- * @example
431
- * ```ts
432
- * const crashedBundles = HotUpdater.getCrashHistory();
433
- * console.log("Crashed bundles:", crashedBundles);
434
- * ```
435
- */
436
- getCrashHistory,
437
- /**
438
- * Clears the crashed bundle history, allowing previously crashed bundles
439
- * to be installed again.
440
- *
441
- * @returns {boolean} true if clearing was successful
325
+ * Wraps the app's root: when it mounts, it checks for an update with
326
+ * this instance's configuration, downloads one, and reloads for a
327
+ * forced update. A `fallbackComponent` replaces the root until the
328
+ * check and a forced update finish.
442
329
  *
443
330
  * @example
444
- * ```ts
445
- * // Clear crash history to allow retrying a previously failed bundle
446
- * HotUpdater.clearCrashHistory();
331
+ * ```tsx
332
+ * export default hotUpdater.wrap({
333
+ * updateStrategy: "appVersion",
334
+ * fallbackComponent: ({ progress }) => <Splash progress={progress} />,
335
+ * })(App);
447
336
  * ```
448
337
  */
449
- clearCrashHistory
338
+ wrap: options => wrap({
339
+ ...options,
340
+ checkForUpdate: check,
341
+ appReady: () => config.launch.appReady,
342
+ onError: error => config.onError?.(error)
343
+ })
450
344
  };
451
- }
452
- export const HotUpdater = createHotUpdaterClient();
345
+ };
346
+
347
+ /** The methods of every instance `HotUpdater.init` returns. */
348
+
349
+ /**
350
+ * The instance `HotUpdater.init` returns: HotUpdater's methods, and each
351
+ * plugin's API under the plugin's id.
352
+ */
353
+
354
+ export const HotUpdater = {
355
+ /**
356
+ * Creates the app's HotUpdater instance: the update server, its request
357
+ * settings, and the client plugins. Each call returns an independent
358
+ * instance with its own configuration and plugins, so create one, at the
359
+ * top level of a module, and export it.
360
+ *
361
+ * The instance has every HotUpdater method, such as `wrap`,
362
+ * `checkForUpdate`, and `reload`, and each plugin's API under the
363
+ * plugin's id, such as `hotUpdater.remoteConfig` for `remoteConfig()`.
364
+ *
365
+ * @example
366
+ * ```tsx
367
+ * export const hotUpdater = HotUpdater.init({
368
+ * baseURL: "<your-update-server-url>",
369
+ * plugins: [remoteConfig({ defaults: { welcome_message: "Hi" } })],
370
+ * });
371
+ *
372
+ * hotUpdater.remoteConfig.getString("welcome_message");
373
+ * ```
374
+ */
375
+ init: options => {
376
+ const {
377
+ baseURL,
378
+ plugins,
379
+ requestHeaders,
380
+ requestTimeout,
381
+ onError,
382
+ onNotifyAppReady
383
+ } = options;
384
+ if (!baseURL) throw createMissingNetworkConfigError();
385
+ const host = createAppPluginHost();
386
+ const launch = createLaunchReporter(host);
387
+ const instance = {
388
+ ...nativeMethods,
389
+ ...createConfiguredMethods({
390
+ client: createHttpClient(baseURL, response => {
391
+ launch.emit("onHttpResponse", () => response);
392
+ }),
393
+ requestHeaders,
394
+ requestTimeout,
395
+ onError,
396
+ launch
397
+ })
398
+ };
399
+ const reservedIds = new Set([...Object.keys(instance), "init"]);
400
+ for (const plugin of plugins ?? []) {
401
+ if (reservedIds.has(plugin.id)) {
402
+ throw new Error(`[HotUpdater] A plugin cannot use the id "${plugin.id}": the HotUpdater instance has its own "${plugin.id}".`);
403
+ }
404
+ }
405
+
406
+ // Plugins are set up before the instance reads the launch they observe.
407
+ const apis = host.configurePlugins(plugins, {
408
+ baseURL,
409
+ requestHeaders,
410
+ requestTimeout,
411
+ onError
412
+ });
413
+ void launch.read({
414
+ onNotifyAppReady,
415
+ onError
416
+ });
417
+ return Object.freeze({
418
+ ...instance,
419
+ ...apis
420
+ });
421
+ }
422
+ };
453
423
  //# sourceMappingURL=index.js.map