@hot-updater/react-native 0.27.0 → 0.28.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 (62) hide show
  1. package/android/build.gradle +12 -0
  2. package/android/src/main/AndroidManifest.xml +3 -0
  3. package/android/src/main/AndroidManifestNew.xml +3 -0
  4. package/android/src/main/cpp/CMakeLists.txt +9 -0
  5. package/android/src/main/cpp/HotUpdaterRecovery.cpp +143 -0
  6. package/android/src/main/java/com/hotupdater/BundleFileStorageService.kt +170 -204
  7. package/android/src/main/java/com/hotupdater/BundleMetadata.kt +73 -16
  8. package/android/src/main/java/com/hotupdater/HotUpdaterImpl.kt +39 -13
  9. package/android/src/main/java/com/hotupdater/HotUpdaterRecoveryManager.kt +533 -0
  10. package/android/src/main/java/com/hotupdater/HotUpdaterRecoveryReceiver.kt +14 -0
  11. package/android/src/newarch/HotUpdaterModule.kt +2 -8
  12. package/android/src/oldarch/HotUpdaterModule.kt +2 -8
  13. package/android/src/oldarch/HotUpdaterSpec.kt +1 -1
  14. package/ios/HotUpdater/Internal/BundleFileStorageService.swift +189 -203
  15. package/ios/HotUpdater/Internal/BundleMetadata.swift +61 -8
  16. package/ios/HotUpdater/Internal/HotUpdater-Bridging-Header.h +9 -1
  17. package/ios/HotUpdater/Internal/HotUpdater.mm +265 -11
  18. package/ios/HotUpdater/Internal/HotUpdaterCrashHandler.h +7 -0
  19. package/ios/HotUpdater/Internal/HotUpdaterCrashHandler.mm +4 -0
  20. package/ios/HotUpdater/Internal/HotUpdaterImpl.swift +293 -9
  21. package/lib/commonjs/native.js +18 -21
  22. package/lib/commonjs/native.js.map +1 -1
  23. package/lib/commonjs/native.spec.js +86 -0
  24. package/lib/commonjs/native.spec.js.map +1 -0
  25. package/lib/commonjs/specs/NativeHotUpdater.js.map +1 -1
  26. package/lib/commonjs/types.js.map +1 -1
  27. package/lib/commonjs/wrap.js +4 -5
  28. package/lib/commonjs/wrap.js.map +1 -1
  29. package/lib/module/native.js +17 -20
  30. package/lib/module/native.js.map +1 -1
  31. package/lib/module/native.spec.js +85 -0
  32. package/lib/module/native.spec.js.map +1 -0
  33. package/lib/module/specs/NativeHotUpdater.js.map +1 -1
  34. package/lib/module/types.js.map +1 -1
  35. package/lib/module/wrap.js +5 -6
  36. package/lib/module/wrap.js.map +1 -1
  37. package/lib/typescript/commonjs/native.d.ts +4 -15
  38. package/lib/typescript/commonjs/native.d.ts.map +1 -1
  39. package/lib/typescript/commonjs/native.spec.d.ts +2 -0
  40. package/lib/typescript/commonjs/native.spec.d.ts.map +1 -0
  41. package/lib/typescript/commonjs/specs/NativeHotUpdater.d.ts +4 -8
  42. package/lib/typescript/commonjs/specs/NativeHotUpdater.d.ts.map +1 -1
  43. package/lib/typescript/commonjs/types.d.ts +2 -3
  44. package/lib/typescript/commonjs/types.d.ts.map +1 -1
  45. package/lib/typescript/commonjs/wrap.d.ts +2 -5
  46. package/lib/typescript/commonjs/wrap.d.ts.map +1 -1
  47. package/lib/typescript/module/native.d.ts +4 -15
  48. package/lib/typescript/module/native.d.ts.map +1 -1
  49. package/lib/typescript/module/native.spec.d.ts +2 -0
  50. package/lib/typescript/module/native.spec.d.ts.map +1 -0
  51. package/lib/typescript/module/specs/NativeHotUpdater.d.ts +4 -8
  52. package/lib/typescript/module/specs/NativeHotUpdater.d.ts.map +1 -1
  53. package/lib/typescript/module/types.d.ts +2 -3
  54. package/lib/typescript/module/types.d.ts.map +1 -1
  55. package/lib/typescript/module/wrap.d.ts +2 -5
  56. package/lib/typescript/module/wrap.d.ts.map +1 -1
  57. package/package.json +6 -6
  58. package/src/native.spec.ts +84 -0
  59. package/src/native.ts +20 -19
  60. package/src/specs/NativeHotUpdater.ts +4 -6
  61. package/src/types.ts +2 -3
  62. package/src/wrap.tsx +7 -11
