org.clojars.benzap/flyer

Clojurescript + Javascript Broadcast Messaging between iFrames, Framesets, and Windows


Keywords
clojurescript, javascript, legacy, messaging
License
Apache-2.0

Documentation

Flyer.js

./doc/intro.png

flyer.js is a lightweight messaging library written for clojurescript & javascript.

It provides broadcast messaging between frames, iframes, and windows.

https://img.shields.io/clojars/v/flyer.svg

Including flyer in your clojurescript project

Leiningen

[flyer "1.1.2"]

Clojure CLI/deps.edn

flyer {:mvn/version "1.1.2"}

Gradle

compile 'flyer:flyer:1.1.2'

Maven

<dependency>
  <groupId>flyer</groupId>
  <artifactId>flyer</artifactId>
  <version>1.1.2</version>
</dependency>

Features and Operation

  • Simple API, from which you can build more elaborate messaging systems.
  • Messages are broadcasted to all frames and registered windows, regardless of whether or not they want them (hence, the name)
  • Frames can subscribe to specific channels, and can regex-match to broader topics being sent on that channel

Javascript Configuration

  • Include flyer.js at the end of your page body
    <script type="text/javascript" src="./js/flyer.js"></script>
        
  • In situations where you would use window.open, use flyer.window.open instead. This is required in order to track window references.

Download

Javascript API

flyer.broadcast([Options])

Options

channel
the channel you wish to broadcast on. default is “*”, which stands for all channels
topic
the topic of your broadcast message. default is “*”, which stands for all topics
data
any JSON serializable object
target
determines what type of target domain is expected to receive this message. By default, the target is “all”, but it can be set to “local”, or a target domain of your choosing.

Examples

flyer.broadcast({
  channel: "default",
  topic: "person.insert",
  data: {id: 1, name: "Ben"}
});

flyer.broadcast({
  channel: "default",
  topic: "person.delete",
  data: {id: 1}
});

flyer.subscribe([Options])

Options

channel
the channel you wish to subscribe to. default is “*”, which stands for all channels
topic
the topic you wish to subscribe to. default is “*”, which stands for all topics. Note that this can also be a string representation of a regex expression.
callback
callback function of the form function(data, [topic], [channel] [origin]) that you wish to call when the subscription is made.
origin
refers to the origin, or domain from which the messages will be received. The default is all, but it can be set to local, or to another origin

Examples

flyer.subscribe({
  channel: "default",
  topic: "person.*",
  callback: function(data, topic, channel) {
    if (topic == "person.insert") {
      console.log("Inserted Person! - " + data.name);
    }
    else if (topic == "person.delete") {
      console.log("Removed Person with id - " + data.id);
    }
  }
});

flyer.window.open(url, name, [Options])

follows the same API as window.open

Mozilla API Page

Clojurescript API (untested)

flyer.messaging/broadcast

(broadcast & options)

Options

channel
the channel you wish to broadcast on. default is “*”, which stands for all channels
topic
the topic of your broadcast message. default is “*”, which stands for all topics
data
any JSON serializable object
target
refers to the target origin, or domain in which to post the message. The default is :all, but it can also be :local, or a target origin of your choosing

Example

(broadcast :channel "default"
           :topic "person.insert"
           :data {:id 1 :name "Ben"}
           :origin :all)

flyer.messaging/subscribe

(subscribe & options)

Options

channel
the channel you wish to subscribe to. default is “*”, which stands for all channels
topic
the topic you wish to subscribe to. default is “*”, which stands for all topics. Note that this can also be a string representation of a regex expression.
callback
callback function of the form (fn [data] [topic] [channel] [origin]) that you wish to call when the subscription is made.
origin
the origin you wish to subscribe to. This is decides on the domain that messages can be received. The default is :all, but it can also be :local, or an origin of your choice

Example

(subscribe :channel "default"
           :origin :local
           :topic "person.*"
           :callback
           (fn [data topic channel]
               (condp = topic
                      "person.insert"
                      (.log js/console "Inserting person! - " (.-name data))
                      "person.delete"
                      (.log js/console "Deleting person! - #" (.-id data)))))

flyer.window/open

(open url name & options)

url parameter

The window URL

name parameter

The unique name you wish to give the window

Options

key / value pairs of options equivalent to window.open options

Example

(open "frame_login.html" "login-page" :width 400 :height 600)

Example

Examples Outdated

Project Compilation

  1. Clone this Repository
  2. Install Leiningen
  3. cd into flyer.js directory
  4. type lein deps
  5. resulting flyer.js should now be present in ./resources/public/js/

Issues

  • In order to communicate with frames and windows that are within an external window, you need to replace window.open with flyer.window.open
  • The size of flyer.js is quite big, at a whopping 1mb. This is due to the nature of compilation. Use flyer.min.js to bring this down to 100kb
  • Refreshing the parent window of an opened window will break any messages from being broadcasted throughout the application. This is due to the external window frame losing its parent (window.opener).
  • external windows can be refreshed without losing communications, however, it requires that flyer.js be included within that html page

Technical Details

How it works

flyer.js works by hijacking window.postMessage, and the event generated, which can be attached to through the “message” event

Mozilla API Page

//attaching a listener to frame[0]
window.frame[0].addEventListener("message", 
  function(event) { 
    console.log(event.data, event.origin)
  });

//posting a message to frame[0]
window.frame[0].postMessage("some message", "*")

It also does a few other things:

  • generates a list of frames, by performing a traversal through all of the frames and external windows.
  • wraps the functionality into two sane functions.

The full set of steps when performing a broadcast:

  1. Generate list of frames and external window frames (flyer.traversal)
  2. Generate a message, by stringifying a defined message object {channel:…, topic:…, data:…} (flyer.messaging)
  3. For each frame, f, perform f.postMessage(msg, target)

The full steps when performing a subscribe:

  1. Grab the callback function, and add a “message” event listener decorated with a set of rules
    • Does it have the same channel?
    • Does it have the same topic?
    • If it isn’t the same topic, does the provided callback topic regex match the broadcasted topic?
    • Does it have a lax origin, or is it accepting all origins?
  2. If it passes the conditions in step 1, the main callback is called with the results of the broadcast. Further conditions can be provided at the developers discretion.