aws-appsync-subscription-link 2.3.0 → 2.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -3,6 +3,28 @@
3
3
  All notable changes to this project will be documented in this file.
4
4
  See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5
5
 
6
+ <a name="2.4.1"></a>
7
+ ## [2.4.1](https://github.com/awslabs/aws-mobile-appsync-sdk-js/compare/aws-appsync-subscription-link@2.4.0...aws-appsync-subscription-link@2.4.1) (2022-10-07)
8
+
9
+
10
+ ### Bug Fixes
11
+
12
+ * **data:** port over Angular fix from master ([#733](https://github.com/awslabs/aws-mobile-appsync-sdk-js/issues/733)) ([0a38f28](https://github.com/awslabs/aws-mobile-appsync-sdk-js/commit/0a38f28))
13
+
14
+
15
+
16
+
17
+ <a name="2.4.0"></a>
18
+ # [2.4.0](https://github.com/awslabs/aws-mobile-appsync-sdk-js/compare/aws-appsync-subscription-link@2.3.0...aws-appsync-subscription-link@2.4.0) (2022-06-24)
19
+
20
+
21
+ ### Features
22
+
23
+ * Add keepAliveTimeoutMs config for AppSync WebSocket link ([#725](https://github.com/awslabs/aws-mobile-appsync-sdk-js/issues/725)) ([c91e507](https://github.com/awslabs/aws-mobile-appsync-sdk-js/commit/c91e507))
24
+
25
+
26
+
27
+
6
28
  <a name="2.3.0"></a>
7
29
  # [2.3.0](https://github.com/awslabs/aws-mobile-appsync-sdk-js/compare/aws-appsync-subscription-link@2.2.7...aws-appsync-subscription-link@2.3.0) (2022-05-02)
8
30
 
@@ -2,11 +2,14 @@ import { AUTH_TYPE } from "aws-appsync-auth-link";
2
2
  import { execute } from "apollo-link";
3
3
  import gql from 'graphql-tag';
4
4
  import { AppSyncRealTimeSubscriptionHandshakeLink } from '../../src/realtime-subscription-handshake-link';
5
+ import { MESSAGE_TYPES } from "../../src/types";
6
+ import { v4 as uuid } from "uuid";
7
+ jest.mock('uuid', () => ({ v4: jest.fn() }));
5
8
 
6
9
  const query = gql`subscription { someSubscription { aField } }`
7
10
 
8
11
  class myWebSocket implements WebSocket {
9
- binaryType: BinaryType;
12
+ binaryType: BinaryType;
10
13
  bufferedAmount: number;
11
14
  extensions: string;
12
15
  onclose: (this: WebSocket, ev: CloseEvent) => any;
@@ -359,6 +362,164 @@ describe("RealTime subscription link", () => {
359
362
  }
360
363
 
361
364
  });
362
- })
365
+ });
366
+
367
+ test("Can use a custom keepAliveTimeoutMs", (done) => {
368
+ const id = "abcd-efgh-ijkl-mnop";
369
+ uuid.mockImplementationOnce(() => id);
370
+
371
+ expect.assertions(5);
372
+ jest.spyOn(Date.prototype, 'toISOString').mockImplementation(jest.fn(() => {
373
+ return "2019-11-13T18:47:04.733Z";
374
+ }));
375
+ AppSyncRealTimeSubscriptionHandshakeLink.createWebSocket = jest.fn((url, protocol) => {
376
+ expect(url).toBe('wss://apikeytest.testcustomdomain.com/graphql/realtime?header=eyJob3N0IjoiYXBpa2V5dGVzdC50ZXN0Y3VzdG9tZG9tYWluLmNvbSIsIngtYW16LWRhdGUiOiIyMDE5MTExM1QxODQ3MDRaIiwieC1hcGkta2V5IjoieHh4eHgifQ==&payload=e30=');
377
+ expect(protocol).toBe('graphql-ws');
378
+ const socket = new myWebSocket();
379
+
380
+ setTimeout(() => {
381
+ socket.close = () => {};
382
+ socket.onopen.call(socket, (undefined as unknown as Event));
383
+ socket.send = (msg: string) => {
384
+ const { type } = JSON.parse(msg);
385
+
386
+ switch (type) {
387
+ case MESSAGE_TYPES.GQL_CONNECTION_INIT:
388
+ socket.onmessage.call(socket, {
389
+ data: JSON.stringify({
390
+ type: MESSAGE_TYPES.GQL_CONNECTION_ACK,
391
+ payload: {
392
+ connectionTimeoutMs: 99999,
393
+ },
394
+ })
395
+ } as MessageEvent);
396
+ setTimeout(() => {
397
+ socket.onmessage.call(socket, {
398
+ data: JSON.stringify({
399
+ id,
400
+ type: MESSAGE_TYPES.GQL_DATA,
401
+ payload: {
402
+ data: { something: 123 },
403
+ },
404
+ })
405
+ } as MessageEvent);
406
+
407
+ }, 100);
408
+ break;
409
+ }
410
+ };
411
+ }, 100);
412
+
413
+ return socket;
414
+ });
415
+ const link = new AppSyncRealTimeSubscriptionHandshakeLink({
416
+ auth: {
417
+ type: AUTH_TYPE.API_KEY,
418
+ apiKey: 'xxxxx'
419
+ },
420
+ region: 'us-west-2',
421
+ url: 'https://apikeytest.testcustomdomain.com/graphql',
422
+ keepAliveTimeoutMs: 123456,
423
+ });
424
+
425
+ expect(link).toBeInstanceOf(AppSyncRealTimeSubscriptionHandshakeLink);
426
+ expect((link as any).keepAliveTimeout).toBe(123456);
427
+
428
+ const sub = execute(link, { query }).subscribe({
429
+ error: (err) => {
430
+ console.log(JSON.stringify(err));
431
+ fail();
432
+ },
433
+ next: (data) => {
434
+ expect((link as any).keepAliveTimeout).toBe(123456);
435
+ done();
436
+ sub.unsubscribe();
437
+ },
438
+ complete: () => {
439
+ console.log('done with this');
440
+ fail();
441
+ }
442
+
443
+ });
444
+ });
445
+
446
+ test("Uses service-provided timeout when no custom keepAliveTimeoutMs is configured", (done) => {
447
+ const id = "abcd-efgh-ijkl-mnop";
448
+ uuid.mockImplementationOnce(() => id);
449
+
450
+ expect.assertions(5);
451
+ jest.spyOn(Date.prototype, 'toISOString').mockImplementation(jest.fn(() => {
452
+ return "2019-11-13T18:47:04.733Z";
453
+ }));
454
+ AppSyncRealTimeSubscriptionHandshakeLink.createWebSocket = jest.fn((url, protocol) => {
455
+ expect(url).toBe('wss://apikeytest.testcustomdomain.com/graphql/realtime?header=eyJob3N0IjoiYXBpa2V5dGVzdC50ZXN0Y3VzdG9tZG9tYWluLmNvbSIsIngtYW16LWRhdGUiOiIyMDE5MTExM1QxODQ3MDRaIiwieC1hcGkta2V5IjoieHh4eHgifQ==&payload=e30=');
456
+ expect(protocol).toBe('graphql-ws');
457
+ const socket = new myWebSocket();
458
+
459
+ setTimeout(() => {
460
+ socket.close = () => {};
461
+ socket.onopen.call(socket, (undefined as unknown as Event));
462
+ socket.send = (msg: string) => {
463
+ const { type } = JSON.parse(msg);
464
+
465
+ switch (type) {
466
+ case MESSAGE_TYPES.GQL_CONNECTION_INIT:
467
+ socket.onmessage.call(socket, {
468
+ data: JSON.stringify({
469
+ type: MESSAGE_TYPES.GQL_CONNECTION_ACK,
470
+ payload: {
471
+ connectionTimeoutMs: 99999,
472
+ },
473
+ })
474
+ } as MessageEvent);
475
+ setTimeout(() => {
476
+ socket.onmessage.call(socket, {
477
+ data: JSON.stringify({
478
+ id,
479
+ type: MESSAGE_TYPES.GQL_DATA,
480
+ payload: {
481
+ data: { something: 123 },
482
+ },
483
+ })
484
+ } as MessageEvent);
485
+
486
+ }, 100);
487
+ break;
488
+ }
489
+ };
490
+ }, 100);
491
+
492
+ return socket;
493
+ });
494
+ const link = new AppSyncRealTimeSubscriptionHandshakeLink({
495
+ auth: {
496
+ type: AUTH_TYPE.API_KEY,
497
+ apiKey: 'xxxxx'
498
+ },
499
+ region: 'us-west-2',
500
+ url: 'https://apikeytest.testcustomdomain.com/graphql',
501
+ });
502
+
503
+ expect(link).toBeInstanceOf(AppSyncRealTimeSubscriptionHandshakeLink);
504
+ expect((link as any).keepAliveTimeout).toBeUndefined();
505
+
506
+ const sub = execute(link, { query }).subscribe({
507
+ error: (err) => {
508
+ console.log(JSON.stringify(err));
509
+ fail();
510
+ },
511
+ next: (data) => {
512
+ expect((link as any).keepAliveTimeout).toBe(99999);
513
+ done();
514
+ sub.unsubscribe();
515
+ },
516
+ complete: () => {
517
+ console.log('done with this');
518
+ fail();
519
+ }
520
+
521
+ });
522
+ });
523
+
363
524
 
