bleam 0.0.10 → 0.0.12

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 (90) hide show
  1. package/dist/ai.cjs +1872 -1182
  2. package/dist/ai.d.cts +267 -234
  3. package/dist/ai.d.ts +267 -234
  4. package/dist/ai.js +1859 -1167
  5. package/dist/cli.cjs +3 -183
  6. package/dist/cli.d.cts +3 -47
  7. package/dist/cli.d.ts +3 -47
  8. package/dist/cli.js +3 -183
  9. package/dist/config.d.cts +1 -1
  10. package/dist/config.d.ts +1 -1
  11. package/dist/{files-Ds1wT8C2.js → files-BXVkPrPN.js} +6 -1
  12. package/dist/{files-Bo7h9fik.cjs → files-DxaQ-Nv0.cjs} +11 -0
  13. package/dist/files.cjs +1 -1
  14. package/dist/files.js +1 -1
  15. package/dist/index.d.cts +1 -1
  16. package/dist/index.d.ts +1 -1
  17. package/dist/platform.cjs +1 -13
  18. package/dist/platform.d.cts +1 -28
  19. package/dist/platform.d.ts +1 -28
  20. package/dist/platform.js +1 -10
  21. package/dist/schema-B5BfdswF.js +226 -0
  22. package/dist/schema-BnVZOXfu.cjs +286 -0
  23. package/dist/schema-D5eImHxu.d.cts +125 -0
  24. package/dist/schema-SSjokbtw.d.ts +125 -0
  25. package/dist/schema.cjs +10 -2
  26. package/dist/schema.d.cts +2 -2
  27. package/dist/schema.d.ts +2 -2
  28. package/dist/schema.js +2 -2
  29. package/dist/state-Dh3HLixb.js +874 -0
  30. package/dist/state-LssDgpff.cjs +973 -0
  31. package/dist/state.cjs +16 -12
  32. package/dist/state.d.cts +144 -140
  33. package/dist/state.d.ts +145 -140
  34. package/dist/state.js +3 -3
  35. package/dist/{ui-Bg11tvlc.d.ts → ui-1WepaMS4.d.cts} +1 -1
  36. package/dist/{ui-Dd7SXdbg.d.cts → ui-D7bRLYee.d.ts} +7 -7
  37. package/dist/ui.d.cts +1 -1
  38. package/dist/ui.d.ts +1 -1
  39. package/dist/window.d.cts +1 -1
  40. package/dist/window.d.ts +1 -1
  41. package/package.json +3 -1
  42. package/templates/foundation-models/app/index.tsx +77 -15
  43. package/templates/image-generation/app/index.tsx +4 -2
  44. package/templates/native/ios/Bleam/AI/Flux2/Configuration/ModelRegistry.swift +1 -1
  45. package/templates/native/ios/Bleam/AI/Flux2/Pipeline/FluxImageRunner.swift +7 -2
  46. package/templates/native/ios/Bleam/AI/TextGeneration/Bonsai/BonsaiTextGenerator.swift +46 -0
  47. package/templates/native/ios/Bleam/AI/TextGeneration/Bonsai/BonsaiTextModel.swift +107 -0
  48. package/templates/native/ios/Bleam/AI/TextGeneration/Bonsai/BonsaiTextSnapshotValidator.swift +201 -0
  49. package/templates/native/ios/Bleam/AI/TextGeneration/Core/GenerationTypes.swift +85 -0
  50. package/templates/native/ios/Bleam/AI/TextGeneration/Core/ModelCapabilities.swift +21 -0
  51. package/templates/native/ios/Bleam/AI/TextGeneration/Core/TextKVCache.swift +23 -0
  52. package/templates/native/ios/Bleam/AI/TextGeneration/Core/TokenSampler.swift +79 -0
  53. package/templates/native/ios/Bleam/AI/TextGeneration/Qwen3/Qwen3CheckpointLoader.swift +161 -0
  54. package/templates/native/ios/Bleam/AI/TextGeneration/Qwen3/Qwen3Configuration.swift +119 -0
  55. package/templates/native/ios/Bleam/AI/TextGeneration/Qwen3/Qwen3InferenceSession.swift +84 -0
  56. package/templates/native/ios/Bleam/AI/TextGeneration/Qwen3/Qwen3Layers.swift +197 -0
  57. package/templates/native/ios/Bleam/AI/TextGeneration/Qwen3/Qwen3Model.swift +130 -0
  58. package/templates/native/ios/Bleam/AI/TextGeneration/Qwen35/Qwen35Checkpoint.swift +102 -0
  59. package/templates/native/ios/Bleam/AI/TextGeneration/Qwen35/Qwen35Configuration.swift +140 -0
  60. package/templates/native/ios/Bleam/AI/TextGeneration/Qwen35/Qwen35Model.swift +112 -0
  61. package/templates/native/ios/Bleam/AI/TextGeneration/Qwen35/Qwen35Session.swift +121 -0
  62. package/templates/native/ios/Bleam/AI/TextGeneration/QwenHybrid/QwenHybridCache.swift +42 -0
  63. package/templates/native/ios/Bleam/AI/TextGeneration/QwenHybrid/QwenHybridFullAttention.swift +57 -0
  64. package/templates/native/ios/Bleam/AI/TextGeneration/QwenHybrid/QwenHybridLayers.swift +58 -0
  65. package/templates/native/ios/Bleam/AI/TextGeneration/QwenHybrid/QwenHybridLinearAttention.swift +111 -0
  66. package/templates/native/ios/Bleam/AppDelegate.swift +0 -206
  67. package/templates/native/ios/Bleam.xcodeproj/project.pbxproj +135 -129
  68. package/templates/native/ios/GenerationService/BonsaiTextGenerationRunner.swift +103 -0
  69. package/templates/native/ios/GenerationService/ImageGenerationRunner.swift +19 -7
  70. package/templates/native/ios/GenerationService/main.swift +174 -48
  71. package/templates/native/ios/Podfile.lock +173 -173
  72. package/templates/native/ios/Shared/Generation/GenerationServiceProtocol.swift +3 -0
  73. package/templates/native/ios/Shared/Generation/GenerationWorkerProtocol.swift +44 -0
  74. package/templates/native/modules/bleam-runtime/ios/AIModule.swift +239 -341
  75. package/templates/native/modules/bleam-runtime/ios/GenerationContracts.swift +47 -0
  76. package/templates/native/modules/bleam-runtime/ios/PlatformModule.swift +0 -191
  77. package/templates/text-generation/app/index.tsx +124 -0
  78. package/templates/text-generation/app.config.ts +5 -0
  79. package/templates/updates/README.md +43 -199
  80. package/templates/updates/src/index.ts +5 -98
  81. package/templates/updates/src/schema.ts +18 -173
  82. package/dist/schema-Bo5Jvqus.js +0 -90
  83. package/dist/schema-DOOjfXvs.d.ts +0 -58
  84. package/dist/schema-ENSMX_1t.d.cts +0 -58
  85. package/dist/schema-rQ13mrpD.cjs +0 -102
  86. package/dist/state-Bx0VlTlO.cjs +0 -852
  87. package/dist/state-CAwe-Vw1.js +0 -767
  88. package/templates/native/ios/PlatformHelper/main.swift +0 -726
  89. /package/dist/{config-Cms0rvqg.d.ts → config-COcRnn5a.d.cts} +0 -0
  90. /package/dist/{config-CufOVJV3.d.cts → config-Chi-flpJ.d.ts} +0 -0
