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.
Files changed (87) hide show
  1. package/InstantpayCodePush.podspec +20 -0
  2. package/LICENSE +20 -0
  3. package/README.md +158 -0
  4. package/android/build.gradle +91 -0
  5. package/android/gradle.properties +5 -0
  6. package/android/src/main/AndroidManifest.xml +2 -0
  7. package/android/src/main/java/com/instantpaycodepush/BundleFileStorageService.kt +835 -0
  8. package/android/src/main/java/com/instantpaycodepush/BundleMetadata.kt +249 -0
  9. package/android/src/main/java/com/instantpaycodepush/CommonHelper.kt +39 -0
  10. package/android/src/main/java/com/instantpaycodepush/DecompressService.kt +85 -0
  11. package/android/src/main/java/com/instantpaycodepush/DecompressionStrategy.kt +24 -0
  12. package/android/src/main/java/com/instantpaycodepush/FileManagerService.kt +105 -0
  13. package/android/src/main/java/com/instantpaycodepush/HashUtils.kt +50 -0
  14. package/android/src/main/java/com/instantpaycodepush/InstantpayCodePushModule.kt +182 -0
  15. package/android/src/main/java/com/instantpaycodepush/InstantpayCodePushPackage.kt +33 -0
  16. package/android/src/main/java/com/instantpaycodepush/IpayCodePush.kt +101 -0
  17. package/android/src/main/java/com/instantpaycodepush/IpayCodePushException.kt +135 -0
  18. package/android/src/main/java/com/instantpaycodepush/IpayCodePushImpl.kt +329 -0
  19. package/android/src/main/java/com/instantpaycodepush/OkHttpDownloadService.kt +283 -0
  20. package/android/src/main/java/com/instantpaycodepush/ReactIntegrationManager.kt +141 -0
  21. package/android/src/main/java/com/instantpaycodepush/ReactIntegrationManagerBase.kt +35 -0
  22. package/android/src/main/java/com/instantpaycodepush/SignatureVerifier.kt +354 -0
  23. package/android/src/main/java/com/instantpaycodepush/VersionedPreferencesService.kt +70 -0
  24. package/android/src/main/java/com/instantpaycodepush/ZipDecompressionStrategy.kt +198 -0
  25. package/ios/InstantpayCodePush.h +5 -0
  26. package/ios/InstantpayCodePush.mm +21 -0
  27. package/lib/module/DefaultResolver.js +34 -0
  28. package/lib/module/DefaultResolver.js.map +1 -0
  29. package/lib/module/NativeInstantpayCodePush.js +5 -0
  30. package/lib/module/NativeInstantpayCodePush.js.map +1 -0
  31. package/lib/module/checkForUpdate.js +68 -0
  32. package/lib/module/checkForUpdate.js.map +1 -0
  33. package/lib/module/error.js +137 -0
  34. package/lib/module/error.js.map +1 -0
  35. package/lib/module/fetchUpdateInfo.js +36 -0
  36. package/lib/module/fetchUpdateInfo.js.map +1 -0
  37. package/lib/module/global.d.js +8 -0
  38. package/lib/module/global.d.js.map +1 -0
  39. package/lib/module/hooks/useEventCallback.js +13 -0
  40. package/lib/module/hooks/useEventCallback.js.map +1 -0
  41. package/lib/module/index.js +291 -0
  42. package/lib/module/index.js.map +1 -0
  43. package/lib/module/native.js +233 -0
  44. package/lib/module/native.js.map +1 -0
  45. package/lib/module/package.json +1 -0
  46. package/lib/module/store.js +53 -0
  47. package/lib/module/store.js.map +1 -0
  48. package/lib/module/types.js +62 -0
  49. package/lib/module/types.js.map +1 -0
  50. package/lib/module/wrap.js +171 -0
  51. package/lib/module/wrap.js.map +1 -0
  52. package/lib/typescript/package.json +1 -0
  53. package/lib/typescript/src/DefaultResolver.d.ts +10 -0
  54. package/lib/typescript/src/DefaultResolver.d.ts.map +1 -0
  55. package/lib/typescript/src/NativeInstantpayCodePush.d.ts +100 -0
  56. package/lib/typescript/src/NativeInstantpayCodePush.d.ts.map +1 -0
  57. package/lib/typescript/src/checkForUpdate.d.ts +29 -0
  58. package/lib/typescript/src/checkForUpdate.d.ts.map +1 -0
  59. package/lib/typescript/src/error.d.ts +124 -0
  60. package/lib/typescript/src/error.d.ts.map +1 -0
  61. package/lib/typescript/src/fetchUpdateInfo.d.ts +8 -0
  62. package/lib/typescript/src/fetchUpdateInfo.d.ts.map +1 -0
  63. package/lib/typescript/src/hooks/useEventCallback.d.ts +5 -0
  64. package/lib/typescript/src/hooks/useEventCallback.d.ts.map +1 -0
  65. package/lib/typescript/src/index.d.ts +203 -0
  66. package/lib/typescript/src/index.d.ts.map +1 -0
  67. package/lib/typescript/src/native.d.ts +128 -0
  68. package/lib/typescript/src/native.d.ts.map +1 -0
  69. package/lib/typescript/src/store.d.ts +11 -0
  70. package/lib/typescript/src/store.d.ts.map +1 -0
  71. package/lib/typescript/src/types.d.ts +174 -0
  72. package/lib/typescript/src/types.d.ts.map +1 -0
  73. package/lib/typescript/src/wrap.d.ts +179 -0
  74. package/lib/typescript/src/wrap.d.ts.map +1 -0
  75. package/package.json +174 -0
  76. package/src/DefaultResolver.ts +36 -0
  77. package/src/NativeInstantpayCodePush.ts +111 -0
  78. package/src/checkForUpdate.ts +122 -0
  79. package/src/error.ts +159 -0
  80. package/src/fetchUpdateInfo.ts +47 -0
  81. package/src/global.d.ts +23 -0
  82. package/src/hooks/useEventCallback.ts +30 -0
  83. package/src/index.tsx +379 -0
  84. package/src/native.ts +280 -0
  85. package/src/store.ts +69 -0
  86. package/src/types.ts +227 -0
  87. package/src/wrap.tsx +384 -0
