Commit 53f06183 authored by Robert Knight's avatar Robert Knight

Buffer RPC calls made before port is connected

Fix a race condition where inter-frame RPC calls would silently fail if made
before the MessagePort discovery process had completed. Fix the issue by queuing
calls in PortRPC and then dispatching them once a port is connected. This is
similar to how MessagePort buffers messages until they are consumed by calling
`start`.
parent 9ace9a71
......@@ -80,6 +80,13 @@ export class PortRPC {
this._callbacks = new Map();
this._listeners = new ListenerCollection();
/**
* Method and arguments of pending RPC calls made before a port was connected.
*
* @type {Array<[CallMethod, any[]]>}
*/
this._pendingCalls = [];
}
/**
......@@ -108,6 +115,11 @@ export class PortRPC {
this._handle(/** @type {MessageEvent} */ (event))
);
port.start();
for (let [method, args] of this._pendingCalls) {
this.call(method, ...args);
}
this._pendingCalls = [];
}
/**
......@@ -123,6 +135,9 @@ export class PortRPC {
/**
* Send an RPC request via the connected port.
*
* If this client is not yet connected to a port, the call will be queued and
* sent when {@link connect} is called.
*
* If the final argument in `args` is a function, it is treated as a callback
* which is invoked with the response in the form of (error, result) arguments.
*
......@@ -130,8 +145,9 @@ export class PortRPC {
* @param {any[]} args
*/
call(method, ...args) {
// TODO - What should happen here if the port is not connected? Buffer
// method calls until the port is connected?
if (!this._port) {
this._pendingCalls.push([method, args]);
}
if (!this._port || this._destroyed) {
return;
......
......@@ -156,4 +156,22 @@ describe('PortRPC', () => {
rpc.on('foo', () => {});
}, 'Cannot add a method handler after a port is connected');
});
it('should queue RPC requests made before port is connected', async () => {
const { port1, port2 } = new MessageChannel();
const sender = new PortRPC();
const receiver = new PortRPC();
const testMethod = sinon.stub();
receiver.on('test', testMethod);
receiver.connect(port2);
sender.call('test', 'first', 'call');
sender.call('test', 'second', 'call');
sender.connect(port1);
await waitForMessageDelivery();
assert.calledWith(testMethod, 'first', 'call');
assert.calledWith(testMethod, 'second', 'call');
});
});
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