/**
* @license
* Copyright The Closure Library Authors.
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @fileoverview Definition of goog.messaging.MultiChannel, which uses a
* single underlying MessageChannel to carry several independent virtual message
* channels.
*/
goog.provide('goog.messaging.MultiChannel');
goog.provide('goog.messaging.MultiChannel.VirtualChannel');
goog.require('goog.Disposable');
goog.require('goog.dispose');
goog.require('goog.log');
goog.require('goog.messaging.MessageChannel'); // interface
goog.require('goog.object');
/**
* Creates a new MultiChannel wrapping a single MessageChannel. The
* underlying channel shouldn't have any other listeners registered, but it
* should be connected.
*
* Note that the other side of the channel should also be connected to a
* MultiChannel with the same number of virtual channels.
*
* @param {goog.messaging.MessageChannel} underlyingChannel The underlying
* channel to use as transport for the virtual channels.
* @constructor
* @extends {goog.Disposable}
* @final
*/
goog.messaging.MultiChannel = function(underlyingChannel) {
'use strict';
goog.messaging.MultiChannel.base(this, 'constructor');
/**
* The underlying channel across which all requests are sent.
* @type {goog.messaging.MessageChannel}
* @private
*/
this.underlyingChannel_ = underlyingChannel;
/**
* All the virtual channels that are registered for this MultiChannel.
* These are null if they've been disposed.
* @type {Object<?goog.messaging.MultiChannel.VirtualChannel>}
* @private
*/
this.virtualChannels_ = {};
this.underlyingChannel_.registerDefaultService(
goog.bind(this.handleDefault_, this));
};
goog.inherits(goog.messaging.MultiChannel, goog.Disposable);
/**
* Logger object for goog.messaging.MultiChannel.
* @type {goog.log.Logger}
* @private
*/
goog.messaging.MultiChannel.prototype.logger_ =
goog.log.getLogger('goog.messaging.MultiChannel');
/**
* Creates a new virtual channel that will communicate across the underlying
* channel.
* @param {string} name The name of the virtual channel. Must be unique for this
* MultiChannel. Cannot contain colons.
* @return {!goog.messaging.MultiChannel.VirtualChannel} The new virtual
* channel.
*/
goog.messaging.MultiChannel.prototype.createVirtualChannel = function(name) {
'use strict';
if (name.indexOf(':') != -1) {
throw new Error(
'Virtual channel name "' + name + '" should not contain colons');
}
if (name in this.virtualChannels_) {
throw new Error(
'Virtual channel "' + name + '" was already created for ' +
'this multichannel.');
}
const channel = new goog.messaging.MultiChannel.VirtualChannel(this, name);
this.virtualChannels_[name] = channel;
return channel;
};
/**
* Handles the default service for the underlying channel. This dispatches any
* unrecognized services to the appropriate virtual channel.
*
* @param {string} serviceName The name of the service being called.
* @param {string|!Object} payload The message payload.
* @private
*/
goog.messaging.MultiChannel.prototype.handleDefault_ = function(
serviceName, payload) {
'use strict';
const match = serviceName.match(/^([^:]*):(.*)/);
if (!match) {
goog.log.warning(
this.logger_, 'Invalid service name "' + serviceName + '": no ' +
'virtual channel specified');
return;
}
const channelName = match[1];
serviceName = match[2];
if (!(channelName in this.virtualChannels_)) {
goog.log.warning(
this.logger_, 'Virtual channel "' + channelName + ' does not ' +
'exist, but a message was received for it: "' + serviceName + '"');
return;
}
const virtualChannel = this.virtualChannels_[channelName];
if (!virtualChannel) {
goog.log.warning(
this.logger_, 'Virtual channel "' + channelName + ' has been ' +
'disposed, but a message was received for it: "' + serviceName +
'"');
return;
}
if (!virtualChannel.defaultService_) {
goog.log.warning(
this.logger_, 'Service "' + serviceName + '" is not registered ' +
'on virtual channel "' + channelName + '"');
return;
}
virtualChannel.defaultService_(serviceName, payload);
};
/** @override */
goog.messaging.MultiChannel.prototype.disposeInternal = function() {
'use strict';
goog.object.forEach(this.virtualChannels_, function(channel) {
'use strict';
goog.dispose(channel);
});
goog.dispose(this.underlyingChannel_);
delete this.virtualChannels_;
delete this.underlyingChannel_;
};
/**
* A message channel that proxies its messages over another underlying channel.
*
* @param {goog.messaging.MultiChannel} parent The MultiChannel
* which created this channel, and which contains the underlying
* MessageChannel that's used as the transport.
* @param {string} name The name of this virtual channel. Unique among the
* virtual channels in parent.
* @constructor
* @implements {goog.messaging.MessageChannel}
* @extends {goog.Disposable}
* @final
*/
goog.messaging.MultiChannel.VirtualChannel = function(parent, name) {
'use strict';
goog.messaging.MultiChannel.VirtualChannel.base(this, 'constructor');
/**
* The MultiChannel containing the underlying transport channel.
* @type {goog.messaging.MultiChannel}
* @private
*/
this.parent_ = parent;
/**
* The name of this virtual channel.
* @type {string}
* @private
*/
this.name_ = name;
};
goog.inherits(goog.messaging.MultiChannel.VirtualChannel, goog.Disposable);
/**
* The default service to run if no other services match.
* @type {?function(string, (string|!Object))}
* @private
*/
goog.messaging.MultiChannel.VirtualChannel.prototype.defaultService_;
/**
* Logger object for goog.messaging.MultiChannel.VirtualChannel.
* @type {goog.log.Logger}
* @private
*/
goog.messaging.MultiChannel.VirtualChannel.prototype.logger_ =
goog.log.getLogger('goog.messaging.MultiChannel.VirtualChannel');
/**
* This is a no-op, since the underlying channel is expected to already be
* initialized when it's passed in.
*
* @override
*/
goog.messaging.MultiChannel.VirtualChannel.prototype.connect = function(
opt_connectCb) {
'use strict';
if (opt_connectCb) {
opt_connectCb();
}
};
/**
* This always returns true, since the underlying channel is expected to already
* be initialized when it's passed in.
*
* @override
*/
goog.messaging.MultiChannel.VirtualChannel.prototype.isConnected = function() {
'use strict';
return true;
};
/**
* @override
*/
goog.messaging.MultiChannel.VirtualChannel.prototype.registerService = function(
serviceName, callback, opt_objectPayload) {
'use strict';
this.parent_.underlyingChannel_.registerService(
this.name_ + ':' + serviceName,
goog.bind(this.doCallback_, this, callback), opt_objectPayload);
};
/**
* @override
*/
goog.messaging.MultiChannel.VirtualChannel.prototype.registerDefaultService =
function(callback) {
'use strict';
this.defaultService_ = goog.bind(this.doCallback_, this, callback);
};
/**
* @override
*/
goog.messaging.MultiChannel.VirtualChannel.prototype.send = function(
serviceName, payload) {
'use strict';
if (this.isDisposed()) {
throw new Error('#send called for disposed VirtualChannel.');
}
this.parent_.underlyingChannel_.send(this.name_ + ':' + serviceName, payload);
};
/**
* Wraps a callback with a function that will log a warning and abort if it's
* called when this channel is disposed.
*
* @param {!Function} callback The callback to wrap.
* @param {...*} var_args Other arguments, passed to the callback.
* @private
*/
goog.messaging.MultiChannel.VirtualChannel.prototype.doCallback_ = function(
callback, var_args) {
'use strict';
if (this.isDisposed()) {
goog.log.warning(
this.logger_, 'Virtual channel "' + this.name_ + '" received ' +
' a message after being disposed.');
return;
}
callback.apply({}, Array.prototype.slice.call(arguments, 1));
};
/** @override */
goog.messaging.MultiChannel.VirtualChannel.prototype.disposeInternal =
function() {
'use strict';
this.parent_.virtualChannels_[this.name_] = null;
this.parent_ = null;
};