@hot-updater/react-native 0.24.0 → 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 (46) hide show
  1. package/lib/commonjs/DefaultResolver.js +38 -0
  2. package/lib/commonjs/DefaultResolver.js.map +1 -0
  3. package/lib/commonjs/checkForUpdate.js +35 -50
  4. package/lib/commonjs/checkForUpdate.js.map +1 -1
  5. package/lib/commonjs/index.js +26 -14
  6. package/lib/commonjs/index.js.map +1 -1
  7. package/lib/commonjs/types.js +12 -0
  8. package/lib/commonjs/types.js.map +1 -1
  9. package/lib/commonjs/wrap.js +44 -13
  10. package/lib/commonjs/wrap.js.map +1 -1
  11. package/lib/module/DefaultResolver.js +34 -0
  12. package/lib/module/DefaultResolver.js.map +1 -0
  13. package/lib/module/checkForUpdate.js +35 -50
  14. package/lib/module/checkForUpdate.js.map +1 -1
  15. package/lib/module/index.js +26 -14
  16. package/lib/module/index.js.map +1 -1
  17. package/lib/module/types.js +12 -0
  18. package/lib/module/types.js.map +1 -1
  19. package/lib/module/wrap.js +44 -13
  20. package/lib/module/wrap.js.map +1 -1
  21. package/lib/typescript/commonjs/DefaultResolver.d.ts +10 -0
  22. package/lib/typescript/commonjs/DefaultResolver.d.ts.map +1 -0
  23. package/lib/typescript/commonjs/checkForUpdate.d.ts +2 -1
  24. package/lib/typescript/commonjs/checkForUpdate.d.ts.map +1 -1
  25. package/lib/typescript/commonjs/index.d.ts +3 -3
  26. package/lib/typescript/commonjs/index.d.ts.map +1 -1
  27. package/lib/typescript/commonjs/types.d.ts +115 -0
  28. package/lib/typescript/commonjs/types.d.ts.map +1 -1
  29. package/lib/typescript/commonjs/wrap.d.ts +64 -10
  30. package/lib/typescript/commonjs/wrap.d.ts.map +1 -1
  31. package/lib/typescript/module/DefaultResolver.d.ts +10 -0
  32. package/lib/typescript/module/DefaultResolver.d.ts.map +1 -0
  33. package/lib/typescript/module/checkForUpdate.d.ts +2 -1
  34. package/lib/typescript/module/checkForUpdate.d.ts.map +1 -1
  35. package/lib/typescript/module/index.d.ts +3 -3
  36. package/lib/typescript/module/index.d.ts.map +1 -1
  37. package/lib/typescript/module/types.d.ts +115 -0
  38. package/lib/typescript/module/types.d.ts.map +1 -1
  39. package/lib/typescript/module/wrap.d.ts +64 -10
  40. package/lib/typescript/module/wrap.d.ts.map +1 -1
  41. package/package.json +7 -7
  42. package/src/DefaultResolver.ts +36 -0
  43. package/src/checkForUpdate.ts +43 -59
  44. package/src/index.ts +51 -19
  45. package/src/types.ts +135 -0
  46. package/src/wrap.tsx +161 -72
