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() {
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) {

View File

@ -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)
}
}

View File

@ -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">
</BreakpointContent>
@ -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">
</BreakpointContent>
@ -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">
</BreakpointContent>

View File

@ -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

View File

@ -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)
}

View File

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