@swmansion/popcorn 0.2.0-rc.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Binary file
@@ -0,0 +1,22 @@
1
+ import { type IframeRequest, type IframeResponse, type AnySerializable } from "./types";
2
+ export type IframeBridgeArgs = {
3
+ container: HTMLElement;
4
+ config: Record<string, string>;
5
+ script: {
6
+ url: string;
7
+ entrypoint: string;
8
+ };
9
+ debug: boolean;
10
+ onMessage: (data: IframeResponse) => void;
11
+ };
12
+ export declare function sendIframeResponse(type: string, data: AnySerializable): void;
13
+ export declare class IframeBridge {
14
+ private iframe;
15
+ private handlerRef;
16
+ private debug;
17
+ private onMessage;
18
+ constructor(args: IframeBridgeArgs);
19
+ sendIframeRequest(data: IframeRequest): void;
20
+ deinit(): void;
21
+ private messageHandler;
22
+ }
@@ -0,0 +1,68 @@
1
+ import { isMessageType } from './types.mjs';
2
+ import { throwError } from './utils.mjs';
3
+
4
+ const STYLE_HIDDEN = "visibility: hidden; width: 0px; height: 0px; border: none";
5
+ function sendIframeResponse(type, data) {
6
+ window.parent.postMessage({ type, value: data });
7
+ }
8
+ class IframeBridge {
9
+ iframe;
10
+ handlerRef;
11
+ // @ts-expect-error TODO: use for tracing
12
+ debug;
13
+ onMessage;
14
+ constructor(args) {
15
+ const { container, config, script, debug, onMessage } = args;
16
+ this.debug = debug;
17
+ this.onMessage = onMessage;
18
+ this.iframe = document.createElement("iframe");
19
+ this.iframe.srcdoc = `
20
+ <html lang="en" dir="ltr">
21
+ <head>
22
+ ${metaTagsFrom(config)}
23
+ </head>
24
+ <body>
25
+ <script type="module" defer>
26
+ import { ${script.entrypoint} } from "${script.url}";
27
+ ${script.entrypoint}();
28
+ </script>
29
+ </body>
30
+ </html>`;
31
+ this.iframe.style = STYLE_HIDDEN;
32
+ this.handlerRef = this.messageHandler.bind(this);
33
+ window.addEventListener("message", this.handlerRef);
34
+ // mount
35
+ container.appendChild(this.iframe);
36
+ }
37
+ sendIframeRequest(data) {
38
+ const w = this.iframe.contentWindow;
39
+ if (w === null)
40
+ throwError({ t: "assert" });
41
+ w.postMessage(data);
42
+ }
43
+ deinit() {
44
+ window.removeEventListener("message", this.handlerRef);
45
+ this.iframe.remove();
46
+ }
47
+ messageHandler({ data }) {
48
+ if (isIframeResponse(data)) {
49
+ this.onMessage(data);
50
+ }
51
+ }
52
+ }
53
+ function isIframeResponse(payload) {
54
+ if (typeof payload !== "object" || payload === null)
55
+ return false;
56
+ if (!Object.hasOwn(payload, "type") || !Object.hasOwn(payload, "value"))
57
+ return false;
58
+ if (typeof payload.type !== "string")
59
+ return false;
60
+ return isMessageType(payload.type);
61
+ }
62
+ function metaTagsFrom(config) {
63
+ return Object.entries(config)
64
+ .map(([key, value]) => `<meta name="${key}" content="${value}" />`)
65
+ .join("\n");
66
+ }
67
+
68
+ export { IframeBridge, sendIframeResponse };
@@ -0,0 +1 @@
1
+ export declare function runIFrame(): Promise<void>;
@@ -0,0 +1,200 @@
1
+ import Module$1 from './AtomVM.mjs';
2
+ import { sendIframeResponse } from './bridge.mjs';
3
+ import { MESSAGES, HEARTBEAT_INTERVAL_MS } from './types.mjs';
4
+
5
+ // @ts-expect-error atomvm doesn't have types yet
6
+ let Module = null;
7
+ class TrackedValue {
8
+ key;
9
+ value;
10
+ constructor({ key, value }) {
11
+ if (typeof key !== "number") {
12
+ throw new Error("key property in TrackedValue must be a number");
13
+ }
14
+ this.key = key;
15
+ this.value = value;
16
+ }
17
+ }
18
+ globalThis.TrackedValue = TrackedValue;
19
+ async function runIFrame() {
20
+ const metaElement = document.querySelector('meta[name="bundle-path"]');
21
+ if (!metaElement) {
22
+ throw new Error("Missing meta[name='bundle-path'] element");
23
+ }
24
+ const bundlePath = metaElement.content;
25
+ const bundleBuffer = await fetch(bundlePath).then((resp) => resp.arrayBuffer());
26
+ const bundle = new Int8Array(bundleBuffer);
27
+ sendIframeResponse(MESSAGES.INIT, null);
28
+ const initProcess = await startVm(bundle);
29
+ window.addEventListener("message", async ({ data }) => {
30
+ const type = data.type;
31
+ if (type === MESSAGES.CALL) {
32
+ await handleCall(data.value);
33
+ }
34
+ else if (type === MESSAGES.CAST) {
35
+ handleCast(data.value);
36
+ }
37
+ });
38
+ sendIframeResponse(MESSAGES.START_VM, initProcess);
39
+ setInterval(() => sendIframeResponse(MESSAGES.HEARTBEAT, null), HEARTBEAT_INTERVAL_MS);
40
+ }
41
+ async function startVm(avmBundle) {
42
+ let resolveResultPromise = null;
43
+ const resultPromise = new Promise((resolve) => {
44
+ resolveResultPromise = resolve;
45
+ });
46
+ const moduleInstance = await Module$1({
47
+ preRun: [
48
+ function ({ FS }) {
49
+ FS.mkdir("/data");
50
+ FS.writeFile("/data/bundle.avm", avmBundle);
51
+ },
52
+ ],
53
+ arguments: ["/data/bundle.avm"],
54
+ print(text) {
55
+ sendIframeResponse(MESSAGES.STDOUT, text);
56
+ },
57
+ printErr(text) {
58
+ sendIframeResponse(MESSAGES.STDERR, text);
59
+ },
60
+ onAbort() {
61
+ // Timeout so that error logs are (hopefully) printed
62
+ // before we terminate
63
+ setTimeout(() => sendIframeResponse(MESSAGES.RELOAD, null), 100);
64
+ },
65
+ });
66
+ Module = moduleInstance;
67
+ moduleInstance["serialize"] = JSON.stringify;
68
+ moduleInstance["deserialize"] = deserialize;
69
+ moduleInstance["cleanupFunctions"] = new Map();
70
+ moduleInstance["onTrackedObjectDelete"] = (key) => {
71
+ const fns = moduleInstance["cleanupFunctions"];
72
+ const fn = fns.get(key);
73
+ fns.delete(key);
74
+ try {
75
+ fn?.();
76
+ }
77
+ catch (e) {
78
+ console.error(e);
79
+ }
80
+ finally {
81
+ moduleInstance["trackedObjectsMap"].delete(key);
82
+ }
83
+ };
84
+ const origCast = moduleInstance["cast"];
85
+ const origCall = moduleInstance["call"];
86
+ moduleInstance["cast"] = (process, args) => {
87
+ const serialized = moduleInstance.serialize(args);
88
+ origCast(process, serialized);
89
+ };
90
+ moduleInstance["call"] = (process, args) => {
91
+ const serialized = moduleInstance.serialize(args);
92
+ return origCall(process, serialized);
93
+ };
94
+ moduleInstance["onRunTrackedJs"] = (scriptString, isDebug) => {
95
+ const trackValue = (tracked) => {
96
+ const getKey = moduleInstance["nextTrackedObjectKey"];
97
+ const map = moduleInstance["trackedObjectsMap"];
98
+ if (tracked instanceof TrackedValue) {
99
+ map.set(tracked.key, tracked.value);
100
+ return tracked.key;
101
+ }
102
+ const key = getKey();
103
+ map.set(key, tracked);
104
+ return key;
105
+ };
106
+ let fn;
107
+ try {
108
+ const indirectEval = eval;
109
+ fn = indirectEval(scriptString);
110
+ }
111
+ catch (e) {
112
+ // TODO: send onEvalError for Popcorn object
113
+ console.error(e);
114
+ return null;
115
+ }
116
+ if (isDebug)
117
+ ensureFunctionEval(fn);
118
+ let result;
119
+ try {
120
+ result = fn?.(moduleInstance);
121
+ }
122
+ catch (e) {
123
+ // TODO: send onEvalError for Popcorn object
124
+ console.error(e);
125
+ return null;
126
+ }
127
+ if (isDebug)
128
+ ensureResultKeyList(result);
129
+ return result?.map(trackValue) ?? [];
130
+ };
131
+ moduleInstance["onGetTrackedObjects"] = (keys) => {
132
+ const getTrackedObject = (key) => {
133
+ const serialize = moduleInstance["serialize"];
134
+ const map = moduleInstance["trackedObjectsMap"];
135
+ return serialize(map.get(key));
136
+ };
137
+ return keys.map(getTrackedObject);
138
+ };
139
+ moduleInstance["onElixirReady"] = (initProcess) => {
140
+ moduleInstance["onElixirReady"] = null;
141
+ resolveResultPromise?.(initProcess);
142
+ };
143
+ return resultPromise;
144
+ }
145
+ async function handleCall(request) {
146
+ if (!Module) {
147
+ throw new Error("Module not initialized");
148
+ }
149
+ const { requestId, process, args } = request;
150
+ sendIframeResponse(MESSAGES.CALL_ACK, { requestId });
151
+ try {
152
+ const result = await Module.call(process, args);
153
+ sendIframeResponse(MESSAGES.CALL, {
154
+ requestId,
155
+ data: Module.deserialize(result),
156
+ });
157
+ }
158
+ catch (error) {
159
+ if (error == "noproc") {
160
+ sendIframeResponse(MESSAGES.RELOAD, null);
161
+ console.error("Runtime VM crashed, popcorn iframe reloaded.");
162
+ return;
163
+ }
164
+ sendIframeResponse(MESSAGES.CALL, {
165
+ requestId,
166
+ error: Module.deserialize(error),
167
+ });
168
+ }
169
+ }
170
+ function handleCast(request) {
171
+ if (!Module) {
172
+ throw new Error("Module not initialized");
173
+ }
174
+ const { process, args } = request;
175
+ Module.cast(process, args);
176
+ }
177
+ function ensureFunctionEval(maybeFunction) {
178
+ if (typeof maybeFunction !== "function") {
179
+ throw new Error("Script passed to onRunTrackedJs() is not wrapped in a function");
180
+ }
181
+ }
182
+ function ensureResultKeyList(result) {
183
+ if (!Array.isArray(result) && result !== undefined) {
184
+ throw new Error("Script passed to onRunTrackedJs() returned invalid value, accepted values are arrays and undefined");
185
+ }
186
+ }
187
+ function deserialize(message) {
188
+ return JSON.parse(message, (_key, value) => {
189
+ const isRef = typeof value === "object" &&
190
+ value !== null &&
191
+ Object.hasOwn(value, "popcorn_ref") &&
192
+ Object.getOwnPropertyNames(value).length == 1;
193
+ if (!isRef) {
194
+ return value;
195
+ }
196
+ return Module?.trackedObjectsMap.get(value.popcorn_ref);
197
+ });
198
+ }
199
+
200
+ export { runIFrame };
@@ -0,0 +1,3 @@
1
+ export { Popcorn, PopcornDeinitializedError } from "./popcorn";
2
+ export type { CastOptions, CallOptions } from "./popcorn";
3
+ export type { AnySerializable } from "./types";
package/dist/index.mjs ADDED
@@ -0,0 +1 @@
1
+ export { Popcorn, PopcornDeinitializedError } from './popcorn.mjs';
@@ -0,0 +1,3 @@
1
+ import type { Plugin } from "esbuild";
2
+ import { type PopcornPluginOptions } from "./shared";
3
+ export declare function popcorn(options: PopcornPluginOptions): Plugin;
@@ -0,0 +1,53 @@
1
+ import { mkdir, copyFile } from 'fs/promises';
2
+ import { dirname, resolve, basename, join } from 'path';
3
+ import { fileURLToPath } from 'url';
4
+
5
+ const __dirname$1 = dirname(fileURLToPath(import.meta.url));
6
+ // Plugin is at dist/plugins/esbuild.mjs, dist/ is one level up
7
+ const popcornDistDir = resolve(__dirname$1, "..");
8
+ function popcorn(options) {
9
+ const bundlePath = options.bundlePath;
10
+ const bundleName = basename(bundlePath);
11
+ const bundleDir = dirname(bundlePath);
12
+ let outputDir;
13
+ return {
14
+ name: "popcorn",
15
+ setup(build) {
16
+ build.onStart(() => {
17
+ const opts = build.initialOptions;
18
+ const isEsm = opts.format === "esm";
19
+ const outdirFallback = opts.outfile !== undefined ? dirname(opts.outfile) : undefined;
20
+ const outdir = opts.outdir ?? outdirFallback;
21
+ if (!isEsm) {
22
+ throw new Error("[popcorn] Popcorn works only with esm type builds.");
23
+ }
24
+ if (outdir === undefined) {
25
+ throw new Error("[popcorn] outdir is not specified, cannot copy files");
26
+ }
27
+ outputDir = outdir;
28
+ });
29
+ build.onEnd(async () => {
30
+ await mkdir(outputDir, { recursive: true });
31
+ try {
32
+ await Promise.all([
33
+ // Copy bundle to wasm directory
34
+ copy(bundleName, { inDir: bundleDir, outDir: outputDir }),
35
+ // Copy popcorn runtime files to output directory
36
+ // These need to be alongside the bundled code for import.meta.url to work
37
+ copy("iframe.mjs", { inDir: popcornDistDir, outDir: outputDir }),
38
+ copy("AtomVM.mjs", { inDir: popcornDistDir, outDir: outputDir }),
39
+ copy("AtomVM.wasm", { inDir: popcornDistDir, outDir: outputDir }),
40
+ ]);
41
+ }
42
+ catch (err) {
43
+ throw new Error("[popcorn] Failed to copy files", { cause: err });
44
+ }
45
+ });
46
+ },
47
+ };
48
+ }
49
+ async function copy(name, { inDir, outDir }) {
50
+ return copyFile(join(inDir, name), join(outDir, name));
51
+ }
52
+
53
+ export { popcorn };
@@ -0,0 +1,3 @@
1
+ import type { Plugin } from "rollup";
2
+ import { type PopcornPluginOptions } from "./shared";
3
+ export declare function popcorn(options: PopcornPluginOptions): Plugin<unknown>;
@@ -0,0 +1,31 @@
1
+ import { readFile } from 'fs/promises';
2
+ import { dirname, resolve, basename } from 'path';
3
+ import { fileURLToPath } from 'url';
4
+
5
+ const __dirname$1 = dirname(fileURLToPath(import.meta.url));
6
+ // Plugin is at dist/plugins/rollup.mjs, dist/ is one level up
7
+ const popcornDistDir = resolve(__dirname$1, "..");
8
+ function popcorn(options) {
9
+ const bundlePath = options.bundlePath;
10
+ const bundleName = basename(bundlePath);
11
+ return {
12
+ name: "popcorn",
13
+ async generateBundle() {
14
+ // Emit bundle to wasm directory
15
+ this.emitFile({
16
+ type: "asset",
17
+ fileName: resolve(popcornDistDir, bundleName),
18
+ source: await readFile(bundlePath),
19
+ });
20
+ // Emit popcorn runtime files to output directory
21
+ // These need to be alongside the bundled code for import.meta.url to work
22
+ for (const name of ["iframe.mjs", "AtomVM.mjs", "AtomVM.wasm"]) {
23
+ const sourcePath = resolve(popcornDistDir, name);
24
+ const source = await readFile(sourcePath);
25
+ this.emitFile({ type: "asset", fileName: name, source });
26
+ }
27
+ },
28
+ };
29
+ }
30
+
31
+ export { popcorn };
@@ -0,0 +1,7 @@
1
+ export declare const DIST_DIR = "node_modules/@swmansion/popcorn/dist";
2
+ export type PopcornPluginOptions = {
3
+ /**
4
+ * Path to the .avm bundle file
5
+ */
6
+ bundlePath: string;
7
+ };
@@ -0,0 +1,3 @@
1
+ import { type PopcornPluginOptions } from "./shared";
2
+ import type { Plugin } from "vite";
3
+ export declare function popcorn(options: PopcornPluginOptions): Plugin;
@@ -0,0 +1,92 @@
1
+ import { readFile, stat } from 'fs/promises';
2
+ import { dirname, resolve, basename } from 'path';
3
+ import { fileURLToPath } from 'url';
4
+
5
+ const __dirname$1 = dirname(fileURLToPath(import.meta.url));
6
+ // Plugin is at dist/plugins/vite.mjs, dist/ is one level up
7
+ const popcornDistDir = resolve(__dirname$1, "..");
8
+ function popcorn(options) {
9
+ const bundlePath = options.bundlePath;
10
+ const bundleName = basename(bundlePath);
11
+ const bundleUrl = `/${bundleName}`;
12
+ let assetsDir;
13
+ return {
14
+ name: "popcorn",
15
+ config() {
16
+ return {
17
+ // Exclude popcorn from prebundling so import.meta.url resolves correctly
18
+ optimizeDeps: {
19
+ exclude: ["@swmansion/popcorn"],
20
+ },
21
+ };
22
+ },
23
+ async configResolved(config) {
24
+ try {
25
+ await stat(bundlePath);
26
+ }
27
+ catch {
28
+ this.error(`[popcorn] Bundle doesn't exist at '${bundlePath}'`);
29
+ }
30
+ config.server.fs.allow.push(popcornDistDir);
31
+ assetsDir = config.build.assetsDir;
32
+ },
33
+ configureServer(server) {
34
+ server.middlewares.use(async (req, res, next) => {
35
+ setSharedArrayBufferHeaders(res);
36
+ const opts = { bundleUrl, bundlePath };
37
+ const served = await serveAvmBundle(req, res, opts);
38
+ if (served)
39
+ return;
40
+ next();
41
+ });
42
+ },
43
+ configurePreviewServer(server) {
44
+ server.middlewares.use(async (req, res, next) => {
45
+ setSharedArrayBufferHeaders(res);
46
+ const opts = { bundleUrl, bundlePath };
47
+ const served = await serveAvmBundle(req, res, opts);
48
+ if (served)
49
+ return;
50
+ next();
51
+ });
52
+ },
53
+ async generateBundle() {
54
+ // Emit bundle to wasm directory
55
+ this.emitFile({
56
+ type: "asset",
57
+ fileName: `${assetsDir}/${bundleName}`,
58
+ source: await readFile(bundlePath),
59
+ });
60
+ // Emit AtomVM files to assets directory (same location as iframe.mjs)
61
+ // Vite treats iframe.mjs as a static asset and doesn't analyze its imports
62
+ for (const name of ["AtomVM.mjs", "AtomVM.wasm"]) {
63
+ this.emitFile({
64
+ type: "asset",
65
+ fileName: `${assetsDir}/${name}`,
66
+ source: await readFile(resolve(popcornDistDir, name)),
67
+ });
68
+ }
69
+ },
70
+ };
71
+ }
72
+ async function serveAvmBundle(req, res, { bundleUrl, bundlePath }) {
73
+ try {
74
+ if (req.url === bundleUrl) {
75
+ const content = await readFile(bundlePath);
76
+ res.setHeader("Content-Type", "application/octet-stream");
77
+ res.end(content);
78
+ return true;
79
+ }
80
+ return false;
81
+ }
82
+ catch (err) {
83
+ console.error(`[popcorn] Failed to serve bundle:`, err);
84
+ throw err;
85
+ }
86
+ }
87
+ function setSharedArrayBufferHeaders(res) {
88
+ res.setHeader("Cross-Origin-Opener-Policy", "same-origin");
89
+ res.setHeader("Cross-Origin-Embedder-Policy", "require-corp");
90
+ }
91
+
92
+ export { popcorn };
@@ -0,0 +1,130 @@
1
+ import { type AnySerializable } from "./types";
2
+ /** Options for Popcorn.init() */
3
+ export type PopcornInitOptions = {
4
+ /** DOM element to mount an iframe */
5
+ container?: HTMLElement;
6
+ /** Path to compiled Elixir bundle (`.avm` file). */
7
+ bundlePath?: string;
8
+ /** Handler for stderr messages. */
9
+ onStderr?: (message: string) => void;
10
+ /** Handler for stdout messages. */
11
+ onStdout?: (message: string) => void;
12
+ /** Handler called when Popcorn reloads due to iframe crash */
13
+ onReload?: (reason: string) => void;
14
+ /** Heartbeat timeout in milliseconds. If an iframe doesn't respond within this time, it is reloaded. */
15
+ heartbeatTimeoutMs?: number;
16
+ /** Directory containing Wasm and scripts used inside iframe. */
17
+ wasmDir?: string;
18
+ /** Enable debug logging. */
19
+ debug?: boolean;
20
+ };
21
+ /** Options for cast method */
22
+ export type CastOptions = {
23
+ /** Receiver process name. */
24
+ process?: string;
25
+ };
26
+ /** Options for call method */
27
+ export type CallOptions = {
28
+ /** Registered Elixir process name. */
29
+ process?: string;
30
+ /** Timeout (in milliseconds) for the call */
31
+ timeoutMs?: number;
32
+ };
33
+ type CallResult = {
34
+ ok: true;
35
+ /** Serialized value returned from Elixir */
36
+ data: AnySerializable;
37
+ /** Amount of time it took to process the call */
38
+ durationMs: number;
39
+ } | {
40
+ ok: false;
41
+ /** Error from failed call */
42
+ error?: AnySerializable;
43
+ /** Amount of time it took to process the call */
44
+ durationMs: number;
45
+ };
46
+ type LogType = "stdout" | "stderr";
47
+ type LogListener = (message: string) => void;
48
+ export declare class PopcornDeinitializedError extends Error {
49
+ }
50
+ /**
51
+ * Manages Elixir by setting up iframe, WASM module, and event listeners. Used to sent messages to Elixir processes.
52
+ */
53
+ export declare class Popcorn {
54
+ heartbeatTimeoutMs: number | null;
55
+ private onReloadCallback;
56
+ private bridge;
57
+ private bridgeConfig;
58
+ private debug;
59
+ private bundleURL;
60
+ private state;
61
+ private initProcess;
62
+ private requestId;
63
+ private calls;
64
+ private logListeners;
65
+ private awaitedMessage;
66
+ private heartbeatTimeout;
67
+ private reloadN;
68
+ private constructor();
69
+ /**
70
+ * Creates an iframe and sets up communication channels.
71
+ * Returns after Elixir code calls `Popcorn.Wasm.register/1`.
72
+ *
73
+ * @example
74
+ * import { Popcorn } from "@swmansion/popcorn";
75
+ * const popcorn = await Popcorn.init({
76
+ * onStdout: console.log,
77
+ * onStderr: console.error,
78
+ * debug: true,
79
+ * });
80
+ */
81
+ static init(options: PopcornInitOptions): Promise<Popcorn>;
82
+ private mount;
83
+ /**
84
+ * Sends a message to an Elixir process and awaits for the response.
85
+ *
86
+ * If Elixir doesn't respond in configured timeout, the returned promise will be rejected with "process timeout" error.
87
+ *
88
+ * Unless passed via options, the name passed in `Popcorn.Wasm.register/1` on the Elixir side is used.
89
+ * Throws "Unspecified target process" if default process is not set and no process is specified.
90
+ *
91
+ * @example
92
+ * const result = await popcorn.call(
93
+ * { action: "get_user", id: 123 },
94
+ * { process: "user_server", timeoutMs: 5_000 },
95
+ * );
96
+ * console.log(result.data); // Deserialized Elixir response
97
+ * console.log(result.durationMs); // Entire call duration
98
+ */
99
+ call(args: AnySerializable, { process, timeoutMs }?: CallOptions): Promise<CallResult>;
100
+ /**
101
+ * Sends a message to an Elixir process (default or from options) and returns immediately.
102
+ *
103
+ * Unless passed via options, the name passed in `Popcorn.Wasm.register/1` on the Elixir side is used.
104
+ * Throws "Unspecified target process" if default process is not set and no process is specified.
105
+ */
106
+ cast(args: AnySerializable, { process }?: CastOptions): void;
107
+ /**
108
+ * Destroys an iframe and resets the instance.
109
+ */
110
+ deinit(): void;
111
+ /**
112
+ * Registers a log listener that will be called when output of the specified type is received.
113
+ */
114
+ registerLogListener(listener: LogListener, type: LogType): void;
115
+ /**
116
+ * Unregisters a previously registered log listener.
117
+ */
118
+ unregisterLogListener(listener: LogListener, type: LogType): void;
119
+ private notifyLogListeners;
120
+ private iframeHandler;
121
+ private onCallAck;
122
+ private onCall;
123
+ private onHeartbeat;
124
+ private reloadIframe;
125
+ private awaitMessage;
126
+ trace(...messages: unknown[]): void;
127
+ private transition;
128
+ private assertStatus;
129
+ }
130
+ export {};