LaunchAgent in Objective C, Sandboxed App in Swift, how to connect

Hi,


the LaunchAgent is implemented with this (cutout):


@interface ServiceDelegate : NSObject 
@end

@implementation ServiceDelegate
- (BOOL)listener:(NSXPCListener *)listener shouldAcceptNewConnection:(NSXPCConnection *)newConnection {
    newConnection.exportedInterface = [NSXPCInterface interfaceWithProtocol:@protocol(pEpMacOSAdapterProtocol)];
    pEpMacOSAdapter *exportedObject = [pEpMacOSAdapter new];
    newConnection.exportedObject = exportedObject;
    [newConnection resume];
    return YES;
}

@end

int main(int argc, const char *argv[])
{
    ServiceDelegate *delegate = [ServiceDelegate new];
    NSXPCListener *listener = [[NSXPCListener alloc] initWithMachServiceName:@"foundation.pEp.adapter.macOS"];
    [listener resume];
    [[NSRunLoop currentRunLoop] run];
    return 0;
}


<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
    <dict>        <key>Label</key>
        <string>foundation.pEp.adapter.macOS</string>
        <key>ProgramArguments</key>
        <array>
            <string>/Library/Application Support/pEp/foundation.pEp.adapter.macOS</string>
        </array>
        <key>MachServices</key>
        <dict>
             <key>foundation.pEp.adapter.macOS</key>
             <true/>
        </dict>
    </dict>
</plist>

The App is implemented with this (cutout):


func proxyErrorHandler(err: Error) -> Void {
    NSLog("%@", err.localizedDescription)
    statusText.title = NSLocalizedString("Connection failed", comment: "")
}

internal func applicationDidFinishLaunching(_ aNotification: Notification) {
    connection = NSXPCConnection.init(machServiceName: "foundation.pEp.adapter.macOS")
    if connection != nil {
        connection.remoteObjectInterface = NSXPCInterface.init(with: pEpMacOSAdapterProtocol.self)
        connection.resume()
        statusText.title = NSLocalizedString("Connecting…", comment: "")
        proxy = connection.remoteObjectProxyWithErrorHandler(proxyErrorHandler) as? pEpMacOSAdapterProtocol
        proxy?.subscribeForUpdate(downloadNotification: notifyDownload)
    }
    else {
        NSLog("pEpNotifications: %@", "cannot connect to pEp.foundation.adapter.macOS")
    }
}


I'm always getting proxyErrorHandler() called with "Couldn’t communicate with a helper application."


Any ideas?

Accepted Reply

it's the right thing to do.

Cool. In that case you need to punch a whole through your sandbox so that your app can connect to the XPC service being advertised by your agent. You can do this using the global Mach service temporary exception (

com.apple.security.temporary-exception.mach-lookup.global-name
).

Outgoing Connections (Client) is checkmarked.

That is only relevant for networking connections, not for XPC.

I'm unsure if this can fit into App Store what we're doing.

A

launchd
agent is definitely incompatible with the Mac App Store. However, Mac App Store apps can run code at login using an ServiceManager login item. You can even arrange for these to advertise an XPC service that your app can use to communicate with it. See
SMLoginItemSetEnabled
and AppSandboxLoginItemXPCDemo.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

Replies

Are you sandboxing because it’s the right thing to do? Or sandboxing because you plan to deploy via the Mac App Store?

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

I find sandboxing a good idea, so it's because it's the right thing to do. I'm unsure if this can fit into App Store what we're doing.


BTW: Outgoing Connections (Client) is checkmarked.


I already was trying to just remove the sandbox information, clean and rebuild. But it made no difference, unfortunately.

it's the right thing to do.

Cool. In that case you need to punch a whole through your sandbox so that your app can connect to the XPC service being advertised by your agent. You can do this using the global Mach service temporary exception (

com.apple.security.temporary-exception.mach-lookup.global-name
).

Outgoing Connections (Client) is checkmarked.

That is only relevant for networking connections, not for XPC.

I'm unsure if this can fit into App Store what we're doing.

A

launchd
agent is definitely incompatible with the Mac App Store. However, Mac App Store apps can run code at login using an ServiceManager login item. You can even arrange for these to advertise an XPC service that your app can use to communicate with it. See
SMLoginItemSetEnabled
and AppSandboxLoginItemXPCDemo.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

Thank you! That was the right hint. Unfortunately, this is only solving half of my problem.


I'm writing a sandboxed app, which should show notifications when a LaunchAgent does actions. Therefore, the app registers with the LaunchAgent for being notified.


To implement this I'm using XPC. The first step is done and works: the app can now register with the LaunchAgent.


With the second step I'm running into troubles again. I'm implementing a publish-subscribe pattern. So there is an interface the LaunchAgent is providing. There the app calls subscribe. For the other direction of the message flow the app offers an interface itself, which is offered via anonymous listener endpoint. The endpoint is the parameter for the subscribe message.


Now the app has its own listener offering an object with an interface. The LaunchAgent sends the notification message. There is no error. But the message never arrives at the app. And therefore, shouldAcceptNewConnection is never called within the app.


Is there another hint you can give me? I'm unsure if this is related to sandboxing, too, or if I'm doing things wrong.

Is there another hint you can give me?

No. The process you’ve described should work just fine, even in a sandboxed app.

One trick for debugging problems like this is to create a test app that contains both the client and the server, and then use your anonymous technique to establish a loopback version of the client-to-server connection. After that setup you’re running your standard client and server code, at which point you can start testing your notification code.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"