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 { ...@@ -21,42 +21,13 @@ export default class Bridge {
/** /**
* Destroy all channels created with `createChannel`. * 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() { destroy() {
this.links.forEach(channel => channel.destroy()); this.links.forEach(channel => channel.destroy());
this.links = []; 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`. * Create a communication channel using a `MessagePort`.
* *
...@@ -64,18 +35,13 @@ export default class Bridge { ...@@ -64,18 +35,13 @@ export default class Bridge {
* and `on` send and receive messages over. * and `on` send and receive messages over.
* *
* @param {MessagePort} port * @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 }; const listeners = { connect: cb => cb(), ...this.channelListeners };
// Set up a channel // Set up a channel
const channel = new RPC( const channel = new RPC(port, listeners);
window /* dummy */,
port,
'*' /* dummy */,
listeners
);
let connected = false; let connected = false;
const ready = () => { const ready = () => {
...@@ -95,48 +61,6 @@ export default class Bridge { ...@@ -95,48 +61,6 @@ export default class Bridge {
return channel; 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 * Make a method call on all channels, collect the results and pass them to a
* callback when all results are collected. * callback when all results are collected.
......
...@@ -51,7 +51,11 @@ const PROTOCOL = 'frame-rpc'; ...@@ -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. * Code adapted from https://github.com/substack/frame-rpc.
* *
...@@ -59,42 +63,15 @@ const PROTOCOL = 'frame-rpc'; ...@@ -59,42 +63,15 @@ const PROTOCOL = 'frame-rpc';
*/ */
export class RPC { export class RPC {
/** /**
* Create an RPC client for sending RPC requests from `sourceFrame` to * Create an RPC client for sending and receiving RPC message using a
* `destFrame`, and receiving RPC responses from `destFrame` to `sourceFrame`. * `MessagePort`.
*
* 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.
* *
* TODO: July 2021, currently this class is a bit of a _frankenstein_ because * @param {MessagePort} port
* 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 {Record<string, (...args: any[]) => void>} methods - Map of method * @param {Record<string, (...args: any[]) => void>} methods - Map of method
* name to method handler * name to method handler
*/ */
constructor(sourceFrame, destFrameOrPort, origin, methods) { constructor(port, methods) {
this.sourceFrame = sourceFrame; // sourceFrame is ignored if using MessagePort this._port = port;
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;
}
this._methods = methods; this._methods = methods;
this._sequence = 0; this._sequence = 0;
...@@ -103,27 +80,10 @@ export class RPC { ...@@ -103,27 +80,10 @@ export class RPC {
this._listeners = new ListenerCollection(); this._listeners = new ListenerCollection();
if (this._port) { this._listeners.add(this._port, 'message', event =>
this._listeners.add(this._port, 'message', event => this._handle(/** @type {MessageEvent} */ (event))
this._handle(/** @type {MessageEvent} */ (event)) );
); this._port.start();
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))
);
}
} }
/** /**
...@@ -133,7 +93,7 @@ export class RPC { ...@@ -133,7 +93,7 @@ export class RPC {
destroy() { destroy() {
this._destroyed = true; this._destroyed = true;
this._listeners.removeAll(); this._listeners.removeAll();
this._port?.close(); this._port.close();
} }
/** /**
...@@ -166,31 +126,7 @@ export class RPC { ...@@ -166,31 +126,7 @@ export class RPC {
version: VERSION, version: VERSION,
}; };
if (this._port) { this._port.postMessage(message);
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;
} }
/** /**
...@@ -241,14 +177,7 @@ export class RPC { ...@@ -241,14 +177,7 @@ export class RPC {
version: VERSION, version: VERSION,
}; };
if (this._port) { this._port.postMessage(message);
this._port.postMessage(message);
}
// Deprecated - Remove after MessagePort conversion
if (this.destFrame) {
this.destFrame.postMessage(message, this.origin);
}
}; };
this._methods[msg.method].call(this._methods, ...msg.arguments, callback); this._methods[msg.method].call(this._methods, ...msg.arguments, callback);
} else if ('response' in msg) { } else if ('response' in msg) {
......
import { default as Bridge, $imports } from '../bridge'; import { default as Bridge, $imports } from '../bridge';
class FakeRPC { class FakeRPC {
constructor(sourceFrame, destFrame, origin, methods) { constructor(port, methods) {
this.destFrame = destFrame; this.port = port;
this.sourceFrame = sourceFrame;
this.origin = origin;
this.methods = methods; this.methods = methods;
this.call = sinon.stub(); this.call = sinon.stub();
...@@ -16,24 +14,12 @@ describe('shared/bridge', () => { ...@@ -16,24 +14,12 @@ describe('shared/bridge', () => {
const sandbox = sinon.createSandbox(); const sandbox = sinon.createSandbox();
let bridge; let bridge;
let createChannel; let createChannel;
let fakeWindow;
beforeEach(() => { beforeEach(() => {
bridge = new Bridge(); bridge = new Bridge();
createChannel = source => { createChannel = (port = {}) => {
if (!source) { return bridge.createChannel(port);
source = {
source: fakeWindow,
origin: 'http://example.com',
token: 'TOKEN',
};
}
return bridge.createChannel(source);
};
fakeWindow = {
postMessage: sandbox.stub(),
}; };
$imports.$mock({ $imports.$mock({
...@@ -47,39 +33,17 @@ describe('shared/bridge', () => { ...@@ -47,39 +33,17 @@ describe('shared/bridge', () => {
}); });
describe('#createChannel', () => { describe('#createChannel', () => {
context('with a Window source', () => { it('creates a new channel', () => {
it('creates a new channel', () => { const port1 = 'myport';
const channel = createChannel();
assert.equal(channel.sourceFrame, window);
assert.equal(channel.destFrame, fakeWindow);
assert.equal(channel.origin, 'http://example.com');
});
it('adds the channel to the `links` property', () => { const channel = createChannel(port1);
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(messageChannel.port1); assert.equal(channel.port, port1);
});
assert.equal(channel.sourceFrame, window);
assert.equal(channel.destFrame, messageChannel.port1);
assert.equal(channel.origin, '*');
});
it('adds the channel to the `links` property', () => { it('adds the channel to the `links` property', () => {
const channel = createChannel(); const channel = createChannel();
assert.isTrue( assert.equal(bridge.links[0], channel);
bridge.links.some(registeredChannel => registeredChannel === channel)
);
});
}); });
it('registers any existing listeners on the channel', () => { it('registers any existing listeners on the channel', () => {
...@@ -119,11 +83,7 @@ describe('shared/bridge', () => { ...@@ -119,11 +83,7 @@ describe('shared/bridge', () => {
it('calls a callback when all channels return successfully', done => { it('calls a callback when all channels return successfully', done => {
const channel1 = createChannel(); const channel1 = createChannel();
const channel2 = bridge.createChannel({ const channel2 = bridge.createChannel();
source: fakeWindow,
origin: 'http://example.com',
token: 'NEKOT',
});
channel1.call.yields(null, 'result1'); channel1.call.yields(null, 'result1');
channel2.call.yields(null, 'result2'); channel2.call.yields(null, 'result2');
...@@ -139,11 +99,7 @@ describe('shared/bridge', () => { ...@@ -139,11 +99,7 @@ describe('shared/bridge', () => {
it('calls a callback with an error when a channels fails', done => { it('calls a callback with an error when a channels fails', done => {
const error = new Error('Uh oh'); const error = new Error('Uh oh');
const channel1 = createChannel(); const channel1 = createChannel();
const channel2 = bridge.createChannel({ const channel2 = createChannel();
source: fakeWindow,
origin: 'http://example.com',
token: 'NEKOT',
});
channel1.call.throws(error); channel1.call.throws(error);
channel2.call.yields(null, 'result2'); channel2.call.yields(null, 'result2');
...@@ -249,45 +205,16 @@ describe('shared/bridge', () => { ...@@ -249,45 +205,16 @@ describe('shared/bridge', () => {
// Invoke the `connect` handler. Here we're invoking it on `channel` but // 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 // in the actual app this would be called on the counterpart channel in
// the other frame. This distinction doesn't matter because all the // the other frame.
// handler does is check a token (which is the same on both sides) and
// call the result callback.
channel.methods.connect(...connectCall.args.slice(1)); channel.methods.connect(...connectCall.args.slice(1));
}; };
it('runs callbacks when a Window channel connects with correct token', () => { it('runs callbacks when channel connects', () => {
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', () => {
const onConnectCallback = sinon.stub(); const onConnectCallback = sinon.stub();
bridge.onConnect(onConnectCallback); bridge.onConnect(onConnectCallback);
const channel = createChannel(); 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); runConnectHandler(channel);
assert.calledWith(onConnectCallback, channel); assert.calledWith(onConnectCallback, channel);
...@@ -322,16 +249,8 @@ describe('shared/bridge', () => { ...@@ -322,16 +249,8 @@ describe('shared/bridge', () => {
describe('#destroy', () => describe('#destroy', () =>
it('destroys all opened channels', () => { it('destroys all opened channels', () => {
const channel1 = bridge.createChannel({ const channel1 = bridge.createChannel();
source: fakeWindow, const channel2 = bridge.createChannel();
origin: 'http://example.com',
token: 'foo',
});
const channel2 = bridge.createChannel({
source: fakeWindow,
origin: 'http://example.com',
token: 'bar',
});
bridge.destroy(); bridge.destroy();
......
...@@ -26,14 +26,9 @@ describe('RPC', () => { ...@@ -26,14 +26,9 @@ describe('RPC', () => {
callback(result); callback(result);
}; };
rpc1 = new RPC( rpc1 = new RPC(port1, {
/* dummy when using ports */ window, concat,
port1, });
/* dummy when using ports */ '*',
{
concat,
}
);
// `plusOne` method for rpc2 // `plusOne` method for rpc2
plusOne = sinon.stub().callsFake((...numbers) => { plusOne = sinon.stub().callsFake((...numbers) => {
...@@ -42,14 +37,9 @@ describe('RPC', () => { ...@@ -42,14 +37,9 @@ describe('RPC', () => {
callback(result); callback(result);
}); });
rpc2 = new RPC( rpc2 = new RPC(port2, {
/* dummy when using ports */ window, plusOne,
port2, });
/* dummy when using ports */ '*',
{
plusOne,
}
);
}); });
afterEach(() => { 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