@hot-updater/react-native 0.24.0 → 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 (46) hide show
  1. package/lib/commonjs/DefaultResolver.js +38 -0
  2. package/lib/commonjs/DefaultResolver.js.map +1 -0
  3. package/lib/commonjs/checkForUpdate.js +35 -50
  4. package/lib/commonjs/checkForUpdate.js.map +1 -1
  5. package/lib/commonjs/index.js +26 -14
  6. package/lib/commonjs/index.js.map +1 -1
  7. package/lib/commonjs/types.js +12 -0
  8. package/lib/commonjs/types.js.map +1 -1
  9. package/lib/commonjs/wrap.js +44 -13
  10. package/lib/commonjs/wrap.js.map +1 -1
  11. package/lib/module/DefaultResolver.js +34 -0
  12. package/lib/module/DefaultResolver.js.map +1 -0
  13. package/lib/module/checkForUpdate.js +35 -50
  14. package/lib/module/checkForUpdate.js.map +1 -1
  15. package/lib/module/index.js +26 -14
  16. package/lib/module/index.js.map +1 -1
  17. package/lib/module/types.js +12 -0
  18. package/lib/module/types.js.map +1 -1
  19. package/lib/module/wrap.js +44 -13
  20. package/lib/module/wrap.js.map +1 -1
  21. package/lib/typescript/commonjs/DefaultResolver.d.ts +10 -0
  22. package/lib/typescript/commonjs/DefaultResolver.d.ts.map +1 -0
  23. package/lib/typescript/commonjs/checkForUpdate.d.ts +2 -1
  24. package/lib/typescript/commonjs/checkForUpdate.d.ts.map +1 -1
  25. package/lib/typescript/commonjs/index.d.ts +3 -3
  26. package/lib/typescript/commonjs/index.d.ts.map +1 -1
  27. package/lib/typescript/commonjs/types.d.ts +115 -0
  28. package/lib/typescript/commonjs/types.d.ts.map +1 -1
  29. package/lib/typescript/commonjs/wrap.d.ts +64 -10
  30. package/lib/typescript/commonjs/wrap.d.ts.map +1 -1
  31. package/lib/typescript/module/DefaultResolver.d.ts +10 -0
  32. package/lib/typescript/module/DefaultResolver.d.ts.map +1 -0
  33. package/lib/typescript/module/checkForUpdate.d.ts +2 -1
  34. package/lib/typescript/module/checkForUpdate.d.ts.map +1 -1
  35. package/lib/typescript/module/index.d.ts +3 -3
  36. package/lib/typescript/module/index.d.ts.map +1 -1
  37. package/lib/typescript/module/types.d.ts +115 -0
  38. package/lib/typescript/module/types.d.ts.map +1 -1
  39. package/lib/typescript/module/wrap.d.ts +64 -10
  40. package/lib/typescript/module/wrap.d.ts.map +1 -1
  41. package/package.json +7 -7
  42. package/src/DefaultResolver.ts +36 -0
  43. package/src/checkForUpdate.ts +43 -59
  44. package/src/index.ts +51 -19
  45. package/src/types.ts +135 -0
  46. package/src/wrap.tsx +161 -72
