An interior · Part 2 · SafariWebExtensionHandler.swift
Inside SafariWebExtensionHandler.swift
The one place Swift actually runs — read line by line, for someone who reads ClojureScript and is new to Swift.
This is the principal class Safari hands every generated web-extension project: the native entry point Safari instantiates when the web extension sends a native message. By the end of this page you can read its Swift unaided — every construct named, every line with a JS or ClojureScript analogue. One honest caveat up front: organon does not use this bridge on its data path. The captured facts flush over HTTP to 127.0.0.1 (Part 3’s subject), never through sendNativeMessage, so this handler is never actually called. Read it as what Safari hands you, not what organon runs — the canonical shape of the native bridge, worth being able to read even when it sits inert.
This interior is reached from Part 2 — the native wall, which frames the Swift as “the Swift you barely write.” Here we step inside that one file.
Act 1 — the shape
The round trip, before any Swift: what happens when the web extension calls sendNativeMessage.
Forget the Swift for a moment and watch the gestalt. The web-extension side — JavaScript, or in organon’s case compiled ClojureScript — calls
browser.runtime.sendNativeMessage. Safari packages that call into an NSExtensionContext and instantiates this class, calling its one method, beginRequest(with:). The method reads the message out of the context, builds a reply dictionary, and signals it is done by calling completeRequest — at which point the value travels back to the JavaScript caller’s promise.
SafariWebExtensionHandler.swift:13–39 · the whole round trip lives in one method.
There is no event loop, no listener registration: Safari calls you once per message and you complete the request once. Play it through:
Each stage maps to a point in the file — beginRequest reads, builds, and completes; the first and last stages are the JavaScript caller on the other side of the bridge.
Act 2 — Swift construct by construct
Rebuild the handler from the simplest thing up, naming each Swift construct with a JS / ClojureScript analogue.
The promise of this act: rebuild the handler from the simplest thing that could work, in three stages, naming each Swift construct as it first appears and pairing it with something a ClojureScript reader already knows. These drafts are simplified — each .srcann below is a teaching scaffold, not the real file (the verbatim source is Act 3). Click any underlined construct token in the code, or any chip in the “Swift you just met” strip, to read what it is and its analogue.
Stage A — the skeleton teaching scaffold
Start with nothing but the frame: bring in the two frameworks, declare the class Safari will instantiate, and stub the one method it calls. No logic yet — just the shape the platform requires.
SafariServices gives the extension types, os.log the system logger.NSObject and conforms to the NSExtensionRequestHandling protocol — the contract Safari calls into.with is the external argument label; context is the internal name — Swift has both.Swift you just met — click a chip:
Stage B — reading the message safely teaching scaffold
Now pull the message out of the context. Every link in that chain can be absent — there may be no input item, no userInfo, no message key — so Swift makes the absence explicit in the types, and short-circuits it safely.
as? yields the value typed as NSExtensionItem, or nil if it is some other type.? short-circuits the whole expression to nil if that link is nil.nil.Swift you just met — click a chip:
Stage C — replying teaching scaffold
Finally, build the reply and hand it back. Construct a fresh extension item, stuff a nested dictionary into its userInfo, log what arrived, and complete the request — passing nil for the completion callback we do not need.
NSExtensionItem() is Swift’s new NSExtensionItem()."echo".String(describing:) renders any value as a readable string for the log line.nil means “no callback when the system finishes.”Swift you just met — click a chip:
Act 3 — the verbatim source
Now the real file, unsimplified. The scaffolds dropped two availability branches and a profile read; here is every line.
Acts 1 and 2 simplified. The real handler does the same three things — read, reply, complete — but guards each modern API behind an availability branch with an older-OS fallback, and reads a profile UUID alongside the message. Below is the file as it ships, line for line. The annotations are the same teaching aids from Act 2; when you think you can read it unaided, hit Read it cold to strip them and test yourself.
NSObject, conforms to the NSExtensionRequestHandling protocol Safari calls into.NSExtensionItem — nil if there is none.UUID: the Safari profile the message came from, which may be absent."profile" string key.Any? — could be any type, or absent."message" key as the fallback.String(describing:) renders the message; profile?.uuidString ?? "none" is optional-chaining + the ?? nil-coalescing default."echo", keyed by the modern message key…"message" key on older OSes.completionHandler: nil — the trailing closure we do not need.SafariWebExtensionHandler.swift:8–42
That is the whole bridge Safari offers: one class, one method, one round trip. organon does not ride it — its facts flush to the localhost receiver, and this handler stays inert — but now you can read it, construct by construct, and recognise the same shape in any other Safari web-extension project you open.