bleam 0.0.8 → 0.0.10

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 (96) hide show
  1. package/dist/cli.cjs +269 -22
  2. package/dist/cli.d.cts +32 -2
  3. package/dist/cli.d.ts +32 -2
  4. package/dist/cli.js +270 -23
  5. package/dist/platform.d.cts +2 -0
  6. package/dist/platform.d.ts +2 -0
  7. package/dist/{ui-1WepaMS4.d.cts → ui-Bg11tvlc.d.ts} +1 -1
  8. package/dist/{ui-Ce-pnUUA.d.ts → ui-Dd7SXdbg.d.cts} +7 -7
  9. package/dist/ui.d.cts +1 -1
  10. package/dist/ui.d.ts +1 -1
  11. package/dist/window.d.cts +1 -1
  12. package/dist/window.d.ts +1 -1
  13. package/package.json +5 -1
  14. package/templates/image-generation/app/index.tsx +1 -1
  15. package/templates/native/.gitattributes +1 -0
  16. package/templates/native/App.tsx +26 -0
  17. package/templates/native/app.json +12 -0
  18. package/templates/native/index.ts +8 -0
  19. package/templates/native/ios/.xcode.env +11 -0
  20. package/templates/native/ios/Bleam/AI/Flux2/Configuration/Flux2Config.swift +288 -0
  21. package/templates/native/ios/Bleam/AI/Flux2/Configuration/MemoryConfig.swift +233 -0
  22. package/templates/native/ios/Bleam/AI/Flux2/Configuration/MemoryOptimizationConfig.swift +157 -0
  23. package/templates/native/ios/Bleam/AI/Flux2/Configuration/ModelRegistry.swift +425 -0
  24. package/templates/native/ios/Bleam/AI/Flux2/Configuration/QuantizationConfig.swift +138 -0
  25. package/templates/native/ios/Bleam/AI/Flux2/Configuration/TransformerRepoProfile.swift +59 -0
  26. package/templates/native/ios/Bleam/AI/Flux2/Configuration/VAEConfig.swift +134 -0
  27. package/templates/native/ios/Bleam/AI/Flux2/Flux2Core.swift +55 -0
  28. package/templates/native/ios/Bleam/AI/Flux2/Loading/WeightLoader.swift +1192 -0
  29. package/templates/native/ios/Bleam/AI/Flux2/Pipeline/FluxImageRunner.swift +1282 -0
  30. package/templates/native/ios/Bleam/AI/Flux2/Pipeline/LatentUtils.swift +191 -0
  31. package/templates/native/ios/Bleam/AI/Flux2/Scheduler/FlowMatchEulerScheduler.swift +279 -0
  32. package/templates/native/ios/Bleam/AI/Flux2/Text/KleinTextEncoder.swift +380 -0
  33. package/templates/native/ios/Bleam/AI/Flux2/Transformer/Flux2Attention.swift +379 -0
  34. package/templates/native/ios/Bleam/AI/Flux2/Transformer/Flux2Embeddings.swift +159 -0
  35. package/templates/native/ios/Bleam/AI/Flux2/Transformer/Flux2FeedForward.swift +154 -0
  36. package/templates/native/ios/Bleam/AI/Flux2/Transformer/Flux2FusedKernels.swift +198 -0
  37. package/templates/native/ios/Bleam/AI/Flux2/Transformer/Flux2KVCache.swift +36 -0
  38. package/templates/native/ios/Bleam/AI/Flux2/Transformer/Flux2Modulation.swift +193 -0
  39. package/templates/native/ios/Bleam/AI/Flux2/Transformer/Flux2ParallelAttention.swift +389 -0
  40. package/templates/native/ios/Bleam/AI/Flux2/Transformer/Flux2RoPE.swift +304 -0
  41. package/templates/native/ios/Bleam/AI/Flux2/Transformer/Flux2SingleBlock.swift +290 -0
  42. package/templates/native/ios/Bleam/AI/Flux2/Transformer/Flux2Transformer.swift +530 -0
  43. package/templates/native/ios/Bleam/AI/Flux2/Transformer/Flux2TransformerBlock.swift +362 -0
  44. package/templates/native/ios/Bleam/AI/Flux2/Utils/Flux2Debug.swift +101 -0
  45. package/templates/native/ios/Bleam/AI/Flux2/Utils/MLXCheckpoint.swift +118 -0
  46. package/templates/native/ios/Bleam/AI/Flux2/Utils/MemoryManager.swift +201 -0
  47. package/templates/native/ios/Bleam/AI/Flux2/VAE/AutoencoderKL.swift +476 -0
  48. package/templates/native/ios/Bleam/AI/Flux2/VAE/ResnetBlock.swift +316 -0
  49. package/templates/native/ios/Bleam/AI/Flux2/VAE/VAEDecoder.swift +120 -0
  50. package/templates/native/ios/Bleam/AI/Flux2/VAE/VAEEncoder.swift +136 -0
  51. package/templates/native/ios/Bleam/AppDelegate.swift +482 -0
  52. package/templates/native/ios/Bleam/Appearance.swift +62 -0
  53. package/templates/native/ios/Bleam/Bridging-Header.h +3 -0
  54. package/templates/native/ios/Bleam/Images.xcassets/AppIcon.appiconset/Contents.json +13 -0
  55. package/templates/native/ios/Bleam/Images.xcassets/Contents.json +6 -0
  56. package/templates/native/ios/Bleam/Info.plist +76 -0
  57. package/templates/native/ios/Bleam/PrivacyInfo.xcprivacy +48 -0
  58. package/templates/native/ios/Bleam/SceneDelegate.swift +58 -0
  59. package/templates/native/ios/Bleam/SplashScreen.storyboard +47 -0
  60. package/templates/native/ios/Bleam/Supporting/Expo.plist +6 -0
  61. package/templates/native/ios/Bleam/bleam.entitlements +10 -0
  62. package/templates/native/ios/Bleam.xcodeproj/project.pbxproj +1147 -0
  63. package/templates/native/ios/Bleam.xcodeproj/xcshareddata/xcschemes/Bleam.xcscheme +88 -0
  64. package/templates/native/ios/GenerationService/GenerationService.entitlements +10 -0
  65. package/templates/native/ios/GenerationService/ImageGenerationRunner.swift +591 -0
  66. package/templates/native/ios/GenerationService/Info.plist +31 -0
  67. package/templates/native/ios/GenerationService/main.swift +165 -0
  68. package/templates/native/ios/PlatformHelper/main.swift +726 -0
  69. package/templates/native/ios/Podfile +198 -0
  70. package/templates/native/ios/Podfile.lock +2284 -0
  71. package/templates/native/ios/Podfile.properties.json +5 -0
  72. package/templates/native/ios/Shared/Generation/GenerationServiceProtocol.swift +12 -0
  73. package/templates/native/ios/Shared/Generation/GenerationWorkerProtocol.swift +124 -0
  74. package/templates/native/metro.config.js +6 -0
  75. package/templates/native/modules/bleam-runtime/BleamRuntime.podspec +14 -0
  76. package/templates/native/modules/bleam-runtime/expo-module.config.json +8 -0
  77. package/templates/native/modules/bleam-runtime/ios/AIModule.swift +1258 -0
  78. package/templates/native/modules/bleam-runtime/ios/GenerationContracts.swift +135 -0
  79. package/templates/native/modules/bleam-runtime/ios/PlatformModule.swift +329 -0
  80. package/templates/native/modules/bleam-runtime/package.json +5 -0
  81. package/templates/native/package.json +39 -0
  82. package/templates/native/scripts/build/mlx-frameworks.ts +421 -0
  83. package/templates/native/scripts/start/index.ts +474 -0
  84. package/templates/native/scripts/start/known-failures.ts +20 -0
  85. package/templates/native/scripts/start/loader.ts +76 -0
  86. package/templates/native/scripts/start/native-state.ts +242 -0
  87. package/templates/native/scripts/start/package.json +4 -0
  88. package/templates/native/scripts/start/swift-packages.ts +414 -0
  89. package/templates/native/scripts/start/xcode-formatter.ts +106 -0
  90. package/templates/native/tsconfig.json +14 -0
  91. package/templates/native/yarn.lock +3749 -0
  92. package/templates/updates/README.md +272 -0
  93. package/templates/updates/src/index.ts +385 -0
  94. package/templates/updates/src/schema.ts +385 -0
  95. package/templates/updates/tsconfig.json +12 -0
  96. package/templates/updates/wrangler.jsonc +19 -0
