JavaScriptBridge: explicit page integration
Global APIs, callbacks, byte ranges, Eval and Web limitations.
JavaScriptBridge: explicit page integration
JavaScriptBridge connects a Web build to page JavaScript. It does not turn a native application into a browser or make browser permissions unnecessary. The installed C# SDK 1.0.0 exposes this API, but its C# exporter does not implement Web. This article does not claim a working C# browser export.
Check the actual target and build
A desktop class/singleton can exist with stub methods. On non-Web targets, Eval/GetInterface/CreateCallback produce empty results; DownloadBuffer and ForceFsSync do nothing, and PwaUpdate is unavailable. A Web template built without javascript_eval also stubs the object/evaluation bridge. A web feature tag identifies the platform, not whether that build enabled every bridge function.
Keep a fallback when page integration is unavailable. Do not use a null result as a reliable distinction between disabled support, absent globals, JavaScript null, unsupported return types or an evaluation exception: several paths produce an empty Variant.
Expose a small page adapter
GetInterface looks up one exact window[name]; it does not interpret dotted paths such as app.sdk. CreateObject uses new window[name](...args). Define an explicit page-global adapter before the game asks for it; a module-local binding is not automatically that global. Keep library loading/readiness separate from game startup.
The adapter below is useful for passing only the intended byte range to the engine. It was executed by the bridge probe:
// Include in the page before the game requests GetInterface("ReckfellPage").
window.ReckfellPage = Object.freeze({
copyViewBytes(view) {
if (!ArrayBuffer.isView(view)) throw new TypeError('Expected an ArrayBuffer view');
return new Uint8Array(view.buffer, view.byteOffset, view.byteLength).slice();
}
});Retrieve it as GetInterface("ReckfellPage"), then call copyViewBytes with the JavaScript view. The returned Uint8Array is a fresh byte copy. Use IsJsBuffer before JsBufferToPackedByteArray when the value may not be a buffer. Do not read through a released wrapper.
Current byte-range limitation: Eval and JsBufferToPackedByteArray normalize DataView and non-Uint8Array views using the entire backing ArrayBuffer. A view of bytes [20,30] inside [10,20,30,40] reproduced all four bytes. The exact adapter copied only [20,30]. Normalize the view in JavaScript first; this is a documented workaround, not an engine repair. Typed-array elements are not automatically a platform-independent serialized numeric format: define byte order and schema separately.
Values and Eval are different paths
| Path | Relevant behavior |
|---|---|
| Object/method bridge | Null/undefined become nil; integer numbers use an integer tag, fractional numbers a floating tag; objects/functions are proxied. |
| Eval | Numbers use a floating tag; strings and byte buffers have dedicated paths; ordinary objects/functions return nil. |
| Big integers | Do not assume arbitrary precision survives the native Variant boundary. Define a string encoding for identifiers outside the supported integer range. |
| Exceptions | Current JS reports errors; empty/error results are not a successful application response. |
The JS Eval branch emits a boolean tag, but the current C++ branch returns a uint32 field into Variant. Native boolean type fidelity was not executed here; validate the actual return type rather than relying on the imported blanket bool promise.
Eval defaults to local execution; its optional global flag changes namespace effects. It is not an object-wrapper substitute. Prefer named adapter methods and pass values as arguments instead of constructing executable strings from external text. The examples removed from this article referenced missing code and did not establish a working export.
Callbacks and lifetime
CreateCallback creates the JavaScript callback wrapper; GetInterface does not. The receiving Callable must accept one engine Array containing all JavaScript arguments, even when JavaScript calls with several arguments. Validate count/types before indexing. Keep the returned JavaScriptObject alive for the entire listener/promise lifetime.
For cleanup, first stop the external subscription/listener or prevent further delivery, then release your own callback reference. Releasing the wrapper does not call removeEventListener for you. The JS probe confirmed that a retained JavaScript function stops dispatching after its wrapper handle is released; this does not prove safe native asynchronous cleanup in every case.
Do not design a synchronous JavaScript result around the callback's managed return value: current native _callback invokes the Callable but constructs an empty return Variant separately. Proxy-to-pthread builds can defer callback delivery to the engine thread. This native behavior was source-reviewed, not executed through WASM/C#.
Downloads, storage and PWA
DownloadBuffer requests a browser Blob/link download using bytes, filename and MIME type; it does not confirm that a file was saved on disk. The default MIME is application/octet-stream. Initiate downloads from a user-facing action and confirm the actual file; browser permission/policy remains relevant.
ForceFsSync schedules filesystem synchronization when persistence is available; it returns no completion receipt. It is not a file download. See browser project storage and backup.
PwaUpdateAvailable/PwaNeedsUpdate concern a waiting PWA update, not the engine product version or a general reload signal. Save state and handle PwaUpdate's result. A non-Web stub returns an unavailable error; no service-worker update flow was executed in this review.
Diagnosis and verification
If GetInterface fails, check exact window name, script readiness, Web build and javascript_eval. If callbacks are silent, check CreateCallback, the single Array parameter, retained wrapper and listener lifetime. If bytes include unrelated data, normalize the view's byteOffset/byteLength first.
Actual engine JS bodies and the exact adapter ran 21 checks in Node with mock native memory/handles. The byte-range defect remains a failed acceptance condition despite the adapter passing. Native C++/C# ABI, real browser/WASM export, callback cleanup, download, permissions, storage and PWA were not accepted. Engine Source, Runtime and SDK were unchanged.