aws-appsync-subscription-link 3.0.11 → 3.1.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,25 @@
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="3.1.1"></a>
7
+ ## [3.1.1](https://github.com/awslabs/aws-mobile-appsync-sdk-js/compare/aws-appsync-subscription-link@3.1.0...aws-appsync-subscription-link@3.1.1) (2022-10-07)
8
+
9
+
10
+
11
+
12
+ **Note:** Version bump only for package aws-appsync-subscription-link
13
+
14
+ <a name="3.1.0"></a>
15
+ # [3.1.0](https://github.com/awslabs/aws-mobile-appsync-sdk-js/compare/aws-appsync-subscription-link@3.0.11...aws-appsync-subscription-link@3.1.0) (2022-06-24)
16
+
17
+
18
+ ### Features
19
+
20
+ * Add keepAliveTimeoutMs config for AppSync WebSocket link ([#724](https://github.com/awslabs/aws-mobile-appsync-sdk-js/issues/724)) ([74b8351](https://github.com/awslabs/aws-mobile-appsync-sdk-js/commit/74b8351))
21
+
22
+
23
+
24
+
6
25
  <a name="3.0.11"></a>
7
26
  ## [3.0.11](https://github.com/awslabs/aws-mobile-appsync-sdk-js/compare/aws-appsync-subscription-link@3.0.10...aws-appsync-subscription-link@3.0.11) (2022-05-02)
8
27
 
@@ -2,11 +2,14 @@ import { AUTH_TYPE } from "aws-appsync-auth-link";
2
2
  import { execute } from "@apollo/client/core";
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/client/core";
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/client/core";
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<Record<string, any>, Record<string, any>, Record<string, any>>>;
21
21
  private _verifySubscriptionAlreadyStarted;
@@ -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
@@ -606,12 +615,13 @@ var AppSyncRealTimeSubscriptionHandshakeLink = /** @class */ (function (_super)
606
615
  rej(new Error(JSON.stringify(event)));
607
616
  };
608
617
  _this.awsRealTimeSocket.onmessage = function (message) {
618
+ var _a;
609
619
  logger("subscription message from AWS AppSyncRealTime: " + message.data + " ");
610
620
  var data = JSON.parse(message.data);
611
- var type = data.type, _a = data.payload, _b = (_a === void 0 ? {} : _a).connectionTimeoutMs, connectionTimeoutMs = _b === void 0 ? DEFAULT_KEEP_ALIVE_TIMEOUT : _b;
621
+ var type = data.type, _b = data.payload, _c = (_b === void 0 ? {} : _b).connectionTimeoutMs, connectionTimeoutMs = _c === void 0 ? DEFAULT_KEEP_ALIVE_TIMEOUT : _c;
612
622
  if (type === types_1.MESSAGE_TYPES.GQL_CONNECTION_ACK) {
613
623
  ackOk = true;
614
- _this.keepAliveTimeout = connectionTimeoutMs;
624
+ _this.keepAliveTimeout = (_a = _this.keepAliveTimeout) !== null && _a !== void 0 ? _a : connectionTimeoutMs;
615
625
  _this.awsRealTimeSocket.onmessage = _this._handleIncomingSubscriptionMessage.bind(_this);
616
626
  _this.awsRealTimeSocket.onerror = function (err) {
617
627
  logger(err);
@@ -625,7 +635,7 @@ var AppSyncRealTimeSubscriptionHandshakeLink = /** @class */ (function (_super)
625
635
  return;
626
636
  }
627
637
  if (type === types_1.MESSAGE_TYPES.GQL_CONNECTION_ERROR) {
628
- 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;
638
+ 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;
629
639
  rej({ errorType: errorType, errorCode: errorCode });
630
640
  }
631
641
  };
@@ -3,6 +3,7 @@
3
3
  * SPDX-License-Identifier: Apache-2.0
4
4
  */
5
5
  import { ApolloLink, Observable, Operation, FetchResult } from "@apollo/client/core";
6
+ import * as ZenObservable from 'zen-observable-ts';
6
7
  declare type MqttConnectionInfo = {
7
8
  client: string;
8
9
  url: string;
@@ -14,7 +15,7 @@ export declare class SubscriptionHandshakeLink extends ApolloLink {
14
15
  private topicObservers;
15
16
  private clientObservers;
16
17
  constructor(subsInfoContextKey: any);
17
- request(operation: Operation): Observable<unknown>;
18
+ request(operation: Operation): Observable<FetchResult> | null;
18
19
  connectNewClients(connectionInfo: MqttConnectionInfo[], observer: ZenObservable.Observer<FetchResult>, operation: Operation): Promise<any[]>;
19
20
  connectNewClient(connectionInfo: MqttConnectionInfo, observer: ZenObservable.Observer<FetchResult>, selectionNames: string[]): Promise<any>;
20
21
  subscribeToTopics<T>(client: any, topics: string[], observer: ZenObservable.Observer<T>): Promise<unknown[]>;
@@ -1,4 +1,5 @@
1
1
  import { AuthOptions } from "aws-appsync-auth-link";
2
+ import * as ZenObservable from 'zen-observable-ts';
2
3
  export declare enum SUBSCRIPTION_STATUS {
3
4
  PENDING = 0,
4
5
  CONNECTED = 1,
@@ -71,6 +72,9 @@ export declare type UrlInfo = {
71
72
  auth: AuthOptions;
72
73
  region: string;
73
74
  };
75
+ export declare type AppSyncRealTimeSubscriptionConfig = UrlInfo & {
76
+ keepAliveTimeoutMs?: number;
77
+ };
74
78
  export declare type ObserverQuery = {
75
79
  observer: ZenObservable.SubscriptionObserver<any>;
76
80
  query: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "aws-appsync-subscription-link",
3
- "version": "3.0.11",
3
+ "version": "3.1.1",
4
4
  "main": "lib/index.js",
5
5
  "license": "Apache-2.0",
6
6
  "description": "AWS Mobile AppSync SDK for JavaScript",
@@ -21,7 +21,8 @@
21
21
  "dependencies": {
22
22
  "aws-appsync-auth-link": "^3.0.7",
23
23
  "debug": "2.6.9",
24
- "url": "^0.11.0"
24
+ "url": "^0.11.0",
25
+ "zen-observable-ts": "^1.2.5"
25
26
  },
26
27
  "devDependencies": {
27
28
  "@apollo/client": "^3.2.0",