diff --git a/extension/background.js b/extension/background.js index 62ec7d0..e31a4c4 100644 --- a/extension/background.js +++ b/extension/background.js @@ -720,15 +720,26 @@ function tryConnect() { if (chrome.runtime.getURL('/').startsWith('safari-web-extension://')) { // Safari-only 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)); - }); + let socket; + function connectSocket(checkAfterTime) { + socket = new WebSocket('ws://localhost:9991'); + socket.addEventListener('message', event => { + onMessage(JSON.parse(event.data)); + }); - port = { postMessage(message) { - socket.send(JSON.stringify(message)); - } }; + port = { postMessage(message) { + socket.send(JSON.stringify(message)); + } }; + + setTimeout(() => { + if (socket.readyState !== 1) { + console.log('ws connection failed, retrying in', checkAfterTime); + connectSocket(checkAfterTime * 2); + } + }, checkAfterTime); + } + connectSocket(200); }); return; } diff --git a/extension/safari/README.md b/extension/safari/README.md index 9ceafdc..8a47d23 100644 --- a/extension/safari/README.md +++ b/extension/safari/README.md @@ -24,8 +24,12 @@ it's mounted. ### tips - To open Web inspector: Safari -> Develop menu -> Web Extension - Background Pages -> TabFS + Background Pages -> TabFS. -- You need to rebuild if you change background.js. This is pretty - annoying. + Refreshing this inspector should reload the tabfs filesystem, also. + +- You need to rebuild in Xcode any time you change background.js + (because the extension files are copied into the extension, rather + than running directly from folder as in Firefox and Chrome). This is + pretty annoying. diff --git a/extension/safari/TabFS/TabFS Extension/SafariWebExtensionHandler.swift b/extension/safari/TabFS/TabFS Extension/SafariWebExtensionHandler.swift index 75790fa..dc30cc4 100644 --- a/extension/safari/TabFS/TabFS Extension/SafariWebExtensionHandler.swift +++ b/extension/safari/TabFS/TabFS Extension/SafariWebExtensionHandler.swift @@ -17,16 +17,20 @@ class SafariWebExtensionHandler: NSObject, NSExtensionRequestHandling { guard message["op"] as! String == "safari_did_connect" else { return } - // The XPC service is a subprocess that lives outside the macOS App Sandbox. + // The XPC service is a process that can live outside the macOS App Sandbox. // (Safari extension native code, including this file, has to live in the sandbox.) // It can do forbidden things like spawn tabfs filesystem and set up WebSocket server. - // We only use one native message to bootstrap the XPC service, then do all communications - // to that service (which in turn talks to tabfs.c) over WebSocket instead. + // We only use one native message, to bootstrap the XPC service (TabFSService). + // Then the XPC service starts TabFSServer. TabFSServer is separate because + // XPC services get killed by the OS after a minute or two; TabFSServer + // is just a normal process that can live on. It talks straight + // to background.js (which in turn talks to tabfs.c) over a WebSocket. + // (Safari makes doing native messaging quite painful, so we try to avoid it. // It forces the browser to pop to front if you message Safari in the obvious way, // for instance: https://developer.apple.com/forums/thread/122232 - // And with the WebSocket, the XPC service can talk straight to background.js, whereas + // And with the WebSocket, the server can talk straight to background.js, whereas // native messaging would require us here to sit in the middle.) let connection = NSXPCConnection(serviceName: "com.rsnous.TabFSService") @@ -44,8 +48,9 @@ class SafariWebExtensionHandler: NSObject, NSExtensionRequestHandling { // FIXME: report port back? let response = NSExtensionItem() response.userInfo = [ "message": "alive" ] - // This response (over native messaging) prompts background.js to - // connect to the WebSocket server that the XPC service should now be running. + // This response (over native messaging) will prompt background.js to + // connect to the WebSocket server of TabFSServer, which should + // now be running. context.completeRequest(returningItems: [response]) { (what) in print(what) } 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 db4d28a..96586ef 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/TabFSServer/main.swift b/extension/safari/TabFS/TabFSServer/main.swift index 245bfb1..76da2a0 100644 --- a/extension/safari/TabFS/TabFSServer/main.swift +++ b/extension/safari/TabFS/TabFSServer/main.swift @@ -75,8 +75,9 @@ class TabFSServer { func read() { conn.receiveMessage { (resp, context, isComplete, err) in guard let resp = resp else { - // FIXME err - os_log(.default, "resp error: %{public}@", err!.debugDescription as CVarArg) + if let err = err { + os_log(.default, "resp error: %{public}@", err.debugDescription as CVarArg) + } return } @@ -104,10 +105,10 @@ class TabFSServer { } } - // FIXME: notify - + print("OK") } } let server = TabFSServer() + dispatchMain() diff --git a/extension/safari/TabFS/TabFSService/TabFSService.swift b/extension/safari/TabFS/TabFSService/TabFSService.swift index 4a86dd6..5bf55ee 100644 --- a/extension/safari/TabFS/TabFSService/TabFSService.swift +++ b/extension/safari/TabFS/TabFSService/TabFSService.swift @@ -12,13 +12,36 @@ import os.log class TabFSService: NSObject, TabFSServiceProtocol { func start(withReply reply: @escaping () -> Void) { // This XPC call is enough to just force the XPC service to be started. - os_log("HELLO") + + // kill old copies of TabFSServer + let killall = Process() + killall.launchPath = "/usr/bin/killall" + killall.arguments = ["TabFSServer"] + killall.launch() + killall.waitUntilExit() + + // spin until old TabFSServer (if any) is gone + while true { + let pgrep = Process() + pgrep.launchPath = "/usr/bin/pgrep" + pgrep.arguments = ["TabFSServer"] + pgrep.launch() + pgrep.waitUntilExit() + if pgrep.terminationStatus != 0 { break } + + Thread.sleep(forTimeInterval: 0.01) + } + let server = Process() - os_log("HOW ARE YOU?") + let serverOutput = Pipe() server.executableURL = Bundle.main.url(forResource: "TabFSServer", withExtension: "")! - os_log("I AM GOOD") + server.standardOutput = serverOutput server.launch() - os_log("GREAT") + + // FIXME: should we wait for some signal that the server is ready? + // right now, background.js will just periodically retry until it can connect. + + // tell background.js to try to connect. reply() } }