/**
* @license
* Copyright The Closure Library Authors.
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @fileoverview The central node of a {@link goog.messaging.PortNetwork}. The
* operator is responsible for providing the two-way communication channels (via
* {@link MessageChannel}s) between each pair of nodes in the network that need
* to communicate with one another. Each network should have one and only one
* operator.
*/
goog.provide('goog.messaging.PortOperator');
goog.require('goog.Disposable');
goog.require('goog.asserts');
goog.require('goog.dispose');
goog.require('goog.log');
goog.require('goog.messaging.PortChannel');
goog.require('goog.messaging.PortNetwork'); // interface
goog.require('goog.object');
goog.requireType('goog.messaging.MessageChannel');
/**
* The central node of a PortNetwork.
*
* @param {string} name The name of this node.
* @constructor
* @extends {goog.Disposable}
* @implements {goog.messaging.PortNetwork}
* @final
*/
goog.messaging.PortOperator = function(name) {
'use strict';
goog.messaging.PortOperator.base(this, 'constructor');
/**
* The collection of channels for communicating with other contexts in the
* network. These are the channels that are returned to the user, as opposed
* to the channels used for internal network communication. This is lazily
* populated as the user requests communication with other contexts, or other
* contexts request communication with the operator.
*
* @type {!Object<!goog.messaging.PortChannel>}
* @private
*/
this.connections_ = {};
/**
* The collection of channels for internal network communication with other
* contexts. This is not lazily populated, and always contains entries for
* each member of the network.
*
* @type {!Object<!goog.messaging.MessageChannel>}
* @private
*/
this.switchboard_ = {};
/**
* The name of the operator context.
*
* @type {string}
* @private
*/
this.name_ = name;
};
goog.inherits(goog.messaging.PortOperator, goog.Disposable);
/**
* The logger for PortOperator.
* @type {goog.log.Logger}
* @private
*/
goog.messaging.PortOperator.prototype.logger_ =
goog.log.getLogger('goog.messaging.PortOperator');
/** @override */
goog.messaging.PortOperator.prototype.dial = function(name) {
'use strict';
this.connectSelfToPort_(name);
return this.connections_[name];
};
/**
* Adds a caller to the network with the given name. This port should have no
* services registered on it. It will be disposed along with the PortOperator.
*
* @param {string} name The name of the port to add.
* @param {!goog.messaging.MessageChannel} port The port to add. Must be either
* a {@link goog.messaging.PortChannel} or a decorator wrapping a
* PortChannel; in particular, it must be able to send and receive
* {@link MessagePort}s.
*/
goog.messaging.PortOperator.prototype.addPort = function(name, port) {
'use strict';
this.switchboard_[name] = port;
port.registerService(
goog.messaging.PortNetwork.REQUEST_CONNECTION_SERVICE,
goog.bind(this.requestConnection_, this, name));
};
/**
* Connects two contexts by creating a {@link MessageChannel} and sending one
* end to one context and the other end to the other. Called when we receive a
* request from a caller to connect it to another context (including potentially
* the operator).
*
* @param {string} sourceName The name of the context requesting the connection.
* @param {!Object|string} message The name of the context to which
* the connection is requested.
* @private
*/
goog.messaging.PortOperator.prototype.requestConnection_ = function(
sourceName, message) {
'use strict';
const requestedName = /** @type {string} */ (message);
if (requestedName == this.name_) {
this.connectSelfToPort_(sourceName);
return;
}
const sourceChannel = this.switchboard_[sourceName];
const requestedChannel = this.switchboard_[requestedName];
goog.asserts.assert(sourceChannel != null);
if (!requestedChannel) {
const err = 'Port "' + sourceName + '" requested a connection to port "' +
requestedName + '", which doesn\'t exist';
goog.log.warning(this.logger_, err);
sourceChannel.send(
goog.messaging.PortNetwork.GRANT_CONNECTION_SERVICE,
{'success': false, 'message': err});
return;
}
const messageChannel = new MessageChannel();
sourceChannel.send(
goog.messaging.PortNetwork.GRANT_CONNECTION_SERVICE,
{'success': true, 'name': requestedName, 'port': messageChannel.port1});
requestedChannel.send(
goog.messaging.PortNetwork.GRANT_CONNECTION_SERVICE,
{'success': true, 'name': sourceName, 'port': messageChannel.port2});
};
/**
* Connects together the operator and a caller by creating a
* {@link MessageChannel} and sending one end to the remote context.
*
* @param {string} contextName The name of the context to which to connect the
* operator.
* @private
*/
goog.messaging.PortOperator.prototype.connectSelfToPort_ = function(
contextName) {
'use strict';
if (contextName in this.connections_) {
// We've already established a connection with this port.
return;
}
const contextChannel = this.switchboard_[contextName];
if (!contextChannel) {
throw new Error('Port "' + contextName + '" doesn\'t exist');
}
const messageChannel = new MessageChannel();
contextChannel.send(
goog.messaging.PortNetwork.GRANT_CONNECTION_SERVICE,
{'success': true, 'name': this.name_, 'port': messageChannel.port1});
messageChannel.port2.start();
this.connections_[contextName] =
new goog.messaging.PortChannel(messageChannel.port2);
};
/** @override */
goog.messaging.PortOperator.prototype.disposeInternal = function() {
'use strict';
goog.object.forEach(this.switchboard_, goog.dispose);
goog.object.forEach(this.connections_, goog.dispose);
delete this.switchboard_;
delete this.connections_;
goog.messaging.PortOperator.base(this, 'disposeInternal');
};