@@ -1,3 +1,118 @@
1
+ import type { AppUpdateInfo } from "@hot-updater/core";
2
+ import type { NotifyAppReadyResult } from "./native";
3
+ /**
4
+ * Parameters passed to resolver.checkUpdate method
5
+ */
6
+ export interface ResolverCheckUpdateParams {
7
+ /**
8
+ * The platform the app is running on
9
+ */
10
+ platform: "ios" | "android";
11
+ /**
12
+ * The current app version
13
+ */
14
+ appVersion: string;
15
+ /**
16
+ * The current bundle ID
17
+ */
18
+ bundleId: string;
19
+ /**
20
+ * Minimum bundle ID from build time
21
+ */
22
+ minBundleId: string;
23
+ /**
24
+ * The channel name (e.g., "production", "staging")
25
+ */
26
+ channel: string;
27
+ /**
28
+ * Update strategy being used
29
+ */
30
+ updateStrategy: "fingerprint" | "appVersion";
31
+ /**
32
+ * The fingerprint hash (only present when using fingerprint strategy)
33
+ */
34
+ fingerprintHash: string | null;
35
+ /**
36
+ * Request headers from global config (for optional use)
37
+ */
38
+ requestHeaders?: Record<string, string>;
39
+ /**
40
+ * Request timeout from global config (for optional use)
41
+ */
42
+ requestTimeout?: number;
43
+ }
44
+ /**
45
+ * Parameters passed to resolver.notifyAppReady method
46
+ */
47
+ export interface ResolverNotifyAppReadyParams {
48
+ /**
49
+ * The bundle state from native notifyAppReady
50
+ * - "PROMOTED": Staging bundle was promoted to stable
51
+ * - "RECOVERED": App recovered from crash, rollback occurred
52
+ * - "STABLE": No changes, bundle is stable
53
+ */
54
+ status: "PROMOTED" | "RECOVERED" | "STABLE";
55
+ /**
56
+ * Present only when status is "RECOVERED"
57
+ */
58
+ crashedBundleId?: string;
59
+ /**
60
+ * Request headers from global config (for optional use)
61
+ */
62
+ requestHeaders?: Record<string, string>;
63
+ /**
64
+ * Request timeout from global config (for optional use)
65
+ */
66
+ requestTimeout?: number;
67
+ }
68
+ /**
69
+ * Resolver interface for custom network operations
70
+ */
71
+ export interface HotUpdaterResolver {
72
+ /**
73
+ * Custom implementation for checking updates.
74
+ * When provided, this completely replaces the default fetchUpdateInfo flow.
75
+ *
76
+ * @param params - All parameters needed to check for updates
77
+ * @returns Update information or null if up to date
78
+ *
79
+ * @example
80
+ * ```typescript
81
+ * checkUpdate: async (params) => {
82
+ * const response = await fetch(`https://api.custom.com/check`, {
83
+ * method: 'POST',
84
+ * body: JSON.stringify(params),
85
+ * headers: params.requestHeaders,
86
+ * });
87
+ *
88
+ * if (!response.ok) return null;
89
+ * return response.json();
90
+ * }
91
+ * ```
92
+ */
93
+ checkUpdate?: (params: ResolverCheckUpdateParams) => Promise<AppUpdateInfo | null>;
94
+ /**
95
+ * Custom implementation for notifying app ready.
96
+ * When provided, this completely replaces the default notifyAppReady network flow.
97
+ * Note: The native notifyAppReady for bundle promotion still happens automatically.
98
+ *
99
+ * @param params - All parameters about the current app state
100
+ * @returns Notification result
101
+ *
102
+ * @example
103
+ * ```typescript
104
+ * notifyAppReady: async (params) => {
105
+ * await fetch(`https://api.custom.com/notify`, {
106
+ * method: 'POST',
107
+ * body: JSON.stringify(params),
108
+ * });
109
+ *
110
+ * return { status: "STABLE" };
111
+ * }
112
+ * ```
113
+ */
114
+ notifyAppReady?: (params: ResolverNotifyAppReadyParams) => Promise<NotifyAppReadyResult | undefined>;
115
+ }
1
116
  /**
2
117
  * Information about a signature verification failure.
3
118
  * This is a security-critical event that indicates the bundle
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/types.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,MAAM,WAAW,4BAA4B;IAC3C;;OAEG;IACH,QAAQ,EAAE,MAAM,CAAC;IACjB;;OAEG;IACH,OAAO,EAAE,MAAM,CAAC;IAChB;;OAEG;IACH,KAAK,EAAE,KAAK,CAAC;CACd;AAED;;;;;;;;GAQG;AACH,wBAAgB,4BAA4B,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAgBpE;AAED;;GAEG;AACH,wBAAgB,uBAAuB,CACrC,KAAK,EAAE,OAAO,EACd,QAAQ,EAAE,MAAM,GACf,4BAA4B,CAS9B"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AACvD,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,UAAU,CAAC;AAErD;;GAEG;AACH,MAAM,WAAW,yBAAyB;IACxC;;OAEG;IACH,QAAQ,EAAE,KAAK,GAAG,SAAS,CAAC;IAE5B;;OAEG;IACH,UAAU,EAAE,MAAM,CAAC;IAEnB;;OAEG;IACH,QAAQ,EAAE,MAAM,CAAC;IAEjB;;OAEG;IACH,WAAW,EAAE,MAAM,CAAC;IAEpB;;OAEG;IACH,OAAO,EAAE,MAAM,CAAC;IAEhB;;OAEG;IACH,cAAc,EAAE,aAAa,GAAG,YAAY,CAAC;IAE7C;;OAEG;IACH,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;IAE/B;;OAEG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAExC;;OAEG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED;;GAEG;AACH,MAAM,WAAW,4BAA4B;IAC3C;;;;;OAKG;IACH,MAAM,EAAE,UAAU,GAAG,WAAW,GAAG,QAAQ,CAAC;IAE5C;;OAEG;IACH,eAAe,CAAC,EAAE,MAAM,CAAC;IAEzB;;OAEG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAExC;;OAEG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED;;GAEG;AACH,MAAM,WAAW,kBAAkB;IACjC;;;;;;;;;;;;;;;;;;;;OAoBG;IACH,WAAW,CAAC,EAAE,CACZ,MAAM,EAAE,yBAAyB,KAC9B,OAAO,CAAC,aAAa,GAAG,IAAI,CAAC,CAAC;IAEnC;;;;;;;;;;;;;;;;;;;OAmBG;IACH,cAAc,CAAC,EAAE,CACf,MAAM,EAAE,4BAA4B,KACjC,OAAO,CAAC,oBAAoB,GAAG,SAAS,CAAC,CAAC;CAChD;AAED;;;;GAIG;AACH,MAAM,WAAW,4BAA4B;IAC3C;;OAEG;IACH,QAAQ,EAAE,MAAM,CAAC;IACjB;;OAEG;IACH,OAAO,EAAE,MAAM,CAAC;IAChB;;OAEG;IACH,KAAK,EAAE,KAAK,CAAC;CACd;AAED;;;;;;;;GAQG;AACH,wBAAgB,4BAA4B,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAgBpE;AAED;;GAEG;AACH,wBAAgB,uBAAuB,CACrC,KAAK,EAAE,OAAO,EACd,QAAQ,EAAE,MAAM,GACf,4BAA4B,CAS9B"}
@@ -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 { HotUpdaterResolver } from "./types";
4
5
  export interface RunUpdateProcessResponse {
5
6
  status: "ROLLBACK" | "UPDATE" | "UP_TO_DATE";
6
7
  shouldForceUpdate: boolean;
@@ -12,11 +13,6 @@ type UpdateStatus = "CHECK_FOR_UPDATE" | "UPDATING" | "UPDATE_PROCESS_COMPLETED"
12
13
  * Common options shared between auto and manual update modes
13
14
  */
