@hot-updater/react-native 0.30.10 → 0.30.12
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/lib/commonjs/DefaultResolver.js +13 -3
- package/lib/commonjs/DefaultResolver.js.map +1 -1
- package/lib/commonjs/DefaultResolver.spec.js +60 -0
- package/lib/commonjs/DefaultResolver.spec.js.map +1 -0
- package/lib/commonjs/error.js +83 -0
- package/lib/commonjs/error.js.map +1 -1
- package/lib/commonjs/fetchUpdateInfo.js +8 -10
- package/lib/commonjs/fetchUpdateInfo.js.map +1 -1
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/index.spec.js +16 -0
- package/lib/commonjs/index.spec.js.map +1 -1
- package/lib/commonjs/types.js.map +1 -1
- package/lib/commonjs/wrap.js.map +1 -1
- package/lib/module/DefaultResolver.js +13 -3
- package/lib/module/DefaultResolver.js.map +1 -1
- package/lib/module/DefaultResolver.spec.js +60 -0
- package/lib/module/DefaultResolver.spec.js.map +1 -0
- package/lib/module/error.js +83 -0
- package/lib/module/error.js.map +1 -1
- package/lib/module/fetchUpdateInfo.js +8 -10
- package/lib/module/fetchUpdateInfo.js.map +1 -1
- package/lib/module/index.js.map +1 -1
- package/lib/module/index.spec.js +16 -0
- package/lib/module/index.spec.js.map +1 -1
- package/lib/module/types.js.map +1 -1
- package/lib/module/wrap.js.map +1 -1
- package/lib/typescript/commonjs/DefaultResolver.d.ts +4 -3
- package/lib/typescript/commonjs/DefaultResolver.d.ts.map +1 -1
- package/lib/typescript/commonjs/fetchUpdateInfo.d.ts +1 -2
- package/lib/typescript/commonjs/fetchUpdateInfo.d.ts.map +1 -1
- package/lib/typescript/commonjs/index.d.ts +1 -1
- package/lib/typescript/commonjs/index.d.ts.map +1 -1
- package/lib/typescript/commonjs/types.d.ts +1 -0
- package/lib/typescript/commonjs/types.d.ts.map +1 -1
- package/lib/typescript/commonjs/wrap.d.ts +5 -3
- package/lib/typescript/commonjs/wrap.d.ts.map +1 -1
- package/lib/typescript/module/DefaultResolver.d.ts +4 -3
- package/lib/typescript/module/DefaultResolver.d.ts.map +1 -1
- package/lib/typescript/module/fetchUpdateInfo.d.ts +1 -2
- package/lib/typescript/module/fetchUpdateInfo.d.ts.map +1 -1
- package/lib/typescript/module/index.d.ts +1 -1
- package/lib/typescript/module/index.d.ts.map +1 -1
- package/lib/typescript/module/types.d.ts +1 -0
- package/lib/typescript/module/types.d.ts.map +1 -1
- package/lib/typescript/module/wrap.d.ts +5 -3
- package/lib/typescript/module/wrap.d.ts.map +1 -1
- package/package.json +11 -18
- package/src/DefaultResolver.spec.ts +78 -0
- package/src/DefaultResolver.ts +24 -5
- package/src/fetchUpdateInfo.ts +9 -12
- package/src/index.spec.ts +21 -1
- package/src/index.ts +1 -0
- package/src/types.ts +2 -0
- package/src/wrap.tsx +5 -3
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
2
|
+
|
|
3
|
+
import { createDefaultResolver } from "./DefaultResolver";
|
|
4
|
+
import type { ResolverCheckUpdateParams } from "./types";
|
|
5
|
+
|
|
6
|
+
const mocks = vi.hoisted(() => ({
|
|
7
|
+
fetchUpdateInfo: vi.fn(),
|
|
8
|
+
}));
|
|
9
|
+
|
|
10
|
+
vi.mock("./fetchUpdateInfo", () => ({
|
|
11
|
+
fetchUpdateInfo: mocks.fetchUpdateInfo,
|
|
12
|
+
}));
|
|
13
|
+
|
|
14
|
+
const createParams = (
|
|
15
|
+
params?: Partial<ResolverCheckUpdateParams>,
|
|
16
|
+
): ResolverCheckUpdateParams => ({
|
|
17
|
+
appVersion: "1.0.0",
|
|
18
|
+
bundleId: "bundle-id",
|
|
19
|
+
channel: "production",
|
|
20
|
+
cohort: "cohort",
|
|
21
|
+
fingerprintHash: null,
|
|
22
|
+
minBundleId: "min-bundle-id",
|
|
23
|
+
platform: "ios",
|
|
24
|
+
updateStrategy: "appVersion",
|
|
25
|
+
...params,
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
describe("createDefaultResolver", () => {
|
|
29
|
+
beforeEach(() => {
|
|
30
|
+
mocks.fetchUpdateInfo.mockReset();
|
|
31
|
+
mocks.fetchUpdateInfo.mockResolvedValue(null);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it("resolves dynamic baseURL before checking for updates", async () => {
|
|
35
|
+
const resolveBaseURL = vi.fn(async () => "https://updates.example.com");
|
|
36
|
+
const resolver = createDefaultResolver(resolveBaseURL);
|
|
37
|
+
|
|
38
|
+
await resolver.checkUpdate?.(createParams());
|
|
39
|
+
|
|
40
|
+
expect(resolveBaseURL).toHaveBeenCalledWith();
|
|
41
|
+
expect(mocks.fetchUpdateInfo).toHaveBeenCalledWith({
|
|
42
|
+
requestHeaders: undefined,
|
|
43
|
+
requestTimeout: undefined,
|
|
44
|
+
url: "https://updates.example.com/app-version/ios/1.0.0/production/min-bundle-id/bundle-id/cohort",
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it("calls dynamic baseURL for each update check", async () => {
|
|
49
|
+
const resolveBaseURL = vi
|
|
50
|
+
.fn<() => Promise<string>>()
|
|
51
|
+
.mockResolvedValueOnce("https://one.example.com")
|
|
52
|
+
.mockResolvedValueOnce("https://two.example.com");
|
|
53
|
+
const resolver = createDefaultResolver(resolveBaseURL);
|
|
54
|
+
|
|
55
|
+
await resolver.checkUpdate?.(createParams());
|
|
56
|
+
await resolver.checkUpdate?.(createParams());
|
|
57
|
+
|
|
58
|
+
expect(resolveBaseURL).toHaveBeenCalledTimes(2);
|
|
59
|
+
expect(mocks.fetchUpdateInfo).toHaveBeenNthCalledWith(1, {
|
|
60
|
+
requestHeaders: undefined,
|
|
61
|
+
requestTimeout: undefined,
|
|
62
|
+
url: "https://one.example.com/app-version/ios/1.0.0/production/min-bundle-id/bundle-id/cohort",
|
|
63
|
+
});
|
|
64
|
+
expect(mocks.fetchUpdateInfo).toHaveBeenNthCalledWith(2, {
|
|
65
|
+
requestHeaders: undefined,
|
|
66
|
+
requestTimeout: undefined,
|
|
67
|
+
url: "https://two.example.com/app-version/ios/1.0.0/production/min-bundle-id/bundle-id/cohort",
|
|
68
|
+
});
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it("rejects an empty dynamic baseURL", async () => {
|
|
72
|
+
const resolver = createDefaultResolver(() => "");
|
|
73
|
+
|
|
74
|
+
await expect(resolver.checkUpdate?.(createParams())).rejects.toThrow(
|
|
75
|
+
"baseURL resolver must return a non-empty string",
|
|
76
|
+
);
|
|
77
|
+
});
|
|
78
|
+
});
|
package/src/DefaultResolver.ts
CHANGED
|
@@ -1,20 +1,39 @@
|
|
|
1
1
|
import type { AppUpdateInfo } from "@hot-updater/core";
|
|
2
2
|
|
|
3
3
|
import { fetchUpdateInfo } from "./fetchUpdateInfo";
|
|
4
|
-
import type {
|
|
4
|
+
import type {
|
|
5
|
+
HotUpdaterBaseURL,
|
|
6
|
+
HotUpdaterResolver,
|
|
7
|
+
ResolverCheckUpdateParams,
|
|
8
|
+
} from "./types";
|
|
9
|
+
|
|
10
|
+
const resolveBaseURL = async (baseURL: HotUpdaterBaseURL): Promise<string> => {
|
|
11
|
+
const resolvedBaseURL =
|
|
12
|
+
typeof baseURL === "function" ? await baseURL() : baseURL;
|
|
13
|
+
|
|
14
|
+
if (!resolvedBaseURL) {
|
|
15
|
+
throw new Error("baseURL resolver must return a non-empty string");
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
return resolvedBaseURL;
|
|
19
|
+
};
|
|
5
20
|
|
|
6
21
|
/**
|
|
7
22
|
* Creates a default resolver that uses baseURL for network operations.
|
|
8
23
|
* This encapsulates the existing baseURL logic into a resolver.
|
|
9
24
|
*
|
|
10
|
-
* @param baseURL - The base URL for the update server
|
|
25
|
+
* @param baseURL - The base URL for the update server, or a function that
|
|
26
|
+
* resolves it before each update check.
|
|
11
27
|
* @returns A HotUpdaterResolver that uses the baseURL
|
|
12
28
|
*/
|
|
13
|
-
export function createDefaultResolver(
|
|
29
|
+
export function createDefaultResolver(
|
|
30
|
+
baseURL: HotUpdaterBaseURL,
|
|
31
|
+
): HotUpdaterResolver {
|
|
14
32
|
return {
|
|
15
33
|
checkUpdate: async (
|
|
16
34
|
params: ResolverCheckUpdateParams,
|
|
17
35
|
): Promise<AppUpdateInfo | null> => {
|
|
36
|
+
const resolvedBaseURL = await resolveBaseURL(baseURL);
|
|
18
37
|
let url: string;
|
|
19
38
|
const cohortPath = `/${encodeURIComponent(params.cohort)}`;
|
|
20
39
|
|
|
@@ -22,9 +41,9 @@ export function createDefaultResolver(baseURL: string): HotUpdaterResolver {
|
|
|
22
41
|
if (!params.fingerprintHash) {
|
|
23
42
|
throw new Error("Fingerprint hash is required");
|
|
24
43
|
}
|
|
25
|
-
url = `${
|
|
44
|
+
url = `${resolvedBaseURL}/fingerprint/${params.platform}/${params.fingerprintHash}/${params.channel}/${params.minBundleId}/${params.bundleId}${cohortPath}`;
|
|
26
45
|
} else {
|
|
27
|
-
url = `${
|
|
46
|
+
url = `${resolvedBaseURL}/app-version/${params.platform}/${params.appVersion}/${params.channel}/${params.minBundleId}/${params.bundleId}${cohortPath}`;
|
|
28
47
|
}
|
|
29
48
|
|
|
30
49
|
return fetchUpdateInfo({
|
package/src/fetchUpdateInfo.ts
CHANGED
|
@@ -3,20 +3,18 @@ import type { AppUpdateInfo } from "@hot-updater/core";
|
|
|
3
3
|
export const fetchUpdateInfo = async ({
|
|
4
4
|
url,
|
|
5
5
|
requestHeaders,
|
|
6
|
-
onError,
|
|
7
6
|
requestTimeout = 5000,
|
|
8
7
|
}: {
|
|
9
8
|
url: string;
|
|
10
9
|
requestHeaders?: Record<string, string>;
|
|
11
|
-
onError?: (error: Error) => void;
|
|
12
10
|
requestTimeout?: number;
|
|
13
11
|
}): Promise<AppUpdateInfo | null> => {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
}, requestTimeout);
|
|
12
|
+
const controller = new AbortController();
|
|
13
|
+
const timeoutId = setTimeout(() => {
|
|
14
|
+
controller.abort();
|
|
15
|
+
}, requestTimeout);
|
|
19
16
|
|
|
17
|
+
try {
|
|
20
18
|
const headers = {
|
|
21
19
|
"Content-Type": "application/json",
|
|
22
20
|
...requestHeaders,
|
|
@@ -26,7 +24,6 @@ export const fetchUpdateInfo = async ({
|
|
|
26
24
|
signal: controller.signal,
|
|
27
25
|
headers,
|
|
28
26
|
});
|
|
29
|
-
clearTimeout(timeoutId);
|
|
30
27
|
|
|
31
28
|
if (response.status !== 200) {
|
|
32
29
|
throw new Error(response.statusText);
|
|
@@ -34,10 +31,10 @@ export const fetchUpdateInfo = async ({
|
|
|
34
31
|
return response.json();
|
|
35
32
|
} catch (error: unknown) {
|
|
36
33
|
if (error instanceof Error && error.name === "AbortError") {
|
|
37
|
-
|
|
38
|
-
} else {
|
|
39
|
-
onError?.(error as Error);
|
|
34
|
+
throw new Error("Request timed out");
|
|
40
35
|
}
|
|
41
|
-
|
|
36
|
+
throw error;
|
|
37
|
+
} finally {
|
|
38
|
+
clearTimeout(timeoutId);
|
|
42
39
|
}
|
|
43
40
|
};
|
package/src/index.spec.ts
CHANGED
|
@@ -74,7 +74,7 @@ describe("HotUpdater client initialization", () => {
|
|
|
74
74
|
|
|
75
75
|
mocks.addListener.mockReturnValue(() => {});
|
|
76
76
|
mocks.checkForUpdate.mockResolvedValue(null);
|
|
77
|
-
mocks.createDefaultResolver.mockImplementation((baseURL:
|
|
77
|
+
mocks.createDefaultResolver.mockImplementation((baseURL: unknown) => ({
|
|
78
78
|
baseURL,
|
|
79
79
|
checkUpdate: vi.fn(),
|
|
80
80
|
notifyAppReady: vi.fn(),
|
|
@@ -114,6 +114,26 @@ describe("HotUpdater client initialization", () => {
|
|
|
114
114
|
});
|
|
115
115
|
});
|
|
116
116
|
|
|
117
|
+
it("accepts dynamic baseURL resolvers for manual update flows", async () => {
|
|
118
|
+
const resolver = {
|
|
119
|
+
checkUpdate: vi.fn(),
|
|
120
|
+
notifyAppReady: vi.fn(),
|
|
121
|
+
};
|
|
122
|
+
const resolveBaseURL = vi.fn(() => "https://updates.example.com");
|
|
123
|
+
mocks.createDefaultResolver.mockReturnValue(resolver);
|
|
124
|
+
|
|
125
|
+
const HotUpdater = await importHotUpdater();
|
|
126
|
+
|
|
127
|
+
HotUpdater.init({
|
|
128
|
+
baseURL: resolveBaseURL,
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
expect(mocks.createDefaultResolver).toHaveBeenCalledWith(resolveBaseURL);
|
|
132
|
+
expect(mocks.init).toHaveBeenCalledWith({
|
|
133
|
+
resolver,
|
|
134
|
+
});
|
|
135
|
+
});
|
|
136
|
+
|
|
117
137
|
it("uses init configuration for later manual update checks", async () => {
|
|
118
138
|
const resolver = {
|
|
119
139
|
checkUpdate: vi.fn(),
|
package/src/index.ts
CHANGED
package/src/types.ts
CHANGED
package/src/wrap.tsx
CHANGED
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
reload,
|
|
11
11
|
} from "./native";
|
|
12
12
|
import { useHotUpdaterStore } from "./store";
|
|
13
|
-
import type { HotUpdaterResolver } from "./types";
|
|
13
|
+
import type { HotUpdaterBaseURL, HotUpdaterResolver } from "./types";
|
|
14
14
|
|
|
15
15
|
export interface RunUpdateProcessResponse {
|
|
16
16
|
status: "ROLLBACK" | "UPDATE" | "UP_TO_DATE";
|
|
@@ -70,10 +70,12 @@ interface CommonHotUpdaterOptions {
|
|
|
70
70
|
*/
|
|
71
71
|
interface BaseURLConfig {
|
|
72
72
|
/**
|
|
73
|
-
* Base URL for update server
|
|
73
|
+
* Base URL for update server. Use a function to resolve it dynamically
|
|
74
|
+
* before each update check.
|
|
74
75
|
* @example "https://update.example.com"
|
|
76
|
+
* @example () => getUpdateServerURL()
|
|
75
77
|
*/
|
|
76
|
-
baseURL:
|
|
78
|
+
baseURL: HotUpdaterBaseURL;
|
|
77
79
|
|
|
78
80
|
/**
|
|
79
81
|
* Resolver is not allowed when using baseURL
|