mirror of
https://github.com/osnr/TabFS.git
synced 2024-05-03 06:13:25 +02:00
safari: start migration to using out-of-band WebSocket to do extension<=>fs comm
This commit is contained in:
parent
930352dd42
commit
7211a5fdea
|
@ -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) {
|
||||||
|
|
|
@ -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)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
Binary file not shown.
|
@ -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>
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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)
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in a new issue