spb/zyquo-atlas Public License
The AI-native macOS web browser — every surface, intelligent.
Swift 75.2%
JavaScript 22%
Shell 2%
Makefile 0.9%
1//2// ContentExtractor.swift3// Zyquo Atlas4//5// Author: Simon-Pierre Boucher6// Mail: contact@spboucher.ai7//8// Injects the extraction scripts (Mozilla Readability + AtlasExtractor driver)9// into an isolated WKContentWorld and pulls a normalized PageContext out of a10// live tab via callAsyncJavaScript. Isolation keeps the extractor from11// colliding with or being observed by page JS (docs/AI-BROWSER-RESEARCH.md §2).12// All page content is treated as untrusted downstream (Phase 3.C prompt13// hygiene).14//1516import Foundation17import WebKit1819enum ContentExtractionError: LocalizedError {20 case scriptsUnavailable21 case scriptFailure(String)22 case decodeFailure(String)2324 var errorDescription: String? {25 switch self {26 case .scriptsUnavailable:27 return "Content extraction scripts are missing from the app bundle."28 case .scriptFailure(let detail):29 return "Couldn't read this page's content (\(detail))."30 case .decodeFailure(let detail):31 return "Couldn't parse the extracted page content (\(detail))."32 }33 }34}3536@MainActor37final class ContentExtractor {38 /// Dedicated world so extraction never touches the page's JS globals.39 static let world = WKContentWorld.world(name: "ZyquoAtlasContent")4041 /// Installs the extraction user scripts into a configuration. Call once per42 /// WKWebViewConfiguration (ProfileStore) before any tab loads.43 static func install(into config: WKWebViewConfiguration) {44 let ucc = config.userContentController45 for name in ["Readability", "Readability-readerable", "AtlasExtractor"] {46 guard let source = script(name) else { continue }47 ucc.addUserScript(WKUserScript(48 source: source,49 injectionTime: .atDocumentEnd,50 forMainFrameOnly: true,51 in: world52 ))53 }54 }5556 /// Extracts a PageContext from a tab's web view. `mode` "auto" tries57 /// Readability first; "rawText" forces the visible-text fallback.58 static func extract(from webView: WKWebView, mode: String = "auto") async throws -> PageContext {59 let result: Any?60 do {61 result = try await webView.callAsyncJavaScript(62 "return window.__zyquoAtlas ? window.__zyquoAtlas.extract(mode, maxBytes) : { ok:false, error:'driver-missing' };",63 arguments: ["mode": mode, "maxBytes": 600_000],64 in: nil,65 contentWorld: world66 )67 } catch {68 throw ContentExtractionError.scriptFailure(error.localizedDescription)69 }7071 guard let dict = result as? [String: Any] else {72 throw ContentExtractionError.scriptFailure("no result")73 }74 if dict["ok"] as? Bool != true {75 throw ContentExtractionError.scriptFailure((dict["error"] as? String) ?? "unknown")76 }77 guard let context = dict["context"] else {78 throw ContentExtractionError.scriptFailure("empty context")79 }80 do {81 let data = try JSONSerialization.data(withJSONObject: context)82 return try JSONDecoder().decode(PageContext.self, from: data)83 } catch {84 throw ContentExtractionError.decodeFailure(error.localizedDescription)85 }86 }8788 // MARK: - Script loading8990 private static func script(_ name: String) -> String? {91 guard let url = Bundle.module.url(forResource: name, withExtension: "js"),92 let source = try? String(contentsOf: url, encoding: .utf8) else {93 return nil94 }95 return source96 }97}98