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 +30 -0
- package/__tests__/link/realtime-subscription-handshake-link-test.ts +339 -18
- package/lib/index.d.ts +2 -2
- package/lib/realtime-subscription-handshake-link.d.ts +7 -9
- package/lib/realtime-subscription-handshake-link.js +44 -23
- package/lib/types/index.d.ts +3 -0
- package/package.json +1 -1
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;
|
|
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-
|
|
53
|
-
url: 'https://
|
|
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://
|
|
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-
|
|
76
|
-
url: 'https://
|
|
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(
|
|
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://
|
|
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-
|
|
113
|
-
url: 'https://
|
|
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(
|
|
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://
|
|
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-
|
|
150
|
-
url: 'https://
|
|
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(
|
|
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://
|
|
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-
|
|
187
|
-
url: 'https://
|
|
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 {
|
|
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 };
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
/*!
|
|
2
|
-
* Copyright 2017-
|
|
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 {
|
|
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 }:
|
|
19
|
-
|
|
20
|
-
|
|
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
|
|
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-
|
|
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 =
|
|
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
|
-
|
|
224
|
-
|
|
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.
|
|
229
|
-
|
|
230
|
-
|
|
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
|
-
|
|
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
|
|
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,
|
|
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
|
|
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;
|
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;
|