@hot-updater/react-native 0.30.12 → 0.31.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 (119) hide show
  1. package/HotUpdater.podspec +1 -0
  2. package/android/build.gradle +1 -0
  3. package/android/src/main/java/com/hotupdater/BsdiffPatch.kt +284 -0
  4. package/android/src/main/java/com/hotupdater/BundleFileStorageService.kt +885 -5
  5. package/android/src/main/java/com/hotupdater/HotUpdaterImpl.kt +13 -2
  6. package/android/src/main/java/com/hotupdater/OkHttpDownloadService.kt +37 -8
  7. package/android/src/main/java/com/hotupdater/ReactNativeValueConverters.kt +48 -12
  8. package/android/src/main/java/com/hotupdater/SignatureVerifier.kt +60 -22
  9. package/android/src/newarch/HotUpdater.kt +14 -2
  10. package/android/src/newarch/HotUpdaterModule.kt +112 -1
  11. package/android/src/oldarch/HotUpdater.kt +14 -2
  12. package/android/src/oldarch/HotUpdaterModule.kt +107 -3
  13. package/android/src/test/java/com/hotupdater/BsdiffPatchTest.kt +46 -0
  14. package/android/src/test/java/com/hotupdater/BundleFileStorageServiceTest.kt +29 -1
  15. package/android/src/test/java/com/hotupdater/HotUpdaterImplTest.kt +4 -1
  16. package/ios/HotUpdater/Internal/BsdiffPatchBridge.h +16 -0
  17. package/ios/HotUpdater/Internal/BsdiffPatchBridge.mm +343 -0
  18. package/ios/HotUpdater/Internal/BundleFileStorageService.swift +1093 -47
  19. package/ios/HotUpdater/Internal/HotUpdater-Bridging-Header.h +2 -0
  20. package/ios/HotUpdater/Internal/HotUpdater.mm +12 -2
  21. package/ios/HotUpdater/Internal/HotUpdaterImpl.swift +64 -5
  22. package/ios/HotUpdater/Internal/NotificationExtension.swift +2 -1
  23. package/ios/HotUpdater/Internal/SignatureVerifier.swift +28 -5
  24. package/ios/HotUpdater/Internal/StreamingTarArchiveExtractor.swift +11 -0
  25. package/ios/HotUpdater/Internal/URLSessionDownloadService.swift +11 -5
  26. package/lib/commonjs/DefaultResolver.js +6 -2
  27. package/lib/commonjs/DefaultResolver.js.map +1 -1
  28. package/lib/commonjs/DefaultResolver.spec.js +79 -3
  29. package/lib/commonjs/DefaultResolver.spec.js.map +1 -1
  30. package/lib/commonjs/checkForUpdate.js +6 -0
  31. package/lib/commonjs/checkForUpdate.js.map +1 -1
  32. package/lib/commonjs/index.js +53 -18
  33. package/lib/commonjs/index.js.map +1 -1
  34. package/lib/commonjs/index.spec.js +117 -1
  35. package/lib/commonjs/index.spec.js.map +1 -1
  36. package/lib/commonjs/native.js +40 -16
  37. package/lib/commonjs/native.js.map +1 -1
  38. package/lib/commonjs/native.spec.js +119 -3
  39. package/lib/commonjs/native.spec.js.map +1 -1
  40. package/lib/commonjs/sdkVersion.js +8 -0
  41. package/lib/commonjs/sdkVersion.js.map +1 -0
  42. package/lib/commonjs/specs/NativeHotUpdater.js.map +1 -1
  43. package/lib/commonjs/store.js +72 -5
  44. package/lib/commonjs/store.js.map +1 -1
  45. package/lib/commonjs/store.spec.js +265 -8
  46. package/lib/commonjs/store.spec.js.map +1 -1
  47. package/lib/commonjs/wrap.js +19 -5
  48. package/lib/commonjs/wrap.js.map +1 -1
  49. package/lib/commonjs/wrap.spec.js +74 -0
  50. package/lib/commonjs/wrap.spec.js.map +1 -1
  51. package/lib/module/DefaultResolver.js +6 -2
  52. package/lib/module/DefaultResolver.js.map +1 -1
  53. package/lib/module/DefaultResolver.spec.js +78 -3
  54. package/lib/module/DefaultResolver.spec.js.map +1 -1
  55. package/lib/module/checkForUpdate.js +6 -0
  56. package/lib/module/checkForUpdate.js.map +1 -1
  57. package/lib/module/index.js +53 -18
  58. package/lib/module/index.js.map +1 -1
  59. package/lib/module/index.spec.js +117 -1
  60. package/lib/module/index.spec.js.map +1 -1
  61. package/lib/module/native.js +40 -16
  62. package/lib/module/native.js.map +1 -1
  63. package/lib/module/native.spec.js +119 -3
  64. package/lib/module/native.spec.js.map +1 -1
  65. package/lib/module/sdkVersion.js +4 -0
  66. package/lib/module/sdkVersion.js.map +1 -0
  67. package/lib/module/specs/NativeHotUpdater.js.map +1 -1
  68. package/lib/module/store.js +72 -5
  69. package/lib/module/store.js.map +1 -1
  70. package/lib/module/store.spec.js +265 -8
  71. package/lib/module/store.spec.js.map +1 -1
  72. package/lib/module/wrap.js +19 -5
  73. package/lib/module/wrap.js.map +1 -1
  74. package/lib/module/wrap.spec.js +74 -0
  75. package/lib/module/wrap.spec.js.map +1 -1
  76. package/lib/typescript/commonjs/DefaultResolver.d.ts.map +1 -1
  77. package/lib/typescript/commonjs/checkForUpdate.d.ts +2 -2
  78. package/lib/typescript/commonjs/checkForUpdate.d.ts.map +1 -1
  79. package/lib/typescript/commonjs/index.d.ts +36 -8
  80. package/lib/typescript/commonjs/index.d.ts.map +1 -1
  81. package/lib/typescript/commonjs/native.d.ts +56 -3
  82. package/lib/typescript/commonjs/native.d.ts.map +1 -1
  83. package/lib/typescript/commonjs/sdkVersion.d.ts +2 -0
  84. package/lib/typescript/commonjs/sdkVersion.d.ts.map +1 -0
  85. package/lib/typescript/commonjs/specs/NativeHotUpdater.d.ts +27 -0
  86. package/lib/typescript/commonjs/specs/NativeHotUpdater.d.ts.map +1 -1
  87. package/lib/typescript/commonjs/store.d.ts +5 -0
  88. package/lib/typescript/commonjs/store.d.ts.map +1 -1
  89. package/lib/typescript/commonjs/wrap.d.ts +43 -29
  90. package/lib/typescript/commonjs/wrap.d.ts.map +1 -1
  91. package/lib/typescript/module/DefaultResolver.d.ts.map +1 -1
  92. package/lib/typescript/module/checkForUpdate.d.ts +2 -2
  93. package/lib/typescript/module/checkForUpdate.d.ts.map +1 -1
  94. package/lib/typescript/module/index.d.ts +36 -8
  95. package/lib/typescript/module/index.d.ts.map +1 -1
  96. package/lib/typescript/module/native.d.ts +56 -3
  97. package/lib/typescript/module/native.d.ts.map +1 -1
  98. package/lib/typescript/module/sdkVersion.d.ts +2 -0
  99. package/lib/typescript/module/sdkVersion.d.ts.map +1 -0
  100. package/lib/typescript/module/specs/NativeHotUpdater.d.ts +27 -0
  101. package/lib/typescript/module/specs/NativeHotUpdater.d.ts.map +1 -1
  102. package/lib/typescript/module/store.d.ts +5 -0
  103. package/lib/typescript/module/store.d.ts.map +1 -1
  104. package/lib/typescript/module/wrap.d.ts +43 -29
  105. package/lib/typescript/module/wrap.d.ts.map +1 -1
  106. package/package.json +20 -10
  107. package/src/DefaultResolver.spec.ts +103 -3
  108. package/src/DefaultResolver.ts +9 -2
  109. package/src/checkForUpdate.ts +9 -2
  110. package/src/index.spec.ts +149 -1
  111. package/src/index.ts +99 -40
  112. package/src/native.spec.ts +139 -1
  113. package/src/native.ts +145 -31
  114. package/src/sdkVersion.ts +1 -0
  115. package/src/specs/NativeHotUpdater.ts +28 -0
  116. package/src/store.spec.ts +324 -9
  117. package/src/store.ts +133 -7
  118. package/src/wrap.spec.ts +91 -0
  119. package/src/wrap.tsx +79 -42
