lava-chicken
v2.0.2
Published
`LavaChicken` — is a JavaScript/TypeScript class for safe two-way communication between browser windows (e.g., parent and `<iframe>`), with support for events, messages, and an automatic connection handshake mechanism.
Readme
🌋🐓 LavaChicken
LavaChicken — is a JavaScript/TypeScript class for safe two-way communication between browser windows (e.g., parent and <iframe>), with support for events, messages, and an automatic connection handshake mechanism.
⚠️ Migration from v1 to v2
v2 replaces postMessage + window.opener with BroadcastChannel as the transport layer.
Why: Modern browsers (Chrome 88+, Safari) sever window.opener when a popup navigates to a cross-origin URL, breaking the connection when the payment provider redirects back to your payment-success page. BroadcastChannel works across tabs and iframes of the same origin without relying on opener references.
Breaking changes:
- No more
connect/connectedhandshake —BroadcastChannelrequires no setup - Directionality filtering removed — BC naturally prevents echo;
_idfield added to messages for cross-transport deduplication onLost()still works via ping/pong
Transport strategy:
BroadcastChannel— same-origin contexts (redirect tab returning to same origin, same-origin iframe)postMessageviawindow.parent— cross-origin iframes (subdomain cashier)- Both run simultaneously; duplicates are deduplicated via
_id
What stays the same: the entire public API (on, trigger, send, onMessage, onLost, destroy).
⚙️ Initialization
In the parent window
import { LavaChicken } from "lava-chicken";
const lavaChicken = new LavaChicken("shared_sauce");In the child window (e.g., inside an iframe or opened via window.open)
import { LavaChicken } from "lava-chicken";
const lavaChicken = new LavaChicken("shared_sauce", { isChicken: true });🧪 API
new LavaChicken(sauce: string, options?: { isChicken?: boolean })
- sauce — unique string used to authenticate both sides.
- options.isChicken — indicates if this instance is a child (
truefor iframe or opened tab). By default, it's auto-detected viawindow.parent !== window || window.opener !== null.
send(data: any): void
Sends a message to the other side.
lavaChicken.send({ text: "Hello from parent!" });trigger(eventName: string, data?: any): void
Sends a named event to the other side.
lavaChicken.trigger("form:submitted", { id: 123 });onMessage(handler: (data: any) => void): void
Subscribe to incoming messages.
lavaChicken.onMessage((data) => {
console.log("Received message:", data);
});on(eventName: string, handler: (data: any) => void): void
Subscribe to specific events.
lavaChicken.on("form:submitted", (data) => {
console.log("Form submitted with data:", data);
});onLost(handler: () => void): void
Subscribe to lost connection event. Use only in Lava (parent window)
lavaChicken.onLost(() => {
console.log("Connection with chicken is lost");
});destroy(): void
Cleans up all intervals, subscriptions, and window references.
lavaChicken.destroy();🔄 Connection mechanism
- The child window (iframe or
window.opentab) automatically tries to connect to the opener by sending aconnectsystem message every second. - The parent responds with
connected, if the sauce matches. - Once connected, you’ll see
🐔 Connected to lava!in the console.
📌 Full example
In the parent window:
const lavaChicken = new LavaChicken("tabasco123");
lavaChicken.on("custom:event", (payload) => {
console.log("Child sent event:", payload);
});
lavaChicken.onMessage((msg) => {
console.log("Message from child:", msg);
});In the <iframe> or opened tab:
const lavaChicken = new LavaChicken("tabasco123", { isChicken: true });
lavaChicken.send({ hello: "parent" });
lavaChicken.trigger("custom:event", { foo: "bar" });❗ Notes
- The
saucevalue must be identical on both sides. - Always call
destroy()when the connection is no longer needed (e.g., when navigating between pages).
🧩 useLavachicken (Composable)
Global singleton composable for working with LavaChicken. Provides convenient access to a single instance across your app.
import { useLavaChicken } from "lava-chicken";
const lavaChicken = useLavaChicken();
onMounted(() => {
lavaChicken.init("secure-sauce", true);
lavaChicken.send({ hello: "iframe!" });
lavaChicken.trigger("currency:selected", { code: "USD" });
lavaChicken.on("payment:success", (data) => {
console.log("Success:", data);
});
lavaChicken.onMessage((msg) => {
console.log("Message received:", msg);
});
});API
| Метод | Опис |
| ------------------------ | ----------------------------------------------- |
| init(sauce, isChicken) | Initializes the single LavaChicken instance |
| send(message) | Sends a message |
| trigger(event, data?) | Triggers an event with optional data |
| on(event, handler) | Listens for an event |
| onMessage(handler) | Listens for any message |
| onLost(handler) | Listens for lost connection (for parent window) |
| destroy() | Destroys the instance and clears all listeners |
🔬 Under the hood
This composable creates a single instance of LavaChicken shared across all components.
If init() is not called, all other methods will do nothing.
⚠️ Don’t forget to call
destroy()when needed (e.g., when navigating between routes inside aniframeor a tab opened viawindow.open).
🧩 useVueLavaChicken (Vue Composable)
Vue-style composable for using LavaChicken as a singleton. Features:
- auto-initialization on
onMounted - automatic cleanup on
onUnmounted - tracks active component count
- auto
destroy, when last component unmounts
Usage
import { useVueLavaChicken } from "lava-chicken";
const { send, trigger, on, onMessage } = useVueLavaChicken(
"secure-sauce",
true,
);
onMounted(() => {
trigger("view:ready");
});
onMessage((data) => {
console.log("Got message:", data);
});
on("payment:success", (payload) => {
console.log("Payment succeeded:", payload);
});⚠️
useVueLavaChicken(sauce)must be called with the same token in all components where you want to use it. It connects/disconnects automatically — no need to manually callinit()ordestroy().
API
| Method | Description |
| ----------------------- | --------------------------------------------- |
| send(message) | Sends a plain message |
| trigger(event, data?) | Triggers an event with data |
| on(event, handler) | Subscribes to an event |
| onMessage(handler) | Subscribes to any message |
| onLost(handler) | Subscribes to lost connection (parent window) |
Example with v-if:
<template>
<ChildComponent v-if="showChild" />
</template>
<script setup lang="ts">
const showChild = ref(true);
</script>// ChildComponent.vue
const { trigger } = useVueLavaChicken("secure-sauce", true);
onMounted(() => {
trigger("child:ready");
});
// When the component disappears, the internal counter is decreased automatically🧠 This composable is a smart alternative to using a Pinia store or global singleton. It prevents duplicated instances and fully manages the lifecycle for you.
📋 Changelog
v2.0.1
- Transport changed to
BroadcastChannel— replacespostMessage+window.opener. Works reliably across same-origin tabs and iframes even after cross-origin redirects that severwindow.opener(Chrome 88+, Safari). - Removed
connect/connectedhandshake — no longer needed;BroadcastChannelhas no connection state. - Removed directionality filter —
data.isChicken !== this.isChickencheck removed;BroadcastChannelnaturally prevents echo; cross-transport deduplication via_idfield. - Dual transport —
BroadcastChannel(same-origin) +postMessage(cross-origin iframe) run simultaneously, enabling both redirect-tab and subdomain-iframe scenarios. onLost()preserved — ping/pong keepalive still works to detect when the child tab/iframe is closed.
v1.1.0
window.opentab support —isChickenauto-detection now also covers tabs opened viawindow.open. Previously onlyiframewas detected automatically (window.parent !== window). Nowwindow.opener !== nullis also checked, so tabs opened by another window are treated as chicken without any extra config.partnerWindowselection — in chicken mode,partnerWindowis now resolved aswindow.parent(iframe) orwindow.opener(opened tab), whichever applies.
