@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.
- package/HotUpdater.podspec +1 -0
- package/android/build.gradle +1 -0
- package/android/src/main/java/com/hotupdater/BsdiffPatch.kt +284 -0
- package/android/src/main/java/com/hotupdater/BundleFileStorageService.kt +885 -5
- package/android/src/main/java/com/hotupdater/HotUpdaterImpl.kt +13 -2
- package/android/src/main/java/com/hotupdater/OkHttpDownloadService.kt +37 -8
- package/android/src/main/java/com/hotupdater/ReactNativeValueConverters.kt +48 -12
- package/android/src/main/java/com/hotupdater/SignatureVerifier.kt +60 -22
- package/android/src/newarch/HotUpdater.kt +14 -2
- package/android/src/newarch/HotUpdaterModule.kt +112 -1
- package/android/src/oldarch/HotUpdater.kt +14 -2
- package/android/src/oldarch/HotUpdaterModule.kt +107 -3
- package/android/src/test/java/com/hotupdater/BsdiffPatchTest.kt +46 -0
- package/android/src/test/java/com/hotupdater/BundleFileStorageServiceTest.kt +29 -1
- package/android/src/test/java/com/hotupdater/HotUpdaterImplTest.kt +4 -1
- package/ios/HotUpdater/Internal/BsdiffPatchBridge.h +16 -0
- package/ios/HotUpdater/Internal/BsdiffPatchBridge.mm +343 -0
- package/ios/HotUpdater/Internal/BundleFileStorageService.swift +1093 -47
- package/ios/HotUpdater/Internal/HotUpdater-Bridging-Header.h +2 -0
- package/ios/HotUpdater/Internal/HotUpdater.mm +12 -2
- package/ios/HotUpdater/Internal/HotUpdaterImpl.swift +64 -5
- package/ios/HotUpdater/Internal/NotificationExtension.swift +2 -1
- package/ios/HotUpdater/Internal/SignatureVerifier.swift +28 -5
- package/ios/HotUpdater/Internal/StreamingTarArchiveExtractor.swift +11 -0
- package/ios/HotUpdater/Internal/URLSessionDownloadService.swift +11 -5
- package/lib/commonjs/DefaultResolver.js +6 -2
- package/lib/commonjs/DefaultResolver.js.map +1 -1
- package/lib/commonjs/DefaultResolver.spec.js +79 -3
- package/lib/commonjs/DefaultResolver.spec.js.map +1 -1
- package/lib/commonjs/checkForUpdate.js +6 -0
- package/lib/commonjs/checkForUpdate.js.map +1 -1
- package/lib/commonjs/index.js +53 -18
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/index.spec.js +117 -1
- package/lib/commonjs/index.spec.js.map +1 -1
- package/lib/commonjs/native.js +40 -16
- package/lib/commonjs/native.js.map +1 -1
- package/lib/commonjs/native.spec.js +119 -3
- package/lib/commonjs/native.spec.js.map +1 -1
- package/lib/commonjs/sdkVersion.js +8 -0
- package/lib/commonjs/sdkVersion.js.map +1 -0
- package/lib/commonjs/specs/NativeHotUpdater.js.map +1 -1
- package/lib/commonjs/store.js +72 -5
- package/lib/commonjs/store.js.map +1 -1
- package/lib/commonjs/store.spec.js +265 -8
- package/lib/commonjs/store.spec.js.map +1 -1
- package/lib/commonjs/wrap.js +19 -5
- package/lib/commonjs/wrap.js.map +1 -1
- package/lib/commonjs/wrap.spec.js +74 -0
- package/lib/commonjs/wrap.spec.js.map +1 -1
- package/lib/module/DefaultResolver.js +6 -2
- package/lib/module/DefaultResolver.js.map +1 -1
- package/lib/module/DefaultResolver.spec.js +78 -3
- package/lib/module/DefaultResolver.spec.js.map +1 -1
- package/lib/module/checkForUpdate.js +6 -0
- package/lib/module/checkForUpdate.js.map +1 -1
- package/lib/module/index.js +53 -18
- package/lib/module/index.js.map +1 -1
- package/lib/module/index.spec.js +117 -1
- package/lib/module/index.spec.js.map +1 -1
- package/lib/module/native.js +40 -16
- package/lib/module/native.js.map +1 -1
- package/lib/module/native.spec.js +119 -3
- package/lib/module/native.spec.js.map +1 -1
- package/lib/module/sdkVersion.js +4 -0
- package/lib/module/sdkVersion.js.map +1 -0
- package/lib/module/specs/NativeHotUpdater.js.map +1 -1
- package/lib/module/store.js +72 -5
- package/lib/module/store.js.map +1 -1
- package/lib/module/store.spec.js +265 -8
- package/lib/module/store.spec.js.map +1 -1
- package/lib/module/wrap.js +19 -5
- package/lib/module/wrap.js.map +1 -1
- package/lib/module/wrap.spec.js +74 -0
- package/lib/module/wrap.spec.js.map +1 -1
- package/lib/typescript/commonjs/DefaultResolver.d.ts.map +1 -1
- package/lib/typescript/commonjs/checkForUpdate.d.ts +2 -2
- package/lib/typescript/commonjs/checkForUpdate.d.ts.map +1 -1
- package/lib/typescript/commonjs/index.d.ts +36 -8
- package/lib/typescript/commonjs/index.d.ts.map +1 -1
- package/lib/typescript/commonjs/native.d.ts +56 -3
- package/lib/typescript/commonjs/native.d.ts.map +1 -1
- package/lib/typescript/commonjs/sdkVersion.d.ts +2 -0
- package/lib/typescript/commonjs/sdkVersion.d.ts.map +1 -0
- package/lib/typescript/commonjs/specs/NativeHotUpdater.d.ts +27 -0
- package/lib/typescript/commonjs/specs/NativeHotUpdater.d.ts.map +1 -1
- package/lib/typescript/commonjs/store.d.ts +5 -0
- package/lib/typescript/commonjs/store.d.ts.map +1 -1
- package/lib/typescript/commonjs/wrap.d.ts +43 -29
- package/lib/typescript/commonjs/wrap.d.ts.map +1 -1
- package/lib/typescript/module/DefaultResolver.d.ts.map +1 -1
- package/lib/typescript/module/checkForUpdate.d.ts +2 -2
- package/lib/typescript/module/checkForUpdate.d.ts.map +1 -1
- package/lib/typescript/module/index.d.ts +36 -8
- package/lib/typescript/module/index.d.ts.map +1 -1
- package/lib/typescript/module/native.d.ts +56 -3
- package/lib/typescript/module/native.d.ts.map +1 -1
- package/lib/typescript/module/sdkVersion.d.ts +2 -0
- package/lib/typescript/module/sdkVersion.d.ts.map +1 -0
- package/lib/typescript/module/specs/NativeHotUpdater.d.ts +27 -0
- package/lib/typescript/module/specs/NativeHotUpdater.d.ts.map +1 -1
- package/lib/typescript/module/store.d.ts +5 -0
- package/lib/typescript/module/store.d.ts.map +1 -1
- package/lib/typescript/module/wrap.d.ts +43 -29
- package/lib/typescript/module/wrap.d.ts.map +1 -1
- package/package.json +20 -10
- package/src/DefaultResolver.spec.ts +103 -3
- package/src/DefaultResolver.ts +9 -2
- package/src/checkForUpdate.ts +9 -2
- package/src/index.spec.ts +149 -1
- package/src/index.ts +99 -40
- package/src/native.spec.ts +139 -1
- package/src/native.ts +145 -31
- package/src/sdkVersion.ts +1 -0
- package/src/specs/NativeHotUpdater.ts +28 -0
- package/src/store.spec.ts +324 -9
- package/src/store.ts +133 -7
- package/src/wrap.spec.ts +91 -0
- 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
|
|
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.
|
|
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
|
-
* })
|
|
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
|
-
*
|
|
141
|
-
*
|
|
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
|
|
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
|
-
|
|
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
|
|
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;
|
|
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.
|
|
3
|
+
"version": "0.31.1",
|
|
4
4
|
"description": "React Native OTA solution for self-hosted",
|
|
5
|
-
"
|
|
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
|
-
"
|
|
11
|
-
|
|
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.
|
|
125
|
+
"hot-updater": "0.31.1"
|
|
117
126
|
},
|
|
118
127
|
"dependencies": {
|
|
119
128
|
"use-sync-external-store": "1.5.0",
|
|
120
|
-
"@hot-updater/
|
|
121
|
-
"@hot-updater/
|
|
122
|
-
"@hot-updater/
|
|
123
|
-
"@hot-updater/
|
|
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:
|
|
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:
|
|
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:
|
|
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
|
|
package/src/DefaultResolver.ts
CHANGED
|
@@ -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:
|
|
55
|
+
requestHeaders: {
|
|
56
|
+
...params.requestHeaders,
|
|
57
|
+
"Hot-Updater-SDK-Version": HOT_UPDATER_SDK_VERSION,
|
|
58
|
+
},
|
|
52
59
|
requestTimeout: params.requestTimeout,
|
|
53
60
|
});
|
|
54
61
|
},
|
package/src/checkForUpdate.ts
CHANGED
|
@@ -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 =
|
|
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
|
});
|