bleam 0.0.10 → 0.0.11
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.
- package/dist/ai.cjs +608 -59
- package/dist/ai.d.cts +217 -89
- package/dist/ai.d.ts +217 -89
- package/dist/ai.js +601 -59
- package/dist/cli.cjs +3 -183
- package/dist/cli.d.cts +2 -46
- package/dist/cli.d.ts +2 -46
- package/dist/cli.js +3 -183
- package/dist/files.d.cts +1 -1
- package/dist/files.d.ts +1 -1
- package/dist/platform.cjs +1 -13
- package/dist/platform.d.cts +1 -28
- package/dist/platform.d.ts +1 -28
- package/dist/platform.js +1 -10
- package/dist/schema.d.cts +1 -1
- package/dist/schema.d.ts +1 -1
- package/dist/state.d.cts +1 -1
- package/dist/state.d.ts +1 -1
- package/dist/{ui-Bg11tvlc.d.ts → ui-1WepaMS4.d.cts} +1 -1
- package/dist/{ui-Dd7SXdbg.d.cts → ui-CHc4xEs_.d.ts} +7 -7
- package/dist/ui.d.cts +1 -1
- package/dist/ui.d.ts +1 -1
- package/dist/window.d.cts +1 -1
- package/dist/window.d.ts +1 -1
- package/package.json +1 -1
- package/templates/image-generation/app/index.tsx +3 -1
- package/templates/native/ios/Bleam/AI/Flux2/Configuration/ModelRegistry.swift +1 -1
- package/templates/native/ios/Bleam/AI/Flux2/Pipeline/FluxImageRunner.swift +7 -2
- package/templates/native/ios/Bleam/AI/TextGeneration/Bonsai/BonsaiTextGenerator.swift +46 -0
- package/templates/native/ios/Bleam/AI/TextGeneration/Bonsai/BonsaiTextModel.swift +107 -0
- package/templates/native/ios/Bleam/AI/TextGeneration/Bonsai/BonsaiTextSnapshotValidator.swift +201 -0
- package/templates/native/ios/Bleam/AI/TextGeneration/Core/GenerationTypes.swift +85 -0
- package/templates/native/ios/Bleam/AI/TextGeneration/Core/ModelCapabilities.swift +21 -0
- package/templates/native/ios/Bleam/AI/TextGeneration/Core/TextKVCache.swift +23 -0
- package/templates/native/ios/Bleam/AI/TextGeneration/Core/TokenSampler.swift +79 -0
- package/templates/native/ios/Bleam/AI/TextGeneration/Qwen3/Qwen3CheckpointLoader.swift +161 -0
- package/templates/native/ios/Bleam/AI/TextGeneration/Qwen3/Qwen3Configuration.swift +119 -0
- package/templates/native/ios/Bleam/AI/TextGeneration/Qwen3/Qwen3InferenceSession.swift +84 -0
- package/templates/native/ios/Bleam/AI/TextGeneration/Qwen3/Qwen3Layers.swift +197 -0
- package/templates/native/ios/Bleam/AI/TextGeneration/Qwen3/Qwen3Model.swift +130 -0
- package/templates/native/ios/Bleam/AI/TextGeneration/Qwen35/Qwen35Checkpoint.swift +102 -0
- package/templates/native/ios/Bleam/AI/TextGeneration/Qwen35/Qwen35Configuration.swift +140 -0
- package/templates/native/ios/Bleam/AI/TextGeneration/Qwen35/Qwen35Model.swift +112 -0
- package/templates/native/ios/Bleam/AI/TextGeneration/Qwen35/Qwen35Session.swift +121 -0
- package/templates/native/ios/Bleam/AI/TextGeneration/QwenHybrid/QwenHybridCache.swift +42 -0
- package/templates/native/ios/Bleam/AI/TextGeneration/QwenHybrid/QwenHybridFullAttention.swift +57 -0
- package/templates/native/ios/Bleam/AI/TextGeneration/QwenHybrid/QwenHybridLayers.swift +58 -0
- package/templates/native/ios/Bleam/AI/TextGeneration/QwenHybrid/QwenHybridLinearAttention.swift +111 -0
- package/templates/native/ios/Bleam/AppDelegate.swift +0 -206
- package/templates/native/ios/Bleam.xcodeproj/project.pbxproj +128 -122
- package/templates/native/ios/GenerationService/BonsaiTextGenerationRunner.swift +103 -0
- package/templates/native/ios/GenerationService/ImageGenerationRunner.swift +19 -7
- package/templates/native/ios/GenerationService/main.swift +174 -48
- package/templates/native/ios/Shared/Generation/GenerationServiceProtocol.swift +3 -0
- package/templates/native/ios/Shared/Generation/GenerationWorkerProtocol.swift +44 -0
- package/templates/native/modules/bleam-runtime/ios/AIModule.swift +225 -5
- package/templates/native/modules/bleam-runtime/ios/GenerationContracts.swift +47 -0
- package/templates/native/modules/bleam-runtime/ios/PlatformModule.swift +0 -191
- package/templates/text-generation/app/index.tsx +93 -0
- package/templates/text-generation/app.config.ts +5 -0
- package/templates/updates/README.md +43 -199
- package/templates/updates/src/index.ts +5 -98
- package/templates/updates/src/schema.ts +18 -173
- package/templates/native/ios/PlatformHelper/main.swift +0 -726
- /package/dist/{files-DwA7pzr3.d.cts → files-4ZEoAWiv.d.ts} +0 -0
- /package/dist/{files-VrkQlKIT.d.ts → files-Dt5mbzLq.d.cts} +0 -0
- /package/dist/{schema-DOOjfXvs.d.ts → schema-CYh6n8GS.d.ts} +0 -0
- /package/dist/{schema-ENSMX_1t.d.cts → schema-oeOrd3i1.d.cts} +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()
|