Giter VIP home page Giter VIP logo

framebus's Introduction

Framebus Build Status Build Status npm version

Framebus allows you to easily send messages across frames (and iframes) with a simple bus.

In one frame:

var Framebus = require("framebus");
var bus = new Framebus();

bus.emit("message", {
  from: "Ron",
  contents: "they named it...San Diago",
});

In another frame:

var Framebus = require("framebus");
var bus = new Framebus();

bus.on("message", function (data) {
  console.log(data.from + " said: " + data.contents);
});

The Framebus class takes a configuration object, where all the params are optional.

type FramebusOptions = {
  origin?: string, // default: "*"
  channel?: string, // no default
  verifyDomain?: (url: string) => boolean, // no default
  targetFrames?: <HTMLFrameElement | Window>[], // by default, all frames available to broadcast to
};

The origin sets the framebus instance to only operate on the chosen origin.

The channel namespaces the events called with on and emit so you can have multiple bus instances on the page and have them only communicate with busses with the same channel value.

If a verifyDomain function is passed, then the on listener will only fire if the domain of the origin of the post message matches the location.href value of page or the function passed for verifyDomain returns true.

var bus = new Framebus({
  verifyDomain: function (url) {
    // only return true if the domain of the url matches exactly
    url.indexOf("https://my-domain") === 0;
  },
});

If a targetFrames array is passed, then framebus will only send messages to those frames and listen for messages from those frames. You can pass a reference to a Window (the return value of window.open) or an HTMLFrameElement (a DOM node representing an iframe).

var myIframe = document.getElementById("my-iframe");

var bus = new Framebus({
  targetFrames: [myIframe],
});

To add additional frames to the targetFrames array in the future, use the addTargetFrame method. targetFrames must be set, even if it's an empty array, for this method to work.

var myIframe = document.getElementById("my-iframe");

var bus = new Framebus({
  targetFrames: [],
});

bus.addTargetFrame(myIframe);

API

target(options: FramebusOptions): framebus

returns: a chainable instance of framebus that operates on the chosen origin.

This method is used in conjuction with emit, on, and off to restrict their results to the given origin. By default, an origin of '*' is used.

framebus
  .target({
    origin: "https://example.com",
  })
  .on("my cool event", function () {});
// will ignore all incoming 'my cool event' NOT from 'https://example.com'
Argument Type Description
options FramebusOptions See above section for more details

emit('event', data?, callback?): boolean

returns: true if the event was successfully published, false otherwise

Argument Type Description
event String The name of the event
data Object The data to give to subscribers
callback(data) Function Give subscribers a function for easy, direct replies

emitAsPromise('event', data?): Promise

returns: A promise that resolves when the emitted event is responded to the first time. It will reject if the event could not be succesfully published.

Argument Type Description
event String The name of the event
data Object The data to give to subscribers

Using this method assumes the browser context you are using supports Promises. If it does not, set a polyfill for the Framebus class with setPromise

// or however you want to polyfill the promise
const PolyfilledPromise = require("promise-polyfill");

Framebus.setPromise(PolyfilledPromise);

on('event', fn): boolean

returns: true if the subscriber was successfully added, false otherwise

Unless already bound to a scope, the listener will be executed with this set to the MessageEvent received over postMessage.

Argument Type Description
event String The name of the event
fn(data?, callback?) Function Event handler. Arguments are from the emit invocation
โ†ณ this scope The MessageEvent object from the underlying postMessage

off('event', fn): boolean

returns: true if the subscriber was successfully removed, false otherwise

Argument Type Description
event String The name of the event
fn Function The function that was subscribed

include(popup): boolean

returns: true if the popup was successfully included, false otherwise

var popup = window.open("https://example.com");

framebus.include(popup);
framebus.emit("hello popup and friends!");
Argument Type Description
popup Window The popup refrence returned by window.open

addTargetFrame(frame): boolean

Used in conjunction with targetFrames configuration. If a targetFrames array is not passed on instantiation, this method will noop.

var frame = document.getElementById("my-iframe");

framebus.addTargetFrame(frame);
framebus.emit("hello targetted iframe!");
Argument Type Description
frame Window or HTMLIFrameElement The iframe or popup to add to targetFrames

teardown(): void

Calls off on all listeners used for this bus instance and makes subsequent calls to all methods noop.

bus.on("event-name", handler);

// event-name listener is torn down
bus.teardown();

// these now do nothing
bus.on("event-name", handler);
bus.emit("event-name", data);
bus.off("event-name", handler);

Pitfalls

These are some things to keep in mind while using framebus to handle your event delegation

Cross-site scripting (XSS)

framebus allows convenient event delegation across iframe borders. By default it will broadcast events to all iframes on the page, regardless of origin. Use the optional target() method when you know the exact domain of the iframes you are communicating with. This will protect your event data from malicious domains.

