craft-native 0.0.78 → 0.0.79
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/cli.js +1 -1
- package/dist/ios/templates/CraftApp.swift +128 -3
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -364,7 +364,12 @@ struct CraftWebView: UIViewRepresentable {
|
|
|
364
364
|
func updateUIView(_ webView: WKWebView, context: Context) {}
|
|
365
365
|
|
|
366
366
|
func makeCoordinator() -> Coordinator {
|
|
367
|
-
Coordinator(config: config)
|
|
367
|
+
let coordinator = Coordinator(config: config)
|
|
368
|
+
// The Zig dispatcher finds CraftSwiftShim by class name; the shim finds
|
|
369
|
+
// its way back to the live coordinator through this. Weak, because
|
|
370
|
+
// SwiftUI owns the coordinator's lifetime.
|
|
371
|
+
CraftSwiftShim.coordinator = coordinator
|
|
372
|
+
return coordinator
|
|
368
373
|
}
|
|
369
374
|
|
|
370
375
|
// MARK: - Coordinator (Native Bridge)
|
|
@@ -511,8 +516,19 @@ struct CraftWebView: UIViewRepresentable {
|
|
|
511
516
|
guard let body = message.body as? [String: Any],
|
|
512
517
|
let action = body["action"] as? String else { return }
|
|
513
518
|
|
|
514
|
-
|
|
519
|
+
dispatch(action: action, body: body, callbackId: body["callbackId"] as? String)
|
|
520
|
+
}
|
|
515
521
|
|
|
522
|
+
/// The action switch, reachable from two directions: the
|
|
523
|
+
/// WKScriptMessageHandler above, and `CraftSwiftShim.handleAction` when
|
|
524
|
+
/// the Zig dispatcher hands over an action it has not migrated yet.
|
|
525
|
+
///
|
|
526
|
+
/// A hand-off call arrives with a synthetic callbackId of the form
|
|
527
|
+
/// "zig:<requestId>". The reply helpers recognise that prefix and route
|
|
528
|
+
/// the answer back through the Zig runtime's exported
|
|
529
|
+
/// `craft_ios_deliver_result` instead of evaluating JavaScript here —
|
|
530
|
+
/// one reply path, owned by whichever side received the page's message.
|
|
531
|
+
func dispatch(action: String, body: [String: Any], callbackId: String?) {
|
|
516
532
|
switch action {
|
|
517
533
|
case "startListening":
|
|
518
534
|
if config.enableSpeechRecognition { startSpeechRecognition() }
|
|
@@ -2426,6 +2442,10 @@ struct CraftWebView: UIViewRepresentable {
|
|
|
2426
2442
|
rejectCallback(callbackId, error: "Native result could not be serialized", code: "SERIALIZATION_ERROR")
|
|
2427
2443
|
return
|
|
2428
2444
|
}
|
|
2445
|
+
// A hand-off from the Zig dispatcher replies through Zig, which
|
|
2446
|
+
// owns the wire format, the request id, and the escaping. Replying
|
|
2447
|
+
// by JavaScript here as well would give the page two answers.
|
|
2448
|
+
if CraftSwiftShim.deliverResultIfHandOff(id, json: resultStr) { return }
|
|
2429
2449
|
let script = "window.craft._resolveCallback('\(id)', \(resultStr));"
|
|
2430
2450
|
DispatchQueue.main.async { self.webView?.evaluateJavaScript(script, completionHandler: nil) }
|
|
2431
2451
|
}
|
|
@@ -2436,7 +2456,17 @@ struct CraftWebView: UIViewRepresentable {
|
|
|
2436
2456
|
|
|
2437
2457
|
private func rejectCallback(_ callbackId: String?, error: String, code: String = "CRAFT_ERROR") {
|
|
2438
2458
|
guard let id = callbackId else { return }
|
|
2439
|
-
|
|
2459
|
+
// A hand-off rejection goes back through Zig's error route, so the
|
|
2460
|
+
// page's promise *rejects*. Delivering it as a result would run the
|
|
2461
|
+
// app's then-branch with an error-shaped object — fabricated
|
|
2462
|
+
// success wearing a different hat.
|
|
2463
|
+
if CraftSwiftShim.deliverErrorIfHandOff(id, message: error, code: code) { return }
|
|
2464
|
+
// Backslashes first: escaping ' and \n but not \ let an error
|
|
2465
|
+
// message containing a backslash break out of the string literal.
|
|
2466
|
+
let escapedError = error
|
|
2467
|
+
.replacingOccurrences(of: "\\", with: "\\\\")
|
|
2468
|
+
.replacingOccurrences(of: "'", with: "\\'")
|
|
2469
|
+
.replacingOccurrences(of: "\n", with: "\\n")
|
|
2440
2470
|
let script = "window.craft._rejectCallback('\(id)', '\(escapedError)', '\(code)');"
|
|
2441
2471
|
DispatchQueue.main.async { self.webView?.evaluateJavaScript(script, completionHandler: nil) }
|
|
2442
2472
|
}
|
|
@@ -5159,3 +5189,98 @@ extension CraftWebView.Coordinator: WCSessionDelegate {
|
|
|
5159
5189
|
}
|
|
5160
5190
|
}
|
|
5161
5191
|
}
|
|
5192
|
+
|
|
5193
|
+
|
|
5194
|
+
// MARK: - Zig hand-off shim
|
|
5195
|
+
|
|
5196
|
+
/// The seam the Zig dispatcher hands unmigrated actions through.
|
|
5197
|
+
///
|
|
5198
|
+
/// Discovery is symmetric and both directions are runtime-only. Zig finds this
|
|
5199
|
+
/// class with `objc_getClass("CraftSwiftShim")`; this class finds Zig's
|
|
5200
|
+
/// delivery exports with `dlsym`. Neither side links the other, so a
|
|
5201
|
+
/// pure-Swift app (no Zig runtime) and a fully-migrated Zig app (no shim work
|
|
5202
|
+
/// left) both build and run without dead dependencies.
|
|
5203
|
+
///
|
|
5204
|
+
/// The shim decides *what* the answer is, never *how* it reaches the page.
|
|
5205
|
+
/// Replies go back through Zig's `craft_ios_deliver_result` /
|
|
5206
|
+
/// `craft_ios_deliver_error`, which own the wire format, the request id, and
|
|
5207
|
+
/// the escaping. Two components replying to one page by different routes is
|
|
5208
|
+
/// how this codebase accumulated five envelopes.
|
|
5209
|
+
@objc(CraftSwiftShim)
|
|
5210
|
+
final class CraftSwiftShim: NSObject {
|
|
5211
|
+
/// The coordinator serving hand-offs. Set by `makeCoordinator`.
|
|
5212
|
+
static weak var coordinator: CraftWebView.Coordinator?
|
|
5213
|
+
|
|
5214
|
+
private typealias DeliverResultFn = @convention(c) (
|
|
5215
|
+
UnsafePointer<CChar>, UInt, UnsafePointer<CChar>, UInt, Int64
|
|
5216
|
+
) -> Void
|
|
5217
|
+
private typealias DeliverErrorFn = @convention(c) (
|
|
5218
|
+
UnsafePointer<CChar>, UInt, UnsafePointer<CChar>, UInt,
|
|
5219
|
+
UnsafePointer<CChar>, UInt, Int64
|
|
5220
|
+
) -> Void
|
|
5221
|
+
|
|
5222
|
+
private static let deliverResult: DeliverResultFn? = {
|
|
5223
|
+
guard let sym = dlsym(dlopen(nil, RTLD_NOW), "craft_ios_deliver_result") else { return nil }
|
|
5224
|
+
return unsafeBitCast(sym, to: DeliverResultFn.self)
|
|
5225
|
+
}()
|
|
5226
|
+
|
|
5227
|
+
private static let deliverError: DeliverErrorFn? = {
|
|
5228
|
+
guard let sym = dlsym(dlopen(nil, RTLD_NOW), "craft_ios_deliver_error") else { return nil }
|
|
5229
|
+
return unsafeBitCast(sym, to: DeliverErrorFn.self)
|
|
5230
|
+
}()
|
|
5231
|
+
|
|
5232
|
+
/// Entry point for the Zig dispatcher. Selector: handleAction:payload:requestId:
|
|
5233
|
+
///
|
|
5234
|
+
/// Returns false when this shim cannot serve the call — no live
|
|
5235
|
+
/// coordinator, or no Zig runtime to reply through — so Zig answers the
|
|
5236
|
+
/// page with UnknownAction instead of the call vanishing.
|
|
5237
|
+
@objc static func handleAction(_ action: String, payload: String, requestId: Int64) -> Bool {
|
|
5238
|
+
guard let coordinator, deliverResult != nil else { return false }
|
|
5239
|
+
|
|
5240
|
+
let body = ((try? JSONSerialization.jsonObject(with: Data(payload.utf8))) as? [String: Any]) ?? [:]
|
|
5241
|
+
|
|
5242
|
+
// The synthetic callbackId routes this call's reply back through Zig.
|
|
5243
|
+
// It carries the action too, because the reply helpers do not otherwise
|
|
5244
|
+
// know it, and Zig's reply names the action for the page's
|
|
5245
|
+
// action-matching fallback.
|
|
5246
|
+
coordinator.dispatch(action: action, body: body, callbackId: "zig:\(requestId):\(action)")
|
|
5247
|
+
return true
|
|
5248
|
+
}
|
|
5249
|
+
|
|
5250
|
+
/// Route a resolve through Zig when the callbackId marks a hand-off.
|
|
5251
|
+
/// Returns true when the reply has been (or could only be) handled here.
|
|
5252
|
+
static func deliverResultIfHandOff(_ callbackId: String, json: String) -> Bool {
|
|
5253
|
+
guard let (requestId, action) = parseHandOffId(callbackId) else { return false }
|
|
5254
|
+
guard let deliverResult else { return true } // hand-off id but no runtime: drop, never eval JS
|
|
5255
|
+
action.withCString { a in
|
|
5256
|
+
json.withCString { j in
|
|
5257
|
+
deliverResult(a, UInt(strlen(a)), j, UInt(strlen(j)), requestId)
|
|
5258
|
+
}
|
|
5259
|
+
}
|
|
5260
|
+
return true
|
|
5261
|
+
}
|
|
5262
|
+
|
|
5263
|
+
/// Route a rejection through Zig's error path, so the page's promise
|
|
5264
|
+
/// rejects rather than resolving with an error-shaped object.
|
|
5265
|
+
static func deliverErrorIfHandOff(_ callbackId: String, message: String, code: String) -> Bool {
|
|
5266
|
+
guard let (requestId, action) = parseHandOffId(callbackId) else { return false }
|
|
5267
|
+
guard let deliverError else { return true }
|
|
5268
|
+
action.withCString { a in
|
|
5269
|
+
message.withCString { m in
|
|
5270
|
+
code.withCString { c in
|
|
5271
|
+
deliverError(a, UInt(strlen(a)), m, UInt(strlen(m)), c, UInt(strlen(c)), requestId)
|
|
5272
|
+
}
|
|
5273
|
+
}
|
|
5274
|
+
}
|
|
5275
|
+
return true
|
|
5276
|
+
}
|
|
5277
|
+
|
|
5278
|
+
/// "zig:<requestId>:<action>" -> (requestId, action). Nil for ordinary
|
|
5279
|
+
/// page-issued callback ids, which keep their JavaScript reply path.
|
|
5280
|
+
private static func parseHandOffId(_ callbackId: String) -> (Int64, String)? {
|
|
5281
|
+
guard callbackId.hasPrefix("zig:") else { return nil }
|
|
5282
|
+
let rest = callbackId.dropFirst(4)
|
|
5283
|
+
guard let sep = rest.firstIndex(of: ":"), let id = Int64(rest[..<sep]) else { return nil }
|
|
5284
|
+
return (id, String(rest[rest.index(after: sep)...]))
|
|
5285
|
+
}
|
|
5286
|
+
}
|