@swmansion/popcorn 0.2.2 → 0.3.0-rc1

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.
package/dist/AtomVM.wasm CHANGED
Binary file
package/dist/bridge.mjs CHANGED
@@ -41,10 +41,11 @@ class IframeBridge {
41
41
  window.removeEventListener("message", this.handlerRef);
42
42
  this.iframe.remove();
43
43
  }
44
- messageHandler({ data }) {
45
- if (isIframeResponse(data)) {
46
- this.onMessage(data);
47
- }
44
+ messageHandler(event) {
45
+ const fromOurIframe = event.source === this.iframe.contentWindow;
46
+ if (!fromOurIframe || !isIframeResponse(event.data))
47
+ return;
48
+ this.onMessage(event.data);
48
49
  }
49
50
  }
50
51
  function isIframeResponse(payload) {
package/dist/errors.d.ts CHANGED
@@ -6,7 +6,7 @@ export declare class PopcornError extends Error {
6
6
  constructor(code: PopcornErrorCode, message?: string);
7
7
  }
8
8
  /** Error codes for internal errors that indicate bugs or misuse */
9
- export type PopcornInternalErrorCode = "assert" | "private_constructor" | "bad_call" | "no_acked_call" | "bad_ack" | "already_awaited" | "already_mounted" | "unmounted" | "bad_target" | "bad_status" | "bundle_not_found";
9
+ export type PopcornInternalErrorCode = "assert" | "private_constructor" | "bad_call" | "no_acked_call" | "bad_ack" | "already_mounted" | "unmounted" | "bad_target" | "bad_status" | "app_ready_timeout" | "bundle_not_found";
10
10
  /** Non-recoverable error indicating a bug or library misuse (always thrown) */
11
11
  export declare class PopcornInternalError extends Error {
12
12
  readonly code: PopcornInternalErrorCode;
@@ -23,10 +23,6 @@ type ErrorData = {
23
23
  t: "no_acked_call";
24
24
  } | {
25
25
  t: "bad_ack";
26
- } | {
27
- t: "already_awaited";
28
- messageType: string;
29
- awaitedMessageType: string;
30
26
  } | {
31
27
  t: "already_mounted";
32
28
  } | {
@@ -37,10 +33,13 @@ type ErrorData = {
37
33
  t: "bad_status";
38
34
  status: string;
39
35
  expectedStatus: string;
36
+ } | {
37
+ t: "app_ready_timeout";
40
38
  } | {
41
39
  t: "bundle_not_found";
42
40
  primary: string;
43
41
  fallback: string;
44
42
  };
43
+ export declare function buildError(error: ErrorData): PopcornInternalError;
45
44
  export declare function throwError(error: ErrorData): never;
46
45
  export {};
package/dist/errors.mjs CHANGED
@@ -12,6 +12,7 @@ class PopcornError extends Error {
12
12
  this.name = "PopcornError";
13
13
  }
14
14
  }
15
+ const INIT_VM_TIMEOUT_MS = 30_000;
15
16
  /** Non-recoverable error indicating a bug or library misuse (always thrown) */
16
17
  class PopcornInternalError extends Error {
17
18
  code;
@@ -21,31 +22,34 @@ class PopcornInternalError extends Error {
21
22
  this.name = "PopcornInternalError";
22
23
  }
23
24
  }
24
- function throwError(error) {
25
+ function buildError(error) {
25
26
  switch (error.t) {
26
27
  case "assert":
27
- throw new PopcornInternalError("assert", "Assertion error");
28
+ return new PopcornInternalError("assert", "Assertion error");
28
29
  case "private_constructor":
29
- throw new PopcornInternalError("private_constructor", "Don't construct the Popcorn object directly, use Popcorn.init() instead");
30
+ return new PopcornInternalError("private_constructor", "Don't construct the Popcorn object directly, use Popcorn.init() instead");
30
31
  case "bad_call":
31
- throw new PopcornInternalError("bad_call", "Response for non-existent call");
32
+ return new PopcornInternalError("bad_call", "Response for non-existent call");
32
33
  case "no_acked_call":
33
- throw new PopcornInternalError("no_acked_call", "Response for non-acknowledged call");
34
+ return new PopcornInternalError("no_acked_call", "Response for non-acknowledged call");
34
35
  case "bad_ack":
35
- throw new PopcornInternalError("bad_ack", "Ack for non-existent call");
36
- case "already_awaited":
37
- throw new PopcornInternalError("already_awaited", `Cannot await message "${error.messageType}" when message "${error.awaitedMessageType}" is already awaited`);
36
+ return new PopcornInternalError("bad_ack", "Ack for non-existent call");
38
37
  case "already_mounted":
39
- throw new PopcornInternalError("already_mounted", "Iframe already mounted");
38
+ return new PopcornInternalError("already_mounted", "Iframe already mounted");
40
39
  case "unmounted":
41
- throw new PopcornInternalError("unmounted", "WASM iframe not mounted");
40
+ return new PopcornInternalError("unmounted", "WASM iframe not mounted");
42
41
  case "bad_target":
43
- throw new PopcornInternalError("bad_target", "Unspecified target process");
42
+ return new PopcornInternalError("bad_target", "Unspecified target process");
44
43
  case "bad_status":
45
- throw new PopcornInternalError("bad_status", `Operation not allowed: instance in "${error.status}" state, expected "${error.expectedStatus}"`);
44
+ return new PopcornInternalError("bad_status", `Operation not allowed: instance in "${error.status}" state, expected "${error.expectedStatus}"`);
45
+ case "app_ready_timeout":
46
+ return new PopcornInternalError("app_ready_timeout", `Elixir app did not call Popcorn.Wasm.ready() within ${INIT_VM_TIMEOUT_MS}ms`);
46
47
  case "bundle_not_found":
47
- throw new PopcornInternalError("bundle_not_found", `Could not find a valid .avm bundle at "${error.primary}" or fallback "${error.fallback}"`);
48
+ return new PopcornInternalError("bundle_not_found", `Could not find a valid .avm bundle at "${error.primary}" or fallback "${error.fallback}"`);
48
49
  }
49
50
  }
51
+ function throwError(error) {
52
+ throw buildError(error);
53
+ }
50
54
 
51
- export { PopcornError, PopcornInternalError, throwError };
55
+ export { PopcornError, PopcornInternalError, buildError, throwError };
package/dist/iframe.d.ts CHANGED
@@ -1 +1 @@
1
- export declare function runIFrame(): Promise<void>;
1
+ export declare function initVm(): Promise<void>;
package/dist/iframe.mjs CHANGED
@@ -2,8 +2,7 @@ import init from './AtomVM.mjs';
2
2
 
3
3
  const HEARTBEAT_INTERVAL_MS = 500;
4
4
  const MESSAGES = {
5
- INIT: "popcorn-init",
6
- START_VM: "popcorn-startVm",
5
+ EVENT: "popcorn-event",
7
6
  CALL: "popcorn-call",
8
7
  CAST: "popcorn-cast",
9
8
  CALL_ACK: "popcorn-callAck",
@@ -32,7 +31,7 @@ class TrackedValue {
32
31
  }
33
32
  }
34
33
  globalThis.TrackedValue = TrackedValue;
35
- async function runIFrame() {
34
+ async function initVm() {
36
35
  const metaElement = document.querySelector('meta[name="bundle-path"]');
37
36
  if (!metaElement) {
38
37
  throw new Error("Missing meta[name='bundle-path'] element");
@@ -40,8 +39,7 @@ async function runIFrame() {
40
39
  const bundlePath = metaElement.content;
41
40
  const bundleBuffer = await fetch(bundlePath).then((resp) => resp.arrayBuffer());
42
41
  const bundle = new Int8Array(bundleBuffer);
43
- sendIframeResponse(MESSAGES.INIT, null);
44
- const initProcess = await startVm(bundle);
42
+ await startVm(bundle);
45
43
  window.addEventListener("message", async ({ data }) => {
46
44
  const type = data.type;
47
45
  if (type === MESSAGES.CALL) {
@@ -51,14 +49,9 @@ async function runIFrame() {
51
49
  handleCast(data.value);
52
50
  }
53
51
  });
54
- sendIframeResponse(MESSAGES.START_VM, initProcess);
55
52
  setInterval(() => sendIframeResponse(MESSAGES.HEARTBEAT, null), HEARTBEAT_INTERVAL_MS);
56
53
  }
57
54
  async function startVm(avmBundle) {
58
- let resolveResultPromise = null;
59
- const resultPromise = new Promise((resolve) => {
60
- resolveResultPromise = resolve;
61
- });
62
55
  const moduleInstance = await init({
63
56
  preRun: [
64
57
  function ({ FS }) {
@@ -152,11 +145,9 @@ async function startVm(avmBundle) {
152
145
  };
153
146
  return keys.map(getTrackedObject);
154
147
  };
155
- moduleInstance["onElixirReady"] = (initProcess) => {
156
- moduleInstance["onElixirReady"] = null;
157
- resolveResultPromise?.(initProcess);
148
+ moduleInstance["sendEvent"] = (eventName, payload) => {
149
+ sendIframeResponse(MESSAGES.EVENT, { eventName, payload });
158
150
  };
159
- return resultPromise;
160
151
  }
161
152
  async function handleCall(request) {
162
153
  if (!Module) {
@@ -213,4 +204,4 @@ function deserialize(message) {
213
204
  });
214
205
  }
215
206
 
216
- export { runIFrame };
207
+ export { initVm };
package/dist/popcorn.d.ts CHANGED
@@ -49,6 +49,7 @@ type CallResult = {
49
49
  };
50
50
  type LogType = "stdout" | "stderr";
51
51
  type LogListener = (message: string) => void;
52
+ type MessageHandler = (eventName: string, payload: AnySerializable) => void;
52
53
  /**
53
54
  * Manages Elixir by setting up iframe, WASM module, and event listeners. Used to sent messages to Elixir processes.
54
55
  */
@@ -60,17 +61,18 @@ export declare class Popcorn {
60
61
  private debug;
61
62
  private bundleURL;
62
63
  private state;
63
- private initProcess;
64
+ private defaultReceiver;
64
65
  private requestId;
65
66
  private calls;
66
67
  private logListeners;
67
- private awaitedMessage;
68
+ private messageHandlers;
69
+ private mountResolve;
68
70
  private heartbeatTimeout;
69
71
  private reloadN;
70
72
  private constructor();
71
73
  /**
72
74
  * Creates an iframe and sets up communication channels.
73
- * Returns after Elixir code calls `Popcorn.Wasm.register/1`.
75
+ * Returns after the Elixir app calls `Popcorn.Wasm.ready/0,1`.
74
76
  *
75
77
  * @example
76
78
  * import { Popcorn } from "@swmansion/popcorn";
@@ -87,7 +89,7 @@ export declare class Popcorn {
87
89
  *
88
90
  * If Elixir doesn't respond in configured timeout, the returned promise will be rejected with "process timeout" error.
89
91
  *
90
- * Unless passed via options, the name passed in `Popcorn.Wasm.register/1` on the Elixir side is used.
92
+ * Unless passed via options, the name passed in `Popcorn.Wasm.set_default_receiver/1` on the Elixir side is used.
91
93
  * Throws "Unspecified target process" if default process is not set and no process is specified.
92
94
  *
93
95
  * @example
@@ -102,7 +104,7 @@ export declare class Popcorn {
102
104
  /**
103
105
  * Sends a message to an Elixir process (default or from options) and returns immediately.
104
106
  *
105
- * Unless passed via options, the name passed in `Popcorn.Wasm.register/1` on the Elixir side is used.
107
+ * Unless passed via options, the name passed in `Popcorn.Wasm.set_default_receiver/1` on the Elixir side is used.
106
108
  * Throws "Unspecified target process" if default process is not set and no process is specified.
107
109
  */
108
110
  cast(args: AnySerializable, { process }?: CastOptions): void;
@@ -110,6 +112,7 @@ export declare class Popcorn {
110
112
  * Destroys an iframe and resets the instance.
111
113
  */
112
114
  deinit(): void;
115
+ private teardownBridge;
113
116
  /**
114
117
  * Registers a log listener that will be called when output of the specified type is received.
115
118
  */
@@ -119,12 +122,16 @@ export declare class Popcorn {
119
122
  */
120
123
  unregisterLogListener(listener: LogListener, type: LogType): void;
121
124
  private notifyLogListeners;
125
+ /**
126
+ * Registers a catch-all event handler. Returns an unsubscribe function.
127
+ */
128
+ onMessage(handler: MessageHandler): () => void;
129
+ private onEvent;
122
130
  private iframeHandler;
123
131
  private onCallAck;
124
132
  private onCall;
125
133
  private onHeartbeat;
126
134
  private reloadIframe;
127
- private awaitMessage;
128
135
  trace(...messages: unknown[]): void;
129
136
  private transition;
130
137
  private assertStatus;
package/dist/popcorn.mjs CHANGED
@@ -1,6 +1,6 @@
1
1
  import { IframeBridge } from './bridge.mjs';
2
- import { HEARTBEAT_TIMEOUT_MS, MESSAGES, INIT_VM_TIMEOUT_MS, MAX_RELOAD_N, CALL_TIMEOUT_MS } from './types.mjs';
3
- import { throwError, PopcornError } from './errors.mjs';
2
+ import { HEARTBEAT_TIMEOUT_MS, INIT_VM_TIMEOUT_MS, MESSAGES, EVENT_NAMES, MAX_RELOAD_N, CALL_TIMEOUT_MS } from './types.mjs';
3
+ import { throwError, buildError, PopcornError } from './errors.mjs';
4
4
  export { PopcornInternalError } from './errors.mjs';
5
5
 
6
6
  const INIT_TOKEN = Symbol();
@@ -16,14 +16,15 @@ class Popcorn {
16
16
  debug = false;
17
17
  bundleURL;
18
18
  state = { status: "uninitialized" };
19
- initProcess = null;
19
+ defaultReceiver = null;
20
20
  requestId = 0;
21
21
  calls = new Map();
22
22
  logListeners = {
23
23
  stdout: new Set(),
24
24
  stderr: new Set(),
25
25
  };
26
- awaitedMessage = null;
26
+ messageHandlers = new Set();
27
+ mountResolve = null;
27
28
  heartbeatTimeout = null;
28
29
  reloadN = 0;
29
30
  constructor(params, token) {
@@ -36,7 +37,7 @@ class Popcorn {
36
37
  this.bundleURL = bundleURL.href;
37
38
  this.bridgeConfig = {
38
39
  container: params.container,
39
- script: { url: IFRAME_URL, entrypoint: "runIFrame" },
40
+ script: { url: IFRAME_URL, entrypoint: "initVm" },
40
41
  config: { "bundle-path": this.bundleURL },
41
42
  debug: true,
42
43
  onMessage: this.iframeHandler.bind(this),
@@ -47,7 +48,7 @@ class Popcorn {
47
48
  }
48
49
  /**
49
50
  * Creates an iframe and sets up communication channels.
50
- * Returns after Elixir code calls `Popcorn.Wasm.register/1`.
51
+ * Returns after the Elixir app calls `Popcorn.Wasm.ready/0,1`.
51
52
  *
52
53
  * @example
53
54
  * import { Popcorn } from "@swmansion/popcorn";
@@ -76,20 +77,19 @@ class Popcorn {
76
77
  this.trace("Main: mount, container: ", this.bridgeConfig.container);
77
78
  this.bridge = new IframeBridge(this.bridgeConfig);
78
79
  try {
79
- await this.awaitMessage(MESSAGES.INIT);
80
- this.transition({ status: "await_vm" });
81
- this.trace("Main: iframe loaded");
82
- const startTime = performance.now();
83
- const startVmResult = await withTimeout(this.awaitMessage(MESSAGES.START_VM).then((data) => ({
84
- ok: true,
85
- data,
86
- durationMs: performance.now() - startTime,
87
- })), INIT_VM_TIMEOUT_MS);
88
- if (!startVmResult.ok)
89
- throwError({ t: "assert" });
90
- this.initProcess = startVmResult.data;
80
+ const mountPromise = new Promise((resolve) => {
81
+ this.mountResolve = resolve;
82
+ });
83
+ let initTimeout;
84
+ await Promise.race([
85
+ mountPromise,
86
+ new Promise((_, reject) => {
87
+ initTimeout = setTimeout(() => reject(buildError({ t: "app_ready_timeout" })), INIT_VM_TIMEOUT_MS);
88
+ }),
89
+ ]);
90
+ clearTimeout(initTimeout);
91
91
  this.transition({ status: "ready" });
92
- this.trace("Main: mounted, main process: ", this.initProcess);
92
+ this.trace("Main: mounted");
93
93
  this.onHeartbeat();
94
94
  }
95
95
  catch (error) {
@@ -102,7 +102,7 @@ class Popcorn {
102
102
  *
103
103
  * If Elixir doesn't respond in configured timeout, the returned promise will be rejected with "process timeout" error.
104
104
  *
105
- * Unless passed via options, the name passed in `Popcorn.Wasm.register/1` on the Elixir side is used.
105
+ * Unless passed via options, the name passed in `Popcorn.Wasm.set_default_receiver/1` on the Elixir side is used.
106
106
  * Throws "Unspecified target process" if default process is not set and no process is specified.
107
107
  *
108
108
  * @example
@@ -115,26 +115,20 @@ class Popcorn {
115
115
  */
116
116
  async call(args, { process, timeoutMs } = {}) {
117
117
  this.assertStatus(["ready"]);
118
- const targetProcess = process ?? this.initProcess;
119
- if (this.bridge === null)
120
- throwError({ t: "unmounted" });
118
+ const targetProcess = process ?? this.defaultReceiver;
121
119
  if (targetProcess === null)
122
120
  throwError({ t: "bad_target" });
121
+ if (this.bridge === null)
122
+ throwError({ t: "unmounted" });
123
123
  const requestId = this.requestId++;
124
124
  const startTimeMs = performance.now();
125
125
  const callPromise = new Promise((resolve) => {
126
- if (this.bridge === null)
127
- throwError({ t: "unmounted" });
128
- this.trace("Main: call: ", { requestId, process, args });
129
- this.bridge.sendIframeRequest({
130
- type: MESSAGES.CALL,
131
- value: { requestId, process: targetProcess, args },
132
- });
133
- this.calls.set(requestId, {
134
- acknowledged: false,
135
- startTimeMs,
136
- resolve,
137
- });
126
+ this.calls.set(requestId, { acknowledged: false, startTimeMs, resolve });
127
+ });
128
+ this.trace("Main: call: ", { requestId, process, args });
129
+ this.bridge.sendIframeRequest({
130
+ type: MESSAGES.CALL,
131
+ value: { requestId, process: targetProcess, args },
138
132
  });
139
133
  const result = await withTimeout(callPromise, timeoutMs ?? CALL_TIMEOUT_MS);
140
134
  this.calls.delete(requestId);
@@ -143,16 +137,16 @@ class Popcorn {
143
137
  /**
144
138
  * Sends a message to an Elixir process (default or from options) and returns immediately.
145
139
  *
146
- * Unless passed via options, the name passed in `Popcorn.Wasm.register/1` on the Elixir side is used.
140
+ * Unless passed via options, the name passed in `Popcorn.Wasm.set_default_receiver/1` on the Elixir side is used.
147
141
  * Throws "Unspecified target process" if default process is not set and no process is specified.
148
142
  */
149
143
  cast(args, { process } = {}) {
150
144
  this.assertStatus(["ready"]);
151
- const targetProcess = process ?? this.initProcess;
152
- if (this.bridge === null)
153
- throwError({ t: "unmounted" });
145
+ const targetProcess = process ?? this.defaultReceiver;
154
146
  if (targetProcess === null)
155
147
  throwError({ t: "bad_target" });
148
+ if (this.bridge === null)
149
+ throwError({ t: "unmounted" });
156
150
  const requestId = this.requestId++;
157
151
  this.trace("Main: cast: ", { requestId, process, args });
158
152
  this.bridge.sendIframeRequest({
@@ -168,20 +162,27 @@ class Popcorn {
168
162
  throwError({ t: "unmounted" });
169
163
  this.trace("Main: deinit");
170
164
  this.transition({ status: "deinit" });
171
- this.bridge.deinit();
172
- this.bridge = null;
173
- this.awaitedMessage = null;
165
+ this.teardownBridge("deinitialized");
166
+ this.logListeners.stdout.clear();
167
+ this.logListeners.stderr.clear();
168
+ this.messageHandlers.clear();
169
+ }
170
+ teardownBridge(errorCode) {
171
+ if (this.bridge) {
172
+ this.bridge.deinit();
173
+ this.bridge = null;
174
+ }
175
+ this.mountResolve = null;
176
+ this.defaultReceiver = null;
174
177
  if (this.heartbeatTimeout) {
175
178
  clearTimeout(this.heartbeatTimeout);
176
179
  this.heartbeatTimeout = null;
177
180
  }
178
- this.logListeners.stdout.clear();
179
- this.logListeners.stderr.clear();
180
181
  for (const callData of this.calls.values()) {
181
182
  const durationMs = performance.now() - callData.startTimeMs;
182
183
  callData.resolve({
183
184
  ok: false,
184
- error: new PopcornError("deinitialized"),
185
+ error: new PopcornError(errorCode),
185
186
  durationMs,
186
187
  });
187
188
  }
@@ -204,14 +205,47 @@ class Popcorn {
204
205
  listener(message);
205
206
  });
206
207
  }
207
- iframeHandler(data) {
208
- const awaitedMessage = this.awaitedMessage;
209
- if (awaitedMessage && data.type == awaitedMessage.type) {
210
- this.awaitedMessage = null;
211
- awaitedMessage.resolve?.(data.value);
208
+ /**
209
+ * Registers a catch-all event handler. Returns an unsubscribe function.
210
+ */
211
+ onMessage(handler) {
212
+ this.messageHandlers.add(handler);
213
+ return () => {
214
+ this.messageHandlers.delete(handler);
215
+ };
216
+ }
217
+ onEvent({ eventName, payload }) {
218
+ if (eventName.startsWith("popcorn")) {
219
+ if (eventName === EVENT_NAMES.ELIXIR_READY) {
220
+ this.trace("Main: elixir VM ready");
221
+ }
222
+ else if (eventName === EVENT_NAMES.APP_READY) {
223
+ this.defaultReceiver = payload.name;
224
+ this.mountResolve?.();
225
+ this.mountResolve = null;
226
+ }
227
+ else if (eventName === EVENT_NAMES.SET_DEFAULT_RECEIVER) {
228
+ this.defaultReceiver = payload.name;
229
+ }
230
+ else {
231
+ this.trace("Unknown internal event:", eventName);
232
+ }
212
233
  return;
213
234
  }
214
- if (data.type === MESSAGES.STDOUT) {
235
+ this.messageHandlers.forEach((handler) => {
236
+ try {
237
+ handler(eventName, payload);
238
+ }
239
+ catch (error) {
240
+ console.error(`Error in onMessage handler for '${eventName}':`, error);
241
+ }
242
+ });
243
+ }
244
+ iframeHandler(data) {
245
+ if (data.type === MESSAGES.EVENT) {
246
+ this.onEvent(data.value);
247
+ }
248
+ else if (data.type === MESSAGES.STDOUT) {
215
249
  this.notifyLogListeners("stdout", data.value);
216
250
  }
217
251
  else if (data.type === MESSAGES.STDERR) {
@@ -283,40 +317,10 @@ class Popcorn {
283
317
  }
284
318
  this.trace("Main: reloading iframe");
285
319
  this.transition({ status: "reload" });
286
- this.bridge.deinit();
287
- this.bridge = null;
288
- this.awaitedMessage = null;
289
- if (this.heartbeatTimeout) {
290
- clearTimeout(this.heartbeatTimeout);
291
- this.heartbeatTimeout = null;
292
- }
293
- for (const callData of this.calls.values()) {
294
- const durationMs = performance.now() - callData.startTimeMs;
295
- callData.resolve({
296
- ok: false,
297
- error: new PopcornError("reload"),
298
- durationMs,
299
- });
300
- }
301
- this.calls.clear();
320
+ this.teardownBridge("reload");
302
321
  this.onReloadCallback(reason);
303
322
  this.mount();
304
323
  }
305
- awaitMessage(type) {
306
- if (this.awaitedMessage) {
307
- throwError({
308
- t: "already_awaited",
309
- messageType: this.awaitedMessage.type,
310
- awaitedMessageType: type,
311
- });
312
- }
313
- this.awaitedMessage = { type };
314
- return new Promise((resolve) => {
315
- if (!this.awaitedMessage)
316
- throwError({ t: "assert" });
317
- this.awaitedMessage.resolve = resolve;
318
- });
319
- }
320
324
  trace(...messages) {
321
325
  if (this.debug) {
322
326
  console.debug(...messages);
package/dist/types.d.ts CHANGED
@@ -4,6 +4,10 @@ export declare const HEARTBEAT_TIMEOUT_MS = 60000;
4
4
  export declare const HEARTBEAT_INTERVAL_MS = 500;
5
5
  export declare const MAX_RELOAD_N = 3;
6
6
  export type AnySerializable = any;
7
+ export type ElixirEvent = {
8
+ eventName: string;
9
+ payload: AnySerializable;
10
+ };
7
11
  export type CallRequest = {
8
12
  requestId: number;
9
13
  process: string;
@@ -32,11 +36,8 @@ export type CallAck = {
32
36
  };
33
37
  /** Messages sent from iframe to parent window */
34
38
  export type IframeResponse = {
35
- type: "popcorn-init";
36
- value: null;
37
- } | {
38
- type: "popcorn-startVm";
39
- value: string;
39
+ type: "popcorn-event";
40
+ value: ElixirEvent;
40
41
  } | {
41
42
  type: "popcorn-call";
42
43
  value: CallResponse;
@@ -59,8 +60,7 @@ export type IframeResponse = {
59
60
  /** Union of all messages (requests and responses) */
60
61
  export type Message = IframeRequest | IframeResponse;
61
62
  export declare const MESSAGES: {
62
- readonly INIT: "popcorn-init";
63
- readonly START_VM: "popcorn-startVm";
63
+ readonly EVENT: "popcorn-event";
64
64
  readonly CALL: "popcorn-call";
65
65
  readonly CAST: "popcorn-cast";
66
66
  readonly CALL_ACK: "popcorn-callAck";
@@ -69,4 +69,9 @@ export declare const MESSAGES: {
69
69
  readonly HEARTBEAT: "popcorn-heartbeat";
70
70
  readonly RELOAD: "popcorn-reload";
71
71
  };
72
+ export declare const EVENT_NAMES: {
73
+ readonly ELIXIR_READY: "popcorn_elixir_ready";
74
+ readonly APP_READY: "popcorn_app_ready";
75
+ readonly SET_DEFAULT_RECEIVER: "popcorn_set_default_receiver";
76
+ };
72
77
  export declare function isMessageType(type: string): type is Message["type"];
package/dist/types.mjs CHANGED
@@ -3,8 +3,7 @@ const CALL_TIMEOUT_MS = 60_000;
3
3
  const HEARTBEAT_TIMEOUT_MS = 60_000;
4
4
  const MAX_RELOAD_N = 3;
5
5
  const MESSAGES = {
6
- INIT: "popcorn-init",
7
- START_VM: "popcorn-startVm",
6
+ EVENT: "popcorn-event",
8
7
  CALL: "popcorn-call",
9
8
  CAST: "popcorn-cast",
10
9
  CALL_ACK: "popcorn-callAck",
@@ -13,9 +12,14 @@ const MESSAGES = {
13
12
  HEARTBEAT: "popcorn-heartbeat",
14
13
  RELOAD: "popcorn-reload",
15
14
  };
15
+ const EVENT_NAMES = {
16
+ ELIXIR_READY: "popcorn_elixir_ready",
17
+ APP_READY: "popcorn_app_ready",
18
+ SET_DEFAULT_RECEIVER: "popcorn_set_default_receiver",
19
+ };
16
20
  const MESSAGES_TYPES = new Set(Object.values(MESSAGES));
17
21
  function isMessageType(type) {
18
22
  return MESSAGES_TYPES.has(type);
19
23
  }
20
24
 
21
- export { CALL_TIMEOUT_MS, HEARTBEAT_TIMEOUT_MS, INIT_VM_TIMEOUT_MS, MAX_RELOAD_N, MESSAGES, isMessageType };
25
+ export { CALL_TIMEOUT_MS, EVENT_NAMES, HEARTBEAT_TIMEOUT_MS, INIT_VM_TIMEOUT_MS, MAX_RELOAD_N, MESSAGES, isMessageType };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@swmansion/popcorn",
3
- "version": "0.2.2",
3
+ "version": "0.3.0-rc1",
4
4
  "description": "JS bindings for Popcorn",
5
5
  "type": "module",
6
6
  "license": "Apache-2.0",