package/src/types.ts CHANGED
@@ -1,3 +1,138 @@
1
+ import type { AppUpdateInfo } from "@hot-updater/core";
2
+ import type { NotifyAppReadyResult } from "./native";
3
+
4
+ /**
5
+ * Parameters passed to resolver.checkUpdate method
6
+ */
7
+ export interface ResolverCheckUpdateParams {
8
+ /**
9
+ * The platform the app is running on
10
+ */
11
+ platform: "ios" | "android";
12
+
13
+ /**
14
+ * The current app version
15
+ */
16
+ appVersion: string;
17
+
18
+ /**
19
+ * The current bundle ID
20
+ */
21
+ bundleId: string;
22
+
23
+ /**
24
+ * Minimum bundle ID from build time
25
+ */
26
+ minBundleId: string;
27
+
28
+ /**
29
+ * The channel name (e.g., "production", "staging")
30
+ */
31
+ channel: string;
32
+
33
+ /**
34
+ * Update strategy being used
35
+ */
36
+ updateStrategy: "fingerprint" | "appVersion";
37
+
38
+ /**
39
+ * The fingerprint hash (only present when using fingerprint strategy)
40
+ */
41
+ fingerprintHash: string | null;
42
+
43
+ /**
44
+ * Request headers from global config (for optional use)
45
+ */
46
+ requestHeaders?: Record<string, string>;
47
+
48
+ /**
49
+ * Request timeout from global config (for optional use)
50
+ */
51
+ requestTimeout?: number;
52
+ }
53
+
54
+ /**
55
+ * Parameters passed to resolver.notifyAppReady method
56
+ */
57
+ export interface ResolverNotifyAppReadyParams {
58
+ /**
59
+ * The bundle state from native notifyAppReady
60
+ * - "PROMOTED": Staging bundle was promoted to stable
61
+ * - "RECOVERED": App recovered from crash, rollback occurred
62
+ * - "STABLE": No changes, bundle is stable
63
+ */
64
+ status: "PROMOTED" | "RECOVERED" | "STABLE";
65
+
66
+ /**
67
+ * Present only when status is "RECOVERED"
68
+ */
69
+ crashedBundleId?: string;
70
+
71
+ /**
72
+ * Request headers from global config (for optional use)
73
+ */
74
+ requestHeaders?: Record<string, string>;
75
+
76
+ /**
77
+ * Request timeout from global config (for optional use)
78
+ */
79
+ requestTimeout?: number;
80
+ }
81
+
82
+ /**
83
+ * Resolver interface for custom network operations
84
+ */
85
+ export interface HotUpdaterResolver {
86
+ /**
87
+ * Custom implementation for checking updates.
88
+ * When provided, this completely replaces the default fetchUpdateInfo flow.
89
+ *
90
+ * @param params - All parameters needed to check for updates
91
+ * @returns Update information or null if up to date
92
+ *
93
+ * @example
94
+ * ```typescript
95
+ * checkUpdate: async (params) => {
96
+ * const response = await fetch(`https://api.custom.com/check`, {
97
+ * method: 'POST',
98
+ * body: JSON.stringify(params),
99
+ * headers: params.requestHeaders,
100
+ * });
101
+ *
102
+ * if (!response.ok) return null;
103
+ * return response.json();
104
+ * }
105
+ * ```
106
+ */
107
+ checkUpdate?: (
108
+ params: ResolverCheckUpdateParams,
109
+ ) => Promise<AppUpdateInfo | null>;
110
+
111
+ /**
112
+ * Custom implementation for notifying app ready.
113
+ * When provided, this completely replaces the default notifyAppReady network flow.
114
+ * Note: The native notifyAppReady for bundle promotion still happens automatically.
115
+ *
116
+ * @param params - All parameters about the current app state
117
+ * @returns Notification result
118
+ *
119
+ * @example
120
+ * ```typescript
121
+ * notifyAppReady: async (params) => {
122
+ * await fetch(`https://api.custom.com/notify`, {
123
+ * method: 'POST',
124
+ * body: JSON.stringify(params),
125
+ * });
126
+ *
127
+ * return { status: "STABLE" };
128
+ * }
129
+ * ```
130
+ */
131
+ notifyAppReady?: (
132
+ params: ResolverNotifyAppReadyParams,
133
+ ) => Promise<NotifyAppReadyResult | undefined>;
134
+ }
135
+
1
136
  /**
2
137
  * Information about a signature verification failure.
3
138
  * This is a security-critical event that indicates the bundle
package/src/wrap.tsx CHANGED
@@ -9,6 +9,7 @@ import {
9
9
  reload,
10
10
  } from "./native";
11
11
  import { useHotUpdaterStore } from "./store";
12
+ import type { HotUpdaterResolver } from "./types";
12
13
 
13
14
  export interface RunUpdateProcessResponse {
14
15
  status: "ROLLBACK" | "UPDATE" | "UP_TO_DATE";
@@ -26,12 +27,6 @@ type UpdateStatus =
26
27
  * Common options shared between auto and manual update modes
27
28
  */
