@swiftpatch/react-native 2.0.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 (182) hide show
  1. package/README.md +430 -0
  2. package/android/build.gradle +105 -0
  3. package/android/src/main/AndroidManifest.xml +6 -0
  4. package/android/src/main/java/com/swiftpatch/BundleManager.kt +107 -0
  5. package/android/src/main/java/com/swiftpatch/CrashDetector.kt +79 -0
  6. package/android/src/main/java/com/swiftpatch/CryptoVerifier.kt +69 -0
  7. package/android/src/main/java/com/swiftpatch/DownloadManager.kt +120 -0
  8. package/android/src/main/java/com/swiftpatch/EventQueue.kt +86 -0
  9. package/android/src/main/java/com/swiftpatch/FileUtils.kt +60 -0
  10. package/android/src/main/java/com/swiftpatch/PatchApplier.kt +60 -0
  11. package/android/src/main/java/com/swiftpatch/SignalCrashHandler.kt +84 -0
  12. package/android/src/main/java/com/swiftpatch/SlotManager.kt +299 -0
  13. package/android/src/main/java/com/swiftpatch/SwiftPatchModule.kt +630 -0
  14. package/android/src/main/java/com/swiftpatch/SwiftPatchPackage.kt +21 -0
  15. package/android/src/main/jni/CMakeLists.txt +12 -0
  16. package/android/src/main/jni/bspatch.c +188 -0
  17. package/android/src/main/jni/bspatch.h +57 -0
  18. package/android/src/main/jni/bspatch_jni.c +28 -0
  19. package/ios/Libraries/bspatch/bspatch.c +188 -0
  20. package/ios/Libraries/bspatch/bspatch.h +50 -0
  21. package/ios/Libraries/bspatch/module.modulemap +4 -0
  22. package/ios/SwiftPatch/BundleManager.swift +113 -0
  23. package/ios/SwiftPatch/CrashDetector.swift +71 -0
  24. package/ios/SwiftPatch/CryptoVerifier.swift +70 -0
  25. package/ios/SwiftPatch/DownloadManager.swift +125 -0
  26. package/ios/SwiftPatch/EventQueue.swift +116 -0
  27. package/ios/SwiftPatch/FileUtils.swift +38 -0
  28. package/ios/SwiftPatch/PatchApplier.swift +41 -0
  29. package/ios/SwiftPatch/SignalCrashHandler.swift +129 -0
  30. package/ios/SwiftPatch/SlotManager.swift +360 -0
  31. package/ios/SwiftPatch/SwiftPatchModule.m +56 -0
  32. package/ios/SwiftPatch/SwiftPatchModule.swift +621 -0
  33. package/lib/commonjs/SwiftPatchCore.js +140 -0
  34. package/lib/commonjs/SwiftPatchCore.js.map +1 -0
  35. package/lib/commonjs/SwiftPatchProvider.js +617 -0
  36. package/lib/commonjs/SwiftPatchProvider.js.map +1 -0
  37. package/lib/commonjs/constants.js +50 -0
  38. package/lib/commonjs/constants.js.map +1 -0
  39. package/lib/commonjs/core/Downloader.js +63 -0
  40. package/lib/commonjs/core/Downloader.js.map +1 -0
  41. package/lib/commonjs/core/Installer.js +46 -0
  42. package/lib/commonjs/core/Installer.js.map +1 -0
  43. package/lib/commonjs/core/Rollback.js +36 -0
  44. package/lib/commonjs/core/Rollback.js.map +1 -0
  45. package/lib/commonjs/core/UpdateChecker.js +57 -0
  46. package/lib/commonjs/core/UpdateChecker.js.map +1 -0
  47. package/lib/commonjs/core/Verifier.js +82 -0
  48. package/lib/commonjs/core/Verifier.js.map +1 -0
  49. package/lib/commonjs/core/index.js +41 -0
  50. package/lib/commonjs/core/index.js.map +1 -0
  51. package/lib/commonjs/index.js +154 -0
  52. package/lib/commonjs/index.js.map +1 -0
  53. package/lib/commonjs/modal/SwiftPatchModal.js +667 -0
  54. package/lib/commonjs/modal/SwiftPatchModal.js.map +1 -0
  55. package/lib/commonjs/modal/useSwiftPatchModal.js +26 -0
  56. package/lib/commonjs/modal/useSwiftPatchModal.js.map +1 -0
  57. package/lib/commonjs/native/NativeSwiftPatch.js +85 -0
  58. package/lib/commonjs/native/NativeSwiftPatch.js.map +1 -0
  59. package/lib/commonjs/native/NativeSwiftPatchSpec.js +15 -0
  60. package/lib/commonjs/native/NativeSwiftPatchSpec.js.map +1 -0
  61. package/lib/commonjs/package.json +1 -0
  62. package/lib/commonjs/types.js +126 -0
  63. package/lib/commonjs/types.js.map +1 -0
  64. package/lib/commonjs/useSwiftPatch.js +31 -0
  65. package/lib/commonjs/useSwiftPatch.js.map +1 -0
  66. package/lib/commonjs/utils/api.js +206 -0
  67. package/lib/commonjs/utils/api.js.map +1 -0
  68. package/lib/commonjs/utils/device.js +23 -0
  69. package/lib/commonjs/utils/device.js.map +1 -0
  70. package/lib/commonjs/utils/logger.js +30 -0
  71. package/lib/commonjs/utils/logger.js.map +1 -0
  72. package/lib/commonjs/utils/storage.js +31 -0
  73. package/lib/commonjs/utils/storage.js.map +1 -0
  74. package/lib/commonjs/withSwiftPatch.js +42 -0
  75. package/lib/commonjs/withSwiftPatch.js.map +1 -0
  76. package/lib/module/SwiftPatchCore.js +135 -0
  77. package/lib/module/SwiftPatchCore.js.map +1 -0
  78. package/lib/module/SwiftPatchProvider.js +611 -0
  79. package/lib/module/SwiftPatchProvider.js.map +1 -0
  80. package/lib/module/constants.js +46 -0
  81. package/lib/module/constants.js.map +1 -0
  82. package/lib/module/core/Downloader.js +57 -0
  83. package/lib/module/core/Downloader.js.map +1 -0
  84. package/lib/module/core/Installer.js +41 -0
  85. package/lib/module/core/Installer.js.map +1 -0
  86. package/lib/module/core/Rollback.js +31 -0
  87. package/lib/module/core/Rollback.js.map +1 -0
  88. package/lib/module/core/UpdateChecker.js +51 -0
  89. package/lib/module/core/UpdateChecker.js.map +1 -0
  90. package/lib/module/core/Verifier.js +76 -0
  91. package/lib/module/core/Verifier.js.map +1 -0
  92. package/lib/module/core/index.js +8 -0
  93. package/lib/module/core/index.js.map +1 -0
  94. package/lib/module/index.js +34 -0
  95. package/lib/module/index.js.map +1 -0
  96. package/lib/module/modal/SwiftPatchModal.js +661 -0
  97. package/lib/module/modal/SwiftPatchModal.js.map +1 -0
  98. package/lib/module/modal/useSwiftPatchModal.js +22 -0
  99. package/lib/module/modal/useSwiftPatchModal.js.map +1 -0
  100. package/lib/module/native/NativeSwiftPatch.js +78 -0
  101. package/lib/module/native/NativeSwiftPatch.js.map +1 -0
  102. package/lib/module/native/NativeSwiftPatchSpec.js +12 -0
  103. package/lib/module/native/NativeSwiftPatchSpec.js.map +1 -0
  104. package/lib/module/types.js +139 -0
  105. package/lib/module/types.js.map +1 -0
  106. package/lib/module/useSwiftPatch.js +26 -0
  107. package/lib/module/useSwiftPatch.js.map +1 -0
  108. package/lib/module/utils/api.js +197 -0
  109. package/lib/module/utils/api.js.map +1 -0
  110. package/lib/module/utils/device.js +18 -0
  111. package/lib/module/utils/device.js.map +1 -0
  112. package/lib/module/utils/logger.js +26 -0
  113. package/lib/module/utils/logger.js.map +1 -0
  114. package/lib/module/utils/storage.js +24 -0
  115. package/lib/module/utils/storage.js.map +1 -0
  116. package/lib/module/withSwiftPatch.js +37 -0
  117. package/lib/module/withSwiftPatch.js.map +1 -0
  118. package/lib/typescript/SwiftPatchCore.d.ts +64 -0
  119. package/lib/typescript/SwiftPatchCore.d.ts.map +1 -0
  120. package/lib/typescript/SwiftPatchProvider.d.ts +22 -0
  121. package/lib/typescript/SwiftPatchProvider.d.ts.map +1 -0
  122. package/lib/typescript/constants.d.ts +33 -0
  123. package/lib/typescript/constants.d.ts.map +1 -0
  124. package/lib/typescript/core/Downloader.d.ts +34 -0
  125. package/lib/typescript/core/Downloader.d.ts.map +1 -0
  126. package/lib/typescript/core/Installer.d.ts +25 -0
  127. package/lib/typescript/core/Installer.d.ts.map +1 -0
  128. package/lib/typescript/core/Rollback.d.ts +18 -0
  129. package/lib/typescript/core/Rollback.d.ts.map +1 -0
  130. package/lib/typescript/core/UpdateChecker.d.ts +27 -0
  131. package/lib/typescript/core/UpdateChecker.d.ts.map +1 -0
  132. package/lib/typescript/core/Verifier.d.ts +31 -0
  133. package/lib/typescript/core/Verifier.d.ts.map +1 -0
  134. package/lib/typescript/core/index.d.ts +8 -0
  135. package/lib/typescript/core/index.d.ts.map +1 -0
  136. package/lib/typescript/index.d.ts +13 -0
  137. package/lib/typescript/index.d.ts.map +1 -0
  138. package/lib/typescript/modal/SwiftPatchModal.d.ts +11 -0
  139. package/lib/typescript/modal/SwiftPatchModal.d.ts.map +1 -0
  140. package/lib/typescript/modal/useSwiftPatchModal.d.ts +7 -0
  141. package/lib/typescript/modal/useSwiftPatchModal.d.ts.map +1 -0
  142. package/lib/typescript/native/NativeSwiftPatch.d.ts +61 -0
  143. package/lib/typescript/native/NativeSwiftPatch.d.ts.map +1 -0
  144. package/lib/typescript/native/NativeSwiftPatchSpec.d.ts +67 -0
  145. package/lib/typescript/native/NativeSwiftPatchSpec.d.ts.map +1 -0
  146. package/lib/typescript/types.d.ts +266 -0
  147. package/lib/typescript/types.d.ts.map +1 -0
  148. package/lib/typescript/useSwiftPatch.d.ts +12 -0
  149. package/lib/typescript/useSwiftPatch.d.ts.map +1 -0
  150. package/lib/typescript/utils/api.d.ts +87 -0
  151. package/lib/typescript/utils/api.d.ts.map +1 -0
  152. package/lib/typescript/utils/device.d.ts +9 -0
  153. package/lib/typescript/utils/device.d.ts.map +1 -0
  154. package/lib/typescript/utils/logger.d.ts +8 -0
  155. package/lib/typescript/utils/logger.d.ts.map +1 -0
  156. package/lib/typescript/utils/storage.d.ts +14 -0
  157. package/lib/typescript/utils/storage.d.ts.map +1 -0
  158. package/lib/typescript/withSwiftPatch.d.ts +12 -0
  159. package/lib/typescript/withSwiftPatch.d.ts.map +1 -0
  160. package/package.json +99 -0
  161. package/react-native-swiftpatch.podspec +50 -0
  162. package/src/SwiftPatchCore.ts +148 -0
  163. package/src/SwiftPatchProvider.tsx +514 -0
  164. package/src/constants.ts +49 -0
  165. package/src/core/Downloader.ts +74 -0
  166. package/src/core/Installer.ts +38 -0
  167. package/src/core/Rollback.ts +28 -0
  168. package/src/core/UpdateChecker.ts +70 -0
  169. package/src/core/Verifier.ts +92 -0
  170. package/src/core/index.ts +11 -0
  171. package/src/index.ts +64 -0
  172. package/src/modal/SwiftPatchModal.tsx +657 -0
  173. package/src/modal/useSwiftPatchModal.ts +24 -0
  174. package/src/native/NativeSwiftPatch.ts +205 -0
  175. package/src/native/NativeSwiftPatchSpec.ts +139 -0
  176. package/src/types.ts +336 -0
  177. package/src/useSwiftPatch.ts +29 -0
  178. package/src/utils/api.ts +244 -0
  179. package/src/utils/device.ts +15 -0
  180. package/src/utils/logger.ts +29 -0
  181. package/src/utils/storage.ts +23 -0
  182. package/src/withSwiftPatch.tsx +41 -0
