Search code examples
c++node.jscocoav8objective-c++

Node js native module, triggering callback from objective-c block event listener doesn't work


I need to create a native node module that listens to an objective-c OSX event, and triggers a callback to javascript every time it happens:

nativeAddon.listen(() => {
    console.log('It works!')
})

The callback works when immediately called in the setUpCallback function, but it doesn't fire from the objective-c observer block.

Here's what my main.mm file looks like

using namespace v8;

Local<Function> event_callback;

void setUpCallback(const FunctionCallbackInfo<Value>& args) {
    Isolate* isolate = args.GetIsolate();
    HandleScope scope(isolate);

    // Store the callback to be used in the runCallback function
    Local<Function> cb = Local<Function>::Cast(args[0]);
    event_callback = cb;

    // THIS WORKS
    runCallback();

    // Listen to a mac event and trigger the callback when it happens
    [[[NSWorkspace sharedWorkspace] notificationCenter] addObserverForName:NSWorkspaceActiveSpaceDidChangeNotification object:NULL queue:NULL usingBlock:^(NSNotification *note) {
        // THIS DOESN'T WORK
        runCallback();
    }];
}

void runCallback() {
    auto isolate = Isolate::GetCurrent();
    HandleScope scope(isolate);

    Local<Context> context = isolate->GetCurrentContext();

    Local<Value> argv[1] = { String::NewFromUtf8(isolate, "hello world", NewStringType::kNormal).ToLocalChecked() };

    auto fn = Local<Function>::New(isolate, event_callback);
    fn->Call(context, Null(isolate), 1, argv).ToLocalChecked();
}

void Initialize(Local<Object> exports) {
    NODE_SET_METHOD(exports, "listen", setUpCallback);
}

NODE_MODULE(addon, Initialize)

Any help would be greatly appreciated!


Solution

  • Try changing the type of event_callback from Local<Function> to Persistent<Function>.

    The lifetime of a Local is tied to that of the HandleScope it was created in. Once the HandleScope goes out of scope (in your case, at the end of function setUpCallback), all Locals created while it was active become invalid. If you need a handle that persists after the HandleScope went away, you need to use a Persistent. More details here: https://v8.dev/docs/embed#advanced-guide.