@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,360 @@
1
+ import Foundation
2
+
3
+ /// Represents the three slots for bundle management
4
+ enum SlotState: String {
5
+ case defaultSlot = "DEFAULT_SLOT"
6
+ case stableSlot = "STABLE_SLOT"
7
+ case newSlot = "NEW_SLOT"
8
+ }
9
+
10
+ /// Represents the environment mode
11
+ enum EnvironmentMode: String {
12
+ case production = "PROD"
13
+ case staging = "STAGE"
14
+ }
15
+
16
+ /// Manages the dual-slot architecture for safe OTA deployments.
17
+ /// Supports: DEFAULT_SLOT (app store), STABLE_SLOT (promoted), NEW_SLOT (latest OTA)
18
+ class SlotManager {
19
+
20
+ private let userDefaults: UserDefaults
21
+ private let prefsKey: String
22
+ private let fileManager = FileManager.default
23
+
24
+ private var baseDir: URL {
25
+ let documentsDir = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
26
+ return documentsDir.appendingPathComponent("swiftpatch")
27
+ }
28
+
29
+ private var prodDir: URL { baseDir.appendingPathComponent("prod") }
30
+ private var prodNewDir: URL { prodDir.appendingPathComponent("new") }
31
+ private var prodStableDir: URL { prodDir.appendingPathComponent("stable") }
32
+ private var stageDir: URL { baseDir.appendingPathComponent("stage") }
33
+ private var stageNewDir: URL { stageDir.appendingPathComponent("new") }
34
+
35
+ init(userDefaults: UserDefaults, prefsKey: String) {
36
+ self.userDefaults = userDefaults
37
+ self.prefsKey = prefsKey
38
+ ensureSlotDirectories()
39
+ }
40
+
41
+ // MARK: - Directory Management
42
+
43
+ func ensureSlotDirectories() {
44
+ try? fileManager.createDirectory(at: prodNewDir, withIntermediateDirectories: true)
45
+ try? fileManager.createDirectory(at: prodStableDir, withIntermediateDirectories: true)
46
+ try? fileManager.createDirectory(at: stageNewDir, withIntermediateDirectories: true)
47
+ }
48
+
49
+ // MARK: - Current State
50
+
51
+ /// Get the current environment mode
52
+ var currentEnvironment: EnvironmentMode {
53
+ get {
54
+ let raw = userDefaults.string(forKey: "\(prefsKey)_environment") ?? "PROD"
55
+ return EnvironmentMode(rawValue: raw) ?? .production
56
+ }
57
+ set {
58
+ userDefaults.set(newValue.rawValue, forKey: "\(prefsKey)_environment")
59
+ }
60
+ }
61
+
62
+ /// Get the current production slot
63
+ var currentProdSlot: SlotState {
64
+ get {
65
+ let raw = userDefaults.string(forKey: "\(prefsKey)_prod_current_slot") ?? "DEFAULT_SLOT"
66
+ return SlotState(rawValue: raw) ?? .defaultSlot
67
+ }
68
+ set {
69
+ userDefaults.set(newValue.rawValue, forKey: "\(prefsKey)_prod_current_slot")
70
+ }
71
+ }
72
+
73
+ /// Get/set production slot hashes
74
+ var prodNewHash: String? {
75
+ get { userDefaults.string(forKey: "\(prefsKey)_prod_new_hash") }
76
+ set { userDefaults.set(newValue, forKey: "\(prefsKey)_prod_new_hash") }
77
+ }
78
+
79
+ var prodStableHash: String? {
80
+ get { userDefaults.string(forKey: "\(prefsKey)_prod_stable_hash") }
81
+ set { userDefaults.set(newValue, forKey: "\(prefsKey)_prod_stable_hash") }
82
+ }
83
+
84
+ var prodTempHash: String? {
85
+ get { userDefaults.string(forKey: "\(prefsKey)_prod_temp_hash") }
86
+ set { userDefaults.set(newValue, forKey: "\(prefsKey)_prod_temp_hash") }
87
+ }
88
+
89
+ /// Stage slot hash
90
+ var stageNewHash: String? {
91
+ get { userDefaults.string(forKey: "\(prefsKey)_stage_new_hash") }
92
+ set { userDefaults.set(newValue, forKey: "\(prefsKey)_stage_new_hash") }
93
+ }
94
+
95
+ // MARK: - Bundle Path Resolution
96
+
97
+ /// Get the correct JS bundle URL based on current slot state
98
+ func getBundleURL() -> URL? {
99
+ let env = currentEnvironment
100
+
101
+ if env == .staging {
102
+ // In staging, use stage new slot if available
103
+ if let hash = stageNewHash {
104
+ let path = stageNewDir.appendingPathComponent("\(hash).bundle")
105
+ if fileManager.fileExists(atPath: path.path) {
106
+ return path
107
+ }
108
+ }
109
+ return nil // Fall back to default
110
+ }
111
+
112
+ // Production mode
113
+ switch currentProdSlot {
114
+ case .newSlot:
115
+ if let hash = prodNewHash {
116
+ let path = prodNewDir.appendingPathComponent("\(hash).bundle")
117
+ if fileManager.fileExists(atPath: path.path) {
118
+ return path
119
+ }
120
+ }
121
+ // Fallthrough to stable
122
+ if let hash = prodStableHash {
123
+ let path = prodStableDir.appendingPathComponent("\(hash).bundle")
124
+ if fileManager.fileExists(atPath: path.path) {
125
+ currentProdSlot = .stableSlot
126
+ return path
127
+ }
128
+ }
129
+ currentProdSlot = .defaultSlot
130
+ return nil
131
+
132
+ case .stableSlot:
133
+ if let hash = prodStableHash {
134
+ let path = prodStableDir.appendingPathComponent("\(hash).bundle")
135
+ if fileManager.fileExists(atPath: path.path) {
136
+ return path
137
+ }
138
+ }
139
+ currentProdSlot = .defaultSlot
140
+ return nil
141
+
142
+ case .defaultSlot:
143
+ return nil // Use app store bundle
144
+ }
145
+ }
146
+
147
+ // MARK: - Temp Hash (Deferred Apply)
148
+
149
+ /// Apply temp hash on restart. Called during app launch.
150
+ /// Moves tempHash → newHash and updates slot.
151
+ func applyTempHashOnLaunch() {
152
+ guard let tempHash = prodTempHash else { return }
153
+
154
+ // Move bundle from bundles dir to prod/new dir
155
+ let bundlesDir = baseDir.appendingPathComponent("bundles")
156
+ let sourcePath = bundlesDir.appendingPathComponent("\(tempHash).bundle")
157
+ let destPath = prodNewDir.appendingPathComponent("\(tempHash).bundle")
158
+
159
+ if fileManager.fileExists(atPath: sourcePath.path) {
160
+ try? fileManager.removeItem(at: destPath)
161
+ try? fileManager.moveItem(at: sourcePath, to: destPath)
162
+ }
163
+
164
+ prodNewHash = tempHash
165
+ prodTempHash = nil
166
+ currentProdSlot = .newSlot
167
+
168
+ userDefaults.set(
169
+ ISO8601DateFormatter().string(from: Date()),
170
+ forKey: "\(prefsKey)_current_bundle_installed_at"
171
+ )
172
+ }
173
+
174
+ // MARK: - Stabilization (NEW → STABLE)
175
+
176
+ /// Promote the current NEW slot bundle to STABLE slot.
177
+ /// This creates a known-good fallback point.
178
+ func stabilize() -> Bool {
179
+ guard let newHash = prodNewHash else { return false }
180
+
181
+ let sourcePath = prodNewDir.appendingPathComponent("\(newHash).bundle")
182
+ let destPath = prodStableDir.appendingPathComponent("\(newHash).bundle")
183
+
184
+ guard fileManager.fileExists(atPath: sourcePath.path) else { return false }
185
+
186
+ // Remove old stable bundle
187
+ if let oldStableHash = prodStableHash {
188
+ let oldPath = prodStableDir.appendingPathComponent("\(oldStableHash).bundle")
189
+ try? fileManager.removeItem(at: oldPath)
190
+ }
191
+
192
+ // Copy new bundle to stable slot
193
+ try? fileManager.removeItem(at: destPath)
194
+ do {
195
+ try fileManager.copyItem(at: sourcePath, to: destPath)
196
+ } catch {
197
+ return false
198
+ }
199
+
200
+ prodStableHash = newHash
201
+ prodNewHash = nil
202
+ currentProdSlot = .stableSlot
203
+
204
+ return true
205
+ }
206
+
207
+ // MARK: - Rollback
208
+
209
+ /// Rollback production: NEW → STABLE → DEFAULT
210
+ func rollbackProd(isAutoRollback: Bool) -> String {
211
+ let currentSlot = currentProdSlot
212
+
213
+ switch currentSlot {
214
+ case .newSlot:
215
+ // Clear new slot
216
+ if let hash = prodNewHash {
217
+ let path = prodNewDir.appendingPathComponent("\(hash).bundle")
218
+ try? fileManager.removeItem(at: path)
219
+ }
220
+ prodNewHash = nil
221
+ prodTempHash = nil
222
+
223
+ // Fall to stable if available
224
+ if prodStableHash != nil {
225
+ currentProdSlot = .stableSlot
226
+ return "ROLLED_BACK_TO_STABLE"
227
+ } else {
228
+ currentProdSlot = .defaultSlot
229
+ return "ROLLED_BACK_TO_DEFAULT"
230
+ }
231
+
232
+ case .stableSlot:
233
+ // Clear stable slot
234
+ if let hash = prodStableHash {
235
+ let path = prodStableDir.appendingPathComponent("\(hash).bundle")
236
+ try? fileManager.removeItem(at: path)
237
+ }
238
+ prodStableHash = nil
239
+ currentProdSlot = .defaultSlot
240
+ return "ROLLED_BACK_TO_DEFAULT"
241
+
242
+ case .defaultSlot:
243
+ return "ALREADY_AT_DEFAULT"
244
+ }
245
+ }
246
+
247
+ /// Rollback staging: NEW → DEFAULT
248
+ func rollbackStage() {
249
+ if let hash = stageNewHash {
250
+ let path = stageNewDir.appendingPathComponent("\(hash).bundle")
251
+ try? fileManager.removeItem(at: path)
252
+ }
253
+ stageNewHash = nil
254
+ }
255
+
256
+ // MARK: - Version Change Detection
257
+
258
+ /// Check if app binary version changed (app store update).
259
+ /// If so, wipe all OTA bundles and reset to default.
260
+ func checkVersionChange() -> Bool {
261
+ let currentAppVersion = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? "0.0.0"
262
+ let lastKnownVersion = userDefaults.string(forKey: "\(prefsKey)_last_known_app_version")
263
+
264
+ if let lastVersion = lastKnownVersion, lastVersion != currentAppVersion {
265
+ // App binary was updated via App Store - wipe all OTA bundles
266
+ fallbackToDefault()
267
+ userDefaults.set(currentAppVersion, forKey: "\(prefsKey)_last_known_app_version")
268
+ return true
269
+ }
270
+
271
+ if lastKnownVersion == nil {
272
+ // First launch, just store the version
273
+ userDefaults.set(currentAppVersion, forKey: "\(prefsKey)_last_known_app_version")
274
+ }
275
+
276
+ return false
277
+ }
278
+
279
+ /// Wipe all OTA bundles and reset to default slot
280
+ func fallbackToDefault() {
281
+ // Clear all hashes
282
+ prodNewHash = nil
283
+ prodStableHash = nil
284
+ prodTempHash = nil
285
+ stageNewHash = nil
286
+ currentProdSlot = .defaultSlot
287
+
288
+ // Clear legacy keys
289
+ userDefaults.removeObject(forKey: "\(prefsKey)_current_bundle_hash")
290
+ userDefaults.removeObject(forKey: "\(prefsKey)_current_bundle_path")
291
+ userDefaults.removeObject(forKey: "\(prefsKey)_previous_bundle_hash")
292
+ userDefaults.removeObject(forKey: "\(prefsKey)_previous_bundle_path")
293
+ userDefaults.removeObject(forKey: "\(prefsKey)_pending_bundle_hash")
294
+ userDefaults.removeObject(forKey: "\(prefsKey)_pending_bundle_path")
295
+ userDefaults.removeObject(forKey: "\(prefsKey)_pending_install_confirmation")
296
+ userDefaults.set(0, forKey: "\(prefsKey)_crash_count")
297
+
298
+ // Delete all bundle directories
299
+ try? fileManager.removeItem(at: prodNewDir)
300
+ try? fileManager.removeItem(at: prodStableDir)
301
+ try? fileManager.removeItem(at: stageNewDir)
302
+
303
+ // Recreate
304
+ ensureSlotDirectories()
305
+ }
306
+
307
+ // MARK: - Metadata
308
+
309
+ /// Get full slot metadata as dictionary (for JS bridge)
310
+ func getMetadata() -> [String: Any] {
311
+ return [
312
+ "environment": currentEnvironment.rawValue,
313
+ "prod": [
314
+ "currentSlot": currentProdSlot.rawValue,
315
+ "newHash": prodNewHash as Any,
316
+ "stableHash": prodStableHash as Any,
317
+ "tempHash": prodTempHash as Any,
318
+ ],
319
+ "stage": [
320
+ "newHash": stageNewHash as Any,
321
+ ]
322
+ ]
323
+ }
324
+
325
+ // MARK: - Cleanup
326
+
327
+ /// Clean up unused bundles from all slots
328
+ func cleanup() {
329
+ let keepHashes = Set([prodNewHash, prodStableHash, stageNewHash].compactMap { $0 })
330
+
331
+ // Clean prod/new
332
+ cleanDirectory(prodNewDir, keeping: keepHashes)
333
+ // Clean prod/stable
334
+ cleanDirectory(prodStableDir, keeping: keepHashes)
335
+ // Clean stage/new
336
+ cleanDirectory(stageNewDir, keeping: keepHashes)
337
+
338
+ // Clean legacy bundles dir
339
+ let bundlesDir = baseDir.appendingPathComponent("bundles")
340
+ cleanDirectory(bundlesDir, keeping: keepHashes)
341
+
342
+ // Clean temp
343
+ let tempDir = baseDir.appendingPathComponent("temp")
344
+ if let files = try? fileManager.contentsOfDirectory(at: tempDir, includingPropertiesForKeys: nil) {
345
+ for file in files {
346
+ try? fileManager.removeItem(at: file)
347
+ }
348
+ }
349
+ }
350
+
351
+ private func cleanDirectory(_ dir: URL, keeping keepHashes: Set<String>) {
352
+ guard let files = try? fileManager.contentsOfDirectory(at: dir, includingPropertiesForKeys: nil) else { return }
353
+ for file in files {
354
+ let fileHash = file.deletingPathExtension().lastPathComponent
355
+ if !keepHashes.contains(fileHash) {
356
+ try? fileManager.removeItem(at: file)
357
+ }
358
+ }
359
+ }
360
+ }
@@ -0,0 +1,56 @@
1
+ #import <React/RCTBridgeModule.h>
2
+ #import <React/RCTEventEmitter.h>
3
+
4
+ @interface RCT_EXTERN_MODULE(SwiftPatch, RCTEventEmitter)
5
+
6
+ RCT_EXTERN_METHOD(initialize:(NSDictionary *)config
7
+ resolver:(RCTPromiseResolveBlock)resolve
8
+ rejecter:(RCTPromiseRejectBlock)reject)
9
+
10
+ RCT_EXTERN_METHOD(setDebugMode:(BOOL)enabled)
11
+
12
+ RCT_EXTERN_METHOD(getCurrentBundle:(RCTPromiseResolveBlock)resolve
13
+ rejecter:(RCTPromiseRejectBlock)reject)
14
+
15
+ RCT_EXTERN_METHOD(getDeviceId:(RCTPromiseResolveBlock)resolve
16
+ rejecter:(RCTPromiseRejectBlock)reject)
17
+
18
+ RCT_EXTERN_METHOD(getAppVersion:(RCTPromiseResolveBlock)resolve
19
+ rejecter:(RCTPromiseRejectBlock)reject)
20
+
21
+ RCT_EXTERN_METHOD(downloadUpdate:(NSString *)url
22
+ expectedHash:(NSString *)expectedHash
23
+ isPatch:(BOOL)isPatch
24
+ signature:(NSString *)signature
25
+ resolver:(RCTPromiseResolveBlock)resolve
26
+ rejecter:(RCTPromiseRejectBlock)reject)
27
+
28
+ RCT_EXTERN_METHOD(installUpdate:(NSString *)bundleHash
29
+ resolver:(RCTPromiseResolveBlock)resolve
30
+ rejecter:(RCTPromiseRejectBlock)reject)
31
+
32
+ RCT_EXTERN_METHOD(hasPendingInstall:(RCTPromiseResolveBlock)resolve
33
+ rejecter:(RCTPromiseRejectBlock)reject)
34
+
35
+ RCT_EXTERN_METHOD(confirmInstall:(RCTPromiseResolveBlock)resolve
36
+ rejecter:(RCTPromiseRejectBlock)reject)
37
+
38
+ RCT_EXTERN_METHOD(restart)
39
+
40
+ RCT_EXTERN_METHOD(rollback:(RCTPromiseResolveBlock)resolve
41
+ rejecter:(RCTPromiseRejectBlock)reject)
42
+
43
+ RCT_EXTERN_METHOD(clearPendingUpdate:(RCTPromiseResolveBlock)resolve
44
+ rejecter:(RCTPromiseRejectBlock)reject)
45
+
46
+ RCT_EXTERN_METHOD(reportStatus:(NSString *)releaseId
47
+ status:(NSString *)status
48
+ resolver:(RCTPromiseResolveBlock)resolve
49
+ rejecter:(RCTPromiseRejectBlock)reject)
50
+
51
+ + (BOOL)requiresMainQueueSetup
52
+ {
53
+ return YES;
54
+ }
55
+
56
+ @end