safari: start migration to using out-of-band WebSocket to do extension<=>fs comm

This commit is contained in:
Omar Rizwan 2021-02-08 02:32:21 -08:00
parent 930352dd42
commit 7211a5fdea
7 changed files with 111 additions and 97 deletions

View file

@ -713,24 +713,29 @@ async function onMessage(req) {
}; };
function tryConnect() { 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, // 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, // 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. // and boot a WebSocket, then connect to it.
// Is there a better way to do this? // Is there a better way to do this?
if (chrome.runtime.getURL('/').startsWith('safari-web-extension://')) { // Safari-only if (chrome.runtime.getURL('/').startsWith('safari-web-extension://')) { // Safari-only
chrome.runtime.sendNativeMessage('com.rsnous.tabfs', {op: 'safari_did_connect'}, function(resp) { chrome.runtime.sendNativeMessage('com.rsnous.tabfs', {op: 'safari_did_connect'}, resp => {
console.log('didConnect resp');
console.log(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) { if (!TESTING) {

View file

@ -6,48 +6,8 @@
// //
import SafariServices import SafariServices
import SafariServices.SFSafariApplication
import os.log 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 { class SafariWebExtensionHandler: NSObject, NSExtensionRequestHandling {
func beginRequest(with context: NSExtensionContext) { func beginRequest(with context: NSExtensionContext) {
@ -60,27 +20,35 @@ class SafariWebExtensionHandler: NSObject, NSExtensionRequestHandling {
guard let message = item.userInfo?["message"] as? [AnyHashable: Any] else { return } guard let message = item.userInfo?["message"] as? [AnyHashable: Any] else { return }
if message["op"] as! String == "safari_did_connect" { if message["op"] as! String == "safari_did_connect" {
os_log(.default, "TabFSmsg sdc") os_log(.default, "TabFSmsg sdc")
TabFSServiceManager.shared.connect()
// // The XPC service is a subprocess that lives outside the macOS App Sandbox.
// let response = NSExtensionItem() // It can do forbidden things like spawn tabfs filesystem and set up WebSocket server.
// response.userInfo = [ "message": [ "aResponse to": "moop" ] ]
// context.completeRequest(returningItems: [response], completionHandler: nil) 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 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)
} }
} }

View file

@ -14,8 +14,8 @@
filePath = "TabFS Extension/SafariWebExtensionHandler.swift" filePath = "TabFS Extension/SafariWebExtensionHandler.swift"
startingColumnNumber = "9223372036854775807" startingColumnNumber = "9223372036854775807"
endingColumnNumber = "9223372036854775807" endingColumnNumber = "9223372036854775807"
startingLineNumber = "57" startingLineNumber = "17"
endingLineNumber = "57" endingLineNumber = "17"
landmarkName = "beginRequest(with:)" landmarkName = "beginRequest(with:)"
landmarkType = "7"> landmarkType = "7">
</BreakpointContent> </BreakpointContent>
@ -46,8 +46,8 @@
filePath = "TabFS Extension/SafariWebExtensionHandler.swift" filePath = "TabFS Extension/SafariWebExtensionHandler.swift"
startingColumnNumber = "9223372036854775807" startingColumnNumber = "9223372036854775807"
endingColumnNumber = "9223372036854775807" endingColumnNumber = "9223372036854775807"
startingLineNumber = "60" startingLineNumber = "20"
endingLineNumber = "60" endingLineNumber = "20"
landmarkName = "beginRequest(with:)" landmarkName = "beginRequest(with:)"
landmarkType = "7"> landmarkType = "7">
</BreakpointContent> </BreakpointContent>
@ -62,8 +62,8 @@
filePath = "TabFS Extension/SafariWebExtensionHandler.swift" filePath = "TabFS Extension/SafariWebExtensionHandler.swift"
startingColumnNumber = "9223372036854775807" startingColumnNumber = "9223372036854775807"
endingColumnNumber = "9223372036854775807" endingColumnNumber = "9223372036854775807"
startingLineNumber = "55" startingLineNumber = "15"
endingLineNumber = "55" endingLineNumber = "15"
landmarkName = "beginRequest(with:)" landmarkName = "beginRequest(with:)"
landmarkType = "7"> landmarkType = "7">
</BreakpointContent> </BreakpointContent>

View file

@ -6,16 +6,14 @@
// //
import Foundation import Foundation
import Network
import os.log import os.log
class TabFSService: NSObject, TabFSServiceProtocol { class TabFSService: NSObject, TabFSServiceProtocol {
var fs: Process! var fs: Process!
var fsInput: FileHandle! var fsInput: FileHandle!
var fsOutput: FileHandle! var fsOutput: FileHandle!
func startFs() {
init(app: TabFSServiceConsumerProtocol) {
super.init()
fs = Process() fs = Process()
fs.executableURL = URL(fileURLWithPath: "/Users/osnr/Code/tabfs/fs/tabfs") fs.executableURL = URL(fileURLWithPath: "/Users/osnr/Code/tabfs/fs/tabfs")
fs.currentDirectoryURL = fs.executableURL?.deletingLastPathComponent() fs.currentDirectoryURL = fs.executableURL?.deletingLastPathComponent()
@ -32,6 +30,52 @@ class TabFSService: NSObject, TabFSServiceProtocol {
os_log(.default, "TabFSmsg tfs service: willrun") os_log(.default, "TabFSmsg tfs service: willrun")
try! fs.run() try! fs.run()
os_log(.default, "TabFSmsg tfs service: ran") 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 // split new thread
DispatchQueue.global(qos: .default).async { 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) } let length = self.fsOutput.readData(ofLength: 4).withUnsafeBytes { $0.load(as: UInt32.self) }
os_log(.default, "TabFSmsg tfs service: read %{public}d", length) os_log(.default, "TabFSmsg tfs service: read %{public}d", length)
let req = self.fsOutput.readData(ofLength: Int(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 // FIXME: disable auto termination
} }
@ -52,22 +100,18 @@ class TabFSService: NSObject, TabFSServiceProtocol {
let response = string.uppercased() let response = string.uppercased()
reply(response) reply(response)
} }
//
func response(_ resp: Data) { // func response(_ resp: Data) {
fsInput.write(withUnsafeBytes(of: UInt32(resp.count)) { Data($0) }) // fsInput.write(withUnsafeBytes(of: UInt32(resp.count)) { Data($0) })
fsInput.write(resp) // fsInput.write(resp)
} // }
} }
class TabFSServiceDelegate: NSObject, NSXPCListenerDelegate { class TabFSServiceDelegate: NSObject, NSXPCListenerDelegate {
func listener(_ listener: NSXPCListener, shouldAcceptNewConnection newConnection: NSXPCConnection) -> Bool { func listener(_ listener: NSXPCListener, shouldAcceptNewConnection newConnection: NSXPCConnection) -> Bool {
os_log(.default, "TabFSmsg tfs service: starting delegate") os_log(.default, "TabFSmsg tfs service: starting delegate")
newConnection.remoteObjectInterface = NSXPCInterface(with: TabFSServiceConsumerProtocol.self) let exportedObject = TabFSService()
let exportedObject = TabFSService(app: newConnection.remoteObjectProxy as! TabFSServiceConsumerProtocol)
newConnection.exportedInterface = NSXPCInterface(with: TabFSServiceProtocol.self) newConnection.exportedInterface = NSXPCInterface(with: TabFSServiceProtocol.self)
newConnection.exportedObject = exportedObject newConnection.exportedObject = exportedObject

View file

@ -7,11 +7,6 @@
import Foundation import Foundation
@objc public protocol TabFSServiceConsumerProtocol {
func request(_ req: Data)
}
@objc public protocol TabFSServiceProtocol { @objc public protocol TabFSServiceProtocol {
func upperCaseString(_ string: String, withReply reply: @escaping (String) -> Void) func upperCaseString(_ string: String, withReply reply: @escaping (String) -> Void)
func response(_ resp: Data)
} }

View file

@ -7,6 +7,8 @@
import Foundation import Foundation
ProcessInfo.processInfo.disableAutomaticTermination("ok")
let delegate = TabFSServiceDelegate() let delegate = TabFSServiceDelegate()
let listener = NSXPCListener.service() let listener = NSXPCListener.service()
listener.delegate = delegate listener.delegate = delegate