@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
@@ -19,8 +19,71 @@ export interface UpdateBundleParams {
19
19
  export interface Spec extends TurboModule {
20
20
  // Methods
21
21
  reload(): Promise<void>;
22
+ /**
23
+ * Downloads and applies a bundle update.
24
+ *
25
+ * @param params - Update bundle parameters
26
+ * @returns Promise that resolves to true if successful
27
+ * @throws {HotUpdaterErrorCode} Rejects with one of the following error codes:
28
+ *
29
+ * Parameter validation:
30
+ * - MISSING_BUNDLE_ID: Missing or empty bundleId
31
+ * - INVALID_FILE_URL: Invalid fileUrl provided
32
+ *
33
+ * Bundle storage:
34
+ * - DIRECTORY_CREATION_FAILED: Failed to create bundle directory
35
+ * - DOWNLOAD_FAILED: Failed to download bundle
36
+ * - INCOMPLETE_DOWNLOAD: Download incomplete (size mismatch)
37
+ * - EXTRACTION_FORMAT_ERROR: Invalid or corrupted archive format
38
+ * - INVALID_BUNDLE: Bundle missing required platform files
39
+ * - INSUFFICIENT_DISK_SPACE: Insufficient disk space
40
+ * - MOVE_OPERATION_FAILED: Failed to move bundle files
41
+ * - BUNDLE_IN_CRASHED_HISTORY: Bundle was previously marked as crashed
42
+ *
43
+ * Signature:
44
+ * - SIGNATURE_VERIFICATION_FAILED: Any signature/hash verification failure
45
+ *
46
+ * Internal:
47
+ * - SELF_DEALLOCATED: Native object was deallocated (iOS)
48
+ * - UNKNOWN_ERROR: Fallback for rare or platform-specific errors
49
+ *
50
+ * Note: iOS normalizes rare signature/storage errors to SIGNATURE_VERIFICATION_FAILED
51
+ * or UNKNOWN_ERROR to keep the JS error surface small.
52
+ */
22
53
  updateBundle(params: UpdateBundleParams): Promise<boolean>;
23
54
 
55
+ /**
56
+ * Notifies the native side that the app has successfully started with the given bundle.
57
+ * If the bundle matches the staging bundle, it promotes to stable.
58
+ *
59
+ * @param params - Parameters containing the bundle ID
60
+ * @returns Object with status and optional crashedBundleId
61
+ * - `status: "PROMOTED"` - Staging bundle was promoted to stable (ACTIVE event)
62
+ * - `status: "RECOVERED"` - App recovered from crash, rollback occurred (ROLLBACK event)
63
+ * - `status: "STABLE"` - No changes, already stable
64
+ * - `crashedBundleId` - Present only when status is "RECOVERED"
65
+ */
66
+ notifyAppReady(params: { bundleId: string }): {
67
+ status: "PROMOTED" | "RECOVERED" | "STABLE";
68
+ crashedBundleId?: string;
69
+ };
70
+
71
+ /**
72
+ * Gets the list of bundle IDs that have been marked as crashed.
73
+ * These bundles will be rejected if attempted to install again.
74
+ *
75
+ * @returns Array of crashed bundle IDs
76
+ */
77
+ getCrashHistory(): string[];
78
+
79
+ /**
80
+ * Clears the crashed bundle history, allowing previously crashed bundles
81
+ * to be installed again.
82
+ *
83
+ * @returns true if clearing was successful
84
+ */
85
+ clearCrashHistory(): boolean;
86
+
24
87
  // EventEmitter
25
88
  addListener(eventName: string): void;
26
89
  removeListeners(count: number): void;
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
@@ -1,63 +1,266 @@
1
1
  import React, { useEffect, useLayoutEffect, useState } from "react";
2
- import { type CheckForUpdateOptions, checkForUpdate } from "./checkForUpdate";
2
+ import { checkForUpdate } from "./checkForUpdate";
3
3
  import type { HotUpdaterError } from "./error";
4
4
  import { useEventCallback } from "./hooks/useEventCallback";
5
- import { getBundleId, reload } from "./native";
6
- import type { RunUpdateProcessResponse } from "./runUpdateProcess";
5
+ import {
6
+ getBundleId,
7
+ type NotifyAppReadyResult,
8
+ notifyAppReady as nativeNotifyAppReady,
9
+ reload,
10
+ } from "./native";
7
11
  import { useHotUpdaterStore } from "./store";
12
+ import type { HotUpdaterResolver } from "./types";
13
+
14
+ export interface RunUpdateProcessResponse {
15
+ status: "ROLLBACK" | "UPDATE" | "UP_TO_DATE";
16
+ shouldForceUpdate: boolean;
17
+ message: string | null;
18
+ id: string;
19
+ }
8
20
 
9
21
  type UpdateStatus =
10
22
  | "CHECK_FOR_UPDATE"
11
23
  | "UPDATING"
12
24
  | "UPDATE_PROCESS_COMPLETED";
13
25
 
14
- export interface HotUpdaterOptions extends CheckForUpdateOptions {
26
+ /**
27
+ * Common options shared between auto and manual update modes
28
+ */
29
+ interface CommonHotUpdaterOptions {
15
30
  /**
16
- * Component to show while downloading a new bundle update.
17
- *
18
- * When an update exists and the bundle is being downloaded, this component will block access
19
- * to the entry point and show download progress.
31
+ * Custom request headers for update checks
32
+ */
33
+ requestHeaders?: Record<string, string>;
34
+
35
+ /**
36
+ * Request timeout in milliseconds
37
+ * @default 5000
38
+ */
39
+ requestTimeout?: number;
40
+
41
+ /**
42
+ * Callback invoked when the app is ready and bundle verification completes.
43
+ * Provides information about bundle promotion, recovery from crashes, or stable state.
20
44
  *
21
- * @see {@link https://hot-updater.dev/docs/react-native-api/wrap#fallback-component}
45
+ * @param result - Bundle state information
46
+ * @param result.status - Current bundle state:
47
+ * - "PROMOTED": Staging bundle was promoted to stable (new update applied)
48
+ * - "RECOVERED": App recovered from a crash, rollback occurred
49
+ * - "STABLE": No changes, bundle is stable
50
+ * @param result.crashedBundleId - Present only when status is "RECOVERED"
22
51
  *
52
+ * @example
23
53
  * ```tsx
24
54
  * HotUpdater.wrap({
25
- * source: "<update-server-url>",
26
- * fallbackComponent: ({ progress = 0 }) => (
27
- * <View style={styles.container}>
28
- * <Text style={styles.text}>Updating... {progress}%</Text>
29
- * </View>
30
- * )
31
- * })(App)
55
+ * baseURL: "https://api.example.com",
56
+ * updateMode: "manual",
57
+ * onNotifyAppReady: ({ status, crashedBundleId }) => {
58
+ * if (status === "RECOVERED") {
59
+ * analytics.track('bundle_rollback', { crashedBundleId });
60
+ * } else if (status === "PROMOTED") {
61
+ * analytics.track('bundle_promoted');
62
+ * }
63
+ * }
64
+ * })(App);
32
65
  * ```
33
- *
34
- * If not defined, the bundle will download in the background without blocking the screen.
35
66
  */
67
+ onNotifyAppReady?: (result: NotifyAppReadyResult) => void;
68
+ }
69
+
70
+ /**
71
+ * Configuration with baseURL for standard server-based updates
72
+ */
73
+ interface BaseURLConfig {
74
+ /**
75
+ * Base URL for update server
76
+ * @example "https://update.example.com"
77
+ */
78
+ baseURL: string;
79
+
80
+ /**
81
+ * Resolver is not allowed when using baseURL
82
+ */
83
+ resolver?: never;
84
+ }
85
+
86
+ /**
87
+ * Configuration with resolver for custom network operations
88
+ */
89
+ interface ResolverConfig {
90
+ /**
91
+ * Custom resolver for network operations
92
+ */
93
+ resolver: HotUpdaterResolver;
94
+
95
+ /**
96
+ * baseURL is not allowed when using resolver
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;
36
195
  fallbackComponent?: React.FC<{
37
196
  status: Exclude<UpdateStatus, "UPDATE_PROCESS_COMPLETED">;
38
197
  progress: number;
39
198
  message: string | null;
40
199
  }>;
41
- onError?: (error: HotUpdaterError | Error | unknown) => void;
42
200
  onProgress?: (progress: number) => void;
43
- /**
44
- * When a force update exists, the app will automatically reload.
45
- * If `false`, When a force update exists, the app will not reload. `shouldForceUpdate` will be returned as `true` in `onUpdateProcessCompleted`.
46
- * If `true`, When a force update exists, the app will automatically reload.
47
- * @default true
48
- */
49
201
  reloadOnForceUpdate?: boolean;
50
- /**
51
- * Callback function that is called when the update process is completed.
52
- *
53
- * @see {@link https://hot-updater.dev/docs/react-native-api/wrap#onupdateprocesscompleted}
54
- */
55
202
  onUpdateProcessCompleted?: (response: RunUpdateProcessResponse) => void;
56
- }
203
+ };
204
+
205
+ type InternalManualUpdateOptions = InternalCommonOptions & {
206
+ updateMode: "manual";
207
+ };
208
+
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
+ };
57
245
 
58
246
  export function wrap<P extends React.JSX.IntrinsicAttributes = object>(
59
- options: HotUpdaterOptions,
247
+ options: InternalWrapOptions,
60
248
  ): (WrappedComponent: React.ComponentType<P>) => React.ComponentType<P> {
249
+ if (options.updateMode === "manual") {
250
+ return (WrappedComponent: React.ComponentType<P>) => {
251
+ const ManualHOC: React.FC<P> = (props: P) => {
252
+ useLayoutEffect(() => {
253
+ void handleNotifyAppReady(options);
254
+ }, []);
255
+
256
+ return <WrappedComponent {...props} />;
257
+ };
258
+
259
+ return ManualHOC as React.ComponentType<P>;
260
+ };
261
+ }
262
+
263
+ // updateMode: "auto"
61
264
  const { reloadOnForceUpdate = true, ...restOptions } = options;
62
265
 
63
266
  return (WrappedComponent: React.ComponentType<P>) => {
@@ -73,8 +276,10 @@ export function wrap<P extends React.JSX.IntrinsicAttributes = object>(
73
276
  setUpdateStatus("CHECK_FOR_UPDATE");
74
277
 
75
278
  const updateInfo = await checkForUpdate({
76
- source: restOptions.source,
279
+ resolver: restOptions.resolver,
280
+ updateStrategy: restOptions.updateStrategy,
77
281
  requestHeaders: restOptions.requestHeaders,
282
+ requestTimeout: restOptions.requestTimeout,
78
283
  onError: restOptions.onError,
79
284
  });
80
285
 
@@ -92,7 +297,7 @@ export function wrap<P extends React.JSX.IntrinsicAttributes = object>(
92
297
  }
93
298
 
94
299
  if (updateInfo.shouldForceUpdate === false) {
95
- void updateInfo.updateBundle().catch((error) => {
300
+ void updateInfo.updateBundle().catch((error: unknown) => {
96
301
  restOptions.onError?.(error);
97
302
  });
98
303
 
@@ -137,6 +342,12 @@ export function wrap<P extends React.JSX.IntrinsicAttributes = object>(
137
342
  restOptions.onProgress?.(progress);
138
343
  }, [progress]);
139
344
 
345
+ // Notify native side that app is ready (JS bundle fully loaded)
346
+ useLayoutEffect(() => {
347
+ void handleNotifyAppReady(restOptions);
348
+ }, []);
349
+
350
+ // Start update check
140
351
  useLayoutEffect(() => {
141
352
  initHotUpdater();
142
353
  }, []);
@@ -1,52 +0,0 @@
1
- package com.hotupdater
2
-
3
- import android.content.Context
4
-
5
- /**
6
- * Factory for creating HotUpdaterImpl instances with proper dependency injection
7
- */
8
- object HotUpdaterFactory {
9
- @Volatile
10
- private var instance: HotUpdaterImpl? = null
11
-
12
- /**
13
- * Gets the singleton instance of HotUpdaterImpl
14
- * @param context Application context
15
- * @return HotUpdaterImpl instance
16
- */
17
- fun getInstance(context: Context): HotUpdaterImpl =
18
- instance ?: synchronized(this) {
19
- instance ?: createHotUpdaterImpl(context).also { instance = it }
20
- }
21
-
22
- /**
23
- * Creates a new HotUpdaterImpl instance with all dependencies
24
- * @param context Application context
25
- * @return New HotUpdaterImpl instance
26
- */
27
- private fun createHotUpdaterImpl(context: Context): HotUpdaterImpl {
28
- val appContext = context.applicationContext
29
-
30
- // Get isolation key using the utility method
31
- val isolationKey = HotUpdaterImpl.getIsolationKey(appContext)
32
-
33
- // Create services
34
- val fileSystem = FileManagerService(appContext)
35
- val preferences = VersionedPreferencesService(appContext, isolationKey)
36
- val downloadService = OkHttpDownloadService()
37
- val decompressService = DecompressService()
38
-
39
- // Create bundle storage with dependencies
40
- val bundleStorage =
41
- BundleFileStorageService(
42
- appContext,
43
- fileSystem,
44
- downloadService,
45
- decompressService,
46
- preferences,
47
- )
48
-
49
- // Create and return the implementation
50
- return HotUpdaterImpl(appContext, bundleStorage, preferences)
51
- }
52
- }
@@ -1,24 +0,0 @@
1
- import Foundation
2
-
3
- @objcMembers
4
- public class HotUpdaterFactory: NSObject {
5
- public static let shared = HotUpdaterFactory()
6
-
7
- private override init() {}
8
-
9
- public func create() -> HotUpdaterImpl {
10
- let fileSystem = FileManagerService()
11
- let preferences = VersionedPreferencesService()
12
- let downloadService = URLSessionDownloadService()
13
- let decompressService = DecompressService()
14
-
15
- let bundleStorage = BundleFileStorageService(
16
- fileSystem: fileSystem,
17
- downloadService: downloadService,
18
- decompressService: decompressService,
19
- preferences: preferences
20
- )
21
-
22
- return HotUpdaterImpl(bundleStorage: bundleStorage, preferences: preferences)
23
- }
24
- }
@@ -1,69 +0,0 @@
1
- "use strict";
2
-
3
- Object.defineProperty(exports, "__esModule", {
4
- value: true
5
- });
6
- exports.runUpdateProcess = void 0;
7
- var _checkForUpdate = require("./checkForUpdate.js");
8
- var _native = require("./native.js");
9
- /**
10
- * Manually checks and applies updates for the application.
11
- *
12
- * @param {RunUpdateProcessConfig} config - Update process configuration
13
- * @param {string} config.source - Update server URL
14
- * @param {Record<string, string>} [config.requestHeaders] - Request headers
15
- * @param {boolean} [config.reloadOnForceUpdate=true] - Whether to automatically reload on force update
16
- *
17
- * @example
18
- * ```ts
19
- * // Auto reload on force update
20
- * const result = await HotUpdater.runUpdateProcess({
21
- * source: "<your-update-server-url>",
22
- * requestHeaders: {
23
- * // Add necessary headers
24
- * },
25
- * reloadOnForceUpdate: true
26
- * });
27
- *
28
- * // Manually handle reload on force update
29
- * const result = await HotUpdater.runUpdateProcess({
30
- * source: "<your-update-server-url>",
31
- * reloadOnForceUpdate: false
32
- * });
33
- *
34
- * if(result.status !== "UP_TO_DATE" && result.shouldForceUpdate) {
35
- * await HotUpdater.reload();
36
- * }
37
- * ```
38
- *
39
- * @returns {Promise<RunUpdateProcessResponse>} The result of the update process
40
- */
41
- const runUpdateProcess = async ({
42
- reloadOnForceUpdate = true,
43
- ...checkForUpdateOptions
44
- }) => {
45
- const updateInfo = await (0, _checkForUpdate.checkForUpdate)(checkForUpdateOptions);
46
- if (!updateInfo) {
47
- return {
48
- status: "UP_TO_DATE",
49
- shouldForceUpdate: false,
50
- message: null,
51
- id: (0, _native.getBundleId)()
52
- };
53
- }
54
- const isUpdated = await updateInfo.updateBundle();
55
- if (isUpdated && updateInfo.shouldForceUpdate && reloadOnForceUpdate) {
56
- await (0, _native.reload)();
57
- }
58
- if (!isUpdated) {
59
- throw new Error("New update was found but failed to download the bundle.");
60
- }
61
- return {
62
- status: updateInfo.status,
63
- shouldForceUpdate: updateInfo.shouldForceUpdate,
64
- id: updateInfo.id,
65
- message: updateInfo.message
66
- };
67
- };
68
- exports.runUpdateProcess = runUpdateProcess;
69
- //# sourceMappingURL=runUpdateProcess.js.map
@@ -1 +0,0 @@
1
- {"version":3,"names":["_checkForUpdate","require","_native","runUpdateProcess","reloadOnForceUpdate","checkForUpdateOptions","updateInfo","checkForUpdate","status","shouldForceUpdate","message","id","getBundleId","isUpdated","updateBundle","reload","Error","exports"],"sourceRoot":"../../src","sources":["runUpdateProcess.ts"],"mappings":";;;;;;AAAA,IAAAA,eAAA,GAAAC,OAAA;AACA,IAAAC,OAAA,GAAAD,OAAA;AAkBA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO,MAAME,gBAAgB,GAAG,MAAAA,CAAO;EACrCC,mBAAmB,GAAG,IAAI;EAC1B,GAAGC;AACoB,CAAC,KAAwC;EAChE,MAAMC,UAAU,GAAG,MAAM,IAAAC,8BAAc,EAACF,qBAAqB,CAAC;EAC9D,IAAI,CAACC,UAAU,EAAE;IACf,OAAO;MACLE,MAAM,EAAE,YAAY;MACpBC,iBAAiB,EAAE,KAAK;MACxBC,OAAO,EAAE,IAAI;MACbC,EAAE,EAAE,IAAAC,mBAAW,EAAC;IAClB,CAAC;EACH;EAEA,MAAMC,SAAS,GAAG,MAAMP,UAAU,CAACQ,YAAY,CAAC,CAAC;EACjD,IAAID,SAAS,IAAIP,UAAU,CAACG,iBAAiB,IAAIL,mBAAmB,EAAE;IACpE,MAAM,IAAAW,cAAM,EAAC,CAAC;EAChB;EAEA,IAAI,CAACF,SAAS,EAAE;IACd,MAAM,IAAIG,KAAK,CAAC,yDAAyD,CAAC;EAC5E;EACA,OAAO;IACLR,MAAM,EAAEF,UAAU,CAACE,MAAM;IACzBC,iBAAiB,EAAEH,UAAU,CAACG,iBAAiB;IAC/CE,EAAE,EAAEL,UAAU,CAACK,EAAE;IACjBD,OAAO,EAAEJ,UAAU,CAACI;EACtB,CAAC;AACH,CAAC;AAACO,OAAA,CAAAd,gBAAA,GAAAA,gBAAA","ignoreList":[]}