objective-cstdinchrome-native-messaging

Objective C : How to keep listening to incoming data on stdin?


I am trying to call a MacOS app written in Objective C from a Browser extension using native messaging.

So far I have come up with this code which handles requests made with chrome.runtime.sendNativeMessage.

int main(int argc, char * argv[]) {
  @autoreleasepool {
    NSFileHandle *stdIn = [NSFileHandle fileHandleWithStandardInput];
    NSError * stdinError = nil;
    NSData * rawReqLen = [stdIn readDataUpToLength:4 error:&stdinError];
    if(rawReqLen == nil || stdinError != nil) return 1;
    NSUInteger reqLen;
    [rawReqLen getBytes:&reqLen length:4];

    NSData * req = [stdIn readDataUpToLength:reqLen error:&stdinError];
    if(stdinError != nil) return 1;

    handleRequest(req); // this does something depending on the received request

    return 0;
  }
}

This is working.

I need help figuring out how to use chrome.connectNative to maintain the connection open. Specifically, my problem is to let the MacOS app keep listening to incoming messages until the port is closed by a call to port.disconnect from the browser extension.

I've tried this code which does not work.

int main(int argc, char * argv[]) {
  @autoreleasepool {
    NSFileHandle *stdIn = [NSFileHandle fileHandleWithStandardInput];
  
    [[NSNotificationCenter defaultCenter] addObserverForName:NSFileHandleDataAvailableNotification
                                                          object:stdIn
                                                         queue:[NSOperationQueue mainQueue]
                                                      usingBlock:^(NSNotification *note) {
      NSError * stdinError = nil;
      NSData * rawReqLen = [stdIn readDataUpToLength:4 error:&stdinError];
      if(rawReqLen == nil || stdinError != nil) exit(1);
      NSUInteger reqLen;
      [rawReqLen getBytes:&reqLen length:4];

      NSData * req = [stdIn readDataUpToLength:reqLen error:&stdinError];
      if(stdinError != nil) exit(1);

      handleRequest(req);
    }];
    [stdIn waitForDataInBackgroundAndNotify];
  }
}

Does someone know how to achieve this ?


Solution

  • The solution is:

    int main(int argc, char * argv[]) {
      @autoreleasepool {
        NSFileHandle *stdIn = [NSFileHandle fileHandleWithStandardInput];
      
        [[NSNotificationCenter defaultCenter] addObserverForName:NSFileHandleDataAvailableNotification
                                                              object:stdIn
                                                             queue:[NSOperationQueue mainQueue]
                                                          usingBlock:^(NSNotification *note) {
          NSError * stdinError = nil;
          NSData * rawReqLen = [stdIn readDataUpToLength:4 error:&stdinError];
          if(rawReqLen == nil || stdinError != nil) exit(1);
          uint32_t reqLen;
          [rawReqLen getBytes:&reqLen length:4];
          reqLen = OSSwapLittleToHostInt32(reqLen);
    
          NSData * req = [stdIn readDataUpToLength:reqLen error:&stdinError];
          if(stdinError != nil) exit(1);
    
          handleRequest(req);
          [stdIn waitForDataInBackgroundAndNotify];
        }];
        [stdIn waitForDataInBackgroundAndNotify];
      }
    
      NSRunLoop *loop = [NSRunLoop currentRunLoop];
      [loop acceptInputForMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
      [loop run];
      return 0;
    }
    
    

    Calling exit(1) could also be replaced by a new call to [stdIn waitForDataInBackgroundAndNotify]; if you want to keep the port opened instead.

    This handles incoming requests well, but breaks the response: writing to stdout does not work anymore, I might need to stop the run loop for each response and restart it when done. (not tested yet)