react-native-instantpay-code-push 1.1.0
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/InstantpayCodePush.podspec +20 -0
- package/LICENSE +20 -0
- package/README.md +158 -0
- package/android/build.gradle +91 -0
- package/android/gradle.properties +5 -0
- package/android/src/main/AndroidManifest.xml +2 -0
- package/android/src/main/java/com/instantpaycodepush/BundleFileStorageService.kt +835 -0
- package/android/src/main/java/com/instantpaycodepush/BundleMetadata.kt +249 -0
- package/android/src/main/java/com/instantpaycodepush/CommonHelper.kt +39 -0
- package/android/src/main/java/com/instantpaycodepush/DecompressService.kt +85 -0
- package/android/src/main/java/com/instantpaycodepush/DecompressionStrategy.kt +24 -0
- package/android/src/main/java/com/instantpaycodepush/FileManagerService.kt +105 -0
- package/android/src/main/java/com/instantpaycodepush/HashUtils.kt +50 -0
- package/android/src/main/java/com/instantpaycodepush/InstantpayCodePushModule.kt +182 -0
- package/android/src/main/java/com/instantpaycodepush/InstantpayCodePushPackage.kt +33 -0
- package/android/src/main/java/com/instantpaycodepush/IpayCodePush.kt +101 -0
- package/android/src/main/java/com/instantpaycodepush/IpayCodePushException.kt +135 -0
- package/android/src/main/java/com/instantpaycodepush/IpayCodePushImpl.kt +329 -0
- package/android/src/main/java/com/instantpaycodepush/OkHttpDownloadService.kt +283 -0
- package/android/src/main/java/com/instantpaycodepush/ReactIntegrationManager.kt +141 -0
- package/android/src/main/java/com/instantpaycodepush/ReactIntegrationManagerBase.kt +35 -0
- package/android/src/main/java/com/instantpaycodepush/SignatureVerifier.kt +354 -0
- package/android/src/main/java/com/instantpaycodepush/VersionedPreferencesService.kt +70 -0
- package/android/src/main/java/com/instantpaycodepush/ZipDecompressionStrategy.kt +198 -0
- package/ios/InstantpayCodePush.h +5 -0
- package/ios/InstantpayCodePush.mm +21 -0
- package/lib/module/DefaultResolver.js +34 -0
- package/lib/module/DefaultResolver.js.map +1 -0
- package/lib/module/NativeInstantpayCodePush.js +5 -0
- package/lib/module/NativeInstantpayCodePush.js.map +1 -0
- package/lib/module/checkForUpdate.js +68 -0
- package/lib/module/checkForUpdate.js.map +1 -0
- package/lib/module/error.js +137 -0
- package/lib/module/error.js.map +1 -0
- package/lib/module/fetchUpdateInfo.js +36 -0
- package/lib/module/fetchUpdateInfo.js.map +1 -0
- package/lib/module/global.d.js +8 -0
- package/lib/module/global.d.js.map +1 -0
- package/lib/module/hooks/useEventCallback.js +13 -0
- package/lib/module/hooks/useEventCallback.js.map +1 -0
- package/lib/module/index.js +291 -0
- package/lib/module/index.js.map +1 -0
- package/lib/module/native.js +233 -0
- package/lib/module/native.js.map +1 -0
- package/lib/module/package.json +1 -0
- package/lib/module/store.js +53 -0
- package/lib/module/store.js.map +1 -0
- package/lib/module/types.js +62 -0
- package/lib/module/types.js.map +1 -0
- package/lib/module/wrap.js +171 -0
- package/lib/module/wrap.js.map +1 -0
- package/lib/typescript/package.json +1 -0
- package/lib/typescript/src/DefaultResolver.d.ts +10 -0
- package/lib/typescript/src/DefaultResolver.d.ts.map +1 -0
- package/lib/typescript/src/NativeInstantpayCodePush.d.ts +100 -0
- package/lib/typescript/src/NativeInstantpayCodePush.d.ts.map +1 -0
- package/lib/typescript/src/checkForUpdate.d.ts +29 -0
- package/lib/typescript/src/checkForUpdate.d.ts.map +1 -0
- package/lib/typescript/src/error.d.ts +124 -0
- package/lib/typescript/src/error.d.ts.map +1 -0
- package/lib/typescript/src/fetchUpdateInfo.d.ts +8 -0
- package/lib/typescript/src/fetchUpdateInfo.d.ts.map +1 -0
- package/lib/typescript/src/hooks/useEventCallback.d.ts +5 -0
- package/lib/typescript/src/hooks/useEventCallback.d.ts.map +1 -0
- package/lib/typescript/src/index.d.ts +203 -0
- package/lib/typescript/src/index.d.ts.map +1 -0
- package/lib/typescript/src/native.d.ts +128 -0
- package/lib/typescript/src/native.d.ts.map +1 -0
- package/lib/typescript/src/store.d.ts +11 -0
- package/lib/typescript/src/store.d.ts.map +1 -0
- package/lib/typescript/src/types.d.ts +174 -0
- package/lib/typescript/src/types.d.ts.map +1 -0
- package/lib/typescript/src/wrap.d.ts +179 -0
- package/lib/typescript/src/wrap.d.ts.map +1 -0
- package/package.json +174 -0
- package/src/DefaultResolver.ts +36 -0
- package/src/NativeInstantpayCodePush.ts +111 -0
- package/src/checkForUpdate.ts +122 -0
- package/src/error.ts +159 -0
- package/src/fetchUpdateInfo.ts +47 -0
- package/src/global.d.ts +23 -0
- package/src/hooks/useEventCallback.ts +30 -0
- package/src/index.tsx +379 -0
- package/src/native.ts +280 -0
- package/src/store.ts +69 -0
- package/src/types.ts +227 -0
- package/src/wrap.tsx +384 -0
package/src/error.ts
ADDED
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ipay Code Push Error Codes
|
|
3
|
+
*
|
|
4
|
+
* This file defines all possible error codes that can be thrown by the native
|
|
5
|
+
* updateBundle function. These error codes are shared across iOS and Android
|
|
6
|
+
* implementations to ensure consistent error handling.
|
|
7
|
+
*
|
|
8
|
+
* Error Classification:
|
|
9
|
+
* - Parameter Validation: Invalid or missing function parameters
|
|
10
|
+
* - Bundle Storage: Errors during download, extraction, and storage
|
|
11
|
+
* - Signature Verification: Cryptographic verification failures (collapsed to a single public code)
|
|
12
|
+
* - Internal: Platform-specific or unexpected errors
|
|
13
|
+
*
|
|
14
|
+
* Retryability:
|
|
15
|
+
* - Retryable: DOWNLOAD_FAILED, INCOMPLETE_DOWNLOAD
|
|
16
|
+
* - Non-retryable: Most validation and verification errors
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
export enum IpayCodePushErrorCode {
|
|
20
|
+
// ==================== Parameter Validation Errors ====================
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Bundle ID is missing or empty.
|
|
24
|
+
* Thrown when bundleId parameter is null, undefined, or empty string.
|
|
25
|
+
* @retryable false
|
|
26
|
+
*/
|
|
27
|
+
MISSING_BUNDLE_ID = "MISSING_BUNDLE_ID",
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* File URL is invalid or malformed.
|
|
31
|
+
* Thrown when fileUrl parameter cannot be parsed as a valid URL.
|
|
32
|
+
* @retryable false
|
|
33
|
+
*/
|
|
34
|
+
INVALID_FILE_URL = "INVALID_FILE_URL",
|
|
35
|
+
|
|
36
|
+
// ==================== Bundle Storage Errors ====================
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Failed to create required directory for bundle storage.
|
|
40
|
+
* Thrown when bundle directory creation fails due to permissions or disk errors.
|
|
41
|
+
* @retryable false - Usually indicates permissions or filesystem corruption
|
|
42
|
+
*/
|
|
43
|
+
DIRECTORY_CREATION_FAILED = "DIRECTORY_CREATION_FAILED",
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Bundle download failed.
|
|
47
|
+
* Covers network errors, HTTP errors (4xx/5xx), timeouts, and connection issues.
|
|
48
|
+
* Check error message for specific cause (network, HTTP status code, etc.).
|
|
49
|
+
* @retryable true - Network issues are often transient
|
|
50
|
+
*/
|
|
51
|
+
DOWNLOAD_FAILED = "DOWNLOAD_FAILED",
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Download incomplete - received size doesn't match expected size.
|
|
55
|
+
* Thrown when downloaded file size doesn't match Content-Length header.
|
|
56
|
+
* Error message includes both expected and actual byte counts.
|
|
57
|
+
* @retryable true - Download may succeed on retry
|
|
58
|
+
*/
|
|
59
|
+
INCOMPLETE_DOWNLOAD = "INCOMPLETE_DOWNLOAD",
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Bundle archive format is invalid or corrupted.
|
|
63
|
+
* Thrown when ZIP file has wrong magic bytes, invalid structure, or unsupported format.
|
|
64
|
+
* Also thrown for path traversal attempts during extraction.
|
|
65
|
+
* @retryable false - Indicates corrupted or malicious bundle
|
|
66
|
+
*/
|
|
67
|
+
EXTRACTION_FORMAT_ERROR = "EXTRACTION_FORMAT_ERROR",
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Bundle missing required platform files.
|
|
71
|
+
* Thrown when extracted bundle doesn't contain index.android.bundle (Android)
|
|
72
|
+
* or main.jsbundle (iOS).
|
|
73
|
+
* @retryable false - Indicates incorrectly built bundle
|
|
74
|
+
*/
|
|
75
|
+
INVALID_BUNDLE = "INVALID_BUNDLE",
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Insufficient disk space for bundle download and extraction.
|
|
79
|
+
* Thrown when available disk space is less than required (file size * 2).
|
|
80
|
+
* Error message includes required and available bytes.
|
|
81
|
+
* @retryable false - User must free up disk space
|
|
82
|
+
*/
|
|
83
|
+
INSUFFICIENT_DISK_SPACE = "INSUFFICIENT_DISK_SPACE",
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Bundle signature verification failed (general).
|
|
87
|
+
* Thrown when cryptographic signature verification fails.
|
|
88
|
+
* All signature/hash sub-errors are collapsed into this public code.
|
|
89
|
+
* @retryable false - Indicates tampered or incorrectly signed bundle
|
|
90
|
+
*/
|
|
91
|
+
SIGNATURE_VERIFICATION_FAILED = "SIGNATURE_VERIFICATION_FAILED",
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Failed to move bundle to final location.
|
|
95
|
+
* Thrown when atomic move from temp directory to final directory fails.
|
|
96
|
+
* iOS: Thrown if move operation fails.
|
|
97
|
+
* Android: Thrown if rename, move, AND copy all fail.
|
|
98
|
+
* @retryable false - Usually indicates filesystem corruption or permissions
|
|
99
|
+
*/
|
|
100
|
+
MOVE_OPERATION_FAILED = "MOVE_OPERATION_FAILED",
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Bundle is in crashed history and cannot be applied.
|
|
104
|
+
* Thrown when attempting to install a bundle that previously caused a crash.
|
|
105
|
+
* Use IpayCodePush.clearCrashHistory() to allow retrying this bundle.
|
|
106
|
+
* @retryable false - Bundle was marked as crashed for safety
|
|
107
|
+
*/
|
|
108
|
+
BUNDLE_IN_CRASHED_HISTORY = "BUNDLE_IN_CRASHED_HISTORY",
|
|
109
|
+
|
|
110
|
+
// ==================== Signature Verification Errors ====================
|
|
111
|
+
// (Collapsed into SIGNATURE_VERIFICATION_FAILED)
|
|
112
|
+
|
|
113
|
+
// ==================== Internal Errors ====================
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Internal error: self deallocated during update (iOS only).
|
|
117
|
+
* Thrown when the native object is deallocated mid-operation.
|
|
118
|
+
* iOS-specific due to manual memory management (ARC).
|
|
119
|
+
* Not applicable to Android (uses garbage collection).
|
|
120
|
+
* @platform iOS
|
|
121
|
+
* @retryable false - Memory management issue
|
|
122
|
+
*/
|
|
123
|
+
SELF_DEALLOCATED = "SELF_DEALLOCATED",
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* An unknown or unexpected error occurred.
|
|
127
|
+
* Catch-all for errors that don't fit other categories.
|
|
128
|
+
* Check error message for details.
|
|
129
|
+
* @retryable unknown - Depends on underlying cause
|
|
130
|
+
*/
|
|
131
|
+
UNKNOWN_ERROR = "UNKNOWN_ERROR",
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Type guard to check if an error is a IpayCodePushError
|
|
136
|
+
*/
|
|
137
|
+
export function isIpayCodePushError(
|
|
138
|
+
error: unknown,
|
|
139
|
+
): error is { code: IpayCodePushErrorCode; message: string } {
|
|
140
|
+
return (
|
|
141
|
+
typeof error === "object" &&
|
|
142
|
+
error !== null &&
|
|
143
|
+
"code" in error &&
|
|
144
|
+
typeof error.code === "string" &&
|
|
145
|
+
Object.values(IpayCodePushErrorCode).includes(
|
|
146
|
+
error.code as IpayCodePushErrorCode,
|
|
147
|
+
)
|
|
148
|
+
);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Base error class for Ipay Code Push
|
|
153
|
+
*/
|
|
154
|
+
export class IpayCodePushError extends Error {
|
|
155
|
+
constructor(message: string) {
|
|
156
|
+
super(message);
|
|
157
|
+
this.name = "IpayCodePushError";
|
|
158
|
+
}
|
|
159
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import type { AppUpdateInfo } from "./types";
|
|
2
|
+
|
|
3
|
+
export const fetchUpdateInfo = async ({
|
|
4
|
+
url,
|
|
5
|
+
requestHeaders,
|
|
6
|
+
onError,
|
|
7
|
+
requestTimeout = 5000,
|
|
8
|
+
}: {
|
|
9
|
+
url: string;
|
|
10
|
+
requestHeaders?: Record<string, string>;
|
|
11
|
+
onError?: (error: Error) => void;
|
|
12
|
+
requestTimeout?: number;
|
|
13
|
+
}): Promise<AppUpdateInfo | null> => {
|
|
14
|
+
try {
|
|
15
|
+
const controller = new AbortController();
|
|
16
|
+
const timeoutId = setTimeout(() => {
|
|
17
|
+
controller.abort();
|
|
18
|
+
}, requestTimeout);
|
|
19
|
+
|
|
20
|
+
const headers = {
|
|
21
|
+
"Content-Type": "application/json",
|
|
22
|
+
...requestHeaders,
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
const response = await fetch(url, {
|
|
26
|
+
signal: controller.signal,
|
|
27
|
+
headers,
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
clearTimeout(timeoutId);
|
|
31
|
+
|
|
32
|
+
if (response.status !== 200) {
|
|
33
|
+
throw new Error(response.statusText);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return response.json();
|
|
37
|
+
} catch (error: unknown) {
|
|
38
|
+
|
|
39
|
+
if (error instanceof Error && error.name === "AbortError") {
|
|
40
|
+
onError?.(new Error("Request timed out"));
|
|
41
|
+
} else {
|
|
42
|
+
onError?.(error as Error);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
};
|
package/src/global.d.ts
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Global type definitions for IpayCodePush
|
|
3
|
+
*/
|
|
4
|
+
declare global {
|
|
5
|
+
/**
|
|
6
|
+
* Gets the base URL for the current active bundle directory.
|
|
7
|
+
* Returns the file:// URL to the bundle directory without trailing slash.
|
|
8
|
+
*
|
|
9
|
+
* This function is globally available for use in Expo DOM components
|
|
10
|
+
* and Babel plugin generated code without requiring an import.
|
|
11
|
+
*
|
|
12
|
+
* @returns {string} Base URL string (e.g., "file:///data/.../bundle-store/abc123") or null if not available
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* ```ts
|
|
16
|
+
* const baseURL = globalThis.IpayCodePushGetBaseURL();
|
|
17
|
+
* const htmlPath = baseURL + "/www.bundle/index.html";
|
|
18
|
+
* ```
|
|
19
|
+
*/
|
|
20
|
+
var IpayCodePushGetBaseURL: () => string | null;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export {};
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { useCallback, useLayoutEffect, useRef } from "react";
|
|
2
|
+
|
|
3
|
+
type EventCallback<Args extends unknown[], R> =
|
|
4
|
+
| ((...args: Args) => R)
|
|
5
|
+
| undefined;
|
|
6
|
+
|
|
7
|
+
export function useEventCallback<Args extends unknown[], R>(
|
|
8
|
+
fn: (...args: Args) => R,
|
|
9
|
+
): (...args: Args) => R;
|
|
10
|
+
|
|
11
|
+
export function useEventCallback<Args extends unknown[], R>(
|
|
12
|
+
fn: EventCallback<Args, R>,
|
|
13
|
+
): EventCallback<Args, R>;
|
|
14
|
+
|
|
15
|
+
export function useEventCallback<Args extends unknown[], R>(
|
|
16
|
+
fn: EventCallback<Args, R>,
|
|
17
|
+
): EventCallback<Args, R> {
|
|
18
|
+
const callbackRef = useRef<EventCallback<Args, R>>(() => {
|
|
19
|
+
throw new Error("Cannot call an event handler while rendering.");
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
useLayoutEffect(() => {
|
|
23
|
+
callbackRef.current = fn;
|
|
24
|
+
}, [fn]);
|
|
25
|
+
|
|
26
|
+
return useCallback(
|
|
27
|
+
(...args: Args) => callbackRef.current?.(...args),
|
|
28
|
+
[callbackRef],
|
|
29
|
+
) as (...args: Args) => R;
|
|
30
|
+
}
|
package/src/index.tsx
ADDED
|
@@ -0,0 +1,379 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type CheckForUpdateOptions,
|
|
3
|
+
checkForUpdate,
|
|
4
|
+
type InternalCheckForUpdateOptions,
|
|
5
|
+
} from "./checkForUpdate";
|
|
6
|
+
|
|
7
|
+
import { createDefaultResolver } from './DefaultResolver';
|
|
8
|
+
|
|
9
|
+
import {
|
|
10
|
+
addListener,
|
|
11
|
+
clearCrashHistory,
|
|
12
|
+
getAppVersion,
|
|
13
|
+
getBaseURL,
|
|
14
|
+
getBundleId,
|
|
15
|
+
getChannel,
|
|
16
|
+
getCrashHistory,
|
|
17
|
+
getFingerprintHash,
|
|
18
|
+
getMinBundleId,
|
|
19
|
+
reload,
|
|
20
|
+
type UpdateParams,
|
|
21
|
+
updateBundle,
|
|
22
|
+
} from "./native";
|
|
23
|
+
|
|
24
|
+
import { ipayCodePushStore } from "./store";
|
|
25
|
+
|
|
26
|
+
import type { IpayCodePushResolver } from "./types";
|
|
27
|
+
|
|
28
|
+
import { type IpayCodePushOptions, type InternalWrapOptions, wrap } from "./wrap";
|
|
29
|
+
|
|
30
|
+
export type { IpayCodePushEvent, NotifyAppReadyResult } from "./native";
|
|
31
|
+
|
|
32
|
+
export * from "./store";
|
|
33
|
+
|
|
34
|
+
export {
|
|
35
|
+
extractSignatureFailure,
|
|
36
|
+
type IpayCodePushResolver,
|
|
37
|
+
isSignatureVerificationError,
|
|
38
|
+
type ResolverCheckUpdateParams,
|
|
39
|
+
type ResolverNotifyAppReadyParams,
|
|
40
|
+
type SignatureVerificationFailure,
|
|
41
|
+
} from "./types";
|
|
42
|
+
|
|
43
|
+
export type { IpayCodePushOptions, RunUpdateProcessResponse } from "./wrap";
|
|
44
|
+
|
|
45
|
+
addListener("onProgress", ({ progress }) => {
|
|
46
|
+
ipayCodePushStore.setState({
|
|
47
|
+
progress,
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Register getBaseURL to global objects for use without imports.
|
|
53
|
+
* This is needed for Expo DOM components and Babel plugin generated code.
|
|
54
|
+
*/
|
|
55
|
+
const registerGlobalGetBaseURL = () => {
|
|
56
|
+
const fn = getBaseURL;
|
|
57
|
+
|
|
58
|
+
// Register to globalThis (modern, cross-platform)
|
|
59
|
+
if (typeof globalThis !== "undefined") {
|
|
60
|
+
|
|
61
|
+
if (!globalThis.IpayCodePushGetBaseURL) {
|
|
62
|
+
globalThis.IpayCodePushGetBaseURL = fn;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Register to global (React Native, Node.js)
|
|
67
|
+
if (typeof global !== "undefined") {
|
|
68
|
+
if (!global.IpayCodePushGetBaseURL) {
|
|
69
|
+
global.IpayCodePushGetBaseURL = fn;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
// Call registration immediately on module load
|
|
75
|
+
registerGlobalGetBaseURL();
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Creates a IpayCodePush client instance with all update management methods.
|
|
80
|
+
* This function is called once on module initialization to create a singleton instance.
|
|
81
|
+
*/
|
|
82
|
+
|
|
83
|
+
function createIpayCodePushClient() {
|
|
84
|
+
|
|
85
|
+
// Global configuration stored from wrap
|
|
86
|
+
const globalConfig: {
|
|
87
|
+
resolver: IpayCodePushResolver | null;
|
|
88
|
+
requestHeaders?: Record<string, string>;
|
|
89
|
+
requestTimeout?: number;
|
|
90
|
+
} = {
|
|
91
|
+
resolver: null,
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
const ensureGlobalResolver = (methodName: string) => {
|
|
95
|
+
if (!globalConfig.resolver) {
|
|
96
|
+
throw new Error(
|
|
97
|
+
`[IpayCodePush] ${methodName} requires IpayCodePush.wrap() to be used.\n\n` +
|
|
98
|
+
`To fix this issue, wrap your root component with IpayCodePush.wrap():\n\n` +
|
|
99
|
+
`Option 1: With automatic updates\n` +
|
|
100
|
+
` export default IpayCodePush.wrap({\n` +
|
|
101
|
+
` baseURL: "<your-update-server-url>",\n` +
|
|
102
|
+
` updateStrategy: "appVersion",\n` +
|
|
103
|
+
` updateMode: "auto"\n` +
|
|
104
|
+
` })(App);\n\n` +
|
|
105
|
+
`Option 2: Manual updates only (custom flow)\n` +
|
|
106
|
+
` export default IpayCodePush.wrap({\n` +
|
|
107
|
+
` baseURL: "<your-update-server-url>",\n` +
|
|
108
|
+
` updateMode: "manual"\n` +
|
|
109
|
+
` })(App);\n\n`
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
return globalConfig.resolver;
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
return {
|
|
116
|
+
/**
|
|
117
|
+
* `IpayCodePush.wrap` checks for updates at the entry point, and if there is a bundle to update, it downloads the bundle and applies the update strategy.
|
|
118
|
+
*
|
|
119
|
+
* @param {object} options - Configuration options
|
|
120
|
+
* @param {string} options.source - Update server URL
|
|
121
|
+
* @param {object} [options.requestHeaders] - Request headers
|
|
122
|
+
* @param {React.ComponentType} [options.fallbackComponent] - Component to display during updates
|
|
123
|
+
* @param {boolean} [options.reloadOnForceUpdate=true] - Whether to automatically reload the app on force updates
|
|
124
|
+
* @param {Function} [options.onUpdateProcessCompleted] - Callback after update process completes
|
|
125
|
+
* @param {Function} [options.onProgress] - Callback to track bundle download progress
|
|
126
|
+
* @returns {Function} Higher-order component that wraps the app component
|
|
127
|
+
*
|
|
128
|
+
* @example
|
|
129
|
+
* ```tsx
|
|
130
|
+
* export default IpayCodePush.wrap({
|
|
131
|
+
* baseURL: "<your-update-server-url>",
|
|
132
|
+
* updateStrategy: "appVersion", //"appVersion" | "fingerprint"
|
|
133
|
+
* updateMode: "auto",
|
|
134
|
+
* requestHeaders: {
|
|
135
|
+
* "Authorization": "Bearer <your-access-token>",
|
|
136
|
+
* },
|
|
137
|
+
* })(App);
|
|
138
|
+
* ```
|
|
139
|
+
*/
|
|
140
|
+
wrap: (options: IpayCodePushOptions) => {
|
|
141
|
+
let normalizedOptions: InternalWrapOptions;
|
|
142
|
+
|
|
143
|
+
if ("baseURL" in options && options.baseURL) {
|
|
144
|
+
const { baseURL, ...rest } = options;
|
|
145
|
+
normalizedOptions = {
|
|
146
|
+
...rest,
|
|
147
|
+
resolver: createDefaultResolver(baseURL),
|
|
148
|
+
};
|
|
149
|
+
} else if ("resolver" in options && options.resolver) {
|
|
150
|
+
normalizedOptions = options;
|
|
151
|
+
} else {
|
|
152
|
+
throw new Error(
|
|
153
|
+
`[IpayCodePush] Either baseURL or resolver must be provided.\n\n` +
|
|
154
|
+
`Option 1: Using baseURL (recommended for most cases)\n` +
|
|
155
|
+
` export default IpayCodePush.wrap({\n` +
|
|
156
|
+
` baseURL: "<your-update-server-url>",\n` +
|
|
157
|
+
` updateStrategy: "appVersion",\n` +
|
|
158
|
+
` updateMode: "auto"\n` +
|
|
159
|
+
` })(App);\n\n` +
|
|
160
|
+
`Option 2: Using custom resolver (advanced)\n` +
|
|
161
|
+
` export default IpayCodePush.wrap({\n` +
|
|
162
|
+
` resolver: {\n` +
|
|
163
|
+
` checkUpdate: async (params) => { /* custom logic */ },\n` +
|
|
164
|
+
` notifyAppReady: async (params) => { /* custom logic */ }\n` +
|
|
165
|
+
` },\n` +
|
|
166
|
+
` updateMode: "manual"\n` +
|
|
167
|
+
` })(App);\n\n`,
|
|
168
|
+
);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
globalConfig.resolver = normalizedOptions.resolver;
|
|
172
|
+
globalConfig.requestHeaders = options.requestHeaders;
|
|
173
|
+
globalConfig.requestTimeout = options.requestTimeout;
|
|
174
|
+
|
|
175
|
+
return wrap(normalizedOptions);
|
|
176
|
+
},
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Reloads the app.
|
|
180
|
+
*/
|
|
181
|
+
reload,
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Returns whether an update has finished downloading in this app session.
|
|
185
|
+
*
|
|
186
|
+
* When it returns true, calling `IpayCodePush.reload()` (or restarting the app)
|
|
187
|
+
* will apply the downloaded update bundle.
|
|
188
|
+
*
|
|
189
|
+
* - Derived from `progress` reaching 1.0
|
|
190
|
+
* - Resets to false when a new download starts (progress < 1)
|
|
191
|
+
*
|
|
192
|
+
* @returns {boolean} True if a downloaded update is ready to apply
|
|
193
|
+
* @example
|
|
194
|
+
* ```ts
|
|
195
|
+
* if (IpayCodePush.isUpdateDownloaded()) {
|
|
196
|
+
* await IpayCodePush.reload();
|
|
197
|
+
* }
|
|
198
|
+
* ```
|
|
199
|
+
*/
|
|
200
|
+
isUpdateDownloaded: () => ipayCodePushStore.getSnapshot().isUpdateDownloaded,
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Fetches the current app version.
|
|
204
|
+
*/
|
|
205
|
+
getAppVersion,
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Fetches the current bundle ID of the app.
|
|
209
|
+
*/
|
|
210
|
+
getBundleId,
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* Retrieves the initial bundle ID based on the build time of the native app.
|
|
214
|
+
*/
|
|
215
|
+
getMinBundleId,
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Fetches the current channel of the app.
|
|
219
|
+
*
|
|
220
|
+
* If no channel is specified, the app is assigned to the 'production' channel.
|
|
221
|
+
*
|
|
222
|
+
* @returns {string} The current release channel of the app
|
|
223
|
+
* @default "production"
|
|
224
|
+
* @example
|
|
225
|
+
* ```ts
|
|
226
|
+
* const channel = IpayCodePush.getChannel();
|
|
227
|
+
* console.log(`Current channel: ${channel}`);
|
|
228
|
+
* ```
|
|
229
|
+
*/
|
|
230
|
+
getChannel,
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* Adds a listener to IpayCodePush events.
|
|
234
|
+
*
|
|
235
|
+
* @param {keyof IpayCodePushEvent} eventName - The name of the event to listen for
|
|
236
|
+
* @param {(event: IpayCodePushEvent[T]) => void} listener - The callback function to handle the event
|
|
237
|
+
* @returns {() => void} A cleanup function that removes the event listener
|
|
238
|
+
*
|
|
239
|
+
* @example
|
|
240
|
+
* ```ts
|
|
241
|
+
* const unsubscribe = IpayCodePush.addListener("onProgress", ({ progress }) => {
|
|
242
|
+
* console.log(`Update progress: ${progress * 100}%`);
|
|
243
|
+
* });
|
|
244
|
+
*
|
|
245
|
+
* // Unsubscribe when no longer needed
|
|
246
|
+
* unsubscribe();
|
|
247
|
+
* ```
|
|
248
|
+
*/
|
|
249
|
+
addListener,
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* Manually checks for updates.
|
|
253
|
+
*
|
|
254
|
+
* @param {Object} config - Update check configuration
|
|
255
|
+
* @param {string} config.source - Update server URL
|
|
256
|
+
* @param {Record<string, string>} [config.requestHeaders] - Request headers
|
|
257
|
+
*
|
|
258
|
+
* @returns {Promise<UpdateInfo | null>} Update information or null if up to date
|
|
259
|
+
*
|
|
260
|
+
* @example
|
|
261
|
+
* ```ts
|
|
262
|
+
* const updateInfo = await IpayCodePush.checkForUpdate({
|
|
263
|
+
* source: "<your-update-server-url>",
|
|
264
|
+
* requestHeaders: {
|
|
265
|
+
* Authorization: "Bearer <your-access-token>",
|
|
266
|
+
* },
|
|
267
|
+
* });
|
|
268
|
+
*
|
|
269
|
+
* if (!updateInfo) {
|
|
270
|
+
* console.log("App is up to date");
|
|
271
|
+
* return;
|
|
272
|
+
* }
|
|
273
|
+
*
|
|
274
|
+
* await IpayCodePush.updateBundle(updateInfo.id, updateInfo.fileUrl);
|
|
275
|
+
* if (updateInfo.shouldForceUpdate) {
|
|
276
|
+
* await HotUpdater.reload();
|
|
277
|
+
* }
|
|
278
|
+
* ```
|
|
279
|
+
*/
|
|
280
|
+
checkForUpdate: (config: CheckForUpdateOptions) => {
|
|
281
|
+
const resolver = ensureGlobalResolver("checkForUpdate");
|
|
282
|
+
|
|
283
|
+
const mergedConfig: InternalCheckForUpdateOptions = {
|
|
284
|
+
...config,
|
|
285
|
+
resolver,
|
|
286
|
+
requestHeaders: {
|
|
287
|
+
...globalConfig.requestHeaders,
|
|
288
|
+
...config.requestHeaders,
|
|
289
|
+
},
|
|
290
|
+
requestTimeout: config.requestTimeout ?? globalConfig.requestTimeout,
|
|
291
|
+
};
|
|
292
|
+
|
|
293
|
+
return checkForUpdate(mergedConfig);
|
|
294
|
+
},
|
|
295
|
+
|
|
296
|
+
/**
|
|
297
|
+
* Updates the bundle of the app.
|
|
298
|
+
*
|
|
299
|
+
* @param {UpdateBundleParams} params - Parameters object required for bundle update
|
|
300
|
+
* @param {string} params.bundleId - The bundle ID of the app
|
|
301
|
+
* @param {string|null} params.fileUrl - The URL of the zip file
|
|
302
|
+
*
|
|
303
|
+
* @returns {Promise<boolean>} Whether the update was successful
|
|
304
|
+
*
|
|
305
|
+
* @example
|
|
306
|
+
* ```ts
|
|
307
|
+
* const updateInfo = await IpayCodePush.checkForUpdate({
|
|
308
|
+
* source: "<your-update-server-url>",
|
|
309
|
+
* requestHeaders: {
|
|
310
|
+
* Authorization: "Bearer <your-access-token>",
|
|
311
|
+
* },
|
|
312
|
+
* });
|
|
313
|
+
*
|
|
314
|
+
* if (!updateInfo) {
|
|
315
|
+
* return {
|
|
316
|
+
* status: "UP_TO_DATE",
|
|
317
|
+
* };
|
|
318
|
+
* }
|
|
319
|
+
*
|
|
320
|
+
* await IpayCodePush.updateBundle({
|
|
321
|
+
* bundleId: updateInfo.id,
|
|
322
|
+
* fileUrl: updateInfo.fileUrl
|
|
323
|
+
* });
|
|
324
|
+
* if (updateInfo.shouldForceUpdate) {
|
|
325
|
+
* await HotUpdater.reload();
|
|
326
|
+
* }
|
|
327
|
+
* ```
|
|
328
|
+
*/
|
|
329
|
+
updateBundle: (params: UpdateParams) => {
|
|
330
|
+
ensureGlobalResolver("updateBundle");
|
|
331
|
+
return updateBundle(params);
|
|
332
|
+
},
|
|
333
|
+
|
|
334
|
+
/**
|
|
335
|
+
* Fetches the fingerprint of the app.
|
|
336
|
+
*
|
|
337
|
+
* @returns {string} The fingerprint of the app
|
|
338
|
+
*
|
|
339
|
+
* @example
|
|
340
|
+
* ```ts
|
|
341
|
+
* const fingerprint = IpayCodePush.getFingerprintHash();
|
|
342
|
+
* console.log(`Fingerprint: ${fingerprint}`);
|
|
343
|
+
* ```
|
|
344
|
+
*/
|
|
345
|
+
getFingerprintHash,
|
|
346
|
+
|
|
347
|
+
/**
|
|
348
|
+
* Gets the list of bundle IDs that have been marked as crashed.
|
|
349
|
+
* These bundles will be rejected if attempted to install again.
|
|
350
|
+
*
|
|
351
|
+
* @returns {string[]} Array of crashed bundle IDs
|
|
352
|
+
*
|
|
353
|
+
* @example
|
|
354
|
+
* ```ts
|
|
355
|
+
* const crashedBundles = IpayCodePush.getCrashHistory();
|
|
356
|
+
* console.log("Crashed bundles:", crashedBundles);
|
|
357
|
+
* ```
|
|
358
|
+
*/
|
|
359
|
+
getCrashHistory,
|
|
360
|
+
|
|
361
|
+
/**
|
|
362
|
+
* Clears the crashed bundle history, allowing previously crashed bundles
|
|
363
|
+
* to be installed again.
|
|
364
|
+
*
|
|
365
|
+
* @returns {boolean} true if clearing was successful
|
|
366
|
+
*
|
|
367
|
+
* @example
|
|
368
|
+
* ```ts
|
|
369
|
+
* // Clear crash history to allow retrying a previously failed bundle
|
|
370
|
+
* IpayCodePush.clearCrashHistory();
|
|
371
|
+
* ```
|
|
372
|
+
*/
|
|
373
|
+
clearCrashHistory,
|
|
374
|
+
};
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
export const IpayCodePush = createIpayCodePushClient();
|
|
378
|
+
|
|
379
|
+
|