@@ -1,726 +0,0 @@
1
- import Foundation
2
- import CryptoKit
3
- import Security
4
- import Darwin
5
-
6
- @objc private protocol PlatformHelperProtocol {
7
- func health(reply: @escaping (Data?) -> Void)
8
- func downloadPlatformUpdate(requestData: Data, reply: @escaping (Data?) -> Void)
9
- func applyPlatformUpdate(requestData: Data, reply: @escaping (Bool) -> Void)
10
- }
11
-
12
- private struct DownloadRequest: Decodable {
13
- let serviceURL: URL
14
- let bundleIdentifier: String
15
- let teamIdentifier: String
16
- let platformVersion: String
17
- let platformFingerprint: String
18
- }
19
-
20
- private struct ApplyRequest: Codable {
21
- let releaseId: String
22
- let bundleIdentifier: String
23
- let teamIdentifier: String
24
- let installedAppPath: String
25
- let appPID: Int32
26
- }
27
-
28
- private struct ApplyTransaction: Codable {
29
- enum Stage: String, Codable {
30
- case accepted, waitingForExit, verifying, replacing, failed
31
- }
32
-
33
- let request: ApplyRequest
34
- var stage: Stage
35
- var updatedAt: Date
36
- var failure: String?
37
- }
38
-
39
- private struct VerifiedReceipt: Codable {
40
- let releaseId: String
41
- let bundleIdentifier: String
42
- let teamIdentifier: String
43
- let appVersion: String
44
- let buildNumber: String
45
- let platformVersion: String
46
- let platformFingerprint: String
47
- let executable: String
48
- let architecture: String
49
- let appPath: String
50
- }
51
-
52
- private struct PlatformRelease: Decodable {
53
- struct Artifact: Decodable {
54
- let format: String
55
- let url: URL
56
- let sizeBytes: Int
57
- let sha256: String
58
- }
59
-
60
- let kind: String
61
- let releaseId: String
62
- let bundleIdentifier: String
63
- let teamIdentifier: String
64
- let appVersion: String
65
- let buildNumber: String
66
- let bleamPlatformVersion: String
67
- let platformFingerprint: String
68
- let redeploy: Bool?
69
- let target: Target
70
- let artifact: Artifact
71
-
72
- struct Target: Decodable {
73
- let minimumMacOSVersion: String
74
- }
75
- }
76
-
77
- private final class PlatformHelper: NSObject {
78
- private let bundleIdentifier: String
79
- private let teamIdentifier: String
80
- private let applyQueue = DispatchQueue(label: "dev.bleam.platform-helper.apply", qos: .userInitiated)
81
-
82
- init(bundleIdentifier: String, teamIdentifier: String) {
83
- self.bundleIdentifier = bundleIdentifier
84
- self.teamIdentifier = teamIdentifier
85
- super.init()
86
- applyQueue.async { [weak self] in
87
- self?.resumePendingApplies()
88
- }
89
- }
90
-
91
- func health(reply: @escaping (Data?) -> Void) {
92
- let environment = ProcessInfo.processInfo.environment
93
- let response: [String: Any] = [
94
- "healthy": true,
95
- "protocolVersion": 1,
96
- "helperVersion": environment["BLEAM_HELPER_VERSION"] ?? "unknown",
97
- "platformVersion": environment["BLEAM_PLATFORM_VERSION"] ?? "unknown",
98
- "capabilities": ["platform-updates"],
99
- ]
100
- reply(try? JSONSerialization.data(withJSONObject: response))
101
- }
102
-
103
- func downloadPlatformUpdate(requestData: Data, reply: @escaping (Data?) -> Void) {
104
- guard
105
- let request = try? JSONDecoder().decode(DownloadRequest.self, from: requestData),
106
- request.bundleIdentifier == bundleIdentifier,
107
- request.teamIdentifier == teamIdentifier,
108
- let resolverURL = URL(
109
- string: "/v1/apps/\(request.bundleIdentifier)/platform/macos-arm64",
110
- relativeTo: request.serviceURL
111
- )?.absoluteURL
112
- else {
113
- reply(nil)
114
- return
115
- }
116
-
117
- URLSession.shared.dataTask(with: resolverURL) { data, response, _ in
118
- guard
119
- let data,
120
- (response as? HTTPURLResponse)?.statusCode == 200,
121
- let release = try? JSONDecoder().decode(PlatformRelease.self, from: data),
122
- release.kind == "platform",
123
- release.bundleIdentifier == request.bundleIdentifier,
124
- release.teamIdentifier == request.teamIdentifier,
125
- release.bleamPlatformVersion.compare(request.platformVersion, options: .numeric) != .orderedAscending,
126
- release.platformFingerprint != request.platformFingerprint || release.redeploy == true,
127
- release.artifact.format == "bleam-macos-app-zip-v1",
128
- release.releaseId.range(of: "^[a-z][a-z0-9_-]{2,127}$", options: .regularExpression) != nil
129
- else {
130
- reply(nil)
131
- return
132
- }
133
-
134
- URLSession.shared.downloadTask(with: release.artifact.url) { temporaryURL, _, _ in
135
- guard let temporaryURL else {
136
- reply(nil)
137
- return
138
- }
139
-
140
- do {
141
- let cache = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask)[0]
142
- .appendingPathComponent("Bleam/Platform", isDirectory: true)
143
- .appendingPathComponent(request.bundleIdentifier, isDirectory: true)
144
- .appendingPathComponent(release.releaseId, isDirectory: true)
145
- let completionReceipt = URL(fileURLWithPath: NSHomeDirectory(), isDirectory: true)
146
- .appendingPathComponent("Library/Application Support/Bleam/Updates", isDirectory: true)
147
- .appendingPathComponent(request.bundleIdentifier, isDirectory: true)
148
- .appendingPathComponent("\(release.releaseId).complete")
149
- guard !FileManager.default.fileExists(atPath: completionReceipt.path) else {
150
- reply(nil)
151
- return
152
- }
153
- try FileManager.default.createDirectory(at: cache, withIntermediateDirectories: true)
154
- let artifactURL = cache.appendingPathComponent("artifact.zip")
155
- try? FileManager.default.removeItem(at: artifactURL)
156
- try FileManager.default.moveItem(at: temporaryURL, to: artifactURL)
157
- let appURL = try self.verify(
158
- artifactURL: artifactURL,
159
- release: release,
160
- request: request,
161
- cache: cache
162
- )
163
- let result: [String: Any] = [
164
- "releaseId": release.releaseId,
165
- "appVersion": release.appVersion,
166
- "buildNumber": release.buildNumber,
167
- "platformVersion": release.bleamPlatformVersion,
168
- "platformFingerprint": release.platformFingerprint,
169
- "redeploy": release.redeploy == true,
170
- "artifactPath": artifactURL.path,
171
- "appPath": appURL.path,
172
- "sizeBytes": release.artifact.sizeBytes,
173
- "sha256": release.artifact.sha256,
174
- "verified": true,
175
- ]
176
- let receiptData = try PropertyListEncoder().encode(
177
- self.verifiedReceipt(appURL: appURL, release: release, request: request)
178
- )
179
- try receiptData.write(to: cache.appendingPathComponent("verified.plist"), options: .atomic)
180
- reply(try? JSONSerialization.data(withJSONObject: result))
181
- } catch {
182
- NSLog("Bleam platform download failed: %@", String(describing: error))
183
- reply(try? JSONSerialization.data(withJSONObject: [
184
- "error": String(describing: error),
185
- ]))
186
- }
187
- }.resume()
188
- }.resume()
189
- }
190
-
191
- func applyPlatformUpdate(
192
- requestData: Data,
193
- clientProcessIdentifier: pid_t,
194
- reply: @escaping (Bool) -> Void
195
- ) {
196
- guard let request = try? JSONDecoder().decode(ApplyRequest.self, from: requestData) else {
197
- NSLog("Bleam platform apply rejected: invalid request")
198
- reply(false)
199
- return
200
- }
201
- guard
202
- request.bundleIdentifier == bundleIdentifier,
203
- request.teamIdentifier == teamIdentifier,
204
- clientProcessIdentifier == request.appPID,
205
- validReleaseId(request.releaseId)
206
- else {
207
- NSLog("Bleam platform apply rejected: request identity mismatch")
208
- reply(false)
209
- return
210
- }
211
- guard
212
- let installedURL = supportedInstalledURL(path: request.installedAppPath),
213
- (try? validateInstalledBundle(at: installedURL)) != nil
214
- else {
215
- NSLog("Bleam platform apply rejected: installed app identity mismatch")
216
- reply(false)
217
- return
218
- }
219
- guard
220
- let candidate = verifiedCandidate(request: request),
221
- let destinationURL = replacementURL(installedURL: installedURL, candidateURL: candidate.appURL),
222
- destinationURL == installedURL || !FileManager.default.fileExists(atPath: destinationURL.path)
223
- else {
224
- NSLog("Bleam platform apply rejected: verified candidate unavailable")
225
- reply(false)
226
- return
227
- }
228
- guard acceptTransaction(request) else {
229
- NSLog("Bleam platform apply rejected: transaction could not be persisted")
230
- reply(false)
231
- return
232
- }
233
- reply(true)
234
- applyQueue.async { [weak self] in
235
- self?.performApply(request)
236
- }
237
- }
238
-
239
- private func resumePendingApplies() {
240
- let receipts = receiptsURL(bundleIdentifier: bundleIdentifier)
241
- guard let files = try? FileManager.default.contentsOfDirectory(
242
- at: receipts,
243
- includingPropertiesForKeys: nil
244
- ) else {
245
- clearPendingMarkerIfIdle()
246
- return
247
- }
248
- for file in files where file.pathExtension == "applying" {
249
- guard
250
- let data = try? Data(contentsOf: file),
251
- let transaction = try? PropertyListDecoder().decode(ApplyTransaction.self, from: data),
252
- transaction.request.bundleIdentifier == bundleIdentifier,
253
- validReleaseId(transaction.request.releaseId),
254
- transaction.stage != .failed
255
- else { continue }
256
- performApply(transaction.request)
257
- }
258
- clearPendingMarkerIfIdle()
259
- }
260
-
261
- private func performApply(_ request: ApplyRequest) {
262
- let completionURL = receiptsURL(bundleIdentifier: request.bundleIdentifier)
263
- .appendingPathComponent("\(request.releaseId).complete")
264
- if FileManager.default.fileExists(atPath: completionURL.path) {
265
- try? FileManager.default.removeItem(at: transactionURL(for: request))
266
- clearPendingMarkerIfIdle()
267
- return
268
- }
269
-
270
- let activity = ProcessInfo.processInfo.beginActivity(
271
- options: [.userInitiated, .idleSystemSleepDisabled],
272
- reason: "Applying a verified Bleam platform update"
273
- )
274
- defer { ProcessInfo.processInfo.endActivity(activity) }
275
-
276
- guard
277
- request.bundleIdentifier == bundleIdentifier,
278
- request.teamIdentifier == teamIdentifier,
279
- let installedURL = supportedInstalledURL(path: request.installedAppPath),
280
- let candidate = verifiedCandidate(request: request),
281
- let destinationURL = replacementURL(installedURL: installedURL, candidateURL: candidate.appURL)
282
- else {
283
- recordFailure(request, error: VerificationError.invalidTransaction)
284
- return
285
- }
286
- let candidateURL = candidate.appURL
287
- let releaseRoot = candidateURL
288
- .deletingLastPathComponent().deletingLastPathComponent()
289
- .deletingLastPathComponent()
290
- let backupURL = releaseRoot.appendingPathComponent("previous.app")
291
-
292
- do {
293
- try writeTransaction(request: request, stage: .waitingForExit)
294
- for _ in 0..<50 where isApplicationProcessRunning(request) {
295
- usleep(100_000)
296
- }
297
- if isApplicationProcessRunning(request) {
298
- _ = kill(request.appPID, SIGTERM)
299
- for _ in 0..<50 where isApplicationProcessRunning(request) {
300
- usleep(100_000)
301
- }
302
- }
303
- guard !isApplicationProcessRunning(request) else {
304
- throw VerificationError.appDidNotTerminate
305
- }
306
-
307
- try writeTransaction(request: request, stage: .verifying)
308
- if isValidBundle(at: destinationURL, expected: candidate.receipt) {
309
- try finishApply(request: request, destinationURL: destinationURL)
310
- return
311
- }
312
-
313
- try validateBundle(at: candidateURL, expected: candidate.receipt)
314
- if FileManager.default.fileExists(atPath: installedURL.path) {
315
- try validateInstalledBundle(at: installedURL)
316
- try? FileManager.default.removeItem(at: backupURL)
317
- try FileManager.default.moveItem(at: installedURL, to: backupURL)
318
- } else if !FileManager.default.fileExists(atPath: backupURL.path) {
319
- throw VerificationError.invalidTransaction
320
- }
321
-
322
- try writeTransaction(request: request, stage: .replacing)
323
- try FileManager.default.moveItem(at: candidateURL, to: destinationURL)
324
- try validateBundle(at: destinationURL, expected: candidate.receipt)
325
- try finishApply(request: request, destinationURL: destinationURL)
326
- } catch {
327
- if FileManager.default.fileExists(atPath: backupURL.path) {
328
- try? FileManager.default.removeItem(at: destinationURL)
329
- if !FileManager.default.fileExists(atPath: installedURL.path) {
330
- try? FileManager.default.moveItem(at: backupURL, to: installedURL)
331
- _ = try? run("/usr/bin/open", [installedURL.path])
332
- }
333
- }
334
- recordFailure(request, error: error)
335
- }
336
- }
337
-
338
- private func finishApply(request: ApplyRequest, destinationURL: URL) throws {
339
- let receipts = receiptsURL(bundleIdentifier: request.bundleIdentifier)
340
- try FileManager.default.createDirectory(at: receipts, withIntermediateDirectories: true)
341
- try Data().write(
342
- to: receipts.appendingPathComponent("\(request.releaseId).complete"),
343
- options: .atomic
344
- )
345
- try? FileManager.default.removeItem(at: transactionURL(for: request))
346
- try? FileManager.default.removeItem(at: failureURL(for: request))
347
- clearPendingMarkerIfIdle()
348
- _ = try? run("/usr/bin/open", [destinationURL.path])
349
- }
350
-
351
- private func recordFailure(_ request: ApplyRequest, error: Error) {
352
- NSLog("Bleam platform replacement failed: %@", String(describing: error))
353
- do {
354
- try writeTransaction(
355
- request: request,
356
- stage: .failed,
357
- failure: String(describing: error),
358
- destination: failureURL(for: request)
359
- )
360
- try? FileManager.default.removeItem(at: transactionURL(for: request))
361
- clearPendingMarkerIfIdle()
362
- } catch {
363
- NSLog("Bleam platform failure receipt could not be written: %@", String(describing: error))
364
- }
365
- }
366
-
367
- private func acceptTransaction(_ request: ApplyRequest) -> Bool {
368
- do {
369
- try writeTransaction(request: request, stage: .accepted)
370
- try Data().write(to: pendingMarkerURL(), options: .atomic)
371
- return true
372
- } catch {
373
- try? FileManager.default.removeItem(at: transactionURL(for: request))
374
- clearPendingMarkerIfIdle()
375
- return false
376
- }
377
- }
378
-
379
- @discardableResult
380
- private func writeTransaction(
381
- request: ApplyRequest,
382
- stage: ApplyTransaction.Stage,
383
- failure: String? = nil,
384
- destination: URL? = nil
385
- ) throws -> URL {
386
- let url = destination ?? transactionURL(for: request)
387
- try FileManager.default.createDirectory(
388
- at: url.deletingLastPathComponent(),
389
- withIntermediateDirectories: true
390
- )
391
- let data = try PropertyListEncoder().encode(
392
- ApplyTransaction(request: request, stage: stage, updatedAt: Date(), failure: failure)
393
- )
394
- try data.write(to: url, options: .atomic)
395
- return url
396
- }
397
-
398
- private func transactionURL(for request: ApplyRequest) -> URL {
399
- receiptsURL(bundleIdentifier: request.bundleIdentifier)
400
- .appendingPathComponent("\(request.releaseId).applying")
401
- }
402
-
403
- private func failureURL(for request: ApplyRequest) -> URL {
404
- receiptsURL(bundleIdentifier: request.bundleIdentifier)
405
- .appendingPathComponent("\(request.releaseId).failed")
406
- }
407
-
408
- private func pendingMarkerURL() -> URL {
409
- receiptsURL(bundleIdentifier: bundleIdentifier).appendingPathComponent("platform-apply.pending")
410
- }
411
-
412
- private func clearPendingMarkerIfIdle() {
413
- let receipts = receiptsURL(bundleIdentifier: bundleIdentifier)
414
- let hasPendingTransaction = (try? FileManager.default.contentsOfDirectory(
415
- at: receipts,
416
- includingPropertiesForKeys: nil
417
- ).contains(where: { $0.pathExtension == "applying" })) == true
418
- if !hasPendingTransaction {
419
- try? FileManager.default.removeItem(at: pendingMarkerURL())
420
- }
421
- }
422
-
423
- private func receiptsURL(bundleIdentifier: String) -> URL {
424
- URL(fileURLWithPath: NSHomeDirectory(), isDirectory: true)
425
- .appendingPathComponent("Library/Application Support/Bleam/Updates", isDirectory: true)
426
- .appendingPathComponent(bundleIdentifier, isDirectory: true)
427
- }
428
-
429
- private func validReleaseId(_ releaseId: String) -> Bool {
430
- releaseId.range(of: "^[a-z][a-z0-9_-]{2,127}$", options: .regularExpression) != nil
431
- }
432
-
433
- private func supportedInstalledURL(path: String) -> URL? {
434
- let applications = URL(fileURLWithPath: NSHomeDirectory(), isDirectory: true)
435
- .appendingPathComponent("Applications", isDirectory: true).standardizedFileURL
436
- let candidate = URL(fileURLWithPath: path, isDirectory: true).standardizedFileURL
437
- guard
438
- candidate.deletingLastPathComponent() == applications,
439
- candidate.pathExtension == "app"
440
- else { return nil }
441
- return candidate
442
- }
443
-
444
- private func replacementURL(installedURL: URL, candidateURL: URL) -> URL? {
445
- let replacement = installedURL.deletingLastPathComponent()
446
- .appendingPathComponent(candidateURL.lastPathComponent, isDirectory: true)
447
- .standardizedFileURL
448
- guard
449
- replacement.pathExtension == "app",
450
- replacement.deletingLastPathComponent() == installedURL.deletingLastPathComponent()
451
- else { return nil }
452
- return replacement
453
- }
454
-
455
- private func isApplicationProcessRunning(_ request: ApplyRequest) -> Bool {
456
- var path = [CChar](repeating: 0, count: Int(MAXPATHLEN) * 4)
457
- guard proc_pidpath(request.appPID, &path, UInt32(path.count)) > 0 else { return false }
458
- let executablePath = String(cString: path)
459
- return executablePath.hasPrefix(request.installedAppPath + "/Contents/MacOS/")
460
- }
461
-
462
- private func verifiedCandidate(request: ApplyRequest) -> (appURL: URL, receipt: VerifiedReceipt)? {
463
- let root = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask)[0]
464
- .appendingPathComponent("Bleam/Platform", isDirectory: true)
465
- .appendingPathComponent(request.bundleIdentifier, isDirectory: true)
466
- .appendingPathComponent(request.releaseId, isDirectory: true)
467
- let receiptURL = root.appendingPathComponent("verified.plist")
468
- guard
469
- let data = try? Data(contentsOf: receiptURL),
470
- let receipt = try? PropertyListDecoder().decode(VerifiedReceipt.self, from: data),
471
- receipt.releaseId == request.releaseId,
472
- receipt.bundleIdentifier == request.bundleIdentifier,
473
- receipt.teamIdentifier == request.teamIdentifier
474
- else { return nil }
475
- let appURL = URL(fileURLWithPath: receipt.appPath, isDirectory: true).standardizedFileURL
476
- let appRoot = root.appendingPathComponent("extracted/app", isDirectory: true).standardizedFileURL
477
- guard
478
- appURL.pathExtension == "app",
479
- appURL.deletingLastPathComponent() == appRoot
480
- else { return nil }
481
- return (appURL, receipt)
482
- }
483
-
484
- private func verify(
485
- artifactURL: URL,
486
- release: PlatformRelease,
487
- request: DownloadRequest,
488
- cache: URL
489
- ) throws -> URL {
490
- let data = try Data(contentsOf: artifactURL, options: .mappedIfSafe)
491
- guard
492
- data.count == release.artifact.sizeBytes,
493
- SHA256.hash(data: data).map({ String(format: "%02x", $0) }).joined() == release.artifact.sha256
494
- else { throw VerificationError.invalidArtifact }
495
-
496
- let entries = try run("/usr/bin/zipinfo", ["-1", artifactURL.path])
497
- .split(separator: "\n").map(String.init)
498
- guard !entries.isEmpty, entries.allSatisfy({ entry in
499
- !entry.hasPrefix("/") && entry.split(separator: "/").allSatisfy { $0 != ".." }
500
- }) else { throw VerificationError.unsafeArchivePath }
501
- let appNames = Set(entries.compactMap { entry -> String? in
502
- let parts = entry.split(separator: "/")
503
- guard parts.count >= 2, parts[0] == "app", parts[1].hasSuffix(".app") else { return nil }
504
- return String(parts[1])
505
- })
506
- guard
507
- appNames.count == 1,
508
- let appName = appNames.first,
509
- entries.allSatisfy({ $0 == "app/" || $0.hasPrefix("app/\(appName)/") })
510
- else { throw VerificationError.invalidArchiveLayout }
511
-
512
- let extracted = cache.appendingPathComponent("extracted", isDirectory: true)
513
- try? FileManager.default.removeItem(at: extracted)
514
- try FileManager.default.createDirectory(at: extracted, withIntermediateDirectories: true)
515
- _ = try run("/usr/bin/ditto", ["-x", "-k", artifactURL.path, extracted.path])
516
- let appURL = extracted.appendingPathComponent("app/\(appName)")
517
- try validateBundle(at: appURL, expected: verifiedReceipt(appURL: appURL, release: release, request: request))
518
-
519
- let required = release.target.minimumMacOSVersion.split(separator: ".").compactMap { Int($0) }
520
- let current = ProcessInfo.processInfo.operatingSystemVersion
521
- let installed = [current.majorVersion, current.minorVersion, current.patchVersion]
522
- guard installed.lexicographicallyPrecedes(required) == false else {
523
- throw VerificationError.unsupportedOS
524
- }
525
- return appURL
526
- }
527
-
528
- private func verifiedReceipt(
529
- appURL: URL,
530
- release: PlatformRelease,
531
- request: DownloadRequest
532
- ) throws -> VerifiedReceipt {
533
- guard
534
- let info = NSDictionary(contentsOf: appURL.appendingPathComponent("Contents/Info.plist")),
535
- let executable = info["CFBundleExecutable"] as? String
536
- else { throw VerificationError.invalidIdentity }
537
- return VerifiedReceipt(
538
- releaseId: release.releaseId,
539
- bundleIdentifier: request.bundleIdentifier,
540
- teamIdentifier: request.teamIdentifier,
541
- appVersion: release.appVersion,
542
- buildNumber: release.buildNumber,
543
- platformVersion: release.bleamPlatformVersion,
544
- platformFingerprint: release.platformFingerprint,
545
- executable: executable,
546
- architecture: "arm64",
547
- appPath: appURL.path
548
- )
549
- }
550
-
551
- private func validateArchiveSymlinks(in appURL: URL) throws {
552
- let root = appURL.resolvingSymlinksInPath().standardizedFileURL
553
- let rootValues = try appURL.resourceValues(forKeys: [.isDirectoryKey, .isSymbolicLinkKey])
554
- var enumerationFailed = false
555
- guard rootValues.isDirectory == true,
556
- rootValues.isSymbolicLink != true,
557
- let enumerator = FileManager.default.enumerator(
558
- at: appURL,
559
- includingPropertiesForKeys: [.isSymbolicLinkKey],
560
- errorHandler: { _, _ in enumerationFailed = true; return false }
561
- )
562
- else { throw VerificationError.unsafeArchivePath }
563
- for case let url as URL in enumerator {
564
- guard (try url.resourceValues(forKeys: [.isSymbolicLinkKey])).isSymbolicLink == true else { continue }
565
- let target = url.resolvingSymlinksInPath().standardizedFileURL
566
- guard
567
- target == root || target.path.hasPrefix(root.path + "/"),
568
- FileManager.default.fileExists(atPath: target.path)
569
- else {
570
- throw VerificationError.unsafeArchivePath
571
- }
572
- }
573
- guard !enumerationFailed else { throw VerificationError.unsafeArchivePath }
574
- }
575
-
576
- private func validateBundle(at appURL: URL, expected: VerifiedReceipt) throws {
577
- try validateArchiveSymlinks(in: appURL)
578
- guard
579
- let info = NSDictionary(contentsOf: appURL.appendingPathComponent("Contents/Info.plist")),
580
- info["CFBundleIdentifier"] as? String == expected.bundleIdentifier,
581
- info["CFBundleShortVersionString"] as? String == expected.appVersion,
582
- info["CFBundleVersion"] as? String == expected.buildNumber,
583
- info["BleamRuntimeVersion"] as? String == expected.platformVersion,
584
- info["BleamPlatformFingerprint"] as? String == expected.platformFingerprint,
585
- info["CFBundleExecutable"] as? String == expected.executable
586
- else { throw VerificationError.invalidIdentity }
587
-
588
- var staticCode: SecStaticCode?
589
- guard
590
- SecStaticCodeCreateWithPath(appURL as CFURL, [], &staticCode) == errSecSuccess,
591
- let staticCode,
592
- SecStaticCodeCheckValidity(staticCode, SecCSFlags(rawValue: kSecCSStrictValidate), nil) == errSecSuccess
593
- else { throw VerificationError.invalidSignature }
594
- var signingInfo: CFDictionary?
595
- guard
596
- SecCodeCopySigningInformation(staticCode, SecCSFlags(rawValue: kSecCSSigningInformation), &signingInfo) == errSecSuccess,
597
- (signingInfo as? [String: Any])?[kSecCodeInfoTeamIdentifier as String] as? String == expected.teamIdentifier
598
- else { throw VerificationError.invalidSignature }
599
-
600
- let architectures = try run(
601
- "/usr/bin/lipo",
602
- ["-archs", appURL.appendingPathComponent("Contents/MacOS/\(expected.executable)").path]
603
- ).trimmingCharacters(in: .whitespacesAndNewlines)
604
- guard architectures == expected.architecture else { throw VerificationError.invalidArchitecture }
605
- }
606
-
607
- private func validateInstalledBundle(at appURL: URL) throws {
608
- guard
609
- let info = NSDictionary(contentsOf: appURL.appendingPathComponent("Contents/Info.plist")),
610
- info["CFBundleIdentifier"] as? String == bundleIdentifier
611
- else { throw VerificationError.invalidIdentity }
612
-
613
- var staticCode: SecStaticCode?
614
- guard
615
- SecStaticCodeCreateWithPath(appURL as CFURL, [], &staticCode) == errSecSuccess,
616
- let staticCode,
617
- SecStaticCodeCheckValidity(staticCode, SecCSFlags(rawValue: kSecCSStrictValidate), nil) == errSecSuccess
618
- else { throw VerificationError.invalidSignature }
619
- var signingInfo: CFDictionary?
620
- guard
621
- SecCodeCopySigningInformation(staticCode, SecCSFlags(rawValue: kSecCSSigningInformation), &signingInfo) == errSecSuccess,
622
- (signingInfo as? [String: Any])?[kSecCodeInfoTeamIdentifier as String] as? String == teamIdentifier
623
- else { throw VerificationError.invalidSignature }
624
- }
625
-
626
- private func isValidBundle(at appURL: URL, expected: VerifiedReceipt) -> Bool {
627
- guard FileManager.default.fileExists(atPath: appURL.path) else { return false }
628
- do {
629
- try validateBundle(at: appURL, expected: expected)
630
- return true
631
- } catch {
632
- return false
633
- }
634
- }
635
-
636
- private func run(_ executable: String, _ arguments: [String]) throws -> String {
637
- let process = Process()
638
- let output = Pipe()
639
- let errors = Pipe()
640
- process.executableURL = URL(fileURLWithPath: executable)
641
- process.arguments = arguments
642
- process.standardOutput = output
643
- process.standardError = errors
644
- try process.run()
645
- process.waitUntilExit()
646
- guard process.terminationStatus == 0 else { throw VerificationError.commandFailed }
647
- return String(decoding: output.fileHandleForReading.readDataToEndOfFile(), as: UTF8.self)
648
- }
649
- }
650
-
651
- private final class PlatformHelperConnection: NSObject, PlatformHelperProtocol {
652
- private let helper: PlatformHelper
653
- private let processIdentifier: pid_t
654
-
655
- init(helper: PlatformHelper, processIdentifier: pid_t) {
656
- self.helper = helper
657
- self.processIdentifier = processIdentifier
658
- }
659
-
660
- func health(reply: @escaping (Data?) -> Void) {
661
- helper.health(reply: reply)
662
- }
663
-
664
- func downloadPlatformUpdate(requestData: Data, reply: @escaping (Data?) -> Void) {
665
- helper.downloadPlatformUpdate(requestData: requestData, reply: reply)
666
- }
667
-
668
- func applyPlatformUpdate(requestData: Data, reply: @escaping (Bool) -> Void) {
669
- helper.applyPlatformUpdate(
670
- requestData: requestData,
671
- clientProcessIdentifier: processIdentifier,
672
- reply: reply
673
- )
674
- }
675
- }
676
-
677
- private enum VerificationError: Error {
678
- case invalidArtifact, unsafeArchivePath, invalidArchiveLayout, invalidIdentity, invalidSignature
679
- case invalidArchitecture, unsupportedOS, commandFailed, invalidTransaction, appDidNotTerminate
680
- }
681
-
682
- private final class PlatformHelperDelegate: NSObject, NSXPCListenerDelegate {
683
- private let helper: PlatformHelper
684
- private let codeSigningRequirement: String
685
-
686
- init(helper: PlatformHelper, bundleIdentifier: String, teamIdentifier: String) {
687
- self.helper = helper
688
- self.codeSigningRequirement = "anchor apple generic and identifier \"\(bundleIdentifier)\" and certificate leaf[subject.OU] = \"\(teamIdentifier)\""
689
- }
690
-
691
- func listener(_ listener: NSXPCListener, shouldAcceptNewConnection connection: NSXPCConnection) -> Bool {
692
- guard
693
- connection.processIdentifier > 0,
694
- connection.effectiveUserIdentifier == getuid()
695
- else { return false }
696
- connection.setCodeSigningRequirement(codeSigningRequirement)
697
- connection.exportedInterface = NSXPCInterface(with: PlatformHelperProtocol.self)
698
- connection.exportedObject = PlatformHelperConnection(
699
- helper: helper,
700
- processIdentifier: connection.processIdentifier
701
- )
702
- connection.resume()
703
- return true
704
- }
705
- }
706
-
707
- let environment = ProcessInfo.processInfo.environment
708
- guard
709
- let serviceName = environment["XPC_SERVICE_NAME"],
710
- let bundleIdentifier = environment["BLEAM_BUNDLE_IDENTIFIER"],
711
- bundleIdentifier.range(of: "^[A-Za-z0-9.-]+$", options: .regularExpression) != nil,
712
- let teamIdentifier = environment["BLEAM_TEAM_IDENTIFIER"],
713
- teamIdentifier.range(of: "^[A-Z0-9]{10}$", options: .regularExpression) != nil
714
- else {
715
- fatalError("Bleam platform helper requires launchd service metadata")
716
- }
717
- private let helper = PlatformHelper(bundleIdentifier: bundleIdentifier, teamIdentifier: teamIdentifier)
718
- private let delegate = PlatformHelperDelegate(
719
- helper: helper,
720
- bundleIdentifier: bundleIdentifier,
721
- teamIdentifier: teamIdentifier
722
- )
723
- private let listener = NSXPCListener(machServiceName: serviceName)
724
- listener.delegate = delegate
725
- listener.resume()
726
- RunLoop.main.run()