/**
* @license
* Copyright The Closure Library Authors.
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @fileoverview A pool of forward channel requests to enable real-time
* messaging from the client to server.
*
*/
goog.module('goog.labs.net.webChannel.ForwardChannelRequestPool');
goog.module.declareLegacyNamespace();
const ChannelRequest = goog.require('goog.labs.net.webChannel.ChannelRequest');
const Wire = goog.require('goog.labs.net.webChannel.Wire');
const array = goog.require('goog.array');
const googString = goog.require('goog.string');
/**
* This class represents the state of all forward channel requests.
*
* @param {number=} opt_maxPoolSize The maximum pool size.
*
* @struct @constructor @final
*/
const ForwardChannelRequestPool = function(opt_maxPoolSize) {
/**
* The max pool size as configured.
*
* @private {number}
*/
this.maxPoolSizeConfigured_ =
opt_maxPoolSize || ForwardChannelRequestPool.MAX_POOL_SIZE_;
/**
* The current size limit of the request pool. This limit is meant to be
* read-only after the channel is fully opened.
*
* If SPDY or HTTP2 is enabled, set it to the max pool size, which is also
* configurable.
*
* @private {number}
*/
this.maxSize_ = ForwardChannelRequestPool.isSpdyOrHttp2Enabled_() ?
this.maxPoolSizeConfigured_ :
1;
/**
* The container for all the pending request objects.
*
* @private {?Set<?ChannelRequest>}
*/
this.requestPool_ = null;
if (this.maxSize_ > 1) {
this.requestPool_ = new Set();
}
/**
* The single request object when the pool size is limited to one.
*
* @private {?ChannelRequest}
*/
this.request_ = null;
/**
* Saved pending messages when the pool is cancelled.
*
* @private {!Array<Wire.QueuedMap>}
*/
this.pendingMessages_ = [];
};
/**
* The default size limit of the request pool.
*
* @private {number}
*/
ForwardChannelRequestPool.MAX_POOL_SIZE_ = 10;
/**
* @return {boolean} True if SPDY or HTTP2 is enabled. Uses chrome-specific APIs
* as a fallback and will always return false for other browsers where
* PerformanceNavigationTiming is not available.
* @private
*/
ForwardChannelRequestPool.isSpdyOrHttp2Enabled_ = function() {
if (goog.global.PerformanceNavigationTiming) {
const entrys = /** @type {!Array<!PerformanceNavigationTiming>} */ (
goog.global.performance.getEntriesByType('navigation'));
return entrys.length > 0 &&
(entrys[0].nextHopProtocol == 'hq' ||
entrys[0].nextHopProtocol == 'h2');
}
return !!(
goog.global.chrome && goog.global.chrome.loadTimes &&
goog.global.chrome.loadTimes() &&
goog.global.chrome.loadTimes().wasFetchedViaSpdy);
};
/**
* Once we know the client protocol (from the handshake), check if we need
* enable the request pool accordingly. This is more robust than using
* browser-internal APIs (specific to Chrome).
*
* @param {string} clientProtocol The client protocol
*/
ForwardChannelRequestPool.prototype.applyClientProtocol = function(
clientProtocol) {
if (this.requestPool_) {
return;
}
if (googString.contains(clientProtocol, 'spdy') ||
googString.contains(clientProtocol, 'quic') ||
googString.contains(clientProtocol, 'h2')) {
this.maxSize_ = this.maxPoolSizeConfigured_;
this.requestPool_ = new Set();
if (this.request_) {
this.addRequest(this.request_);
this.request_ = null;
}
}
};
/**
* @return {boolean} True if the pool is full.
*/
ForwardChannelRequestPool.prototype.isFull = function() {
if (this.request_) {
return true;
}
if (this.requestPool_) {
return this.requestPool_.size >= this.maxSize_;
}
return false;
};
/**
* @return {number} The current size limit.
*/
ForwardChannelRequestPool.prototype.getMaxSize = function() {
return this.maxSize_;
};
/**
* @return {number} The number of pending requests in the pool.
*/
ForwardChannelRequestPool.prototype.getRequestCount = function() {
if (this.request_) {
return 1;
}
if (this.requestPool_) {
return this.requestPool_.size;
}
return 0;
};
/**
* @param {ChannelRequest} req The channel request.
* @return {boolean} True if the request is a included inside the pool.
*/
ForwardChannelRequestPool.prototype.hasRequest = function(req) {
if (this.request_) {
return this.request_ == req;
}
if (this.requestPool_) {
return this.requestPool_.has(req);
}
return false;
};
/**
* Adds a new request to the pool.
*
* @param {!ChannelRequest} req The new channel request.
*/
ForwardChannelRequestPool.prototype.addRequest = function(req) {
if (this.requestPool_) {
this.requestPool_.add(req);
} else {
this.request_ = req;
}
};
/**
* Removes the given request from the pool.
*
* @param {ChannelRequest} req The channel request.
* @return {boolean} Whether the request has been removed from the pool.
*/
ForwardChannelRequestPool.prototype.removeRequest = function(req) {
if (this.request_ && this.request_ == req) {
this.request_ = null;
return true;
}
if (this.requestPool_ && this.requestPool_.has(req)) {
this.requestPool_.delete(req);
return true;
}
return false;
};
/**
* Clears the pool and cancel all the pending requests.
*/
ForwardChannelRequestPool.prototype.cancel = function() {
// save any pending messages
this.pendingMessages_ = this.getPendingMessages();
if (this.request_) {
this.request_.cancel();
this.request_ = null;
return;
}
if (this.requestPool_ && this.requestPool_.size !== 0) {
for (const val of this.requestPool_.values()) {
val.cancel();
}
this.requestPool_.clear();
}
};
/**
* @return {boolean} Whether there are any pending requests.
*/
ForwardChannelRequestPool.prototype.hasPendingRequest = function() {
return (this.request_ != null) ||
(this.requestPool_ != null && this.requestPool_.size !== 0);
};
/**
* @return {!Array<Wire.QueuedMap>} All the pending messages from the pool,
* as a new array.
*/
ForwardChannelRequestPool.prototype.getPendingMessages = function() {
if (this.request_ != null) {
return this.pendingMessages_.concat(this.request_.getPendingMessages());
}
if (this.requestPool_ != null && this.requestPool_.size !== 0) {
let result = this.pendingMessages_;
for (const val of this.requestPool_.values()) {
result = result.concat(val.getPendingMessages());
}
return result;
}
return array.clone(this.pendingMessages_);
};
/**
* Records pending messages, e.g. when a request receives a failed response.
*
* @param {!Array<Wire.QueuedMap>} messages Pending messages.
*/
ForwardChannelRequestPool.prototype.addPendingMessages = function(messages) {
this.pendingMessages_ = this.pendingMessages_.concat(messages);
};
/**
* Clears any recorded pending messages.
*/
ForwardChannelRequestPool.prototype.clearPendingMessages = function() {
this.pendingMessages_.length = 0;
};
/**
* Cancels all pending requests and force the completion of channel requests.
*
* Need go through the standard onRequestComplete logic to expose the max-retry
* failure in the standard way.
*
* @param {function(!ChannelRequest)} onComplete The completion callback.
* @return {boolean} true if any request has been forced to complete.
*/
ForwardChannelRequestPool.prototype.forceComplete = function(onComplete) {
if (this.request_ != null) {
this.request_.cancel();
onComplete(this.request_);
return true;
}
if (this.requestPool_ && this.requestPool_.size !== 0) {
for (const val of this.requestPool_.values()) {
val.cancel();
onComplete(val);
}
return true;
}
return false;
};
exports = ForwardChannelRequestPool;