IHop is a utility to allow for objects and functions from one iframe context to be usable from many connected contexts - even cross-origin - as though those objects were local.
The term "iframe hopping" refers to the fact that each participant in the "network" (tree, really) of window objects only communicates only with it's immediate parent and children. Sending messages to window contexts beyond the immediate family must be done by routing through each intermediate node(s) (ie. hopping).
At its core, IHop is three things:
- A proxy engine that can generate proxies for complex objects (even DOM elements!)
- A network-agnostic routing fabric designed for hierarchical topologies
- A globally-coherent state built on top of the routing fabric
- Clone the repository
npm install
npm run build
You will then have ihop.js
and ihop.min.js
in the ./dist
directory. They are both UMD modules and therefore should work with the most popular module systems or as a script tag.
NOTE: The non-minified file does very noisy console.debug
logging of every message that goes through the Router
and should only be used for development.
Step1 - Include IHop on your page
<script type="text/javascript" src="ihop.min.js"></script>
Step 2 - Initialize ihop
const ihop = new IHop('pick_a_namespace');
Step 3a - Either export an object...
ihop.export('test', { foo: 'bar' });
Step 3b - ...or a function
ihop.export('func', () => 'Hello world!');
Step 4 - Use what you exported from another iframe or the parent context
<script type="text/javascript">
// This is assuming that we are in another iframe:
Promise.all([
ihop.import('pick_a_namespace.test'),
ihop.import('pick_a_namespace.func')
]).then(async ([test, func]) => {
console.log(await test.foo); //=> bar
console.log(await func()); //=> Hello world!
});
</script>
Congratulations! You’ve just successfully used IHop to export an object and a function across those pesky iframe barriers!
- new IHop(nameSpace, options)
- methods
- .export(name, object) ⇒
void
- .import(path) ⇒
Promise
- .registerWorker(worker) ⇒
void
- .export(name, object) ⇒
- properties
- methods
Construct the local IHop instance.
Kind: constructor of IHop
Param | Type | Description |
---|---|---|
nameSpace | string |
A globally available name-space to hold all of this context's exported objects. |
options | object |
Options to alter default behavior. |
[options.model] | object |
Container for Model-related options. |
[options.model.forceRoot] | boolean |
Set to true to stop this node from attempting to contact it's parent. |
[options.network] | object |
Container for Network-related options. |
[options.network.allowedOrigins] | array<string> |
A list of allowed origins for child nodes. Any messages received from origins not listed are immediately dropped. Leave empty to allow all origins. |
[options.network.parentOrigin] | string |
The allowed origin to use when communicating with the context's parent. |
[options.network.parentWindow] | window |
Override the default parent context. Mainly useful when initializing IHop in an Worker context. |
Example
> const ihop = new IHop('myNameSpace');
Makes object
available to every connected iframe via name
within this iframe's namespace.
Kind: instance method of IHop
Param | Type | Description |
---|---|---|
name | string |
The name to export the object under in the current namespace. |
object | object |
function |
Example
> const ihop = new IHop('foo');
> const bar = {baz: 'hello!'};
// Make the 'bar' object available in other contexts under 'foo.bar'
> ihop.export('bar', baz);
Waits for a specific path to becomes available and then resolves the promise with the object or namespace at that path.
Kind: instance method of IHop
Param | Type | Description |
---|---|---|
path | string |
A path is one or more namespaces separated by a period (.) and optionally a final exported name. |
Example
// An iframe with the namespace `A` is the root namespace and contains another iframe with the namespace `B`
// The iframe `B` exports an object named `foo`
> ihop.import('A.B.foo').then((foo) => {
// Do something with 'foo'
});
// Wait for more than one export
> Promise.all([
ihop.import('A.B.foo'),
ihop.import('A.bar')
]).then(([foo, bar]) => {
// Do something with 'foo' and 'bar'
});
Register a web worker context. Workers are not able to automatically register themselves like iframes and must be explicitly linked in their parent context.
Kind: instance method of IHop
Param | Type | Description |
---|---|---|
worker | Worker |
An instance of a Web Worker. |
Example
> const worker = new Worker('worker.js');
> ihop.registerWorker(worker);
Contains the exported namespace hierarchy.
Kind: instance property of IHop
Example
// An iframe with the namespace `A` contains another iframe with the namespace `B`
// The iframe `B` exports an object named `foo`
> ihop.import('A.B.foo').then(() => {
// We can also access foo via:
const foo = ihop.tree.A.B.foo;
});
IHop has support for some pretty advanced proxying. Not only can you export DOM nodes and manipulate them as though they were local, but you can also treat functions as local too!
This means that you can pass functions across the proxy as arguments and even return functions from other functions. The proxy engine handles all the fun stuff behind the scenes for you.
For example let's say that you export a function that returns a function from iframe A:
<script type="text/javascript">
const ihop = new IHop('A');
const compose = (fnA, fnB) => async (...args) => await fnA(await fnB(...args));
ihop.export('compose', compose);
</script>
NOTE: When we execute the fnA and fnB functions, we need to await
- any function passed between contexts has it’s return value encapsulated in a promise.
Now in B, you want to use that function:
<script type="text/javascript">
const ihop = new IHop('B');
ihop.import('A.compose').then(async (compose) => {
const add = (a, b) => a + b;
const double = (n) => n * 2;
const sumAndDouble = await compose(double, add);
console.log(await sumAndDouble(3, 4));
});
</script>
And it just works!
Blue labels represent event types.
There are a few things to be aware of when using this library.
Don't expect performing magic to be fast. This library should not be used for performance intensive operations. Even events that trigger more than a few times a second are not a good fit for cross-frame access.
Exported objects are running in different threads and the library doesn't provide any synchronization primitives. Operations can happen out of order and nothing is atomic. It's best to avoid making changes to an object from more than one context.
There are obviously going to be places where the proxying breaks down but every attempt has been made to make it as transparent as possible.
Currently unsupported operations on proxies:
- Both
getPrototypeOf
/setPrototypeOf
- The
delete
operator - The
in
operator