JavaScript API reference¶
This page documents the raw JavaScript surface exposed by the EngawaUI Unreal
plugin and the named-event facade supplied by @studiouwu/engawa.
For step-by-step setup, see Send data between JavaScript and Unreal and Use the EngawaUI Event component.
Availability¶
The bridge is installed on the main page before its authored scripts execute:
Because a browser Window is the global object, window.engawa refers to the
same value.
const isRunningInEngawaUI = Boolean(
globalThis.engawa && typeof globalThis.engawa.postMessage === "function"
);
The bridge does not exist when the HTML file is opened in Chrome, Edge, Firefox, Safari, or another ordinary browser.
globalThis.engawa¶
engawa is a frozen object installed as a read-only, non-configurable global.
Application code cannot replace it or add methods to it.
Conceptual type declaration:
interface EngawaUIBridge {
postMessage(value: JSONValue): void;
}
type JSONPrimitive = string | number | boolean | null;
type JSONValue =
| JSONPrimitive
| JSONValue[]
| { [key: string]: JSONValue };
declare const engawa: Readonly<EngawaUIBridge>;
engawa.postMessage(value)¶
Sends one JSON-compatible JavaScript value from the page to the owning Engawa UI Component.
| Detail | Behavior |
|---|---|
| Parameters | Exactly one JSON-compatible value |
| Return value | JavaScript undefined after synchronous native admission |
| Unreal receiver | On Page Message(Json Value) |
| Unreal value type | Serialized JSON text |
| Delivery | Asynchronous, bounded, at-most-once |
The bridge applies JSON.stringify internally. Pass objects and arrays
directly rather than stringifying them first.
Serialization happens before the native call, so postMessage throws
synchronously when JSON.stringify throws—for example, for a cycle or
BigInt. It also throws synchronously if the native bridge rejects admission.
Only JSON-compatible top-level values are supported; values such as
undefined or a function do not produce a JSON string and are not valid
messages. On success, the function returns JavaScript undefined; there is no
result or acknowledgement channel in that return value.
Normal JavaScript JSON behavior still applies. For example, a finite number
remains a number, unsupported object properties may be omitted, and NaN or
infinity serialize as null. Prefer deliberately constructed data-only
objects instead of relying on those conversions.
try {
engawa.postMessage({ type: "ready", value: 42 });
} catch (error) {
console.error("The message could not be serialized or admitted", error);
}
Do not treat a non-throwing call as an acknowledgement from Blueprint. If application confirmation matters, send a separate response message carrying your own request ID.
engawa-message¶
Unreal-to-page messages dispatch a CustomEvent on window/globalThis:
function onEngawaMessage(event) {
console.log(event.detail);
}
window.addEventListener("engawa-message", onEngawaMessage);
// Later, if this code no longer owns the listener:
window.removeEventListener("engawa-message", onEngawaMessage);
event.detail is the parsed value represented by the JSON string passed to
Blueprint's Post Message to Page node.
| Blueprint JSON input | JavaScript event.detail type |
|---|---|
{"health":100} |
Object |
[1,2,3] |
Array |
"hello" |
String |
42 |
Number |
false |
Boolean |
null |
null |
The DOM event does not carry a return channel. Send a separate page message if the page needs to acknowledge the host message.
@studiouwu/engawa and globalThis.EngawaUIEvents¶
The native runtime installs globalThis.engawa, but it does not install the
named-event facade. The recommended setup is to add the published package to a
Vite or other bundled web project:
Import its default export in the application entry module:
That import creates the facade and also exposes the same object as
globalThis.EngawaUIEvents. The package includes TypeScript declarations for
the facade, window.engawa, and the engawa-message DOM event.
The package import must be resolved by a bundler; EngawaUI does not resolve a
bare package specifier in page JavaScript. Build the application and import the
bundler's output instead of trying to expose a node_modules tree to the page.
Type application events with the package¶
The package supports declaration merging for application-specific payloads.
For example, create src/engawa-events.d.ts:
import "@studiouwu/engawa";
declare global {
namespace EngawaUI {
interface Events {
"inventory.updated": {
itemId: string;
count: number;
};
}
interface GameplayTagEvents {
"UI.Inventory.Open": {
slot: number;
};
}
}
}
export {};
Calls using those names now receive their declared payload types through the
imported facade and globalThis.EngawaUIEvents.
Use the global build without installing through npm¶
Download the pinned standalone browser build, save it inside the local web source, and load it before application code:
Do not use the CDN URL directly in an EngawaUI page; remote resources are blocked. The local standalone script installs the facade, which plain JavaScript can read explicitly:
TypeScript users who choose this vendored-global path must provide a custom
ambient declaration. The event setup guide
contains a complete engawa-events.d.ts. That file supplies compile-time
types only; it cannot install the runtime facade.
Conceptual declaration:
type EngawaUIEventListener = (
payload: JSONValue,
eventName: string
) => void;
type EngawaUIUnsubscribe = () => boolean;
interface EngawaUIReceivedEvent {
readonly eventName: string;
readonly payload: JSONValue;
}
interface EngawaUIWaitOptions {
readonly signal?: AbortSignal;
readonly timeoutMs?: number;
}
interface EngawaUIEventChannel {
emit(eventName: string, payload?: JSONValue): true;
addListener(
eventName: string,
listener: EngawaUIEventListener
): EngawaUIUnsubscribe;
once(
eventName: string,
listener: EngawaUIEventListener
): EngawaUIUnsubscribe;
removeListener(
eventName: string,
listener: EngawaUIEventListener
): boolean;
clearListeners(eventName?: string): void;
waitFor(
eventName: string,
options?: EngawaUIWaitOptions
): Promise<EngawaUIReceivedEvent>;
}
interface EngawaUIEventsFacade extends EngawaUIEventChannel {
readonly protocolVersion: 1;
readonly gameplayTags: Readonly<EngawaUIEventChannel>;
}
declare var EngawaUIEvents: Readonly<EngawaUIEventsFacade>;
EngawaUIEvents.protocolVersion¶
The facade's numeric event-envelope version. Its current value is 1.
Do not use this value to invent a different envelope. Keep the npm package or standalone browser build compatible with the EngawaUI plugin version.
EngawaUIEvents.emit(eventName, payload?)¶
Sends a simple named event from JavaScript to an EngawaUI Event component.
EngawaUIEvents.emit("inventory.open", { slot: 3 });
EngawaUIEvents.emit("menu.closed"); // payload becomes null
| Detail | Behavior |
|---|---|
| Name matching | Case-sensitive |
| Missing payload | JSON null |
| Success return | true after validation and a synchronous call to the raw bridge |
| Failure | Throws for an invalid name, missing bridge, serialization failure, or synchronous native admission failure |
| Unreal receiver | On Event Received(Event Name, Json Payload) |
Its true return is not proof that a named-event component or Blueprint
handler processed the event, and it is not an application acknowledgement.
EngawaUIEvents.addListener(eventName, listener)¶
Listens for a simple event emitted by an EngawaUI Event component.
function onInventoryUpdated(payload, eventName) {
console.log(eventName, payload);
}
const unsubscribe = EngawaUIEvents.addListener(
"inventory.updated",
onInventoryUpdated
);
The callback receives the parsed payload first and the event name second.
The method returns an idempotent unsubscribe function:
unsubscribe(); // true if it removed the subscription
unsubscribe(); // false; it was already removed
EngawaUIEvents.once(eventName, listener)¶
Registers a listener that removes itself before its first callback runs.
EngawaUIEvents.once("dialog.initialized", (payload, eventName) => {
console.log(eventName, payload);
});
It returns the same kind of unsubscribe function as addListener, so the
one-shot listener can also be cancelled before it fires.
EngawaUIEvents.waitFor(eventName, options?)¶
Returns a promise for the next matching event and removes its internal listener after the promise settles:
const response = await EngawaUIEvents.waitFor("inventory.response", {
timeoutMs: 5000
});
console.log(response.eventName, response.payload);
timeoutMs must be finite and non-negative. The promise rejects when that
timeout expires. Pass an AbortSignal as signal when application lifecycle
code needs to cancel the wait. If the signal is already aborted, the returned
promise rejects immediately with the signal's reason.
Call waitFor before emitting a request that could produce the response. Like
the other listener APIs, waitFor does not turn EngawaUI events into RPCs; the
application still defines the matching response event and request ID.
EngawaUIEvents.removeListener(eventName, listener)¶
Removes the exact function previously registered for the exact event name.
function onUpdate(payload) {
console.log(payload);
}
EngawaUIEvents.addListener("inventory.updated", onUpdate);
const removed = EngawaUIEvents.removeListener("inventory.updated", onUpdate);
Returns true when the listener existed and was removed; otherwise returns
false. An anonymous function cannot be removed later unless its reference was
saved.
EngawaUIEvents.clearListeners(eventName?)¶
With a name, removes every simple listener for that exact event:
With no argument, removes every simple-channel listener:
It does not clear Gameplay Tag listeners. Use the method on
EngawaUIEvents.gameplayTags for that channel.
EngawaUIEvents.gameplayTags¶
The gameplayTags object has the same six methods as the simple channel,
including waitFor. It uses the gameplay-tag envelope channel and is paired
with the EngawaUI Gameplay Tag Event component.
EngawaUIEvents.gameplayTags.emit(
"UI.Inventory.Item.Selected",
{ itemId: "potion-small" }
);
const unsubscribe = EngawaUIEvents.gameplayTags.addListener(
"UI.Inventory.Item.Confirmed",
(payload, eventName) => {
console.log(eventName, payload);
}
);
Gameplay Tag listener lookup is case-insensitive. Unreal accepts page tags only when an exact registered tag is selected in the component's Gameplay Tags To Listen For container. An empty container accepts none.
See Use Gameplay Tag events for the Unreal setup.
Event-name validation¶
Both facade channels require an event name that is:
- a string;
- non-empty;
- no more than 256 UTF-16 code units (
eventName.length); and - free of control characters from U+0000 through U+001F.
An invalid name throws TypeError in JavaScript. Simple names are
case-sensitive; Gameplay Tag listener names are case-insensitive.
Listener scheduling and errors¶
The facade takes a stable snapshot of matching listeners, then invokes them in a JavaScript microtask. As a result:
- a native
engawa-messagedispatch does not directly re-enter application callbacks; - adding or removing a listener during a callback does not alter the current snapshot;
- one listener throwing does not prevent the remaining listeners from running; and
- a thrown listener error is reported asynchronously with
setTimeout.
The facade records its page-owned router on the Window. Reinstalling it—by loading the standalone build or importing a fresh bundled entry—removes the previous router and clears its listeners before installing the replacement.
Protocol envelope¶
The facade serializes this envelope:
interface EngawaUIEventEnvelope {
protocol: "engawaui.event";
version: 1;
channel: "event" | "gameplay-tag";
event: string;
payload: JSONValue;
}
Example:
{
"protocol": "engawaui.event",
"version": 1,
"channel": "event",
"event": "inventory.open",
"payload": {
"slot": 3
}
}
The Unreal component returns only event and a reserialized compact
payload to Blueprint. A missing envelope payload is normalized to JSON
null.
Use the facade rather than manually building envelopes. Wrong versions, missing fields, malformed JSON, and the wrong channel are invalid or ignored by the corresponding component.
Unreal-side API map¶
| Direction/use | Blueprint node or event | Meaning |
|---|---|---|
| Page → Unreal, raw | On Page Message | Complete serialized JSON value |
| Unreal → page, raw | Post Message to Page | Accept a strict JSON value string for async delivery |
| Page → Unreal, named | On Event Received | Simple event name + compact JSON payload |
| Unreal → page, named | Emit Event | Prepare a named event asynchronously |
| Event failure | On Event Error | Validation, readiness, size, source, or queue error |
| Page → Unreal, tag | On Gameplay Tag Event Received | Typed registered tag + compact JSON payload |
| Unreal → page, tag | Emit Gameplay Tag Event | Emit a typed registered tag |
| One-off script | Evaluate JavaScript Async | Return request ID immediately |
| Script result | On JavaScript Result | Request ID, success, JSON result, exception |
All of these Blueprint delegates run on Unreal's game thread.
Evaluate JavaScript Async¶
JavaScript evaluation is a Blueprint/C++ component operation rather than a page global, but its result follows the same JSON contract.
Evaluate JavaScript Async(Script) → Request Id
On JavaScript Result(Request Id, Success, Json Result, Exception)
Rules:
- call it after On Page Loaded;
- request ID
0means the call was rejected; - correlate every result with its request ID;
- successful values are returned as serialized JSON;
undefinedbecomesnull;- thrown errors and non-JSON-compatible return values set Success = false;
- plain values and promises that settle in the bounded evaluation turn are supported; arbitrary asynchronous promises are not waited on; and
- EngawaUI has no synchronous JavaScript execution API.
Example script:
Possible successful Json Result:
Limits and delivery guarantees¶
For EngawaUI Event and EngawaUI Gameplay Tag Event:
- Max Event Bytes defaults to 65,536 bytes and is clamped to 1,024–65,536;
- the limit covers the complete UTF-8 envelope;
- each component keeps at most 64 active/queued preparation items per direction;
- one item per direction is prepared at a time, preserving that component's order;
- runtime transport queues impose additional limits;
- a Blueprint emit's
trueoutput means accepted for component preparation, while a JavaScript facade emit'strueonly means it called the raw bridge without a synchronous exception; - delivery is at-most-once, with no automatic retry or acknowledgement; and
- separate components and raw/navigation/evaluation APIs have independent queues, so cross-API order is not guaranteed.
For reliable application confirmation, include a request ID in the JSON payload and send a separate response event.
Quick examples¶
Page action to Blueprint¶
Blueprint receives:
Blueprint state to page¶
Blueprint emits:
Page listener:
EngawaUIEvents.addListener("player.stats.changed", (stats) => {
document.querySelector("#health").textContent = String(stats.health);
document.querySelector("#armor").textContent = String(stats.armor);
});