{"id":16278654,"url":"https://github.com/pixelass/esdeka","last_synced_at":"2025-10-06T01:51:58.142Z","repository":{"id":63686579,"uuid":"569863092","full_name":"pixelass/esdeka","owner":"pixelass","description":"Communicate between iframe and host","archived":false,"fork":false,"pushed_at":"2023-04-01T15:44:31.000Z","size":1237,"stargazers_count":12,"open_issues_count":0,"forks_count":1,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-09-29T15:52:04.145Z","etag":null,"topics":["broadcast","communication","hooks","iframe","message","postmessage","reactjs"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/pixelass.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":".github/CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":".github/CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2022-11-23T19:37:52.000Z","updated_at":"2025-07-20T07:14:31.000Z","dependencies_parsed_at":"2024-10-27T21:40:46.643Z","dependency_job_id":"16c068e7-74fa-40b2-90a8-ce0207dcbcbc","html_url":"https://github.com/pixelass/esdeka","commit_stats":{"total_commits":76,"total_committers":2,"mean_commits":38.0,"dds":0.09210526315789469,"last_synced_commit":"375ffd1819b695a5364458a22ae813eadd4c4324"},"previous_names":[],"tags_count":19,"template":false,"template_full_name":"pixelass/react-package-template","purl":"pkg:github/pixelass/esdeka","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pixelass%2Fesdeka","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pixelass%2Fesdeka/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pixelass%2Fesdeka/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pixelass%2Fesdeka/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/pixelass","download_url":"https://codeload.github.com/pixelass/esdeka/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pixelass%2Fesdeka/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":278547818,"owners_count":26004773,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-10-05T02:00:06.059Z","response_time":54,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["broadcast","communication","hooks","iframe","message","postmessage","reactjs"],"created_at":"2024-10-10T18:59:20.053Z","updated_at":"2025-10-06T01:51:58.127Z","avatar_url":"https://github.com/pixelass.png","language":"TypeScript","funding_links":["https://github.com/sponsors/pixelass"],"categories":[],"sub_categories":[],"readme":"# Esdeka\n\nCommunicate between `\u003ciframe\u003e` and host\n\n\u003cp align=\"center\"\u003e\n  \u003cimg src=\"https://raw.githubusercontent.com/pixelass/esdeka/main/resources/esdeka-logo.svg\" alt=\"\" width=\"256\"/\u003e\n\u003c/p\u003e\n\n[![Codacy coverage](https://img.shields.io/codacy/coverage/e6bcc5f792d54ed0902f9116e9435da3?logo=jest\u0026style=for-the-badge)](https://app.codacy.com/gh/pixelass/esdeka/dashboard?branch=main)\n[![Codacy grade](https://img.shields.io/codacy/grade/e6bcc5f792d54ed0902f9116e9435da3?logo=codacy\u0026style=for-the-badge)](https://app.codacy.com/gh/pixelass/esdeka/dashboard?branch=main)\n[![Snyk Vulnerabilities for GitHub Repo](https://img.shields.io/snyk/vulnerabilities/github/pixelass/esdeka?color=%23471694\u0026logo=snyk\u0026style=for-the-badge)](https://snyk.io/test/github/pixelass/esdeka)\n\n[![npm](https://img.shields.io/npm/v/esdeka?color=%23cb3837\u0026logo=npm\u0026style=for-the-badge)](https://www.npmjs.com/package/esdeka)\n[![npm downloads weekly](https://img.shields.io/npm/dw/esdeka?color=%23cb3837\u0026logo=npm\u0026style=for-the-badge)](https://www.npmjs.com/package/esdeka)\n\n[![License MIT](https://img.shields.io/github/license/pixelass/esdeka?style=for-the-badge)](https://github.com/pixelass/esdeka/blob/main/LICENSE)\n![node-current](https://img.shields.io/node/v/esdeka?color=%23026e00\u0026style=for-the-badge)\n\n[![GitHub Sponsors](https://img.shields.io/github/sponsors/pixelass?color=%23db61a2\u0026logo=github\u0026style=for-the-badge)](https://github.com/sponsors/pixelass)\n\n## Table of Contents\n\n\u003c!-- toc --\u003e\n\n- [Mechanism](#mechanism)\n  - [Creating a connection](#creating-a-connection)\n- [Functions](#functions)\n  - [`call`](#call)\n  - [`answer`](#answer)\n  - [`disconnect`](#disconnect)\n  - [`subscribe`](#subscribe)\n  - [`dispatch`](#dispatch)\n  - [`broadcast`](#broadcast)\n- [React hooks](#react-hooks)\n  - [`useHost`](#usehost)\n  - [`useGuest`](#useguest)\n- [Bundle size](#bundle-size)\n- [Full React example (using Zustand)](#full-react-example-using-zustand)\n\n\u003c!-- tocstop --\u003e\n\n## Mechanism\n\nEsdeka is a small and lightweight library that provides a simple mechanism for communication between a host window and one or more guest iframes. It uses [`window.postMessage`](https://developer.mozilla.org/en-US/docs/Web/API/Window/postMessage) to transmit data between the two, and offers several helper functions to make the process as easy as possible.\n\nWith Esdeka, you can stream data down to the iframe, and if the iframe needs to communicate back, it can dispatch an action with an optional payload. You can then choose how to respond to the transmitted data.\n\nEsdeka is easy to use and offers a range of functions to create and manage connections, including `call`, `answer`, `broadcast`, and `subscribe`. It also includes a set of React hooks for even simpler integration with your React projects.\n\nWith all bundles smaller than 1KB, Esdeka is a great choice for lightweight communication between your web pages and iframes.\n\n\n\n\u003cimg src=\"https://raw.githubusercontent.com/pixelass/esdeka/main/resources/esdeka-flow.svg\" alt=\"\"/\u003e\n\u003csmall\u003eFlux flow\u003c/small\u003e\n\n### Creating a connection\n\nTo create a connection we need to call a client and wait for an answer.\n\nSetting up a **Host**.\n\n```ts\nimport { call } from \"esdeka\";\n\nconst iframe = document.querySelector(\"iframe\");\n\ncall(iframe.contentWindow, \"my-channel\", { some: \"Data\" });\n```\n\nSetting up a **Guest**.\n\n```ts\nimport { answer, subscribe } from \"esdeka\";\n\nsubscribe(\"my-channel\", event =\u003e {\n  if (event.data.action.type === \"call\") {\n    answer(event.source, \"my-channel\");\n  }\n});\n```\n\nOnce a connection exists, we can broadcast information from the host to the guest.\n\n**Host**\n\n```ts\nimport { broadcast, call, subscribe } from \"esdeka\";\n\nconst iframe = document.querySelector(\"iframe\");\n\ncall(iframe.contentWindow, \"my-channel\", { some: \"Data\" });\n\nsubscribe(\"my-channel\", event =\u003e {\n  if (event.data.action.type === \"answer\") {\n    broadcast(event.source, \"my-channel\", {\n      question: \"How are you?\",\n    });\n  }\n});\n```\n\nThe guest subscribes to all messages and act accordingly.\n\n**Guest**\n\n```ts\nimport { answer, subscribe } from \"esdeka\";\n\nconst questions = [];\n\nsubscribe(\"my-channel\", event =\u003e {\n  const { type, payload } = event.data.action;\n  switch (type) {\n    case \"broadcast\":\n      if (payload?.question) {\n        questions.push(payload.question);\n      }\n      break;\n    case \"call\":\n      answer(event.source, \"my-channel\");\n      break;\n    default:\n      console.error(\"Not implemented\");\n      break;\n  }\n});\n```\n\n## Functions\n\n### `call`\n\nSends a connection request from the host to a guest. The payload can be anything that you want to\nsend through a channel.\n\n| Argument        | Type      | Description                                                                                                                                                                 |\n| --------------- | --------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `source`        | `Window`  | Has to be a [`Window`](https://developer.mozilla.org/en-US/docs/Web/API/Window) to use [`postMessage`](https://developer.mozilla.org/en-US/docs/Web/API/Window/postMessage) |\n| `channel`       | `string`  | The channel on which the host and gues communicate. The host and guest have to use the same channel to communicate.                                                         |\n| `payload`       | `unknown` | The payload that of the message can contain any data. We cannot transmit functions or circular objects, therefore we recommend using a serializer.                          |\n| `targetOrigin?` | `string`  | Optional origin to prevent insecure communication.                                                                                                                          |\n\n```ts\ncall(window, \"my-channel\", {\n  message: \"Hello\",\n});\n```\n\n### `answer`\n\nAnswer to a host to confirm the connection.\n\n| Argument        | Type     | Description                                                                                                                                                                 |\n| --------------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `source`        | `Window` | Has to be a [`Window`](https://developer.mozilla.org/en-US/docs/Web/API/Window) to use [`postMessage`](https://developer.mozilla.org/en-US/docs/Web/API/Window/postMessage) |\n| `channel`       | `string` | The channel on which the host and gues communicate. The host and guest have to use the same channel to communicate.                                                         |\n| `targetOrigin?` | `string` | Optional origin to prevent insecure communication.                                                                                                                          |\n\n```ts\nanswer(window, \"my-channel\");\n```\n\n### `disconnect`\n\nTell the host that the guest disconnected.\n\n| Argument        | Type     | Description                                                                                                                                                                 |\n| --------------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `source`        | `Window` | Has to be a [`Window`](https://developer.mozilla.org/en-US/docs/Web/API/Window) to use [`postMessage`](https://developer.mozilla.org/en-US/docs/Web/API/Window/postMessage) |\n| `channel`       | `string` | The channel on which the host and gues communicate. The host and guest have to use the same channel to communicate.                                                         |\n| `targetOrigin?` | `string` | Optional origin to prevent insecure communication.                                                                                                                          |\n\n```ts\ndisconnect(window, \"my-channel\");\n```\n\n### `subscribe`\n\nListen to all messages in a channel.\n\n| Argument        | Type                            | Description                                                                                                                                                                 |\n| --------------- | ------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `source`        | `Window`                        | Has to be a [`Window`](https://developer.mozilla.org/en-US/docs/Web/API/Window) to use [`postMessage`](https://developer.mozilla.org/en-US/docs/Web/API/Window/postMessage) |\n| `channel`       | `string`                        | The channel on which the host and gues communicate. The host and guest have to use the same channel to communicate.                                                         |\n| `callback`      | `(event: MessageEvent) =\u003e void` | The callback function of the subscription                                                                                                                                   |\n| `targetOrigin?` | `string`                        | Optional origin to prevent insecure communication.                                                                                                                          |\n\n```ts\nsubscribe(\"my-channel\", event =\u003e {\n  console.log(event);\n});\n```\n\n### `dispatch`\n\nSend an action to Esdeka. The host will be informed and can act un the request.\n\n| Argument        | Type              | Description                                                                                                                                                                 |\n| --------------- | ----------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `source`        | `Window`          | Has to be a [`Window`](https://developer.mozilla.org/en-US/docs/Web/API/Window) to use [`postMessage`](https://developer.mozilla.org/en-US/docs/Web/API/Window/postMessage) |\n| `channel`       | `string`          | The channel on which the host and gues communicate. The host and guest have to use the same channel to communicate.                                                         |\n| `action`        | `Action\u003cunknown\u003e` | The action that is dispatched by the guest.                                                                                                                                 |\n| `targetOrigin?` | `string`          | Optional origin to prevent insecure communication.                                                                                                                          |\n\n**Without payload**\n\n```ts\ndispatch(window, \"my-channel\", {\n  type: \"increment\",\n});\n```\n\n**With payload**\n\n```ts\ndispatch(window, \"my-channel\", {\n  type: \"greet\",\n  payload: {\n    message: \"Hello\",\n  },\n});\n```\n\n### `broadcast`\n\nSend data from the host window to the guest. The payload can be anything that you want to send\nthrough a channel.\n\n| Argument        | Type      | Description                                                                                                                                                                 |\n| --------------- | --------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `source`        | `Window`  | Has to be a [`Window`](https://developer.mozilla.org/en-US/docs/Web/API/Window) to use [`postMessage`](https://developer.mozilla.org/en-US/docs/Web/API/Window/postMessage) |\n| `channel`       | `string`  | The channel on which the host and gues communicate. The host and guest have to use the same channel to communicate.                                                         |\n| `payload`       | `unknown` | The payload that of the message can contain any data. We cannot transmit functions or circular objects, therefore we recommend using a serializer.                          |\n| `targetOrigin?` | `string`  | Optional origin to prevent insecure communication.                                                                                                                          |\n\n```ts\nbroadcast(window, \"my-channel\", {\n  message: \"Hello\",\n});\n```\n\n## React hooks\n\n### `useHost`\n\nCurried host functions that don't need the window and channel.\n\n```tsx\nconst { broadcast, call, subscribe } = useHost(ref, \"my-channel\");\n\ncall({\n  message: \"Hello\",\n});\n\nbroadcast({\n  message: \"Hello\",\n});\n\nsubscribe(event =\u003e {\n  console.log(event);\n});\n```\n\n### `useGuest`\n\nCurried guest functions that don't need the window and channel.\n\n```tsx\nconst { answer, disconnect, dispatch, subscribe } = useGuest(ref, \"my-channel\");\n\nanswer();\n\ndisconnect();\n\nsubscribe(event =\u003e {\n  console.log(event);\n});\n\ndispatch({\n  type: \"greet\",\n  payload: {\n    message: \"Hello\",\n  },\n});\n```\n\n## Bundle size\n\nAll bundles are smaller than 1KB\n\n\u003c!-- bundle --\u003e\n\n```shell\n\n PASS  ./dist/index.js: 568B \u003c maxSize 1KB (gzip)\n\n PASS  ./dist/index.mjs: 526B \u003c maxSize 1KB (gzip)\n\n PASS  ./dist/react.js: 752B \u003c maxSize 1KB (gzip)\n\n PASS  ./dist/react.mjs: 729B \u003c maxSize 1KB (gzip)\n\n```\n\n\u003c!-- bundlestop --\u003e\n\n## Full React example (using Zustand)\n\n**Host**\n\n`http://localhost:3000/`\n\n```tsx\nimport { serialize, useHost } from \"esdeka/react\";\nimport { DetailedHTMLProps, IframeHTMLAttributes, useEffect, useRef, useState } from \"react\";\nimport create from \"zustand\";\n\nexport interface StoreModel {\n  counter: number;\n  increment(): void;\n  decrement(): void;\n}\n\nexport const useStore = create\u003cStoreModel\u003e(set =\u003e ({\n  counter: 0,\n  increment() {\n    set(state =\u003e ({ counter: state.counter + 1 }));\n  },\n  decrement() {\n    set(state =\u003e ({ counter: state.counter - 1 }));\n  },\n}));\n\nexport interface EsdekaHostProps\n  extends DetailedHTMLProps\u003cIframeHTMLAttributes\u003cHTMLIFrameElement\u003e, HTMLIFrameElement\u003e {\n  channel: string;\n  maxTries?: number;\n  interval?: number;\n}\n\nexport function EsdekaHost({ channel, maxTries = 30, interval = 30, ...props }: EsdekaHostProps) {\n  const ref = useRef\u003cHTMLIFrameElement\u003e(null);\n  const connection = useRef(false);\n  const [tries, setTries] = useState(maxTries);\n\n  const { broadcast, call, subscribe } = useHost(ref, channel);\n\n  // Send a connection request\n  useEffect(() =\u003e {\n    if (connection.current || tries \u003c= 0) {\n      return () =\u003e {\n        /* Consistency */\n      };\n    }\n\n    call(serialize(useStore.getState()));\n    const timeout = setTimeout(() =\u003e {\n      call(serialize(useStore.getState()));\n      setTries(tries - 1);\n    }, interval);\n\n    return () =\u003e {\n      clearTimeout(timeout);\n    };\n  }, [call, tries, interval]);\n\n  useEffect(() =\u003e {\n    if (!connection.current) {\n      const unsubscribe = subscribe(event =\u003e {\n        const store = useStore.getState();\n        const { action } = event.data;\n        switch (action.type) {\n          case \"answer\":\n            connection.current = true;\n            break;\n          default:\n            if (typeof store[action.type] === \"function\") {\n              store[action.type](action.payload.store);\n            }\n            break;\n        }\n      });\n      return () =\u003e {\n        unsubscribe();\n      };\n    }\n    return () =\u003e {\n      /* Consistency */\n    };\n  }, [subscribe]);\n\n  // Broadcast store to guest\n  useEffect(() =\u003e {\n    if (connection.current) {\n      const unsubscribe = useStore.subscribe(newState =\u003e {\n        broadcast(serialize(newState));\n      });\n      return () =\u003e {\n        unsubscribe();\n      };\n    }\n    return () =\u003e {\n      /* Consistency */\n    };\n  }, [broadcast]);\n\n  return \u003ciframe ref={ref} {...props} /\u003e;\n}\n\nexport default function App() {\n  const increment = useStore(state =\u003e state.increment);\n  const decrement = useStore(state =\u003e state.decrement);\n  const counter = useStore(state =\u003e state.counter);\n  return (\n    \u003cdiv\u003e\n      \u003cbutton onClick={increment}\u003eUp\u003c/button\u003e\n      \u003cspan\u003e{counter}\u003c/span\u003e\n      \u003cbutton onClick={decrement}\u003eDown\u003c/button\u003e\n      \u003cEsdekaHost channel=\"esdeka-test\" src=\"http://localhost:3001\" /\u003e\n    \u003c/div\u003e\n  );\n}\n```\n\n**Guest**\n\n`http://localhost:3001`\n\n```tsx\nimport { useGuest } from \"esdeka/react\";\nimport { useEffect, useRef } from \"react\";\nimport create from \"zustand\";\n\nexport interface StoreModel {\n  [key: string]: any;\n  // eslint-disable-next-line no-unused-vars\n  set(state: Omit\u003cStoreModel, \"set\"\u003e): void;\n}\n\nexport const useStore = create\u003cStoreModel\u003e(set =\u003e ({\n  set(state) {\n    set(state);\n  },\n}));\n\nexport function EsdekaGuest({ channel }: { channel: string }) {\n  const counter = useStore(state =\u003e state.counter);\n  const host = useRef\u003cWindow | null\u003e(null);\n  const { answer, dispatch, subscribe } = useGuest();\n\n  useEffect(() =\u003e {\n    const unsubscribe = subscribe\u003cExcept\u003cStoreModel, \"set\"\u003e\u003e(event =\u003e {\n      const { action } = event.data;\n      switch (action.type) {\n        case \"call\":\n          host.current = event.source as Window;\n          answer();\n          break;\n        case \"broadcast\":\n          useStore.getState().set(action.payload);\n          break;\n        default:\n          break;\n      }\n    });\n    return () =\u003e {\n      unsubscribe();\n    };\n  }, [answer, subscribe]);\n\n  return (\n    \u003cdiv\u003e\n      \u003ch1\u003eCurrent Count: {counter}\u003c/h1\u003e\n      \u003cbutton\n        onClick={() =\u003e {\n          dispatch({ type: \"decrement\" });\n        }}\n      \u003e\n        Down\n      \u003c/button\u003e\n      \u003cbutton\n        onClick={() =\u003e {\n          dispatch({ type: \"increment\" });\n        }}\n      \u003e\n        Up\n      \u003c/button\u003e\n    \u003c/div\u003e\n  );\n}\n\nexport default function Page() {\n  return \u003cEsdekaGuest channel=\"esdeka-test\" /\u003e;\n}\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpixelass%2Fesdeka","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fpixelass%2Fesdeka","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpixelass%2Fesdeka/lists"}