UNPKG

47 kBJavaScriptView Raw
1var __extends = (this && this.__extends) || (function () {
2 var extendStatics = function (d, b) {
3 extendStatics = Object.setPrototypeOf ||
4 ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
5 function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
6 return extendStatics(d, b);
7 };
8 return function (d, b) {
9 extendStatics(d, b);
10 function __() { this.constructor = d; }
11 d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
12 };
13})();
14var __assign = (this && this.__assign) || function () {
15 __assign = Object.assign || function(t) {
16 for (var s, i = 1, n = arguments.length; i < n; i++) {
17 s = arguments[i];
18 for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
19 t[p] = s[p];
20 }
21 return t;
22 };
23 return __assign.apply(this, arguments);
24};
25var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
26 function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
27 return new (P || (P = Promise))(function (resolve, reject) {
28 function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
29 function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
30 function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
31 step((generator = generator.apply(thisArg, _arguments || [])).next());
32 });
33};
34var __generator = (this && this.__generator) || function (thisArg, body) {
35 var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
36 return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
37 function verb(n) { return function (v) { return step([n, v]); }; }
38 function step(op) {
39 if (f) throw new TypeError("Generator is already executing.");
40 while (_) try {
41 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;
42 if (y = 0, t) op = [op[0] & 2, t.value];
43 switch (op[0]) {
44 case 0: case 1: t = op; break;
45 case 4: _.label++; return { value: op[1], done: false };
46 case 5: _.label++; y = op[1]; op = [0]; continue;
47 case 7: op = _.ops.pop(); _.trys.pop(); continue;
48 default:
49 if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
50 if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
51 if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
52 if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
53 if (t[2]) _.ops.pop();
54 _.trys.pop(); continue;
55 }
56 op = body.call(thisArg, _);
57 } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
58 if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
59 }
60};
61var __read = (this && this.__read) || function (o, n) {
62 var m = typeof Symbol === "function" && o[Symbol.iterator];
63 if (!m) return o;
64 var i = m.call(o), r, ar = [], e;
65 try {
66 while ((n === void 0 || n-- > 0) && !(r = i.next()).done) ar.push(r.value);
67 }
68 catch (error) { e = { error: error }; }
69 finally {
70 try {
71 if (r && !r.done && (m = i["return"])) m.call(i);
72 }
73 finally { if (e) throw e.error; }
74 }
75 return ar;
76};
77/*
78 * Copyright 2017-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
79 *
80 * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance with
81 * the License. A copy of the License is located at
82 *
83 * http://aws.amazon.com/apache2.0/
84 *
85 * or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
86 * CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions
87 * and limitations under the License.
88 */
89import Observable from 'zen-observable-ts';
90import { GraphQLError } from 'graphql';
91import * as url from 'url';
92import { v4 as uuid } from 'uuid';
93import { Buffer } from 'buffer';
94import { Logger, Credentials, Signer, Hub, Constants, USER_AGENT_HEADER, jitteredExponentialRetry, NonRetryableError, } from '@aws-amplify/core';
95import Cache from '@aws-amplify/cache';
96import Auth from '@aws-amplify/auth';
97import { AbstractPubSubProvider } from './PubSubProvider';
98import { CONTROL_MSG } from '../index';
99var logger = new Logger('AWSAppSyncRealTimeProvider');
100var AMPLIFY_SYMBOL = (typeof Symbol !== 'undefined' &&
101 typeof Symbol.for === 'function'
102 ? Symbol.for('amplify_default')
103 : '@@amplify_default');
104var dispatchApiEvent = function (event, data, message) {
105 Hub.dispatch('api', { event: event, data: data, message: message }, 'PubSub', AMPLIFY_SYMBOL);
106};
107var MAX_DELAY_MS = 5000;
108var NON_RETRYABLE_CODES = [400, 401, 403];
109var MESSAGE_TYPES;
110(function (MESSAGE_TYPES) {
111 /**
112 * Client -> Server message.
113 * This message type is the first message after handshake and this will initialize AWS AppSync RealTime communication
114 */
115 MESSAGE_TYPES["GQL_CONNECTION_INIT"] = "connection_init";
116 /**
117 * Server -> Client message
118 * This message type is in case there is an issue with AWS AppSync RealTime when establishing connection
119 */
120 MESSAGE_TYPES["GQL_CONNECTION_ERROR"] = "connection_error";
121 /**
122 * Server -> Client message.
123 * This message type is for the ack response from AWS AppSync RealTime for GQL_CONNECTION_INIT message
124 */
125 MESSAGE_TYPES["GQL_CONNECTION_ACK"] = "connection_ack";
126 /**
127 * Client -> Server message.
128 * This message type is for register subscriptions with AWS AppSync RealTime
129 */
130 MESSAGE_TYPES["GQL_START"] = "start";
131 /**
132 * Server -> Client message.
133 * This message type is for the ack response from AWS AppSync RealTime for GQL_START message
134 */
135 MESSAGE_TYPES["GQL_START_ACK"] = "start_ack";
136 /**
137 * Server -> Client message.
138 * This message type is for subscription message from AWS AppSync RealTime
139 */
140 MESSAGE_TYPES["GQL_DATA"] = "data";
141 /**
142 * Server -> Client message.
143 * This message type helps the client to know is still receiving messages from AWS AppSync RealTime
144 */
145 MESSAGE_TYPES["GQL_CONNECTION_KEEP_ALIVE"] = "ka";
146 /**
147 * Client -> Server message.
148 * This message type is for unregister subscriptions with AWS AppSync RealTime
149 */
150 MESSAGE_TYPES["GQL_STOP"] = "stop";
151 /**
152 * Server -> Client message.
153 * This message type is for the ack response from AWS AppSync RealTime for GQL_STOP message
154 */
155 MESSAGE_TYPES["GQL_COMPLETE"] = "complete";
156 /**
157 * Server -> Client message.
158 * This message type is for sending error messages from AWS AppSync RealTime to the client
159 */
160 MESSAGE_TYPES["GQL_ERROR"] = "error";
161})(MESSAGE_TYPES || (MESSAGE_TYPES = {}));
162var SUBSCRIPTION_STATUS;
163(function (SUBSCRIPTION_STATUS) {
164 SUBSCRIPTION_STATUS[SUBSCRIPTION_STATUS["PENDING"] = 0] = "PENDING";
165 SUBSCRIPTION_STATUS[SUBSCRIPTION_STATUS["CONNECTED"] = 1] = "CONNECTED";
166 SUBSCRIPTION_STATUS[SUBSCRIPTION_STATUS["FAILED"] = 2] = "FAILED";
167})(SUBSCRIPTION_STATUS || (SUBSCRIPTION_STATUS = {}));
168var SOCKET_STATUS;
169(function (SOCKET_STATUS) {
170 SOCKET_STATUS[SOCKET_STATUS["CLOSED"] = 0] = "CLOSED";
171 SOCKET_STATUS[SOCKET_STATUS["READY"] = 1] = "READY";
172 SOCKET_STATUS[SOCKET_STATUS["CONNECTING"] = 2] = "CONNECTING";
173})(SOCKET_STATUS || (SOCKET_STATUS = {}));
174var AWS_APPSYNC_REALTIME_HEADERS = {
175 accept: 'application/json, text/javascript',
176 'content-encoding': 'amz-1.0',
177 'content-type': 'application/json; charset=UTF-8',
178};
179/**
180 * Time in milleseconds to wait for GQL_CONNECTION_INIT message
181 */
182var CONNECTION_INIT_TIMEOUT = 15000;
183/**
184 * Time in milleseconds to wait for GQL_START_ACK message
185 */
186var START_ACK_TIMEOUT = 15000;
187/**
188 * Default Time in milleseconds to wait for GQL_CONNECTION_KEEP_ALIVE message
189 */
190var DEFAULT_KEEP_ALIVE_TIMEOUT = 5 * 60 * 1000;
191var AWSAppSyncRealTimeProvider = /** @class */ (function (_super) {
192 __extends(AWSAppSyncRealTimeProvider, _super);
193 function AWSAppSyncRealTimeProvider() {
194 var _this = _super !== null && _super.apply(this, arguments) || this;
195 _this.socketStatus = SOCKET_STATUS.CLOSED;
196 _this.keepAliveTimeout = DEFAULT_KEEP_ALIVE_TIMEOUT;
197 _this.subscriptionObserverMap = new Map();
198 _this.promiseArray = [];
199 return _this;
200 }
201 AWSAppSyncRealTimeProvider.prototype.getProviderName = function () {
202 return 'AWSAppSyncRealTimeProvider';
203 };
204 AWSAppSyncRealTimeProvider.prototype.newClient = function () {
205 throw new Error('Not used here');
206 };
207 AWSAppSyncRealTimeProvider.prototype.publish = function (_topics, _msg, _options) {
208 return __awaiter(this, void 0, void 0, function () {
209 return __generator(this, function (_a) {
210 throw new Error('Operation not supported');
211 });
212 });
213 };
214 AWSAppSyncRealTimeProvider.prototype.subscribe = function (_topics, options) {
215 var _this = this;
216 var appSyncGraphqlEndpoint = options.appSyncGraphqlEndpoint;
217 return new Observable(function (observer) {
218 if (!appSyncGraphqlEndpoint) {
219 observer.error({
220 errors: [
221 __assign({}, new GraphQLError("Subscribe only available for AWS AppSync endpoint")),
222 ],
223 });
224 observer.complete();
225 }
226 else {
227 var subscriptionId_1 = uuid();
228 _this._startSubscriptionWithAWSAppSyncRealTime({
229 options: options,
230 observer: observer,
231 subscriptionId: subscriptionId_1,
232 }).catch(function (err) {
233 observer.error({
234 errors: [
235 __assign({}, new GraphQLError(CONTROL_MSG.REALTIME_SUBSCRIPTION_INIT_ERROR + ": " + err)),
236 ],
237 });
238 observer.complete();
239 });
240 return function () { return __awaiter(_this, void 0, void 0, function () {
241 var subscriptionState, err_1;
242 return __generator(this, function (_a) {
243 switch (_a.label) {
244 case 0:
245 _a.trys.push([0, 2, 3, 4]);
246 // Waiting that subscription has been connected before trying to unsubscribe
247 return [4 /*yield*/, this._waitForSubscriptionToBeConnected(subscriptionId_1)];
248 case 1:
249 // Waiting that subscription has been connected before trying to unsubscribe
250 _a.sent();
251 subscriptionState = (this.subscriptionObserverMap.get(subscriptionId_1) || {}).subscriptionState;
252 if (!subscriptionState) {
253 // subscription already unsubscribed
254 return [2 /*return*/];
255 }
256 if (subscriptionState === SUBSCRIPTION_STATUS.CONNECTED) {
257 this._sendUnsubscriptionMessage(subscriptionId_1);
258 }
259 else {
260 throw new Error('Subscription never connected');
261 }
262 return [3 /*break*/, 4];
263 case 2:
264 err_1 = _a.sent();
265 logger.debug("Error while unsubscribing " + err_1);
266 return [3 /*break*/, 4];
267 case 3:
268 this._removeSubscriptionObserver(subscriptionId_1);
269 return [7 /*endfinally*/];
270 case 4: return [2 /*return*/];
271 }
272 });
273 }); };
274 }
275 });
276 };
277 Object.defineProperty(AWSAppSyncRealTimeProvider.prototype, "isSSLEnabled", {
278 get: function () {
279 return !this.options
280 .aws_appsync_dangerously_connect_to_http_endpoint_for_testing;
281 },
282 enumerable: true,
283 configurable: true
284 });
285 AWSAppSyncRealTimeProvider.prototype._startSubscriptionWithAWSAppSyncRealTime = function (_a) {
286 var options = _a.options, observer = _a.observer, subscriptionId = _a.subscriptionId;
287 return __awaiter(this, void 0, void 0, function () {
288 var appSyncGraphqlEndpoint, authenticationType, query, variables, apiKey, region, _b, graphql_headers, _c, additionalHeaders, subscriptionState, data, dataString, headerObj, _d, _e, subscriptionMessage, stringToAWSRealTime, err_2, _f, message, subscriptionFailedCallback_1, _g, subscriptionFailedCallback, subscriptionReadyCallback;
289 var _h;
290 var _this = this;
291 return __generator(this, function (_j) {
292 switch (_j.label) {
293 case 0:
294 appSyncGraphqlEndpoint = options.appSyncGraphqlEndpoint, authenticationType = options.authenticationType, query = options.query, variables = options.variables, apiKey = options.apiKey, region = options.region, _b = options.graphql_headers, graphql_headers = _b === void 0 ? function () { return ({}); } : _b, _c = options.additionalHeaders, additionalHeaders = _c === void 0 ? {} : _c;
295 subscriptionState = SUBSCRIPTION_STATUS.PENDING;
296 data = {
297 query: query,
298 variables: variables,
299 };
300 // Having a subscription id map will make it simple to forward messages received
301 this.subscriptionObserverMap.set(subscriptionId, {
302 observer: observer,
303 query: query,
304 variables: variables,
305 subscriptionState: subscriptionState,
306 startAckTimeoutId: null,
307 });
308 dataString = JSON.stringify(data);
309 _d = [{}];
310 return [4 /*yield*/, this._awsRealTimeHeaderBasedAuth({
311 apiKey: apiKey,
312 appSyncGraphqlEndpoint: appSyncGraphqlEndpoint,
313 authenticationType: authenticationType,
314 payload: dataString,
315 canonicalUri: '',
316 region: region,
317 additionalHeaders: additionalHeaders,
318 })];
319 case 1:
320 _e = [__assign.apply(void 0, _d.concat([(_j.sent())]))];
321 return [4 /*yield*/, graphql_headers()];
322 case 2:
323 headerObj = __assign.apply(void 0, [__assign.apply(void 0, [__assign.apply(void 0, _e.concat([(_j.sent())])), additionalHeaders]), (_h = {}, _h[USER_AGENT_HEADER] = Constants.userAgent, _h)]);
324 subscriptionMessage = {
325 id: subscriptionId,
326 payload: {
327 data: dataString,
328 extensions: {
329 authorization: __assign({}, headerObj),
330 },
331 },
332 type: MESSAGE_TYPES.GQL_START,
333 };
334 stringToAWSRealTime = JSON.stringify(subscriptionMessage);
335 _j.label = 3;
336 case 3:
337 _j.trys.push([3, 5, , 6]);
338 return [4 /*yield*/, this._initializeWebSocketConnection({
339 apiKey: apiKey,
340 appSyncGraphqlEndpoint: appSyncGraphqlEndpoint,
341 authenticationType: authenticationType,
342 region: region,
343 additionalHeaders: additionalHeaders,
344 })];
345 case 4:
346 _j.sent();
347 return [3 /*break*/, 6];
348 case 5:
349 err_2 = _j.sent();
350 logger.debug({ err: err_2 });
351 _f = err_2.message, message = _f === void 0 ? '' : _f;
352 observer.error({
353 errors: [
354 __assign({}, new GraphQLError(CONTROL_MSG.CONNECTION_FAILED + ": " + message)),
355 ],
356 });
357 observer.complete();
358 subscriptionFailedCallback_1 = (this.subscriptionObserverMap.get(subscriptionId) || {}).subscriptionFailedCallback;
359 // Notify concurrent unsubscription
360 if (typeof subscriptionFailedCallback_1 === 'function') {
361 subscriptionFailedCallback_1();
362 }
363 return [2 /*return*/];
364 case 6:
365 _g = this.subscriptionObserverMap.get(subscriptionId), subscriptionFailedCallback = _g.subscriptionFailedCallback, subscriptionReadyCallback = _g.subscriptionReadyCallback;
366 // This must be done before sending the message in order to be listening immediately
367 this.subscriptionObserverMap.set(subscriptionId, {
368 observer: observer,
369 subscriptionState: subscriptionState,
370 variables: variables,
371 query: query,
372 subscriptionReadyCallback: subscriptionReadyCallback,
373 subscriptionFailedCallback: subscriptionFailedCallback,
374 startAckTimeoutId: setTimeout(function () {
375 _this._timeoutStartSubscriptionAck.call(_this, subscriptionId);
376 }, START_ACK_TIMEOUT),
377 });
378 if (this.awsRealTimeSocket) {
379 this.awsRealTimeSocket.send(stringToAWSRealTime);
380 }
381 return [2 /*return*/];
382 }
383 });
384 });
385 };
386 // Waiting that subscription has been connected before trying to unsubscribe
387 AWSAppSyncRealTimeProvider.prototype._waitForSubscriptionToBeConnected = function (subscriptionId) {
388 return __awaiter(this, void 0, void 0, function () {
389 var subscriptionState;
390 var _this = this;
391 return __generator(this, function (_a) {
392 subscriptionState = this.subscriptionObserverMap.get(subscriptionId).subscriptionState;
393 // This in case unsubscribe is invoked before sending start subscription message
394 if (subscriptionState === SUBSCRIPTION_STATUS.PENDING) {
395 return [2 /*return*/, new Promise(function (res, rej) {
396 var _a = _this.subscriptionObserverMap.get(subscriptionId), observer = _a.observer, subscriptionState = _a.subscriptionState, variables = _a.variables, query = _a.query;
397 _this.subscriptionObserverMap.set(subscriptionId, {
398 observer: observer,
399 subscriptionState: subscriptionState,
400 variables: variables,
401 query: query,
402 subscriptionReadyCallback: res,
403 subscriptionFailedCallback: rej,
404 });
405 })];
406 }
407 return [2 /*return*/];
408 });
409 });
410 };
411 AWSAppSyncRealTimeProvider.prototype._sendUnsubscriptionMessage = function (subscriptionId) {
412 try {
413 if (this.awsRealTimeSocket &&
414 this.awsRealTimeSocket.readyState === WebSocket.OPEN &&
415 this.socketStatus === SOCKET_STATUS.READY) {
416 // Preparing unsubscribe message to stop receiving messages for that subscription
417 var unsubscribeMessage = {
418 id: subscriptionId,
419 type: MESSAGE_TYPES.GQL_STOP,
420 };
421 var stringToAWSRealTime = JSON.stringify(unsubscribeMessage);
422 this.awsRealTimeSocket.send(stringToAWSRealTime);
423 }
424 }
425 catch (err) {
426 // If GQL_STOP is not sent because of disconnection issue, then there is nothing the client can do
427 logger.debug({ err: err });
428 }
429 };
430 AWSAppSyncRealTimeProvider.prototype._removeSubscriptionObserver = function (subscriptionId) {
431 this.subscriptionObserverMap.delete(subscriptionId);
432 // Verifying 1000ms after removing subscription in case there are new subscription unmount/mount
433 setTimeout(this._closeSocketIfRequired.bind(this), 1000);
434 };
435 AWSAppSyncRealTimeProvider.prototype._closeSocketIfRequired = function () {
436 if (this.subscriptionObserverMap.size > 0) {
437 // Active subscriptions on the WebSocket
438 return;
439 }
440 if (!this.awsRealTimeSocket) {
441 this.socketStatus = SOCKET_STATUS.CLOSED;
442 return;
443 }
444 if (this.awsRealTimeSocket.bufferedAmount > 0) {
445 // Still data on the WebSocket
446 setTimeout(this._closeSocketIfRequired.bind(this), 1000);
447 }
448 else {
449 logger.debug('closing WebSocket...');
450 clearTimeout(this.keepAliveTimeoutId);
451 var tempSocket = this.awsRealTimeSocket;
452 // Cleaning callbacks to avoid race condition, socket still exists
453 tempSocket.onclose = undefined;
454 tempSocket.onerror = undefined;
455 tempSocket.close(1000);
456 this.awsRealTimeSocket = null;
457 this.socketStatus = SOCKET_STATUS.CLOSED;
458 }
459 };
460 AWSAppSyncRealTimeProvider.prototype._handleIncomingSubscriptionMessage = function (message) {
461 logger.debug("subscription message from AWS AppSync RealTime: " + message.data);
462 var _a = JSON.parse(message.data), _b = _a.id, id = _b === void 0 ? '' : _b, payload = _a.payload, type = _a.type;
463 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;
464 logger.debug({ id: id, observer: observer, query: query, variables: variables });
465 if (type === MESSAGE_TYPES.GQL_DATA && payload && payload.data) {
466 if (observer) {
467 observer.next(payload);
468 }
469 else {
470 logger.debug("observer not found for id: " + id);
471 }
472 return;
473 }
474 if (type === MESSAGE_TYPES.GQL_START_ACK) {
475 logger.debug("subscription ready for " + JSON.stringify({ query: query, variables: variables }));
476 if (typeof subscriptionReadyCallback === 'function') {
477 subscriptionReadyCallback();
478 }
479 clearTimeout(startAckTimeoutId);
480 dispatchApiEvent(CONTROL_MSG.SUBSCRIPTION_ACK, { query: query, variables: variables }, 'Connection established for subscription');
481 var subscriptionState = SUBSCRIPTION_STATUS.CONNECTED;
482 this.subscriptionObserverMap.set(id, {
483 observer: observer,
484 query: query,
485 variables: variables,
486 startAckTimeoutId: null,
487 subscriptionState: subscriptionState,
488 subscriptionReadyCallback: subscriptionReadyCallback,
489 subscriptionFailedCallback: subscriptionFailedCallback,
490 });
491 // TODO: emit event on hub but it requires to store the id first
492 return;
493 }
494 if (type === MESSAGE_TYPES.GQL_CONNECTION_KEEP_ALIVE) {
495 clearTimeout(this.keepAliveTimeoutId);
496 this.keepAliveTimeoutId = setTimeout(this._errorDisconnect.bind(this, CONTROL_MSG.TIMEOUT_DISCONNECT), this.keepAliveTimeout);
497 return;
498 }
499 if (type === MESSAGE_TYPES.GQL_ERROR) {
500 var subscriptionState = SUBSCRIPTION_STATUS.FAILED;
501 this.subscriptionObserverMap.set(id, {
502 observer: observer,
503 query: query,
504 variables: variables,
505 startAckTimeoutId: startAckTimeoutId,
506 subscriptionReadyCallback: subscriptionReadyCallback,
507 subscriptionFailedCallback: subscriptionFailedCallback,
508 subscriptionState: subscriptionState,
509 });
510 observer.error({
511 errors: [
512 __assign({}, new GraphQLError(CONTROL_MSG.CONNECTION_FAILED + ": " + JSON.stringify(payload))),
513 ],
514 });
515 clearTimeout(startAckTimeoutId);
516 observer.complete();
517 if (typeof subscriptionFailedCallback === 'function') {
518 subscriptionFailedCallback();
519 }
520 }
521 };
522 AWSAppSyncRealTimeProvider.prototype._errorDisconnect = function (msg) {
523 logger.debug("Disconnect error: " + msg);
524 this.subscriptionObserverMap.forEach(function (_a) {
525 var observer = _a.observer;
526 if (observer && !observer.closed) {
527 observer.error({
528 errors: [__assign({}, new GraphQLError(msg))],
529 });
530 }
531 });
532 this.subscriptionObserverMap.clear();
533 if (this.awsRealTimeSocket) {
534 this.awsRealTimeSocket.close();
535 }
536 this.socketStatus = SOCKET_STATUS.CLOSED;
537 };
538 AWSAppSyncRealTimeProvider.prototype._timeoutStartSubscriptionAck = function (subscriptionId) {
539 var _a = this.subscriptionObserverMap.get(subscriptionId) || {}, observer = _a.observer, query = _a.query, variables = _a.variables;
540 if (!observer) {
541 return;
542 }
543 this.subscriptionObserverMap.set(subscriptionId, {
544 observer: observer,
545 query: query,
546 variables: variables,
547 subscriptionState: SUBSCRIPTION_STATUS.FAILED,
548 });
549 if (observer && !observer.closed) {
550 observer.error({
551 errors: [
552 __assign({}, new GraphQLError("Subscription timeout " + JSON.stringify({
553 query: query,
554 variables: variables,
555 }))),
556 ],
557 });
558 // Cleanup will be automatically executed
559 observer.complete();
560 }
561 logger.debug('timeoutStartSubscription', JSON.stringify({ query: query, variables: variables }));
562 };
563 AWSAppSyncRealTimeProvider.prototype._initializeWebSocketConnection = function (_a) {
564 var _this = this;
565 var appSyncGraphqlEndpoint = _a.appSyncGraphqlEndpoint, authenticationType = _a.authenticationType, apiKey = _a.apiKey, region = _a.region, additionalHeaders = _a.additionalHeaders;
566 if (this.socketStatus === SOCKET_STATUS.READY) {
567 return;
568 }
569 return new Promise(function (res, rej) { return __awaiter(_this, void 0, void 0, function () {
570 var protocol, discoverableEndpoint, payloadString, headerString, _a, _b, headerQs, payloadQs, awsRealTimeUrl, err_3;
571 return __generator(this, function (_c) {
572 switch (_c.label) {
573 case 0:
574 this.promiseArray.push({ res: res, rej: rej });
575 if (!(this.socketStatus === SOCKET_STATUS.CLOSED)) return [3 /*break*/, 5];
576 _c.label = 1;
577 case 1:
578 _c.trys.push([1, 4, , 5]);
579 this.socketStatus = SOCKET_STATUS.CONNECTING;
580 protocol = this.isSSLEnabled ? 'wss://' : 'ws://';
581 discoverableEndpoint = appSyncGraphqlEndpoint
582 .replace('https://', protocol)
583 .replace('http://', protocol)
584 .replace('appsync-api', 'appsync-realtime-api')
585 .replace('gogi-beta', 'grt-beta');
586 payloadString = '{}';
587 _b = (_a = JSON).stringify;
588 return [4 /*yield*/, this._awsRealTimeHeaderBasedAuth({
589 authenticationType: authenticationType,
590 payload: payloadString,
591 canonicalUri: '/connect',
592 apiKey: apiKey,
593 appSyncGraphqlEndpoint: appSyncGraphqlEndpoint,
594 region: region,
595 additionalHeaders: additionalHeaders,
596 })];
597 case 2:
598 headerString = _b.apply(_a, [_c.sent()]);
599 headerQs = Buffer.from(headerString).toString('base64');
600 payloadQs = Buffer.from(payloadString).toString('base64');
601 awsRealTimeUrl = discoverableEndpoint + "?header=" + headerQs + "&payload=" + payloadQs;
602 return [4 /*yield*/, this._initializeRetryableHandshake({ awsRealTimeUrl: awsRealTimeUrl })];
603 case 3:
604 _c.sent();
605 this.promiseArray.forEach(function (_a) {
606 var res = _a.res;
607 logger.debug('Notifying connection successful');
608 res();
609 });
610 this.socketStatus = SOCKET_STATUS.READY;
611 this.promiseArray = [];
612 return [3 /*break*/, 5];
613 case 4:
614 err_3 = _c.sent();
615 this.promiseArray.forEach(function (_a) {
616 var rej = _a.rej;
617 return rej(err_3);
618 });
619 this.promiseArray = [];
620 if (this.awsRealTimeSocket &&
621 this.awsRealTimeSocket.readyState === WebSocket.OPEN) {
622 this.awsRealTimeSocket.close(3001);
623 }
624 this.awsRealTimeSocket = null;
625 this.socketStatus = SOCKET_STATUS.CLOSED;
626 return [3 /*break*/, 5];
627 case 5: return [2 /*return*/];
628 }
629 });
630 }); });
631 };
632 AWSAppSyncRealTimeProvider.prototype._initializeRetryableHandshake = function (_a) {
633 var awsRealTimeUrl = _a.awsRealTimeUrl;
634 return __awaiter(this, void 0, void 0, function () {
635 return __generator(this, function (_b) {
636 switch (_b.label) {
637 case 0:
638 logger.debug("Initializaling retryable Handshake");
639 return [4 /*yield*/, jitteredExponentialRetry(this._initializeHandshake.bind(this), [{ awsRealTimeUrl: awsRealTimeUrl }], MAX_DELAY_MS)];
640 case 1:
641 _b.sent();
642 return [2 /*return*/];
643 }
644 });
645 });
646 };
647 AWSAppSyncRealTimeProvider.prototype._initializeHandshake = function (_a) {
648 var awsRealTimeUrl = _a.awsRealTimeUrl;
649 return __awaiter(this, void 0, void 0, function () {
650 var err_4, errorType, errorCode;
651 var _this = this;
652 return __generator(this, function (_b) {
653 switch (_b.label) {
654 case 0:
655 logger.debug("Initializing handshake " + awsRealTimeUrl);
656 _b.label = 1;
657 case 1:
658 _b.trys.push([1, 4, , 5]);
659 return [4 /*yield*/, (function () {
660 return new Promise(function (res, rej) {
661 var newSocket = new WebSocket(awsRealTimeUrl, 'graphql-ws');
662 newSocket.onerror = function () {
663 logger.debug("WebSocket connection error");
664 };
665 newSocket.onclose = function () {
666 rej(new Error('Connection handshake error'));
667 };
668 newSocket.onopen = function () {
669 _this.awsRealTimeSocket = newSocket;
670 return res();
671 };
672 });
673 })()];
674 case 2:
675 _b.sent();
676 // Step 2: wait for ack from AWS AppSyncReaTime after sending init
677 return [4 /*yield*/, (function () {
678 return new Promise(function (res, rej) {
679 var ackOk = false;
680 _this.awsRealTimeSocket.onerror = function (error) {
681 logger.debug("WebSocket error " + JSON.stringify(error));
682 };
683 _this.awsRealTimeSocket.onclose = function (event) {
684 logger.debug("WebSocket closed " + event.reason);
685 rej(new Error(JSON.stringify(event)));
686 };
687 _this.awsRealTimeSocket.onmessage = function (message) {
688 logger.debug("subscription message from AWS AppSyncRealTime: " + message.data + " ");
689 var data = JSON.parse(message.data);
690 var type = data.type, _a = data.payload, _b = (_a === void 0 ? {} : _a).connectionTimeoutMs, connectionTimeoutMs = _b === void 0 ? DEFAULT_KEEP_ALIVE_TIMEOUT : _b;
691 if (type === MESSAGE_TYPES.GQL_CONNECTION_ACK) {
692 ackOk = true;
693 _this.keepAliveTimeout = connectionTimeoutMs;
694 _this.awsRealTimeSocket.onmessage = _this._handleIncomingSubscriptionMessage.bind(_this);
695 _this.awsRealTimeSocket.onerror = function (err) {
696 logger.debug(err);
697 _this._errorDisconnect(CONTROL_MSG.CONNECTION_CLOSED);
698 };
699 _this.awsRealTimeSocket.onclose = function (event) {
700 logger.debug("WebSocket closed " + event.reason);
701 _this._errorDisconnect(CONTROL_MSG.CONNECTION_CLOSED);
702 };
703 res('Cool, connected to AWS AppSyncRealTime');
704 return;
705 }
706 if (type === MESSAGE_TYPES.GQL_CONNECTION_ERROR) {
707 var _c = data.payload, _d = (_c === void 0 ? {} : _c).errors, _e = __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;
708 rej({ errorType: errorType, errorCode: errorCode });
709 }
710 };
711 var gqlInit = {
712 type: MESSAGE_TYPES.GQL_CONNECTION_INIT,
713 };
714 _this.awsRealTimeSocket.send(JSON.stringify(gqlInit));
715 function checkAckOk() {
716 if (!ackOk) {
717 rej(new Error("Connection timeout: ack from AWSRealTime was not received on " + CONNECTION_INIT_TIMEOUT + " ms"));
718 }
719 }
720 setTimeout(checkAckOk.bind(_this), CONNECTION_INIT_TIMEOUT);
721 });
722 })()];
723 case 3:
724 // Step 2: wait for ack from AWS AppSyncReaTime after sending init
725 _b.sent();
726 return [3 /*break*/, 5];
727 case 4:
728 err_4 = _b.sent();
729 errorType = err_4.errorType, errorCode = err_4.errorCode;
730 if (NON_RETRYABLE_CODES.includes(errorCode)) {
731 throw new NonRetryableError(errorType);
732 }
733 else if (errorType) {
734 throw new Error(errorType);
735 }
736 else {
737 throw err_4;
738 }
739 return [3 /*break*/, 5];
740 case 5: return [2 /*return*/];
741 }
742 });
743 });
744 };
745 AWSAppSyncRealTimeProvider.prototype._awsRealTimeHeaderBasedAuth = function (_a) {
746 var authenticationType = _a.authenticationType, payload = _a.payload, canonicalUri = _a.canonicalUri, appSyncGraphqlEndpoint = _a.appSyncGraphqlEndpoint, apiKey = _a.apiKey, region = _a.region, additionalHeaders = _a.additionalHeaders;
747 return __awaiter(this, void 0, void 0, function () {
748 var headerHandler, handler, host, result;
749 return __generator(this, function (_b) {
750 switch (_b.label) {
751 case 0:
752 headerHandler = {
753 API_KEY: this._awsRealTimeApiKeyHeader.bind(this),
754 AWS_IAM: this._awsRealTimeIAMHeader.bind(this),
755 OPENID_CONNECT: this._awsRealTimeOPENIDHeader.bind(this),
756 AMAZON_COGNITO_USER_POOLS: this._awsRealTimeCUPHeader.bind(this),
757 AWS_LAMBDA: this._customAuthHeader,
758 };
759 handler = headerHandler[authenticationType];
760 if (typeof handler !== 'function') {
761 logger.debug("Authentication type " + authenticationType + " not supported");
762 return [2 /*return*/, ''];
763 }
764 host = url.parse(appSyncGraphqlEndpoint).host;
765 return [4 /*yield*/, handler({
766 payload: payload,
767 canonicalUri: canonicalUri,
768 appSyncGraphqlEndpoint: appSyncGraphqlEndpoint,
769 apiKey: apiKey,
770 region: region,
771 host: host,
772 additionalHeaders: additionalHeaders,
773 })];
774 case 1:
775 result = _b.sent();
776 return [2 /*return*/, result];
777 }
778 });
779 });
780 };
781 AWSAppSyncRealTimeProvider.prototype._awsRealTimeCUPHeader = function (_a) {
782 var host = _a.host;
783 return __awaiter(this, void 0, void 0, function () {
784 var session;
785 return __generator(this, function (_b) {
786 switch (_b.label) {
787 case 0: return [4 /*yield*/, Auth.currentSession()];
788 case 1:
789 session = _b.sent();
790 return [2 /*return*/, {
791 Authorization: session.getAccessToken().getJwtToken(),
792 host: host,
793 }];
794 }
795 });
796 });
797 };
798 AWSAppSyncRealTimeProvider.prototype._awsRealTimeOPENIDHeader = function (_a) {
799 var host = _a.host;
800 return __awaiter(this, void 0, void 0, function () {
801 var token, federatedInfo, currentUser;
802 return __generator(this, function (_b) {
803 switch (_b.label) {
804 case 0: return [4 /*yield*/, Cache.getItem('federatedInfo')];
805 case 1:
806 federatedInfo = _b.sent();
807 if (!federatedInfo) return [3 /*break*/, 2];
808 token = federatedInfo.token;
809 return [3 /*break*/, 4];
810 case 2: return [4 /*yield*/, Auth.currentAuthenticatedUser()];
811 case 3:
812 currentUser = _b.sent();
813 if (currentUser) {
814 token = currentUser.token;
815 }
816 _b.label = 4;
817 case 4:
818 if (!token) {
819 throw new Error('No federated jwt');
820 }
821 return [2 /*return*/, {
822 Authorization: token,
823 host: host,
824 }];
825 }
826 });
827 });
828 };
829 AWSAppSyncRealTimeProvider.prototype._awsRealTimeApiKeyHeader = function (_a) {
830 var apiKey = _a.apiKey, host = _a.host;
831 return __awaiter(this, void 0, void 0, function () {
832 var dt, dtStr;
833 return __generator(this, function (_b) {
834 dt = new Date();
835 dtStr = dt.toISOString().replace(/[:\-]|\.\d{3}/g, '');
836 return [2 /*return*/, {
837 host: host,
838 'x-amz-date': dtStr,
839 'x-api-key': apiKey,
840 }];
841 });
842 });
843 };
844 AWSAppSyncRealTimeProvider.prototype._awsRealTimeIAMHeader = function (_a) {
845 var payload = _a.payload, canonicalUri = _a.canonicalUri, appSyncGraphqlEndpoint = _a.appSyncGraphqlEndpoint, region = _a.region;
846 return __awaiter(this, void 0, void 0, function () {
847 var endpointInfo, credentialsOK, creds, request, signed_params;
848 return __generator(this, function (_b) {
849 switch (_b.label) {
850 case 0:
851 endpointInfo = {
852 region: region,
853 service: 'appsync',
854 };
855 return [4 /*yield*/, this._ensureCredentials()];
856 case 1:
857 credentialsOK = _b.sent();
858 if (!credentialsOK) {
859 throw new Error('No credentials');
860 }
861 return [4 /*yield*/, Credentials.get().then(function (credentials) { return ({
862 secret_key: credentials.secretAccessKey,
863 access_key: credentials.accessKeyId,
864 session_token: credentials.sessionToken,
865 }); })];
866 case 2:
867 creds = _b.sent();
868 request = {
869 url: "" + appSyncGraphqlEndpoint + canonicalUri,
870 data: payload,
871 method: 'POST',
872 headers: __assign({}, AWS_APPSYNC_REALTIME_HEADERS),
873 };
874 signed_params = Signer.sign(request, creds, endpointInfo);
875 return [2 /*return*/, signed_params.headers];
876 }
877 });
878 });
879 };
880 AWSAppSyncRealTimeProvider.prototype._customAuthHeader = function (_a) {
881 var host = _a.host, additionalHeaders = _a.additionalHeaders;
882 if (!additionalHeaders.Authorization) {
883 throw new Error('No auth token specified');
884 }
885 return {
886 Authorization: additionalHeaders.Authorization,
887 host: host,
888 };
889 };
890 /**
891 * @private
892 */
893 AWSAppSyncRealTimeProvider.prototype._ensureCredentials = function () {
894 return Credentials.get()
895 .then(function (credentials) {
896 if (!credentials)
897 return false;
898 var cred = Credentials.shear(credentials);
899 logger.debug('set credentials for AWSAppSyncRealTimeProvider', cred);
900 return true;
901 })
902 .catch(function (err) {
903 logger.warn('ensure credentials error', err);
904 return false;
905 });
906 };
907 return AWSAppSyncRealTimeProvider;
908}(AbstractPubSubProvider));
909export { AWSAppSyncRealTimeProvider };
910//# sourceMappingURL=AWSAppSyncRealTimeProvider.js.map
\No newline at end of file