package/src/wrap.tsx ADDED
@@ -0,0 +1,384 @@
1
+ import React,{ useEffect, useLayoutEffect, useState } from "react";
2
+ import { checkForUpdate } from "./checkForUpdate";
3
+ import {
4
+ getBundleId,
5
+ type NotifyAppReadyResult,
6
+ notifyAppReady as nativeNotifyAppReady,
7
+ reload,
8
+ } from "./native";
9
+ import type { IpayCodePushResolver } from "./types";
10
+ import type { IpayCodePushError } from "./error";
11
+ import { useEventCallback } from "./hooks/useEventCallback";
12
+ import { useIpayCodePushStore } from "./store";
13
+
14
+
15
+ export interface RunUpdateProcessResponse {
16
+ status: "ROLLBACK" | "UPDATE" | "UP_TO_DATE";
17
+ shouldForceUpdate: boolean;
18
+ message: string | null;
19
+ id: string;
20
+ }
21
+
22
+ type UpdateStatus =
23
+ | "CHECK_FOR_UPDATE"
24
+ | "UPDATING"
25
+ | "UPDATE_PROCESS_COMPLETED";
26
+
27
+
28
+ /**
29
+ * Common options shared between auto and manual update modes
30
+ */
31
+ interface CommonIpayCodePushOptions {
32
+ /**
33
+ * Custom request headers for update checks
34
+ */
35
+ requestHeaders?: Record<string, string>;
36
+
37
+ /**
38
+ * Request timeout in milliseconds
39
+ * @default 5000
40
+ */
41
+ requestTimeout?: number;
42
+
43
+ /**
44
+ * Callback invoked when the app is ready and bundle verification completes.
45
+ * Provides information about bundle promotion, recovery from crashes, or stable state.
46
+ *
47
+ * @param result - Bundle state information
48
+ * @param result.status - Current bundle state:
49
+ * - "PROMOTED": Staging bundle was promoted to stable (new update applied)
50
+ * - "RECOVERED": App recovered from a crash, rollback occurred
51
+ * - "STABLE": No changes, bundle is stable
52
+ * @param result.crashedBundleId - Present only when status is "RECOVERED"
53
+ *
54
+ * @example
55
+ * ```tsx
56
+ * IpayCodePush.wrap({
57
+ * baseURL: "https://api.example.com",
58
+ * updateMode: "manual",
59
+ * onNotifyAppReady: ({ status, crashedBundleId }) => {
60
+ * if (status === "RECOVERED") {
61
+ * analytics.track('bundle_rollback', { crashedBundleId });
62
+ * } else if (status === "PROMOTED") {
63
+ * analytics.track('bundle_promoted');
64
+ * }
65
+ * }
66
+ * })(App);
67
+ * ```
68
+ */
69
+ onNotifyAppReady?: (result: NotifyAppReadyResult) => void;
70
+ }
71
+
72
+ /**
73
+ * Configuration with baseURL for standard server-based updates
74
+ */
75
+ interface BaseURLConfig {
76
+ /**
77
+ * Base URL for update server
78
+ * @example "https://update.example.com"
79
+ */
80
+ baseURL: string;
81
+
82
+ /**
83
+ * Resolver is not allowed when using baseURL
84
+ */
85
+ resolver?: never;
86
+ }
87
+
88
+ /**
89
+ * Configuration with resolver for custom network operations
90
+ */
91
+ interface ResolverConfig {
92
+ /**
93
+ * Custom resolver for network operations
94
+ */
95
+ resolver: IpayCodePushResolver;
96
+
97
+ /**
98
+ * baseURL is not allowed when using resolver
99
+ */
100
+ baseURL?: never;
101
+ }
102
+
103
+ /**
104
+ * Union type ensuring baseURL and resolver are mutually exclusive
105
+ */
106
+ type NetworkConfig = BaseURLConfig | ResolverConfig;
107
+
108
+ export type AutoUpdateOptions = CommonIpayCodePushOptions & NetworkConfig & {
109
+ /**
110
+ * Update strategy
111
+ * - "fingerprint": Use fingerprint hash to check for updates
112
+ * - "appVersion": Use app version to check for updates
113
+ */
114
+ updateStrategy: "fingerprint" | "appVersion";
115
+
116
+ /**
117
+ * Update mode
118
+ * - "auto": Automatically check and download updates
119
+ */
120
+ updateMode: "auto";
121
+
122
+ onError?: (error: IpayCodePushError | Error | unknown) => void;
123
+
124
+ /**
125
+ * Component to show while downloading a new bundle update.
126
+ *
127
+ * When an update exists and the bundle is being downloaded, this component will block access
128
+ * to the entry point and show download progress.
129
+ *
130
+ *
131
+ * ```tsx
132
+ * IpayCodePush.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
+ */
165
+ onUpdateProcessCompleted?: (response: RunUpdateProcessResponse) => void;
166
+ };
167
+
168
+ export type ManualUpdateOptions = CommonIpayCodePushOptions & NetworkConfig & {
169
+ /**
170
+ * Update mode
171
+ * - "manual": Only notify app ready, user manually calls checkForUpdate()
172
+ */
173
+ updateMode: "manual";
174
+ };
175
+
176
+ export type IpayCodePushOptions = AutoUpdateOptions | ManualUpdateOptions;
177
+
178
+ /**
179
+ * Internal options after normalization in index.ts
180
+ * Always has resolver (never baseURL)
181
+ */
182
+ type InternalCommonOptions = {
183
+ resolver: IpayCodePushResolver;
184
+ requestHeaders?: Record<string, string>;
185
+ requestTimeout?: number;
186
+ onNotifyAppReady?: (result: NotifyAppReadyResult) => void;
187
+ };
188
+
189
+ type InternalAutoUpdateOptions = InternalCommonOptions & {
190
+ updateStrategy: "fingerprint" | "appVersion";
191
+ updateMode: "auto";
192
+ onError?: (error: IpayCodePushError | Error | unknown) => void;
193
+ fallbackComponent?: React.FC<{
194
+ status: Exclude<UpdateStatus, "UPDATE_PROCESS_COMPLETED">;
195
+ progress: number;
196
+ message: string | null;
197
+ }>;
198
+ onProgress?: (progress: number) => void;
199
+ reloadOnForceUpdate?: boolean;
200
+ onUpdateProcessCompleted?: (response: RunUpdateProcessResponse) => void;
201
+ };
202
+
203
+ type InternalManualUpdateOptions = InternalCommonOptions & {
204
+ updateMode: "manual";
205
+ };
206
+
207
+ export type InternalWrapOptions =
208
+ | InternalAutoUpdateOptions
209
+ | InternalManualUpdateOptions;
210
+
211
+
212
+ /**
213
+ * Helper function to handle notifyAppReady flow
214
+ */
215
+ const handleNotifyAppReady = async (options: {
216
+ resolver?: IpayCodePushResolver;
217
+ requestHeaders?: Record<string, string>;
218
+ requestTimeout?: number;
219
+ onNotifyAppReady?: (result: NotifyAppReadyResult) => void;
220
+ }): Promise<void> => {
221
+ try {
222
+ // Always call native notifyAppReady for bundle promotion
223
+ const nativeResult = nativeNotifyAppReady();
224
+
225
+ // If resolver.notifyAppReady exists, call it with simplified params
226
+ if (options.resolver?.notifyAppReady) {
227
+ await options.resolver.notifyAppReady({
228
+ status: nativeResult.status,
229
+ crashedBundleId: nativeResult.crashedBundleId,
230
+ requestHeaders: options.requestHeaders,
231
+ requestTimeout: options.requestTimeout,
232
+ })
233
+ .catch((e: unknown) => {
234
+ console.warn("[IpayCodePush] Resolver notifyAppReady failed:", e);
235
+ });
236
+ }
237
+
238
+ options.onNotifyAppReady?.(nativeResult);
239
+ } catch (e) {
240
+ console.warn("[IpayCodePush] Failed to notify app ready:", e);
241
+ }
242
+ };
243
+
244
+ /**
245
+ * @usage IpayCodePushHOC.wrap({})(App); App Component or any other component
246
+ * @param options
247
+ * @returns
248
+ */
249
+ export function wrap<T extends React.JSX.IntrinsicAttributes = object>(
250
+ options: InternalWrapOptions,
251
+ ): (WrappedComponent: React.ComponentType<T>) => React.ComponentType<T> {
252
+
253
+ if (options.updateMode === "manual") {
254
+ return (WrappedComponent: React.ComponentType<T>) => {
255
+ const ManualHOC: React.FC<T> = (props: T) => {
256
+ useLayoutEffect(() => {
257
+ void handleNotifyAppReady(options);
258
+ }, []);
259
+
260
+ return <WrappedComponent {...props} />;
261
+ };
262
+
263
+ return ManualHOC as React.ComponentType<T>;
264
+ };
265
+ }
266
+
267
+ // updateMode: "auto"
268
+ const { reloadOnForceUpdate = true, ...restOptions } = options;
269
+
270
+ return (WrappedComponent: React.ComponentType<T>) => {
271
+ const IpayCodePushHOC: React.FC<T> = (props: T) => {
272
+
273
+ const progress = useIpayCodePushStore((state) => state.progress);
274
+
275
+ const [message, setMessage] = useState<string | null>(null);
276
+
277
+ const [updateStatus, setUpdateStatus] = useState<UpdateStatus>("CHECK_FOR_UPDATE");
278
+
279
+ const initIpayCodePush = useEventCallback(async () => {
280
+
281
+ try {
282
+ setUpdateStatus("CHECK_FOR_UPDATE");
283
+
284
+ const updateInfo = await checkForUpdate({
285
+ resolver: restOptions.resolver,
286
+ updateStrategy: restOptions.updateStrategy,
287
+ requestHeaders: restOptions.requestHeaders,
288
+ requestTimeout: restOptions.requestTimeout,
289
+ onError: restOptions.onError,
290
+ });
291
+
292
+ setMessage(updateInfo?.message ?? null);
293
+
294
+ if (!updateInfo) {
295
+ restOptions.onUpdateProcessCompleted?.({
296
+ status: "UP_TO_DATE",
297
+ shouldForceUpdate: false,
298
+ message: null,
299
+ id: getBundleId(),
300
+ });
301
+ setUpdateStatus("UPDATE_PROCESS_COMPLETED");
302
+ return;
303
+ }
304
+
305
+ //If forceupdate is not allowed for the bunlde
306
+ if (updateInfo.shouldForceUpdate === false) {
307
+ void updateInfo.updateBundle().catch((error: unknown) => {
308
+ restOptions.onError?.(error);
309
+ });
310
+
311
+ restOptions.onUpdateProcessCompleted?.({
312
+ id: updateInfo.id,
313
+ status: updateInfo.status,
314
+ shouldForceUpdate: updateInfo.shouldForceUpdate,
315
+ message: updateInfo.message,
316
+ });
317
+
318
+ setUpdateStatus("UPDATE_PROCESS_COMPLETED");
319
+ return;
320
+ }
321
+
322
+ // Force Update Scenario
323
+ setUpdateStatus("UPDATING");
324
+ const isSuccess = await updateInfo.updateBundle();
325
+
326
+ if (!isSuccess) {
327
+ throw new Error(
328
+ "New update was found but failed to download the bundle.",
329
+ );
330
+ }
331
+
332
+ if (reloadOnForceUpdate) {
333
+ await reload();
334
+ }
335
+
336
+ restOptions.onUpdateProcessCompleted?.({
337
+ id: updateInfo.id,
338
+ status: updateInfo.status,
339
+ shouldForceUpdate: updateInfo.shouldForceUpdate,
340
+ message: updateInfo.message,
341
+ });
342
+
343
+ setUpdateStatus("UPDATE_PROCESS_COMPLETED");
344
+
345
+ } catch (error) {
346
+ restOptions.onError?.(error);
347
+ setUpdateStatus("UPDATE_PROCESS_COMPLETED");
348
+ }
349
+ });
350
+
351
+ useEffect(() => {
352
+ restOptions.onProgress?.(progress);
353
+ }, [progress]);
354
+
355
+ // Notify native side that app is ready (JS bundle fully loaded)
356
+ useLayoutEffect(() => {
357
+ void handleNotifyAppReady(restOptions);
358
+ }, []);
359
+
360
+ // Start update check
361
+ useLayoutEffect(() => {
362
+ initIpayCodePush();
363
+ }, []);
364
+
365
+ if (
366
+ restOptions.fallbackComponent &&
367
+ updateStatus !== "UPDATE_PROCESS_COMPLETED"
368
+ ) {
369
+ const Fallback = restOptions.fallbackComponent;
370
+ return (
371
+ <Fallback
372
+ progress={progress}
373
+ status={updateStatus}
374
+ message={message}
375
+ />
376
+ );
377
+ }
378
+
379
+ return <WrappedComponent {...props} />;
380
+ };
381
+
382
+ return IpayCodePushHOC as React.ComponentType<T>;
383
+ }
384
+ }