@aws-amplify/pubsub 4.4.9 → 4.4.10-next.20
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 +73 -0
- package/dist/aws-amplify-pubsub.js +1260 -2214
- package/dist/aws-amplify-pubsub.js.map +1 -1
- package/dist/aws-amplify-pubsub.min.js +3 -3
- package/dist/aws-amplify-pubsub.min.js.map +1 -1
- package/lib/Providers/AWSAppSyncProvider.js +20 -102
- package/lib/Providers/AWSAppSyncProvider.js.map +1 -1
- package/lib/Providers/AWSAppSyncRealTimeProvider/index.d.ts +10 -2
- package/lib/Providers/AWSAppSyncRealTimeProvider/index.js +166 -208
- package/lib/Providers/AWSAppSyncRealTimeProvider/index.js.map +1 -1
- package/lib/Providers/AWSIotProvider.js +4 -52
- package/lib/Providers/AWSIotProvider.js.map +1 -1
- package/lib/Providers/MqttOverWSProvider.d.ts +5 -1
- package/lib/Providers/MqttOverWSProvider.js +172 -187
- package/lib/Providers/MqttOverWSProvider.js.map +1 -1
- package/lib/Providers/PubSubProvider.d.ts +1 -1
- package/lib/Providers/PubSubProvider.js +3 -13
- package/lib/Providers/PubSubProvider.js.map +1 -1
- package/lib/Providers/{AWSAppSyncRealTimeProvider/constants.d.ts → constants.d.ts} +9 -0
- package/lib/Providers/{AWSAppSyncRealTimeProvider/constants.js → constants.js} +9 -0
- package/lib/Providers/constants.js.map +1 -0
- package/lib/Providers/index.js +6 -8
- package/lib/Providers/index.js.map +1 -1
- package/lib/PubSub.js +6 -44
- package/lib/PubSub.js.map +1 -1
- package/lib/index.d.ts +3 -10
- package/lib/index.js +5 -14
- package/lib/index.js.map +1 -1
- package/lib/types/Provider.d.ts +10 -1
- package/lib/types/PubSub.d.ts +22 -8
- package/lib/types/PubSub.js +56 -0
- package/lib/types/PubSub.js.map +1 -1
- package/lib/types/index.d.ts +0 -17
- package/lib/types/index.js +2 -36
- package/lib/types/index.js.map +1 -1
- package/lib/utils/ConnectionStateMonitor.d.ts +2 -1
- package/lib/utils/ConnectionStateMonitor.js +25 -30
- package/lib/utils/ConnectionStateMonitor.js.map +1 -1
- package/lib/utils/ReachabilityMonitor/index.d.ts +3 -0
- package/lib/utils/ReachabilityMonitor/index.js +5 -0
- package/lib/utils/ReachabilityMonitor/index.js.map +1 -0
- package/lib/utils/ReachabilityMonitor/index.native.d.ts +3 -0
- package/lib/utils/ReachabilityMonitor/index.native.js +9 -0
- package/lib/utils/ReachabilityMonitor/index.native.js.map +1 -0
- package/lib/utils/ReconnectionMonitor.d.ts +29 -0
- package/lib/utils/ReconnectionMonitor.js +73 -0
- package/lib/utils/ReconnectionMonitor.js.map +1 -0
- package/lib-esm/Providers/AWSAppSyncProvider.js +1 -80
- package/lib-esm/Providers/AWSAppSyncProvider.js.map +1 -1
- package/lib-esm/Providers/AWSAppSyncRealTimeProvider/index.d.ts +10 -2
- package/lib-esm/Providers/AWSAppSyncRealTimeProvider/index.js +130 -162
- package/lib-esm/Providers/AWSAppSyncRealTimeProvider/index.js.map +1 -1
- package/lib-esm/Providers/AWSIotProvider.js +1 -49
- package/lib-esm/Providers/AWSIotProvider.js.map +1 -1
- package/lib-esm/Providers/MqttOverWSProvider.d.ts +5 -1
- package/lib-esm/Providers/MqttOverWSProvider.js +154 -159
- package/lib-esm/Providers/MqttOverWSProvider.js.map +1 -1
- package/lib-esm/Providers/PubSubProvider.d.ts +1 -1
- package/lib-esm/Providers/PubSubProvider.js +1 -11
- package/lib-esm/Providers/PubSubProvider.js.map +1 -1
- package/lib-esm/Providers/{AWSAppSyncRealTimeProvider/constants.d.ts → constants.d.ts} +9 -0
- package/lib-esm/Providers/{AWSAppSyncRealTimeProvider/constants.js → constants.js} +9 -0
- package/lib-esm/Providers/constants.js.map +1 -0
- package/lib-esm/PubSub.js +1 -36
- package/lib-esm/PubSub.js.map +1 -1
- package/lib-esm/index.d.ts +3 -10
- package/lib-esm/index.js +3 -11
- package/lib-esm/index.js.map +1 -1
- package/lib-esm/types/Provider.d.ts +10 -1
- package/lib-esm/types/PubSub.d.ts +22 -8
- package/lib-esm/types/PubSub.js +56 -0
- package/lib-esm/types/PubSub.js.map +1 -1
- package/lib-esm/types/index.d.ts +0 -17
- package/lib-esm/types/index.js +1 -36
- package/lib-esm/types/index.js.map +1 -1
- package/lib-esm/utils/ConnectionStateMonitor.d.ts +2 -1
- package/lib-esm/utils/ConnectionStateMonitor.js +14 -16
- package/lib-esm/utils/ConnectionStateMonitor.js.map +1 -1
- package/lib-esm/utils/ReachabilityMonitor/index.d.ts +3 -0
- package/lib-esm/utils/ReachabilityMonitor/index.js +3 -0
- package/lib-esm/utils/ReachabilityMonitor/index.js.map +1 -0
- package/lib-esm/utils/ReachabilityMonitor/index.native.d.ts +3 -0
- package/lib-esm/utils/ReachabilityMonitor/index.native.js +6 -0
- package/lib-esm/utils/ReachabilityMonitor/index.native.js.map +1 -0
- package/lib-esm/utils/ReconnectionMonitor.d.ts +29 -0
- package/lib-esm/utils/ReconnectionMonitor.js +71 -0
- package/lib-esm/utils/ReconnectionMonitor.js.map +1 -0
- package/package.json +6 -5
- package/src/Providers/AWSAppSyncRealTimeProvider/index.ts +154 -90
- package/src/Providers/MqttOverWSProvider.ts +120 -53
- package/src/Providers/PubSubProvider.ts +1 -1
- package/src/Providers/{AWSAppSyncRealTimeProvider/constants.ts → constants.ts} +12 -0
- package/src/index.ts +3 -10
- package/src/types/Provider.ts +13 -1
- package/src/types/PubSub.ts +47 -8
- package/src/types/index.ts +0 -43
- package/src/utils/ConnectionStateMonitor.ts +20 -5
- package/src/utils/ReachabilityMonitor/index.native.ts +5 -0
- package/src/utils/ReachabilityMonitor/index.ts +3 -0
- package/src/utils/ReconnectionMonitor.ts +74 -0
- package/lib/Providers/AWSAppSyncRealTimeProvider/constants.js.map +0 -1
- package/lib-esm/Providers/AWSAppSyncRealTimeProvider/constants.js.map +0 -1
|
@@ -15,7 +15,7 @@ import { GraphQLError } from 'graphql';
|
|
|
15
15
|
import * as url from 'url';
|
|
16
16
|
import { v4 as uuid } from 'uuid';
|
|
17
17
|
import { Buffer } from 'buffer';
|
|
18
|
-
import { ProviderOptions } from '../../types';
|
|
18
|
+
import { ProviderOptions } from '../../types/Provider';
|
|
19
19
|
import {
|
|
20
20
|
Logger,
|
|
21
21
|
Credentials,
|
|
@@ -26,11 +26,12 @@ import {
|
|
|
26
26
|
jitteredExponentialRetry,
|
|
27
27
|
NonRetryableError,
|
|
28
28
|
ICredentials,
|
|
29
|
+
isNonRetryableError,
|
|
29
30
|
} from '@aws-amplify/core';
|
|
30
31
|
import Cache from '@aws-amplify/cache';
|
|
31
32
|
import Auth, { GRAPHQL_AUTH_MODE } from '@aws-amplify/auth';
|
|
32
33
|
import { AbstractPubSubProvider } from '../PubSubProvider';
|
|
33
|
-
import {
|
|
34
|
+
import { CONTROL_MSG, ConnectionState } from '../../types/PubSub';
|
|
34
35
|
|
|
35
36
|
import {
|
|
36
37
|
AMPLIFY_SYMBOL,
|
|
@@ -44,11 +45,16 @@ import {
|
|
|
44
45
|
SOCKET_STATUS,
|
|
45
46
|
START_ACK_TIMEOUT,
|
|
46
47
|
SUBSCRIPTION_STATUS,
|
|
47
|
-
|
|
48
|
+
CONNECTION_STATE_CHANGE,
|
|
49
|
+
} from '../constants';
|
|
48
50
|
import {
|
|
49
51
|
ConnectionStateMonitor,
|
|
50
52
|
CONNECTION_CHANGE,
|
|
51
53
|
} from '../../utils/ConnectionStateMonitor';
|
|
54
|
+
import {
|
|
55
|
+
ReconnectEvent,
|
|
56
|
+
ReconnectionMonitor,
|
|
57
|
+
} from '../../utils/ReconnectionMonitor';
|
|
52
58
|
|
|
53
59
|
const logger = new Logger('AWSAppSyncRealTimeProvider');
|
|
54
60
|
|
|
@@ -98,23 +104,63 @@ export class AWSAppSyncRealTimeProvider extends AbstractPubSubProvider {
|
|
|
98
104
|
private keepAliveAlertTimeoutId?: ReturnType<typeof setTimeout>;
|
|
99
105
|
private subscriptionObserverMap: Map<string, ObserverQuery> = new Map();
|
|
100
106
|
private promiseArray: Array<{ res: Function; rej: Function }> = [];
|
|
107
|
+
private connectionState: ConnectionState;
|
|
101
108
|
private readonly connectionStateMonitor = new ConnectionStateMonitor();
|
|
109
|
+
private readonly reconnectionMonitor = new ReconnectionMonitor();
|
|
110
|
+
private connectionStateMonitorSubscription: ZenObservable.Subscription;
|
|
102
111
|
|
|
103
112
|
constructor(options: ProviderOptions = {}) {
|
|
104
113
|
super(options);
|
|
105
114
|
// Monitor the connection state and pass changes along to Hub
|
|
106
|
-
this.
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
115
|
+
this.connectionStateMonitorSubscription =
|
|
116
|
+
this.connectionStateMonitor.connectionStateObservable.subscribe(
|
|
117
|
+
connectionState => {
|
|
118
|
+
dispatchApiEvent(
|
|
119
|
+
CONNECTION_STATE_CHANGE,
|
|
120
|
+
{
|
|
121
|
+
provider: this,
|
|
122
|
+
connectionState,
|
|
123
|
+
},
|
|
124
|
+
`Connection state is ${connectionState}`
|
|
125
|
+
);
|
|
126
|
+
this.connectionState = connectionState;
|
|
127
|
+
|
|
128
|
+
// Trigger START_RECONNECT when the connection is disrupted
|
|
129
|
+
if (connectionState === ConnectionState.ConnectionDisrupted) {
|
|
130
|
+
this.reconnectionMonitor.record(ReconnectEvent.START_RECONNECT);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Trigger HALT_RECONNECT to halt reconnection attempts when the state is anything other than
|
|
134
|
+
// ConnectionDisrupted or Connecting
|
|
135
|
+
if (
|
|
136
|
+
[
|
|
137
|
+
ConnectionState.Connected,
|
|
138
|
+
ConnectionState.ConnectedPendingDisconnect,
|
|
139
|
+
ConnectionState.ConnectedPendingKeepAlive,
|
|
140
|
+
ConnectionState.ConnectedPendingNetwork,
|
|
141
|
+
ConnectionState.ConnectedPendingNetwork,
|
|
142
|
+
ConnectionState.ConnectionDisruptedPendingNetwork,
|
|
143
|
+
ConnectionState.Disconnected,
|
|
144
|
+
].includes(connectionState)
|
|
145
|
+
) {
|
|
146
|
+
this.reconnectionMonitor.record(ReconnectEvent.HALT_RECONNECT);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Mark the socket closed and release all active listeners
|
|
154
|
+
*/
|
|
155
|
+
close() {
|
|
156
|
+
// Mark the socket closed both in status and the connection monitor
|
|
157
|
+
this.socketStatus = SOCKET_STATUS.CLOSED;
|
|
158
|
+
this.connectionStateMonitor.record(CONNECTION_CHANGE.CONNECTION_FAILED);
|
|
159
|
+
|
|
160
|
+
// Turn off the subscription monitor Hub publishing
|
|
161
|
+
this.connectionStateMonitorSubscription.unsubscribe();
|
|
162
|
+
// Complete all reconnect observers
|
|
163
|
+
this.reconnectionMonitor.close();
|
|
118
164
|
}
|
|
119
165
|
|
|
120
166
|
getNewWebSocket(url, protocol) {
|
|
@@ -157,26 +203,44 @@ export class AWSAppSyncRealTimeProvider extends AbstractPubSubProvider {
|
|
|
157
203
|
});
|
|
158
204
|
observer.complete();
|
|
159
205
|
} else {
|
|
206
|
+
let subscriptionStartActive = false;
|
|
160
207
|
const subscriptionId = uuid();
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
208
|
+
const startSubscription = () => {
|
|
209
|
+
if (!subscriptionStartActive) {
|
|
210
|
+
subscriptionStartActive = true;
|
|
211
|
+
const startSubscriptionPromise =
|
|
212
|
+
this._startSubscriptionWithAWSAppSyncRealTime({
|
|
213
|
+
options,
|
|
214
|
+
observer,
|
|
215
|
+
subscriptionId,
|
|
216
|
+
}).catch<any>(err => {
|
|
217
|
+
logger.debug(
|
|
170
218
|
`${CONTROL_MSG.REALTIME_SUBSCRIPTION_INIT_ERROR}: ${err}`
|
|
171
|
-
)
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
219
|
+
);
|
|
220
|
+
|
|
221
|
+
this.connectionStateMonitor.record(CONNECTION_CHANGE.CLOSED);
|
|
222
|
+
});
|
|
223
|
+
startSubscriptionPromise.finally(() => {
|
|
224
|
+
subscriptionStartActive = false;
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
};
|
|
228
|
+
|
|
229
|
+
let reconnectSubscription: ZenObservable.Subscription;
|
|
230
|
+
|
|
231
|
+
// Add an observable to the reconnection list to manage reconnection for this subscription
|
|
232
|
+
reconnectSubscription = new Observable(observer => {
|
|
233
|
+
this.reconnectionMonitor.addObserver(observer);
|
|
234
|
+
}).subscribe(() => {
|
|
235
|
+
startSubscription();
|
|
177
236
|
});
|
|
178
237
|
|
|
238
|
+
startSubscription();
|
|
239
|
+
|
|
179
240
|
return async () => {
|
|
241
|
+
// Cleanup reconnection subscription
|
|
242
|
+
reconnectSubscription?.unsubscribe();
|
|
243
|
+
|
|
180
244
|
// Cleanup after unsubscribing or observer.complete was called after _startSubscriptionWithAWSAppSyncRealTime
|
|
181
245
|
try {
|
|
182
246
|
// Waiting that subscription has been connected before trying to unsubscribe
|
|
@@ -287,24 +351,7 @@ export class AWSAppSyncRealTimeProvider extends AbstractPubSubProvider {
|
|
|
287
351
|
additionalHeaders,
|
|
288
352
|
});
|
|
289
353
|
} catch (err) {
|
|
290
|
-
|
|
291
|
-
const message = err['message'] ?? '';
|
|
292
|
-
this.connectionStateMonitor.record(CONNECTION_CHANGE.CLOSED);
|
|
293
|
-
observer.error({
|
|
294
|
-
errors: [
|
|
295
|
-
{
|
|
296
|
-
...new GraphQLError(`${CONTROL_MSG.CONNECTION_FAILED}: ${message}`),
|
|
297
|
-
},
|
|
298
|
-
],
|
|
299
|
-
});
|
|
300
|
-
observer.complete();
|
|
301
|
-
const { subscriptionFailedCallback } =
|
|
302
|
-
this.subscriptionObserverMap.get(subscriptionId) || {};
|
|
303
|
-
|
|
304
|
-
// Notify concurrent unsubscription
|
|
305
|
-
if (typeof subscriptionFailedCallback === 'function') {
|
|
306
|
-
subscriptionFailedCallback();
|
|
307
|
-
}
|
|
354
|
+
this._logStartSubscriptionError(subscriptionId, observer, err);
|
|
308
355
|
return;
|
|
309
356
|
}
|
|
310
357
|
|
|
@@ -332,6 +379,44 @@ export class AWSAppSyncRealTimeProvider extends AbstractPubSubProvider {
|
|
|
332
379
|
}
|
|
333
380
|
}
|
|
334
381
|
|
|
382
|
+
// Log logic for start subscription failures
|
|
383
|
+
private _logStartSubscriptionError(subscriptionId, observer, err) {
|
|
384
|
+
logger.debug({ err });
|
|
385
|
+
const message = err['message'] ?? '';
|
|
386
|
+
// Resolving to give the state observer time to propogate the update
|
|
387
|
+
Promise.resolve(
|
|
388
|
+
this.connectionStateMonitor.record(CONNECTION_CHANGE.CLOSED)
|
|
389
|
+
);
|
|
390
|
+
|
|
391
|
+
// Capture the error only when the network didn't cause disruption
|
|
392
|
+
if (
|
|
393
|
+
this.connectionState !== ConnectionState.ConnectionDisruptedPendingNetwork
|
|
394
|
+
) {
|
|
395
|
+
// When the error is non-retriable, error out the observable
|
|
396
|
+
if (isNonRetryableError(err)) {
|
|
397
|
+
observer.error({
|
|
398
|
+
errors: [
|
|
399
|
+
{
|
|
400
|
+
...new GraphQLError(
|
|
401
|
+
`${CONTROL_MSG.CONNECTION_FAILED}: ${message}`
|
|
402
|
+
),
|
|
403
|
+
},
|
|
404
|
+
],
|
|
405
|
+
});
|
|
406
|
+
} else {
|
|
407
|
+
logger.debug(`${CONTROL_MSG.CONNECTION_FAILED}: ${message}`);
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
const { subscriptionFailedCallback } =
|
|
411
|
+
this.subscriptionObserverMap.get(subscriptionId) || {};
|
|
412
|
+
|
|
413
|
+
// Notify concurrent unsubscription
|
|
414
|
+
if (typeof subscriptionFailedCallback === 'function') {
|
|
415
|
+
subscriptionFailedCallback();
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
|
|
335
420
|
// Waiting that subscription has been connected before trying to unsubscribe
|
|
336
421
|
private async _waitForSubscriptionToBeConnected(subscriptionId: string) {
|
|
337
422
|
const subscriptionObserver =
|
|
@@ -504,6 +589,10 @@ export class AWSAppSyncRealTimeProvider extends AbstractPubSubProvider {
|
|
|
504
589
|
subscriptionState,
|
|
505
590
|
});
|
|
506
591
|
|
|
592
|
+
logger.debug(
|
|
593
|
+
`${CONTROL_MSG.CONNECTION_FAILED}: ${JSON.stringify(payload)}`
|
|
594
|
+
);
|
|
595
|
+
|
|
507
596
|
observer.error({
|
|
508
597
|
errors: [
|
|
509
598
|
{
|
|
@@ -513,9 +602,9 @@ export class AWSAppSyncRealTimeProvider extends AbstractPubSubProvider {
|
|
|
513
602
|
},
|
|
514
603
|
],
|
|
515
604
|
});
|
|
605
|
+
|
|
516
606
|
if (startAckTimeoutId) clearTimeout(startAckTimeoutId);
|
|
517
607
|
|
|
518
|
-
observer.complete();
|
|
519
608
|
if (typeof subscriptionFailedCallback === 'function') {
|
|
520
609
|
subscriptionFailedCallback();
|
|
521
610
|
}
|
|
@@ -525,14 +614,7 @@ export class AWSAppSyncRealTimeProvider extends AbstractPubSubProvider {
|
|
|
525
614
|
|
|
526
615
|
private _errorDisconnect(msg: string) {
|
|
527
616
|
logger.debug(`Disconnect error: ${msg}`);
|
|
528
|
-
|
|
529
|
-
if (observer && !observer.closed) {
|
|
530
|
-
observer.error({
|
|
531
|
-
errors: [{ ...new GraphQLError(msg) }],
|
|
532
|
-
});
|
|
533
|
-
}
|
|
534
|
-
});
|
|
535
|
-
this.subscriptionObserverMap.clear();
|
|
617
|
+
|
|
536
618
|
if (this.awsRealTimeSocket) {
|
|
537
619
|
this.connectionStateMonitor.record(CONNECTION_CHANGE.CLOSED);
|
|
538
620
|
this.awsRealTimeSocket.close();
|
|
@@ -556,22 +638,7 @@ export class AWSAppSyncRealTimeProvider extends AbstractPubSubProvider {
|
|
|
556
638
|
subscriptionState: SUBSCRIPTION_STATUS.FAILED,
|
|
557
639
|
});
|
|
558
640
|
|
|
559
|
-
|
|
560
|
-
observer.error({
|
|
561
|
-
errors: [
|
|
562
|
-
{
|
|
563
|
-
...new GraphQLError(
|
|
564
|
-
`Subscription timeout ${JSON.stringify({
|
|
565
|
-
query,
|
|
566
|
-
variables,
|
|
567
|
-
})}`
|
|
568
|
-
),
|
|
569
|
-
},
|
|
570
|
-
],
|
|
571
|
-
});
|
|
572
|
-
// Cleanup will be automatically executed
|
|
573
|
-
observer.complete();
|
|
574
|
-
}
|
|
641
|
+
this.connectionStateMonitor.record(CONNECTION_CHANGE.CLOSED);
|
|
575
642
|
logger.debug(
|
|
576
643
|
'timeoutStartSubscription',
|
|
577
644
|
JSON.stringify({ query, variables })
|
|
@@ -640,6 +707,7 @@ export class AWSAppSyncRealTimeProvider extends AbstractPubSubProvider {
|
|
|
640
707
|
this.socketStatus = SOCKET_STATUS.READY;
|
|
641
708
|
this.promiseArray = [];
|
|
642
709
|
} catch (err) {
|
|
710
|
+
logger.debug('Connection exited with', err);
|
|
643
711
|
this.promiseArray.forEach(({ rej }) => rej(err));
|
|
644
712
|
this.promiseArray = [];
|
|
645
713
|
if (
|
|
@@ -676,9 +744,6 @@ export class AWSAppSyncRealTimeProvider extends AbstractPubSubProvider {
|
|
|
676
744
|
logger.debug(`WebSocket connection error`);
|
|
677
745
|
};
|
|
678
746
|
newSocket.onclose = () => {
|
|
679
|
-
this.connectionStateMonitor.record(
|
|
680
|
-
CONNECTION_CHANGE.CONNECTION_FAILED
|
|
681
|
-
);
|
|
682
747
|
rej(new Error('Connection handshake error'));
|
|
683
748
|
};
|
|
684
749
|
newSocket.onopen = () => {
|
|
@@ -687,7 +752,6 @@ export class AWSAppSyncRealTimeProvider extends AbstractPubSubProvider {
|
|
|
687
752
|
};
|
|
688
753
|
});
|
|
689
754
|
})();
|
|
690
|
-
|
|
691
755
|
// Step 2: wait for ack from AWS AppSyncReaTime after sending init
|
|
692
756
|
await (() => {
|
|
693
757
|
return new Promise((res, rej) => {
|
|
@@ -747,20 +811,20 @@ export class AWSAppSyncRealTimeProvider extends AbstractPubSubProvider {
|
|
|
747
811
|
};
|
|
748
812
|
this.awsRealTimeSocket.send(JSON.stringify(gqlInit));
|
|
749
813
|
|
|
750
|
-
|
|
751
|
-
|
|
814
|
+
const checkAckOk = (ackOk: boolean) => {
|
|
815
|
+
if (!ackOk) {
|
|
816
|
+
this.connectionStateMonitor.record(
|
|
817
|
+
CONNECTION_CHANGE.CONNECTION_FAILED
|
|
818
|
+
);
|
|
819
|
+
rej(
|
|
820
|
+
new Error(
|
|
821
|
+
`Connection timeout: ack from AWSAppSyncRealTime was not received after ${CONNECTION_INIT_TIMEOUT} ms`
|
|
822
|
+
)
|
|
823
|
+
);
|
|
824
|
+
}
|
|
825
|
+
};
|
|
752
826
|
|
|
753
|
-
|
|
754
|
-
if (!ackOk) {
|
|
755
|
-
this.connectionStateMonitor.record(
|
|
756
|
-
CONNECTION_CHANGE.CONNECTION_FAILED
|
|
757
|
-
);
|
|
758
|
-
rej(
|
|
759
|
-
new Error(
|
|
760
|
-
`Connection timeout: ack from AWSRealTime was not received on ${CONNECTION_INIT_TIMEOUT} ms`
|
|
761
|
-
)
|
|
762
|
-
);
|
|
763
|
-
}
|
|
827
|
+
setTimeout(() => checkAckOk(ackOk), CONNECTION_INIT_TIMEOUT);
|
|
764
828
|
}
|
|
765
829
|
});
|
|
766
830
|
})();
|
|
@@ -12,11 +12,21 @@
|
|
|
12
12
|
*/
|
|
13
13
|
import * as Paho from 'paho-mqtt';
|
|
14
14
|
import { v4 as uuid } from 'uuid';
|
|
15
|
-
import Observable from 'zen-observable-ts';
|
|
15
|
+
import Observable, { ZenObservable } from 'zen-observable-ts';
|
|
16
16
|
|
|
17
17
|
import { AbstractPubSubProvider } from './PubSubProvider';
|
|
18
|
-
import {
|
|
19
|
-
import {
|
|
18
|
+
import { SubscriptionObserver, ConnectionState } from '../types/PubSub';
|
|
19
|
+
import { ProviderOptions } from '../types/Provider';
|
|
20
|
+
import { ConsoleLogger as Logger, Hub } from '@aws-amplify/core';
|
|
21
|
+
import {
|
|
22
|
+
ConnectionStateMonitor,
|
|
23
|
+
CONNECTION_CHANGE,
|
|
24
|
+
} from '../utils/ConnectionStateMonitor';
|
|
25
|
+
import {
|
|
26
|
+
ReconnectEvent,
|
|
27
|
+
ReconnectionMonitor,
|
|
28
|
+
} from '../utils/ReconnectionMonitor';
|
|
29
|
+
import { AMPLIFY_SYMBOL, CONNECTION_STATE_CHANGE } from './constants';
|
|
20
30
|
|
|
21
31
|
const logger = new Logger('MqttOverWSProvider');
|
|
22
32
|
|
|
@@ -49,17 +59,15 @@ class ClientsQueue {
|
|
|
49
59
|
|
|
50
60
|
async get(clientId: string, clientFactory?: (input: string) => Promise<any>) {
|
|
51
61
|
const cachedPromise = this.promises.get(clientId);
|
|
52
|
-
if (cachedPromise)
|
|
53
|
-
return cachedPromise;
|
|
54
|
-
}
|
|
62
|
+
if (cachedPromise) return cachedPromise;
|
|
55
63
|
|
|
56
64
|
if (clientFactory) {
|
|
57
65
|
const newPromise = clientFactory(clientId);
|
|
58
|
-
|
|
59
66
|
this.promises.set(clientId, newPromise);
|
|
60
|
-
|
|
67
|
+
newPromise.catch(v => this.promises.delete(clientId));
|
|
61
68
|
return newPromise;
|
|
62
69
|
}
|
|
70
|
+
|
|
63
71
|
return undefined;
|
|
64
72
|
}
|
|
65
73
|
|
|
@@ -72,13 +80,44 @@ class ClientsQueue {
|
|
|
72
80
|
}
|
|
73
81
|
}
|
|
74
82
|
|
|
83
|
+
const dispatchPubSubEvent = (event: string, data: any, message: string) => {
|
|
84
|
+
Hub.dispatch('pubsub', { event, data, message }, 'PubSub', AMPLIFY_SYMBOL);
|
|
85
|
+
};
|
|
86
|
+
|
|
75
87
|
const topicSymbol = typeof Symbol !== 'undefined' ? Symbol('topic') : '@@topic';
|
|
76
88
|
|
|
77
89
|
export class MqttOverWSProvider extends AbstractPubSubProvider {
|
|
78
90
|
private _clientsQueue = new ClientsQueue();
|
|
91
|
+
private connectionState: ConnectionState;
|
|
92
|
+
private readonly connectionStateMonitor = new ConnectionStateMonitor();
|
|
93
|
+
private readonly reconnectionMonitor = new ReconnectionMonitor();
|
|
79
94
|
|
|
80
95
|
constructor(options: MqttProviderOptions = {}) {
|
|
81
96
|
super({ ...options, clientId: options.clientId || uuid() });
|
|
97
|
+
|
|
98
|
+
// Monitor the connection health state and pass changes along to Hub
|
|
99
|
+
this.connectionStateMonitor.connectionStateObservable.subscribe(
|
|
100
|
+
connectionStateChange => {
|
|
101
|
+
dispatchPubSubEvent(
|
|
102
|
+
CONNECTION_STATE_CHANGE,
|
|
103
|
+
{
|
|
104
|
+
provider: this,
|
|
105
|
+
connectionState: connectionStateChange,
|
|
106
|
+
},
|
|
107
|
+
`Connection state is ${connectionStateChange}`
|
|
108
|
+
);
|
|
109
|
+
|
|
110
|
+
this.connectionState = connectionStateChange;
|
|
111
|
+
|
|
112
|
+
// Trigger reconnection when the connection is disrupted
|
|
113
|
+
if (connectionStateChange === ConnectionState.ConnectionDisrupted) {
|
|
114
|
+
this.reconnectionMonitor.record(ReconnectEvent.START_RECONNECT);
|
|
115
|
+
} else if (connectionStateChange !== ConnectionState.Connecting) {
|
|
116
|
+
// Trigger connected to halt reconnection attempts
|
|
117
|
+
this.reconnectionMonitor.record(ReconnectEvent.HALT_RECONNECT);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
);
|
|
82
121
|
}
|
|
83
122
|
|
|
84
123
|
protected get clientId() {
|
|
@@ -117,7 +156,6 @@ export class MqttOverWSProvider extends AbstractPubSubProvider {
|
|
|
117
156
|
if (errorCode !== 0) {
|
|
118
157
|
logger.warn(clientId, JSON.stringify({ errorCode, ...args }, null, 2));
|
|
119
158
|
|
|
120
|
-
const topicsToDelete: string[] = [];
|
|
121
159
|
if (!clientId) {
|
|
122
160
|
return;
|
|
123
161
|
}
|
|
@@ -125,33 +163,17 @@ export class MqttOverWSProvider extends AbstractPubSubProvider {
|
|
|
125
163
|
if (!clientIdObservers) {
|
|
126
164
|
return;
|
|
127
165
|
}
|
|
128
|
-
|
|
129
|
-
observer.error('Disconnected, error code: ' + errorCode);
|
|
130
|
-
// removing observers for disconnected clientId
|
|
131
|
-
this._topicObservers.forEach((observerForTopic, observerTopic) => {
|
|
132
|
-
observerForTopic.delete(observer);
|
|
133
|
-
if (observerForTopic.size === 0) {
|
|
134
|
-
topicsToDelete.push(observerTopic);
|
|
135
|
-
}
|
|
136
|
-
});
|
|
137
|
-
});
|
|
138
|
-
|
|
139
|
-
// forgiving any trace of clientId
|
|
140
|
-
this._clientIdObservers.delete(clientId);
|
|
141
|
-
|
|
142
|
-
// Removing topics that are not listen by an observer
|
|
143
|
-
topicsToDelete.forEach(topic => {
|
|
144
|
-
this._topicObservers.delete(topic);
|
|
145
|
-
});
|
|
166
|
+
this.disconnect(clientId);
|
|
146
167
|
}
|
|
147
168
|
}
|
|
148
169
|
|
|
149
170
|
public async newClient({ url, clientId }: MqttProviderOptions): Promise<any> {
|
|
150
171
|
logger.debug('Creating new MQTT client', clientId);
|
|
151
172
|
|
|
173
|
+
this.connectionStateMonitor.record(CONNECTION_CHANGE.OPENING_CONNECTION);
|
|
152
174
|
// @ts-ignore
|
|
153
175
|
const client = new Paho.Client(url, clientId);
|
|
154
|
-
|
|
176
|
+
|
|
155
177
|
client.onMessageArrived = ({
|
|
156
178
|
destinationName: topic,
|
|
157
179
|
payloadString: msg,
|
|
@@ -168,17 +190,28 @@ export class MqttOverWSProvider extends AbstractPubSubProvider {
|
|
|
168
190
|
errorCode: number;
|
|
169
191
|
}) => {
|
|
170
192
|
this.onDisconnect({ clientId, errorCode, ...args });
|
|
193
|
+
this.connectionStateMonitor.record(CONNECTION_CHANGE.CLOSED);
|
|
171
194
|
};
|
|
172
195
|
|
|
173
|
-
await new Promise((resolve, reject) => {
|
|
196
|
+
const connected = await new Promise((resolve, reject) => {
|
|
174
197
|
client.connect({
|
|
175
198
|
useSSL: this.isSSLEnabled,
|
|
176
199
|
mqttVersion: 3,
|
|
177
|
-
onSuccess: () => resolve(
|
|
178
|
-
onFailure:
|
|
200
|
+
onSuccess: () => resolve(true),
|
|
201
|
+
onFailure: x => {
|
|
202
|
+
if (clientId) this._clientsQueue.remove(clientId);
|
|
203
|
+
this.connectionStateMonitor.record(CONNECTION_CHANGE.CLOSED);
|
|
204
|
+
resolve(false);
|
|
205
|
+
},
|
|
179
206
|
});
|
|
180
207
|
});
|
|
181
208
|
|
|
209
|
+
if (connected) {
|
|
210
|
+
this.connectionStateMonitor.record(
|
|
211
|
+
CONNECTION_CHANGE.CONNECTION_ESTABLISHED
|
|
212
|
+
);
|
|
213
|
+
}
|
|
214
|
+
|
|
182
215
|
return client;
|
|
183
216
|
}
|
|
184
217
|
|
|
@@ -186,9 +219,19 @@ export class MqttOverWSProvider extends AbstractPubSubProvider {
|
|
|
186
219
|
clientId: string,
|
|
187
220
|
options: MqttProviderOptions = {}
|
|
188
221
|
): Promise<any> {
|
|
189
|
-
return await this.clientsQueue.get(clientId, clientId =>
|
|
190
|
-
this.newClient({ ...options, clientId })
|
|
191
|
-
|
|
222
|
+
return await this.clientsQueue.get(clientId, async clientId => {
|
|
223
|
+
const client = await this.newClient({ ...options, clientId });
|
|
224
|
+
|
|
225
|
+
if (client) {
|
|
226
|
+
// Once connected, subscribe to all topics registered observers
|
|
227
|
+
this._topicObservers.forEach(
|
|
228
|
+
(_value: Set<SubscriptionObserver<any>>, key: string) => {
|
|
229
|
+
client.subscribe(key);
|
|
230
|
+
}
|
|
231
|
+
);
|
|
232
|
+
}
|
|
233
|
+
return client;
|
|
234
|
+
});
|
|
192
235
|
}
|
|
193
236
|
|
|
194
237
|
protected async disconnect(clientId: string): Promise<void> {
|
|
@@ -198,18 +241,25 @@ export class MqttOverWSProvider extends AbstractPubSubProvider {
|
|
|
198
241
|
client.disconnect();
|
|
199
242
|
}
|
|
200
243
|
this.clientsQueue.remove(clientId);
|
|
244
|
+
this.connectionStateMonitor.record(CONNECTION_CHANGE.CLOSED);
|
|
201
245
|
}
|
|
202
246
|
|
|
203
247
|
async publish(topics: string[] | string, msg: any) {
|
|
204
248
|
const targetTopics = ([] as string[]).concat(topics);
|
|
205
249
|
const message = JSON.stringify(msg);
|
|
206
250
|
|
|
207
|
-
const
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
251
|
+
const client = await this.clientsQueue.get(this.clientId);
|
|
252
|
+
|
|
253
|
+
if (client) {
|
|
254
|
+
logger.debug('Publishing to topic(s)', targetTopics.join(','), message);
|
|
255
|
+
targetTopics.forEach(topic => client.send(topic, message));
|
|
256
|
+
} else {
|
|
257
|
+
logger.debug(
|
|
258
|
+
'Publishing to topic(s) failed',
|
|
259
|
+
targetTopics.join(','),
|
|
260
|
+
message
|
|
261
|
+
);
|
|
262
|
+
}
|
|
213
263
|
}
|
|
214
264
|
|
|
215
265
|
protected _topicObservers: Map<string, Set<SubscriptionObserver<any>>> =
|
|
@@ -246,6 +296,7 @@ export class MqttOverWSProvider extends AbstractPubSubProvider {
|
|
|
246
296
|
): Observable<any> {
|
|
247
297
|
const targetTopics = ([] as string[]).concat(topics);
|
|
248
298
|
logger.debug('Subscribing to topic(s)', targetTopics.join(','));
|
|
299
|
+
let reconnectSubscription: ZenObservable.Subscription;
|
|
249
300
|
|
|
250
301
|
return new Observable(observer => {
|
|
251
302
|
targetTopics.forEach(topic => {
|
|
@@ -261,8 +312,6 @@ export class MqttOverWSProvider extends AbstractPubSubProvider {
|
|
|
261
312
|
observersForTopic.add(observer);
|
|
262
313
|
});
|
|
263
314
|
|
|
264
|
-
// @ts-ignore
|
|
265
|
-
let client: Paho.Client;
|
|
266
315
|
const { clientId = this.clientId } = options;
|
|
267
316
|
|
|
268
317
|
// this._clientIdObservers is used to close observers when client gets disconnected
|
|
@@ -274,26 +323,44 @@ export class MqttOverWSProvider extends AbstractPubSubProvider {
|
|
|
274
323
|
this._clientIdObservers.set(clientId, observersForClientId);
|
|
275
324
|
|
|
276
325
|
(async () => {
|
|
277
|
-
const
|
|
326
|
+
const getClient = async () => {
|
|
327
|
+
try {
|
|
328
|
+
const { url = await this.endpoint } = options;
|
|
329
|
+
const client = await this.connect(clientId, { url });
|
|
330
|
+
if (client !== undefined) {
|
|
331
|
+
targetTopics.forEach(topic => {
|
|
332
|
+
client.subscribe(topic);
|
|
333
|
+
});
|
|
334
|
+
}
|
|
335
|
+
} catch (e) {
|
|
336
|
+
logger.debug('Error forming connection', e);
|
|
337
|
+
}
|
|
338
|
+
};
|
|
278
339
|
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
340
|
+
// Establish the initial connection
|
|
341
|
+
await getClient();
|
|
342
|
+
|
|
343
|
+
// Add an observable to the reconnection list to manage reconnection for this subscription
|
|
344
|
+
reconnectSubscription = new Observable(observer => {
|
|
345
|
+
this.reconnectionMonitor.addObserver(observer);
|
|
346
|
+
}).subscribe(() => {
|
|
347
|
+
getClient();
|
|
348
|
+
});
|
|
287
349
|
})();
|
|
288
350
|
|
|
289
|
-
return () => {
|
|
290
|
-
|
|
351
|
+
return async () => {
|
|
352
|
+
const client = await this.clientsQueue.get(clientId);
|
|
353
|
+
|
|
354
|
+
reconnectSubscription?.unsubscribe();
|
|
291
355
|
|
|
292
356
|
if (client) {
|
|
293
357
|
this._clientIdObservers.get(clientId)?.delete(observer);
|
|
294
358
|
// No more observers per client => client not needed anymore
|
|
295
359
|
if (this._clientIdObservers.get(clientId)?.size === 0) {
|
|
296
360
|
this.disconnect(clientId);
|
|
361
|
+
this.connectionStateMonitor.record(
|
|
362
|
+
CONNECTION_CHANGE.CLOSING_CONNECTION
|
|
363
|
+
);
|
|
297
364
|
this._clientIdObservers.delete(clientId);
|
|
298
365
|
}
|
|
299
366
|
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
* and limitations under the License.
|
|
12
12
|
*/
|
|
13
13
|
import Observable from 'zen-observable-ts';
|
|
14
|
-
import { PubSubProvider, ProviderOptions } from '../types';
|
|
14
|
+
import { PubSubProvider, ProviderOptions } from '../types/Provider';
|
|
15
15
|
import { ConsoleLogger as Logger } from '@aws-amplify/core';
|
|
16
16
|
|
|
17
17
|
const logger = new Logger('AbstractPubSubProvider');
|
|
@@ -2,6 +2,8 @@ export const MAX_DELAY_MS = 5000;
|
|
|
2
2
|
|
|
3
3
|
export const NON_RETRYABLE_CODES = [400, 401, 403];
|
|
4
4
|
|
|
5
|
+
export const CONNECTION_STATE_CHANGE = 'ConnectionStateChange';
|
|
6
|
+
|
|
5
7
|
export enum MESSAGE_TYPES {
|
|
6
8
|
/**
|
|
7
9
|
* Client -> Server message.
|
|
@@ -98,3 +100,13 @@ export const DEFAULT_KEEP_ALIVE_TIMEOUT = 5 * 60 * 1000;
|
|
|
98
100
|
* Default Time in milleseconds to alert for missed GQL_CONNECTION_KEEP_ALIVE message
|
|
99
101
|
*/
|
|
100
102
|
export const DEFAULT_KEEP_ALIVE_ALERT_TIMEOUT = 65 * 1000;
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Default delay time in milleseconds between when reconnect is triggered vs when it is attempted
|
|
106
|
+
*/
|
|
107
|
+
export const RECONNECT_DELAY = 5 * 1000;
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Default interval time in milleseconds between when reconnect is re-attempted
|
|
111
|
+
*/
|
|
112
|
+
export const RECONNECT_INTERVAL = 60 * 1000;
|