Sending messages from Google Chrome extension to macOS app

I'm building a macOS Google Chrome extension. I need to be able to send messages from the Chrome extension to the macOS app

What's the set up flow? I've heard about native messaging, but I struggle to implement it.

I've heard about XPC, but not sure JS can send messages to a macOS XPC service.

I was able to set up the Native Messaging https://developer.chrome.com/docs/apps/nativeMessaging

But there is a catch: In order for the messaging to work, i need to create a json here (referencing an executable intended to listen to messages from an extension): $HOME/Library/Application\ Support/Google/Chrome/NativeMessagingHosts. https://developer.chrome.com/docs/apps/nativeMessaging#native-messaging-host-location

Unfortunately you can not bundle it with a Chrome plugin so it automatically gets added there.

Basically the json looks like this:

{
    "name": "com.kopyl.tabfidnder.nativehost",
    "description": "Tab Finder Chrome",
    "path": "/usr/local/bin/native_host.py",
    "type": "stdio",
    "allowed_origins": [
      "chrome-extension://jcbclkhailmoenaeddooicbemefilmje/"
    ]
  }

(for rapid prototyping i used Python, but eventually i guess it would be better to utilize an XPC service)

Of course i could make a .pkg installer which would add that json config to the file system, but in terms of user experience i don't think it's optimal. And i doubt App Store will approve an application which requires external installations for it to work properly.

I tried copying the json on macOS app launch, but with enabled App Sandboxing Entitlement, the path turns to this (and hence becomes invalid):

$HOME/Library/Containers/kopyl.tab-finder-chrome-app/Data/Library/Application Support/Google/Chrome/NativeMessagingHosts/com.kopyl.tabfidnder.nativehost.json

Without the App Sandboxing the macOS app can't be published to the App Store.

Seems like you can access $HOME/Library/Application\ Support/Google/Chrome/NativeMessagingHosts using this approach:

func requestAccess() {
    let chromeNativeMessagingPath = NSString(string: "~/Library/Application Support/Google/Chrome/NativeMessagingHosts").expandingTildeInPath
    
    let openPanel = NSOpenPanel()
    openPanel.canChooseDirectories = true
    openPanel.canChooseFiles = false
    openPanel.allowsMultipleSelection = false
    openPanel.directoryURL = URL(fileURLWithPath: chromeNativeMessagingPath)
    openPanel.message = "Please grant access to the Chrome NativeMessagingHosts folder"
    openPanel.prompt = "Grant Access"
    
    openPanel.begin { [weak self] (result) in
        if result == .OK, let selectedURL = openPanel.url {
            // Bookmark the URL for persistent access
            do {
                let bookmarkData = try selectedURL.bookmarkData(options: .withSecurityScope, includingResourceValuesForKeys: nil, relativeTo: nil)
                UserDefaults.standard.set(bookmarkData, forKey: "ChromeNativeMessagingHostsBookmark")
                self?.accessDirectory(selectedURL)
            } catch {
                print("Failed to create security-scoped bookmark: \(error)")
            }
        }
    }
}

func accessDirectory(_ url: URL) {
    // Example of how to access the directory later using the bookmark
    do {
        // Start accessing the security-scoped resource
        guard url.startAccessingSecurityScopedResource() else {
            print("Failed to access security-scoped resource")
            return
        }
        
        // Now you have access to the directory, you can read/write files
        let fileManager = FileManager.default
        let contents = try fileManager.contentsOfDirectory(at: url, includingPropertiesForKeys: nil)
        print("Contents of directory: \(contents)")
        
        // When done, release the access
        url.stopAccessingSecurityScopedResource()
    } catch {
        print("Error accessing directory: \(error)")
        url.stopAccessingSecurityScopedResource()
    }
}
Sending messages from Google Chrome extension to macOS app
 
 
Q