@@ -1,6 +1,7 @@
1
1
  import React from "react";
2
2
  import type { HotUpdaterError } from "./error";
3
3
  import { type NotifyAppReadyResult } from "./native";
4
+ import { type HotUpdaterState } from "./store";
4
5
  import type { HotUpdaterBaseURL, HotUpdaterResolver } from "./types";
5
6
  export interface RunUpdateProcessResponse {
6
7
  status: "ROLLBACK" | "UPDATE" | "UP_TO_DATE";
@@ -9,8 +10,17 @@ export interface RunUpdateProcessResponse {
9
10
  id: string;
10
11
  }
11
12
  type UpdateStatus = "CHECK_FOR_UPDATE" | "UPDATING" | "UPDATE_PROCESS_COMPLETED";
13
+ export type HotUpdaterFallbackComponentProps = {
14
+ status: Exclude<UpdateStatus, "UPDATE_PROCESS_COMPLETED">;
15
+ progress: number;
16
+ downloadedBytes: HotUpdaterState["downloadedBytes"];
17
+ totalBytes: HotUpdaterState["totalBytes"];
18
+ message: string | null;
19
+ artifactType: HotUpdaterState["artifactType"];
20
+ details: HotUpdaterState["details"];
21
+ };
12
22
  /**
13
- * Common options shared between auto and manual update modes
23
+ * Common options shared by HotUpdater initialization APIs.
14
24
  */
15
25
  interface CommonHotUpdaterOptions {
16
26
  /**
@@ -34,18 +44,23 @@ interface CommonHotUpdaterOptions {
34
44
  *
35
45
  * @example
36
46
  * ```tsx
37
- * HotUpdater.wrap({
47
+ * HotUpdater.init({
38
48
  * baseURL: "https://api.example.com",
39
- * updateMode: "manual",
40
49
  * onNotifyAppReady: ({ status, crashedBundleId }) => {
41
50
  * if (status === "RECOVERED") {
42
51
  * analytics.track("bundle_rollback", { crashedBundleId });
43
52
  * }
44
- * }
45
- * })(App);
53
+ * },
54
+ * });
46
55
  * ```
47
56
  */
48
57
  onNotifyAppReady?: (result: NotifyAppReadyResult) => void;
58
+ /**
59
+ * Default callback invoked when Hot Updater initialization or update checks
60
+ * fail. Per-call `HotUpdater.checkForUpdate({ onError })` handlers override
61
+ * this callback for that check.
62
+ */
63
+ onError?: (error: HotUpdaterError | Error | unknown) => void;
49
64
  }
50
65
  /**
51
66
  * Configuration with baseURL for standard server-based updates
@@ -81,18 +96,16 @@ interface ResolverConfig {
81
96
  */
82
97
  type NetworkConfig = BaseURLConfig | ResolverConfig;
83
98
  export type AutoUpdateOptions = CommonHotUpdaterOptions & NetworkConfig & {
99
+ /**
100
+ * Automatic update mode. This is the default and can be omitted.
101
+ */
102
+ updateMode?: "auto";
84
103
  /**
85
104
  * Update strategy
86
105
  * - "fingerprint": Use fingerprint hash to check for updates
87
106
  * - "appVersion": Use app version to check for updates
88
107
  */
89
108
  updateStrategy: "fingerprint" | "appVersion";
90
- /**
91
- * Update mode
92
- * - "auto": Automatically check and download updates
93
- */
94
- updateMode: "auto";
95
- onError?: (error: HotUpdaterError | Error | unknown) => void;
96
109
  /**
97
110
  * Component to show while downloading a new bundle update.
98
111
  *
@@ -115,11 +128,7 @@ export type AutoUpdateOptions = CommonHotUpdaterOptions & NetworkConfig & {
115
128
  *
116
129
  * If not defined, the bundle will download in the background without blocking the screen.
117
130
  */
118
- fallbackComponent?: React.FC<{
119
- status: Exclude<UpdateStatus, "UPDATE_PROCESS_COMPLETED">;
120
- progress: number;
121
- message: string | null;
122
- }>;
131
+ fallbackComponent?: React.FC<HotUpdaterFallbackComponentProps>;
123
132
  onProgress?: (progress: number) => void;
124
133
  /**
125
134
  * When a force update exists, the app will automatically reload.
@@ -137,15 +146,24 @@ export type AutoUpdateOptions = CommonHotUpdaterOptions & NetworkConfig & {
137
146
  };
138
147
  export type ManualUpdateOptions = CommonHotUpdaterOptions & NetworkConfig & {
139
148
  /**
140
- * Update mode
141
- * - "manual": Only notify app ready, user manually calls checkForUpdate()
149
+ * @deprecated Replace manual `HotUpdater.wrap` with `HotUpdater.init`.
150
+ *
151
+ * ```tsx
152
+ * import { HotUpdater } from "@hot-updater/react-native";
153
+ *
154
+ * HotUpdater.init({
155
+ * baseURL: "<your-update-server-url>",
156
+ * });
157
+ *
158
+ * export default App;
159
+ * ```
160
+ *
161
+ * Then call `HotUpdater.checkForUpdate(...)` from your manual update flow.
162
+ * See https://hot-updater.dev/docs/guides/custom-update
142
163
  */
143
164
  updateMode: "manual";
144
165
  };
145
- type ManualBaseURLUpdateOptions = CommonHotUpdaterOptions & BaseURLConfig & {
146
- updateMode: "manual";
147
- };
148
- export type HotUpdaterInitOptions = Omit<ManualBaseURLUpdateOptions, "updateMode">;
166
+ export type HotUpdaterInitOptions = CommonHotUpdaterOptions & NetworkConfig;
149
167
  export type HotUpdaterOptions = AutoUpdateOptions | ManualUpdateOptions;
150
168
  /**
151
169
  * Internal options after normalization in index.ts
@@ -156,16 +174,12 @@ type InternalCommonOptions = {
156
174
  requestHeaders?: Record<string, string>;
157
175
  requestTimeout?: number;
158
176
  onNotifyAppReady?: (result: NotifyAppReadyResult) => void;
177
+ onError?: (error: HotUpdaterError | Error | unknown) => void;
159
178
  };
160
179
  type InternalAutoUpdateOptions = InternalCommonOptions & {
161
180
  updateStrategy: "fingerprint" | "appVersion";
162
181
  updateMode: "auto";
163
- onError?: (error: HotUpdaterError | Error | unknown) => void;
164
- fallbackComponent?: React.FC<{
165
- status: Exclude<UpdateStatus, "UPDATE_PROCESS_COMPLETED">;
166
- progress: number;
167
- message: string | null;
168
- }>;
182
+ fallbackComponent?: React.FC<HotUpdaterFallbackComponentProps>;
169
183
  onProgress?: (progress: number) => void;
170
184
  reloadOnForceUpdate?: boolean;
171
185
  onUpdateProcessCompleted?: (response: RunUpdateProcessResponse) => void;
@@ -176,6 +190,6 @@ type InternalManualUpdateOptions = InternalCommonOptions & {
176
190
  export type InternalInitOptions = InternalCommonOptions;
177
191
  export type InternalWrapOptions = InternalAutoUpdateOptions | InternalManualUpdateOptions;
178
192
  export declare function init(options: InternalInitOptions): void;
179
- export declare function wrap<P extends React.JSX.IntrinsicAttributes = object>(options: InternalWrapOptions): (WrappedComponent: React.ComponentType<P>) => React.ComponentType<P>;
193
+ export declare function wrap(options: InternalWrapOptions): <P extends object>(WrappedComponent: React.ComponentType<P>) => React.ComponentType<P>;
180
194
  export {};
181
195
  //# sourceMappingURL=wrap.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"wrap.d.ts","sourceRoot":"","sources":["../../../src/wrap.tsx"],"names":[],"mappings":"AAAA,OAAO,KAA8B,MAAM,OAAO,CAAC;AAGnD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAE/C,OAAO,EAEL,KAAK,oBAAoB,EAG1B,MAAM,UAAU,CAAC;AAElB,OAAO,KAAK,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,MAAM,SAAS,CAAC;AAErE,MAAM,WAAW,wBAAwB;IACvC,MAAM,EAAE,UAAU,GAAG,QAAQ,GAAG,YAAY,CAAC;IAC7C,iBAAiB,EAAE,OAAO,CAAC;IAC3B,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,EAAE,EAAE,MAAM,CAAC;CACZ;AAED,KAAK,YAAY,GACb,kBAAkB,GAClB,UAAU,GACV,0BAA0B,CAAC;AAE/B;;GAEG;AACH,UAAU,uBAAuB;IAC/B;;OAEG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAExC;;;OAGG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;IAExB;;;;;;;;;;;;;;;;;;;;;;OAsBG;IACH,gBAAgB,CAAC,EAAE,CAAC,MAAM,EAAE,oBAAoB,KAAK,IAAI,CAAC;CAC3D;AAED;;GAEG;AACH,UAAU,aAAa;IACrB;;;;;OAKG;IACH,OAAO,EAAE,iBAAiB,CAAC;IAE3B;;OAEG;IACH,QAAQ,CAAC,EAAE,KAAK,CAAC;CAClB;AAED;;GAEG;AACH,UAAU,cAAc;IACtB;;OAEG;IACH,QAAQ,EAAE,kBAAkB,CAAC;IAE7B;;OAEG;IACH,OAAO,CAAC,EAAE,KAAK,CAAC;CACjB;AAED;;GAEG;AACH,KAAK,aAAa,GAAG,aAAa,GAAG,cAAc,CAAC;AAEpD,MAAM,MAAM,iBAAiB,GAAG,uBAAuB,GACrD,aAAa,GAAG;IACd;;;;OAIG;IACH,cAAc,EAAE,aAAa,GAAG,YAAY,CAAC;IAE7C;;;OAGG;IACH,UAAU,EAAE,MAAM,CAAC;IAEnB,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,eAAe,GAAG,KAAK,GAAG,OAAO,KAAK,IAAI,CAAC;IAE7D;;;;;;;;;;;;;;;;;;;;;OAqBG;IACH,iBAAiB,CAAC,EAAE,KAAK,CAAC,EAAE,CAAC;QAC3B,MAAM,EAAE,OAAO,CAAC,YAAY,EAAE,0BAA0B,CAAC,CAAC;QAC1D,QAAQ,EAAE,MAAM,CAAC;QACjB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;KACxB,CAAC,CAAC;IAEH,UAAU,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAC;IAExC;;;;;OAKG;IACH,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAE9B;;;;OAIG;IACH,wBAAwB,CAAC,EAAE,CAAC,QAAQ,EAAE,wBAAwB,KAAK,IAAI,CAAC;CACzE,CAAC;AAEJ,MAAM,MAAM,mBAAmB,GAAG,uBAAuB,GACvD,aAAa,GAAG;IACd;;;OAGG;IACH,UAAU,EAAE,QAAQ,CAAC;CACtB,CAAC;AAEJ,KAAK,0BAA0B,GAAG,uBAAuB,GACvD,aAAa,GAAG;IACd,UAAU,EAAE,QAAQ,CAAC;CACtB,CAAC;AAEJ,MAAM,MAAM,qBAAqB,GAAG,IAAI,CACtC,0BAA0B,EAC1B,YAAY,CACb,CAAC;AAEF,MAAM,MAAM,iBAAiB,GAAG,iBAAiB,GAAG,mBAAmB,CAAC;AAExE;;;GAGG;AACH,KAAK,qBAAqB,GAAG;IAC3B,QAAQ,EAAE,kBAAkB,CAAC;IAC7B,cAAc,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACxC,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,gBAAgB,CAAC,EAAE,CAAC,MAAM,EAAE,oBAAoB,KAAK,IAAI,CAAC;CAC3D,CAAC;AAEF,KAAK,yBAAyB,GAAG,qBAAqB,GAAG;IACvD,cAAc,EAAE,aAAa,GAAG,YAAY,CAAC;IAC7C,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,eAAe,GAAG,KAAK,GAAG,OAAO,KAAK,IAAI,CAAC;IAC7D,iBAAiB,CAAC,EAAE,KAAK,CAAC,EAAE,CAAC;QAC3B,MAAM,EAAE,OAAO,CAAC,YAAY,EAAE,0BAA0B,CAAC,CAAC;QAC1D,QAAQ,EAAE,MAAM,CAAC;QACjB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;KACxB,CAAC,CAAC;IACH,UAAU,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAC;IACxC,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAC9B,wBAAwB,CAAC,EAAE,CAAC,QAAQ,EAAE,wBAAwB,KAAK,IAAI,CAAC;CACzE,CAAC;AAEF,KAAK,2BAA2B,GAAG,qBAAqB,GAAG;IACzD,UAAU,EAAE,QAAQ,CAAC;CACtB,CAAC;AAEF,MAAM,MAAM,mBAAmB,GAAG,qBAAqB,CAAC;AAExD,MAAM,MAAM,mBAAmB,GAC3B,yBAAyB,GACzB,2BAA2B,CAAC;AAsDhC,wBAAgB,IAAI,CAAC,OAAO,EAAE,mBAAmB,GAAG,IAAI,CAEvD;AAED,wBAAgB,IAAI,CAAC,CAAC,SAAS,KAAK,CAAC,GAAG,CAAC,mBAAmB,GAAG,MAAM,EACnE,OAAO,EAAE,mBAAmB,GAC3B,CAAC,gBAAgB,EAAE,KAAK,CAAC,aAAa,CAAC,CAAC,CAAC,KAAK,KAAK,CAAC,aAAa,CAAC,CAAC,CAAC,CA8HtE"}
1
+ {"version":3,"file":"wrap.d.ts","sourceRoot":"","sources":["../../../src/wrap.tsx"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,14 +1,22 @@
1
1
  {
2
2
  "name": "@hot-updater/react-native",
3
- "version": "0.30.12",
3
+ "version": "0.31.1",
4
4
  "description": "React Native OTA solution for self-hosted",
5
- "main": "./lib/module/index.js",
5
+ "source": "./src/index.ts",
6
+ "main": "./lib/commonjs/index.js",
7
+ "module": "./lib/module/index.js",
6
8
  "types": "./lib/typescript/commonjs/index.d.ts",
7
9
  "exports": {
8
10
  ".": {
9
11
  "source": "./src/index.ts",
10
- "types": "./lib/typescript/commonjs/index.d.ts",
11
- "default": "./lib/module/index.js"
12
+ "import": {
13
+ "types": "./lib/typescript/module/index.d.ts",
14
+ "default": "./lib/module/index.js"
15
+ },
16
+ "require": {
17
+ "types": "./lib/typescript/commonjs/index.d.ts",
18
+ "default": "./lib/commonjs/index.js"
19
+ }
12
20
  },
13
21
  "./package.json": "./package.json",
14
22
  "./app.plugin.js": "./app.plugin.js"
@@ -109,22 +117,24 @@
109
117
  "@types/use-sync-external-store": "^0.0.6",
110
118
  "del-cli": "^6.0.0",
111
119
  "expo": "^50.0.0",
120
+ "oxc-transform": "0.121.0",
112
121
  "react": "19.1.3",
113
122
  "react-native": "0.79.1",
114
123
  "react-native-builder-bob": "0.41.0",
115
124
  "typescript": "^6.0.2",
116
- "hot-updater": "0.30.12"
125
+ "hot-updater": "0.31.1"
117
126
  },
118
127
  "dependencies": {
119
128
  "use-sync-external-store": "1.5.0",
120
- "@hot-updater/cli-tools": "0.30.12",
121
- "@hot-updater/core": "0.30.12",
122
- "@hot-updater/js": "0.30.12",
123
- "@hot-updater/plugin-core": "0.30.12"
129
+ "@hot-updater/core": "0.31.1",
130
+ "@hot-updater/js": "0.31.1",
131
+ "@hot-updater/plugin-core": "0.31.1",
132
+ "@hot-updater/cli-tools": "0.31.1"
124
133
  },
125
134
  "scripts": {
126
- "build": "bob build && tsc -p plugin/tsconfig.build.json",
135
+ "build": "pnpm sync:sdk-version && bob build && tsc -p plugin/tsconfig.build.json",
127
136
  "build:plugin": "tsc -p plugin/tsconfig.build.json",
137
+ "sync:sdk-version": "node ./scripts/sync-sdk-version.mjs",
128
138
  "test:type": "tsc --noEmit",
129
139
  "test": "vitest",
130
140
  "clean": "del-cli android/build example/android/build example/android/app/build example/ios/build lib",
@@ -1,6 +1,10 @@
1
+ import { readFile } from "node:fs/promises";
2
+ import { join } from "node:path";
3
+
1
4
  import { beforeEach, describe, expect, it, vi } from "vitest";
2
5
 
3
6
  import { createDefaultResolver } from "./DefaultResolver";
7
+ import { HOT_UPDATER_SDK_VERSION } from "./sdkVersion";
4
8
  import type { ResolverCheckUpdateParams } from "./types";
5
9
 
6
10
  const mocks = vi.hoisted(() => ({
@@ -31,6 +35,80 @@ describe("createDefaultResolver", () => {
31
35
  mocks.fetchUpdateInfo.mockResolvedValue(null);
32
36
  });
33
37
 
38
+ it("strips trailing slashes from baseURL for app-version requests", async () => {
39
+ const resolver = createDefaultResolver(
40
+ "http://localhost:3007/hot-updater/",
41
+ );
42
+
43
+ await resolver.checkUpdate?.(
44
+ createParams({
45
+ appVersion: "1.0",
46
+ bundleId: "current-bundle",
47
+ channel: "production",
48
+ cohort: "730",
49
+ minBundleId: "min-bundle",
50
+ platform: "android",
51
+ }),
52
+ );
53
+
54
+ expect(mocks.fetchUpdateInfo).toHaveBeenCalledWith({
55
+ requestHeaders: {
56
+ "Hot-Updater-SDK-Version": HOT_UPDATER_SDK_VERSION,
57
+ },
58
+ requestTimeout: undefined,
59
+ url: "http://localhost:3007/hot-updater/app-version/android/1.0/production/min-bundle/current-bundle/730",
60
+ });
61
+ });
62
+
63
+ it("strips trailing slashes from baseURL for fingerprint requests", async () => {
64
+ const resolver = createDefaultResolver(
65
+ "http://localhost:3007/hot-updater///",
66
+ );
67
+
68
+ await resolver.checkUpdate?.(
69
+ createParams({
70
+ appVersion: "1.0",
71
+ bundleId: "current-bundle",
72
+ channel: "beta",
73
+ cohort: "qa",
74
+ fingerprintHash: "fingerprint-hash",
75
+ minBundleId: "min-bundle",
76
+ platform: "ios",
77
+ requestHeaders: { authorization: "Bearer token" },
78
+ requestTimeout: 1500,
79
+ updateStrategy: "fingerprint",
80
+ }),
81
+ );
82
+
83
+ expect(mocks.fetchUpdateInfo).toHaveBeenCalledWith({
84
+ requestHeaders: {
85
+ authorization: "Bearer token",
86
+ "Hot-Updater-SDK-Version": HOT_UPDATER_SDK_VERSION,
87
+ },
88
+ requestTimeout: 1500,
89
+ url: "http://localhost:3007/hot-updater/fingerprint/ios/fingerprint-hash/beta/min-bundle/current-bundle/qa",
90
+ });
91
+ });
92
+
93
+ it("keeps the SDK version header aligned with package.json", async () => {
94
+ const [{ HOT_UPDATER_SDK_VERSION }, packageJson] = await Promise.all([
95
+ import("./sdkVersion"),
96
+ readFile(join(__dirname, "../package.json"), "utf-8"),
97
+ ]);
98
+ const pkg = JSON.parse(packageJson) as { version: string };
99
+
100
+ expect(HOT_UPDATER_SDK_VERSION).toBe(pkg.version);
101
+ });
102
+
103
+ it("propagates fetchUpdateInfo errors", async () => {
104
+ mocks.fetchUpdateInfo.mockRejectedValueOnce(new Error("Network failed"));
105
+ const resolver = createDefaultResolver("http://localhost:3007/hot-updater");
106
+
107
+ await expect(resolver.checkUpdate?.(createParams())).rejects.toThrow(
108
+ "Network failed",
109
+ );
110
+ });
111
+
34
112
  it("resolves dynamic baseURL before checking for updates", async () => {
35
113
  const resolveBaseURL = vi.fn(async () => "https://updates.example.com");
36
114
  const resolver = createDefaultResolver(resolveBaseURL);
@@ -39,7 +117,9 @@ describe("createDefaultResolver", () => {
39
117
 
40
118
  expect(resolveBaseURL).toHaveBeenCalledWith();
41
119
  expect(mocks.fetchUpdateInfo).toHaveBeenCalledWith({
42
- requestHeaders: undefined,
120
+ requestHeaders: {
121
+ "Hot-Updater-SDK-Version": HOT_UPDATER_SDK_VERSION,
122
+ },
43
123
  requestTimeout: undefined,
44
124
  url: "https://updates.example.com/app-version/ios/1.0.0/production/min-bundle-id/bundle-id/cohort",
45
125
  });
@@ -57,17 +137,37 @@ describe("createDefaultResolver", () => {
57
137
 
58
138
  expect(resolveBaseURL).toHaveBeenCalledTimes(2);
59
139
  expect(mocks.fetchUpdateInfo).toHaveBeenNthCalledWith(1, {
60
- requestHeaders: undefined,
140
+ requestHeaders: {
141
+ "Hot-Updater-SDK-Version": HOT_UPDATER_SDK_VERSION,
142
+ },
61
143
  requestTimeout: undefined,
62
144
  url: "https://one.example.com/app-version/ios/1.0.0/production/min-bundle-id/bundle-id/cohort",
63
145
  });
64
146
  expect(mocks.fetchUpdateInfo).toHaveBeenNthCalledWith(2, {
65
- requestHeaders: undefined,
147
+ requestHeaders: {
148
+ "Hot-Updater-SDK-Version": HOT_UPDATER_SDK_VERSION,
149
+ },
66
150
  requestTimeout: undefined,
67
151
  url: "https://two.example.com/app-version/ios/1.0.0/production/min-bundle-id/bundle-id/cohort",
68
152
  });
69
153
  });
70
154
 
155
+ it("strips trailing slashes from dynamic baseURL results", async () => {
156
+ const resolver = createDefaultResolver(
157
+ () => "https://updates.example.com/",
158
+ );
159
+
160
+ await resolver.checkUpdate?.(createParams());
161
+
162
+ expect(mocks.fetchUpdateInfo).toHaveBeenCalledWith({
163
+ requestHeaders: {
164
+ "Hot-Updater-SDK-Version": HOT_UPDATER_SDK_VERSION,
165
+ },
166
+ requestTimeout: undefined,
167
+ url: "https://updates.example.com/app-version/ios/1.0.0/production/min-bundle-id/bundle-id/cohort",
168
+ });
169
+ });
170
+
71
171
  it("rejects an empty dynamic baseURL", async () => {
72
172
  const resolver = createDefaultResolver(() => "");
73
173
 
@@ -1,6 +1,7 @@
1
1
  import type { AppUpdateInfo } from "@hot-updater/core";
2
2
 
3
3
  import { fetchUpdateInfo } from "./fetchUpdateInfo";
4
+ import { HOT_UPDATER_SDK_VERSION } from "./sdkVersion";
4
5
  import type {
5
6
  HotUpdaterBaseURL,
6
7
  HotUpdaterResolver,
@@ -33,7 +34,10 @@ export function createDefaultResolver(
33
34
  checkUpdate: async (
34
35
  params: ResolverCheckUpdateParams,
35
36
  ): Promise<AppUpdateInfo | null> => {
36
- const resolvedBaseURL = await resolveBaseURL(baseURL);
37
+ const resolvedBaseURL = (await resolveBaseURL(baseURL)).replace(
38
+ /\/+$/,
39
+ "",
40
+ );
37
41
  let url: string;
38
42
  const cohortPath = `/${encodeURIComponent(params.cohort)}`;
39
43
 
@@ -48,7 +52,10 @@ export function createDefaultResolver(
48
52
 
49
53
  return fetchUpdateInfo({
50
54
  url,
51
- requestHeaders: params.requestHeaders,
55
+ requestHeaders: {
56
+ ...params.requestHeaders,
57
+ "Hot-Updater-SDK-Version": HOT_UPDATER_SDK_VERSION,
58
+ },
52
59
  requestTimeout: params.requestTimeout,
53
60
  });
54
61
  },
@@ -1,4 +1,4 @@
1
- import type { AppUpdateInfo } from "@hot-updater/core";
1
+ import type { AppUpdateAvailableInfo, AppUpdateInfo } from "@hot-updater/core";
2
2
  import { Platform } from "react-native";
3
3
 
4
4
  import { HotUpdaterError } from "./error";
@@ -42,7 +42,7 @@ export interface CheckForUpdateOptions {
42
42
  requestTimeout?: number;
43
43
  }
44
44
 
45
- export type CheckForUpdateResult = AppUpdateInfo & {
45
+ export type CheckForUpdateResult = AppUpdateAvailableInfo & {
46
46
  /**
47
47
  * Updates the bundle.
48
48
  * This method is equivalent to `HotUpdater.updateBundle()` but with all required arguments pre-filled.
@@ -142,6 +142,10 @@ export async function checkForUpdate(
142
142
  return null;
143
143
  }
144
144
 
145
+ if (updateInfo.status === "UP_TO_DATE") {
146
+ return null;
147
+ }
148
+
145
149
  if (
146
150
  explicitChannel &&
147
151
  explicitChannel !== defaultChannel &&
@@ -168,8 +172,11 @@ export async function checkForUpdate(
168
172
  return updateBundle({
169
173
  bundleId: updateInfo.id,
170
174
  channel: runtimeChannel,
175
+ changedAssets: updateInfo.changedAssets ?? null,
171
176
  fileUrl: updateInfo.fileUrl,
172
177
  fileHash: updateInfo.fileHash,
178
+ manifestFileHash: updateInfo.manifestFileHash ?? null,
179
+ manifestUrl: updateInfo.manifestUrl ?? null,
173
180
  status: updateInfo.status,
174
181
  shouldSkipCurrentBundleIdCheck: isFirstRuntimeChannelSwitchAttempt,
175
182
  });
package/src/index.spec.ts CHANGED
@@ -1,5 +1,8 @@
1
1
  import { beforeEach, describe, expect, it, vi } from "vitest";
2
2
 
3
+ import type { HotUpdater as HotUpdaterValue } from "./index";
4
+ import type { HotUpdaterInitOptions, HotUpdaterOptions } from "./wrap";
5
+
3
6
  const mocks = vi.hoisted(() => ({
4
7
  addListener: vi.fn(() => () => {}),
5
8
  checkForUpdate: vi.fn(),
@@ -134,6 +137,151 @@ describe("HotUpdater client initialization", () => {
134
137
  });
135
138
  });
136
139
 
140
+ it("accepts custom resolvers for manual update flows", async () => {
141
+ const resolver = {
142
+ checkUpdate: vi.fn(),
143
+ notifyAppReady: vi.fn(),
144
+ };
145
+
146
+ const HotUpdater = await importHotUpdater();
147
+
148
+ HotUpdater.init({
149
+ resolver,
150
+ requestHeaders: {
151
+ Authorization: "Bearer token",
152
+ },
153
+ });
154
+
155
+ expect(mocks.createDefaultResolver).not.toHaveBeenCalled();
156
+ expect(mocks.init).toHaveBeenCalledWith({
157
+ resolver,
158
+ requestHeaders: {
159
+ Authorization: "Bearer token",
160
+ },
161
+ });
162
+ });
163
+
164
+ it("accepts onError during init and uses it for later manual update checks", async () => {
165
+ const resolver = {
166
+ checkUpdate: vi.fn(),
167
+ notifyAppReady: vi.fn(),
168
+ };
169
+ const onError = vi.fn();
170
+ mocks.createDefaultResolver.mockReturnValue(resolver);
171
+
172
+ const HotUpdater = await importHotUpdater();
173
+
174
+ HotUpdater.init({
175
+ baseURL: "https://updates.example.com",
176
+ onError,
177
+ });
178
+
179
+ await HotUpdater.checkForUpdate({
180
+ updateStrategy: "appVersion",
181
+ });
182
+
183
+ expect(mocks.init).toHaveBeenCalledWith({
184
+ onError,
185
+ resolver,
186
+ });
187
+ expect(mocks.checkForUpdate).toHaveBeenCalledWith({
188
+ onError,
189
+ requestHeaders: {},
190
+ requestTimeout: undefined,
191
+ resolver,
192
+ updateStrategy: "appVersion",
193
+ });
194
+ });
195
+
196
+ it("lets checkForUpdate override the init onError handler", async () => {
197
+ const resolver = {
198
+ checkUpdate: vi.fn(),
199
+ notifyAppReady: vi.fn(),
200
+ };
201
+ const initOnError = vi.fn();
202
+ const checkOnError = vi.fn();
203
+ mocks.createDefaultResolver.mockReturnValue(resolver);
204
+
205
+ const HotUpdater = await importHotUpdater();
206
+
207
+ HotUpdater.init({
208
+ baseURL: "https://updates.example.com",
209
+ onError: initOnError,
210
+ });
211
+
212
+ await HotUpdater.checkForUpdate({
213
+ onError: checkOnError,
214
+ updateStrategy: "appVersion",
215
+ });
216
+
217
+ expect(mocks.checkForUpdate).toHaveBeenCalledWith({
218
+ onError: checkOnError,
219
+ requestHeaders: {},
220
+ requestTimeout: undefined,
221
+ resolver,
222
+ updateStrategy: "appVersion",
223
+ });
224
+ });
225
+
226
+ it("defaults wrap to automatic update mode", async () => {
227
+ const resolver = {
228
+ checkUpdate: vi.fn(),
229
+ notifyAppReady: vi.fn(),
230
+ };
231
+ mocks.createDefaultResolver.mockReturnValue(resolver);
232
+
233
+ const HotUpdater = await importHotUpdater();
234
+
235
+ HotUpdater.wrap({
236
+ baseURL: "https://updates.example.com",
237
+ updateStrategy: "appVersion",
238
+ });
239
+
240
+ expect(mocks.wrap).toHaveBeenCalledWith({
241
+ resolver,
242
+ updateMode: "auto",
243
+ updateStrategy: "appVersion",
244
+ });
245
+ });
246
+
247
+ it("keeps deprecated manual wrap calls working", async () => {
248
+ const resolver = {
249
+ checkUpdate: vi.fn(),
250
+ notifyAppReady: vi.fn(),
251
+ };
252
+ mocks.createDefaultResolver.mockReturnValue(resolver);
253
+
254
+ const HotUpdater = await importHotUpdater();
255
+
256
+ HotUpdater.wrap({
257
+ baseURL: "https://updates.example.com",
258
+ updateMode: "manual",
259
+ });
260
+
261
+ expect(mocks.wrap).toHaveBeenCalledWith({
262
+ resolver,
263
+ updateMode: "manual",
264
+ });
265
+ });
266
+
267
+ it("types public wrap to accept pre-typed option unions", () => {
268
+ const compileOnly = (
269
+ wrap: typeof HotUpdaterValue.wrap,
270
+ options: HotUpdaterOptions,
271
+ ) => wrap(options);
272
+
273
+ expect(compileOnly).toBeTypeOf("function");
274
+ });
275
+
276
+ it("types public init options with onError", () => {
277
+ const options = {
278
+ baseURL: "https://updates.example.com",
279
+ onError: vi.fn(),
280
+ } satisfies HotUpdaterInitOptions;
281
+
282
+ expect(options.baseURL).toBe("https://updates.example.com");
283
+ });
284
+
137
285
  it("uses init configuration for later manual update checks", async () => {
138
286
  const resolver = {
139
287
  checkUpdate: vi.fn(),
@@ -183,7 +331,7 @@ describe("HotUpdater client initialization", () => {
183
331
  const HotUpdater = await importHotUpdater();
184
332
 
185
333
  expect(() => HotUpdater.init({} as never)).toThrow(
186
- "baseURL must be provided",
334
+ "Either baseURL or resolver must be provided",
187
335
  );
188
336
  expect(mocks.init).not.toHaveBeenCalled();
189
337
  });