aws-appsync-subscription-link 2.2.6 → 2.4.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,40 @@
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.0"></a>
7
+ # [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)
8
+
9
+
10
+ ### Features
11
+
12
+ * 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))
13
+
14
+
15
+
16
+
17
+ <a name="2.3.0"></a>
18
+ # [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)
19
+
20
+
21
+ ### Features
22
+
23
+ * add custom domain support for Apollo V2 ([#698](https://github.com/awslabs/aws-mobile-appsync-sdk-js/issues/698)) ([42db16d](https://github.com/awslabs/aws-mobile-appsync-sdk-js/commit/42db16d))
24
+
25
+
26
+
27
+
28
+ <a name="2.2.7"></a>
29
+ ## [2.2.7](https://github.com/awslabs/aws-mobile-appsync-sdk-js/compare/aws-appsync-subscription-link@2.2.5...aws-appsync-subscription-link@2.2.7) (2022-03-04)
30
+
31
+
32
+ ### Bug Fixes
33
+
34
+ * Port over Amplify fix for subscription race conditions for ApolloV2 ([#509](https://github.com/awslabs/aws-mobile-appsync-sdk-js/issues/509)) ([#705](https://github.com/awslabs/aws-mobile-appsync-sdk-js/issues/705)) ([f5022ba](https://github.com/awslabs/aws-mobile-appsync-sdk-js/commit/f5022ba))
35
+ * **aws-appsync-subscription-link:** graphql header refactor to fix IAM-based auth for Apollo V2 packages ([#679](https://github.com/awslabs/aws-mobile-appsync-sdk-js/issues/679)) ([09fc430](https://github.com/awslabs/aws-mobile-appsync-sdk-js/commit/09fc430))
36
+
37
+
38
+
39
+
6
40
  <a name="2.2.6"></a>
7
41
  ## [2.2.6](https://github.com/awslabs/aws-mobile-appsync-sdk-js/compare/aws-appsync-subscription-link@2.2.5...aws-appsync-subscription-link@2.2.6) (2021-10-18)
8
42
 
@@ -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;
@@ -50,8 +53,22 @@ describe("RealTime subscription link", () => {
50
53
  type: AUTH_TYPE.API_KEY,
51
54
  apiKey: 'xxxxx'
52
55
  },
53
- region: 'us-east-1',
54
- 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'
55
72
  });
56
73
 
57
74
  expect(link).toBeInstanceOf(AppSyncRealTimeSubscriptionHandshakeLink);
@@ -63,7 +80,7 @@ describe("RealTime subscription link", () => {
63
80
  return "2019-11-13T18:47:04.733Z";
64
81
  }));
65
82
  AppSyncRealTimeSubscriptionHandshakeLink.createWebSocket = jest.fn((url, protocol) => {
66
- 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=');
67
84
  expect(protocol).toBe('graphql-ws');
68
85
  done();
69
86
  return new myWebSocket();
@@ -73,13 +90,50 @@ describe("RealTime subscription link", () => {
73
90
  type: AUTH_TYPE.API_KEY,
74
91
  apiKey: 'xxxxx'
75
92
  },
76
- region: 'us-east-1',
77
- 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'
78
95
  });
79
96
 
80
97
  execute(link, { query }).subscribe({
81
98
  error: (err) => {
82
- 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));
83
137
  fail;
84
138
  },
85
139
  next: (data) => {
@@ -100,7 +154,7 @@ describe("RealTime subscription link", () => {
100
154
  return "2019-11-13T18:47:04.733Z";
101
155
  }));
102
156
  AppSyncRealTimeSubscriptionHandshakeLink.createWebSocket = jest.fn((url, protocol) => {
103
- 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=');
104
158
  expect(protocol).toBe('graphql-ws');
105
159
  done();
106
160
  return new myWebSocket();
@@ -110,13 +164,50 @@ describe("RealTime subscription link", () => {
110
164
  type: AUTH_TYPE.AMAZON_COGNITO_USER_POOLS,
111
165
  jwtToken: 'token'
112
166
  },
113
- region: 'us-east-1',
114
- 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'
115
169
  });
116
170
 
117
171
  execute(link, { query }).subscribe({
118
172
  error: (err) => {
119
- 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));
120
211
  fail;
121
212
  },
122
213
  next: (data) => {
@@ -137,7 +228,44 @@ describe("RealTime subscription link", () => {
137
228
  return "2019-11-13T18:47:04.733Z";
138
229
  }));
139
230
  AppSyncRealTimeSubscriptionHandshakeLink.createWebSocket = jest.fn((url, protocol) => {
140
- 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=');
141
269
  expect(protocol).toBe('graphql-ws');
142
270
  done();
143
271
  return new myWebSocket();
@@ -147,13 +275,13 @@ describe("RealTime subscription link", () => {
147
275
  type: AUTH_TYPE.OPENID_CONNECT,
148
276
  jwtToken: 'token'
149
277
  },
150
- region: 'us-east-1',
151
- url: 'https://xxxxx.appsync-api.amazonaws.com/graphql'
278
+ region: 'us-west-2',
279
+ url: 'https://openidconnecttesturl.testcustomdomain.com/graphql'
152
280
  });
153
281
 
154
282
  execute(link, { query }).subscribe({
155
283
  error: (err) => {
156
- console.log({ err });
284
+ console.log(JSON.stringify(err));
157
285
  fail;
158
286
  },
159
287
  next: (data) => {
@@ -174,7 +302,7 @@ describe("RealTime subscription link", () => {
174
302
  return "2019-11-13T18:47:04.733Z";
175
303
  }));
176
304
  AppSyncRealTimeSubscriptionHandshakeLink.createWebSocket = jest.fn((url, protocol) => {
177
- 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=');
178
306
  expect(protocol).toBe('graphql-ws');
179
307
  done();
180
308
  return new myWebSocket();
@@ -184,8 +312,8 @@ describe("RealTime subscription link", () => {
184
312
  type: AUTH_TYPE.AWS_LAMBDA,
185
313
  token: 'token'
186
314
  },
187
- region: 'us-east-1',
188
- 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'
189
317
  });
190
318
 
191
319
  execute(link, { query }).subscribe({
@@ -202,4 +330,196 @@ describe("RealTime subscription link", () => {
202
330
  });
203
331
  })
204
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
+
205
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 };
@@ -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-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,17 +12,18 @@ 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
+ private isCustomDomain;
19
20
  request(operation: Operation): Observable<FetchResult<{
20
21
  [key: string]: any;
21
22
  }, Record<string, any>, Record<string, any>>>;
22
23
  private _verifySubscriptionAlreadyStarted;
23
24
  private _sendUnsubscriptionMessage;
24
25
  private _removeSubscriptionObserver;
25
- private _closeSocketWhenFlushed;
26
+ private _closeSocketIfRequired;
26
27
  private _startSubscriptionWithAWSAppSyncRealTime;
27
28
  private _initializeWebSocketConnection;
28
29
  private _awsRealTimeHeaderBasedAuth;
@@ -35,5 +36,4 @@ export declare class AppSyncRealTimeSubscriptionHandshakeLink extends ApolloLink
35
36
  private _errorDisconnect;
36
37
  private _timeoutStartSubscriptionAck;
37
38
  static createWebSocket(awsRealTimeUrl: string, protocol: string): WebSocket;
38
- private static _discoverAppSyncRealTimeEndpoint;
39
39
  }
@@ -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 apollo_link_1 = require("apollo-link");
@@ -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;
@@ -221,22 +236,25 @@ var AppSyncRealTimeSubscriptionHandshakeLink = /** @class */ (function (_super)
221
236
  };
222
237
  AppSyncRealTimeSubscriptionHandshakeLink.prototype._removeSubscriptionObserver = function (subscriptionId) {
223
238
  this.subscriptionObserverMap.delete(subscriptionId);
224
- if (this.subscriptionObserverMap.size === 0) {
225
- // Socket could be sending data to unsubscribe so is required to wait until is flushed
226
- this._closeSocketWhenFlushed();
227
- }
239
+ // Verifying for 1000ms after removing subscription in case there are new subscriptions on mount / unmount
240
+ setTimeout(this._closeSocketIfRequired.bind(this), 1000);
228
241
  };
229
- AppSyncRealTimeSubscriptionHandshakeLink.prototype._closeSocketWhenFlushed = function () {
230
- logger("closing WebSocket...");
231
- clearTimeout(this.keepAliveTimeoutId);
242
+ AppSyncRealTimeSubscriptionHandshakeLink.prototype._closeSocketIfRequired = function () {
243
+ if (this.subscriptionObserverMap.size > 0) {
244
+ // There are active subscriptions on the WebSocket
245
+ return;
246
+ }
232
247
  if (!this.awsRealTimeSocket) {
233
248
  this.socketStatus = types_1.SOCKET_STATUS.CLOSED;
234
249
  return;
235
250
  }
236
251
  if (this.awsRealTimeSocket.bufferedAmount > 0) {
237
- setTimeout(this._closeSocketWhenFlushed.bind(this), 1000);
252
+ // There is still data on the WebSocket
253
+ setTimeout(this._closeSocketIfRequired.bind(this), 1000);
238
254
  }
239
255
  else {
256
+ logger("closing WebSocket...");
257
+ clearTimeout(this.keepAliveTimeoutId);
240
258
  var tempSocket = this.awsRealTimeSocket;
241
259
  tempSocket.close(1000);
242
260
  this.awsRealTimeSocket = null;
@@ -350,7 +368,7 @@ var AppSyncRealTimeSubscriptionHandshakeLink = /** @class */ (function (_super)
350
368
  return;
351
369
  }
352
370
  return new Promise(function (res, rej) { return __awaiter(_this, void 0, void 0, function () {
353
- var discoverableEndpoint, payloadString, headerString, _a, _b, headerQs, payloadQs, awsRealTimeUrl, err_2;
371
+ var payloadString, headerString, _a, _b, headerQs, payloadQs, discoverableEndpoint, awsRealTimeUrl, err_2;
354
372
  return __generator(this, function (_c) {
355
373
  switch (_c.label) {
356
374
  case 0:
@@ -360,7 +378,6 @@ var AppSyncRealTimeSubscriptionHandshakeLink = /** @class */ (function (_super)
360
378
  case 1:
361
379
  _c.trys.push([1, 4, , 5]);
362
380
  this.socketStatus = types_1.SOCKET_STATUS.CONNECTING;
363
- discoverableEndpoint = AppSyncRealTimeSubscriptionHandshakeLink._discoverAppSyncRealTimeEndpoint(this.url);
364
381
  payloadString = "{}";
365
382
  _b = (_a = JSON).stringify;
366
383
  return [4 /*yield*/, this._awsRealTimeHeaderBasedAuth({
@@ -372,12 +389,22 @@ var AppSyncRealTimeSubscriptionHandshakeLink = /** @class */ (function (_super)
372
389
  region: region,
373
390
  credentials: credentials,
374
391
  token: token,
375
- graphql_headers: function () { },
392
+ graphql_headers: function () { }
376
393
  })];
377
394
  case 2:
378
395
  headerString = _b.apply(_a, [_c.sent()]);
379
396
  headerQs = Buffer.from(headerString).toString("base64");
380
397
  payloadQs = Buffer.from(payloadString).toString("base64");
398
+ discoverableEndpoint = appSyncGraphqlEndpoint;
399
+ if (this.isCustomDomain(discoverableEndpoint)) {
400
+ discoverableEndpoint = discoverableEndpoint.concat(customDomainPath);
401
+ }
402
+ else {
403
+ discoverableEndpoint = discoverableEndpoint.replace('appsync-api', 'appsync-realtime-api').replace('gogi-beta', 'grt-beta');
404
+ }
405
+ discoverableEndpoint = discoverableEndpoint
406
+ .replace("https://", "wss://")
407
+ .replace('http://', 'ws://');
381
408
  awsRealTimeUrl = discoverableEndpoint + "?header=" + headerQs + "&payload=" + payloadQs;
382
409
  return [4 /*yield*/, this._initializeRetryableHandshake({ awsRealTimeUrl: awsRealTimeUrl })];
383
410
  case 3:
@@ -589,14 +616,14 @@ var AppSyncRealTimeSubscriptionHandshakeLink = /** @class */ (function (_super)
589
616
  rej(new Error(JSON.stringify(event)));
590
617
  };
591
618
  _this.awsRealTimeSocket.onmessage = function (message) {
619
+ var _a;
592
620
  logger("subscription message from AWS AppSyncRealTime: " + message.data + " ");
593
621
  var data = JSON.parse(message.data);
594
- 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;
595
623
  if (type === types_1.MESSAGE_TYPES.GQL_CONNECTION_ACK) {
596
624
  ackOk = true;
597
- _this.keepAliveTimeout = connectionTimeoutMs;
598
- _this.awsRealTimeSocket.onmessage =
599
- _this._handleIncomingSubscriptionMessage.bind(_this);
625
+ _this.keepAliveTimeout = (_a = _this.keepAliveTimeout) !== null && _a !== void 0 ? _a : connectionTimeoutMs;
626
+ _this.awsRealTimeSocket.onmessage = _this._handleIncomingSubscriptionMessage.bind(_this);
600
627
  _this.awsRealTimeSocket.onerror = function (err) {
601
628
  logger(err);
602
629
  _this._errorDisconnect(types_1.CONTROL_MSG.CONNECTION_CLOSED);
@@ -609,7 +636,7 @@ var AppSyncRealTimeSubscriptionHandshakeLink = /** @class */ (function (_super)
609
636
  return;
610
637
  }
611
638
  if (type === types_1.MESSAGE_TYPES.GQL_CONNECTION_ERROR) {
612
- 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;
613
640
  rej({ errorType: errorType, errorCode: errorCode });
614
641
  }
615
642
  };
@@ -759,13 +786,6 @@ var AppSyncRealTimeSubscriptionHandshakeLink = /** @class */ (function (_super)
759
786
  AppSyncRealTimeSubscriptionHandshakeLink.createWebSocket = function (awsRealTimeUrl, protocol) {
760
787
  return new WebSocket(awsRealTimeUrl, protocol);
761
788
  };
762
- AppSyncRealTimeSubscriptionHandshakeLink._discoverAppSyncRealTimeEndpoint = function (url) {
763
- return url
764
- .replace("https://", "wss://")
765
- .replace("http://", "ws://")
766
- .replace("appsync-api", "appsync-realtime-api")
767
- .replace("gogi-beta", "grt-beta");
768
- };
769
789
  return AppSyncRealTimeSubscriptionHandshakeLink;
770
790
  }(apollo_link_1.ApolloLink));
771
791
  exports.AppSyncRealTimeSubscriptionHandshakeLink = AppSyncRealTimeSubscriptionHandshakeLink;
@@ -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.2.6",
3
+ "version": "2.4.0",
4
4
  "main": "lib/index.js",
5
5
  "license": "Apache-2.0",
6
6
  "description": "AWS Mobile AppSync SDK for JavaScript",
@@ -23,7 +23,7 @@
23
23
  "apollo-link-context": "1.0.11",
24
24
  "apollo-link-http": "1.5.8",
25
25
  "apollo-link-retry": "2.2.7",
26
- "aws-appsync-auth-link": "^2.0.7",
26
+ "aws-appsync-auth-link": "^2.0.8",
27
27
  "debug": "2.6.9",
28
28
  "url": "^0.11.0"
29
29
  },