@@ -0,0 +1,148 @@
1
+ import NativeSwiftPatch from './native/NativeSwiftPatch';
2
+ import { reportInstallStatus } from './utils/api';
3
+ import { logger } from './utils/logger';
4
+ import { UpdateChecker } from './core/UpdateChecker';
5
+ import { Downloader } from './core/Downloader';
6
+ import { Installer } from './core/Installer';
7
+ import { Rollback as RollbackModule } from './core/Rollback';
8
+ import type {
9
+ SwiftPatchConfig,
10
+ ReleaseInfo,
11
+ BundleInfo,
12
+ } from './types';
13
+ import { DEFAULT_CONFIG } from './constants';
14
+
15
+ /**
16
+ * Imperative API for SwiftPatch (non-React usage).
17
+ *
18
+ * Use this when you need to interact with SwiftPatch outside of
19
+ * React components, such as in native module callbacks or background tasks.
20
+ *
21
+ * @example
22
+ * ```ts
23
+ * import { SwiftPatch } from '@orbitplus/react-native';
24
+ *
25
+ * const sp = new SwiftPatch({ deploymentKey: 'your-key' });
26
+ * await sp.init();
27
+ * const update = await sp.checkForUpdate();
28
+ * if (update) {
29
+ * await sp.downloadAndInstall(update);
30
+ * }
31
+ * ```
32
+ */
33
+ export class SwiftPatch {
34
+ private config: Required<Omit<SwiftPatchConfig, 'customHeaders' | 'publicKey'>> & {
35
+ customHeaders: Record<string, string>;
36
+ publicKey: string | null;
37
+ };
38
+
39
+ private updateChecker!: UpdateChecker;
40
+ private downloader!: Downloader;
41
+ private installer!: Installer;
42
+ private rollbackModule!: RollbackModule;
43
+
44
+ constructor(userConfig: SwiftPatchConfig) {
45
+ this.config = { ...DEFAULT_CONFIG, ...userConfig } as typeof this.config;
46
+ }
47
+
48
+ /**
49
+ * Initialize the native module
50
+ */
51
+ async init(): Promise<void> {
52
+ await NativeSwiftPatch.initialize({
53
+ deploymentKey: this.config.deploymentKey,
54
+ serverUrl: this.config.serverUrl,
55
+ publicKey: this.config.publicKey,
56
+ });
57
+ NativeSwiftPatch.setDebugMode(this.config.debug);
58
+ logger.setDebug(this.config.debug);
59
+
60
+ // Initialize core modules
61
+ this.updateChecker = new UpdateChecker({
62
+ serverUrl: this.config.serverUrl,
63
+ deploymentKey: this.config.deploymentKey,
64
+ customHeaders: this.config.customHeaders,
65
+ });
66
+ this.downloader = new Downloader();
67
+ this.installer = new Installer();
68
+ this.rollbackModule = new RollbackModule();
69
+
70
+ logger.info('SwiftPatch initialized (imperative)');
71
+ }
72
+
73
+ /**
74
+ * Check for an available update
75
+ */
76
+ async checkForUpdate(): Promise<ReleaseInfo | null> {
77
+ const result = await this.updateChecker.check();
78
+
79
+ if (result.updateAvailable && result.release) {
80
+ return result.release;
81
+ }
82
+ return null;
83
+ }
84
+
85
+ /**
86
+ * Download an update
87
+ */
88
+ async download(release: ReleaseInfo): Promise<void> {
89
+ await this.downloader.downloadRelease(release);
90
+
91
+ await reportInstallStatus({
92
+ serverUrl: this.config.serverUrl,
93
+ releaseId: release.id,
94
+ status: 'downloaded',
95
+ deploymentKey: this.config.deploymentKey,
96
+ });
97
+ }
98
+
99
+ /**
100
+ * Install a downloaded update
101
+ */
102
+ async install(release: ReleaseInfo): Promise<void> {
103
+ await this.installer.install(release.bundleHash);
104
+
105
+ await reportInstallStatus({
106
+ serverUrl: this.config.serverUrl,
107
+ releaseId: release.id,
108
+ status: 'installed',
109
+ deploymentKey: this.config.deploymentKey,
110
+ });
111
+ }
112
+
113
+ /**
114
+ * Download and install an update in one call
115
+ */
116
+ async downloadAndInstall(release: ReleaseInfo): Promise<void> {
117
+ await this.download(release);
118
+ await this.install(release);
119
+ }
120
+
121
+ /**
122
+ * Restart the app to apply the update
123
+ */
124
+ restart(): void {
125
+ NativeSwiftPatch.restart();
126
+ }
127
+
128
+ /**
129
+ * Rollback to the previous bundle
130
+ */
131
+ async rollback(): Promise<void> {
132
+ await this.rollbackModule.rollback();
133
+ }
134
+
135
+ /**
136
+ * Get current bundle info
137
+ */
138
+ async getCurrentBundle(): Promise<BundleInfo | null> {
139
+ return NativeSwiftPatch.getCurrentBundle();
140
+ }
141
+
142
+ /**
143
+ * Clear any pending update
144
+ */
145
+ async clearPendingUpdate(): Promise<void> {
146
+ await this.rollbackModule.clearPending();
147
+ }
148
+ }
@@ -0,0 +1,514 @@
1
+ import React, {
2
+ createContext,
3
+ useContext,
4
+ useEffect,
5
+ useReducer,
6
+ useCallback,
7
+ useRef,
8
+ type ReactNode,
9
+ } from 'react';
10
+ import { AppState, type AppStateStatus } from 'react-native';
11
+ import type {
12
+ SwiftPatchConfig,
13
+ SwiftPatchState,
14
+ SwiftPatchActions,
15
+ ReleaseInfo,
16
+ UpdateStatus,
17
+ SwiftPatchError,
18
+ DownloadProgress,
19
+ SlotMetadata,
20
+ BundleInfo,
21
+ } from './types';
22
+ import {
23
+ UpdateStatus as Status,
24
+ InstallMode,
25
+ SwiftPatchErrorCode,
26
+ EnvironmentMode,
27
+ } from './types';
28
+ import { DEFAULT_CONFIG, EVENT_POLL_INTERVAL_MS } from './constants';
29
+ import NativeSwiftPatch, {
30
+ onDownloadProgress,
31
+ onRollback,
32
+ onVersionChanged,
33
+ } from './native/NativeSwiftPatch';
34
+ import { reportInstallStatus, logBulkEvents } from './utils/api';
35
+ import { logger } from './utils/logger';
36
+ import { UpdateChecker } from './core/UpdateChecker';
37
+ import { Downloader } from './core/Downloader';
38
+ import { Installer } from './core/Installer';
39
+ import { Rollback as RollbackModule } from './core/Rollback';
40
+
41
+ // Context
42
+ interface SwiftPatchContextValue extends SwiftPatchState, SwiftPatchActions {}
43
+
44
+ const SwiftPatchContext = createContext<SwiftPatchContextValue | null>(null);
45
+
46
+ // State reducer
47
+ type Action =
48
+ | { type: 'SET_STATUS'; payload: UpdateStatus }
49
+ | { type: 'SET_DOWNLOAD_PROGRESS'; payload: DownloadProgress | null }
50
+ | { type: 'SET_AVAILABLE_UPDATE'; payload: ReleaseInfo | null }
51
+ | { type: 'SET_ERROR'; payload: SwiftPatchError | null }
52
+ | { type: 'SET_RESTART_REQUIRED'; payload: boolean }
53
+ | { type: 'SET_LAST_CHECKED'; payload: Date }
54
+ | { type: 'SET_CURRENT_BUNDLE'; payload: BundleInfo | null }
55
+ | { type: 'SET_SLOT_METADATA'; payload: SlotMetadata | null }
56
+ | { type: 'SET_ENVIRONMENT'; payload: EnvironmentMode };
57
+
58
+ function reducer(state: SwiftPatchState, action: Action): SwiftPatchState {
59
+ switch (action.type) {
60
+ case 'SET_STATUS':
61
+ return { ...state, status: action.payload };
62
+ case 'SET_DOWNLOAD_PROGRESS':
63
+ return { ...state, downloadProgress: action.payload };
64
+ case 'SET_AVAILABLE_UPDATE':
65
+ return { ...state, availableUpdate: action.payload };
66
+ case 'SET_ERROR':
67
+ return {
68
+ ...state,
69
+ error: action.payload,
70
+ status: action.payload ? Status.ERROR : state.status,
71
+ };
72
+ case 'SET_RESTART_REQUIRED':
73
+ return { ...state, isRestartRequired: action.payload };
74
+ case 'SET_LAST_CHECKED':
75
+ return { ...state, lastCheckedAt: action.payload };
76
+ case 'SET_CURRENT_BUNDLE':
77
+ return { ...state, currentBundle: action.payload };
78
+ case 'SET_SLOT_METADATA':
79
+ return { ...state, slotMetadata: action.payload };
80
+ case 'SET_ENVIRONMENT':
81
+ return { ...state, environment: action.payload };
82
+ default:
83
+ return state;
84
+ }
85
+ }
86
+
87
+ const initialState: SwiftPatchState = {
88
+ status: Status.UP_TO_DATE,
89
+ downloadProgress: null,
90
+ currentBundle: null,
91
+ availableUpdate: null,
92
+ isRestartRequired: false,
93
+ error: null,
94
+ lastCheckedAt: null,
95
+ slotMetadata: null,
96
+ environment: EnvironmentMode.PRODUCTION,
97
+ };
98
+
99
+ // Provider Props
100
+ interface SwiftPatchProviderProps {
101
+ config: SwiftPatchConfig;
102
+ children: ReactNode;
103
+ }
104
+
105
+ /**
106
+ * SwiftPatch Provider Component
107
+ *
108
+ * Wrap your app with this provider to enable OTA updates
109
+ * with dual-slot architecture, crash detection, and event analytics.
110
+ */
111
+ export function SwiftPatchProvider({
112
+ config,
113
+ children,
114
+ }: SwiftPatchProviderProps) {
115
+ const mergedConfig = { ...DEFAULT_CONFIG, ...config };
116
+ const [state, dispatch] = useReducer(reducer, initialState);
117
+ const lastCheckRef = useRef<number>(0);
118
+ const isCheckingRef = useRef<boolean>(false);
119
+ const configRef = useRef(mergedConfig);
120
+ configRef.current = mergedConfig;
121
+
122
+ // Core modules refs
123
+ const updateCheckerRef = useRef<UpdateChecker | null>(null);
124
+ const downloaderRef = useRef<Downloader | null>(null);
125
+ const installerRef = useRef<Installer | null>(null);
126
+ const rollbackModuleRef = useRef<RollbackModule | null>(null);
127
+ const eventPollIntervalRef = useRef<ReturnType<typeof setInterval> | null>(null);
128
+
129
+ // Initialize native module and core modules
130
+ useEffect(() => {
131
+ const init = async () => {
132
+ try {
133
+ await NativeSwiftPatch.initialize({
134
+ deploymentKey: mergedConfig.deploymentKey,
135
+ serverUrl: mergedConfig.serverUrl!,
136
+ publicKey: mergedConfig.publicKey || null,
137
+ });
138
+
139
+ NativeSwiftPatch.setDebugMode(mergedConfig.debug || false);
140
+ logger.setDebug(mergedConfig.debug || false);
141
+
142
+ // Mark app as mounted (for signal crash detection)
143
+ await NativeSwiftPatch.markMounted();
144
+
145
+ // Initialize core modules
146
+ updateCheckerRef.current = new UpdateChecker({
147
+ serverUrl: mergedConfig.serverUrl!,
148
+ deploymentKey: mergedConfig.deploymentKey,
149
+ customHeaders: mergedConfig.customHeaders,
150
+ });
151
+ downloaderRef.current = new Downloader();
152
+ installerRef.current = new Installer();
153
+ rollbackModuleRef.current = new RollbackModule();
154
+
155
+ // Get current bundle info
156
+ const currentBundle = await NativeSwiftPatch.getCurrentBundle();
157
+ if (currentBundle) {
158
+ dispatch({ type: 'SET_CURRENT_BUNDLE', payload: currentBundle });
159
+ dispatch({ type: 'SET_STATUS', payload: Status.UP_TO_DATE });
160
+ }
161
+
162
+ // Get slot metadata
163
+ const metadata = await NativeSwiftPatch.getSlotMetadata();
164
+ dispatch({ type: 'SET_SLOT_METADATA', payload: metadata });
165
+ dispatch({
166
+ type: 'SET_ENVIRONMENT',
167
+ payload: metadata.environment,
168
+ });
169
+
170
+ // Check for pending install (temp hash set)
171
+ const hasPending = await NativeSwiftPatch.hasPendingInstall();
172
+ if (hasPending) {
173
+ dispatch({ type: 'SET_RESTART_REQUIRED', payload: true });
174
+ dispatch({ type: 'SET_STATUS', payload: Status.RESTART_REQUIRED });
175
+ }
176
+
177
+ // Record successful launch & auto-stabilize
178
+ if (currentBundle && !currentBundle.isOriginal) {
179
+ const launchCount = await NativeSwiftPatch.recordSuccessfulLaunch(currentBundle.hash);
180
+ const autoStabilizeThreshold = mergedConfig.autoStabilizeAfterLaunches || 0;
181
+ if (autoStabilizeThreshold > 0 && launchCount >= autoStabilizeThreshold) {
182
+ if (metadata.prod.currentSlot === 'NEW_SLOT') {
183
+ try {
184
+ await NativeSwiftPatch.stabilize();
185
+ const updatedMeta = await NativeSwiftPatch.getSlotMetadata();
186
+ dispatch({ type: 'SET_SLOT_METADATA', payload: updatedMeta });
187
+ logger.info('Auto-stabilized after', launchCount, 'successful launches');
188
+ } catch (_e) {
189
+ logger.warn('Auto-stabilization failed');
190
+ }
191
+ }
192
+ }
193
+ }
194
+
195
+ logger.info('SwiftPatch initialized (v2.0.0 with dual-slot architecture)');
196
+ } catch (error: unknown) {
197
+ const message = error instanceof Error ? error.message : 'Unknown initialization error';
198
+ logger.error('Failed to initialize SwiftPatch', error);
199
+ dispatch({
200
+ type: 'SET_ERROR',
201
+ payload: { code: SwiftPatchErrorCode.CONFIG_ERROR, message },
202
+ });
203
+ }
204
+ };
205
+
206
+ init();
207
+
208
+ // Cleanup mount state on unmount
209
+ return () => {
210
+ NativeSwiftPatch.markUnmounted().catch(() => {});
211
+ };
212
+ }, [mergedConfig.deploymentKey, mergedConfig.serverUrl, mergedConfig.publicKey, mergedConfig.debug, mergedConfig.autoStabilizeAfterLaunches]);
213
+
214
+ // Event queue polling
215
+ useEffect(() => {
216
+ const pollEvents = async () => {
217
+ try {
218
+ const events = await NativeSwiftPatch.popEvents();
219
+ if (events && events.length > 0) {
220
+ const deviceId = await NativeSwiftPatch.getDeviceId();
221
+ const cfg = configRef.current;
222
+
223
+ const result = await logBulkEvents({
224
+ serverUrl: cfg.serverUrl!,
225
+ deploymentKey: cfg.deploymentKey,
226
+ deviceId,
227
+ events: events as any,
228
+ customHeaders: cfg.customHeaders,
229
+ });
230
+
231
+ if (result.acknowledged.length > 0) {
232
+ await NativeSwiftPatch.acknowledgeEvents(
233
+ JSON.stringify(result.acknowledged)
234
+ );
235
+ }
236
+ }
237
+ } catch (_e) {
238
+ // Silent - event polling is non-critical
239
+ }
240
+ };
241
+
242
+ eventPollIntervalRef.current = setInterval(pollEvents, EVENT_POLL_INTERVAL_MS);
243
+
244
+ return () => {
245
+ if (eventPollIntervalRef.current) {
246
+ clearInterval(eventPollIntervalRef.current);
247
+ }
248
+ };
249
+ }, []);
250
+
251
+ // Subscribe to download progress
252
+ useEffect(() => {
253
+ const unsubscribe = onDownloadProgress((progress) => {
254
+ dispatch({ type: 'SET_DOWNLOAD_PROGRESS', payload: progress });
255
+ });
256
+ return unsubscribe;
257
+ }, []);
258
+
259
+ // Subscribe to rollback events
260
+ useEffect(() => {
261
+ const unsubscribe = onRollback((reason) => {
262
+ logger.warn('Rollback occurred:', reason);
263
+ dispatch({ type: 'SET_STATUS', payload: Status.UP_TO_DATE });
264
+ dispatch({ type: 'SET_AVAILABLE_UPDATE', payload: null });
265
+ dispatch({ type: 'SET_RESTART_REQUIRED', payload: false });
266
+ // Refresh metadata
267
+ NativeSwiftPatch.getSlotMetadata().then((meta) => {
268
+ dispatch({ type: 'SET_SLOT_METADATA', payload: meta });
269
+ }).catch(() => {});
270
+ });
271
+ return unsubscribe;
272
+ }, []);
273
+
274
+ // Subscribe to version change events
275
+ useEffect(() => {
276
+ const unsubscribe = onVersionChanged((reason) => {
277
+ logger.info('Version changed:', reason);
278
+ dispatch({ type: 'SET_STATUS', payload: Status.UP_TO_DATE });
279
+ dispatch({ type: 'SET_AVAILABLE_UPDATE', payload: null });
280
+ dispatch({ type: 'SET_RESTART_REQUIRED', payload: false });
281
+ NativeSwiftPatch.getSlotMetadata().then((meta) => {
282
+ dispatch({ type: 'SET_SLOT_METADATA', payload: meta });
283
+ }).catch(() => {});
284
+ });
285
+ return unsubscribe;
286
+ }, []);
287
+
288
+ // Check for update
289
+ const checkForUpdate = useCallback(async (): Promise<ReleaseInfo | null> => {
290
+ if (isCheckingRef.current) return null;
291
+ if (!updateCheckerRef.current) return null;
292
+
293
+ isCheckingRef.current = true;
294
+ dispatch({ type: 'SET_STATUS', payload: Status.CHECKING });
295
+ dispatch({ type: 'SET_ERROR', payload: null });
296
+
297
+ try {
298
+ const result = await updateCheckerRef.current.check();
299
+ lastCheckRef.current = Date.now();
300
+ dispatch({ type: 'SET_LAST_CHECKED', payload: new Date() });
301
+
302
+ if (result.updateAvailable && result.release) {
303
+ dispatch({ type: 'SET_AVAILABLE_UPDATE', payload: result.release });
304
+ dispatch({ type: 'SET_STATUS', payload: Status.UPDATE_AVAILABLE });
305
+ return result.release;
306
+ } else {
307
+ dispatch({ type: 'SET_STATUS', payload: Status.UP_TO_DATE });
308
+ return null;
309
+ }
310
+ } catch (error: unknown) {
311
+ const message = error instanceof Error ? error.message : 'Failed to check for update';
312
+ dispatch({
313
+ type: 'SET_ERROR',
314
+ payload: { code: SwiftPatchErrorCode.NETWORK_ERROR, message },
315
+ });
316
+ return null;
317
+ } finally {
318
+ isCheckingRef.current = false;
319
+ }
320
+ }, []);
321
+
322
+ // Check on resume
323
+ useEffect(() => {
324
+ if (!mergedConfig.checkOnResume) return;
325
+
326
+ const handleAppStateChange = (nextState: AppStateStatus) => {
327
+ if (nextState === 'active') {
328
+ const now = Date.now();
329
+ if (now - lastCheckRef.current >= (mergedConfig.checkInterval ?? 60_000)) {
330
+ checkForUpdate();
331
+ }
332
+ }
333
+ };
334
+
335
+ const subscription = AppState.addEventListener('change', handleAppStateChange);
336
+ return () => subscription.remove();
337
+ }, [mergedConfig.checkOnResume, mergedConfig.checkInterval, checkForUpdate]);
338
+
339
+ // Restart
340
+ const restart = useCallback((): void => {
341
+ NativeSwiftPatch.restart();
342
+ }, []);
343
+
344
+ // Install update
345
+ const installUpdate = useCallback(async (): Promise<void> => {
346
+ const update = state.availableUpdate;
347
+ if (!update) throw new Error('No update available to install');
348
+
349
+ dispatch({ type: 'SET_STATUS', payload: Status.INSTALLING });
350
+
351
+ try {
352
+ await NativeSwiftPatch.installUpdate(update.bundleHash);
353
+
354
+ dispatch({ type: 'SET_STATUS', payload: Status.RESTART_REQUIRED });
355
+ dispatch({ type: 'SET_RESTART_REQUIRED', payload: true });
356
+
357
+ const cfg = configRef.current;
358
+ await reportInstallStatus({
359
+ serverUrl: cfg.serverUrl!,
360
+ releaseId: update.id,
361
+ status: 'installed',
362
+ deploymentKey: cfg.deploymentKey,
363
+ });
364
+
365
+ // Auto-restart for immediate install mode
366
+ if (
367
+ (update.isMandatory && cfg.mandatoryInstallMode === InstallMode.IMMEDIATE) ||
368
+ (!update.isMandatory && cfg.installMode === InstallMode.IMMEDIATE)
369
+ ) {
370
+ restart();
371
+ }
372
+ } catch (error: unknown) {
373
+ const message = error instanceof Error ? error.message : 'Failed to install update';
374
+ dispatch({
375
+ type: 'SET_ERROR',
376
+ payload: { code: SwiftPatchErrorCode.INSTALL_ERROR, message },
377
+ });
378
+ }
379
+ }, [state.availableUpdate, restart]);
380
+
381
+ // Download update
382
+ const downloadUpdate = useCallback(async (): Promise<void> => {
383
+ const update = state.availableUpdate;
384
+ if (!update) throw new Error('No update available to download');
385
+ if (!downloaderRef.current) throw new Error('Downloader not initialized');
386
+
387
+ dispatch({ type: 'SET_STATUS', payload: Status.DOWNLOADING });
388
+ dispatch({
389
+ type: 'SET_DOWNLOAD_PROGRESS',
390
+ payload: { downloadedBytes: 0, totalBytes: update.downloadSize, percentage: 0 },
391
+ });
392
+
393
+ try {
394
+ await downloaderRef.current.downloadRelease(update);
395
+
396
+ dispatch({ type: 'SET_STATUS', payload: Status.READY_TO_INSTALL });
397
+ dispatch({ type: 'SET_DOWNLOAD_PROGRESS', payload: null });
398
+
399
+ const cfg = configRef.current;
400
+ await reportInstallStatus({
401
+ serverUrl: cfg.serverUrl!,
402
+ releaseId: update.id,
403
+ status: 'downloaded',
404
+ deploymentKey: cfg.deploymentKey,
405
+ });
406
+
407
+ // Auto-install for mandatory updates
408
+ if (update.isMandatory && cfg.mandatoryInstallMode === InstallMode.IMMEDIATE) {
409
+ await installUpdate();
410
+ }
411
+ } catch (error: unknown) {
412
+ const message = error instanceof Error ? error.message : 'Failed to download update';
413
+ dispatch({
414
+ type: 'SET_ERROR',
415
+ payload: { code: SwiftPatchErrorCode.DOWNLOAD_ERROR, message },
416
+ });
417
+ dispatch({ type: 'SET_DOWNLOAD_PROGRESS', payload: null });
418
+ }
419
+ }, [state.availableUpdate, installUpdate]);
420
+
421
+ // Rollback
422
+ const rollback = useCallback(async (): Promise<void> => {
423
+ await NativeSwiftPatch.rollback();
424
+ dispatch({ type: 'SET_STATUS', payload: Status.UP_TO_DATE });
425
+ dispatch({ type: 'SET_AVAILABLE_UPDATE', payload: null });
426
+ dispatch({ type: 'SET_RESTART_REQUIRED', payload: false });
427
+ const meta = await NativeSwiftPatch.getSlotMetadata();
428
+ dispatch({ type: 'SET_SLOT_METADATA', payload: meta });
429
+ }, []);
430
+
431
+ // Clear pending
432
+ const clearPendingUpdate = useCallback(async (): Promise<void> => {
433
+ await NativeSwiftPatch.clearPendingUpdate();
434
+ dispatch({ type: 'SET_STATUS', payload: Status.UP_TO_DATE });
435
+ dispatch({ type: 'SET_AVAILABLE_UPDATE', payload: null });
436
+ dispatch({ type: 'SET_RESTART_REQUIRED', payload: false });
437
+ }, []);
438
+
439
+ // Get current bundle
440
+ const getCurrentBundle = useCallback(async () => {
441
+ return NativeSwiftPatch.getCurrentBundle();
442
+ }, []);
443
+
444
+ // Stabilize (promote NEW → STABLE)
445
+ const stabilize = useCallback(async (): Promise<SlotMetadata> => {
446
+ const meta = await NativeSwiftPatch.stabilize();
447
+ dispatch({ type: 'SET_SLOT_METADATA', payload: meta });
448
+ return meta;
449
+ }, []);
450
+
451
+ // Switch environment
452
+ const switchEnvironment = useCallback(async (env: EnvironmentMode): Promise<SlotMetadata> => {
453
+ const meta = await NativeSwiftPatch.switchEnvironment(env);
454
+ dispatch({ type: 'SET_ENVIRONMENT', payload: env });
455
+ dispatch({ type: 'SET_SLOT_METADATA', payload: meta });
456
+ // Refresh current bundle info for the new environment
457
+ const bundle = await NativeSwiftPatch.getCurrentBundle();
458
+ dispatch({ type: 'SET_CURRENT_BUNDLE', payload: bundle });
459
+ return meta;
460
+ }, []);
461
+
462
+ // Get slot metadata
463
+ const getSlotMetadata = useCallback(async (): Promise<SlotMetadata> => {
464
+ const meta = await NativeSwiftPatch.getSlotMetadata();
465
+ dispatch({ type: 'SET_SLOT_METADATA', payload: meta });
466
+ return meta;
467
+ }, []);
468
+
469
+ // Mark mounted
470
+ const markMounted = useCallback((): void => {
471
+ NativeSwiftPatch.markMounted().catch(() => {});
472
+ }, []);
473
+
474
+ // Download stage bundle
475
+ const downloadStageBundle = useCallback(async (url: string, hash: string): Promise<void> => {
476
+ await NativeSwiftPatch.downloadStageBundle(url, hash);
477
+ const meta = await NativeSwiftPatch.getSlotMetadata();
478
+ dispatch({ type: 'SET_SLOT_METADATA', payload: meta });
479
+ }, []);
480
+
481
+ const contextValue: SwiftPatchContextValue = {
482
+ ...state,
483
+ checkForUpdate,
484
+ downloadUpdate,
485
+ installUpdate,
486
+ restart,
487
+ rollback,
488
+ clearPendingUpdate,
489
+ getCurrentBundle,
490
+ stabilize,
491
+ switchEnvironment,
492
+ getSlotMetadata,
493
+ markMounted,
494
+ downloadStageBundle,
495
+ };
496
+
497
+ return (
498
+ <SwiftPatchContext.Provider value={contextValue}>
499
+ {children}
500
+ </SwiftPatchContext.Provider>
501
+ );
502
+ }
503
+
504
+ /**
505
+ * Hook to access SwiftPatch context.
506
+ * Must be used within SwiftPatchProvider.
507
+ */
508
+ export function useSwiftPatchContext(): SwiftPatchContextValue {
509
+ const context = useContext(SwiftPatchContext);
510
+ if (!context) {
511
+ throw new Error('useSwiftPatch must be used within a SwiftPatchProvider');
512
+ }
513
+ return context;
514
+ }
@@ -0,0 +1,49 @@
1
+ import { InstallMode } from './types';
2
+ import type { SwiftPatchConfig } from './types';
3
+
4
+ /**
5
+ * Default SDK configuration values
6
+ */
7
+ export const DEFAULT_CONFIG: Required<Omit<SwiftPatchConfig, 'deploymentKey' | 'customHeaders' | 'publicKey' | 'sdkPin'>> & {
8
+ customHeaders: Record<string, string>;
9
+ publicKey: string | null;
10
+ sdkPin: string | null;
11
+ } = {
12
+ serverUrl: 'https://orbitplus.hyperbrainlabs.com/api/v1',
13
+ checkOnResume: true,
14
+ checkInterval: 60_000,
15
+ installMode: InstallMode.ON_NEXT_RESTART,
16
+ mandatoryInstallMode: InstallMode.IMMEDIATE,
17
+ debug: false,
18
+ customHeaders: {},
19
+ publicKey: null,
20
+ autoStabilizeAfterLaunches: 0,
21
+ sdkPin: null,
22
+ };
23
+
24
+ /**
25
+ * Native event names emitted from native modules
26
+ */
27
+ export const NativeEvents = {
28
+ DOWNLOAD_PROGRESS: 'SwiftPatch:downloadProgress',
29
+ INSTALL_COMPLETE: 'SwiftPatch:installComplete',
30
+ ROLLBACK_OCCURRED: 'SwiftPatch:rollbackOccurred',
31
+ ERROR: 'SwiftPatch:error',
32
+ VERSION_CHANGED: 'SwiftPatch:versionChanged',
33
+ NATIVE_EVENT: 'SwiftPatch:event',
34
+ } as const;
35
+
36
+ /**
37
+ * SDK version identifier
38
+ */
39
+ export const SDK_VERSION = '2.0.0';
40
+
41
+ /**
42
+ * Crash detection window in milliseconds
43
+ */
44
+ export const CRASH_DETECTION_WINDOW_MS = 10_000;
45
+
46
+ /**
47
+ * Event polling interval (ms)
48
+ */
49
+ export const EVENT_POLL_INTERVAL_MS = 3_000;