liaison-core
v0.0.17
Published
A simple messaging library meant to enable easy CORS communication between windows and embedded iframes - using the browser postMessage API.
Downloads
12
Maintainers
Readme
Liaison
Liaison is a simple library with 0 dependencies that enables easy, secure communication between a browser window and embedded iframes, using the browser postMessage API.
The window.postMessage() method safely enables cross-origin communication between Window objects; e.g., between a page and a pop-up that it spawned, or between a page and an iframe embedded within it.
Parent Model
The Parent
model is used to:
- Define side effects (
effects
) that theIFrame
model can expect to have the parent window run - whenever it requests the parent window to run them.
For most use cases, these side effects are going to be used for 2 general purposes:
- Requesting the parent window send data to an iframe
- Ex.) Authorization tokens
- Notifiying the parent window that some event has occurred within an iframe.
- Ex.) Informing the parent window that an iframe has finished logging a user out of the iframe.
Initialization
The Parent factory function specifies the id of the iframe we expect to receive messages from, and the origin we should validate that those messages originate from.
const parent = Parent({
iframe: {
id: 'my-iframe-id',
src: 'https://embedded.com',
}
...
});
Both iframe.id
and iframe.src
are required.
Lifecycle Methods
The Parent
model sets all event handlers when it is initially called (Parent({ ... })
)
The destroy()
removes all event listeners on the parent window that are listening for signals from the specified iframe:
// initialize event handlers
const parent = Parent({ ... });
// remove event handlers if needed
parent.destroy();
Message Handling (Signals
)
When the parent window receives a MessageEvent, the Parent model checks if:
- If the MessageEvent has an
origin
exactly matchingsrc
.- If the message has any other origin than
src
, it is completely ignored.
- If the message has any other origin than
- If the
origin
matchessrc
, the Parent model checks to ensure that the data passed in theMessageEvent
matches the expected API (i.e., contains aSignal
) - If the
MessageEvent
contains aSignal
, thisSignal
is then used to call a correspondingEffect
on the IFrame model.
Effects
Effects
(a.k.a., "side effects") are functions defined on the Parent model, that the Parent model can expect to call on the IFrame model.
const parent = Parent({
...
effects: {
// each `effect` can be synchronous
sendToken: () => {
const token = nanoid();
// ...
},
// ... or asynchronous
sendTokenAsync: async () => {
await timeout(3000);
const token = nanoid();
// ...
}
}
});
// ...
function timeout(ms: number) {
return new Promise(resolve => setTimeout(resolve, ms));
}
Each effect
has access to an args
object, which contains all the arguments passed from the IFrame
model when it requests a certain side effect occur in the parent window:
// ... in parent window
const parent = Parent({
...
effects: {
logMessageFromIFrame: ({ args }) => {
console.log(`Message received from iframe: ${args.message}`)
},
}
});
// ... in iframe window
const iframe = IFrame({ ... });
iframe.callParentEffect({
name: 'logMessageFromIFrame',
args: { message: 'Greetings' },
});
// logs "Message received from iframe: Greetings"
Each effect
defined in the call to Parent
has access to the callIFrameEffect
function, allowing it to call back to the iframe:
// ... in parent window
const parent = Parent({
...
effects: {
sendToken: ({ callIFrameEffect }) => {
const token = nanoid();
callIFrameEffect({
name: 'saveToken',
args: { token }
});
},
}
});
// ... in iframe window
const iframe = IFrame({
...
effects: {
saveToken: ({ token }) => {
localStorage.setItem('authToken', token);
}
}
});
You can also use both args
and callIFrameEffect
together:
// ... in parent window
const parent = Parent({
...
effects: {
sendToken: ({ args, callIFrameEffect }) => {
if (args.system === 'client1') {
token = 'xyz';
} else {
token = 'zyx';
}
callIFrameEffect({
name: 'saveToken',
args: { token }
});
},
}
});
All Together:
const parent = Parent({
iframe: {
id: 'my-iframe-id',
src: 'https://embedded.com',
},
effects: {
sendToken: ({ args, callIFrameEffect }) => {
if (args.system === 'client1') {
token = 'xyz';
} else {
token = 'zyx';
}
callIFrameEffect({
name: 'saveToken',
args: { token }
});
},
}
});
IFrame Model
The IFrame
model is used to:
- Define side effects (
effects
) that theParent
model can expect to have the iframe window run - whenever it requests the iframe window to run them.
Similarly to the Parent
model, these effects can be used to enable the parent window to:
- Request data from the iframe
- Notify the iframe that some event has occurred in the parent window.
Configuration
The iframe model will only initiate side effects in response to messages that have been verified to come from a recognized domain (parentOrigin
):
const iframe = IFrame({
parentOrigin: 'https://parent.com',
...
});
Effects
Each effect
defined on the IFrame
model can be synchronous or asynchronous:
const iframe = IFrame({
...
effects: {
load: () => {
// fetch some data
},
lazyLoad: async () => {
timeout(3000);
// fetch some data
}
}
});
// ...
function timeout(ms: number) {
return new Promise(resolve => setTimeout(resolve, ms));
}
Each effect
defined on the IFrame
model has access to the args
object containing all the arguments passed from the parent window to be used with this effect
:
const iframe = IFrame({
parentOrigin: 'https://parent.com'
effects: {
saveData: ({ args }) => {
// save this data to a db
},
}
});
Each effect
defined on the IFrame
model has access to the callParentEffect
function so it can call back to the parent window:
const iframe = IFrame({
parentOrigin: 'https://parent.com'
effects: {
notifyParent: ({ callParentEffect }) => {
callParentEffect({ name: 'notify', args: { notification: 'Something happened' } })
},
}
});
// ... in window with url of 'https://parent.com'
const parent = Parent({
...
effects: {
notify: ({ args }) => {
console.log(`Notification: ${args.notification}`)
},
}
});
// logs "Notification: Something happened"
API Glossary:
Parent window
The browser window that contains an embedded window
Parent model
The function that can be used to define which iframe the parent window expects to receive signals from, and what effects can run when the iframe requests them to be run.
// use named import
import { Parent } from 'liaison-core';
IFrame window
The embedded iframe window within the parent window
IFrame model
The function that can be used to define which origin it can expect to receive signals from, and what effects can be run when the that origin requests them to be run.
// use named import
import { IFrame } from 'liaison-core';
Signals:
A Signal
is an object that contains all the data needed for one client to understand what function it needs to run and the arguments it needs to call that function with.
The
name
property indicates the name of theEffect
one client (Parent
orIFrame
) wants to initiate on the other.The
args
property is an object containing all of the arguments that the client was the other to include in its call to that effect.
Effects
An Effect
is a function that can be run on one of the clients, whenever the other client requests it be run.