aws-appsync-subscription-link 3.0.9 → 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 CHANGED
@@ -3,6 +3,36 @@
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
+
17
+ <a name="3.0.11"></a>
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)
19
+
20
+
21
+
22
+
23
+ **Note:** Version bump only for package aws-appsync-subscription-link
24
+
25
+ <a name="3.0.10"></a>
26
+ ## [3.0.10](https://github.com/awslabs/aws-mobile-appsync-sdk-js/compare/aws-appsync-subscription-link@3.0.9...aws-appsync-subscription-link@3.0.10) (2022-03-04)
27
+
28
+
29
+ ### Bug Fixes
30
+
31
+ * Port over Amplify fix for subscription race conditions ([#509](https://github.com/awslabs/aws-mobile-appsync-sdk-js/issues/509)) ([#704](https://github.com/awslabs/aws-mobile-appsync-sdk-js/issues/704)) ([92b50c4](https://github.com/awslabs/aws-mobile-appsync-sdk-js/commit/92b50c4))
32
+
33
+
34
+
35
+
6
36
  <a name="3.0.9"></a>
7
37
  ## [3.0.9](https://github.com/awslabs/aws-mobile-appsync-sdk-js/compare/aws-appsync-subscription-link@3.0.8...aws-appsync-subscription-link@3.0.9) (2021-09-24)
8
38
 
@@ -2,11 +2,15 @@ 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; bufferedAmount: number;
12
+ binaryType: BinaryType;
13
+ bufferedAmount: number;
10
14
  extensions: string;
11
15
  onclose: (this: WebSocket, ev: CloseEvent) => any;
12
16
  onerror: (this: WebSocket, ev: Event) => any;
@@ -49,8 +53,22 @@ describe("RealTime subscription link", () => {
49
53
  type: AUTH_TYPE.API_KEY,
50
54
  apiKey: 'xxxxx'
51
55
  },
52
- region: 'us-east-1',
53
- url: 'https://xxxxx.appsync-api.amazonaws.com/graphql'
56
+ region: 'us-west-2',
57
+ url: 'https://firsttesturl12345678901234.appsync-api.us-west-2.amazonaws.com/graphql'
58
+ });
59
+
60
+ expect(link).toBeInstanceOf(AppSyncRealTimeSubscriptionHandshakeLink);
61
+ });
62
+
63
+ test("Can instantiate link with custom domain", () => {
64
+ expect.assertions(1);
65
+ const link = new AppSyncRealTimeSubscriptionHandshakeLink({
66
+ auth: {
67
+ type: AUTH_TYPE.API_KEY,
68
+ apiKey: 'xxxxx'
69
+ },
70
+ region: 'us-west-2',
71
+ url: 'https://test1.testcustomdomain.com/graphql'
54
72
  });
55
73
 
56
74
  expect(link).toBeInstanceOf(AppSyncRealTimeSubscriptionHandshakeLink);
@@ -62,7 +80,7 @@ describe("RealTime subscription link", () => {
62
80
  return "2019-11-13T18:47:04.733Z";
63
81
  }));
64
82
  AppSyncRealTimeSubscriptionHandshakeLink.createWebSocket = jest.fn((url, protocol) => {
65
- expect(url).toBe('wss://xxxxx.appsync-realtime-api.amazonaws.com/graphql?header=eyJob3N0IjoieHh4eHguYXBwc3luYy1hcGkuYW1hem9uYXdzLmNvbSIsIngtYW16LWRhdGUiOiIyMDE5MTExM1QxODQ3MDRaIiwieC1hcGkta2V5IjoieHh4eHgifQ==&payload=e30=');
83
+ expect(url).toBe('wss://apikeytesturl1234567890123.appsync-realtime-api.us-west-2.amazonaws.com/graphql?header=eyJob3N0IjoiYXBpa2V5dGVzdHVybDEyMzQ1Njc4OTAxMjMuYXBwc3luYy1hcGkudXMtd2VzdC0yLmFtYXpvbmF3cy5jb20iLCJ4LWFtei1kYXRlIjoiMjAxOTExMTNUMTg0NzA0WiIsIngtYXBpLWtleSI6Inh4eHh4In0=&payload=e30=');
66
84
  expect(protocol).toBe('graphql-ws');
67
85
  done();
68
86
  return new myWebSocket();
@@ -72,13 +90,50 @@ describe("RealTime subscription link", () => {
72
90
  type: AUTH_TYPE.API_KEY,
73
91
  apiKey: 'xxxxx'
74
92
  },
75
- region: 'us-east-1',
76
- url: 'https://xxxxx.appsync-api.amazonaws.com/graphql'
93
+ region: 'us-west-2',
94
+ url: 'https://apikeytesturl1234567890123.appsync-api.us-west-2.amazonaws.com/graphql'
77
95
  });
78
96
 
79
97
  execute(link, { query }).subscribe({
80
98
  error: (err) => {
81
- console.log({ err });
99
+ console.log(JSON.stringify(err));
100
+ fail;
101
+ },
102
+ next: (data) => {
103
+ console.log({ data });
104
+ done();
105
+ },
106
+ complete: () => {
107
+ console.log('done with this');
108
+ done();
109
+ }
110
+
111
+ });
112
+ });
113
+
114
+ test("Initialize WebSocket correctly for API KEY with custom domain", (done) => {
115
+ expect.assertions(2);
116
+ jest.spyOn(Date.prototype, 'toISOString').mockImplementation(jest.fn(() => {
117
+ return "2019-11-13T18:47:04.733Z";
118
+ }));
119
+ AppSyncRealTimeSubscriptionHandshakeLink.createWebSocket = jest.fn((url, protocol) => {
120
+ expect(url).toBe('wss://apikeytest.testcustomdomain.com/graphql/realtime?header=eyJob3N0IjoiYXBpa2V5dGVzdC50ZXN0Y3VzdG9tZG9tYWluLmNvbSIsIngtYW16LWRhdGUiOiIyMDE5MTExM1QxODQ3MDRaIiwieC1hcGkta2V5IjoieHh4eHgifQ==&payload=e30=');
121
+ expect(protocol).toBe('graphql-ws');
122
+ done();
123
+ return new myWebSocket();
124
+ });
125
+ const link = new AppSyncRealTimeSubscriptionHandshakeLink({
126
+ auth: {
127
+ type: AUTH_TYPE.API_KEY,
128
+ apiKey: 'xxxxx'
129
+ },
130
+ region: 'us-west-2',
131
+ url: 'https://apikeytest.testcustomdomain.com/graphql'
132
+ });
133
+
134
+ execute(link, { query }).subscribe({
135
+ error: (err) => {
136
+ console.log(JSON.stringify(err));
82
137
  fail;
83
138
  },
84
139
  next: (data) => {
@@ -99,7 +154,7 @@ describe("RealTime subscription link", () => {
99
154
  return "2019-11-13T18:47:04.733Z";
100
155
  }));
101
156
  AppSyncRealTimeSubscriptionHandshakeLink.createWebSocket = jest.fn((url, protocol) => {
102
- expect(url).toBe('wss://xxxxx.appsync-realtime-api.amazonaws.com/graphql?header=eyJBdXRob3JpemF0aW9uIjoidG9rZW4iLCJob3N0IjoieHh4eHguYXBwc3luYy1hcGkuYW1hem9uYXdzLmNvbSJ9&payload=e30=');
157
+ expect(url).toBe('wss://cognitouserpooltesturl1234.appsync-realtime-api.us-west-2.amazonaws.com/graphql?header=eyJBdXRob3JpemF0aW9uIjoidG9rZW4iLCJob3N0IjoiY29nbml0b3VzZXJwb29sdGVzdHVybDEyMzQuYXBwc3luYy1hcGkudXMtd2VzdC0yLmFtYXpvbmF3cy5jb20ifQ==&payload=e30=');
103
158
  expect(protocol).toBe('graphql-ws');
104
159
  done();
105
160
  return new myWebSocket();
@@ -109,13 +164,50 @@ describe("RealTime subscription link", () => {
109
164
  type: AUTH_TYPE.AMAZON_COGNITO_USER_POOLS,
110
165
  jwtToken: 'token'
111
166
  },
112
- region: 'us-east-1',
113
- url: 'https://xxxxx.appsync-api.amazonaws.com/graphql'
167
+ region: 'us-west-2',
168
+ url: 'https://cognitouserpooltesturl1234.appsync-api.us-west-2.amazonaws.com/graphql'
114
169
  });
115
170
 
116
171
  execute(link, { query }).subscribe({
117
172
  error: (err) => {
118
- console.log({ err });
173
+ console.log(JSON.stringify(err));
174
+ fail;
175
+ },
176
+ next: (data) => {
177
+ console.log({ data });
178
+ done();
179
+ },
180
+ complete: () => {
181
+ console.log('done with this');
182
+ done();
183
+ }
184
+
185
+ });
186
+ });
187
+
188
+ test("Initialize WebSocket correctly for COGNITO USER POOLS with custom domain", (done) => {
189
+ expect.assertions(2);
190
+ jest.spyOn(Date.prototype, 'toISOString').mockImplementation(jest.fn(() => {
191
+ return "2019-11-13T18:47:04.733Z";
192
+ }));
193
+ AppSyncRealTimeSubscriptionHandshakeLink.createWebSocket = jest.fn((url, protocol) => {
194
+ expect(url).toBe('wss://cognitouserpools.testcustomdomain.com/graphql/realtime?header=eyJBdXRob3JpemF0aW9uIjoidG9rZW4iLCJob3N0IjoiY29nbml0b3VzZXJwb29scy50ZXN0Y3VzdG9tZG9tYWluLmNvbSJ9&payload=e30=');
195
+ expect(protocol).toBe('graphql-ws');
196
+ done();
197
+ return new myWebSocket();
198
+ });
199
+ const link = new AppSyncRealTimeSubscriptionHandshakeLink({
200
+ auth: {
201
+ type: AUTH_TYPE.AMAZON_COGNITO_USER_POOLS,
202
+ jwtToken: 'token'
203
+ },
204
+ region: 'us-west-2',
205
+ url: 'https://cognitouserpools.testcustomdomain.com/graphql'
206
+ });
207
+
208
+ execute(link, { query }).subscribe({
209
+ error: (err) => {
210
+ console.log(JSON.stringify(err));
119
211
  fail;
120
212
  },
121
213
  next: (data) => {
@@ -136,7 +228,44 @@ describe("RealTime subscription link", () => {
136
228
  return "2019-11-13T18:47:04.733Z";
137
229
  }));
138
230
  AppSyncRealTimeSubscriptionHandshakeLink.createWebSocket = jest.fn((url, protocol) => {
139
- expect(url).toBe('wss://xxxxx.appsync-realtime-api.amazonaws.com/graphql?header=eyJBdXRob3JpemF0aW9uIjoidG9rZW4iLCJob3N0IjoieHh4eHguYXBwc3luYy1hcGkuYW1hem9uYXdzLmNvbSJ9&payload=e30=');
231
+ expect(url).toBe('wss://openidconnecttesturl123456.appsync-realtime-api.us-west-2.amazonaws.com/graphql?header=eyJBdXRob3JpemF0aW9uIjoidG9rZW4iLCJob3N0Ijoib3BlbmlkY29ubmVjdHRlc3R1cmwxMjM0NTYuYXBwc3luYy1hcGkudXMtd2VzdC0yLmFtYXpvbmF3cy5jb20ifQ==&payload=e30=');
232
+ expect(protocol).toBe('graphql-ws');
233
+ done();
234
+ return new myWebSocket();
235
+ });
236
+ const link = new AppSyncRealTimeSubscriptionHandshakeLink({
237
+ auth: {
238
+ type: AUTH_TYPE.OPENID_CONNECT,
239
+ jwtToken: 'token'
240
+ },
241
+ region: 'us-west-2',
242
+ url: 'https://openidconnecttesturl123456.appsync-api.us-west-2.amazonaws.com/graphql'
243
+ });
244
+
245
+ execute(link, { query }).subscribe({
246
+ error: (err) => {
247
+ console.log(JSON.stringify(err));
248
+ fail;
249
+ },
250
+ next: (data) => {
251
+ console.log({ data });
252
+ done();
253
+ },
254
+ complete: () => {
255
+ console.log('done with this');
256
+ done();
257
+ }
258
+
259
+ });
260
+ });
261
+
262
+ test("Initialize WebSocket correctly for OPENID_CONNECT with custom domain", (done) => {
263
+ expect.assertions(2);
264
+ jest.spyOn(Date.prototype, 'toISOString').mockImplementation(jest.fn(() => {
265
+ return "2019-11-13T18:47:04.733Z";
266
+ }));
267
+ AppSyncRealTimeSubscriptionHandshakeLink.createWebSocket = jest.fn((url, protocol) => {
268
+ expect(url).toBe('wss://openidconnecttesturl.testcustomdomain.com/graphql/realtime?header=eyJBdXRob3JpemF0aW9uIjoidG9rZW4iLCJob3N0Ijoib3BlbmlkY29ubmVjdHRlc3R1cmwudGVzdGN1c3RvbWRvbWFpbi5jb20ifQ==&payload=e30=');
140
269
  expect(protocol).toBe('graphql-ws');
141
270
  done();
142
271
  return new myWebSocket();
@@ -146,13 +275,13 @@ describe("RealTime subscription link", () => {
146
275
  type: AUTH_TYPE.OPENID_CONNECT,
147
276
  jwtToken: 'token'
148
277
  },
149
- region: 'us-east-1',
150
- url: 'https://xxxxx.appsync-api.amazonaws.com/graphql'
278
+ region: 'us-west-2',
279
+ url: 'https://openidconnecttesturl.testcustomdomain.com/graphql'
151
280
  });
152
281
 
153
282
  execute(link, { query }).subscribe({
154
283
  error: (err) => {
155
- console.log({ err });
284
+ console.log(JSON.stringify(err));
156
285
  fail;
157
286
  },
158
287
  next: (data) => {
@@ -173,7 +302,7 @@ describe("RealTime subscription link", () => {
173
302
  return "2019-11-13T18:47:04.733Z";
174
303
  }));
175
304
  AppSyncRealTimeSubscriptionHandshakeLink.createWebSocket = jest.fn((url, protocol) => {
176
- expect(url).toBe('wss://xxxxx.appsync-realtime-api.amazonaws.com/graphql?header=eyJBdXRob3JpemF0aW9uIjoidG9rZW4iLCJob3N0IjoieHh4eHguYXBwc3luYy1hcGkuYW1hem9uYXdzLmNvbSJ9&payload=e30=');
305
+ expect(url).toBe('wss://awslambdatesturl1234567890.appsync-realtime-api.us-west-2.amazonaws.com/graphql?header=eyJBdXRob3JpemF0aW9uIjoidG9rZW4iLCJob3N0IjoiYXdzbGFtYmRhdGVzdHVybDEyMzQ1Njc4OTAuYXBwc3luYy1hcGkudXMtd2VzdC0yLmFtYXpvbmF3cy5jb20ifQ==&payload=e30=');
177
306
  expect(protocol).toBe('graphql-ws');
178
307
  done();
179
308
  return new myWebSocket();
@@ -183,8 +312,8 @@ describe("RealTime subscription link", () => {
183
312
  type: AUTH_TYPE.AWS_LAMBDA,
184
313
  token: 'token'
185
314
  },
186
- region: 'us-east-1',
187
- url: 'https://xxxxx.appsync-api.amazonaws.com/graphql'
315
+ region: 'us-west-2',
316
+ url: 'https://awslambdatesturl1234567890.appsync-api.us-west-2.amazonaws.com/graphql'
188
317
  });
189
318
 
190
319
  execute(link, { query }).subscribe({
@@ -201,4 +330,196 @@ describe("RealTime subscription link", () => {
201
330
  });
202
331
  })
203
332
 
333
+ test('Initialize WebSocket correctly for AWS_LAMBDA with custom domain', (done) => {
334
+ expect.assertions(2);
335
+ jest.spyOn(Date.prototype, 'toISOString').mockImplementation(jest.fn(() => {
336
+ return "2019-11-13T18:47:04.733Z";
337
+ }));
338
+ AppSyncRealTimeSubscriptionHandshakeLink.createWebSocket = jest.fn((url, protocol) => {
339
+ expect(url).toBe('wss://awslambdatesturl.testcustomdomain.com/graphql/realtime?header=eyJBdXRob3JpemF0aW9uIjoidG9rZW4iLCJob3N0IjoiYXdzbGFtYmRhdGVzdHVybC50ZXN0Y3VzdG9tZG9tYWluLmNvbSJ9&payload=e30=');
340
+ expect(protocol).toBe('graphql-ws');
341
+ done();
342
+ return new myWebSocket();
343
+ });
344
+ const link = new AppSyncRealTimeSubscriptionHandshakeLink({
345
+ auth: {
346
+ type: AUTH_TYPE.AWS_LAMBDA,
347
+ token: 'token'
348
+ },
349
+ region: 'us-west-2',
350
+ url: 'https://awslambdatesturl.testcustomdomain.com/graphql'
351
+ });
352
+
353
+ execute(link, { query }).subscribe({
354
+ error: (err) => {
355
+ fail;
356
+ },
357
+ next: (data) => {
358
+ done();
359
+ },
360
+ complete: () => {
361
+ done();
362
+ }
363
+
364
+ });
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
+
524
+
204
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 };
@@ -1,9 +1,9 @@
1
1
  /*!
2
- * Copyright 2017-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
2
+ * Copyright 2017-2021 Amazon.com, Inc. or its affiliates. All Rights Reserved.
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,17 +12,16 @@ 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);
19
- request(operation: Operation): Observable<FetchResult<{
20
- [key: string]: any;
21
- }, Record<string, any>, Record<string, any>>>;
18
+ constructor({ url: theUrl, region: theRegion, auth: theAuth, keepAliveTimeoutMs }: AppSyncRealTimeSubscriptionConfig);
19
+ private isCustomDomain;
20
+ request(operation: Operation): Observable<FetchResult<Record<string, any>, Record<string, any>, Record<string, any>>>;
22
21
  private _verifySubscriptionAlreadyStarted;
23
22
  private _sendUnsubscriptionMessage;
24
23
  private _removeSubscriptionObserver;
25
- private _closeSocketWhenFlushed;
24
+ private _closeSocketIfRequired;
26
25
  private _startSubscriptionWithAWSAppSyncRealTime;
27
26
  private _initializeWebSocketConnection;
28
27
  private _awsRealTimeHeaderBasedAuth;
@@ -35,5 +34,4 @@ export declare class AppSyncRealTimeSubscriptionHandshakeLink extends ApolloLink
35
34
  private _errorDisconnect;
36
35
  private _timeoutStartSubscriptionAck;
37
36
  static createWebSocket(awsRealTimeUrl: string, protocol: string): WebSocket;
38
- private static _discoverAppSyncRealTimeEndpoint;
39
37
  }
@@ -61,7 +61,7 @@ var __generator = (this && this.__generator) || function (thisArg, body) {
61
61
  };
62
62
  Object.defineProperty(exports, "__esModule", { value: true });
63
63
  /*!
64
- * Copyright 2017-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
64
+ * Copyright 2017-2021 Amazon.com, Inc. or its affiliates. All Rights Reserved.
65
65
  * SPDX-License-Identifier: Apache-2.0
66
66
  */
67
67
  var core_1 = require("@apollo/client/core");
@@ -89,24 +89,39 @@ 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
  */
95
99
  var DEFAULT_KEEP_ALIVE_TIMEOUT = 5 * 60 * 1000;
100
+ var standardDomainPattern = /^https:\/\/\w{26}\.appsync\-api\.\w{2}(?:(?:\-\w{2,})+)\-\d\.amazonaws.com\/graphql$/i;
101
+ var customDomainPath = '/realtime';
96
102
  var AppSyncRealTimeSubscriptionHandshakeLink = /** @class */ (function (_super) {
97
103
  __extends(AppSyncRealTimeSubscriptionHandshakeLink, _super);
98
104
  function AppSyncRealTimeSubscriptionHandshakeLink(_a) {
99
- var theUrl = _a.url, theRegion = _a.region, theAuth = _a.auth;
105
+ var theUrl = _a.url, theRegion = _a.region, theAuth = _a.auth, keepAliveTimeoutMs = _a.keepAliveTimeoutMs;
100
106
  var _this = _super.call(this) || this;
101
107
  _this.socketStatus = types_1.SOCKET_STATUS.CLOSED;
102
- _this.keepAliveTimeout = DEFAULT_KEEP_ALIVE_TIMEOUT;
108
+ _this.keepAliveTimeout = undefined;
103
109
  _this.subscriptionObserverMap = new Map();
104
110
  _this.promiseArray = [];
105
111
  _this.url = theUrl;
106
112
  _this.region = theRegion;
107
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
+ }
108
119
  return _this;
109
120
  }
121
+ // Check if url matches standard domain pattern
122
+ AppSyncRealTimeSubscriptionHandshakeLink.prototype.isCustomDomain = function (url) {
123
+ return url.match(standardDomainPattern) === null;
124
+ };
110
125
  AppSyncRealTimeSubscriptionHandshakeLink.prototype.request = function (operation) {
111
126
  var _a;
112
127
  var _this = this;
@@ -220,22 +235,25 @@ var AppSyncRealTimeSubscriptionHandshakeLink = /** @class */ (function (_super)
220
235
  };
221
236
  AppSyncRealTimeSubscriptionHandshakeLink.prototype._removeSubscriptionObserver = function (subscriptionId) {
222
237
  this.subscriptionObserverMap.delete(subscriptionId);
223
- if (this.subscriptionObserverMap.size === 0) {
224
- // Socket could be sending data to unsubscribe so is required to wait until is flushed
225
- this._closeSocketWhenFlushed();
226
- }
238
+ // Verifying for 1000ms after removing subscription in case there are new subscriptions on mount / unmount
239
+ setTimeout(this._closeSocketIfRequired.bind(this), 1000);
227
240
  };
228
- AppSyncRealTimeSubscriptionHandshakeLink.prototype._closeSocketWhenFlushed = function () {
229
- logger("closing WebSocket...");
230
- clearTimeout(this.keepAliveTimeoutId);
241
+ AppSyncRealTimeSubscriptionHandshakeLink.prototype._closeSocketIfRequired = function () {
242
+ if (this.subscriptionObserverMap.size > 0) {
243
+ // There are active subscriptions on the WebSocket
244
+ return;
245
+ }
231
246
  if (!this.awsRealTimeSocket) {
232
247
  this.socketStatus = types_1.SOCKET_STATUS.CLOSED;
233
248
  return;
234
249
  }
235
250
  if (this.awsRealTimeSocket.bufferedAmount > 0) {
236
- setTimeout(this._closeSocketWhenFlushed.bind(this), 1000);
251
+ // There is still data on the WebSocket
252
+ setTimeout(this._closeSocketIfRequired.bind(this), 1000);
237
253
  }
238
254
  else {
255
+ logger("closing WebSocket...");
256
+ clearTimeout(this.keepAliveTimeoutId);
239
257
  var tempSocket = this.awsRealTimeSocket;
240
258
  tempSocket.close(1000);
241
259
  this.awsRealTimeSocket = null;
@@ -349,7 +367,7 @@ var AppSyncRealTimeSubscriptionHandshakeLink = /** @class */ (function (_super)
349
367
  return;
350
368
  }
351
369
  return new Promise(function (res, rej) { return __awaiter(_this, void 0, void 0, function () {
352
- var discoverableEndpoint, payloadString, headerString, _a, _b, headerQs, payloadQs, awsRealTimeUrl, err_2;
370
+ var payloadString, headerString, _a, _b, headerQs, payloadQs, discoverableEndpoint, awsRealTimeUrl, err_2;
353
371
  return __generator(this, function (_c) {
354
372
  switch (_c.label) {
355
373
  case 0:
@@ -359,7 +377,6 @@ var AppSyncRealTimeSubscriptionHandshakeLink = /** @class */ (function (_super)
359
377
  case 1:
360
378
  _c.trys.push([1, 4, , 5]);
361
379
  this.socketStatus = types_1.SOCKET_STATUS.CONNECTING;
362
- discoverableEndpoint = AppSyncRealTimeSubscriptionHandshakeLink._discoverAppSyncRealTimeEndpoint(this.url);
363
380
  payloadString = "{}";
364
381
  _b = (_a = JSON).stringify;
365
382
  return [4 /*yield*/, this._awsRealTimeHeaderBasedAuth({
@@ -377,6 +394,16 @@ var AppSyncRealTimeSubscriptionHandshakeLink = /** @class */ (function (_super)
377
394
  headerString = _b.apply(_a, [_c.sent()]);
378
395
  headerQs = Buffer.from(headerString).toString("base64");
379
396
  payloadQs = Buffer.from(payloadString).toString("base64");
397
+ discoverableEndpoint = appSyncGraphqlEndpoint;
398
+ if (this.isCustomDomain(discoverableEndpoint)) {
399
+ discoverableEndpoint = discoverableEndpoint.concat(customDomainPath);
400
+ }
401
+ else {
402
+ discoverableEndpoint = discoverableEndpoint.replace('appsync-api', 'appsync-realtime-api').replace('gogi-beta', 'grt-beta');
403
+ }
404
+ discoverableEndpoint = discoverableEndpoint
405
+ .replace("https://", "wss://")
406
+ .replace('http://', 'ws://');
380
407
  awsRealTimeUrl = discoverableEndpoint + "?header=" + headerQs + "&payload=" + payloadQs;
381
408
  return [4 /*yield*/, this._initializeRetryableHandshake({ awsRealTimeUrl: awsRealTimeUrl })];
382
409
  case 3:
@@ -588,12 +615,13 @@ var AppSyncRealTimeSubscriptionHandshakeLink = /** @class */ (function (_super)
588
615
  rej(new Error(JSON.stringify(event)));
589
616
  };
590
617
  _this.awsRealTimeSocket.onmessage = function (message) {
618
+ var _a;
591
619
  logger("subscription message from AWS AppSyncRealTime: " + message.data + " ");
592
620
  var data = JSON.parse(message.data);
593
- 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;
594
622
  if (type === types_1.MESSAGE_TYPES.GQL_CONNECTION_ACK) {
595
623
  ackOk = true;
596
- _this.keepAliveTimeout = connectionTimeoutMs;
624
+ _this.keepAliveTimeout = (_a = _this.keepAliveTimeout) !== null && _a !== void 0 ? _a : connectionTimeoutMs;
597
625
  _this.awsRealTimeSocket.onmessage = _this._handleIncomingSubscriptionMessage.bind(_this);
598
626
  _this.awsRealTimeSocket.onerror = function (err) {
599
627
  logger(err);
@@ -607,7 +635,7 @@ var AppSyncRealTimeSubscriptionHandshakeLink = /** @class */ (function (_super)
607
635
  return;
608
636
  }
609
637
  if (type === types_1.MESSAGE_TYPES.GQL_CONNECTION_ERROR) {
610
- 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;
611
639
  rej({ errorType: errorType, errorCode: errorCode });
612
640
  }
613
641
  };
@@ -757,13 +785,6 @@ var AppSyncRealTimeSubscriptionHandshakeLink = /** @class */ (function (_super)
757
785
  AppSyncRealTimeSubscriptionHandshakeLink.createWebSocket = function (awsRealTimeUrl, protocol) {
758
786
  return new WebSocket(awsRealTimeUrl, protocol);
759
787
  };
760
- AppSyncRealTimeSubscriptionHandshakeLink._discoverAppSyncRealTimeEndpoint = function (url) {
761
- return url
762
- .replace("https://", "wss://")
763
- .replace('http://', 'ws://')
764
- .replace("appsync-api", "appsync-realtime-api")
765
- .replace("gogi-beta", "grt-beta");
766
- };
767
788
  return AppSyncRealTimeSubscriptionHandshakeLink;
768
789
  }(core_1.ApolloLink));
769
790
  exports.AppSyncRealTimeSubscriptionHandshakeLink = AppSyncRealTimeSubscriptionHandshakeLink;
@@ -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;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "aws-appsync-subscription-link",
3
- "version": "3.0.9",
3
+ "version": "3.1.0",
4
4
  "main": "lib/index.js",
5
5
  "license": "Apache-2.0",
6
6
  "description": "AWS Mobile AppSync SDK for JavaScript",