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 1fff19a..93b8259 100644 Binary files a/extension/safari/TabFS/TabFS.xcodeproj/project.xcworkspace/xcuserdata/osnr.xcuserdatad/UserInterfaceState.xcuserstate and b/extension/safari/TabFS/TabFS.xcodeproj/project.xcworkspace/xcuserdata/osnr.xcuserdatad/UserInterfaceState.xcuserstate differ diff --git a/extension/safari/TabFS/TabFS.xcodeproj/xcuserdata/osnr.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist b/extension/safari/TabFS/TabFS.xcodeproj/xcuserdata/osnr.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist index 87a2930..0a7caef 100644 --- a/extension/safari/TabFS/TabFS.xcodeproj/xcuserdata/osnr.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist +++ b/extension/safari/TabFS/TabFS.xcodeproj/xcuserdata/osnr.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist @@ -14,8 +14,8 @@ filePath = "TabFS Extension/SafariWebExtensionHandler.swift" startingColumnNumber = "9223372036854775807" endingColumnNumber = "9223372036854775807" - startingLineNumber = "57" - endingLineNumber = "57" + startingLineNumber = "17" + endingLineNumber = "17" landmarkName = "beginRequest(with:)" landmarkType = "7"> @@ -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