Data is serialized as JSON

framebus operates over postMessage using JSON.parse and JSON.stringify to facilitate message data passing. Keep in mind that not all JavaScript objects serialize cleanly into and out of JSON, such as undefined.

Asynchronicity

Even when the subscriber and publisher are within the same frame, events go through postMessage. Keep in mind that postMessage is an asynchronous protocol and that publication and subscription handling occur on separate iterations of the event loop (MDN).

Published callback functions are an abstraction

When you specify a callback while using emit, the function is not actually given to the subscriber. The subscriber receives a one-time-use function that is generated locally by the subscriber's framebus. This one-time-use callback function is pre-configured to publish an event back to the event origin's domain using a UUID as the event name. The events occur as follows:

  1. http://emitter.example.com publishes an event with a function as the event data

    var callback = function (data) {
      console.log("Got back %s as a reply!", data);
    };
    
    framebus.emit("Marco!", callback, "http://listener.example.com");
  2. The framebus on http://emitter.example.com generates a UUID as an event name and adds the callback as a subscriber to this event.

  3. The framebus on http://listener.example.com sees that a special callback event is in the event payload. A one-time-use function is created locally and given to subscribers of 'Marco!' as the event data.

  4. The subscriber on http://listener.example.com uses the local one-time-use callback function to send data back to the emitter's origin

    framebus
      .target("http://emitter.example.com")
      .on("Marco!", function (callback) {
        callback("Polo!");
      });
  5. The one-time-use function on http://listener.example.com publishes an event as the UUID generated in step 2 to the origin that emitted the event.

  6. Back on http://emitter.example.com, the callback is called and unsubscribed from the special UUID event afterward.

Development and contributing

See CONTRIBUTING.md

framebus's People

Contributors

blutorange avatar braintreeps avatar crookedneighbor avatar dependabot[bot] avatar evanhahn avatar hollabaq86 avatar ibooker avatar jeffcarp avatar jplukarski avatar lilaconlee avatar mrak avatar oscarleonnogales avatar quinnjn avatar sancsalix avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

framebus's Issues

Near impossible to transpile into ESM

General information

  • Library version: master
  • Browser and OS: chrome-unstable 66.0.3355

Issue description

The "UMD" wrapper of your source makes it virtually impossible to transpile this from ES5 CJS to ESM. Notably, the module.exports are declared within an if statement inside an IIFE. Since ESM requires exports to be at the top level, a transform would have to be intelligent enough to unwrap both the if statement and the wrapping IIFE. This would be... difficult.

There are wonderful codemods like 5to6 that help bring code into the future developers want.

Ideally, this module would be converted into ECMAScript Modules according to the specification that governs the JavaScript language. The dist/framebus.js could continue to be a UMD bundle, perhaps created with tooling to generate that wrapper from the ECMAScript standard code that the library could be authored in.

Alternatively, availability of a CJS form of the module would be fine.

postMessage errors when listening to events as well as emitting

General information

  • Library version: 4.0.2
  • Browser and OS: FireFox 79.0 (64-bit) on Mac OS 10.15.6

Issue description

When using the target function to communicate between two frame, I get a lot of errors from the originating frame saying:

Failed to execute 'postMessage' on 'DOMWindow': The target origin provided ('http://127.0.0.1:8081') does not match the recipient window's origin ('http://127.0.0.1:8080').

It seems that if you have both frames listening and emitting events, you inevitably get a lot of errors on either side when using target. I assume this is outside of framebus's scope to be able to fix?

IE9 and Popups?

Is there any reason this wouldn't work in IE9 using popups? I havent generated a standalone example of it not working yet, but I can't seem to make it work in IE9 using a popup (not an iframe) for some reason.

Add note for mandatory "attach" call to README (for typescript)

General information

  • Library version: 5.0.0+ (now 6.0.0)
  • Typescript: 5.1.6
  • Angular 16.2.12/14

Issue description

I was upgrading from 2.0.5 and wondered several hours of debugging why it didn't work anymore.
Narrowed it down to the 4.x to 5.x change.
IntelliJ only let me import framebus via import {Framebus} from 'framebus/dist/framebus';
So, I was obviously missing the mandatory attach() call done in index.ts.

I want therefore to request a note in README.md to either add an additional line import 'framebus/dist/index'; or alternativly call attach() in dist/lib/attach directly.
This might prevent some frustration other users might have.

Thank you.

Sidenote - I found some typos in the README: conjuction, succesfully, refrence

Circular dependency

General information

  • Library version: 5.2.0
  • Browser and OS: Safari 16.4 on macOS 13.3.1

Issue description

When doing a build for a project that uses [email protected], I am seeing a circular dependency in the frames package...

Circular dependency: node_modules/framebus/dist/lib/package-payload.js -> node_modules/framebus/dist/lib/subscribe-replier.js -> node_modules/framebus/dist/framebus.js -> node_modules/framebus/dist/lib/package-payload.js

