docs

Emitting Events

Push events from native code to the page and handle them in JavaScript.

On this page

You can push events from native code to the page whenever native state changes, such as when a save finishes or a download progresses. That prevents the page from having to poll native code for updates.

Events travel in one direction— native code emits the event, and the page listens on the API's namespace object.

Emitting an Event

Call api.Emit() with an event name and any payload values:

C++
api.Emit("saved", std::string("slot1.dat"));

You can call api.Emit() from any thread. Arguments are copied during the call, so values you pass don't need to outlive it.

Payload types convert to JavaScript types the same way as exposed-function parameters (see Passing Data Across the Bridge). You can pass up to 16 payload arguments.

Events are delivered on the Renderer's thread during a later call to Renderer::Update(). The event reaches every attached page that held the bindings when api.Emit() was called.

📘 Native Values Only

You can't pass a js::Value to api.Emit() (doing so causes a build error). An emitted event reaches every attached page, while a js::Value belongs to a single page. Pass native types like numbers, strings, containers, or structs instead.

Listening on the Page

Subscribe to events on the API's namespace object using on() or once():

JavaScript
myApp.on('saved', (path) => refreshList(path));    // every save
myApp.once('saved', () => showToast("Saved"));    // the next save only

The listener receives the payload values that native code emitted. If a listener throws an error, the other listeners still run.

For nested event names like fs.changed, the page can subscribe to the full path on the root namespace object (myApp.on('fs.changed', fn)). If bindings exist under fs, the page can also subscribe on the child namespace (myApp.fs.on('changed', fn)).

🚧 Reserved Method Names

The names on, once, and off are reserved on every namespace object. A native binding or child namespace with one of these names is still registered, but it replaces the event method on that object. The renderer logs a warning if diagnostics are set to Warn or Strict (at Off, the method is replaced silently).

Removing Listeners

Call off() to remove a specific listener or all listeners for an event:

JavaScript
function onSaved(path) { refreshList(path); }

myApp.on('saved', onSaved);

// Later:
myApp.off('saved', onSaved);  // remove this listener
myApp.off('saved');           // remove every listener for 'saved'

Removing a single listener requires passing the same function object that was registered, so you'll need to use a named function rather than an inline arrow function.

Missed Events

Events are not queued or replayed— if a page isn't listening when an event is delivered, it misses that event. A page that subscribes later receives only subsequent events.

Restoring from the Back-Forward Cache

Re-subscribe to events in a pageshow handler when a page is restored from the back-forward cache:

JavaScript
window.addEventListener('pageshow', (event) => {
  if (event.persisted)
    myApp.on('saved', onSaved);
});

A page restored from the back-forward cache keeps its API bindings, but it loses its event subscriptions.

Check event.persisted before re-subscribing— regular page loads also fire pageshow, but with persisted set to false.

Declaring Events

Declare an event and its payload types using DefineEvent():

C++
api.DefineEvent<std::string>("saved", js::Param("path"),
                             js::Doc("A save finished."));

Declaring events is optional— events work without declaration. Pass payload types as template arguments, with optional parameter names in js::Param and documentation in js::Doc.

Declaring an event includes it in the API's schema. Generated TypeScript declarations gain typed on(), once(), and off() overloads (see API Schemas and TypeScript), and diagnostics check event names (see JavaScript Errors and Diagnostics).

A declaration only describes the event. Calling api.Emit() doesn't check its arguments against declared types, and declarations cannot be removed with api.Unbind().

🚧 Declare All Events or None

Declaring an event enables name checks in diagnostics at Warn and Strict levels. The renderer then logs a warning for every api.Emit() call or page subscription for an undeclared event. You should declare every event the API uses, or none of them.

Requesting Data from the Page

Events only travel from native code to the page.

To get an answer back, emit a request event carrying an ID and have the page reply through an exposed function:

C++
api["answerInventory"] = [](double request_id,
                            std::vector<std::string> items) {
  OnInventoryAnswer(request_id, items);
};
api.Emit("queryInventory", next_request_id++);

On the page, listen for the request event and return the data with that ID:

JavaScript
myApp.on('queryInventory', (id) => myApp.answerInventory(id, collectItems()));

Native code then matches the reply to the original request using the ID.