@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.
- package/lib/commonjs/DefaultResolver.js +38 -0
- package/lib/commonjs/DefaultResolver.js.map +1 -0
- package/lib/commonjs/checkForUpdate.js +35 -50
- package/lib/commonjs/checkForUpdate.js.map +1 -1
- package/lib/commonjs/index.js +26 -14
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/types.js +12 -0
- package/lib/commonjs/types.js.map +1 -1
- package/lib/commonjs/wrap.js +44 -13
- package/lib/commonjs/wrap.js.map +1 -1
- package/lib/module/DefaultResolver.js +34 -0
- package/lib/module/DefaultResolver.js.map +1 -0
- package/lib/module/checkForUpdate.js +35 -50
- package/lib/module/checkForUpdate.js.map +1 -1
- package/lib/module/index.js +26 -14
- package/lib/module/index.js.map +1 -1
- package/lib/module/types.js +12 -0
- package/lib/module/types.js.map +1 -1
- package/lib/module/wrap.js +44 -13
- package/lib/module/wrap.js.map +1 -1
- package/lib/typescript/commonjs/DefaultResolver.d.ts +10 -0
- package/lib/typescript/commonjs/DefaultResolver.d.ts.map +1 -0
- package/lib/typescript/commonjs/checkForUpdate.d.ts +2 -1
- package/lib/typescript/commonjs/checkForUpdate.d.ts.map +1 -1
- package/lib/typescript/commonjs/index.d.ts +3 -3
- package/lib/typescript/commonjs/index.d.ts.map +1 -1
- package/lib/typescript/commonjs/types.d.ts +115 -0
- package/lib/typescript/commonjs/types.d.ts.map +1 -1
- package/lib/typescript/commonjs/wrap.d.ts +64 -10
- package/lib/typescript/commonjs/wrap.d.ts.map +1 -1
- package/lib/typescript/module/DefaultResolver.d.ts +10 -0
- package/lib/typescript/module/DefaultResolver.d.ts.map +1 -0
- package/lib/typescript/module/checkForUpdate.d.ts +2 -1
- package/lib/typescript/module/checkForUpdate.d.ts.map +1 -1
- package/lib/typescript/module/index.d.ts +3 -3
- package/lib/typescript/module/index.d.ts.map +1 -1
- package/lib/typescript/module/types.d.ts +115 -0
- package/lib/typescript/module/types.d.ts.map +1 -1
- package/lib/typescript/module/wrap.d.ts +64 -10
- package/lib/typescript/module/wrap.d.ts.map +1 -1
- package/package.json +7 -7
- package/src/DefaultResolver.ts +36 -0
- package/src/checkForUpdate.ts +43 -59
- package/src/index.ts +51 -19
- package/src/types.ts +135 -0
- package/src/wrap.tsx +161 -72
package/src/types.ts
CHANGED
|
@@ -1,3 +1,138 @@
|
|
|
1
|
+
import type { AppUpdateInfo } from "@hot-updater/core";
|
|
2
|
+
import type { NotifyAppReadyResult } from "./native";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Parameters passed to resolver.checkUpdate method
|
|
6
|
+
*/
|
|
7
|
+
export interface ResolverCheckUpdateParams {
|
|
8
|
+
/**
|
|
9
|
+
* The platform the app is running on
|
|
10
|
+
*/
|
|
11
|
+
platform: "ios" | "android";
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* The current app version
|
|
15
|
+
*/
|
|
16
|
+
appVersion: string;
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* The current bundle ID
|
|
20
|
+
*/
|
|
21
|
+
bundleId: string;
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Minimum bundle ID from build time
|
|
25
|
+
*/
|
|
26
|
+
minBundleId: string;
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* The channel name (e.g., "production", "staging")
|
|
30
|
+
*/
|
|
31
|
+
channel: string;
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Update strategy being used
|
|
35
|
+
*/
|
|
36
|
+
updateStrategy: "fingerprint" | "appVersion";
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* The fingerprint hash (only present when using fingerprint strategy)
|
|
40
|
+
*/
|
|
41
|
+
fingerprintHash: string | null;
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Request headers from global config (for optional use)
|
|
45
|
+
*/
|
|
46
|
+
requestHeaders?: Record<string, string>;
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Request timeout from global config (for optional use)
|
|
50
|
+
*/
|
|
51
|
+
requestTimeout?: number;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Parameters passed to resolver.notifyAppReady method
|
|
56
|
+
*/
|
|
57
|
+
export interface ResolverNotifyAppReadyParams {
|
|
58
|
+
/**
|
|
59
|
+
* The bundle state from native notifyAppReady
|
|
60
|
+
* - "PROMOTED": Staging bundle was promoted to stable
|
|
61
|
+
* - "RECOVERED": App recovered from crash, rollback occurred
|
|
62
|
+
* - "STABLE": No changes, bundle is stable
|
|
63
|
+
*/
|
|
64
|
+
status: "PROMOTED" | "RECOVERED" | "STABLE";
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Present only when status is "RECOVERED"
|
|
68
|
+
*/
|
|
69
|
+
crashedBundleId?: string;
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Request headers from global config (for optional use)
|
|
73
|
+
*/
|
|
74
|
+
requestHeaders?: Record<string, string>;
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Request timeout from global config (for optional use)
|
|
78
|
+
*/
|
|
79
|
+
requestTimeout?: number;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Resolver interface for custom network operations
|
|
84
|
+
*/
|
|
85
|
+
export interface HotUpdaterResolver {
|
|
86
|
+
/**
|
|
87
|
+
* Custom implementation for checking updates.
|
|
88
|
+
* When provided, this completely replaces the default fetchUpdateInfo flow.
|
|
89
|
+
*
|
|
90
|
+
* @param params - All parameters needed to check for updates
|
|
91
|
+
* @returns Update information or null if up to date
|
|
92
|
+
*
|
|
93
|
+
* @example
|
|
94
|
+
* ```typescript
|
|
95
|
+
* checkUpdate: async (params) => {
|
|
96
|
+
* const response = await fetch(`https://api.custom.com/check`, {
|
|
97
|
+
* method: 'POST',
|
|
98
|
+
* body: JSON.stringify(params),
|
|
99
|
+
* headers: params.requestHeaders,
|
|
100
|
+
* });
|
|
101
|
+
*
|
|
102
|
+
* if (!response.ok) return null;
|
|
103
|
+
* return response.json();
|
|
104
|
+
* }
|
|
105
|
+
* ```
|
|
106
|
+
*/
|
|
107
|
+
checkUpdate?: (
|
|
108
|
+
params: ResolverCheckUpdateParams,
|
|
109
|
+
) => Promise<AppUpdateInfo | null>;
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Custom implementation for notifying app ready.
|
|
113
|
+
* When provided, this completely replaces the default notifyAppReady network flow.
|
|
114
|
+
* Note: The native notifyAppReady for bundle promotion still happens automatically.
|
|
115
|
+
*
|
|
116
|
+
* @param params - All parameters about the current app state
|
|
117
|
+
* @returns Notification result
|
|
118
|
+
*
|
|
119
|
+
* @example
|
|
120
|
+
* ```typescript
|
|
121
|
+
* notifyAppReady: async (params) => {
|
|
122
|
+
* await fetch(`https://api.custom.com/notify`, {
|
|
123
|
+
* method: 'POST',
|
|
124
|
+
* body: JSON.stringify(params),
|
|
125
|
+
* });
|
|
126
|
+
*
|
|
127
|
+
* return { status: "STABLE" };
|
|
128
|
+
* }
|
|
129
|
+
* ```
|
|
130
|
+
*/
|
|
131
|
+
notifyAppReady?: (
|
|
132
|
+
params: ResolverNotifyAppReadyParams,
|
|
133
|
+
) => Promise<NotifyAppReadyResult | undefined>;
|
|
134
|
+
}
|
|
135
|
+
|
|
1
136
|
/**
|
|
2
137
|
* Information about a signature verification failure.
|
|
3
138
|
* This is a security-critical event that indicates the bundle
|
package/src/wrap.tsx
CHANGED
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
reload,
|
|
10
10
|
} from "./native";
|
|
11
11
|
import { useHotUpdaterStore } from "./store";
|
|
12
|
+
import type { HotUpdaterResolver } from "./types";
|
|
12
13
|
|
|
13
14
|
export interface RunUpdateProcessResponse {
|
|
14
15
|
status: "ROLLBACK" | "UPDATE" | "UP_TO_DATE";
|
|
@@ -26,12 +27,6 @@ type UpdateStatus =
|
|
|
26
27
|
* Common options shared between auto and manual update modes
|
|
27
28
|
*/
|
|
28
29
|
interface CommonHotUpdaterOptions {
|
|
29
|
-
/**
|
|
30
|
-
* Base URL for update server
|
|
31
|
-
* @example "https://update.example.com"
|
|
32
|
-
*/
|
|
33
|
-
baseURL: string;
|
|
34
|
-
|
|
35
30
|
/**
|
|
36
31
|
* Custom request headers for update checks
|
|
37
32
|
*/
|
|
@@ -72,91 +67,190 @@ interface CommonHotUpdaterOptions {
|
|
|
72
67
|
onNotifyAppReady?: (result: NotifyAppReadyResult) => void;
|
|
73
68
|
}
|
|
74
69
|
|
|
75
|
-
|
|
70
|
+
/**
|
|
71
|
+
* Configuration with baseURL for standard server-based updates
|
|
72
|
+
*/
|
|
73
|
+
interface BaseURLConfig {
|
|
76
74
|
/**
|
|
77
|
-
*
|
|
78
|
-
*
|
|
79
|
-
* - "appVersion": Use app version to check for updates
|
|
75
|
+
* Base URL for update server
|
|
76
|
+
* @example "https://update.example.com"
|
|
80
77
|
*/
|
|
81
|
-
|
|
78
|
+
baseURL: string;
|
|
82
79
|
|
|
83
80
|
/**
|
|
84
|
-
*
|
|
85
|
-
* - "auto": Automatically check and download updates
|
|
81
|
+
* Resolver is not allowed when using baseURL
|
|
86
82
|
*/
|
|
87
|
-
|
|
83
|
+
resolver?: never;
|
|
84
|
+
}
|
|
88
85
|
|
|
89
|
-
|
|
86
|
+
/**
|
|
87
|
+
* Configuration with resolver for custom network operations
|
|
88
|
+
*/
|
|
89
|
+
interface ResolverConfig {
|
|
90
|
+
/**
|
|
91
|
+
* Custom resolver for network operations
|
|
92
|
+
*/
|
|
93
|
+
resolver: HotUpdaterResolver;
|
|
90
94
|
|
|
91
95
|
/**
|
|
92
|
-
*
|
|
93
|
-
*
|
|
94
|
-
* When an update exists and the bundle is being downloaded, this component will block access
|
|
95
|
-
* to the entry point and show download progress.
|
|
96
|
-
*
|
|
97
|
-
* @see {@link https://hot-updater.dev/docs/react-native-api/wrap#fallback-component}
|
|
98
|
-
*
|
|
99
|
-
* ```tsx
|
|
100
|
-
* HotUpdater.wrap({
|
|
101
|
-
* baseURL: "<update-server-url>",
|
|
102
|
-
* updateStrategy: "appVersion",
|
|
103
|
-
* fallbackComponent: ({ progress = 0 }) => (
|
|
104
|
-
* <View style={styles.container}>
|
|
105
|
-
* <Text style={styles.text}>Updating... {progress}%</Text>
|
|
106
|
-
* </View>
|
|
107
|
-
* )
|
|
108
|
-
* })(App)
|
|
109
|
-
* ```
|
|
110
|
-
*
|
|
111
|
-
* If not defined, the bundle will download in the background without blocking the screen.
|
|
96
|
+
* baseURL is not allowed when using resolver
|
|
112
97
|
*/
|
|
98
|
+
baseURL?: never;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Union type ensuring baseURL and resolver are mutually exclusive
|
|
103
|
+
*/
|
|
104
|
+
type NetworkConfig = BaseURLConfig | ResolverConfig;
|
|
105
|
+
|
|
106
|
+
export type AutoUpdateOptions = CommonHotUpdaterOptions &
|
|
107
|
+
NetworkConfig & {
|
|
108
|
+
/**
|
|
109
|
+
* Update strategy
|
|
110
|
+
* - "fingerprint": Use fingerprint hash to check for updates
|
|
111
|
+
* - "appVersion": Use app version to check for updates
|
|
112
|
+
*/
|
|
113
|
+
updateStrategy: "fingerprint" | "appVersion";
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Update mode
|
|
117
|
+
* - "auto": Automatically check and download updates
|
|
118
|
+
*/
|
|
119
|
+
updateMode: "auto";
|
|
120
|
+
|
|
121
|
+
onError?: (error: HotUpdaterError | Error | unknown) => void;
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Component to show while downloading a new bundle update.
|
|
125
|
+
*
|
|
126
|
+
* When an update exists and the bundle is being downloaded, this component will block access
|
|
127
|
+
* to the entry point and show download progress.
|
|
128
|
+
*
|
|
129
|
+
* @see {@link https://hot-updater.dev/docs/react-native-api/wrap#fallback-component}
|
|
130
|
+
*
|
|
131
|
+
* ```tsx
|
|
132
|
+
* HotUpdater.wrap({
|
|
133
|
+
* baseURL: "<update-server-url>",
|
|
134
|
+
* updateStrategy: "appVersion",
|
|
135
|
+
* fallbackComponent: ({ progress = 0 }) => (
|
|
136
|
+
* <View style={styles.container}>
|
|
137
|
+
* <Text style={styles.text}>Updating... {progress}%</Text>
|
|
138
|
+
* </View>
|
|
139
|
+
* )
|
|
140
|
+
* })(App)
|
|
141
|
+
* ```
|
|
142
|
+
*
|
|
143
|
+
* If not defined, the bundle will download in the background without blocking the screen.
|
|
144
|
+
*/
|
|
145
|
+
fallbackComponent?: React.FC<{
|
|
146
|
+
status: Exclude<UpdateStatus, "UPDATE_PROCESS_COMPLETED">;
|
|
147
|
+
progress: number;
|
|
148
|
+
message: string | null;
|
|
149
|
+
}>;
|
|
150
|
+
|
|
151
|
+
onProgress?: (progress: number) => void;
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* When a force update exists, the app will automatically reload.
|
|
155
|
+
* If `false`, When a force update exists, the app will not reload. `shouldForceUpdate` will be returned as `true` in `onUpdateProcessCompleted`.
|
|
156
|
+
* If `true`, When a force update exists, the app will automatically reload.
|
|
157
|
+
* @default true
|
|
158
|
+
*/
|
|
159
|
+
reloadOnForceUpdate?: boolean;
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Callback function that is called when the update process is completed.
|
|
163
|
+
*
|
|
164
|
+
* @see {@link https://hot-updater.dev/docs/react-native-api/wrap#onupdateprocesscompleted}
|
|
165
|
+
*/
|
|
166
|
+
onUpdateProcessCompleted?: (response: RunUpdateProcessResponse) => void;
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
export type ManualUpdateOptions = CommonHotUpdaterOptions &
|
|
170
|
+
NetworkConfig & {
|
|
171
|
+
/**
|
|
172
|
+
* Update mode
|
|
173
|
+
* - "manual": Only notify app ready, user manually calls checkForUpdate()
|
|
174
|
+
*/
|
|
175
|
+
updateMode: "manual";
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
export type HotUpdaterOptions = AutoUpdateOptions | ManualUpdateOptions;
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Internal options after normalization in index.ts
|
|
182
|
+
* Always has resolver (never baseURL)
|
|
183
|
+
*/
|
|
184
|
+
type InternalCommonOptions = {
|
|
185
|
+
resolver: HotUpdaterResolver;
|
|
186
|
+
requestHeaders?: Record<string, string>;
|
|
187
|
+
requestTimeout?: number;
|
|
188
|
+
onNotifyAppReady?: (result: NotifyAppReadyResult) => void;
|
|
189
|
+
};
|
|
190
|
+
|
|
191
|
+
type InternalAutoUpdateOptions = InternalCommonOptions & {
|
|
192
|
+
updateStrategy: "fingerprint" | "appVersion";
|
|
193
|
+
updateMode: "auto";
|
|
194
|
+
onError?: (error: HotUpdaterError | Error | unknown) => void;
|
|
113
195
|
fallbackComponent?: React.FC<{
|
|
114
196
|
status: Exclude<UpdateStatus, "UPDATE_PROCESS_COMPLETED">;
|
|
115
197
|
progress: number;
|
|
116
198
|
message: string | null;
|
|
117
199
|
}>;
|
|
118
|
-
|
|
119
200
|
onProgress?: (progress: number) => void;
|
|
120
|
-
|
|
121
|
-
/**
|
|
122
|
-
* When a force update exists, the app will automatically reload.
|
|
123
|
-
* If `false`, When a force update exists, the app will not reload. `shouldForceUpdate` will be returned as `true` in `onUpdateProcessCompleted`.
|
|
124
|
-
* If `true`, When a force update exists, the app will automatically reload.
|
|
125
|
-
* @default true
|
|
126
|
-
*/
|
|
127
201
|
reloadOnForceUpdate?: boolean;
|
|
128
|
-
|
|
129
|
-
/**
|
|
130
|
-
* Callback function that is called when the update process is completed.
|
|
131
|
-
*
|
|
132
|
-
* @see {@link https://hot-updater.dev/docs/react-native-api/wrap#onupdateprocesscompleted}
|
|
133
|
-
*/
|
|
134
202
|
onUpdateProcessCompleted?: (response: RunUpdateProcessResponse) => void;
|
|
135
|
-
}
|
|
203
|
+
};
|
|
136
204
|
|
|
137
|
-
|
|
138
|
-
/**
|
|
139
|
-
* Update mode
|
|
140
|
-
* - "manual": Only notify app ready, user manually calls checkForUpdate()
|
|
141
|
-
*/
|
|
205
|
+
type InternalManualUpdateOptions = InternalCommonOptions & {
|
|
142
206
|
updateMode: "manual";
|
|
143
|
-
}
|
|
207
|
+
};
|
|
144
208
|
|
|
145
|
-
export type
|
|
209
|
+
export type InternalWrapOptions =
|
|
210
|
+
| InternalAutoUpdateOptions
|
|
211
|
+
| InternalManualUpdateOptions;
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Helper function to handle notifyAppReady flow
|
|
215
|
+
*/
|
|
216
|
+
const handleNotifyAppReady = async (options: {
|
|
217
|
+
resolver?: HotUpdaterResolver;
|
|
218
|
+
requestHeaders?: Record<string, string>;
|
|
219
|
+
requestTimeout?: number;
|
|
220
|
+
onNotifyAppReady?: (result: NotifyAppReadyResult) => void;
|
|
221
|
+
}): Promise<void> => {
|
|
222
|
+
try {
|
|
223
|
+
// Always call native notifyAppReady for bundle promotion
|
|
224
|
+
const nativeResult = nativeNotifyAppReady();
|
|
225
|
+
|
|
226
|
+
// If resolver.notifyAppReady exists, call it with simplified params
|
|
227
|
+
if (options.resolver?.notifyAppReady) {
|
|
228
|
+
await options.resolver
|
|
229
|
+
.notifyAppReady({
|
|
230
|
+
status: nativeResult.status,
|
|
231
|
+
crashedBundleId: nativeResult.crashedBundleId,
|
|
232
|
+
requestHeaders: options.requestHeaders,
|
|
233
|
+
requestTimeout: options.requestTimeout,
|
|
234
|
+
})
|
|
235
|
+
.catch((e: unknown) => {
|
|
236
|
+
console.warn("[HotUpdater] Resolver notifyAppReady failed:", e);
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
options.onNotifyAppReady?.(nativeResult);
|
|
241
|
+
} catch (e) {
|
|
242
|
+
console.warn("[HotUpdater] Failed to notify app ready:", e);
|
|
243
|
+
}
|
|
244
|
+
};
|
|
146
245
|
|
|
147
246
|
export function wrap<P extends React.JSX.IntrinsicAttributes = object>(
|
|
148
|
-
options:
|
|
247
|
+
options: InternalWrapOptions,
|
|
149
248
|
): (WrappedComponent: React.ComponentType<P>) => React.ComponentType<P> {
|
|
150
249
|
if (options.updateMode === "manual") {
|
|
151
250
|
return (WrappedComponent: React.ComponentType<P>) => {
|
|
152
251
|
const ManualHOC: React.FC<P> = (props: P) => {
|
|
153
252
|
useLayoutEffect(() => {
|
|
154
|
-
|
|
155
|
-
const result = nativeNotifyAppReady();
|
|
156
|
-
options.onNotifyAppReady?.(result);
|
|
157
|
-
} catch (e) {
|
|
158
|
-
console.warn("[HotUpdater] Failed to notify app ready:", e);
|
|
159
|
-
}
|
|
253
|
+
void handleNotifyAppReady(options);
|
|
160
254
|
}, []);
|
|
161
255
|
|
|
162
256
|
return <WrappedComponent {...props} />;
|
|
@@ -182,12 +276,12 @@ export function wrap<P extends React.JSX.IntrinsicAttributes = object>(
|
|
|
182
276
|
setUpdateStatus("CHECK_FOR_UPDATE");
|
|
183
277
|
|
|
184
278
|
const updateInfo = await checkForUpdate({
|
|
185
|
-
|
|
279
|
+
resolver: restOptions.resolver,
|
|
186
280
|
updateStrategy: restOptions.updateStrategy,
|
|
187
281
|
requestHeaders: restOptions.requestHeaders,
|
|
188
282
|
requestTimeout: restOptions.requestTimeout,
|
|
189
283
|
onError: restOptions.onError,
|
|
190
|
-
}
|
|
284
|
+
});
|
|
191
285
|
|
|
192
286
|
setMessage(updateInfo?.message ?? null);
|
|
193
287
|
|
|
@@ -250,12 +344,7 @@ export function wrap<P extends React.JSX.IntrinsicAttributes = object>(
|
|
|
250
344
|
|
|
251
345
|
// Notify native side that app is ready (JS bundle fully loaded)
|
|
252
346
|
useLayoutEffect(() => {
|
|
253
|
-
|
|
254
|
-
const result = nativeNotifyAppReady();
|
|
255
|
-
restOptions.onNotifyAppReady?.(result);
|
|
256
|
-
} catch (e) {
|
|
257
|
-
console.warn("[HotUpdater] Failed to notify app ready:", e);
|
|
258
|
-
}
|
|
347
|
+
void handleNotifyAppReady(restOptions);
|
|
259
348
|
}, []);
|
|
260
349
|
|
|
261
350
|
// Start update check
|