Skip to content

evilkiwi/embed

Repository files navigation

NPM Discord GPL-3.0-only

Embedded iFrame IPC for Vue 3

@evilkiwi/embed provides a single Vue 3 hook which can be used to communicate between an iFrame and its parent via postMessage IPC.

  • sync/async messaging/responses
  • Configurable timeouts
  • Bi-directional communication
  • Cross-origin support
  • Same usage/API for both Host & Client
  • Support for enforcing origins for increased security
  • No limit to number of instances you can use/create at any given time
  • TypeScript
  • Tiny (1.73kb)

Installation

This package is available via NPM:

yarn add @evilkiwi/embed

# or

npm install @evilkiwi/embed

Usage

For this example, we'll assume the host is a webpage (example.com) and the client is a webpage embedded in an iFrame (frame.example.com). The only difference between a host and a client is that the host requires an iFrame ref for binding and sending the messages.

/** * Host */
<template>
  <iframe src="https://frame.example.com" ref="iframe" sandbox="allow-scripts" />
</template>

<script lang="ts" setup>
import { useEmbed } from '@evilkiwi/embed';
import { onMounted, ref } from 'vue';

const iframe = ref<InstanceType<typeof HTMLIFrame>>();

const { send, events } = useEmbed('host', {
  id: 'shared-id',
  iframe,
  remote: 'https://frame.example.com',
});

// Listen for any synchronous events being emitted over IPC
events.on('yay', payload => {
  console.log(payload);
});

onMounted(async () => {
  // Send an event to the iFrame and wait for a response.
  const response = await send('hello-world', {
    hello: 'world',
  });
});
</script>

/** * Client */
<template>
  <button @click.prevent="submit">Click me!</button>
</template>

<script lang="ts" setup>
import { useEmbed } from '@evilkiwi/embed';

const { handle, post } = useEmbed('client', {
  id: 'shared-id',
  remote: 'https://example.com',
});

// Resolves incoming (a)synchronous operations.
handle('hello-world', async payload => {
  if (payload.hello === 'world') {
    return 'hey';
  }

  return 'go away';
});

const submit = () => {
  // Send a synchronous event to the host
  post('yay', { test: 123 });
};
</script>

This example shows:

  • Initializing the Host and Client
  • Sending and waiting for asynchronous events
  • Sending and receiving synchronous events

Since communication is bi-directional, you can use any of the methods on either Host or Client. For example, asynchronous operations aren't limited to Host -> Client, the Client can also call asynchronous operations and the Host can register handlers/resolvers.

Option Default Type Description
id [Required] string The Host and Client that you want to talk to each other should share the _same_ ID.
timeout 15000 number Configures the global timeout for all asynchronous operations against this ID pair.
iframe [Required for Host] Ref<InstanceType<typeof HTMLIFrame>> A Vue 3 ref for a Template reference.
remote * string A remote URL to limit who can recieve/process Events over this Host/Client pair.
debug false boolean Whether to print Debug messages to the console, providing an overview of the IPC process.

Security Note

By default, if you don't supply a remote, the library will process all incoming messages and send events that any party can recieve. By setting this to a URL (See above example), you can limit this and hugely reduce the impact it has on security.

To-do

  • Add a test suite