Commit 9330488b authored by Eduardo Sanz García's avatar Eduardo Sanz García Committed by Eduardo

Remove deprecated uses of `window.postMessage`

After #3611 there is no more need to support `window.postMessage` on `RPC` and `Bridge` classes.
parent 67d3924b
......@@ -21,42 +21,13 @@ export default class Bridge {
/**
* Destroy all channels created with `createChannel`.
*
* This removes the event listeners for messages arriving from other windows.
* This removes the event listeners for messages arriving from other ports.
*/
destroy() {
this.links.forEach(channel => channel.destroy());
this.links = [];
}
/**
* Deprecated - Remove after MessagePort conversion
*
* @typedef WindowOptions
* @prop {Window} source - The source window
* @prop {string} origin - The origin of the document in `source`
* @prop {string} token - Shared token between the `source` and this window
* agreed during the discovery process
*/
/**
* Create a communication channel between frames using either `MessagePort` or
* `Window`.
*
* The created channel is added to the list of channels which `call`
* and `on` send and receive messages over.
*
* @param {WindowOptions|MessagePort} options
* @return {RPC} - Channel for communicating with the window.
*/
createChannel(options) {
if (options instanceof MessagePort) {
return this._createChannelForPort(options);
} else {
// Deprecated - Remove after MessagePort conversion
return this._createChannelForWindow(options);
}
}
/**
* Create a communication channel using a `MessagePort`.
*
......@@ -64,18 +35,13 @@ export default class Bridge {
* and `on` send and receive messages over.
*
* @param {MessagePort} port
* @return {RPC} - Channel for communicating with the window.
* @return {RPC} - Channel for communicating with the reciprocal port.
*/
_createChannelForPort(port) {
createChannel(port) {
const listeners = { connect: cb => cb(), ...this.channelListeners };
// Set up a channel
const channel = new RPC(
window /* dummy */,
port,
'*' /* dummy */,
listeners
);
const channel = new RPC(port, listeners);
let connected = false;
const ready = () => {
......@@ -95,48 +61,6 @@ export default class Bridge {
return channel;
}
/**
* Create a communication channel between this window and `source`.
*
* The created channel is added to the list of channels which `call`
* and `on` send and receive messages over.
*
* @param {WindowOptions} options
* @return {RPC} - Channel for communicating with the window.
* @deprecated
*/
_createChannelForWindow({ source, origin, token }) {
let channel = null;
let connected = false;
const ready = () => {
if (connected) {
return;
}
connected = true;
this.onConnectListeners.forEach(cb => cb(channel));
};
const connect = (_token, cb) => {
if (_token === token) {
cb();
}
};
const listeners = { connect, ...this.channelListeners };
// Set up a channel
channel = new RPC(window, source, origin, listeners);
// Fire off a connection attempt
channel.call('connect', token, ready);
// Store the newly created channel in our collection
this.links.push(channel);
return channel;
}
/**
* Make a method call on all channels, collect the results and pass them to a
* callback when all results are collected.
......
......@@ -51,7 +51,11 @@ const PROTOCOL = 'frame-rpc';
*/
/**
* Class for making RPC requests between two frames.
* RPC provides remote procedure calls between frames.
*
* It uses the Channel Messaging API [1] for inter-frame communication.
*
* [1] https://developer.mozilla.org/en-US/docs/Web/API/Channel_Messaging_API
*
* Code adapted from https://github.com/substack/frame-rpc.
*
......@@ -59,42 +63,15 @@ const PROTOCOL = 'frame-rpc';
*/
export class RPC {
/**
* Create an RPC client for sending RPC requests from `sourceFrame` to
* `destFrame`, and receiving RPC responses from `destFrame` to `sourceFrame`.
*
* This class has been adapted to work with `MessageChannel`. Because messages
* sent through `MessageChannel` are only transmitted to and from `port1` and
* `port2`, there is no need for `sourceFrame` and `origin` properties.
* Create an RPC client for sending and receiving RPC message using a
* `MessagePort`.
*
* TODO: July 2021, currently this class is a bit of a _frankenstein_ because
* we support both `Window.postMessage` and `MessagePort.postMessage`.
* Once we move all the inter-frame communication to `MessageChannel` we will
* be able to cleanup this class. We have added `deprecated` comments in
* the pieces of code that need to be removed.
*
* @param {Window} sourceFrame -- deprecated - Remove after MessagePort conversion
* @param {Window|MessagePort} destFrameOrPort
* @param {string} origin - Origin of destination frame (deprecated - Remove after MessagePort conversion)
* @param {MessagePort} port
* @param {Record<string, (...args: any[]) => void>} methods - Map of method
* name to method handler
*/
constructor(sourceFrame, destFrameOrPort, origin, methods) {
this.sourceFrame = sourceFrame; // sourceFrame is ignored if using MessagePort
if (destFrameOrPort instanceof MessagePort) {
this._port = destFrameOrPort;
} else {
/** @deprecated */
this.destFrame = destFrameOrPort;
}
// Deprecated - Remove after MessagePort conversion
if (origin === '*') {
this.origin = '*';
} else {
this.origin = new URL(origin).origin;
}
constructor(port, methods) {
this._port = port;
this._methods = methods;
this._sequence = 0;
......@@ -103,27 +80,10 @@ export class RPC {
this._listeners = new ListenerCollection();
if (this._port) {
this._listeners.add(this._port, 'message', event =>
this._handle(/** @type {MessageEvent} */ (event))
);
this._port.start();
} else {
// Deprecated - Remove after MessagePort conversion
/**
* @param {MessageEvent} event
* @deprecated
*/
const onmessage = event => {
if (!this._isValidSender(event)) {
return;
}
this._handle(event);
};
this._listeners.add(this.sourceFrame, 'message', event =>
onmessage(/** @type {MessageEvent} */ (event))
);
}
this._listeners.add(this._port, 'message', event =>
this._handle(/** @type {MessageEvent} */ (event))
);
this._port.start();
}
/**
......@@ -133,7 +93,7 @@ export class RPC {
destroy() {
this._destroyed = true;
this._listeners.removeAll();
this._port?.close();
this._port.close();
}
/**
......@@ -166,31 +126,7 @@ export class RPC {
version: VERSION,
};
if (this._port) {
this._port.postMessage(message);
}
// Deprecated - Remove after MessagePort conversion
if (this.destFrame) {
this.destFrame.postMessage(message, this.origin);
}
}
/**
* Validate sender
*
* @param {MessageEvent} event
* @deprecated
*/
_isValidSender(event) {
if (event.source !== this.destFrame) {
return false;
}
if (this.origin !== '*' && event.origin !== this.origin) {
return false;
}
return true;
this._port.postMessage(message);
}
/**
......@@ -241,14 +177,7 @@ export class RPC {
version: VERSION,
};
if (this._port) {
this._port.postMessage(message);
}
// Deprecated - Remove after MessagePort conversion
if (this.destFrame) {
this.destFrame.postMessage(message, this.origin);
}
this._port.postMessage(message);
};
this._methods[msg.method].call(this._methods, ...msg.arguments, callback);
} else if ('response' in msg) {
......
import { default as Bridge, $imports } from '../bridge';
class FakeRPC {
constructor(sourceFrame, destFrame, origin, methods) {
this.destFrame = destFrame;
this.sourceFrame = sourceFrame;
this.origin = origin;
constructor(port, methods) {
this.port = port;
this.methods = methods;
this.call = sinon.stub();
......@@ -16,24 +14,12 @@ describe('shared/bridge', () => {
const sandbox = sinon.createSandbox();
let bridge;
let createChannel;
let fakeWindow;
beforeEach(() => {
bridge = new Bridge();
createChannel = source => {
if (!source) {
source = {
source: fakeWindow,
origin: 'http://example.com',
token: 'TOKEN',
};
}
return bridge.createChannel(source);
};
fakeWindow = {
postMessage: sandbox.stub(),
createChannel = (port = {}) => {
return bridge.createChannel(port);
};
$imports.$mock({
......@@ -47,39 +33,17 @@ describe('shared/bridge', () => {
});
describe('#createChannel', () => {
context('with a Window source', () => {
it('creates a new channel', () => {
const channel = createChannel();
assert.equal(channel.sourceFrame, window);
assert.equal(channel.destFrame, fakeWindow);
assert.equal(channel.origin, 'http://example.com');
});
it('creates a new channel', () => {
const port1 = 'myport';
it('adds the channel to the `links` property', () => {
const channel = createChannel();
assert.isTrue(
bridge.links.some(registeredChannel => registeredChannel === channel)
);
});
});
context('with a MessagePort source', () => {
it('creates a new channel', () => {
const messageChannel = new MessageChannel();
const channel = createChannel(port1);
const channel = createChannel(messageChannel.port1);
assert.equal(channel.sourceFrame, window);
assert.equal(channel.destFrame, messageChannel.port1);
assert.equal(channel.origin, '*');
});
assert.equal(channel.port, port1);
});
it('adds the channel to the `links` property', () => {
const channel = createChannel();
assert.isTrue(
bridge.links.some(registeredChannel => registeredChannel === channel)
);
});
it('adds the channel to the `links` property', () => {
const channel = createChannel();
assert.equal(bridge.links[0], channel);
});
it('registers any existing listeners on the channel', () => {
......@@ -119,11 +83,7 @@ describe('shared/bridge', () => {
it('calls a callback when all channels return successfully', done => {
const channel1 = createChannel();
const channel2 = bridge.createChannel({
source: fakeWindow,
origin: 'http://example.com',
token: 'NEKOT',
});
const channel2 = bridge.createChannel();
channel1.call.yields(null, 'result1');
channel2.call.yields(null, 'result2');
......@@ -139,11 +99,7 @@ describe('shared/bridge', () => {
it('calls a callback with an error when a channels fails', done => {
const error = new Error('Uh oh');
const channel1 = createChannel();
const channel2 = bridge.createChannel({
source: fakeWindow,
origin: 'http://example.com',
token: 'NEKOT',
});
const channel2 = createChannel();
channel1.call.throws(error);
channel2.call.yields(null, 'result2');
......@@ -249,45 +205,16 @@ describe('shared/bridge', () => {
// Invoke the `connect` handler. Here we're invoking it on `channel` but
// in the actual app this would be called on the counterpart channel in
// the other frame. This distinction doesn't matter because all the
// handler does is check a token (which is the same on both sides) and
// call the result callback.
// the other frame.
channel.methods.connect(...connectCall.args.slice(1));
};
it('runs callbacks when a Window channel connects with correct token', () => {
const onConnectCallback = sinon.stub();
bridge.onConnect(onConnectCallback);
const channel = createChannel();
runConnectHandler(channel);
assert.calledWith(onConnectCallback, channel);
});
it('does not run callback if a Window channel connects with wrong token', () => {
it('runs callbacks when channel connects', () => {
const onConnectCallback = sinon.stub();
bridge.onConnect(onConnectCallback);
const channel = createChannel();
// Simulate "connect" RPC call by Bridge instance in channel's destination frame.
const connectCall = channel.call
.getCalls()
.find(call => call.firstArg === 'connect');
channel.methods.connect('WRONG-TOKEN', connectCall.lastArg);
assert.notCalled(onConnectCallback);
});
it('runs callbacks when a MessagePort channel connects', () => {
const onConnectCallback = sinon.stub();
bridge.onConnect(onConnectCallback);
const messageChannel = new MessageChannel();
const channel = createChannel(messageChannel.port1);
runConnectHandler(channel);
assert.calledWith(onConnectCallback, channel);
......@@ -322,16 +249,8 @@ describe('shared/bridge', () => {
describe('#destroy', () =>
it('destroys all opened channels', () => {
const channel1 = bridge.createChannel({
source: fakeWindow,
origin: 'http://example.com',
token: 'foo',
});
const channel2 = bridge.createChannel({
source: fakeWindow,
origin: 'http://example.com',
token: 'bar',
});
const channel1 = bridge.createChannel();
const channel2 = bridge.createChannel();
bridge.destroy();
......
......@@ -26,14 +26,9 @@ describe('RPC', () => {
callback(result);
};
rpc1 = new RPC(
/* dummy when using ports */ window,
port1,
/* dummy when using ports */ '*',
{
concat,
}
);
rpc1 = new RPC(port1, {
concat,
});
// `plusOne` method for rpc2
plusOne = sinon.stub().callsFake((...numbers) => {
......@@ -42,14 +37,9 @@ describe('RPC', () => {
callback(result);
});
rpc2 = new RPC(
/* dummy when using ports */ window,
port2,
/* dummy when using ports */ '*',
{
plusOne,
}
);
rpc2 = new RPC(port2, {
plusOne,
});
});
afterEach(() => {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment