UNPKG

@aws-amplify/pubsub

Version:

Pubsub category of aws-amplify

856 lines • 48.8 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); var tslib_1 = require("tslib"); // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 var zen_observable_ts_1 = tslib_1.__importDefault(require("zen-observable-ts")); var graphql_1 = require("graphql"); var url = tslib_1.__importStar(require("url")); var uuid_1 = require("uuid"); var buffer_1 = require("buffer"); var core_1 = require("@aws-amplify/core"); var cache_1 = require("@aws-amplify/cache"); var auth_1 = require("@aws-amplify/auth"); var PubSubProvider_1 = require("../PubSubProvider"); var PubSub_1 = require("../../types/PubSub"); var constants_1 = require("../constants"); var ConnectionStateMonitor_1 = require("../../utils/ConnectionStateMonitor"); var ReconnectionMonitor_1 = require("../../utils/ReconnectionMonitor"); var logger = new core_1.Logger('AWSAppSyncRealTimeProvider'); var dispatchApiEvent = function (event, data, message) { core_1.Hub.dispatch('api', { event: event, data: data, message: message }, 'PubSub', constants_1.AMPLIFY_SYMBOL); }; var standardDomainPattern = /^https:\/\/\w{26}\.appsync\-api\.\w{2}(?:(?:\-\w{2,})+)\-\d\.amazonaws.com(?:\.cn)?\/graphql$/i; var customDomainPath = '/realtime'; var AWSAppSyncRealTimeProvider = /** @class */ (function (_super) { tslib_1.__extends(AWSAppSyncRealTimeProvider, _super); function AWSAppSyncRealTimeProvider(options) { if (options === void 0) { options = {}; } var _this = _super.call(this, options) || this; _this.socketStatus = constants_1.SOCKET_STATUS.CLOSED; _this.keepAliveTimeout = constants_1.DEFAULT_KEEP_ALIVE_TIMEOUT; _this.subscriptionObserverMap = new Map(); _this.promiseArray = []; _this.connectionStateMonitor = new ConnectionStateMonitor_1.ConnectionStateMonitor(); _this.reconnectionMonitor = new ReconnectionMonitor_1.ReconnectionMonitor(); // Monitor the connection state and pass changes along to Hub _this.connectionStateMonitorSubscription = _this.connectionStateMonitor.connectionStateObservable.subscribe(function (connectionState) { dispatchApiEvent(constants_1.CONNECTION_STATE_CHANGE, { provider: _this, connectionState: connectionState, }, "Connection state is " + connectionState); _this.connectionState = connectionState; // Trigger START_RECONNECT when the connection is disrupted if (connectionState === PubSub_1.ConnectionState.ConnectionDisrupted) { _this.reconnectionMonitor.record(ReconnectionMonitor_1.ReconnectEvent.START_RECONNECT); } // Trigger HALT_RECONNECT to halt reconnection attempts when the state is anything other than // ConnectionDisrupted or Connecting if ([ PubSub_1.ConnectionState.Connected, PubSub_1.ConnectionState.ConnectedPendingDisconnect, PubSub_1.ConnectionState.ConnectedPendingKeepAlive, PubSub_1.ConnectionState.ConnectedPendingNetwork, PubSub_1.ConnectionState.ConnectionDisruptedPendingNetwork, PubSub_1.ConnectionState.Disconnected, ].includes(connectionState)) { _this.reconnectionMonitor.record(ReconnectionMonitor_1.ReconnectEvent.HALT_RECONNECT); } }); return _this; } /** * Mark the socket closed and release all active listeners */ AWSAppSyncRealTimeProvider.prototype.close = function () { // Mark the socket closed both in status and the connection monitor this.socketStatus = constants_1.SOCKET_STATUS.CLOSED; this.connectionStateMonitor.record(ConnectionStateMonitor_1.CONNECTION_CHANGE.CONNECTION_FAILED); // Turn off the subscription monitor Hub publishing this.connectionStateMonitorSubscription.unsubscribe(); // Complete all reconnect observers this.reconnectionMonitor.close(); }; AWSAppSyncRealTimeProvider.prototype.getNewWebSocket = function (url, protocol) { return new WebSocket(url, protocol); }; AWSAppSyncRealTimeProvider.prototype.getProviderName = function () { return 'AWSAppSyncRealTimeProvider'; }; AWSAppSyncRealTimeProvider.prototype.newClient = function () { throw new Error('Not used here'); }; AWSAppSyncRealTimeProvider.prototype.publish = function (_topics, _msg, _options) { return tslib_1.__awaiter(this, void 0, void 0, function () { return tslib_1.__generator(this, function (_a) { throw new Error('Operation not supported'); }); }); }; // Check if url matches standard domain pattern AWSAppSyncRealTimeProvider.prototype.isCustomDomain = function (url) { return url.match(standardDomainPattern) === null; }; AWSAppSyncRealTimeProvider.prototype.subscribe = function (_topics, options, customUserAgentDetails) { var _this = this; var appSyncGraphqlEndpoint = options === null || options === void 0 ? void 0 : options.appSyncGraphqlEndpoint; return new zen_observable_ts_1.default(function (observer) { if (!options || !appSyncGraphqlEndpoint) { observer.error({ errors: [ tslib_1.__assign({}, new graphql_1.GraphQLError("Subscribe only available for AWS AppSync endpoint")), ], }); observer.complete(); } else { var subscriptionStartActive_1 = false; var subscriptionId_1 = uuid_1.v4(); var startSubscription_1 = function () { if (!subscriptionStartActive_1) { subscriptionStartActive_1 = true; var startSubscriptionPromise = _this._startSubscriptionWithAWSAppSyncRealTime({ options: options, observer: observer, subscriptionId: subscriptionId_1, customUserAgentDetails: customUserAgentDetails, }).catch(function (err) { logger.debug(PubSub_1.CONTROL_MSG.REALTIME_SUBSCRIPTION_INIT_ERROR + ": " + err); _this.connectionStateMonitor.record(ConnectionStateMonitor_1.CONNECTION_CHANGE.CLOSED); }); startSubscriptionPromise.finally(function () { subscriptionStartActive_1 = false; }); } }; var reconnectSubscription_1; // Add an observable to the reconnection list to manage reconnection for this subscription reconnectSubscription_1 = new zen_observable_ts_1.default(function (observer) { _this.reconnectionMonitor.addObserver(observer); }).subscribe(function () { startSubscription_1(); }); startSubscription_1(); return function () { return tslib_1.__awaiter(_this, void 0, void 0, function () { var subscriptionState, err_1; return tslib_1.__generator(this, function (_a) { switch (_a.label) { case 0: // Cleanup reconnection subscription reconnectSubscription_1 === null || reconnectSubscription_1 === void 0 ? void 0 : reconnectSubscription_1.unsubscribe(); _a.label = 1; case 1: _a.trys.push([1, 3, 4, 5]); // Waiting that subscription has been connected before trying to unsubscribe return [4 /*yield*/, this._waitForSubscriptionToBeConnected(subscriptionId_1)]; case 2: // Waiting that subscription has been connected before trying to unsubscribe _a.sent(); subscriptionState = (this.subscriptionObserverMap.get(subscriptionId_1) || {}).subscriptionState; if (!subscriptionState) { // subscription already unsubscribed return [2 /*return*/]; } if (subscriptionState === constants_1.SUBSCRIPTION_STATUS.CONNECTED) { this._sendUnsubscriptionMessage(subscriptionId_1); } else { throw new Error('Subscription never connected'); } return [3 /*break*/, 5]; case 3: err_1 = _a.sent(); logger.debug("Error while unsubscribing " + err_1); return [3 /*break*/, 5]; case 4: this._removeSubscriptionObserver(subscriptionId_1); return [7 /*endfinally*/]; case 5: return [2 /*return*/]; } }); }); }; } }); }; Object.defineProperty(AWSAppSyncRealTimeProvider.prototype, "isSSLEnabled", { get: function () { return !this.options['aws_appsync_dangerously_connect_to_http_endpoint_for_testing']; }, enumerable: true, configurable: true }); AWSAppSyncRealTimeProvider.prototype._startSubscriptionWithAWSAppSyncRealTime = function (_a) { var options = _a.options, observer = _a.observer, subscriptionId = _a.subscriptionId, customUserAgentDetails = _a.customUserAgentDetails; var _b; return tslib_1.__awaiter(this, void 0, void 0, function () { var appSyncGraphqlEndpoint, authenticationType, query, variables, apiKey, region, _c, graphql_headers, _d, additionalHeaders, subscriptionState, data, dataString, headerObj, _e, _f, subscriptionMessage, stringToAWSRealTime, err_2, _g, subscriptionFailedCallback, subscriptionReadyCallback; var _h; var _this = this; return tslib_1.__generator(this, function (_j) { switch (_j.label) { case 0: appSyncGraphqlEndpoint = options.appSyncGraphqlEndpoint, authenticationType = options.authenticationType, query = options.query, variables = options.variables, apiKey = options.apiKey, region = options.region, _c = options.graphql_headers, graphql_headers = _c === void 0 ? function () { return ({}); } : _c, _d = options.additionalHeaders, additionalHeaders = _d === void 0 ? {} : _d; subscriptionState = constants_1.SUBSCRIPTION_STATUS.PENDING; data = { query: query, variables: variables, }; // Having a subscription id map will make it simple to forward messages received this.subscriptionObserverMap.set(subscriptionId, { observer: observer, query: query !== null && query !== void 0 ? query : '', variables: variables !== null && variables !== void 0 ? variables : {}, subscriptionState: subscriptionState, startAckTimeoutId: undefined, }); dataString = JSON.stringify(data); _e = [{}]; return [4 /*yield*/, this._awsRealTimeHeaderBasedAuth({ apiKey: apiKey, appSyncGraphqlEndpoint: appSyncGraphqlEndpoint, authenticationType: authenticationType, payload: dataString, canonicalUri: '', region: region, additionalHeaders: additionalHeaders, })]; case 1: _f = [tslib_1.__assign.apply(void 0, _e.concat([(_j.sent())]))]; return [4 /*yield*/, graphql_headers()]; case 2: headerObj = tslib_1.__assign.apply(void 0, [tslib_1.__assign.apply(void 0, [tslib_1.__assign.apply(void 0, _f.concat([(_j.sent())])), additionalHeaders]), (_h = {}, _h[core_1.USER_AGENT_HEADER] = core_1.getAmplifyUserAgent(customUserAgentDetails), _h)]); subscriptionMessage = { id: subscriptionId, payload: { data: dataString, extensions: { authorization: tslib_1.__assign({}, headerObj), }, }, type: constants_1.MESSAGE_TYPES.GQL_START, }; stringToAWSRealTime = JSON.stringify(subscriptionMessage); _j.label = 3; case 3: _j.trys.push([3, 5, , 6]); this.connectionStateMonitor.record(ConnectionStateMonitor_1.CONNECTION_CHANGE.OPENING_CONNECTION); return [4 /*yield*/, this._initializeWebSocketConnection({ apiKey: apiKey, appSyncGraphqlEndpoint: appSyncGraphqlEndpoint, authenticationType: authenticationType, region: region, additionalHeaders: additionalHeaders, })]; case 4: _j.sent(); return [3 /*break*/, 6]; case 5: err_2 = _j.sent(); this._logStartSubscriptionError(subscriptionId, observer, err_2); return [2 /*return*/]; case 6: _g = (_b = this.subscriptionObserverMap.get(subscriptionId)) !== null && _b !== void 0 ? _b : {}, subscriptionFailedCallback = _g.subscriptionFailedCallback, subscriptionReadyCallback = _g.subscriptionReadyCallback; // This must be done before sending the message in order to be listening immediately this.subscriptionObserverMap.set(subscriptionId, { observer: observer, subscriptionState: subscriptionState, query: query !== null && query !== void 0 ? query : '', variables: variables !== null && variables !== void 0 ? variables : {}, subscriptionReadyCallback: subscriptionReadyCallback, subscriptionFailedCallback: subscriptionFailedCallback, startAckTimeoutId: setTimeout(function () { _this._timeoutStartSubscriptionAck.call(_this, subscriptionId); }, constants_1.START_ACK_TIMEOUT), }); if (this.awsRealTimeSocket) { this.awsRealTimeSocket.send(stringToAWSRealTime); } return [2 /*return*/]; } }); }); }; // Log logic for start subscription failures AWSAppSyncRealTimeProvider.prototype._logStartSubscriptionError = function (subscriptionId, observer, err) { var _a; logger.debug({ err: err }); var message = String((_a = err.message) !== null && _a !== void 0 ? _a : ''); // Resolving to give the state observer time to propogate the update Promise.resolve(this.connectionStateMonitor.record(ConnectionStateMonitor_1.CONNECTION_CHANGE.CLOSED)); // Capture the error only when the network didn't cause disruption if (this.connectionState !== PubSub_1.ConnectionState.ConnectionDisruptedPendingNetwork) { // When the error is non-retriable, error out the observable if (core_1.isNonRetryableError(err)) { observer.error({ errors: [ tslib_1.__assign({}, new graphql_1.GraphQLError(PubSub_1.CONTROL_MSG.CONNECTION_FAILED + ": " + message)), ], }); } else { logger.debug(PubSub_1.CONTROL_MSG.CONNECTION_FAILED + ": " + message); } var subscriptionFailedCallback = (this.subscriptionObserverMap.get(subscriptionId) || {}).subscriptionFailedCallback; // Notify concurrent unsubscription if (typeof subscriptionFailedCallback === 'function') { subscriptionFailedCallback(); } } }; // Waiting that subscription has been connected before trying to unsubscribe AWSAppSyncRealTimeProvider.prototype._waitForSubscriptionToBeConnected = function (subscriptionId) { return tslib_1.__awaiter(this, void 0, void 0, function () { var subscriptionObserver, subscriptionState; var _this = this; return tslib_1.__generator(this, function (_a) { subscriptionObserver = this.subscriptionObserverMap.get(subscriptionId); if (subscriptionObserver) { subscriptionState = subscriptionObserver.subscriptionState; // This in case unsubscribe is invoked before sending start subscription message if (subscriptionState === constants_1.SUBSCRIPTION_STATUS.PENDING) { return [2 /*return*/, new Promise(function (res, rej) { var observer = subscriptionObserver.observer, subscriptionState = subscriptionObserver.subscriptionState, variables = subscriptionObserver.variables, query = subscriptionObserver.query; _this.subscriptionObserverMap.set(subscriptionId, { observer: observer, subscriptionState: subscriptionState, variables: variables, query: query, subscriptionReadyCallback: res, subscriptionFailedCallback: rej, }); })]; } } return [2 /*return*/]; }); }); }; AWSAppSyncRealTimeProvider.prototype._sendUnsubscriptionMessage = function (subscriptionId) { try { if (this.awsRealTimeSocket && this.awsRealTimeSocket.readyState === WebSocket.OPEN && this.socketStatus === constants_1.SOCKET_STATUS.READY) { // Preparing unsubscribe message to stop receiving messages for that subscription var unsubscribeMessage = { id: subscriptionId, type: constants_1.MESSAGE_TYPES.GQL_STOP, }; var stringToAWSRealTime = JSON.stringify(unsubscribeMessage); this.awsRealTimeSocket.send(stringToAWSRealTime); } } catch (err) { // If GQL_STOP is not sent because of disconnection issue, then there is nothing the client can do logger.debug({ err: err }); } }; AWSAppSyncRealTimeProvider.prototype._removeSubscriptionObserver = function (subscriptionId) { this.subscriptionObserverMap.delete(subscriptionId); // Verifying 1000ms after removing subscription in case there are new subscription unmount/mount setTimeout(this._closeSocketIfRequired.bind(this), 1000); }; AWSAppSyncRealTimeProvider.prototype._closeSocketIfRequired = function () { if (this.subscriptionObserverMap.size > 0) { // Active subscriptions on the WebSocket return; } if (!this.awsRealTimeSocket) { this.socketStatus = constants_1.SOCKET_STATUS.CLOSED; return; } this.connectionStateMonitor.record(ConnectionStateMonitor_1.CONNECTION_CHANGE.CLOSING_CONNECTION); if (this.awsRealTimeSocket.bufferedAmount > 0) { // Still data on the WebSocket setTimeout(this._closeSocketIfRequired.bind(this), 1000); } else { logger.debug('closing WebSocket...'); if (this.keepAliveTimeoutId) { clearTimeout(this.keepAliveTimeoutId); } if (this.keepAliveAlertTimeoutId) { clearTimeout(this.keepAliveAlertTimeoutId); } var tempSocket = this.awsRealTimeSocket; // Cleaning callbacks to avoid race condition, socket still exists tempSocket.onclose = null; tempSocket.onerror = null; tempSocket.close(1000); this.awsRealTimeSocket = undefined; this.socketStatus = constants_1.SOCKET_STATUS.CLOSED; this.connectionStateMonitor.record(ConnectionStateMonitor_1.CONNECTION_CHANGE.CLOSED); } }; AWSAppSyncRealTimeProvider.prototype._handleIncomingSubscriptionMessage = function (message) { var _this = this; if (typeof message.data !== 'string') { return; } logger.debug("subscription message from AWS AppSync RealTime: " + message.data); var _a = JSON.parse(String(message.data)), _b = _a.id, id = _b === void 0 ? '' : _b, payload = _a.payload, type = _a.type; var _c = this.subscriptionObserverMap.get(id) || {}, _d = _c.observer, observer = _d === void 0 ? null : _d, _e = _c.query, query = _e === void 0 ? '' : _e, _f = _c.variables, variables = _f === void 0 ? {} : _f, startAckTimeoutId = _c.startAckTimeoutId, subscriptionReadyCallback = _c.subscriptionReadyCallback, subscriptionFailedCallback = _c.subscriptionFailedCallback; logger.debug({ id: id, observer: observer, query: query, variables: variables }); if (type === constants_1.MESSAGE_TYPES.GQL_DATA && payload && payload.data) { if (observer) { observer.next(payload); } else { logger.debug("observer not found for id: " + id); } return; } if (type === constants_1.MESSAGE_TYPES.GQL_START_ACK) { logger.debug("subscription ready for " + JSON.stringify({ query: query, variables: variables })); if (typeof subscriptionReadyCallback === 'function') { subscriptionReadyCallback(); } if (startAckTimeoutId) clearTimeout(startAckTimeoutId); dispatchApiEvent(PubSub_1.CONTROL_MSG.SUBSCRIPTION_ACK, { query: query, variables: variables }, 'Connection established for subscription'); var subscriptionState = constants_1.SUBSCRIPTION_STATUS.CONNECTED; if (observer) { this.subscriptionObserverMap.set(id, { observer: observer, query: query, variables: variables, startAckTimeoutId: undefined, subscriptionState: subscriptionState, subscriptionReadyCallback: subscriptionReadyCallback, subscriptionFailedCallback: subscriptionFailedCallback, }); } this.connectionStateMonitor.record(ConnectionStateMonitor_1.CONNECTION_CHANGE.CONNECTION_ESTABLISHED); return; } if (type === constants_1.MESSAGE_TYPES.GQL_CONNECTION_KEEP_ALIVE) { if (this.keepAliveTimeoutId) clearTimeout(this.keepAliveTimeoutId); if (this.keepAliveAlertTimeoutId) clearTimeout(this.keepAliveAlertTimeoutId); this.keepAliveTimeoutId = setTimeout(function () { return _this._errorDisconnect(PubSub_1.CONTROL_MSG.TIMEOUT_DISCONNECT); }, this.keepAliveTimeout); this.keepAliveAlertTimeoutId = setTimeout(function () { _this.connectionStateMonitor.record(ConnectionStateMonitor_1.CONNECTION_CHANGE.KEEP_ALIVE_MISSED); }, constants_1.DEFAULT_KEEP_ALIVE_ALERT_TIMEOUT); this.connectionStateMonitor.record(ConnectionStateMonitor_1.CONNECTION_CHANGE.KEEP_ALIVE); return; } if (type === constants_1.MESSAGE_TYPES.GQL_ERROR) { var subscriptionState = constants_1.SUBSCRIPTION_STATUS.FAILED; if (observer) { this.subscriptionObserverMap.set(id, { observer: observer, query: query, variables: variables, startAckTimeoutId: startAckTimeoutId, subscriptionReadyCallback: subscriptionReadyCallback, subscriptionFailedCallback: subscriptionFailedCallback, subscriptionState: subscriptionState, }); logger.debug(PubSub_1.CONTROL_MSG.CONNECTION_FAILED + ": " + JSON.stringify(payload)); observer.error({ errors: [ tslib_1.__assign({}, new graphql_1.GraphQLError(PubSub_1.CONTROL_MSG.CONNECTION_FAILED + ": " + JSON.stringify(payload))), ], }); if (startAckTimeoutId) clearTimeout(startAckTimeoutId); if (typeof subscriptionFailedCallback === 'function') { subscriptionFailedCallback(); } } } }; AWSAppSyncRealTimeProvider.prototype._errorDisconnect = function (msg) { logger.debug("Disconnect error: " + msg); if (this.awsRealTimeSocket) { this.connectionStateMonitor.record(ConnectionStateMonitor_1.CONNECTION_CHANGE.CLOSED); this.awsRealTimeSocket.close(); } this.socketStatus = constants_1.SOCKET_STATUS.CLOSED; }; AWSAppSyncRealTimeProvider.prototype._timeoutStartSubscriptionAck = function (subscriptionId) { var subscriptionObserver = this.subscriptionObserverMap.get(subscriptionId); if (subscriptionObserver) { var observer = subscriptionObserver.observer, query = subscriptionObserver.query, variables = subscriptionObserver.variables; if (!observer) { return; } this.subscriptionObserverMap.set(subscriptionId, { observer: observer, query: query, variables: variables, subscriptionState: constants_1.SUBSCRIPTION_STATUS.FAILED, }); this.connectionStateMonitor.record(ConnectionStateMonitor_1.CONNECTION_CHANGE.CLOSED); logger.debug('timeoutStartSubscription', JSON.stringify({ query: query, variables: variables })); } }; AWSAppSyncRealTimeProvider.prototype._initializeWebSocketConnection = function (_a) { var _this = this; var appSyncGraphqlEndpoint = _a.appSyncGraphqlEndpoint, authenticationType = _a.authenticationType, apiKey = _a.apiKey, region = _a.region, additionalHeaders = _a.additionalHeaders; if (this.socketStatus === constants_1.SOCKET_STATUS.READY) { return; } return new Promise(function (res, rej) { return tslib_1.__awaiter(_this, void 0, void 0, function () { var payloadString, authHeader, headerString, headerQs, payloadQs, discoverableEndpoint, protocol, awsRealTimeUrl, err_3; return tslib_1.__generator(this, function (_a) { switch (_a.label) { case 0: this.promiseArray.push({ res: res, rej: rej }); if (!(this.socketStatus === constants_1.SOCKET_STATUS.CLOSED)) return [3 /*break*/, 5]; _a.label = 1; case 1: _a.trys.push([1, 4, , 5]); this.socketStatus = constants_1.SOCKET_STATUS.CONNECTING; payloadString = '{}'; return [4 /*yield*/, this._awsRealTimeHeaderBasedAuth({ authenticationType: authenticationType, payload: payloadString, canonicalUri: '/connect', apiKey: apiKey, appSyncGraphqlEndpoint: appSyncGraphqlEndpoint, region: region, additionalHeaders: additionalHeaders, })]; case 2: authHeader = _a.sent(); headerString = authHeader ? JSON.stringify(authHeader) : ''; headerQs = buffer_1.Buffer.from(headerString).toString('base64'); payloadQs = buffer_1.Buffer.from(payloadString).toString('base64'); discoverableEndpoint = appSyncGraphqlEndpoint !== null && appSyncGraphqlEndpoint !== void 0 ? appSyncGraphqlEndpoint : ''; if (this.isCustomDomain(discoverableEndpoint)) { discoverableEndpoint = discoverableEndpoint.concat(customDomainPath); } else { discoverableEndpoint = discoverableEndpoint .replace('appsync-api', 'appsync-realtime-api') .replace('gogi-beta', 'grt-beta'); } protocol = this.isSSLEnabled ? 'wss://' : 'ws://'; discoverableEndpoint = discoverableEndpoint .replace('https://', protocol) .replace('http://', protocol); awsRealTimeUrl = discoverableEndpoint + "?header=" + headerQs + "&payload=" + payloadQs; return [4 /*yield*/, this._initializeRetryableHandshake(awsRealTimeUrl)]; case 3: _a.sent(); this.promiseArray.forEach(function (_a) { var res = _a.res; logger.debug('Notifying connection successful'); res(); }); this.socketStatus = constants_1.SOCKET_STATUS.READY; this.promiseArray = []; return [3 /*break*/, 5]; case 4: err_3 = _a.sent(); logger.debug('Connection exited with', err_3); this.promiseArray.forEach(function (_a) { var rej = _a.rej; return rej(err_3); }); this.promiseArray = []; if (this.awsRealTimeSocket && this.awsRealTimeSocket.readyState === WebSocket.OPEN) { this.awsRealTimeSocket.close(3001); } this.awsRealTimeSocket = undefined; this.socketStatus = constants_1.SOCKET_STATUS.CLOSED; return [3 /*break*/, 5]; case 5: return [2 /*return*/]; } }); }); }); }; AWSAppSyncRealTimeProvider.prototype._initializeRetryableHandshake = function (awsRealTimeUrl) { return tslib_1.__awaiter(this, void 0, void 0, function () { return tslib_1.__generator(this, function (_a) { switch (_a.label) { case 0: logger.debug("Initializaling retryable Handshake"); return [4 /*yield*/, core_1.jitteredExponentialRetry(this._initializeHandshake.bind(this), [awsRealTimeUrl], constants_1.MAX_DELAY_MS)]; case 1: _a.sent(); return [2 /*return*/]; } }); }); }; AWSAppSyncRealTimeProvider.prototype._initializeHandshake = function (awsRealTimeUrl) { return tslib_1.__awaiter(this, void 0, void 0, function () { var err_4, _a, errorType, errorCode; var _this = this; return tslib_1.__generator(this, function (_b) { switch (_b.label) { case 0: logger.debug("Initializing handshake " + awsRealTimeUrl); _b.label = 1; case 1: _b.trys.push([1, 4, , 5]); return [4 /*yield*/, (function () { return new Promise(function (res, rej) { var newSocket = _this.getNewWebSocket(awsRealTimeUrl, 'graphql-ws'); newSocket.onerror = function () { logger.debug("WebSocket connection error"); }; newSocket.onclose = function () { rej(new Error('Connection handshake error')); }; newSocket.onopen = function () { _this.awsRealTimeSocket = newSocket; return res(); }; }); })()]; case 2: _b.sent(); // Step 2: wait for ack from AWS AppSyncReaTime after sending init return [4 /*yield*/, (function () { return new Promise(function (res, rej) { if (_this.awsRealTimeSocket) { var ackOk_1 = false; _this.awsRealTimeSocket.onerror = function (error) { logger.debug("WebSocket error " + JSON.stringify(error)); }; _this.awsRealTimeSocket.onclose = function (event) { logger.debug("WebSocket closed " + event.reason); rej(new Error(JSON.stringify(event))); }; _this.awsRealTimeSocket.onmessage = function (message) { if (typeof message.data !== 'string') { return; } logger.debug("subscription message from AWS AppSyncRealTime: " + message.data + " "); var data = JSON.parse(message.data); var type = data.type, _a = data.payload, _b = (_a === void 0 ? {} : _a).connectionTimeoutMs, connectionTimeoutMs = _b === void 0 ? constants_1.DEFAULT_KEEP_ALIVE_TIMEOUT : _b; if (type === constants_1.MESSAGE_TYPES.GQL_CONNECTION_ACK) { ackOk_1 = true; if (_this.awsRealTimeSocket) { _this.keepAliveTimeout = connectionTimeoutMs; _this.awsRealTimeSocket.onmessage = _this._handleIncomingSubscriptionMessage.bind(_this); _this.awsRealTimeSocket.onerror = function (err) { logger.debug(err); _this._errorDisconnect(PubSub_1.CONTROL_MSG.CONNECTION_CLOSED); }; _this.awsRealTimeSocket.onclose = function (event) { logger.debug("WebSocket closed " + event.reason); _this._errorDisconnect(PubSub_1.CONTROL_MSG.CONNECTION_CLOSED); }; } res('Cool, connected to AWS AppSyncRealTime'); return; } if (type === constants_1.MESSAGE_TYPES.GQL_CONNECTION_ERROR) { var _c = data.payload, _d = (_c === void 0 ? {} : _c).errors, _e = tslib_1.__read(_d === void 0 ? [] : _d, 1), _f = _e[0], _g = _f === void 0 ? {} : _f, _h = _g.errorType, errorType = _h === void 0 ? '' : _h, _j = _g.errorCode, errorCode = _j === void 0 ? 0 : _j; rej({ errorType: errorType, errorCode: errorCode }); } }; var gqlInit = { type: constants_1.MESSAGE_TYPES.GQL_CONNECTION_INIT, }; _this.awsRealTimeSocket.send(JSON.stringify(gqlInit)); var checkAckOk_1 = function (ackOk) { if (!ackOk) { _this.connectionStateMonitor.record(ConnectionStateMonitor_1.CONNECTION_CHANGE.CONNECTION_FAILED); rej(new Error("Connection timeout: ack from AWSAppSyncRealTime was not received after " + constants_1.CONNECTION_INIT_TIMEOUT + " ms")); } }; setTimeout(function () { return checkAckOk_1(ackOk_1); }, constants_1.CONNECTION_INIT_TIMEOUT); } }); })()]; case 3: // Step 2: wait for ack from AWS AppSyncReaTime after sending init _b.sent(); return [3 /*break*/, 5]; case 4: err_4 = _b.sent(); _a = err_4, errorType = _a.errorType, errorCode = _a.errorCode; if (constants_1.NON_RETRYABLE_CODES.includes(errorCode)) { throw new core_1.NonRetryableError(errorType); } else if (errorType) { throw new Error(errorType); } else { throw err_4; } return [3 /*break*/, 5]; case 5: return [2 /*return*/]; } }); }); }; AWSAppSyncRealTimeProvider.prototype._awsRealTimeHeaderBasedAuth = function (_a) { var authenticationType = _a.authenticationType, payload = _a.payload, canonicalUri = _a.canonicalUri, appSyncGraphqlEndpoint = _a.appSyncGraphqlEndpoint, apiKey = _a.apiKey, region = _a.region, additionalHeaders = _a.additionalHeaders; return tslib_1.__awaiter(this, void 0, void 0, function () { var headerHandler, handler, host, result; return tslib_1.__generator(this, function (_b) { switch (_b.label) { case 0: headerHandler = { API_KEY: this._awsRealTimeApiKeyHeader.bind(this), AWS_IAM: this._awsRealTimeIAMHeader.bind(this), OPENID_CONNECT: this._awsRealTimeOPENIDHeader.bind(this), AMAZON_COGNITO_USER_POOLS: this._awsRealTimeCUPHeader.bind(this), AWS_LAMBDA: this._customAuthHeader, }; if (!(!authenticationType || !headerHandler[authenticationType])) return [3 /*break*/, 1]; logger.debug("Authentication type " + authenticationType + " not supported"); return [2 /*return*/, undefined]; case 1: handler = headerHandler[authenticationType]; host = url.parse(appSyncGraphqlEndpoint !== null && appSyncGraphqlEndpoint !== void 0 ? appSyncGraphqlEndpoint : '').host; logger.debug("Authenticating with " + authenticationType); return [4 /*yield*/, handler({ payload: payload, canonicalUri: canonicalUri, appSyncGraphqlEndpoint: appSyncGraphqlEndpoint, apiKey: apiKey, region: region, host: host, additionalHeaders: additionalHeaders, })]; case 2: result = _b.sent(); return [2 /*return*/, result]; } }); }); }; AWSAppSyncRealTimeProvider.prototype._awsRealTimeCUPHeader = function (_a) { var host = _a.host; return tslib_1.__awaiter(this, void 0, void 0, function () { var session; return tslib_1.__generator(this, function (_b) { switch (_b.label) { case 0: return [4 /*yield*/, auth_1.Auth.currentSession()]; case 1: session = _b.sent(); return [2 /*return*/, { Authorization: session.getAccessToken().getJwtToken(), host: host, }]; } }); }); }; AWSAppSyncRealTimeProvider.prototype._awsRealTimeOPENIDHeader = function (_a) { var host = _a.host; return tslib_1.__awaiter(this, void 0, void 0, function () { var token, federatedInfo, currentUser; return tslib_1.__generator(this, function (_b) { switch (_b.label) { case 0: return [4 /*yield*/, cache_1.Cache.getItem('federatedInfo')]; case 1: federatedInfo = _b.sent(); if (!federatedInfo) return [3 /*break*/, 2]; token = federatedInfo.token; return [3 /*break*/, 4]; case 2: return [4 /*yield*/, auth_1.Auth.currentAuthenticatedUser()]; case 3: currentUser = _b.sent(); if (currentUser) { token = currentUser.token; } _b.label = 4; case 4: if (!token) { throw new Error('No federated jwt'); } return [2 /*return*/, { Authorization: token, host: host, }]; } }); }); }; AWSAppSyncRealTimeProvider.prototype._awsRealTimeApiKeyHeader = function (_a) { var apiKey = _a.apiKey, host = _a.host; return tslib_1.__awaiter(this, void 0, void 0, function () { var dt, dtStr; return tslib_1.__generator(this, function (_b) { dt = new Date(); dtStr = dt.toISOString().replace(/[:\-]|\.\d{3}/g, ''); return [2 /*return*/, { host: host, 'x-amz-date': dtStr, 'x-api-key': apiKey, }]; }); }); }; AWSAppSyncRealTimeProvider.prototype._awsRealTimeIAMHeader = function (_a) { var payload = _a.payload, canonicalUri = _a.canonicalUri, appSyncGraphqlEndpoint = _a.appSyncGraphqlEndpoint, region = _a.region; return tslib_1.__awaiter(this, void 0, void 0, function () { var endpointInfo, credentialsOK, creds, request, signed_params; return tslib_1.__generator(this, function (_b) { switch (_b.label) { case 0: endpointInfo = { region: region, service: 'appsync', }; return [4 /*yield*/, this._ensureCredentials()]; case 1: credentialsOK = _b.sent(); if (!credentialsOK) { throw new Error('No credentials'); } return [4 /*yield*/, core_1.Credentials.get().then(function (credentials) { var _a = credentials, secretAccessKey = _a.secretAccessKey, accessKeyId = _a.accessKeyId, sessionToken = _a.sessionToken; return { secret_key: secretAccessKey, access_key: accessKeyId, session_token: sessionToken, }; })]; case 2: creds = _b.sent(); request = { url: "" + appSyncGraphqlEndpoint + canonicalUri, data: payload, method: 'POST', headers: tslib_1.__assign({}, constants_1.AWS_APPSYNC_REALTIME_HEADERS), }; signed_params = core_1.Signer.sign(request, creds, endpointInfo); return [2 /*return*/, signed_params.headers]; } }); }); }; AWSAppSyncRealTimeProvider.prototype._customAuthHeader = function (_a) { var host = _a.host, additionalHeaders = _a.additionalHeaders; if (!additionalHeaders || !additionalHeaders['Authorization']) { throw new Error('No auth token specified'); } return { Authorization: additionalHeaders.Authorization, host: host, }; }; /** * @private */ AWSAppSyncRealTimeProvider.prototype._ensureCredentials = function () { return core_1.Credentials.get() .then(function (credentials) { if (!credentials) return false; var cred = core_1.Credentials.shear(credentials); logger.debug('set credentials for AWSAppSyncRealTimeProvider', cred); return true; }) .catch(function (err) { logger.warn('ensure credentials error', err); return false; }); }; return AWSAppSyncRealTimeProvider; }(PubSubProvider_1.AbstractPubSubProvider)); exports.AWSAppSyncRealTimeProvider = AWSAppSyncRealTimeProvider; //# sourceMappingURL=index.js.map