UNPKG

google-closure-library

Version:
592 lines (493 loc) 15.2 kB
/** * @license * Copyright The Closure Library Authors. * SPDX-License-Identifier: Apache-2.0 */ /** * @fileoverview Implementation of a WebChannel transport using WebChannelBase. * * When WebChannelBase is used as the underlying transport, the capabilities * of the WebChannel are limited to what's supported by the implementation. * Particularly, multiplexing is not possible, and only strings are * supported as message types. */ goog.provide('goog.labs.net.webChannel.WebChannelBaseTransport'); goog.require('goog.asserts'); goog.require('goog.collections.maps'); goog.require('goog.events.EventTarget'); goog.require('goog.json'); goog.require('goog.labs.net.webChannel.ChannelRequest'); goog.require('goog.labs.net.webChannel.WebChannelBase'); goog.require('goog.labs.net.webChannel.Wire'); goog.require('goog.log'); goog.require('goog.net.WebChannel'); goog.require('goog.net.WebChannelTransport'); goog.require('goog.object'); goog.require('goog.string'); /** * Implementation of {@link goog.net.WebChannelTransport} with * {@link goog.labs.net.webChannel.WebChannelBase} as the underlying channel * implementation. * * @constructor * @struct * @implements {goog.net.WebChannelTransport} * @final */ goog.labs.net.webChannel.WebChannelBaseTransport = function() { 'use strict'; if (!goog.labs.net.webChannel.ChannelRequest.supportsXhrStreaming()) { throw new Error('Environmental error: no available transport.'); } }; goog.scope(function() { 'use strict'; const WebChannelBaseTransport = goog.labs.net.webChannel.WebChannelBaseTransport; const WebChannelBase = goog.labs.net.webChannel.WebChannelBase; const Wire = goog.labs.net.webChannel.Wire; /** * @override */ WebChannelBaseTransport.prototype.createWebChannel = function( url, opt_options) { 'use strict'; return new WebChannelBaseTransport.Channel(url, opt_options); }; /** * Implementation of the {@link goog.net.WebChannel} interface. * * @param {string} url The URL path for the new WebChannel instance. * @param {!goog.net.WebChannel.Options=} opt_options Configuration for the * new WebChannel instance. * * @constructor * @implements {goog.net.WebChannel} * @extends {goog.events.EventTarget} * @final */ WebChannelBaseTransport.Channel = function(url, opt_options) { 'use strict'; WebChannelBaseTransport.Channel.base(this, 'constructor'); /** * @private {!WebChannelBase} The underlying channel object. */ this.channel_ = new WebChannelBase( opt_options, goog.net.WebChannelTransport.CLIENT_VERSION); /** * @private {string} The URL of the target server end-point. */ this.url_ = url; /** * @private {goog.log.Logger} The logger for this class. */ this.logger_ = goog.log.getLogger('goog.labs.net.webChannel.WebChannelBaseTransport'); /** * @private {Object<string, string>} Extra URL parameters * to be added to each HTTP request. */ this.messageUrlParams_ = (opt_options && opt_options.messageUrlParams) || null; let messageHeaders = (opt_options && opt_options.messageHeaders) || null; // default is false if (opt_options && opt_options.clientProtocolHeaderRequired) { if (messageHeaders) { goog.object.set( messageHeaders, goog.net.WebChannel.X_CLIENT_PROTOCOL, goog.net.WebChannel.X_CLIENT_PROTOCOL_WEB_CHANNEL); } else { messageHeaders = goog.object.create( goog.net.WebChannel.X_CLIENT_PROTOCOL, goog.net.WebChannel.X_CLIENT_PROTOCOL_WEB_CHANNEL); } } this.channel_.setExtraHeaders(messageHeaders); let initHeaders = (opt_options && opt_options.initMessageHeaders) || null; if (opt_options && opt_options.messageContentType) { if (initHeaders) { goog.object.set( initHeaders, goog.net.WebChannel.X_WEBCHANNEL_CONTENT_TYPE, opt_options.messageContentType); } else { initHeaders = goog.object.create( goog.net.WebChannel.X_WEBCHANNEL_CONTENT_TYPE, opt_options.messageContentType); } } if (opt_options && opt_options.clientProfile) { if (initHeaders) { goog.object.set( initHeaders, goog.net.WebChannel.X_WEBCHANNEL_CLIENT_PROFILE, opt_options.clientProfile); } else { initHeaders = goog.object.create( goog.net.WebChannel.X_WEBCHANNEL_CLIENT_PROFILE, opt_options.clientProfile); } } this.channel_.setInitHeaders(initHeaders); const httpHeadersOverwriteParam = opt_options && opt_options.httpHeadersOverwriteParam; if (httpHeadersOverwriteParam && !goog.string.isEmptyOrWhitespace(httpHeadersOverwriteParam)) { this.channel_.setHttpHeadersOverwriteParam(httpHeadersOverwriteParam); } /** * @private {boolean} Whether to enable CORS. */ this.supportsCrossDomainXhr_ = (opt_options && opt_options.supportsCrossDomainXhr) || false; /** * @private {boolean} Whether to send raw Json and bypass v8 wire format. */ this.sendRawJson_ = (opt_options && opt_options.sendRawJson) || false; // Note that httpSessionIdParam will be ignored if the same parameter name // has already been specified with messageUrlParams const httpSessionIdParam = opt_options && opt_options.httpSessionIdParam; if (httpSessionIdParam && !goog.string.isEmptyOrWhitespace(httpSessionIdParam)) { this.channel_.setHttpSessionIdParam(httpSessionIdParam); if (goog.object.containsKey(this.messageUrlParams_, httpSessionIdParam)) { goog.object.remove(this.messageUrlParams_, httpSessionIdParam); goog.log.warning( this.logger_, 'Ignore httpSessionIdParam also specified with messageUrlParams: ' + httpSessionIdParam); } } /** * The channel handler. * * @private {!WebChannelBaseTransport.Channel.Handler_} */ this.channelHandler_ = new WebChannelBaseTransport.Channel.Handler_(this); }; goog.inherits(WebChannelBaseTransport.Channel, goog.events.EventTarget); /** * @override */ WebChannelBaseTransport.Channel.prototype.open = function() { 'use strict'; this.channel_.setHandler(this.channelHandler_); if (this.supportsCrossDomainXhr_) { this.channel_.setSupportsCrossDomainXhrs(true); } this.channel_.connect(this.url_, (this.messageUrlParams_ || undefined)); }; /** * @override */ WebChannelBaseTransport.Channel.prototype.close = function() { 'use strict'; this.channel_.disconnect(); }; /** * @override */ WebChannelBaseTransport.Channel.prototype.halfClose = function() { 'use strict'; // to be implemented throw new Error('Not implemented'); }; /** * The WebChannelBase only supports object types. * * @param {!goog.net.WebChannel.MessageData} message The message to send. * * @override */ WebChannelBaseTransport.Channel.prototype.send = function(message) { 'use strict'; this.channel_.sendMap(this.messageToMapObject_(message)); }; /** * Converts a message to the map used by the underlying channel. * * @param {!goog.net.WebChannel.MessageData} message * @return {!Object|!goog.collections.maps.MapLike} */ WebChannelBaseTransport.Channel.prototype.messageToMapObject_ = function( message) { 'use strict'; goog.asserts.assert( goog.isObject(message) || typeof message === 'string', 'only object type or raw string is supported'); if (typeof message === 'string') { const rawJson = {}; rawJson[Wire.RAW_DATA_KEY] = message; return rawJson; } if (this.sendRawJson_) { const rawJson = {}; rawJson[Wire.RAW_DATA_KEY] = goog.json.serialize(message); return rawJson; } return message; }; /** * Converts the map used by the underlying channel to a message. * * NOTE: In the case of the message being JS Object or string, the exact same * object passed during `messageToMapObject_()` is returned. In the case of raw * JSON, an equal (but not the same) object is returned (due to serialization). * * @param {!Object|!goog.collections.maps.MapLike} map * @return {!goog.net.WebChannel.MessageData} */ WebChannelBaseTransport.Channel.prototype.mapObjectToMessage_ = function(map) { 'use strict'; if (Wire.RAW_DATA_KEY in map) { const rawMessage = map[Wire.RAW_DATA_KEY]; if (this.sendRawJson_) { return /** @type {!goog.net.WebChannel.MessageData} */ ( goog.json.parse(rawMessage)); } else { // string message return rawMessage; } } return map; }; /** * @override */ WebChannelBaseTransport.Channel.prototype.disposeInternal = function() { 'use strict'; this.channel_.setHandler(null); delete this.channelHandler_; this.channel_.disconnect(); delete this.channel_; WebChannelBaseTransport.Channel.base(this, 'disposeInternal'); }; /** * The message event. * * @param {!Array<?>|!Object} array The data array from the underlying channel. * @constructor * @extends {goog.net.WebChannel.MessageEvent} * @final */ WebChannelBaseTransport.Channel.MessageEvent = function(array) { 'use strict'; WebChannelBaseTransport.Channel.MessageEvent.base(this, 'constructor'); // Metadata as HTTP headers and status code (always come in a pair). if (array['__headers__']) { this.headers = array['__headers__']; this.statusCode = array['__status__']; delete array['__headers__']; delete array['__status__']; } // single-metadata only const metadata = array['__sm__']; if (metadata) { this.metadataKey = goog.object.getAnyKey(metadata); if (this.metadataKey) { this.data = goog.object.get(metadata, this.metadataKey); } else { this.data = metadata; // empty } } else { this.data = array; } }; goog.inherits( WebChannelBaseTransport.Channel.MessageEvent, goog.net.WebChannel.MessageEvent); /** * The error event. * * @param {WebChannelBase.Error} error The error code. * @constructor * @extends {goog.net.WebChannel.ErrorEvent} * @final */ WebChannelBaseTransport.Channel.ErrorEvent = function(error) { 'use strict'; WebChannelBaseTransport.Channel.ErrorEvent.base(this, 'constructor'); /** * High-level status code. */ this.status = goog.net.WebChannel.ErrorStatus.NETWORK_ERROR; /** * @const {WebChannelBase.Error} Internal error code, for debugging use only. */ this.errorCode = error; }; goog.inherits( WebChannelBaseTransport.Channel.ErrorEvent, goog.net.WebChannel.ErrorEvent); /** * Implementation of {@link WebChannelBase.Handler} interface. * * @param {!WebChannelBaseTransport.Channel} channel The enclosing WebChannel. * * @constructor * @extends {WebChannelBase.Handler} * @private */ WebChannelBaseTransport.Channel.Handler_ = function(channel) { 'use strict'; WebChannelBaseTransport.Channel.Handler_.base(this, 'constructor'); /** * @type {!WebChannelBaseTransport.Channel} * @private */ this.channel_ = channel; }; goog.inherits(WebChannelBaseTransport.Channel.Handler_, WebChannelBase.Handler); /** * @override */ WebChannelBaseTransport.Channel.Handler_.prototype.channelOpened = function( channel) { 'use strict'; goog.log.info( this.channel_.logger_, 'WebChannel opened on ' + this.channel_.url_); this.channel_.dispatchEvent(goog.net.WebChannel.EventType.OPEN); }; /** * @override */ WebChannelBaseTransport.Channel.Handler_.prototype.channelHandleArray = function(channel, array) { 'use strict'; goog.asserts.assert(array, 'array expected to be defined'); this.channel_.dispatchEvent( new WebChannelBaseTransport.Channel.MessageEvent(array)); }; /** * @override */ WebChannelBaseTransport.Channel.Handler_.prototype.channelError = function( channel, error) { 'use strict'; goog.log.info( this.channel_.logger_, 'WebChannel aborted on ' + this.channel_.url_ + ' due to channel error: ' + error); this.channel_.dispatchEvent( new WebChannelBaseTransport.Channel.ErrorEvent(error)); }; /** * @override */ WebChannelBaseTransport.Channel.Handler_.prototype.channelClosed = function( channel, opt_pendingMaps, opt_undeliveredMaps) { 'use strict'; goog.log.info( this.channel_.logger_, 'WebChannel closed on ' + this.channel_.url_); this.channel_.dispatchEvent(goog.net.WebChannel.EventType.CLOSE); }; /** * @override */ WebChannelBaseTransport.Channel.prototype.getRuntimeProperties = function() { 'use strict'; return new WebChannelBaseTransport.ChannelProperties(this, this.channel_); }; /** * Implementation of the {@link goog.net.WebChannel.RuntimeProperties}. * * @param {!WebChannelBaseTransport.Channel} transportChannel The transport * channel object. * @param {!WebChannelBase} channel The underlying channel object. * * @constructor * @implements {goog.net.WebChannel.RuntimeProperties} * @final */ WebChannelBaseTransport.ChannelProperties = function( transportChannel, channel) { 'use strict'; /** * The transport channel object. * * @private @const {!WebChannelBaseTransport.Channel} */ this.transportChannel_ = transportChannel; /** * The underlying channel object. * * @private @const {!WebChannelBase} */ this.channel_ = channel; }; /** * @override */ WebChannelBaseTransport.ChannelProperties.prototype.getConcurrentRequestLimit = function() { 'use strict'; return this.channel_.getForwardChannelRequestPool().getMaxSize(); }; /** * @override */ WebChannelBaseTransport.ChannelProperties.prototype.isSpdyEnabled = function() { 'use strict'; return this.getConcurrentRequestLimit() > 1; }; /** * @override */ WebChannelBaseTransport.ChannelProperties.prototype.getPendingRequestCount = function() { 'use strict'; return this.channel_.getForwardChannelRequestPool().getRequestCount(); }; /** * @override * @return {!Array<!goog.net.WebChannel.MessageData>} */ WebChannelBaseTransport.ChannelProperties.prototype.getNonAckedMessages = function() { 'use strict'; return this.channel_.getNonAckedMaps().map( queued_map => this.transportChannel_.mapObjectToMessage_(queued_map.map)); }; /** * @override */ WebChannelBaseTransport.ChannelProperties.prototype.getHttpSessionId = function() { 'use strict'; return this.channel_.getHttpSessionId(); }; /** * @override */ WebChannelBaseTransport.ChannelProperties.prototype.commit = function( callback) { 'use strict'; this.channel_.setForwardChannelFlushCallback(callback); }; /** * @override */ WebChannelBaseTransport.ChannelProperties.prototype.notifyNonAckedMessageCount = goog.abstractMethod; /** * @override */ WebChannelBaseTransport.ChannelProperties.prototype.onCommit = goog.abstractMethod; /** * @override */ WebChannelBaseTransport.ChannelProperties.prototype.ackCommit = goog.abstractMethod; /** * @override * @return {!Object<string, string>|undefined} */ WebChannelBaseTransport.ChannelProperties.prototype.getLastResponseHeaders = function() { 'use strict'; return this.channel_.getLastResponseHeaders(); }; /** @override */ WebChannelBaseTransport.ChannelProperties.prototype.getLastStatusCode = function() { 'use strict'; return this.channel_.getLastStatusCode(); }; }); // goog.scope