@@ -1 +1 @@
1
- {"version":3,"file":"wrap.d.ts","sourceRoot":"","sources":["../../../src/wrap.tsx"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"wrap.d.ts","sourceRoot":"","sources":["../../../src/wrap.tsx"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hot-updater/react-native",
3
- "version": "0.27.0",
3
+ "version": "0.28.0",
4
4
  "description": "React Native OTA solution for self-hosted",
5
5
  "main": "lib/commonjs/index",
6
6
  "module": "lib/module/index",
@@ -120,14 +120,14 @@
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.27.0"
123
+ "hot-updater": "0.28.0"
124
124
  },
125
125
  "dependencies": {
126
126
  "use-sync-external-store": "1.5.0",
127
- "@hot-updater/cli-tools": "0.27.0",
128
- "@hot-updater/core": "0.27.0",
129
- "@hot-updater/js": "0.27.0",
130
- "@hot-updater/plugin-core": "0.27.0"
127
+ "@hot-updater/core": "0.28.0",
128
+ "@hot-updater/js": "0.28.0",
129
+ "@hot-updater/cli-tools": "0.28.0",
130
+ "@hot-updater/plugin-core": "0.28.0"
131
131
  },
132
132
  "scripts": {
133
133
  "build": "bob build && tsc -p plugin/tsconfig.build.json",
@@ -0,0 +1,84 @@
1
+ import { beforeEach, describe, expect, it, vi } from "vitest";
2
+
3
+ const nativeModuleMock = vi.hoisted(() => {
4
+ vi.stubGlobal("__HOT_UPDATER_BUNDLE_ID", "bundle-id");
5
+
6
+ return {
7
+ clearCrashHistory: vi.fn(() => true),
8
+ getBaseURL: vi.fn(() => null),
9
+ getConstants: vi.fn(() => ({
10
+ APP_VERSION: null,
11
+ CHANNEL: "production",
12
+ DEFAULT_CHANNEL: "production",
13
+ FINGERPRINT_HASH: null,
14
+ })),
15
+ getCrashHistory: vi.fn(() => []),
16
+ notifyAppReady: vi.fn(),
17
+ reload: vi.fn(),
18
+ resetChannel: vi.fn(),
19
+ setBundleURL: vi.fn(),
20
+ switchChannel: vi.fn(),
21
+ updateBundle: vi.fn(),
22
+ };
23
+ });
24
+
25
+ vi.mock("react-native", () => ({
26
+ NativeEventEmitter: class {
27
+ addListener() {
28
+ return { remove: () => {} };
29
+ }
30
+ },
31
+ Platform: {
32
+ OS: "ios",
33
+ },
34
+ }));
35
+
36
+ vi.mock("./specs/NativeHotUpdater", () => ({
37
+ default: nativeModuleMock,
38
+ }));
39
+
40
+ describe("notifyAppReady", () => {
41
+ beforeEach(() => {
42
+ vi.resetModules();
43
+ nativeModuleMock.notifyAppReady.mockReset();
44
+ nativeModuleMock.getConstants.mockReturnValue({
45
+ APP_VERSION: null,
46
+ CHANNEL: "production",
47
+ DEFAULT_CHANNEL: "production",
48
+ FINGERPRINT_HASH: null,
49
+ });
50
+ });
51
+
52
+ it("normalizes legacy PROMOTED launch reports to STABLE", async () => {
53
+ nativeModuleMock.notifyAppReady.mockReturnValue(
54
+ JSON.stringify({ status: "PROMOTED" }),
55
+ );
56
+
57
+ const { notifyAppReady } = await import("./native");
58
+
59
+ expect(notifyAppReady()).toEqual({ status: "STABLE" });
60
+ expect(nativeModuleMock.notifyAppReady).toHaveBeenCalledWith();
61
+ });
62
+
63
+ it("returns RECOVERED launch reports unchanged", async () => {
64
+ nativeModuleMock.notifyAppReady.mockReturnValue({
65
+ crashedBundleId: "bundle-123",
66
+ status: "RECOVERED",
67
+ });
68
+
69
+ const { notifyAppReady } = await import("./native");
70
+
71
+ expect(notifyAppReady()).toEqual({
72
+ crashedBundleId: "bundle-123",
73
+ status: "RECOVERED",
74
+ });
75
+ });
76
+
77
+ it("falls back to STABLE for malformed old-arch payloads", async () => {
78
+ nativeModuleMock.notifyAppReady.mockReturnValue("{");
79
+
80
+ const { notifyAppReady } = await import("./native");
81
+
82
+ expect(notifyAppReady()).toEqual({ status: "STABLE" });
83
+ });
84
+ });
package/src/native.ts CHANGED
@@ -364,18 +364,16 @@ export const getFingerprintHash = (): string | null => {
364
364
  * Result returned by notifyAppReady()
365
365
  */
366
366
  export type NotifyAppReadyResult = {
367
- status: "PROMOTED" | "RECOVERED" | "STABLE";
367
+ status: "RECOVERED" | "STABLE";
368
368
  crashedBundleId?: string;
369
369
  };
370
370
 
371
371
  /**
372
- * Notifies the native side that the app has successfully started with the current bundle.
373
- * If the bundle matches the staging bundle, it promotes to stable.
372
+ * Reads the native launch report for the current process.
374
373
  *
375
- * This function is called automatically when the module loads.
374
+ * This function is called automatically after the app has rendered.
376
375
  *
377
376
  * @returns {NotifyAppReadyResult} Bundle state information
378
- * - `status: "PROMOTED"` - Staging bundle was promoted to stable (ACTIVE event)
379
377
  * - `status: "RECOVERED"` - App recovered from crash, rollback occurred (ROLLBACK event)
380
378
  * - `status: "STABLE"` - No changes, already stable
381
379
  * - `crashedBundleId` - Present only when status is "RECOVERED"
@@ -384,33 +382,36 @@ export type NotifyAppReadyResult = {
384
382
  * ```ts
385
383
  * const result = HotUpdater.notifyAppReady();
386
384
  *
387
- * switch (result.status) {
388
- * case "PROMOTED":
389
- * // Send ACTIVE analytics event
390
- * analytics.track('bundle_active', { bundleId: HotUpdater.getBundleId() });
391
- * break;
392
- * case "RECOVERED":
385
+ * if (result.status === "RECOVERED") {
393
386
  * // Send ROLLBACK analytics event
394
387
  * analytics.track('bundle_rollback', { crashedBundleId: result.crashedBundleId });
395
- * break;
396
- * case "STABLE":
397
- * // No special action needed
398
- * break;
399
388
  * }
400
389
  * ```
401
390
  */
402
391
  export const notifyAppReady = (): NotifyAppReadyResult => {
403
- const bundleId = getBundleId();
404
- const result = HotUpdaterNative.notifyAppReady({ bundleId });
392
+ const result = HotUpdaterNative.notifyAppReady();
405
393
  // Oldarch returns JSON string, newarch returns array
406
394
  if (typeof result === "string") {
407
395
  try {
408
- return JSON.parse(result);
396
+ return normalizeNotifyAppReadyResult(JSON.parse(result));
409
397
  } catch {
410
398
  return { status: "STABLE" };
411
399
  }
412
400
  }
413
- return result;
401
+ return normalizeNotifyAppReadyResult(result);
402
+ };
403
+
404
+ const normalizeNotifyAppReadyResult = (
405
+ result: NotifyAppReadyResult | { status?: string; crashedBundleId?: string },
406
+ ): NotifyAppReadyResult => {
407
+ if (result.status === "RECOVERED") {
408
+ return {
409
+ status: "RECOVERED",
410
+ crashedBundleId: result.crashedBundleId,
411
+ };
412
+ }
413
+
414
+ return { status: "STABLE" };
414
415
  };
415
416
 
416
417
  /**
@@ -60,18 +60,16 @@ export interface Spec extends TurboModule {
60
60
  updateBundle(params: UpdateBundleParams): Promise<boolean>;
61
61
 
62
62
  /**
63
- * Notifies the native side that the app has successfully started with the given bundle.
64
- * If the bundle matches the staging bundle, it promotes to stable.
63
+ * Reads the launch report for the current process.
64
+ * This is a read-only API; native launch state has already been finalized.
65
65
  *
66
- * @param params - Parameters containing the bundle ID
67
66
  * @returns Object with status and optional crashedBundleId
68
- * - `status: "PROMOTED"` - Staging bundle was promoted to stable (ACTIVE event)
69
67
  * - `status: "RECOVERED"` - App recovered from crash, rollback occurred (ROLLBACK event)
70
68
  * - `status: "STABLE"` - No changes, already stable
71
69
  * - `crashedBundleId` - Present only when status is "RECOVERED"
72
70
  */
73
- notifyAppReady(params: { bundleId: string }): {
74
- status: "PROMOTED" | "RECOVERED" | "STABLE";
71
+ notifyAppReady(): {
72
+ status: "RECOVERED" | "STABLE";
75
73
  crashedBundleId?: string;
76
74
  };
77
75
 
package/src/types.ts CHANGED
@@ -57,11 +57,10 @@ export interface ResolverCheckUpdateParams {
57
57
  export interface ResolverNotifyAppReadyParams {
58
58
  /**
59
59
  * The bundle state from native notifyAppReady
60
- * - "PROMOTED": Staging bundle was promoted to stable
61
60
  * - "RECOVERED": App recovered from crash, rollback occurred
62
61
  * - "STABLE": No changes, bundle is stable
63
62
  */
64
- status: "PROMOTED" | "RECOVERED" | "STABLE";
63
+ status: "RECOVERED" | "STABLE";
65
64
 
66
65
  /**
67
66
  * Present only when status is "RECOVERED"
@@ -111,7 +110,7 @@ export interface HotUpdaterResolver {
111
110
  /**
112
111
  * Custom implementation for notifying app ready.
113
112
  * When provided, this completely replaces the default notifyAppReady network flow.
114
- * Note: The native notifyAppReady for bundle promotion still happens automatically.
113
+ * Note: Native rollback/promotion semantics are already finalized before this callback runs.
115
114
  *
116
115
  * @param params - All parameters about the current app state
117
116
  * @returns Notification result
package/src/wrap.tsx CHANGED
@@ -1,4 +1,4 @@
1
- import React, { useEffect, useLayoutEffect, useState } from "react";
1
+ import React, { useEffect, useState } from "react";
2
2
  import { checkForUpdate } from "./checkForUpdate";
3
3
  import type { HotUpdaterError } from "./error";
4
4
  import { useEventCallback } from "./hooks/useEventCallback";
@@ -39,12 +39,11 @@ interface CommonHotUpdaterOptions {
39
39
  requestTimeout?: number;
40
40
 
41
41
  /**
42
- * Callback invoked when the app is ready and bundle verification completes.
43
- * Provides information about bundle promotion, recovery from crashes, or stable state.
42
+ * Callback invoked when the app is ready and the native launch report is available.
43
+ * Provides information about rollback recovery or stable state.
44
44
  *
45
45
  * @param result - Bundle state information
46
46
  * @param result.status - Current bundle state:
47
- * - "PROMOTED": Staging bundle was promoted to stable (new update applied)
48
47
  * - "RECOVERED": App recovered from a crash, rollback occurred
49
48
  * - "STABLE": No changes, bundle is stable
50
49
  * @param result.crashedBundleId - Present only when status is "RECOVERED"
@@ -57,8 +56,6 @@ interface CommonHotUpdaterOptions {
57
56
  * onNotifyAppReady: ({ status, crashedBundleId }) => {
58
57
  * if (status === "RECOVERED") {
59
58
  * analytics.track('bundle_rollback', { crashedBundleId });
60
- * } else if (status === "PROMOTED") {
61
- * analytics.track('bundle_promoted');
62
59
  * }
63
60
  * }
64
61
  * })(App);
@@ -220,7 +217,6 @@ const handleNotifyAppReady = async (options: {
220
217
  onNotifyAppReady?: (result: NotifyAppReadyResult) => void;
221
218
  }): Promise<void> => {
222
219
  try {
223
- // Always call native notifyAppReady for bundle promotion
224
220
  const nativeResult = nativeNotifyAppReady();
225
221
 
226
222
  // If resolver.notifyAppReady exists, call it with simplified params
@@ -249,7 +245,7 @@ export function wrap<P extends React.JSX.IntrinsicAttributes = object>(
249
245
  if (options.updateMode === "manual") {
250
246
  return (WrappedComponent: React.ComponentType<P>) => {
251
247
  const ManualHOC: React.FC<P> = (props: P) => {
252
- useLayoutEffect(() => {
248
+ useEffect(() => {
253
249
  void handleNotifyAppReady(options);
254
250
  }, []);
255
251
 
@@ -342,13 +338,13 @@ export function wrap<P extends React.JSX.IntrinsicAttributes = object>(
342
338
  restOptions.onProgress?.(progress);
343
339
  }, [progress]);
344
340
 
345
- // Notify native side that app is ready (JS bundle fully loaded)
346
- useLayoutEffect(() => {
341
+ // Read the native launch report after the first render commit.
342
+ useEffect(() => {
347
343
  void handleNotifyAppReady(restOptions);
348
344
  }, []);
349
345
 
350
346
  // Start update check
351
- useLayoutEffect(() => {
347
+ useEffect(() => {
352
348
  initHotUpdater();
353
349
  }, []);
354
350