UNPKG

ran-boilerplate

Version:

React . Apollo (GraphQL) . Next.js Toolkit

352 lines (350 loc) 14.1 kB
"use strict"; /** * Copyright 2017 Google Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ Object.defineProperty(exports, "__esModule", { value: true }); var app_1 = require("@firebase/app"); var util_1 = require("@firebase/util"); var util_2 = require("../core/util/util"); var StatsManager_1 = require("../core/stats/StatsManager"); var Constants_1 = require("./Constants"); var util_3 = require("@firebase/util"); var storage_1 = require("../core/storage/storage"); var util_4 = require("@firebase/util"); var util_5 = require("@firebase/util"); var WEBSOCKET_MAX_FRAME_SIZE = 16384; var WEBSOCKET_KEEPALIVE_INTERVAL = 45000; var WebSocketImpl = null; if (typeof MozWebSocket !== 'undefined') { WebSocketImpl = MozWebSocket; } else if (typeof WebSocket !== 'undefined') { WebSocketImpl = WebSocket; } function setWebSocketImpl(impl) { WebSocketImpl = impl; } exports.setWebSocketImpl = setWebSocketImpl; /** * Create a new websocket connection with the given callbacks. * @constructor * @implements {Transport} */ var WebSocketConnection = /** @class */ (function () { /** * @param {string} connId identifier for this transport * @param {RepoInfo} repoInfo The info for the websocket endpoint. * @param {string=} transportSessionId Optional transportSessionId if this is connecting to an existing transport * session * @param {string=} lastSessionId Optional lastSessionId if there was a previous connection */ function WebSocketConnection(connId, repoInfo, transportSessionId, lastSessionId) { this.connId = connId; this.keepaliveTimer = null; this.frames = null; this.totalFrames = 0; this.bytesSent = 0; this.bytesReceived = 0; this.log_ = util_2.logWrapper(this.connId); this.stats_ = StatsManager_1.StatsManager.getCollection(repoInfo); this.connURL = WebSocketConnection.connectionURL_(repoInfo, transportSessionId, lastSessionId); } /** * @param {RepoInfo} repoInfo The info for the websocket endpoint. * @param {string=} transportSessionId Optional transportSessionId if this is connecting to an existing transport * session * @param {string=} lastSessionId Optional lastSessionId if there was a previous connection * @return {string} connection url * @private */ WebSocketConnection.connectionURL_ = function (repoInfo, transportSessionId, lastSessionId) { var urlParams = {}; urlParams[Constants_1.VERSION_PARAM] = Constants_1.PROTOCOL_VERSION; if (!util_5.isNodeSdk() && typeof location !== 'undefined' && location.href && location.href.indexOf(Constants_1.FORGE_DOMAIN) !== -1) { urlParams[Constants_1.REFERER_PARAM] = Constants_1.FORGE_REF; } if (transportSessionId) { urlParams[Constants_1.TRANSPORT_SESSION_PARAM] = transportSessionId; } if (lastSessionId) { urlParams[Constants_1.LAST_SESSION_PARAM] = lastSessionId; } return repoInfo.connectionURL(Constants_1.WEBSOCKET, urlParams); }; /** * * @param onMessage Callback when messages arrive * @param onDisconnect Callback with connection lost. */ WebSocketConnection.prototype.open = function (onMessage, onDisconnect) { var _this = this; this.onDisconnect = onDisconnect; this.onMessage = onMessage; this.log_('Websocket connecting to ' + this.connURL); this.everConnected_ = false; // Assume failure until proven otherwise. storage_1.PersistentStorage.set('previous_websocket_failure', true); try { if (util_5.isNodeSdk()) { var device = util_3.CONSTANTS.NODE_ADMIN ? 'AdminNode' : 'Node'; // UA Format: Firebase/<wire_protocol>/<sdk_version>/<platform>/<device> var options = { headers: { 'User-Agent': "Firebase/" + Constants_1.PROTOCOL_VERSION + "/" + app_1.default.SDK_VERSION + "/" + process.platform + "/" + device } }; // Plumb appropriate http_proxy environment variable into faye-websocket if it exists. var env = process['env']; var proxy = this.connURL.indexOf('wss://') == 0 ? env['HTTPS_PROXY'] || env['https_proxy'] : env['HTTP_PROXY'] || env['http_proxy']; if (proxy) { options['proxy'] = { origin: proxy }; } this.mySock = new WebSocketImpl(this.connURL, [], options); } else { this.mySock = new WebSocketImpl(this.connURL); } } catch (e) { this.log_('Error instantiating WebSocket.'); var error = e.message || e.data; if (error) { this.log_(error); } this.onClosed_(); return; } this.mySock.onopen = function () { _this.log_('Websocket connected.'); _this.everConnected_ = true; }; this.mySock.onclose = function () { _this.log_('Websocket connection was disconnected.'); _this.mySock = null; _this.onClosed_(); }; this.mySock.onmessage = function (m) { _this.handleIncomingFrame(m); }; this.mySock.onerror = function (e) { _this.log_('WebSocket error. Closing connection.'); var error = e.message || e.data; if (error) { _this.log_(error); } _this.onClosed_(); }; }; /** * No-op for websockets, we don't need to do anything once the connection is confirmed as open */ WebSocketConnection.prototype.start = function () { }; WebSocketConnection.forceDisallow = function () { WebSocketConnection.forceDisallow_ = true; }; WebSocketConnection.isAvailable = function () { var isOldAndroid = false; if (typeof navigator !== 'undefined' && navigator.userAgent) { var oldAndroidRegex = /Android ([0-9]{0,}\.[0-9]{0,})/; var oldAndroidMatch = navigator.userAgent.match(oldAndroidRegex); if (oldAndroidMatch && oldAndroidMatch.length > 1) { if (parseFloat(oldAndroidMatch[1]) < 4.4) { isOldAndroid = true; } } } return (!isOldAndroid && WebSocketImpl !== null && !WebSocketConnection.forceDisallow_); }; /** * Returns true if we previously failed to connect with this transport. * @return {boolean} */ WebSocketConnection.previouslyFailed = function () { // If our persistent storage is actually only in-memory storage, // we default to assuming that it previously failed to be safe. return (storage_1.PersistentStorage.isInMemoryStorage || storage_1.PersistentStorage.get('previous_websocket_failure') === true); }; WebSocketConnection.prototype.markConnectionHealthy = function () { storage_1.PersistentStorage.remove('previous_websocket_failure'); }; WebSocketConnection.prototype.appendFrame_ = function (data) { this.frames.push(data); if (this.frames.length == this.totalFrames) { var fullMess = this.frames.join(''); this.frames = null; var jsonMess = util_4.jsonEval(fullMess); //handle the message this.onMessage(jsonMess); } }; /** * @param {number} frameCount The number of frames we are expecting from the server * @private */ WebSocketConnection.prototype.handleNewFrameCount_ = function (frameCount) { this.totalFrames = frameCount; this.frames = []; }; /** * Attempts to parse a frame count out of some text. If it can't, assumes a value of 1 * @param {!String} data * @return {?String} Any remaining data to be process, or null if there is none * @private */ WebSocketConnection.prototype.extractFrameCount_ = function (data) { util_1.assert(this.frames === null, 'We already have a frame buffer'); // TODO: The server is only supposed to send up to 9999 frames (i.e. length <= 4), but that isn't being enforced // currently. So allowing larger frame counts (length <= 6). See https://app.asana.com/0/search/8688598998380/8237608042508 if (data.length <= 6) { var frameCount = Number(data); if (!isNaN(frameCount)) { this.handleNewFrameCount_(frameCount); return null; } } this.handleNewFrameCount_(1); return data; }; /** * Process a websocket frame that has arrived from the server. * @param mess The frame data */ WebSocketConnection.prototype.handleIncomingFrame = function (mess) { if (this.mySock === null) return; // Chrome apparently delivers incoming packets even after we .close() the connection sometimes. var data = mess['data']; this.bytesReceived += data.length; this.stats_.incrementCounter('bytes_received', data.length); this.resetKeepAlive(); if (this.frames !== null) { // we're buffering this.appendFrame_(data); } else { // try to parse out a frame count, otherwise, assume 1 and process it var remainingData = this.extractFrameCount_(data); if (remainingData !== null) { this.appendFrame_(remainingData); } } }; /** * Send a message to the server * @param {Object} data The JSON object to transmit */ WebSocketConnection.prototype.send = function (data) { this.resetKeepAlive(); var dataStr = util_4.stringify(data); this.bytesSent += dataStr.length; this.stats_.incrementCounter('bytes_sent', dataStr.length); //We can only fit a certain amount in each websocket frame, so we need to split this request //up into multiple pieces if it doesn't fit in one request. var dataSegs = util_2.splitStringBySize(dataStr, WEBSOCKET_MAX_FRAME_SIZE); //Send the length header if (dataSegs.length > 1) { this.sendString_(String(dataSegs.length)); } //Send the actual data in segments. for (var i = 0; i < dataSegs.length; i++) { this.sendString_(dataSegs[i]); } }; WebSocketConnection.prototype.shutdown_ = function () { this.isClosed_ = true; if (this.keepaliveTimer) { clearInterval(this.keepaliveTimer); this.keepaliveTimer = null; } if (this.mySock) { this.mySock.close(); this.mySock = null; } }; WebSocketConnection.prototype.onClosed_ = function () { if (!this.isClosed_) { this.log_('WebSocket is closing itself'); this.shutdown_(); // since this is an internal close, trigger the close listener if (this.onDisconnect) { this.onDisconnect(this.everConnected_); this.onDisconnect = null; } } }; /** * External-facing close handler. * Close the websocket and kill the connection. */ WebSocketConnection.prototype.close = function () { if (!this.isClosed_) { this.log_('WebSocket is being closed'); this.shutdown_(); } }; /** * Kill the current keepalive timer and start a new one, to ensure that it always fires N seconds after * the last activity. */ WebSocketConnection.prototype.resetKeepAlive = function () { var _this = this; clearInterval(this.keepaliveTimer); this.keepaliveTimer = setInterval(function () { //If there has been no websocket activity for a while, send a no-op if (_this.mySock) { _this.sendString_('0'); } _this.resetKeepAlive(); }, Math.floor(WEBSOCKET_KEEPALIVE_INTERVAL)); }; /** * Send a string over the websocket. * * @param {string} str String to send. * @private */ WebSocketConnection.prototype.sendString_ = function (str) { // Firefox seems to sometimes throw exceptions (NS_ERROR_UNEXPECTED) from websocket .send() // calls for some unknown reason. We treat these as an error and disconnect. // See https://app.asana.com/0/58926111402292/68021340250410 try { this.mySock.send(str); } catch (e) { this.log_('Exception thrown from WebSocket.send():', e.message || e.data, 'Closing connection.'); setTimeout(this.onClosed_.bind(this), 0); } }; /** * Number of response before we consider the connection "healthy." * @type {number} */ WebSocketConnection.responsesRequiredToBeHealthy = 2; /** * Time to wait for the connection te become healthy before giving up. * @type {number} */ WebSocketConnection.healthyTimeout = 30000; return WebSocketConnection; }()); exports.WebSocketConnection = WebSocketConnection; //# sourceMappingURL=WebSocketConnection.js.map