"use strict"; var __assign = (this && this.__assign) || function () { __assign = Object.assign || function(t) { for (var s, i = 1, n = arguments.length; i < n; i++) { s = arguments[i]; for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p]; } return t; }; return __assign.apply(this, arguments); }; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; var __generator = (this && this.__generator) || function (thisArg, body) { var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; function verb(n) { return function (v) { return step([n, v]); }; } function step(op) { if (f) throw new TypeError("Generator is already executing."); while (_) try { if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; if (y = 0, t) op = [op[0] & 2, t.value]; switch (op[0]) { case 0: case 1: t = op; break; case 4: _.label++; return { value: op[1], done: false }; case 5: _.label++; y = op[1]; op = [0]; continue; case 7: op = _.ops.pop(); _.trys.pop(); continue; default: if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } if (t[2]) _.ops.pop(); _.trys.pop(); continue; } op = body.call(thisArg, _); } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; } }; var __spreadArrays = (this && this.__spreadArrays) || function () { for (var s = 0, i = 0, il = arguments.length; i < il; i++) s += arguments[i].length; for (var r = Array(s), k = 0, i = 0; i < il; i++) for (var a = arguments[i], j = 0, jl = a.length; j < jl; j++, k++) r[k] = a[j]; return r; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.SubscriptionClient = void 0; var _global = typeof global !== 'undefined' ? global : (typeof window !== 'undefined' ? window : {}); var NativeWebSocket = _global.WebSocket || _global.MozWebSocket; var Backoff = require("backo2"); var eventemitter3_1 = require("eventemitter3"); var is_string_1 = require("./utils/is-string"); var is_object_1 = require("./utils/is-object"); var printer_1 = require("graphql/language/printer"); var getOperationAST_1 = require("graphql/utilities/getOperationAST"); var symbol_observable_1 = require("symbol-observable"); var protocol_1 = require("./protocol"); var defaults_1 = require("./defaults"); var message_types_1 = require("./message-types"); var SubscriptionClient = (function () { function SubscriptionClient(url, options, webSocketImpl, webSocketProtocols) { var _a = (options || {}), _b = _a.connectionCallback, connectionCallback = _b === void 0 ? undefined : _b, _c = _a.connectionParams, connectionParams = _c === void 0 ? {} : _c, _d = _a.minTimeout, minTimeout = _d === void 0 ? defaults_1.MIN_WS_TIMEOUT : _d, _e = _a.timeout, timeout = _e === void 0 ? defaults_1.WS_TIMEOUT : _e, _f = _a.reconnect, reconnect = _f === void 0 ? false : _f, _g = _a.reconnectionAttempts, reconnectionAttempts = _g === void 0 ? Infinity : _g, _h = _a.lazy, lazy = _h === void 0 ? false : _h, _j = _a.inactivityTimeout, inactivityTimeout = _j === void 0 ? 0 : _j, _k = _a.wsOptionArguments, wsOptionArguments = _k === void 0 ? [] : _k; this.wsImpl = webSocketImpl || NativeWebSocket; if (!this.wsImpl) { throw new Error('Unable to find native implementation, or alternative implementation for WebSocket!'); } this.wsProtocols = webSocketProtocols || protocol_1.GRAPHQL_WS; this.connectionCallback = connectionCallback; this.url = url; this.operations = {}; this.nextOperationId = 0; this.minWsTimeout = minTimeout; this.wsTimeout = timeout; this.unsentMessagesQueue = []; this.reconnect = reconnect; this.reconnecting = false; this.reconnectionAttempts = reconnectionAttempts; this.lazy = !!lazy; this.inactivityTimeout = inactivityTimeout; this.closedByUser = false; this.backoff = new Backoff({ jitter: 0.5 }); this.eventEmitter = new eventemitter3_1.EventEmitter(); this.middlewares = []; this.client = null; this.maxConnectTimeGenerator = this.createMaxConnectTimeGenerator(); this.connectionParams = this.getConnectionParams(connectionParams); this.wsOptionArguments = wsOptionArguments; if (!this.lazy) { this.connect(); } } Object.defineProperty(SubscriptionClient.prototype, "status", { get: function () { if (this.client === null) { return this.wsImpl.CLOSED; } return this.client.readyState; }, enumerable: false, configurable: true }); SubscriptionClient.prototype.close = function (isForced, closedByUser) { if (isForced === void 0) { isForced = true; } if (closedByUser === void 0) { closedByUser = true; } this.clearInactivityTimeout(); if (this.client !== null) { this.closedByUser = closedByUser; if (isForced) { this.clearCheckConnectionInterval(); this.clearMaxConnectTimeout(); this.clearTryReconnectTimeout(); this.unsubscribeAll(); this.sendMessage(undefined, message_types_1.default.GQL_CONNECTION_TERMINATE, null); } this.client.close(); this.client.onopen = null; this.client.onclose = null; this.client.onerror = null; this.client.onmessage = null; this.client = null; this.eventEmitter.emit('disconnected'); if (!isForced) { this.tryReconnect(); } } }; SubscriptionClient.prototype.request = function (request) { var _a; var getObserver = this.getObserver.bind(this); var executeOperation = this.executeOperation.bind(this); var unsubscribe = this.unsubscribe.bind(this); var opId; this.clearInactivityTimeout(); return _a = {}, _a[symbol_observable_1.default] = function () { return this; }, _a.subscribe = function (observerOrNext, onError, onComplete) { var observer = getObserver(observerOrNext, onError, onComplete); opId = executeOperation(request, function (error, result) { if (error === null && result === null) { if (observer.complete) { observer.complete(); } } else if (error) { if (observer.error) { observer.error(error[0]); } } else { if (observer.next) { observer.next(result); } } }); return { unsubscribe: function () { if (opId) { unsubscribe(opId); opId = null; } }, }; }, _a; }; SubscriptionClient.prototype.on = function (eventName, callback, context) { var handler = this.eventEmitter.on(eventName, callback, context); return function () { handler.off(eventName, callback, context); }; }; SubscriptionClient.prototype.onConnected = function (callback, context) { return this.on('connected', callback, context); }; SubscriptionClient.prototype.onConnecting = function (callback, context) { return this.on('connecting', callback, context); }; SubscriptionClient.prototype.onDisconnected = function (callback, context) { return this.on('disconnected', callback, context); }; SubscriptionClient.prototype.onReconnected = function (callback, context) { return this.on('reconnected', callback, context); }; SubscriptionClient.prototype.onReconnecting = function (callback, context) { return this.on('reconnecting', callback, context); }; SubscriptionClient.prototype.onError = function (callback, context) { return this.on('error', callback, context); }; SubscriptionClient.prototype.unsubscribeAll = function () { var _this = this; Object.keys(this.operations).forEach(function (subId) { _this.unsubscribe(subId); }); }; SubscriptionClient.prototype.applyMiddlewares = function (options) { var _this = this; return new Promise(function (resolve, reject) { var queue = function (funcs, scope) { var next = function (error) { if (error) { reject(error); } else { if (funcs.length > 0) { var f = funcs.shift(); if (f) { f.applyMiddleware.apply(scope, [options, next]); } } else { resolve(options); } } }; next(); }; queue(__spreadArrays(_this.middlewares), _this); }); }; SubscriptionClient.prototype.use = function (middlewares) { var _this = this; middlewares.map(function (middleware) { if (typeof middleware.applyMiddleware === 'function') { _this.middlewares.push(middleware); } else { throw new Error('Middleware must implement the applyMiddleware function.'); } }); return this; }; SubscriptionClient.prototype.getConnectionParams = function (connectionParams) { return function () { return new Promise(function (resolve, reject) { if (typeof connectionParams === 'function') { try { return resolve(connectionParams.call(null)); } catch (error) { return reject(error); } } resolve(connectionParams); }); }; }; SubscriptionClient.prototype.executeOperation = function (options, handler) { var _this = this; if (this.client === null) { this.connect(); } var opId = this.generateOperationId(); this.operations[opId] = { options: options, handler: handler }; this.applyMiddlewares(options) .then(function (processedOptions) { _this.checkOperationOptions(processedOptions, handler); if (_this.operations[opId]) { _this.operations[opId] = { options: processedOptions, handler: handler }; _this.sendMessage(opId, message_types_1.default.GQL_START, processedOptions); } }) .catch(function (error) { _this.unsubscribe(opId); handler(_this.formatErrors(error)); }); return opId; }; SubscriptionClient.prototype.getObserver = function (observerOrNext, error, complete) { if (typeof observerOrNext === 'function') { return { next: function (v) { return observerOrNext(v); }, error: function (e) { return error && error(e); }, complete: function () { return complete && complete(); }, }; } return observerOrNext; }; SubscriptionClient.prototype.createMaxConnectTimeGenerator = function () { var minValue = this.minWsTimeout; var maxValue = this.wsTimeout; return new Backoff({ min: minValue, max: maxValue, factor: 1.2, }); }; SubscriptionClient.prototype.clearCheckConnectionInterval = function () { if (this.checkConnectionIntervalId) { clearInterval(this.checkConnectionIntervalId); this.checkConnectionIntervalId = null; } }; SubscriptionClient.prototype.clearMaxConnectTimeout = function () { if (this.maxConnectTimeoutId) { clearTimeout(this.maxConnectTimeoutId); this.maxConnectTimeoutId = null; } }; SubscriptionClient.prototype.clearTryReconnectTimeout = function () { if (this.tryReconnectTimeoutId) { clearTimeout(this.tryReconnectTimeoutId); this.tryReconnectTimeoutId = null; } }; SubscriptionClient.prototype.clearInactivityTimeout = function () { if (this.inactivityTimeoutId) { clearTimeout(this.inactivityTimeoutId); this.inactivityTimeoutId = null; } }; SubscriptionClient.prototype.setInactivityTimeout = function () { var _this = this; if (this.inactivityTimeout > 0 && Object.keys(this.operations).length === 0) { this.inactivityTimeoutId = setTimeout(function () { if (Object.keys(_this.operations).length === 0) { _this.close(); } }, this.inactivityTimeout); } }; SubscriptionClient.prototype.checkOperationOptions = function (options, handler) { var query = options.query, variables = options.variables, operationName = options.operationName; if (!query) { throw new Error('Must provide a query.'); } if (!handler) { throw new Error('Must provide an handler.'); } if ((!is_string_1.default(query) && !getOperationAST_1.getOperationAST(query, operationName)) || (operationName && !is_string_1.default(operationName)) || (variables && !is_object_1.default(variables))) { throw new Error('Incorrect option types. query must be a string or a document,' + '`operationName` must be a string, and `variables` must be an object.'); } }; SubscriptionClient.prototype.buildMessage = function (id, type, payload) { var payloadToReturn = payload && payload.query ? __assign(__assign({}, payload), { query: typeof payload.query === 'string' ? payload.query : printer_1.print(payload.query) }) : payload; return { id: id, type: type, payload: payloadToReturn, }; }; SubscriptionClient.prototype.formatErrors = function (errors) { if (Array.isArray(errors)) { return errors; } if (errors && errors.errors) { return this.formatErrors(errors.errors); } if (errors && errors.message) { return [errors]; } return [{ name: 'FormatedError', message: 'Unknown error', originalError: errors, }]; }; SubscriptionClient.prototype.sendMessage = function (id, type, payload) { this.sendMessageRaw(this.buildMessage(id, type, payload)); }; SubscriptionClient.prototype.sendMessageRaw = function (message) { switch (this.status) { case this.wsImpl.OPEN: var serializedMessage = JSON.stringify(message); try { JSON.parse(serializedMessage); } catch (e) { this.eventEmitter.emit('error', new Error("Message must be JSON-serializable. Got: " + message)); } this.client.send(serializedMessage); break; case this.wsImpl.CONNECTING: this.unsentMessagesQueue.push(message); break; default: if (!this.reconnecting) { this.eventEmitter.emit('error', new Error('A message was not sent because socket is not connected, is closing or ' + 'is already closed. Message was: ' + JSON.stringify(message))); } } }; SubscriptionClient.prototype.generateOperationId = function () { return String(++this.nextOperationId); }; SubscriptionClient.prototype.tryReconnect = function () { var _this = this; if (!this.reconnect || this.backoff.attempts >= this.reconnectionAttempts) { return; } if (!this.reconnecting) { Object.keys(this.operations).forEach(function (key) { _this.unsentMessagesQueue.push(_this.buildMessage(key, message_types_1.default.GQL_START, _this.operations[key].options)); }); this.reconnecting = true; } this.clearTryReconnectTimeout(); var delay = this.backoff.duration(); this.tryReconnectTimeoutId = setTimeout(function () { _this.connect(); }, delay); }; SubscriptionClient.prototype.flushUnsentMessagesQueue = function () { var _this = this; this.unsentMessagesQueue.forEach(function (message) { _this.sendMessageRaw(message); }); this.unsentMessagesQueue = []; }; SubscriptionClient.prototype.checkConnection = function () { if (this.wasKeepAliveReceived) { this.wasKeepAliveReceived = false; return; } if (!this.reconnecting) { this.close(false, true); } }; SubscriptionClient.prototype.checkMaxConnectTimeout = function () { var _this = this; this.clearMaxConnectTimeout(); this.maxConnectTimeoutId = setTimeout(function () { if (_this.status !== _this.wsImpl.OPEN) { _this.reconnecting = true; _this.close(false, true); } }, this.maxConnectTimeGenerator.duration()); }; SubscriptionClient.prototype.connect = function () { var _a; var _this = this; this.client = new ((_a = this.wsImpl).bind.apply(_a, __spreadArrays([void 0, this.url, this.wsProtocols], this.wsOptionArguments)))(); this.checkMaxConnectTimeout(); this.client.onopen = function () { return __awaiter(_this, void 0, void 0, function () { var connectionParams, error_1; return __generator(this, function (_a) { switch (_a.label) { case 0: if (!(this.status === this.wsImpl.OPEN)) return [3, 4]; this.clearMaxConnectTimeout(); this.closedByUser = false; this.eventEmitter.emit(this.reconnecting ? 'reconnecting' : 'connecting'); _a.label = 1; case 1: _a.trys.push([1, 3, , 4]); return [4, this.connectionParams()]; case 2: connectionParams = _a.sent(); this.sendMessage(undefined, message_types_1.default.GQL_CONNECTION_INIT, connectionParams); this.flushUnsentMessagesQueue(); return [3, 4]; case 3: error_1 = _a.sent(); this.sendMessage(undefined, message_types_1.default.GQL_CONNECTION_ERROR, error_1); this.flushUnsentMessagesQueue(); return [3, 4]; case 4: return [2]; } }); }); }; this.client.onclose = function () { if (!_this.closedByUser) { _this.close(false, false); } }; this.client.onerror = function (err) { _this.eventEmitter.emit('error', err); }; this.client.onmessage = function (_a) { var data = _a.data; _this.processReceivedData(data); }; }; SubscriptionClient.prototype.processReceivedData = function (receivedData) { var parsedMessage; var opId; try { parsedMessage = JSON.parse(receivedData); opId = parsedMessage.id; } catch (e) { throw new Error("Message must be JSON-parseable. Got: " + receivedData); } if ([message_types_1.default.GQL_DATA, message_types_1.default.GQL_COMPLETE, message_types_1.default.GQL_ERROR, ].indexOf(parsedMessage.type) !== -1 && !this.operations[opId]) { this.unsubscribe(opId); return; } switch (parsedMessage.type) { case message_types_1.default.GQL_CONNECTION_ERROR: if (this.connectionCallback) { this.connectionCallback(parsedMessage.payload); } break; case message_types_1.default.GQL_CONNECTION_ACK: this.eventEmitter.emit(this.reconnecting ? 'reconnected' : 'connected', parsedMessage.payload); this.reconnecting = false; this.backoff.reset(); this.maxConnectTimeGenerator.reset(); if (this.connectionCallback) { this.connectionCallback(); } break; case message_types_1.default.GQL_COMPLETE: var handler = this.operations[opId].handler; delete this.operations[opId]; handler.call(this, null, null); break; case message_types_1.default.GQL_ERROR: this.operations[opId].handler(this.formatErrors(parsedMessage.payload), null); delete this.operations[opId]; break; case message_types_1.default.GQL_DATA: var parsedPayload = !parsedMessage.payload.errors ? parsedMessage.payload : __assign(__assign({}, parsedMessage.payload), { errors: this.formatErrors(parsedMessage.payload.errors) }); this.operations[opId].handler(null, parsedPayload); break; case message_types_1.default.GQL_CONNECTION_KEEP_ALIVE: var firstKA = typeof this.wasKeepAliveReceived === 'undefined'; this.wasKeepAliveReceived = true; if (firstKA) { this.checkConnection(); } if (this.checkConnectionIntervalId) { clearInterval(this.checkConnectionIntervalId); this.checkConnection(); } this.checkConnectionIntervalId = setInterval(this.checkConnection.bind(this), this.wsTimeout); break; default: throw new Error('Invalid message type!'); } }; SubscriptionClient.prototype.unsubscribe = function (opId) { if (this.operations[opId]) { delete this.operations[opId]; this.setInactivityTimeout(); this.sendMessage(opId, message_types_1.default.GQL_STOP, undefined); } }; return SubscriptionClient; }()); exports.SubscriptionClient = SubscriptionClient; //# sourceMappingURL=client.js.map