364
525
  });
package/lib/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { CONTROL_EVENTS_KEY } from "./subscription-handshake-link";
2
2
  import { ApolloLink } from "apollo-link";
3
- import { UrlInfo } from "./types";
4
- declare function createSubscriptionHandshakeLink(args: UrlInfo, resultsFetcherLink?: ApolloLink): ApolloLink;
3
+ import { AppSyncRealTimeSubscriptionConfig } from "./types";
4
+ declare function createSubscriptionHandshakeLink(args: AppSyncRealTimeSubscriptionConfig, resultsFetcherLink?: ApolloLink): ApolloLink;
5
5
  declare function createSubscriptionHandshakeLink(url: string, resultsFetcherLink?: ApolloLink): ApolloLink;
6
6
  export { CONTROL_EVENTS_KEY, createSubscriptionHandshakeLink };
@@ -3,7 +3,7 @@
3
3
  * SPDX-License-Identifier: Apache-2.0
4
4
  */
5
5
  import { ApolloLink, Observable, Operation, FetchResult } from "apollo-link";
6
- import { UrlInfo } from "./types";
6
+ import { AppSyncRealTimeSubscriptionConfig } from "./types";
7
7
  export declare const CONTROL_EVENTS_KEY = "@@controlEvents";
8
8
  export declare class AppSyncRealTimeSubscriptionHandshakeLink extends ApolloLink {
9
9
  private url;
@@ -12,10 +12,10 @@ export declare class AppSyncRealTimeSubscriptionHandshakeLink extends ApolloLink
12
12
  private awsRealTimeSocket;
13
13
  private socketStatus;
14
14
  private keepAliveTimeoutId;
15
- private keepAliveTimeout;
15
+ private keepAliveTimeout?;
16
16
  private subscriptionObserverMap;
17
17
  private promiseArray;
18
- constructor({ url: theUrl, region: theRegion, auth: theAuth }: UrlInfo);
18
+ constructor({ url: theUrl, region: theRegion, auth: theAuth, keepAliveTimeoutMs }: AppSyncRealTimeSubscriptionConfig);
19
19
  private isCustomDomain;
20
20
  request(operation: Operation): Observable<FetchResult<{
21
21
  [key: string]: any;
@@ -89,6 +89,10 @@ var CONNECTION_INIT_TIMEOUT = 15000;
89
89
  * Time in milliseconds to wait for GQL_START_ACK message
90
90
  */
91
91
  var START_ACK_TIMEOUT = 15000;
92
+ /**
93
+ * Frequency in milliseconds in which the server sends GQL_CONNECTION_KEEP_ALIVE messages
94
+ */
95
+ var SERVER_KEEP_ALIVE_TIMEOUT = 1 * 60 * 1000;
92
96
  /**
93
97
  * Default Time in milliseconds to wait for GQL_CONNECTION_KEEP_ALIVE message
94
98
  */
@@ -98,15 +102,20 @@ var customDomainPath = '/realtime';
98
102
  var AppSyncRealTimeSubscriptionHandshakeLink = /** @class */ (function (_super) {
99
103
  __extends(AppSyncRealTimeSubscriptionHandshakeLink, _super);
100
104
  function AppSyncRealTimeSubscriptionHandshakeLink(_a) {
101
- var theUrl = _a.url, theRegion = _a.region, theAuth = _a.auth;
105
+ var theUrl = _a.url, theRegion = _a.region, theAuth = _a.auth, keepAliveTimeoutMs = _a.keepAliveTimeoutMs;
102
106
  var _this = _super.call(this) || this;
103
107
  _this.socketStatus = types_1.SOCKET_STATUS.CLOSED;
104
- _this.keepAliveTimeout = DEFAULT_KEEP_ALIVE_TIMEOUT;
108
+ _this.keepAliveTimeout = undefined;
105
109
  _this.subscriptionObserverMap = new Map();
106
110
  _this.promiseArray = [];
107
111
  _this.url = theUrl;
108
112
  _this.region = theRegion;
109
113
  _this.auth = theAuth;
114
+ _this.keepAliveTimeout = keepAliveTimeoutMs;
115
+ if (_this.keepAliveTimeout < SERVER_KEEP_ALIVE_TIMEOUT) {
116
+ var configName = 'keepAliveTimeoutMs';
117
+ throw new Error(configName + " must be greater than or equal to " + SERVER_KEEP_ALIVE_TIMEOUT + " (" + _this.keepAliveTimeout + " used).");
118
+ }
110
119
  return _this;
111
120
  }
112
121
  // Check if url matches standard domain pattern
@@ -380,7 +389,7 @@ var AppSyncRealTimeSubscriptionHandshakeLink = /** @class */ (function (_super)
380
389
  region: region,
381
390
  credentials: credentials,
382
391
  token: token,
383
- graphql_headers: function () { },
392
+ graphql_headers: function () { }
384
393
  })];
385
394
  case 2:
386
395
  headerString = _b.apply(_a, [_c.sent()]);
@@ -607,14 +616,14 @@ var AppSyncRealTimeSubscriptionHandshakeLink = /** @class */ (function (_super)
607
616
  rej(new Error(JSON.stringify(event)));
608
617
  };
609
618
  _this.awsRealTimeSocket.onmessage = function (message) {
619
+ var _a;
610
620
  logger("subscription message from AWS AppSyncRealTime: " + message.data + " ");
611
621
  var data = JSON.parse(message.data);
612
- var type = data.type, _a = data.payload, _b = (_a === void 0 ? {} : _a).connectionTimeoutMs, connectionTimeoutMs = _b === void 0 ? DEFAULT_KEEP_ALIVE_TIMEOUT : _b;
622
+ var type = data.type, _b = data.payload, _c = (_b === void 0 ? {} : _b).connectionTimeoutMs, connectionTimeoutMs = _c === void 0 ? DEFAULT_KEEP_ALIVE_TIMEOUT : _c;
613
623
  if (type === types_1.MESSAGE_TYPES.GQL_CONNECTION_ACK) {
614
624
  ackOk = true;
615
- _this.keepAliveTimeout = connectionTimeoutMs;
616
- _this.awsRealTimeSocket.onmessage =
617
- _this._handleIncomingSubscriptionMessage.bind(_this);
625
+ _this.keepAliveTimeout = (_a = _this.keepAliveTimeout) !== null && _a !== void 0 ? _a : connectionTimeoutMs;
626
+ _this.awsRealTimeSocket.onmessage = _this._handleIncomingSubscriptionMessage.bind(_this);
618
627
  _this.awsRealTimeSocket.onerror = function (err) {
619
628
  logger(err);
620
629
  _this._errorDisconnect(types_1.CONTROL_MSG.CONNECTION_CLOSED);
@@ -627,7 +636,7 @@ var AppSyncRealTimeSubscriptionHandshakeLink = /** @class */ (function (_super)
627
636
  return;
628
637
  }
629
638
  if (type === types_1.MESSAGE_TYPES.GQL_CONNECTION_ERROR) {
630
- var _c = data.payload, _d = (_c === void 0 ? {} : _c).errors, _e = (_d === void 0 ? [] : _d)[0], _f = _e === void 0 ? {} : _e, _g = _f.errorType, errorType = _g === void 0 ? "" : _g, _h = _f.errorCode, errorCode = _h === void 0 ? 0 : _h;
639
+ var _d = data.payload, _e = (_d === void 0 ? {} : _d).errors, _f = (_e === void 0 ? [] : _e)[0], _g = _f === void 0 ? {} : _f, _h = _g.errorType, errorType = _h === void 0 ? "" : _h, _j = _g.errorCode, errorCode = _j === void 0 ? 0 : _j;
631
640
  rej({ errorType: errorType, errorCode: errorCode });
632
641
  }
633
642
  };
@@ -1,9 +1,9 @@
1
- /// <reference types="zen-observable" />
2
1
  /*!
3
2
  * Copyright 2017-2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
4
3
  * SPDX-License-Identifier: Apache-2.0
5
4
  */
6
5
  import { ApolloLink, Observable, Operation, FetchResult } from "apollo-link";
6
+ import * as ZenObservable from 'zen-observable-ts';
7
7
  declare type MqttConnectionInfo = {
8
8
  client: string;
9
9
  url: string;
@@ -15,7 +15,7 @@ export declare class SubscriptionHandshakeLink extends ApolloLink {
15
15
  private topicObservers;
16
16
  private clientObservers;
17
17
  constructor(subsInfoContextKey: any);
18
- request(operation: Operation): Observable<unknown>;
18
+ request(operation: Operation): Observable<FetchResult> | null;
19
19
  connectNewClients(connectionInfo: MqttConnectionInfo[], observer: ZenObservable.Observer<FetchResult>, operation: Operation): Promise<any[]>;
20
20
  connectNewClient(connectionInfo: MqttConnectionInfo, observer: ZenObservable.Observer<FetchResult>, selectionNames: string[]): Promise<any>;
21
21
  subscribeToTopics<T>(client: any, topics: string[], observer: ZenObservable.Observer<T>): Promise<unknown[]>;
@@ -1,5 +1,5 @@
1
- /// <reference types="zen-observable" />
2
1
  import { AuthOptions } from "aws-appsync-auth-link";
2
+ import * as ZenObservable from 'zen-observable-ts';
3
3
  export declare enum SUBSCRIPTION_STATUS {
4
4
  PENDING = 0,
5
5
  CONNECTED = 1,
@@ -72,6 +72,9 @@ export declare type UrlInfo = {
72
72
  auth: AuthOptions;
73
73
  region: string;
74
74
  };
75
+ export declare type AppSyncRealTimeSubscriptionConfig = UrlInfo & {
76
+ keepAliveTimeoutMs?: number;
77
+ };
75
78
  export declare type ObserverQuery = {
76
79
  observer: ZenObservable.SubscriptionObserver<any>;
77
80
  query: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "aws-appsync-subscription-link",
3
- "version": "2.3.0",
3
+ "version": "2.4.1",
4
4
  "main": "lib/index.js",
5
5
  "license": "Apache-2.0",
6
6
  "description": "AWS Mobile AppSync SDK for JavaScript",
@@ -25,7 +25,8 @@
25
25
  "apollo-link-retry": "2.2.7",
26
26
  "aws-appsync-auth-link": "^2.0.8",
27
27
  "debug": "2.6.9",
28
- "url": "^0.11.0"
28
+ "url": "^0.11.0",
29
+ "zen-observable-ts": "^1.2.5"
29
30
  },
30
31
  "devDependencies": {
31
32
  "@redux-offline/redux-offline": "2.5.2-native.0",