aws-appsync-subscription-link 3.0.11 → 3.1.0
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 +11 -0
- package/__tests__/link/realtime-subscription-handshake-link-test.ts +163 -2
- package/lib/index.d.ts +2 -2
- package/lib/realtime-subscription-handshake-link.d.ts +3 -3
- package/lib/realtime-subscription-handshake-link.js +15 -5
- package/lib/types/index.d.ts +3 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,17 @@
|
|
|
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.0"></a>
|
|
7
|
+
# [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)
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
### Features
|
|
11
|
+
|
|
12
|
+
* 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))
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
|
|
6
17
|
<a name="3.0.11"></a>
|
|
7
18
|
## [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
19
|
|
|
@@ -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 {
|
|
4
|
-
declare function createSubscriptionHandshakeLink(args:
|
|
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 {
|
|
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 }:
|
|
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 =
|
|
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,
|
|
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
|
|
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
|
};
|
package/lib/types/index.d.ts
CHANGED
|
@@ -71,6 +71,9 @@ export declare type UrlInfo = {
|
|
|
71
71
|
auth: AuthOptions;
|
|
72
72
|
region: string;
|
|
73
73
|
};
|
|
74
|
+
export declare type AppSyncRealTimeSubscriptionConfig = UrlInfo & {
|
|
75
|
+
keepAliveTimeoutMs?: number;
|
|
76
|
+
};
|
|
74
77
|
export declare type ObserverQuery = {
|
|
75
78
|
observer: ZenObservable.SubscriptionObserver<any>;
|
|
76
79
|
query: string;
|