14
15
  interface CommonHotUpdaterOptions {
15
- /**
16
- * Base URL for update server
17
- * @example "https://update.example.com"
18
- */
19
- baseURL: string;
20
16
  /**
21
17
  * Custom request headers for update checks
22
18
  */
@@ -54,7 +50,38 @@ interface CommonHotUpdaterOptions {
54
50
  */
55
51
  onNotifyAppReady?: (result: NotifyAppReadyResult) => void;
56
52
  }
57
- export interface AutoUpdateOptions extends CommonHotUpdaterOptions {
53
+ /**
54
+ * Configuration with baseURL for standard server-based updates
55
+ */
56
+ interface BaseURLConfig {
57
+ /**
58
+ * Base URL for update server
59
+ * @example "https://update.example.com"
60
+ */
61
+ baseURL: string;
62
+ /**
63
+ * Resolver is not allowed when using baseURL
64
+ */
65
+ resolver?: never;
66
+ }
67
+ /**
68
+ * Configuration with resolver for custom network operations
69
+ */
70
+ interface ResolverConfig {
71
+ /**
72
+ * Custom resolver for network operations
73
+ */
74
+ resolver: HotUpdaterResolver;
75
+ /**
76
+ * baseURL is not allowed when using resolver
77
+ */
78
+ baseURL?: never;
79
+ }
80
+ /**
81
+ * Union type ensuring baseURL and resolver are mutually exclusive
82
+ */
83
+ type NetworkConfig = BaseURLConfig | ResolverConfig;
84
+ export type AutoUpdateOptions = CommonHotUpdaterOptions & NetworkConfig & {
58
85
  /**
59
86
  * Update strategy
60
87
  * - "fingerprint": Use fingerprint hash to check for updates
@@ -108,15 +135,42 @@ export interface AutoUpdateOptions extends CommonHotUpdaterOptions {
108
135
  * @see {@link https://hot-updater.dev/docs/react-native-api/wrap#onupdateprocesscompleted}
109
136
  */
110
137
  onUpdateProcessCompleted?: (response: RunUpdateProcessResponse) => void;
111
- }
112
- export interface ManualUpdateOptions extends CommonHotUpdaterOptions {
138
+ };
139
+ export type ManualUpdateOptions = CommonHotUpdaterOptions & NetworkConfig & {
113
140
  /**
114
141
  * Update mode
115
142
  * - "manual": Only notify app ready, user manually calls checkForUpdate()
116
143
  */
117
144
  updateMode: "manual";
118
- }
145
+ };
119
146
  export type HotUpdaterOptions = AutoUpdateOptions | ManualUpdateOptions;
120
- export declare function wrap<P extends React.JSX.IntrinsicAttributes = object>(options: HotUpdaterOptions): (WrappedComponent: React.ComponentType<P>) => React.ComponentType<P>;
147
+ /**
148
+ * Internal options after normalization in index.ts
149
+ * Always has resolver (never baseURL)
150
+ */
151
+ type InternalCommonOptions = {
152
+ resolver: HotUpdaterResolver;
153
+ requestHeaders?: Record<string, string>;
154
+ requestTimeout?: number;
155
+ onNotifyAppReady?: (result: NotifyAppReadyResult) => void;
156
+ };
157
+ type InternalAutoUpdateOptions = InternalCommonOptions & {
158
+ updateStrategy: "fingerprint" | "appVersion";
159
+ updateMode: "auto";
160
+ onError?: (error: HotUpdaterError | Error | unknown) => void;
161
+ fallbackComponent?: React.FC<{
162
+ status: Exclude<UpdateStatus, "UPDATE_PROCESS_COMPLETED">;
163
+ progress: number;
164
+ message: string | null;
165
+ }>;
166
+ onProgress?: (progress: number) => void;
167
+ reloadOnForceUpdate?: boolean;
168
+ onUpdateProcessCompleted?: (response: RunUpdateProcessResponse) => void;
169
+ };
170
+ type InternalManualUpdateOptions = InternalCommonOptions & {
171
+ updateMode: "manual";
172
+ };
173
+ export type InternalWrapOptions = InternalAutoUpdateOptions | InternalManualUpdateOptions;
174
+ export declare function wrap<P extends React.JSX.IntrinsicAttributes = object>(options: InternalWrapOptions): (WrappedComponent: React.ComponentType<P>) => React.ComponentType<P>;
121
175
  export {};
122
176
  //# sourceMappingURL=wrap.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"wrap.d.ts","sourceRoot":"","sources":["../../../src/wrap.tsx"],"names":[],"mappings":"AAAA,OAAO,KAA+C,MAAM,OAAO,CAAC;AAEpE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAE/C,OAAO,EAEL,KAAK,oBAAoB,EAG1B,MAAM,UAAU,CAAC;AAGlB,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;;;OAGG;IACH,OAAO,EAAE,MAAM,CAAC;IAEhB;;OAEG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAExC;;;OAGG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;IAExB;;;;;;;;;;;;;;;;;;;;;;;;;OAyBG;IACH,gBAAgB,CAAC,EAAE,CAAC,MAAM,EAAE,oBAAoB,KAAK,IAAI,CAAC;CAC3D;AAED,MAAM,WAAW,iBAAkB,SAAQ,uBAAuB;IAChE;;;;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;AAED,MAAM,WAAW,mBAAoB,SAAQ,uBAAuB;IAClE;;;OAGG;IACH,UAAU,EAAE,QAAQ,CAAC;CACtB;AAED,MAAM,MAAM,iBAAiB,GAAG,iBAAiB,GAAG,mBAAmB,CAAC;AAExE,wBAAgB,IAAI,CAAC,CAAC,SAAS,KAAK,CAAC,GAAG,CAAC,mBAAmB,GAAG,MAAM,EACnE,OAAO,EAAE,iBAAiB,GACzB,CAAC,gBAAgB,EAAE,KAAK,CAAC,aAAa,CAAC,CAAC,CAAC,KAAK,KAAK,CAAC,aAAa,CAAC,CAAC,CAAC,CAwItE"}
1
+ {"version":3,"file":"wrap.d.ts","sourceRoot":"","sources":["../../../src/wrap.tsx"],"names":[],"mappings":"AAAA,OAAO,KAA+C,MAAM,OAAO,CAAC;AAEpE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAE/C,OAAO,EAEL,KAAK,oBAAoB,EAG1B,MAAM,UAAU,CAAC;AAElB,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,SAAS,CAAC;AAElD,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;;;;;;;;;;;;;;;;;;;;;;;;;OAyBG;IACH,gBAAgB,CAAC,EAAE,CAAC,MAAM,EAAE,oBAAoB,KAAK,IAAI,CAAC;CAC3D;AAED;;GAEG;AACH,UAAU,aAAa;IACrB;;;OAGG;IACH,OAAO,EAAE,MAAM,CAAC;IAEhB;;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,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,GAC3B,yBAAyB,GACzB,2BAA2B,CAAC;AAmChC,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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hot-updater/react-native",
3
- "version": "0.24.0",
3
+ "version": "0.24.1",
4
4
  "description": "React Native OTA solution for self-hosted",
5
5
  "main": "lib/commonjs/index",
6
6
  "module": "lib/module/index",
@@ -116,18 +116,18 @@
116
116
  "@types/use-sync-external-store": "^0.0.6",
117
117
  "del-cli": "^6.0.0",
118
118
  "expo": "^50.0.0",
119
- "react": "19.1.2",
119
+ "react": "19.1.0",
120
120
  "react-native": "0.79.1",
121
121
  "react-native-builder-bob": "^0.40.10",
122
122
  "typescript": "^5.8.3",
123
- "hot-updater": "0.24.0"
123
+ "hot-updater": "0.24.1"
124
124
  },
125
125
  "dependencies": {
126
126
  "use-sync-external-store": "1.5.0",
127
- "@hot-updater/cli-tools": "0.24.0",
128
- "@hot-updater/js": "0.24.0",
129
- "@hot-updater/core": "0.24.0",
130
- "@hot-updater/plugin-core": "0.24.0"
127
+ "@hot-updater/cli-tools": "0.24.1",
128
+ "@hot-updater/core": "0.24.1",
129
+ "@hot-updater/plugin-core": "0.24.1",
130
+ "@hot-updater/js": "0.24.1"
131
131
  },
132
132
  "scripts": {
133
133
  "build": "bob build && tsc -p plugin/tsconfig.build.json",
@@ -0,0 +1,36 @@
1
+ import type { AppUpdateInfo } from "@hot-updater/core";
2
+ import { fetchUpdateInfo } from "./fetchUpdateInfo";
3
+ import type { HotUpdaterResolver, ResolverCheckUpdateParams } from "./types";
4
+
5
+ /**
6
+ * Creates a default resolver that uses baseURL for network operations.
7
+ * This encapsulates the existing baseURL logic into a resolver.
8
+ *
9
+ * @param baseURL - The base URL for the update server
10
+ * @returns A HotUpdaterResolver that uses the baseURL
11
+ */
12
+ export function createDefaultResolver(baseURL: string): HotUpdaterResolver {
13
+ return {
14
+ checkUpdate: async (
15
+ params: ResolverCheckUpdateParams,
16
+ ): Promise<AppUpdateInfo | null> => {
17
+ // Build URL based on strategy (existing buildUpdateUrl logic)
18
+ let url: string;
19
+ if (params.updateStrategy === "fingerprint") {
20
+ if (!params.fingerprintHash) {
21
+ throw new Error("Fingerprint hash is required");
22
+ }
23
+ url = `${baseURL}/fingerprint/${params.platform}/${params.fingerprintHash}/${params.channel}/${params.minBundleId}/${params.bundleId}`;
24
+ } else {
25
+ url = `${baseURL}/app-version/${params.platform}/${params.appVersion}/${params.channel}/${params.minBundleId}/${params.bundleId}`;
26
+ }
27
+
28
+ // Use existing fetchUpdateInfo
29
+ return fetchUpdateInfo({
30
+ url,
31
+ requestHeaders: params.requestHeaders,
32
+ requestTimeout: params.requestTimeout,
33
+ });
34
+ },
35
+ };
36
+ }
@@ -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 } from "./fetchUpdateInfo";
5
4
  import {
6
5
  getAppVersion,
7
6
  getBundleId,
@@ -10,6 +9,7 @@ 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
15
  /**
@@ -37,28 +37,9 @@ export type CheckForUpdateResult = AppUpdateInfo & {
37
37
  updateBundle: () => Promise<boolean>;
38
38
  };
39
39
 
40
- // Internal type that includes baseURL for use within index.ts
40
+ // Internal type that includes resolver for use within index.ts
41
41
  export interface InternalCheckForUpdateOptions extends CheckForUpdateOptions {
42
- baseURL: string;
43
- }
44
-
45
- // Internal function to build update URL (not exported)
46
- function buildUpdateUrl(
47
- baseURL: string,
48
- updateStrategy: "appVersion" | "fingerprint",
49
- params: UpdateBundleParams,
50
- ): string {
51
- switch (updateStrategy) {
52
- case "fingerprint": {
53
- if (!params.fingerprintHash) {
54
- throw new HotUpdaterError("Fingerprint hash is required");
55
- }
56
- return `${baseURL}/fingerprint/${params.platform}/${params.fingerprintHash}/${params.channel}/${params.minBundleId}/${params.bundleId}`;
57
- }
58
- case "appVersion": {
59
- return `${baseURL}/app-version/${params.platform}/${params.appVersion}/${params.channel}/${params.minBundleId}/${params.bundleId}`;
60
- }
61
- }
42
+ resolver: HotUpdaterResolver;
62
43
  }
63
44
 
64
45
  export async function checkForUpdate(
@@ -75,13 +56,6 @@ export async function checkForUpdate(
75
56
  return null;
76
57
  }
77
58
 
78
- if (!options.baseURL || !options.updateStrategy) {
79
- options.onError?.(
80
- new HotUpdaterError("'baseURL' and 'updateStrategy' are required"),
81
- );
82
- return null;
83
- }
84
-
85
59
  const currentAppVersion = getAppVersion();
86
60
  const platform = Platform.OS as "ios" | "android";
87
61
  const currentBundleId = getBundleId();
@@ -95,35 +69,45 @@ export async function checkForUpdate(
95
69
 
96
70
  const fingerprintHash = getFingerprintHash();
97
71
 
98
- const url = buildUpdateUrl(options.baseURL, options.updateStrategy, {
99
- platform,
100
- appVersion: currentAppVersion,
101
- fingerprintHash: fingerprintHash ?? null,
102
- channel,
103
- minBundleId,
104
- bundleId: currentBundleId,
105
- });
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;
106
80
 
107
- return fetchUpdateInfo({
108
- url,
109
- requestHeaders: options.requestHeaders,
110
- onError: options.onError,
111
- requestTimeout: options.requestTimeout,
112
- }).then((updateInfo) => {
113
- if (!updateInfo) {
114
- return null;
115
- }
81
+ try {
82
+ updateInfo = await options.resolver.checkUpdate({
83
+ platform,
84
+ appVersion: currentAppVersion,
85
+ bundleId: currentBundleId,
86
+ minBundleId,
87
+ channel,
88
+ updateStrategy: options.updateStrategy,
89
+ fingerprintHash,
90
+ requestHeaders: options.requestHeaders,
91
+ requestTimeout: options.requestTimeout,
92
+ });
93
+ } catch (error) {
94
+ options.onError?.(error as Error);
95
+ return null;
96
+ }
97
+
98
+ if (!updateInfo) {
99
+ return null;
100
+ }
116
101
 
117
- return {
118
- ...updateInfo,
119
- updateBundle: async () => {
120
- return updateBundle({
121
- bundleId: updateInfo.id,
122
- fileUrl: updateInfo.fileUrl,
123
- fileHash: updateInfo.fileHash,
124
- status: updateInfo.status,
125
- });
126
- },
127
- };
128
- });
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
+ },
112
+ };
129
113
  }
package/src/index.ts CHANGED
@@ -3,6 +3,7 @@ import {
3
3
  checkForUpdate,
4
4
  type InternalCheckForUpdateOptions,
5
5
  } from "./checkForUpdate";
6
+ import { createDefaultResolver } from "./DefaultResolver";
6
7
  import {
7
8
  addListener,
8
9
  clearCrashHistory,
@@ -17,13 +18,17 @@ import {
17
18
  updateBundle,
18
19
  } from "./native";
19
20
  import { hotUpdaterStore } from "./store";
20
- import { type HotUpdaterOptions, wrap } from "./wrap";
21
+ import type { HotUpdaterResolver } from "./types";
22
+ import { type HotUpdaterOptions, type InternalWrapOptions, wrap } from "./wrap";
21
23
 
22
24
  export type { HotUpdaterEvent, NotifyAppReadyResult } from "./native";
23
25
  export * from "./store";
24
26
  export {
25
27
  extractSignatureFailure,
28
+ type HotUpdaterResolver,
26
29
  isSignatureVerificationError,
30
+ type ResolverCheckUpdateParams,
31
+ type ResolverNotifyAppReadyParams,
27
32
  type SignatureVerificationFailure,
28
33
  } from "./types";
29
34
  export type { HotUpdaterOptions, RunUpdateProcessResponse } from "./wrap";
@@ -41,15 +46,15 @@ addListener("onProgress", ({ progress }) => {
41
46
  function createHotUpdaterClient() {
42
47
  // Global configuration stored from wrap
43
48
  const globalConfig: {
44
- baseURL: string | null;
49
+ resolver: HotUpdaterResolver | null;
45
50
  requestHeaders?: Record<string, string>;
46
51
  requestTimeout?: number;
47
52
  } = {
48
- baseURL: null,
53
+ resolver: null,
49
54
  };
50
55
 
51
- function ensureGlobalBaseURL(methodName: string) {
52
- if (globalConfig.baseURL === null) {
56
+ const ensureGlobalResolver = (methodName: string) => {
57
+ if (!globalConfig.resolver) {
53
58
  throw new Error(
54
59
  `[HotUpdater] ${methodName} requires HotUpdater.wrap() to be used.\n\n` +
55
60
  `To fix this issue, wrap your root component with HotUpdater.wrap():\n\n` +
@@ -67,9 +72,8 @@ function createHotUpdaterClient() {
67
72
  `For more information, visit: https://hot-updater.dev/docs/react-native-api/wrap`,
68
73
  );
69
74
  }
70
-
71
- return globalConfig.baseURL;
72
- }
75
+ return globalConfig.resolver;
76
+ };
73
77
 
74
78
  return {
75
79
  /**
@@ -96,12 +100,44 @@ function createHotUpdaterClient() {
96
100
  * })(App);
97
101
  * ```
98
102
  */
99
- wrap: ((options: HotUpdaterOptions) => {
100
- globalConfig.baseURL = options.baseURL;
103
+ wrap: (options: HotUpdaterOptions) => {
104
+ let normalizedOptions: InternalWrapOptions;
105
+
106
+ if ("baseURL" in options && options.baseURL) {
107
+ const { baseURL, ...rest } = options;
108
+ normalizedOptions = {
109
+ ...rest,
110
+ resolver: createDefaultResolver(baseURL),
111
+ };
112
+ } else if ("resolver" in options && options.resolver) {
113
+ normalizedOptions = options;
114
+ } else {
115
+ throw new Error(
116
+ `[HotUpdater] Either baseURL or resolver must be provided.\n\n` +
117
+ `Option 1: Using baseURL (recommended for most cases)\n` +
118
+ ` export default HotUpdater.wrap({\n` +
119
+ ` baseURL: "<your-update-server-url>",\n` +
120
+ ` updateStrategy: "appVersion",\n` +
121
+ ` updateMode: "auto"\n` +
122
+ ` })(App);\n\n` +
123
+ `Option 2: Using custom resolver (advanced)\n` +
124
+ ` export default HotUpdater.wrap({\n` +
125
+ ` resolver: {\n` +
126
+ ` checkUpdate: async (params) => { /* custom logic */ },\n` +
127
+ ` notifyAppReady: async (params) => { /* custom logic */ }\n` +
128
+ ` },\n` +
129
+ ` updateMode: "manual"\n` +
130
+ ` })(App);\n\n` +
131
+ `For more information, visit: https://hot-updater.dev/docs/react-native-api/wrap`,
132
+ );
133
+ }
134
+
135
+ globalConfig.resolver = normalizedOptions.resolver;
101
136
  globalConfig.requestHeaders = options.requestHeaders;
102
137
  globalConfig.requestTimeout = options.requestTimeout;
103
- return wrap(options);
104
- }) as typeof wrap,
138
+
139
+ return wrap(normalizedOptions);
140
+ },
105
141
 
106
142
  /**
107
143
  * Reloads the app.
@@ -206,14 +242,11 @@ function createHotUpdaterClient() {
206
242
  * ```
207
243
  */
208
244
  checkForUpdate: (config: CheckForUpdateOptions) => {
209
- const baseURL = ensureGlobalBaseURL("checkForUpdate");
245
+ const resolver = ensureGlobalResolver("checkForUpdate");
210
246
 
211
- // Merge globalConfig with provided config
212
- // baseURL is always from wrap (globalConfig)
213
- // requestHeaders/requestTimeout from wrap are used as defaults, but can be overridden
214
247
  const mergedConfig: InternalCheckForUpdateOptions = {
215
248
  ...config,
216
- baseURL,
249
+ resolver,
217
250
  requestHeaders: {
218
251
  ...globalConfig.requestHeaders,
219
252
  ...config.requestHeaders,
@@ -258,8 +291,7 @@ function createHotUpdaterClient() {
258
291
  * ```
259
292
  */
260
293
  updateBundle: (params: UpdateParams) => {
261
- ensureGlobalBaseURL("updateBundle");
262
-
294
+ ensureGlobalResolver("updateBundle");
263
295
  return updateBundle(params);
264
296
  },
265
297