@aws-amplify/pubsub
Version:
Pubsub category of aws-amplify
856 lines • 48.8 kB
JavaScript
"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