From 7211a5fdead06a3c1b4cb15af7ce2cf883b19346 Mon Sep 17 00:00:00 2001 From: Omar Rizwan Date: Mon, 8 Feb 2021 02:32:21 -0800 Subject: [PATCH] safari: start migration to using out-of-band WebSocket to do extension<=>fs comm --- extension/background.js | 29 ++++--- .../SafariWebExtensionHandler.swift | 82 ++++++------------ .../UserInterfaceState.xcuserstate | Bin 61059 -> 58463 bytes .../xcdebugger/Breakpoints_v2.xcbkptlist | 12 +-- .../TabFS/TabFSService/TabFSService.swift | 78 +++++++++++++---- .../TabFSService/TabFSServiceProtocols.swift | 5 -- .../safari/TabFS/TabFSService/main.swift | 2 + 7 files changed, 111 insertions(+), 97 deletions(-) diff --git a/extension/background.js b/extension/background.js index f38f29e..62ec7d0 100644 --- a/extension/background.js +++ b/extension/background.js @@ -713,24 +713,29 @@ async function onMessage(req) { }; function tryConnect() { - console.log('start tryConnect'); - port = chrome.runtime.connectNative('com.rsnous.tabfs'); - console.log('start tryConnect - did connectNative'); - port.onMessage.addListener(onMessage); - port.onDisconnect.addListener(p => {console.log('disconnect', p)}); - - console.log('tryConnect - about to sNM'); // Safari is very weird -- it has this native app that we have to talk to, - // so we poke that app to wake it up, get it to start the TabFS process, - // and get it to start calling us whenever TabFS wants to do an FS call. + // so we poke that app to wake it up, get it to start the TabFS process + // and boot a WebSocket, then connect to it. // Is there a better way to do this? if (chrome.runtime.getURL('/').startsWith('safari-web-extension://')) { // Safari-only - chrome.runtime.sendNativeMessage('com.rsnous.tabfs', {op: 'safari_did_connect'}, function(resp) { - console.log('didConnect resp'); + chrome.runtime.sendNativeMessage('com.rsnous.tabfs', {op: 'safari_did_connect'}, resp => { console.log(resp); + const socket = new WebSocket('ws://localhost:9991'); + + socket.addEventListener('message', event => { + onMessage(JSON.parse(event.data)); + }); + + port = { postMessage(message) { + socket.send(JSON.stringify(message)); + } }; }); + return; } - console.log('tryConnect - did sNM'); + + port = chrome.runtime.connectNative('com.rsnous.tabfs'); + port.onMessage.addListener(onMessage); + port.onDisconnect.addListener(p => {console.log('disconnect', p)}); } if (!TESTING) { diff --git a/extension/safari/TabFS/TabFS Extension/SafariWebExtensionHandler.swift b/extension/safari/TabFS/TabFS Extension/SafariWebExtensionHandler.swift index 67da485..a1ae4ff 100644 --- a/extension/safari/TabFS/TabFS Extension/SafariWebExtensionHandler.swift +++ b/extension/safari/TabFS/TabFS Extension/SafariWebExtensionHandler.swift @@ -6,48 +6,8 @@ // import SafariServices -import SafariServices.SFSafariApplication import os.log -class TabFSServiceManager: TabFSServiceConsumerProtocol { - static let shared = TabFSServiceManager() - - var service: TabFSServiceProtocol! - - func connect() { - let connection = NSXPCConnection(serviceName: "com.rsnous.TabFSService") - - connection.remoteObjectInterface = NSXPCInterface(with: TabFSServiceProtocol.self) - - connection.exportedInterface = NSXPCInterface(with: TabFSServiceConsumerProtocol.self) - connection.exportedObject = self - - connection.resume() - - service = connection.remoteObjectProxyWithErrorHandler { error in - os_log(.default, "Received error: %{public}@", error as! CVarArg) - } as? TabFSServiceProtocol - - service?.upperCaseString("hello XPC") { response in - os_log(.default, "Response from XPC service: %{public}@", response) - } - } - - func request(_ req: Data) { - SFSafariApplication.dispatchMessage( - withName: "ToSafari", - toExtensionWithIdentifier: "com.rsnous.TabFS-Extension", - userInfo: try! JSONSerialization.jsonObject(with: req, options: []) as! [String : Any] - ) { error in - debugPrint("Message attempted. Error info: \(String.init(describing: error))") - } - } - - func response(_ resp: [AnyHashable: Any]) { - try! service.response(JSONSerialization.data(withJSONObject: resp, options: [])) - } -} - class SafariWebExtensionHandler: NSObject, NSExtensionRequestHandling { func beginRequest(with context: NSExtensionContext) { @@ -60,27 +20,35 @@ class SafariWebExtensionHandler: NSObject, NSExtensionRequestHandling { guard let message = item.userInfo?["message"] as? [AnyHashable: Any] else { return } if message["op"] as! String == "safari_did_connect" { - os_log(.default, "TabFSmsg sdc") - TabFSServiceManager.shared.connect() -// -// let response = NSExtensionItem() -// response.userInfo = [ "message": [ "aResponse to": "moop" ] ] -// context.completeRequest(returningItems: [response], completionHandler: nil) + + // The XPC service is a subprocess that lives outside the macOS App Sandbox. + // It can do forbidden things like spawn tabfs filesystem and set up WebSocket server. + + let connection = NSXPCConnection(serviceName: "com.rsnous.TabFSService") + + connection.remoteObjectInterface = NSXPCInterface(with: TabFSServiceProtocol.self) + + connection.resume() + + let service = connection.remoteObjectProxyWithErrorHandler { error in + os_log(.default, "Received error: %{public}@", error as! CVarArg) + } as? TabFSServiceProtocol + + // need this one XPC call to actually initialize the service + service?.upperCaseString("hello XPC") { response in + os_log(.default, "Response from XPC service: %{public}@", response) + } + + // FIXME: report port back? + let response = NSExtensionItem() + response.userInfo = [ "message": [ "aResponse to": "moop" ] ] + context.completeRequest(returningItems: [response]) { (what) in + print(what) + } return } - - TabFSServiceManager.shared.response(message) -// -// os_log(.default, "Received message from browser.runtime.sendNativeMessage: %@", op as! CVarArg) - -// let response = NSExtensionItem() -// response.userInfo = [ "message": [ "Response to": op ] ] -// -// // How do I get too the app???? -// -// context.completeRequest(returningItems: [response], completionHandler: nil) } } diff --git a/extension/safari/TabFS/TabFS.xcodeproj/project.xcworkspace/xcuserdata/osnr.xcuserdatad/UserInterfaceState.xcuserstate b/extension/safari/TabFS/TabFS.xcodeproj/project.xcworkspace/xcuserdata/osnr.xcuserdatad/UserInterfaceState.xcuserstate index 1fff19a1210ad433f797509a76e8cd5b534c4e65..93b8259604f621dcce19c544ab4782fcc692767c 100644 GIT binary patch delta 26418 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 EKWjf?dH?_b delta 28755 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 M9RJ(@@6%KM3$_I^!T @@ -46,8 +46,8 @@ filePath = "TabFS Extension/SafariWebExtensionHandler.swift" startingColumnNumber = "9223372036854775807" endingColumnNumber = "9223372036854775807" - startingLineNumber = "60" - endingLineNumber = "60" + startingLineNumber = "20" + endingLineNumber = "20" landmarkName = "beginRequest(with:)" landmarkType = "7"> @@ -62,8 +62,8 @@ filePath = "TabFS Extension/SafariWebExtensionHandler.swift" startingColumnNumber = "9223372036854775807" endingColumnNumber = "9223372036854775807" - startingLineNumber = "55" - endingLineNumber = "55" + startingLineNumber = "15" + endingLineNumber = "15" landmarkName = "beginRequest(with:)" landmarkType = "7"> diff --git a/extension/safari/TabFS/TabFSService/TabFSService.swift b/extension/safari/TabFS/TabFSService/TabFSService.swift index eb7508f..e49d80f 100644 --- a/extension/safari/TabFS/TabFSService/TabFSService.swift +++ b/extension/safari/TabFS/TabFSService/TabFSService.swift @@ -6,16 +6,14 @@ // import Foundation +import Network import os.log class TabFSService: NSObject, TabFSServiceProtocol { var fs: Process! var fsInput: FileHandle! var fsOutput: FileHandle! - - init(app: TabFSServiceConsumerProtocol) { - super.init() - + func startFs() { fs = Process() fs.executableURL = URL(fileURLWithPath: "/Users/osnr/Code/tabfs/fs/tabfs") fs.currentDirectoryURL = fs.executableURL?.deletingLastPathComponent() @@ -32,6 +30,52 @@ class TabFSService: NSObject, TabFSServiceProtocol { os_log(.default, "TabFSmsg tfs service: willrun") try! fs.run() os_log(.default, "TabFSmsg tfs service: ran") + } + + var ws: NWListener! + func startWs() { + // websocket server + let port = NWEndpoint.Port(rawValue: 9991)! + let parameters = NWParameters(tls: nil) + parameters.allowLocalEndpointReuse = true + parameters.includePeerToPeer = true + let opts = NWProtocolWebSocket.Options() + opts.autoReplyPing = true + parameters.defaultProtocolStack.applicationProtocols.insert(opts, at: 0) + + ws = try! NWListener(using: parameters, on: port) + ws.start(queue: .main) + } + + override init() { + super.init() + + startFs() + startWs() + + var handleRequest: ((_ req: Data) -> Void)? + ws.newConnectionHandler = { conn in + conn.start(queue: .main) + handleRequest = { req in + conn.send(content: req, completion: .contentProcessed({ err in + if err != nil { + // FIXME: ERROR + } + })) + } + + func read() { + conn.receiveMessage { (resp, context, isComplete, err) in + guard let resp = resp else { + // FIXME err + return + } + self.fsInput.write(withUnsafeBytes(of: UInt32(resp.count)) { Data($0) }) + self.fsInput.write(resp) + read() + } + } + } // split new thread DispatchQueue.global(qos: .default).async { @@ -40,11 +84,15 @@ class TabFSService: NSObject, TabFSServiceProtocol { let length = self.fsOutput.readData(ofLength: 4).withUnsafeBytes { $0.load(as: UInt32.self) } os_log(.default, "TabFSmsg tfs service: read %{public}d", length) let req = self.fsOutput.readData(ofLength: Int(length)) - // send to other side of XPC conn - app.request(req) + + // send to other side of WEBSOCKET + if let handleRequest = handleRequest { + handleRequest(req) + } else { + // FIXME: ERROR + } } } - // FIXME: disable auto termination } @@ -52,22 +100,18 @@ class TabFSService: NSObject, TabFSServiceProtocol { let response = string.uppercased() reply(response) } - - func response(_ resp: Data) { - fsInput.write(withUnsafeBytes(of: UInt32(resp.count)) { Data($0) }) - fsInput.write(resp) - } +// +// func response(_ resp: Data) { +// fsInput.write(withUnsafeBytes(of: UInt32(resp.count)) { Data($0) }) +// fsInput.write(resp) +// } } class TabFSServiceDelegate: NSObject, NSXPCListenerDelegate { func listener(_ listener: NSXPCListener, shouldAcceptNewConnection newConnection: NSXPCConnection) -> Bool { - - os_log(.default, "TabFSmsg tfs service: starting delegate") - newConnection.remoteObjectInterface = NSXPCInterface(with: TabFSServiceConsumerProtocol.self) - - let exportedObject = TabFSService(app: newConnection.remoteObjectProxy as! TabFSServiceConsumerProtocol) + let exportedObject = TabFSService() newConnection.exportedInterface = NSXPCInterface(with: TabFSServiceProtocol.self) newConnection.exportedObject = exportedObject diff --git a/extension/safari/TabFS/TabFSService/TabFSServiceProtocols.swift b/extension/safari/TabFS/TabFSService/TabFSServiceProtocols.swift index 298015a..8aedd87 100644 --- a/extension/safari/TabFS/TabFSService/TabFSServiceProtocols.swift +++ b/extension/safari/TabFS/TabFSService/TabFSServiceProtocols.swift @@ -7,11 +7,6 @@ import Foundation -@objc public protocol TabFSServiceConsumerProtocol { - func request(_ req: Data) -} @objc public protocol TabFSServiceProtocol { func upperCaseString(_ string: String, withReply reply: @escaping (String) -> Void) - - func response(_ resp: Data) } diff --git a/extension/safari/TabFS/TabFSService/main.swift b/extension/safari/TabFS/TabFSService/main.swift index 39c5a75..f0a4486 100644 --- a/extension/safari/TabFS/TabFSService/main.swift +++ b/extension/safari/TabFS/TabFSService/main.swift @@ -7,6 +7,8 @@ import Foundation +ProcessInfo.processInfo.disableAutomaticTermination("ok") + let delegate = TabFSServiceDelegate() let listener = NSXPCListener.service() listener.delegate = delegate