28
29
  interface CommonHotUpdaterOptions {
29
- /**
30
- * Base URL for update server
31
- * @example "https://update.example.com"
32
- */
33
- baseURL: string;
34
-
35
30
  /**
36
31
  * Custom request headers for update checks
37
32
  */
@@ -72,91 +67,190 @@ interface CommonHotUpdaterOptions {
72
67
  onNotifyAppReady?: (result: NotifyAppReadyResult) => void;
73
68
  }
74
69
 
75
- export interface AutoUpdateOptions extends CommonHotUpdaterOptions {
70
+ /**
71
+ * Configuration with baseURL for standard server-based updates
72
+ */
73
+ interface BaseURLConfig {
76
74
  /**
77
- * Update strategy
78
- * - "fingerprint": Use fingerprint hash to check for updates
79
- * - "appVersion": Use app version to check for updates
75
+ * Base URL for update server
76
+ * @example "https://update.example.com"
80
77
  */
81
- updateStrategy: "fingerprint" | "appVersion";
78
+ baseURL: string;
82
79
 
83
80
  /**
84
- * Update mode
85
- * - "auto": Automatically check and download updates
81
+ * Resolver is not allowed when using baseURL
86
82
  */
87
- updateMode: "auto";
83
+ resolver?: never;
84
+ }
88
85
 
89
- onError?: (error: HotUpdaterError | Error | unknown) => void;
86
+ /**
87
+ * Configuration with resolver for custom network operations
88
+ */
89
+ interface ResolverConfig {
90
+ /**
91
+ * Custom resolver for network operations
92
+ */
93
+ resolver: HotUpdaterResolver;
90
94
 
91
95
  /**
92
- * Component to show while downloading a new bundle update.
93
- *
94
- * When an update exists and the bundle is being downloaded, this component will block access
95
- * to the entry point and show download progress.
96
- *
97
- * @see {@link https://hot-updater.dev/docs/react-native-api/wrap#fallback-component}
98
- *
99
- * ```tsx
100
- * HotUpdater.wrap({
101
- * baseURL: "<update-server-url>",
102
- * updateStrategy: "appVersion",
103
- * fallbackComponent: ({ progress = 0 }) => (
104
- * <View style={styles.container}>
105
- * <Text style={styles.text}>Updating... {progress}%</Text>
106
- * </View>
107
- * )
108
- * })(App)
109
- * ```
110
- *
111
- * If not defined, the bundle will download in the background without blocking the screen.
96
+ * baseURL is not allowed when using resolver
112
97
  */
98
+ baseURL?: never;
99
+ }
100
+
101
+ /**
102
+ * Union type ensuring baseURL and resolver are mutually exclusive
103
+ */
104
+ type NetworkConfig = BaseURLConfig | ResolverConfig;
105
+
106
+ export type AutoUpdateOptions = CommonHotUpdaterOptions &
107
+ NetworkConfig & {
108
+ /**
109
+ * Update strategy
110
+ * - "fingerprint": Use fingerprint hash to check for updates
111
+ * - "appVersion": Use app version to check for updates
112
+ */
113
+ updateStrategy: "fingerprint" | "appVersion";
114
+
115
+ /**
116
+ * Update mode
117
+ * - "auto": Automatically check and download updates
118
+ */
119
+ updateMode: "auto";
120
+
121
+ onError?: (error: HotUpdaterError | Error | unknown) => void;
122
+
123
+ /**
124
+ * Component to show while downloading a new bundle update.
125
+ *
126
+ * When an update exists and the bundle is being downloaded, this component will block access
127
+ * to the entry point and show download progress.
128
+ *
129
+ * @see {@link https://hot-updater.dev/docs/react-native-api/wrap#fallback-component}
130
+ *
131
+ * ```tsx
132
+ * HotUpdater.wrap({
133
+ * baseURL: "<update-server-url>",
134
+ * updateStrategy: "appVersion",
135
+ * fallbackComponent: ({ progress = 0 }) => (
136
+ * <View style={styles.container}>
137
+ * <Text style={styles.text}>Updating... {progress}%</Text>
138
+ * </View>
139
+ * )
140
+ * })(App)
141
+ * ```
142
+ *
143
+ * If not defined, the bundle will download in the background without blocking the screen.
144
+ */
145
+ fallbackComponent?: React.FC<{
146
+ status: Exclude<UpdateStatus, "UPDATE_PROCESS_COMPLETED">;
147
+ progress: number;
148
+ message: string | null;
149
+ }>;
150
+
151
+ onProgress?: (progress: number) => void;
152
+
153
+ /**
154
+ * When a force update exists, the app will automatically reload.
155
+ * If `false`, When a force update exists, the app will not reload. `shouldForceUpdate` will be returned as `true` in `onUpdateProcessCompleted`.
156
+ * If `true`, When a force update exists, the app will automatically reload.
157
+ * @default true
158
+ */
159
+ reloadOnForceUpdate?: boolean;
160
+
161
+ /**
162
+ * Callback function that is called when the update process is completed.
163
+ *
164
+ * @see {@link https://hot-updater.dev/docs/react-native-api/wrap#onupdateprocesscompleted}
165
+ */
166
+ onUpdateProcessCompleted?: (response: RunUpdateProcessResponse) => void;
167
+ };
168
+
169
+ export type ManualUpdateOptions = CommonHotUpdaterOptions &
170
+ NetworkConfig & {
171
+ /**
172
+ * Update mode
173
+ * - "manual": Only notify app ready, user manually calls checkForUpdate()
174
+ */
175
+ updateMode: "manual";
176
+ };
177
+
178
+ export type HotUpdaterOptions = AutoUpdateOptions | ManualUpdateOptions;
179
+
180
+ /**
181
+ * Internal options after normalization in index.ts
182
+ * Always has resolver (never baseURL)
183
+ */
184
+ type InternalCommonOptions = {
185
+ resolver: HotUpdaterResolver;
186
+ requestHeaders?: Record<string, string>;
187
+ requestTimeout?: number;
188
+ onNotifyAppReady?: (result: NotifyAppReadyResult) => void;
189
+ };
190
+
191
+ type InternalAutoUpdateOptions = InternalCommonOptions & {
192
+ updateStrategy: "fingerprint" | "appVersion";
193
+ updateMode: "auto";
194
+ onError?: (error: HotUpdaterError | Error | unknown) => void;
113
195
  fallbackComponent?: React.FC<{
114
196
  status: Exclude<UpdateStatus, "UPDATE_PROCESS_COMPLETED">;
115
197
  progress: number;
116
198
  message: string | null;
117
199
  }>;
118
-
119
200
  onProgress?: (progress: number) => void;
120
-
121
- /**
122
- * When a force update exists, the app will automatically reload.
123
- * If `false`, When a force update exists, the app will not reload. `shouldForceUpdate` will be returned as `true` in `onUpdateProcessCompleted`.
124
- * If `true`, When a force update exists, the app will automatically reload.
125
- * @default true
126
- */
127
201
  reloadOnForceUpdate?: boolean;
128
-
129
- /**
130
- * Callback function that is called when the update process is completed.
131
- *
132
- * @see {@link https://hot-updater.dev/docs/react-native-api/wrap#onupdateprocesscompleted}
133
- */
134
202
  onUpdateProcessCompleted?: (response: RunUpdateProcessResponse) => void;
135
- }
203
+ };
136
204
 
137
- export interface ManualUpdateOptions extends CommonHotUpdaterOptions {
138
- /**
139
- * Update mode
140
- * - "manual": Only notify app ready, user manually calls checkForUpdate()
141
- */
205
+ type InternalManualUpdateOptions = InternalCommonOptions & {
142
206
  updateMode: "manual";
143
- }
207
+ };
144
208
 
145
- export type HotUpdaterOptions = AutoUpdateOptions | ManualUpdateOptions;
209
+ export type InternalWrapOptions =
210
+ | InternalAutoUpdateOptions
211
+ | InternalManualUpdateOptions;
212
+
213
+ /**
214
+ * Helper function to handle notifyAppReady flow
215
+ */
216
+ const handleNotifyAppReady = async (options: {
217
+ resolver?: HotUpdaterResolver;
218
+ requestHeaders?: Record<string, string>;
219
+ requestTimeout?: number;
220
+ onNotifyAppReady?: (result: NotifyAppReadyResult) => void;
221
+ }): Promise<void> => {
222
+ try {
223
+ // Always call native notifyAppReady for bundle promotion
224
+ const nativeResult = nativeNotifyAppReady();
225
+
226
+ // If resolver.notifyAppReady exists, call it with simplified params
227
+ if (options.resolver?.notifyAppReady) {
228
+ await options.resolver
229
+ .notifyAppReady({
230
+ status: nativeResult.status,
231
+ crashedBundleId: nativeResult.crashedBundleId,
232
+ requestHeaders: options.requestHeaders,
233
+ requestTimeout: options.requestTimeout,
234
+ })
235
+ .catch((e: unknown) => {
236
+ console.warn("[HotUpdater] Resolver notifyAppReady failed:", e);
237
+ });
238
+ }
239
+
240
+ options.onNotifyAppReady?.(nativeResult);
241
+ } catch (e) {
242
+ console.warn("[HotUpdater] Failed to notify app ready:", e);
243
+ }
244
+ };
146
245
 
147
246
  export function wrap<P extends React.JSX.IntrinsicAttributes = object>(
148
- options: HotUpdaterOptions,
247
+ options: InternalWrapOptions,
149
248
  ): (WrappedComponent: React.ComponentType<P>) => React.ComponentType<P> {
150
249
  if (options.updateMode === "manual") {
151
250
  return (WrappedComponent: React.ComponentType<P>) => {
152
251
  const ManualHOC: React.FC<P> = (props: P) => {
153
252
  useLayoutEffect(() => {
154
- try {
155
- const result = nativeNotifyAppReady();
156
- options.onNotifyAppReady?.(result);
157
- } catch (e) {
158
- console.warn("[HotUpdater] Failed to notify app ready:", e);
159
- }
253
+ void handleNotifyAppReady(options);
160
254
  }, []);
161
255
 
162
256
  return <WrappedComponent {...props} />;
@@ -182,12 +276,12 @@ export function wrap<P extends React.JSX.IntrinsicAttributes = object>(
182
276
  setUpdateStatus("CHECK_FOR_UPDATE");
183
277
 
184
278
  const updateInfo = await checkForUpdate({
185
- baseURL: restOptions.baseURL,
279
+ resolver: restOptions.resolver,
186
280
  updateStrategy: restOptions.updateStrategy,
187
281
  requestHeaders: restOptions.requestHeaders,
188
282
  requestTimeout: restOptions.requestTimeout,
189
283
  onError: restOptions.onError,
190
- } as Parameters<typeof checkForUpdate>[0]);
284
+ });
191
285
 
192
286
  setMessage(updateInfo?.message ?? null);
193
287
 
@@ -250,12 +344,7 @@ export function wrap<P extends React.JSX.IntrinsicAttributes = object>(
250
344
 
251
345
  // Notify native side that app is ready (JS bundle fully loaded)
252
346
  useLayoutEffect(() => {
253
- try {
254
- const result = nativeNotifyAppReady();
255
- restOptions.onNotifyAppReady?.(result);
256
- } catch (e) {
257
- console.warn("[HotUpdater] Failed to notify app ready:", e);
258
- }
347
+ void handleNotifyAppReady(restOptions);
259
348
  }, []);
260
349
 
261
350
  // Start update check