How do I remove a particular listener after it has been used?

General information

  • Library version: 5.x
  • Browser and OS: Chrome 92.0

Issue description

How do I remove a particular listener after it has been used?

My listener has done its job and sent the data via callback. I need to kill only that particular listener?

Infinite loop when sending events to/from a window included via framebus#include

General information

  • Library version: 3.0.x / 4.0.x / 5.0.x
  • Browser: 86.0.4240.183 (Official Build) (64-bit) / FireFox 83.0 (64-bit)

Issue description

First of all, let me say that this is a really nice and simple library. I made a simple demo to check out how it works and test if I can use it. I think I may have found one issue that occurs when using #include:

  • Main page creates a framebus, opens a window via window.open, includes it in the event system via framebus#include and emits a message event when a button is clicked
  • Opened window also creates a framebus, registers a listener for message and logs a message when the event occurs
  • This result in an endless loop with the event listener being invoked over and over again

See attached simple-demo.zip for a simple example:

npm i
npx webpack
npx http-server

Then open "index.html" in a web browser, click on open, then open dev tools in the opened window, then click on open in the original window. The dev tools in the opened window keeps logging a message.

image

Safari not reacting to bus.on

General information

  • Library version: 3.0.1
  • Browser and OS Safari Version 11.1 (13605.1.33.1.4) on Mac 10.13.4

Issue description

bus.on never triggers on Safari.

I have a small widget that sits on blogs, and sends iframe messages between the host and the iframe.

after some fetch calls i have a piece of code that goes like, it just passes a few simple text messages as json, and handleCookieReaderMessage just passes a small text to tell me if the user has cookies enabled or not, so i can also retrieve them later.

    // get messages from notices iframes
    bus.on('agate-iframe-message',  (data) => props.handleIframeMessage(data));
    // get messages from iframe-cookie-reader iframe
    bus.on('agate-cookies', (data) => props.handleCookieReaderMessage(data));

This works perfectly well in chrome, firefox, brave, lots of browsers. Safari, does not even react to it.

To see this in action, you can go to this staging website in chrome and safari: http://popbitch.agate.one/2017/10/i-the-tabloid-triangle/ (sorry for the gossip type of website, but to get the point across).

I have noticed that this issue also does not appear in an earlier Safari, v10. Which might be the cause of it, but I wonder if someone can replicate it or give some indications of what else can i do around it to detect safari and still pass messages

Does not publish event when iframe is inside a web component

Hi
Thank you so much for the very easy to use and brilliant library.

We've been using this library for a while and recently we have started using StencilJS to convert our normal components into Web components. This means we have enabled shadow DOM for each of our components.

The issue that we're running into now is as soon as we turn the shadow DOM on, we cannot send events to the iframe anymore, even though we can still receive events.!!

So I did a little experimentation and realised if I send my events directly to the iframe instance, it works :

iframeInstance = document.querySelector('#myIframeElement');
iframeInstance.contentWindow.postMessage('myMessage',"*");

I can receive this message, which does not use framebus, inside my iframe.

However, below does not work :

const frameBusInstance = new FrameBus();
frameBusInstance.emit('myMessage',{someData:"Whatever"})

If I turn off the shadow DOM, they both work.

We're using the latest version which is 5.0.0

Thank you so much :)

Use MessageEvent from 'this' without TypeScript errors

General information

  • Library version: 2.0.5
  • n/a

Issue description

This isn't a bug, I just need some advice to stop me going crazy with TypeScript errors.

Within the 'on' function, I would like to be able to get all contextual information from the MessageEvent object, which is contained in this (for example the channel and message). I'm having trouble getting it without TypeScript errors, and also casting as an object.

   bus
    .target({channel: "ParentApp"})
    .on("contextUpdate", function (data: Record<string, unknown>) {
      console.log("contextUpdate from ParentApp");
       const ev = this as MessageEvent;
       console.log(ev.data);
      console.log(data.from + " was updated to: " + data.contents);
    });

Gives compiler error: 'this' implicitly has type 'any' because it does not have a type annotation.

The next thing ...

Within my browser the output is /*framebus*/{"event":"ParentApp:contextUpdate","origin":"*","eventData":{"from":"filtermenu","contents":["foo","bar","fizz"]}}. So the object has the prefix /framebus/.

My questions:

  • Do you have a code snippet to show how I should get the message context cleanly without compiler errors? :)
  • Is there a clean way to unpack the MessageEvent data object? Or should I call JSON.parse(result.replace(messagePrefix, "")) like you do in your codebase?

Thanks!

What is new in 5.0.0?

Hi there, I just upgraded to 5.0.0 and I am getting a ton of errors. it seems the library's API has changed. Is there any new documentation for 5.0?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.