@@ -0,0 +1,482 @@
1
+ internal import Expo
2
+ import React
3
+ import ReactAppDependencyProvider
4
+ import UIKit
5
+ import CryptoKit
6
+ import Darwin
7
+ import Security
8
+
9
+ func launchArgument(_ name: String) -> String? {
10
+ let arguments = ProcessInfo.processInfo.arguments
11
+ guard let flagIndex = arguments.firstIndex(of: name) else {
12
+ return nil
13
+ }
14
+
15
+ let valueIndex = arguments.index(after: flagIndex)
16
+ guard arguments.indices.contains(valueIndex) else {
17
+ return nil
18
+ }
19
+
20
+ return arguments[valueIndex]
21
+ }
22
+
23
+ @main
24
+ class AppDelegate: ExpoAppDelegate {
25
+ public override func application(
26
+ _ application: UIApplication,
27
+ didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil
28
+ ) -> Bool {
29
+ let launched = super.application(application, didFinishLaunchingWithOptions: launchOptions)
30
+ if launched {
31
+ PlatformHelperInstaller.refreshIfInstalledApp()
32
+ }
33
+ return launched
34
+ }
35
+
36
+ public func application(
37
+ _ application: UIApplication,
38
+ configurationForConnecting connectingSceneSession: UISceneSession,
39
+ options: UIScene.ConnectionOptions
40
+ ) -> UISceneConfiguration {
41
+ let configuration = UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role)
42
+ configuration.delegateClass = SceneDelegate.self
43
+ return configuration
44
+ }
45
+
46
+ // Linking API
47
+ public override func application(
48
+ _ app: UIApplication,
49
+ open url: URL,
50
+ options: [UIApplication.OpenURLOptionsKey: Any] = [:]
51
+ ) -> Bool {
52
+ return super.application(app, open: url, options: options) || RCTLinkingManager.application(app, open: url, options: options)
53
+ }
54
+
55
+ // Universal Links
56
+ public override func application(
57
+ _ application: UIApplication,
58
+ continue userActivity: NSUserActivity,
59
+ restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void
60
+ ) -> Bool {
61
+ let result = RCTLinkingManager.application(application, continue: userActivity, restorationHandler: restorationHandler)
62
+ return super.application(application, continue: userActivity, restorationHandler: restorationHandler) || result
63
+ }
64
+ }
65
+
66
+ private enum PlatformHelperInstaller {
67
+ static func refreshIfInstalledApp() {
68
+ #if targetEnvironment(macCatalyst)
69
+ let homeDirectory = URL(fileURLWithPath: NSHomeDirectory(), isDirectory: true)
70
+ let homeApplications = homeDirectory
71
+ .appendingPathComponent("Applications", isDirectory: true)
72
+ .standardizedFileURL.path + "/"
73
+ guard Bundle.main.bundleURL.standardizedFileURL.path.hasPrefix(homeApplications) else {
74
+ return
75
+ }
76
+ guard let bundleIdentifier = Bundle.main.bundleIdentifier else { return }
77
+
78
+ let serviceName = "PlatformHelper"
79
+ let bundledHelper = Bundle.main.bundleURL
80
+ .appendingPathComponent("Contents/Helpers/\(serviceName)")
81
+ guard FileManager.default.isExecutableFile(atPath: bundledHelper.path) else {
82
+ NSLog("Bleam helper unavailable: bundled executable is missing")
83
+ return
84
+ }
85
+ guard
86
+ let teamIdentifier = Bundle.main.object(forInfoDictionaryKey: "BleamTeamIdentifier") as? String,
87
+ let platformVersion = Bundle.main.object(forInfoDictionaryKey: "BleamRuntimeVersion") as? String,
88
+ validSignature(at: bundledHelper, teamIdentifier: teamIdentifier)
89
+ else {
90
+ NSLog("Bleam helper unavailable: bundled executable has an invalid identity")
91
+ return
92
+ }
93
+
94
+ let label = "\(bundleIdentifier).PlatformHelper"
95
+ let domain = "gui/\(getuid())"
96
+ let helperRoot = homeDirectory
97
+ .appendingPathComponent("Library/Application Support/Bleam/Helpers", isDirectory: true)
98
+ .appendingPathComponent(bundleIdentifier, isDirectory: true)
99
+ let installedHelper = helperRoot.appendingPathComponent(serviceName)
100
+ let stagedHelper = helperRoot.appendingPathComponent(".\(serviceName).\(UUID().uuidString)")
101
+ let backupHelper = helperRoot.appendingPathComponent(".\(serviceName).previous.\(UUID().uuidString)")
102
+ let failedHelper = helperRoot.appendingPathComponent(".\(serviceName).failed.\(UUID().uuidString)")
103
+ let pendingApply = homeDirectory
104
+ .appendingPathComponent("Library/Application Support/Bleam/Updates", isDirectory: true)
105
+ .appendingPathComponent(bundleIdentifier, isDirectory: true)
106
+ .appendingPathComponent("platform-apply.pending")
107
+ let launchAgents = homeDirectory
108
+ .appendingPathComponent("Library/LaunchAgents", isDirectory: true)
109
+ let plistURL = launchAgents.appendingPathComponent("\(label).plist")
110
+ let propertyList: [String: Any] = [
111
+ "Label": label,
112
+ "ProgramArguments": [installedHelper.path],
113
+ "ProcessType": "Background",
114
+ "MachServices": [label: true],
115
+ "KeepAlive": [
116
+ "SuccessfulExit": false,
117
+ "PathState": [pendingApply.path: true],
118
+ ],
119
+ "EnvironmentVariables": [
120
+ "XPC_SERVICE_NAME": label,
121
+ "BLEAM_BUNDLE_IDENTIFIER": bundleIdentifier,
122
+ "BLEAM_TEAM_IDENTIFIER": teamIdentifier,
123
+ "BLEAM_HELPER_VERSION": platformVersion,
124
+ "BLEAM_PLATFORM_VERSION": platformVersion,
125
+ ],
126
+ ]
127
+
128
+ do {
129
+ defer { try? FileManager.default.removeItem(at: stagedHelper) }
130
+ try FileManager.default.createDirectory(
131
+ at: helperRoot,
132
+ withIntermediateDirectories: true
133
+ )
134
+ try FileManager.default.copyItem(at: bundledHelper, to: stagedHelper)
135
+ guard
136
+ FileManager.default.isExecutableFile(atPath: stagedHelper.path),
137
+ validSignature(at: stagedHelper, teamIdentifier: teamIdentifier)
138
+ else {
139
+ throw HelperInstallError.invalidSignature
140
+ }
141
+ try FileManager.default.createDirectory(
142
+ at: launchAgents,
143
+ withIntermediateDirectories: true
144
+ )
145
+ let data = try PropertyListSerialization.data(
146
+ fromPropertyList: propertyList,
147
+ format: .xml,
148
+ options: 0
149
+ )
150
+ let previousPlist = try? Data(contentsOf: plistURL)
151
+ let wasRegistered = runLaunchctl(["print", "\(domain)/\(label)"]) == 0
152
+ if wasRegistered, runLaunchctl(["bootout", "\(domain)/\(label)"]) != 0 {
153
+ throw HelperInstallError.registrationFailed
154
+ }
155
+
156
+ do {
157
+ if FileManager.default.fileExists(atPath: installedHelper.path),
158
+ rename(installedHelper.path, backupHelper.path) != 0
159
+ {
160
+ throw HelperInstallError.replacementFailed
161
+ }
162
+ guard rename(stagedHelper.path, installedHelper.path) == 0 else {
163
+ throw HelperInstallError.replacementFailed
164
+ }
165
+ try data.write(to: plistURL, options: .atomic)
166
+ let bootstrapStatus = runLaunchctl(["bootstrap", domain, plistURL.path])
167
+ guard
168
+ bootstrapStatus == 0 || runLaunchctl(["print", "\(domain)/\(label)"]) == 0
169
+ else {
170
+ throw HelperInstallError.registrationFailed
171
+ }
172
+ try? FileManager.default.removeItem(at: backupHelper)
173
+ try? FileManager.default.removeItem(
174
+ at: helperRoot.appendingPathComponent("PlatformHelper.xpc", isDirectory: true)
175
+ )
176
+ } catch let installError {
177
+ do {
178
+ let newJobRegistered = runLaunchctl(["print", "\(domain)/\(label)"]) == 0
179
+ if newJobRegistered,
180
+ runLaunchctl(["bootout", "\(domain)/\(label)"]) != 0
181
+ {
182
+ throw HelperInstallError.rollbackFailed
183
+ }
184
+ if FileManager.default.fileExists(atPath: installedHelper.path),
185
+ rename(installedHelper.path, failedHelper.path) != 0
186
+ {
187
+ throw HelperInstallError.rollbackFailed
188
+ }
189
+ if FileManager.default.fileExists(atPath: backupHelper.path),
190
+ rename(backupHelper.path, installedHelper.path) != 0
191
+ {
192
+ throw HelperInstallError.rollbackFailed
193
+ }
194
+ if let previousPlist {
195
+ try previousPlist.write(to: plistURL, options: .atomic)
196
+ if wasRegistered,
197
+ runLaunchctl(["bootstrap", domain, plistURL.path]) != 0
198
+ {
199
+ throw HelperInstallError.rollbackFailed
200
+ }
201
+ } else {
202
+ try? FileManager.default.removeItem(at: plistURL)
203
+ }
204
+ try? FileManager.default.removeItem(at: failedHelper)
205
+ } catch {
206
+ NSLog("Bleam helper rollback failed: %@", error.localizedDescription)
207
+ throw error
208
+ }
209
+ throw installError
210
+ }
211
+ } catch {
212
+ NSLog("Bleam helper install failed: %@", error.localizedDescription)
213
+ return
214
+ }
215
+ #endif
216
+ }
217
+
218
+ private static func validSignature(at url: URL, teamIdentifier: String) -> Bool {
219
+ var staticCode: SecStaticCode?
220
+ guard
221
+ SecStaticCodeCreateWithPath(url as CFURL, [], &staticCode) == errSecSuccess,
222
+ let staticCode,
223
+ SecStaticCodeCheckValidity(
224
+ staticCode,
225
+ SecCSFlags(rawValue: kSecCSStrictValidate),
226
+ nil
227
+ ) == errSecSuccess
228
+ else { return false }
229
+
230
+ var signingInfo: CFDictionary?
231
+ guard
232
+ SecCodeCopySigningInformation(
233
+ staticCode,
234
+ SecCSFlags(rawValue: kSecCSSigningInformation),
235
+ &signingInfo
236
+ ) == errSecSuccess
237
+ else { return false }
238
+ return (signingInfo as? [String: Any])?[kSecCodeInfoTeamIdentifier as String] as? String == teamIdentifier
239
+ }
240
+
241
+ private static func runLaunchctl(_ arguments: [String]) -> Int32 {
242
+ let values = ["launchctl"] + arguments
243
+ var argv = values.map { strdup($0) }
244
+ argv.append(nil)
245
+ defer { argv.compactMap { $0 }.forEach { free($0) } }
246
+
247
+ var pid: pid_t = 0
248
+ let result = posix_spawn(&pid, "/bin/launchctl", nil, nil, &argv, environ)
249
+ guard result == 0 else { return Int32(result) }
250
+ var status: Int32 = 0
251
+ guard waitpid(pid, &status, 0) == pid else { return -1 }
252
+ return status
253
+ }
254
+ }
255
+
256
+ private enum HelperInstallError: Error {
257
+ case invalidSignature, registrationFailed, replacementFailed, rollbackFailed
258
+ }
259
+
260
+ class ReactNativeDelegate: ExpoReactNativeFactoryDelegate {
261
+ private let bundleOverrideURL: URL?
262
+
263
+ init(bundleURL: URL? = nil) {
264
+ bundleOverrideURL = bundleURL
265
+ super.init()
266
+ }
267
+
268
+ // Extension point for config-plugins
269
+
270
+ override func sourceURL(for bridge: RCTBridge) -> URL? {
271
+ // needed to return the correct URL for expo-dev-client.
272
+ bridge.bundleURL ?? bundleURL()
273
+ }
274
+
275
+ override func customize(_ rootView: UIView) {
276
+ super.customize(rootView)
277
+ applyRootAppearance(to: rootView)
278
+ }
279
+
280
+ override func bundleURL() -> URL? {
281
+ if let bundleOverrideURL {
282
+ return bundleOverrideURL
283
+ }
284
+
285
+ if let bundleURL = launchArgument("--bleam-bundle-url") {
286
+ return URL(string: bundleURL)
287
+ }
288
+
289
+ #if DEBUG
290
+ return RCTBundleURLProvider.sharedSettings().jsBundleURL(forBundleRoot: ".expo/.virtual-metro-entry")
291
+ #else
292
+ return OTAUpdateManager.shared.launchBundleURL()
293
+ #endif
294
+ }
295
+ }
296
+
297
+ private struct OTAArtifact: Decodable {
298
+ let url: URL
299
+ let sizeBytes: Int
300
+ let sha256: String
301
+ }
302
+
303
+ private struct OTARelease: Decodable {
304
+ let schemaVersion: Int
305
+ let kind: String
306
+ let releaseId: String
307
+ let bundleIdentifier: String
308
+ let teamIdentifier: String
309
+ let runtimeVersion: String
310
+ let artifact: OTAArtifact
311
+ }
312
+
313
+ final class OTAUpdateManager {
314
+ static let shared = OTAUpdateManager()
315
+
316
+ private let fileManager = FileManager.default
317
+ private let defaults = UserDefaults.standard
318
+ private let activeKey = "BleamOTAActiveRelease"
319
+ private let attemptedKey = "BleamOTAAttemptedRelease"
320
+ private let rejectedKey = "BleamOTARejectedRelease"
321
+ private var launchingRelease: String?
322
+
323
+ private var cacheRoot: URL {
324
+ let caches = fileManager.urls(for: .cachesDirectory, in: .userDomainMask)[0]
325
+ let bundleIdentifier = Bundle.main.bundleIdentifier ?? "app"
326
+ return caches.appendingPathComponent("Bleam/OTA/\(bundleIdentifier)", isDirectory: true)
327
+ }
328
+
329
+ private init() {
330
+ for name in ["RCTJavaScriptDidLoadNotification", "RCTContentDidAppearNotification"] {
331
+ NotificationCenter.default.addObserver(
332
+ forName: NSNotification.Name(rawValue: name),
333
+ object: nil,
334
+ queue: .main
335
+ ) { [weak self] _ in
336
+ self?.markLaunchSuccessful()
337
+ }
338
+ }
339
+ }
340
+
341
+ func launchBundleURL() -> URL? {
342
+ guard let releaseId = defaults.string(forKey: activeKey) else {
343
+ return embeddedBundleURL()
344
+ }
345
+
346
+ if defaults.string(forKey: attemptedKey) == releaseId {
347
+ defaults.set(releaseId, forKey: rejectedKey)
348
+ defaults.removeObject(forKey: activeKey)
349
+ defaults.removeObject(forKey: attemptedKey)
350
+ return embeddedBundleURL()
351
+ }
352
+
353
+ let bundle = cacheRoot
354
+ .appendingPathComponent(releaseId, isDirectory: true)
355
+ .appendingPathComponent("bundle/bundle.js")
356
+ guard fileManager.fileExists(atPath: bundle.path) else {
357
+ defaults.removeObject(forKey: activeKey)
358
+ return embeddedBundleURL()
359
+ }
360
+
361
+ launchingRelease = releaseId
362
+ defaults.set(releaseId, forKey: attemptedKey)
363
+ return bundle
364
+ }
365
+
366
+ func checkForUpdate() {
367
+ guard
368
+ let baseUrlString = Bundle.main.object(forInfoDictionaryKey: "BleamUpdateServiceURL") as? String,
369
+ let baseUrl = URL(string: baseUrlString),
370
+ let bundleIdentifier = Bundle.main.bundleIdentifier,
371
+ let runtimeVersion = Bundle.main.object(forInfoDictionaryKey: "BleamRuntimeVersion") as? String
372
+ else {
373
+ NSLog("Bleam OTA disabled: missing update URL, bundle identifier, or runtime version")
374
+ return
375
+ }
376
+
377
+ let path = "/v1/apps/\(bundleIdentifier)/ota/\(runtimeVersion)"
378
+ guard let url = URL(string: path, relativeTo: baseUrl)?.absoluteURL else {
379
+ NSLog("Bleam OTA disabled: invalid resolver URL")
380
+ return
381
+ }
382
+
383
+ NSLog("Bleam OTA checking %@", url.absoluteString)
384
+ URLSession.shared.dataTask(with: url) { [weak self] data, response, error in
385
+ if let error {
386
+ NSLog("Bleam OTA resolver failed: %@", error.localizedDescription)
387
+ }
388
+ guard
389
+ let self,
390
+ let response = response as? HTTPURLResponse,
391
+ response.statusCode == 200,
392
+ let data,
393
+ let release = try? JSONDecoder().decode(OTARelease.self, from: data)
394
+ else {
395
+ let status = (response as? HTTPURLResponse)?.statusCode ?? 0
396
+ NSLog("Bleam OTA resolver returned an unusable response: %d", status)
397
+ return
398
+ }
399
+ self.download(release)
400
+ }.resume()
401
+ }
402
+
403
+ private func download(_ release: OTARelease) {
404
+ guard
405
+ release.schemaVersion == 1,
406
+ release.kind == "ota",
407
+ release.bundleIdentifier == Bundle.main.bundleIdentifier,
408
+ release.runtimeVersion == Bundle.main.object(forInfoDictionaryKey: "BleamRuntimeVersion") as? String,
409
+ release.teamIdentifier == Bundle.main.object(forInfoDictionaryKey: "BleamTeamIdentifier") as? String,
410
+ release.releaseId != defaults.string(forKey: activeKey),
411
+ release.releaseId != defaults.string(forKey: rejectedKey)
412
+ else {
413
+ NSLog("Bleam OTA rejected release %@", release.releaseId)
414
+ return
415
+ }
416
+
417
+ NSLog("Bleam OTA downloading %@", release.releaseId)
418
+ URLSession.shared.downloadTask(with: release.artifact.url) { [weak self] temporaryUrl, _, error in
419
+ if let error {
420
+ NSLog("Bleam OTA download failed: %@", error.localizedDescription)
421
+ }
422
+ guard let self, let temporaryUrl else { return }
423
+ guard
424
+ let attributes = try? self.fileManager.attributesOfItem(atPath: temporaryUrl.path),
425
+ attributes[.size] as? Int == release.artifact.sizeBytes,
426
+ let data = try? Data(contentsOf: temporaryUrl),
427
+ SHA256.hash(data: data).map({ String(format: "%02x", $0) }).joined() == release.artifact.sha256
428
+ else {
429
+ NSLog("Bleam OTA artifact verification failed for %@", release.releaseId)
430
+ return
431
+ }
432
+
433
+ let destination = self.cacheRoot.appendingPathComponent(release.releaseId, isDirectory: true)
434
+ try? self.fileManager.removeItem(at: destination)
435
+ do {
436
+ try self.fileManager.createDirectory(at: destination, withIntermediateDirectories: true)
437
+ try self.extract(temporaryUrl, to: destination)
438
+ guard self.fileManager.fileExists(
439
+ atPath: destination.appendingPathComponent("bundle/bundle.js").path
440
+ ) else {
441
+ try? self.fileManager.removeItem(at: destination)
442
+ return
443
+ }
444
+ self.defaults.removeObject(forKey: self.attemptedKey)
445
+ self.defaults.set(release.releaseId, forKey: self.activeKey)
446
+ NSLog("Bleam OTA staged %@ for next launch", release.releaseId)
447
+ } catch {
448
+ NSLog("Bleam OTA extraction failed: %@", error.localizedDescription)
449
+ try? self.fileManager.removeItem(at: destination)
450
+ }
451
+ }.resume()
452
+ }
453
+
454
+ private func extract(_ archive: URL, to destination: URL) throws {
455
+ let arguments = ["ditto", "-x", "-k", archive.path, destination.path]
456
+ var argv = arguments.map { strdup($0) }
457
+ argv.append(nil)
458
+ defer { argv.compactMap { $0 }.forEach { free($0) } }
459
+
460
+ var pid: pid_t = 0
461
+ let spawnResult = posix_spawn(&pid, "/usr/bin/ditto", nil, nil, &argv, environ)
462
+ guard spawnResult == 0 else {
463
+ throw NSError(domain: NSPOSIXErrorDomain, code: Int(spawnResult))
464
+ }
465
+ var status: Int32 = 0
466
+ guard waitpid(pid, &status, 0) == pid, status == 0 else {
467
+ throw NSError(domain: NSPOSIXErrorDomain, code: Int(status))
468
+ }
469
+ }
470
+
471
+ private func markLaunchSuccessful() {
472
+ guard let releaseId = launchingRelease else { return }
473
+ defaults.removeObject(forKey: attemptedKey)
474
+ launchingRelease = nil
475
+ let releaseRoot = cacheRoot.appendingPathComponent(releaseId, isDirectory: true)
476
+ try? fileManager.createDirectory(at: releaseRoot, withIntermediateDirectories: true)
477
+ }
478
+
479
+ private func embeddedBundleURL() -> URL? {
480
+ Bundle.main.url(forResource: "main", withExtension: "jsbundle")
481
+ }
482
+ }
@@ -0,0 +1,62 @@
1
+ import UIKit
2
+
3
+ struct Appearance {
4
+ let windowBackgroundColor: UIColor?
5
+ let userInterfaceStyle: UIUserInterfaceStyle
6
+
7
+ static let `default` = Appearance(
8
+ windowBackgroundColor: nil,
9
+ userInterfaceStyle: .unspecified
10
+ )
11
+ }
12
+
13
+ func parseColor(from hex: String?) -> UIColor? {
14
+ guard let hex else {
15
+ return nil
16
+ }
17
+
18
+ let value = hex.trimmingCharacters(in: .whitespacesAndNewlines).replacingOccurrences(of: "#", with: "")
19
+ guard value.count == 6, let number = Int(value, radix: 16) else {
20
+ return nil
21
+ }
22
+
23
+ return UIColor(
24
+ red: CGFloat((number >> 16) & 0xff) / 255,
25
+ green: CGFloat((number >> 8) & 0xff) / 255,
26
+ blue: CGFloat(number & 0xff) / 255,
27
+ alpha: 1
28
+ )
29
+ }
30
+
31
+ func parseAppearance(from value: Any?) -> Appearance {
32
+ guard let object = value as? [String: Any] else {
33
+ return .default
34
+ }
35
+
36
+ let window = object["window"] as? [String: Any]
37
+ let style = object["userInterfaceStyle"] as? String
38
+
39
+ return Appearance(
40
+ windowBackgroundColor: parseColor(from: window?["backgroundColor"] as? String),
41
+ userInterfaceStyle: style == "light" ? .light : style == "dark" ? .dark : .unspecified
42
+ )
43
+ }
44
+
45
+ func projectAppearance() -> Appearance {
46
+ guard let configPath = launchArgument("--bleam-project-config") else {
47
+ return .default
48
+ }
49
+
50
+ do {
51
+ let data = try Data(contentsOf: URL(fileURLWithPath: configPath))
52
+ let value = try JSONSerialization.jsonObject(with: data) as? [String: Any]
53
+ return parseAppearance(from: value?["appearance"])
54
+ } catch {
55
+ return .default
56
+ }
57
+ }
58
+
59
+ func applyRootAppearance(to rootView: UIView) {
60
+ rootView.backgroundColor = .clear
61
+ rootView.isOpaque = false
62
+ }
@@ -0,0 +1,3 @@
1
+ //
2
+ // Use this file to import your target's public headers that you would like to expose to Swift.
3
+ //
@@ -0,0 +1,13 @@
1
+ {
2
+ "images" : [
3
+ {
4
+ "idiom" : "universal",
5
+ "platform" : "ios",
6
+ "size" : "1024x1024"
7
+ }
8
+ ],
9
+ "info" : {
10
+ "version" : 1,
11
+ "author" : "expo"
12
+ }
13
+ }
@@ -0,0 +1,6 @@
1
+ {
2
+ "info" : {
3
+ "version" : 1,
4
+ "author" : "expo"
5
+ }
6
+ }
@@ -0,0 +1,76 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3
+ <plist version="1.0">
4
+ <dict>
5
+ <key>CADisableMinimumFrameDurationOnPhone</key>
6
+ <true/>
7
+ <key>CFBundleDevelopmentRegion</key>
8
+ <string>$(DEVELOPMENT_LANGUAGE)</string>
9
+ <key>CFBundleExecutable</key>
10
+ <string>$(EXECUTABLE_NAME)</string>
11
+ <key>CFBundleIdentifier</key>
12
+ <string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
13
+ <key>CFBundleInfoDictionaryVersion</key>
14
+ <string>6.0</string>
15
+ <key>CFBundleName</key>
16
+ <string>$(PRODUCT_NAME)</string>
17
+ <key>CFBundleDisplayName</key>
18
+ <string>Bleam</string>
19
+ <key>CFBundlePackageType</key>
20
+ <string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
21
+ <key>CFBundleShortVersionString</key>
22
+ <string>1.0</string>
23
+ <key>CFBundleSignature</key>
24
+ <string>????</string>
25
+ <key>CFBundleVersion</key>
26
+ <string>1</string>
27
+ <key>AppGroupIdentifier</key>
28
+ <string>group.dev.bleam.app</string>
29
+ <key>LSMinimumSystemVersion</key>
30
+ <string>12.0</string>
31
+ <key>LSRequiresIPhoneOS</key>
32
+ <true/>
33
+ <key>NSAppTransportSecurity</key>
34
+ <dict>
35
+ <key>NSAllowsArbitraryLoads</key>
36
+ <false/>
37
+ <key>NSAllowsLocalNetworking</key>
38
+ <true/>
39
+ </dict>
40
+ <key>RCTNewArchEnabled</key>
41
+ <true/>
42
+ <key>UILaunchStoryboardName</key>
43
+ <string>SplashScreen</string>
44
+ <key>UIRequiredDeviceCapabilities</key>
45
+ <array>
46
+ <string>arm64</string>
47
+ </array>
48
+ <key>UIStatusBarStyle</key>
49
+ <string>UIStatusBarStyleDefault</string>
50
+ <key>UIApplicationSceneManifest</key>
51
+ <dict>
52
+ <key>UIApplicationSupportsMultipleScenes</key>
53
+ <false/>
54
+ <key>UISceneConfigurations</key>
55
+ <dict>
56
+ <key>UIWindowSceneSessionRoleApplication</key>
57
+ <array>
58
+ <dict>
59
+ <key>UISceneConfigurationName</key>
60
+ <string>Default Configuration</string>
61
+ <key>UISceneDelegateClassName</key>
62
+ <string>$(PRODUCT_MODULE_NAME).SceneDelegate</string>
63
+ </dict>
64
+ </array>
65
+ </dict>
66
+ </dict>
67
+ <key>UISupportedInterfaceOrientations</key>
68
+ <array>
69
+ <string>UIInterfaceOrientationPortrait</string>
70
+ <string>UIInterfaceOrientationLandscapeLeft</string>
71
+ <string>UIInterfaceOrientationLandscapeRight</string>
72
+ </array>
73
+ <key>UIViewControllerBasedStatusBarAppearance</key>
74
+ <false/>
75
+ </dict>
76
+ </plist>
@@ -0,0 +1,48 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3
+ <plist version="1.0">
4
+ <dict>
5
+ <key>NSPrivacyAccessedAPITypes</key>
6
+ <array>
7
+ <dict>
8
+ <key>NSPrivacyAccessedAPIType</key>
9
+ <string>NSPrivacyAccessedAPICategoryUserDefaults</string>
10
+ <key>NSPrivacyAccessedAPITypeReasons</key>
11
+ <array>
12
+ <string>CA92.1</string>
13
+ </array>
14
+ </dict>
15
+ <dict>
16
+ <key>NSPrivacyAccessedAPIType</key>
17
+ <string>NSPrivacyAccessedAPICategoryFileTimestamp</string>
18
+ <key>NSPrivacyAccessedAPITypeReasons</key>
19
+ <array>
20
+ <string>0A2A.1</string>
21
+ <string>3B52.1</string>
22
+ <string>C617.1</string>
23
+ </array>
24
+ </dict>
25
+ <dict>
26
+ <key>NSPrivacyAccessedAPIType</key>
27
+ <string>NSPrivacyAccessedAPICategoryDiskSpace</string>
28
+ <key>NSPrivacyAccessedAPITypeReasons</key>
29
+ <array>
30
+ <string>E174.1</string>
31
+ <string>85F4.1</string>
32
+ </array>
33
+ </dict>
34
+ <dict>
35
+ <key>NSPrivacyAccessedAPIType</key>
36
+ <string>NSPrivacyAccessedAPICategorySystemBootTime</string>
37
+ <key>NSPrivacyAccessedAPITypeReasons</key>
38
+ <array>
39
+ <string>35F9.1</string>
40
+ </array>
41
+ </dict>
42
+ </array>
43
+ <key>NSPrivacyCollectedDataTypes</key>
44
+ <array/>
45
+ <key>NSPrivacyTracking</key>
46
+ <false/>
47
+ </dict>
48
+ </plist>