Emitting Events
Push events from native code to the page and handle them in JavaScript.
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:
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::Valuetoapi.Emit()(doing so causes a build error). An emitted event reaches every attached page, while ajs::Valuebelongs 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():
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, andoffare 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 toWarnorStrict(atOff, the method is replaced silently).
Removing Listeners
Call off() to remove a specific listener or all listeners for an event:
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:
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():
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
WarnandStrictlevels. The renderer then logs a warning for everyapi.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:
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:
myApp.on('queryInventory', (id) => myApp.answerInventory(id, collectItems()));
Native code then matches the reply to the original request using the ID.