@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
@@ -1,7 +1,6 @@
1
- import type { AppUpdateInfo, UpdateBundleParams } from "@hot-updater/core";
1
+ import type { AppUpdateInfo } from "@hot-updater/core";
2
2
  import { Platform } from "react-native";
3
3
  import { HotUpdaterError } from "./error";
4
- import { fetchUpdateInfo, type UpdateSource } from "./fetchUpdateInfo";
5
4
  import {
6
5
  getAppVersion,
7
6
  getBundleId,
@@ -10,9 +9,17 @@ import {
10
9
  getMinBundleId,
11
10
  updateBundle,
12
11
  } from "./native";
12
+ import type { HotUpdaterResolver } from "./types";
13
13
 
14
14
  export interface CheckForUpdateOptions {
15
- source: UpdateSource;
15
+ /**
16
+ * Update strategy
17
+ * - "fingerprint": Use fingerprint hash to check for updates
18
+ * - "appVersion": Use app version to check for updates
19
+ * - Can override the strategy set in HotUpdater.wrap()
20
+ */
21
+ updateStrategy: "appVersion" | "fingerprint";
22
+
16
23
  requestHeaders?: Record<string, string>;
17
24
  onError?: (error: Error) => void;
18
25
  /**
@@ -30,8 +37,13 @@ export type CheckForUpdateResult = AppUpdateInfo & {
30
37
  updateBundle: () => Promise<boolean>;
31
38
  };
32
39
 
40
+ // Internal type that includes resolver for use within index.ts
41
+ export interface InternalCheckForUpdateOptions extends CheckForUpdateOptions {
42
+ resolver: HotUpdaterResolver;
43
+ }
44
+
33
45
  export async function checkForUpdate(
34
- options: CheckForUpdateOptions,
46
+ options: InternalCheckForUpdateOptions,
35
47
  ): Promise<CheckForUpdateResult | null> {
36
48
  if (__DEV__) {
37
49
  return null;
@@ -57,62 +69,45 @@ export async function checkForUpdate(
57
69
 
58
70
  const fingerprintHash = getFingerprintHash();
59
71
 
60
- return fetchUpdateInfo({
61
- source: options.source,
62
- params: {
63
- bundleId: currentBundleId,
64
- appVersion: currentAppVersion,
72
+ if (!options.resolver?.checkUpdate) {
73
+ options.onError?.(
74
+ new HotUpdaterError("Resolver is required but not configured"),
75
+ );
76
+ return null;
77
+ }
78
+
79
+ let updateInfo: AppUpdateInfo | null = null;
80
+
81
+ try {
82
+ updateInfo = await options.resolver.checkUpdate({
65
83
  platform,
84
+ appVersion: currentAppVersion,
85
+ bundleId: currentBundleId,
66
86
  minBundleId,
67
87
  channel,
88
+ updateStrategy: options.updateStrategy,
68
89
  fingerprintHash,
69
- },
70
- requestHeaders: options.requestHeaders,
71
- onError: options.onError,
72
- requestTimeout: options.requestTimeout,
73
- }).then((updateInfo) => {
74
- if (!updateInfo) {
75
- return null;
76
- }
77
-
78
- return {
79
- ...updateInfo,
80
- updateBundle: async () => {
81
- return updateBundle({
82
- bundleId: updateInfo.id,
83
- fileUrl: updateInfo.fileUrl,
84
- fileHash: updateInfo.fileHash,
85
- status: updateInfo.status,
86
- });
87
- },
88
- };
89
- });
90
- }
90
+ requestHeaders: options.requestHeaders,
91
+ requestTimeout: options.requestTimeout,
92
+ });
93
+ } catch (error) {
94
+ options.onError?.(error as Error);
95
+ return null;
96
+ }
91
97
 
92
- export interface GetUpdateSourceOptions {
93
- /**
94
- * The update strategy to use.
95
- * @description
96
- * - "fingerprint": Use the fingerprint hash to check for updates.
97
- * - "appVersion": Use the target app version to check for updates.
98
- */
99
- updateStrategy: "appVersion" | "fingerprint";
100
- }
98
+ if (!updateInfo) {
99
+ return null;
100
+ }
101
101
 
102
- export const getUpdateSource =
103
- (baseUrl: string, options: GetUpdateSourceOptions) =>
104
- (params: UpdateBundleParams) => {
105
- switch (options.updateStrategy) {
106
- case "fingerprint": {
107
- if (!params.fingerprintHash) {
108
- throw new HotUpdaterError("Fingerprint hash is required");
109
- }
110
- return `${baseUrl}/fingerprint/${params.platform}/${params.fingerprintHash}/${params.channel}/${params.minBundleId}/${params.bundleId}`;
111
- }
112
- case "appVersion": {
113
- return `${baseUrl}/app-version/${params.platform}/${params.appVersion}/${params.channel}/${params.minBundleId}/${params.bundleId}`;
114
- }
115
- default:
116
- return baseUrl;
117
- }
102
+ return {
103
+ ...updateInfo,
104
+ updateBundle: async () => {
105
+ return updateBundle({
106
+ bundleId: updateInfo.id,
107
+ fileUrl: updateInfo.fileUrl,
108
+ fileHash: updateInfo.fileHash,
109
+ status: updateInfo.status,
110
+ });
111
+ },
118
112
  };
113
+ }
package/src/error.ts CHANGED
@@ -1,3 +1,156 @@
1
+ /**
2
+ * Hot Updater Error Codes
3
+ *
4
+ * This file defines all possible error codes that can be thrown by the native
5
+ * updateBundle function. These error codes are shared across iOS and Android
6
+ * implementations to ensure consistent error handling.
7
+ *
8
+ * Error Classification:
9
+ * - Parameter Validation: Invalid or missing function parameters
10
+ * - Bundle Storage: Errors during download, extraction, and storage
11
+ * - Signature Verification: Cryptographic verification failures (collapsed to a single public code)
12
+ * - Internal: Platform-specific or unexpected errors
13
+ *
14
+ * Retryability:
15
+ * - Retryable: DOWNLOAD_FAILED, INCOMPLETE_DOWNLOAD
16
+ * - Non-retryable: Most validation and verification errors
17
+ */
18
+
19
+ export enum HotUpdaterErrorCode {
20
+ // ==================== Parameter Validation Errors ====================
21
+
22
+ /**
23
+ * Bundle ID is missing or empty.
24
+ * Thrown when bundleId parameter is null, undefined, or empty string.
25
+ * @retryable false
26
+ */
27
+ MISSING_BUNDLE_ID = "MISSING_BUNDLE_ID",
28
+
29
+ /**
30
+ * File URL is invalid or malformed.
31
+ * Thrown when fileUrl parameter cannot be parsed as a valid URL.
32
+ * @retryable false
33
+ */
34
+ INVALID_FILE_URL = "INVALID_FILE_URL",
35
+
36
+ // ==================== Bundle Storage Errors ====================
37
+
38
+ /**
39
+ * Failed to create required directory for bundle storage.
40
+ * Thrown when bundle directory creation fails due to permissions or disk errors.
41
+ * @retryable false - Usually indicates permissions or filesystem corruption
42
+ */
43
+ DIRECTORY_CREATION_FAILED = "DIRECTORY_CREATION_FAILED",
44
+
45
+ /**
46
+ * Bundle download failed.
47
+ * Covers network errors, HTTP errors (4xx/5xx), timeouts, and connection issues.
48
+ * Check error message for specific cause (network, HTTP status code, etc.).
49
+ * @retryable true - Network issues are often transient
50
+ */
51
+ DOWNLOAD_FAILED = "DOWNLOAD_FAILED",
52
+
53
+ /**
54
+ * Download incomplete - received size doesn't match expected size.
55
+ * Thrown when downloaded file size doesn't match Content-Length header.
56
+ * Error message includes both expected and actual byte counts.
57
+ * @retryable true - Download may succeed on retry
58
+ */
59
+ INCOMPLETE_DOWNLOAD = "INCOMPLETE_DOWNLOAD",
60
+
61
+ /**
62
+ * Bundle archive format is invalid or corrupted.
63
+ * Thrown when ZIP file has wrong magic bytes, invalid structure, or unsupported format.
64
+ * Also thrown for path traversal attempts during extraction.
65
+ * @retryable false - Indicates corrupted or malicious bundle
66
+ */
67
+ EXTRACTION_FORMAT_ERROR = "EXTRACTION_FORMAT_ERROR",
68
+
69
+ /**
70
+ * Bundle missing required platform files.
71
+ * Thrown when extracted bundle doesn't contain index.android.bundle (Android)
72
+ * or main.jsbundle (iOS).
73
+ * @retryable false - Indicates incorrectly built bundle
74
+ */
75
+ INVALID_BUNDLE = "INVALID_BUNDLE",
76
+
77
+ /**
78
+ * Insufficient disk space for bundle download and extraction.
79
+ * Thrown when available disk space is less than required (file size * 2).
80
+ * Error message includes required and available bytes.
81
+ * @retryable false - User must free up disk space
82
+ */
83
+ INSUFFICIENT_DISK_SPACE = "INSUFFICIENT_DISK_SPACE",
84
+
85
+ /**
86
+ * Bundle signature verification failed (general).
87
+ * Thrown when cryptographic signature verification fails.
88
+ * All signature/hash sub-errors are collapsed into this public code.
89
+ * @retryable false - Indicates tampered or incorrectly signed bundle
90
+ */
91
+ SIGNATURE_VERIFICATION_FAILED = "SIGNATURE_VERIFICATION_FAILED",
92
+
93
+ /**
94
+ * Failed to move bundle to final location.
95
+ * Thrown when atomic move from temp directory to final directory fails.
96
+ * iOS: Thrown if move operation fails.
97
+ * Android: Thrown if rename, move, AND copy all fail.
98
+ * @retryable false - Usually indicates filesystem corruption or permissions
99
+ */
100
+ MOVE_OPERATION_FAILED = "MOVE_OPERATION_FAILED",
101
+
102
+ /**
103
+ * Bundle is in crashed history and cannot be applied.
104
+ * Thrown when attempting to install a bundle that previously caused a crash.
105
+ * Use HotUpdater.clearCrashHistory() to allow retrying this bundle.
106
+ * @retryable false - Bundle was marked as crashed for safety
107
+ */
108
+ BUNDLE_IN_CRASHED_HISTORY = "BUNDLE_IN_CRASHED_HISTORY",
109
+
110
+ // ==================== Signature Verification Errors ====================
111
+ // (Collapsed into SIGNATURE_VERIFICATION_FAILED)
112
+
113
+ // ==================== Internal Errors ====================
114
+
115
+ /**
116
+ * Internal error: self deallocated during update (iOS only).
117
+ * Thrown when the native object is deallocated mid-operation.
118
+ * iOS-specific due to manual memory management (ARC).
119
+ * Not applicable to Android (uses garbage collection).
120
+ * @platform iOS
121
+ * @retryable false - Memory management issue
122
+ */
123
+ SELF_DEALLOCATED = "SELF_DEALLOCATED",
124
+
125
+ /**
126
+ * An unknown or unexpected error occurred.
127
+ * Catch-all for errors that don't fit other categories.
128
+ * Check error message for details.
129
+ * @retryable unknown - Depends on underlying cause
130
+ */
131
+ UNKNOWN_ERROR = "UNKNOWN_ERROR",
132
+ }
133
+
134
+ /**
135
+ * Type guard to check if an error is a HotUpdaterError
136
+ */
137
+ export function isHotUpdaterError(
138
+ error: unknown,
139
+ ): error is { code: HotUpdaterErrorCode; message: string } {
140
+ return (
141
+ typeof error === "object" &&
142
+ error !== null &&
143
+ "code" in error &&
144
+ typeof error.code === "string" &&
145
+ Object.values(HotUpdaterErrorCode).includes(
146
+ error.code as HotUpdaterErrorCode,
147
+ )
148
+ );
149
+ }
150
+
151
+ /**
152
+ * Base error class for Hot Updater
153
+ */
1
154
  export class HotUpdaterError extends Error {
2
155
  constructor(message: string) {
3
156
  super(message);
@@ -1,76 +1,28 @@
1
- import type {
2
- AppUpdateInfo,
3
- UpdateBundleParams,
4
- UpdateStrategy,
5
- } from "@hot-updater/core";
6
-
7
- export type UpdateSource =
8
- | string
9
- | ((params: UpdateBundleParams) => Promise<AppUpdateInfo | null>)
10
- | ((params: UpdateBundleParams) => string);
11
-
12
- function buildRequestHeaders(
13
- params: UpdateBundleParams,
14
- requestHeaders?: Record<string, string>,
15
- ): Record<string, string> {
16
- const updateStrategy: UpdateStrategy = params.fingerprintHash
17
- ? "fingerprint"
18
- : "appVersion";
19
-
20
- return {
21
- "Content-Type": "application/json",
22
- "x-app-platform": params.platform,
23
- "x-bundle-id": params.bundleId,
24
- ...(updateStrategy === "fingerprint"
25
- ? { "x-fingerprint-hash": params.fingerprintHash! }
26
- : { "x-app-version": params.appVersion }),
27
- ...(params.minBundleId && { "x-min-bundle-id": params.minBundleId }),
28
- ...(params.channel && { "x-channel": params.channel }),
29
- ...requestHeaders,
30
- };
31
- }
32
-
33
- async function resolveSource(
34
- source: UpdateSource,
35
- params: UpdateBundleParams,
36
- ): Promise<{ url: string } | { info: AppUpdateInfo | null }> {
37
- if (typeof source !== "function") {
38
- return { url: source };
39
- }
40
- const result = source(params);
41
- if (typeof result === "string") {
42
- return { url: result };
43
- }
44
- return { info: await result };
45
- }
1
+ import type { AppUpdateInfo } from "@hot-updater/core";
46
2
 
47
3
  export const fetchUpdateInfo = async ({
48
- source,
49
- params,
4
+ url,
50
5
  requestHeaders,
51
6
  onError,
52
7
  requestTimeout = 5000,
53
8
  }: {
54
- source: UpdateSource;
55
- params: UpdateBundleParams;
9
+ url: string;
56
10
  requestHeaders?: Record<string, string>;
57
11
  onError?: (error: Error) => void;
58
12
  requestTimeout?: number;
59
13
  }): Promise<AppUpdateInfo | null> => {
60
14
  try {
61
- const resolvedSource = await resolveSource(source, params);
62
- if ("info" in resolvedSource) {
63
- return resolvedSource.info;
64
- }
65
-
66
15
  const controller = new AbortController();
67
16
  const timeoutId = setTimeout(() => {
68
17
  controller.abort();
69
18
  }, requestTimeout);
70
19
 
71
- const headers = buildRequestHeaders(params, requestHeaders);
20
+ const headers = {
21
+ "Content-Type": "application/json",
22
+ ...requestHeaders,
23
+ };
72
24
 
73
- const response = await fetch(resolvedSource.url, {
25
+ const response = await fetch(url, {
74
26
  signal: controller.signal,
75
27
  headers,
76
28
  });
@@ -80,8 +32,8 @@ export const fetchUpdateInfo = async ({
80
32
  throw new Error(response.statusText);
81
33
  }
82
34
  return response.json();
83
- } catch (error: any) {
84
- if (error.name === "AbortError") {
35
+ } catch (error: unknown) {
36
+ if (error instanceof Error && error.name === "AbortError") {
85
37
  onError?.(new Error("Request timed out"));
86
38
  } else {
87
39
  onError?.(error as Error);