@aws-amplify/pubsub 4.5.11 → 4.5.12-unstable.4
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 +0 -8
- package/lib/.tsbuildinfo +3 -0
- package/lib/Providers/AWSAppSyncProvider.d.ts +21 -0
- package/lib/Providers/AWSAppSyncProvider.js +20 -102
- package/lib/Providers/AWSAppSyncProvider.js.map +1 -1
- package/lib/Providers/AWSAppSyncRealTimeProvider/index.d.ts +75 -0
- package/lib/Providers/AWSAppSyncRealTimeProvider/index.js +158 -210
- package/lib/Providers/AWSAppSyncRealTimeProvider/index.js.map +1 -1
- package/lib/Providers/AWSIotProvider.d.ts +11 -0
- package/lib/Providers/AWSIotProvider.js +6 -64
- package/lib/Providers/AWSIotProvider.js.map +1 -1
- package/lib/Providers/MqttOverWSProvider.d.ts +41 -0
- package/lib/Providers/MqttOverWSProvider.js +158 -204
- package/lib/Providers/MqttOverWSProvider.js.map +1 -1
- package/lib/Providers/PubSubProvider.d.ts +13 -0
- package/lib/Providers/PubSubProvider.js +3 -13
- package/lib/Providers/PubSubProvider.js.map +1 -1
- package/lib/Providers/constants.d.ts +95 -0
- package/lib/Providers/constants.js +8 -0
- package/lib/Providers/constants.js.map +1 -1
- package/lib/Providers/index.d.ts +5 -0
- package/lib/Providers/index.js +13 -20
- package/lib/Providers/index.js.map +1 -1
- package/lib/PubSub.d.ts +51 -0
- package/lib/PubSub.js +8 -56
- package/lib/PubSub.js.map +1 -1
- package/lib/index.d.ts +4 -0
- package/lib/index.js +9 -20
- package/lib/index.js.map +1 -1
- package/lib/types/Provider.d.ts +14 -0
- package/lib/types/PubSub.d.ts +24 -0
- package/lib/types/PubSub.js +2 -12
- package/lib/types/PubSub.js.map +1 -1
- package/lib/types/index.d.ts +2 -0
- package/lib/types/index.js +2 -4
- package/lib/types/index.js.map +1 -1
- package/lib/utils/ConnectionStateMonitor.d.ts +41 -0
- package/lib/utils/ConnectionStateMonitor.js +16 -29
- package/lib/utils/ConnectionStateMonitor.js.map +1 -1
- package/lib/utils/ReachabilityMonitor/index.d.ts +3 -0
- package/lib/utils/ReachabilityMonitor/index.native.d.ts +3 -0
- package/lib/utils/ReachabilityMonitor/index.native.js +2 -4
- package/lib/utils/ReachabilityMonitor/index.native.js.map +1 -1
- 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/.tsbuildinfo +3 -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 +8 -0
- package/lib-esm/Providers/AWSAppSyncRealTimeProvider/index.js +126 -168
- package/lib-esm/Providers/AWSAppSyncRealTimeProvider/index.js.map +1 -1
- package/lib-esm/Providers/AWSIotProvider.js +3 -61
- package/lib-esm/Providers/AWSIotProvider.js.map +1 -1
- package/lib-esm/Providers/MqttOverWSProvider.d.ts +2 -4
- package/lib-esm/Providers/MqttOverWSProvider.js +139 -175
- package/lib-esm/Providers/MqttOverWSProvider.js.map +1 -1
- package/lib-esm/Providers/PubSubProvider.js +1 -11
- package/lib-esm/Providers/PubSubProvider.js.map +1 -1
- package/lib-esm/Providers/constants.d.ts +8 -0
- package/lib-esm/Providers/constants.js +8 -0
- package/lib-esm/Providers/constants.js.map +1 -1
- package/lib-esm/Providers/index.d.ts +5 -5
- package/lib-esm/Providers/index.js +7 -17
- package/lib-esm/Providers/index.js.map +1 -1
- package/lib-esm/PubSub.js +3 -48
- package/lib-esm/PubSub.js.map +1 -1
- package/lib-esm/index.d.ts +2 -7
- package/lib-esm/index.js +4 -19
- package/lib-esm/index.js.map +1 -1
- package/lib-esm/types/Provider.d.ts +0 -4
- package/lib-esm/types/PubSub.js +2 -12
- package/lib-esm/types/PubSub.js.map +1 -1
- package/lib-esm/types/index.js.map +1 -1
- package/lib-esm/utils/ConnectionStateMonitor.d.ts +1 -0
- package/lib-esm/utils/ConnectionStateMonitor.js +13 -23
- package/lib-esm/utils/ConnectionStateMonitor.js.map +1 -1
- 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 +14 -10
- package/src/Providers/AWSAppSyncProvider.ts +2 -12
- package/src/Providers/AWSAppSyncRealTimeProvider/index.ts +142 -89
- package/src/Providers/AWSIotProvider.ts +2 -12
- package/src/Providers/MqttOverWSProvider.ts +90 -79
- package/src/Providers/PubSubProvider.ts +2 -12
- package/src/Providers/constants.ts +10 -0
- package/src/Providers/index.ts +15 -17
- package/src/PubSub.ts +2 -12
- package/src/index.ts +15 -23
- package/src/types/Provider.ts +2 -17
- package/src/types/PubSub.ts +2 -12
- package/src/types/index.ts +2 -12
- package/src/utils/ConnectionStateMonitor.ts +16 -12
- package/src/utils/ReconnectionMonitor.ts +74 -0
- package/build.js +0 -5
- package/dist/aws-amplify-pubsub.js +0 -10314
- package/dist/aws-amplify-pubsub.js.map +0 -1
- package/dist/aws-amplify-pubsub.min.js +0 -11
- package/dist/aws-amplify-pubsub.min.js.map +0 -1
- package/index.js +0 -7
- package/webpack.config.dev.js +0 -6
|
@@ -1,15 +1,5 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
*
|
|
4
|
-
* Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance with
|
|
5
|
-
* the License. A copy of the License is located at
|
|
6
|
-
*
|
|
7
|
-
* http://aws.amazon.com/apache2.0/
|
|
8
|
-
*
|
|
9
|
-
* or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
|
10
|
-
* CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions
|
|
11
|
-
* and limitations under the License.
|
|
12
|
-
*/
|
|
1
|
+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
13
3
|
import Observable, { ZenObservable } from 'zen-observable-ts';
|
|
14
4
|
import { GraphQLError } from 'graphql';
|
|
15
5
|
import * as url from 'url';
|
|
@@ -26,11 +16,12 @@ import {
|
|
|
26
16
|
jitteredExponentialRetry,
|
|
27
17
|
NonRetryableError,
|
|
28
18
|
ICredentials,
|
|
19
|
+
isNonRetryableError,
|
|
29
20
|
} from '@aws-amplify/core';
|
|
30
|
-
import Cache from '@aws-amplify/cache';
|
|
31
|
-
import Auth,
|
|
21
|
+
import { Cache } from '@aws-amplify/cache';
|
|
22
|
+
import { Auth, GRAPHQL_AUTH_MODE } from '@aws-amplify/auth';
|
|
32
23
|
import { AbstractPubSubProvider } from '../PubSubProvider';
|
|
33
|
-
import { CONTROL_MSG } from '../../types/PubSub';
|
|
24
|
+
import { CONTROL_MSG, ConnectionState } from '../../types/PubSub';
|
|
34
25
|
|
|
35
26
|
import {
|
|
36
27
|
AMPLIFY_SYMBOL,
|
|
@@ -50,6 +41,10 @@ import {
|
|
|
50
41
|
ConnectionStateMonitor,
|
|
51
42
|
CONNECTION_CHANGE,
|
|
52
43
|
} from '../../utils/ConnectionStateMonitor';
|
|
44
|
+
import {
|
|
45
|
+
ReconnectEvent,
|
|
46
|
+
ReconnectionMonitor,
|
|
47
|
+
} from '../../utils/ReconnectionMonitor';
|
|
53
48
|
|
|
54
49
|
const logger = new Logger('AWSAppSyncRealTimeProvider');
|
|
55
50
|
|
|
@@ -99,23 +94,63 @@ export class AWSAppSyncRealTimeProvider extends AbstractPubSubProvider {
|
|
|
99
94
|
private keepAliveAlertTimeoutId?: ReturnType<typeof setTimeout>;
|
|
100
95
|
private subscriptionObserverMap: Map<string, ObserverQuery> = new Map();
|
|
101
96
|
private promiseArray: Array<{ res: Function; rej: Function }> = [];
|
|
97
|
+
private connectionState: ConnectionState;
|
|
102
98
|
private readonly connectionStateMonitor = new ConnectionStateMonitor();
|
|
99
|
+
private readonly reconnectionMonitor = new ReconnectionMonitor();
|
|
100
|
+
private connectionStateMonitorSubscription: ZenObservable.Subscription;
|
|
103
101
|
|
|
104
102
|
constructor(options: ProviderOptions = {}) {
|
|
105
103
|
super(options);
|
|
106
104
|
// Monitor the connection state and pass changes along to Hub
|
|
107
|
-
this.
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
105
|
+
this.connectionStateMonitorSubscription =
|
|
106
|
+
this.connectionStateMonitor.connectionStateObservable.subscribe(
|
|
107
|
+
connectionState => {
|
|
108
|
+
dispatchApiEvent(
|
|
109
|
+
CONNECTION_STATE_CHANGE,
|
|
110
|
+
{
|
|
111
|
+
provider: this,
|
|
112
|
+
connectionState,
|
|
113
|
+
},
|
|
114
|
+
`Connection state is ${connectionState}`
|
|
115
|
+
);
|
|
116
|
+
this.connectionState = connectionState;
|
|
117
|
+
|
|
118
|
+
// Trigger START_RECONNECT when the connection is disrupted
|
|
119
|
+
if (connectionState === ConnectionState.ConnectionDisrupted) {
|
|
120
|
+
this.reconnectionMonitor.record(ReconnectEvent.START_RECONNECT);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Trigger HALT_RECONNECT to halt reconnection attempts when the state is anything other than
|
|
124
|
+
// ConnectionDisrupted or Connecting
|
|
125
|
+
if (
|
|
126
|
+
[
|
|
127
|
+
ConnectionState.Connected,
|
|
128
|
+
ConnectionState.ConnectedPendingDisconnect,
|
|
129
|
+
ConnectionState.ConnectedPendingKeepAlive,
|
|
130
|
+
ConnectionState.ConnectedPendingNetwork,
|
|
131
|
+
ConnectionState.ConnectedPendingNetwork,
|
|
132
|
+
ConnectionState.ConnectionDisruptedPendingNetwork,
|
|
133
|
+
ConnectionState.Disconnected,
|
|
134
|
+
].includes(connectionState)
|
|
135
|
+
) {
|
|
136
|
+
this.reconnectionMonitor.record(ReconnectEvent.HALT_RECONNECT);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Mark the socket closed and release all active listeners
|
|
144
|
+
*/
|
|
145
|
+
close() {
|
|
146
|
+
// Mark the socket closed both in status and the connection monitor
|
|
147
|
+
this.socketStatus = SOCKET_STATUS.CLOSED;
|
|
148
|
+
this.connectionStateMonitor.record(CONNECTION_CHANGE.CONNECTION_FAILED);
|
|
149
|
+
|
|
150
|
+
// Turn off the subscription monitor Hub publishing
|
|
151
|
+
this.connectionStateMonitorSubscription.unsubscribe();
|
|
152
|
+
// Complete all reconnect observers
|
|
153
|
+
this.reconnectionMonitor.close();
|
|
119
154
|
}
|
|
120
155
|
|
|
121
156
|
getNewWebSocket(url, protocol) {
|
|
@@ -158,26 +193,44 @@ export class AWSAppSyncRealTimeProvider extends AbstractPubSubProvider {
|
|
|
158
193
|
});
|
|
159
194
|
observer.complete();
|
|
160
195
|
} else {
|
|
196
|
+
let subscriptionStartActive = false;
|
|
161
197
|
const subscriptionId = uuid();
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
198
|
+
const startSubscription = () => {
|
|
199
|
+
if (!subscriptionStartActive) {
|
|
200
|
+
subscriptionStartActive = true;
|
|
201
|
+
const startSubscriptionPromise =
|
|
202
|
+
this._startSubscriptionWithAWSAppSyncRealTime({
|
|
203
|
+
options,
|
|
204
|
+
observer,
|
|
205
|
+
subscriptionId,
|
|
206
|
+
}).catch<any>(err => {
|
|
207
|
+
logger.debug(
|
|
171
208
|
`${CONTROL_MSG.REALTIME_SUBSCRIPTION_INIT_ERROR}: ${err}`
|
|
172
|
-
)
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
209
|
+
);
|
|
210
|
+
|
|
211
|
+
this.connectionStateMonitor.record(CONNECTION_CHANGE.CLOSED);
|
|
212
|
+
});
|
|
213
|
+
startSubscriptionPromise.finally(() => {
|
|
214
|
+
subscriptionStartActive = false;
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
};
|
|
218
|
+
|
|
219
|
+
let reconnectSubscription: ZenObservable.Subscription;
|
|
220
|
+
|
|
221
|
+
// Add an observable to the reconnection list to manage reconnection for this subscription
|
|
222
|
+
reconnectSubscription = new Observable(observer => {
|
|
223
|
+
this.reconnectionMonitor.addObserver(observer);
|
|
224
|
+
}).subscribe(() => {
|
|
225
|
+
startSubscription();
|
|
178
226
|
});
|
|
179
227
|
|
|
228
|
+
startSubscription();
|
|
229
|
+
|
|
180
230
|
return async () => {
|
|
231
|
+
// Cleanup reconnection subscription
|
|
232
|
+
reconnectSubscription?.unsubscribe();
|
|
233
|
+
|
|
181
234
|
// Cleanup after unsubscribing or observer.complete was called after _startSubscriptionWithAWSAppSyncRealTime
|
|
182
235
|
try {
|
|
183
236
|
// Waiting that subscription has been connected before trying to unsubscribe
|
|
@@ -288,24 +341,7 @@ export class AWSAppSyncRealTimeProvider extends AbstractPubSubProvider {
|
|
|
288
341
|
additionalHeaders,
|
|
289
342
|
});
|
|
290
343
|
} catch (err) {
|
|
291
|
-
|
|
292
|
-
const message = err['message'] ?? '';
|
|
293
|
-
this.connectionStateMonitor.record(CONNECTION_CHANGE.CLOSED);
|
|
294
|
-
observer.error({
|
|
295
|
-
errors: [
|
|
296
|
-
{
|
|
297
|
-
...new GraphQLError(`${CONTROL_MSG.CONNECTION_FAILED}: ${message}`),
|
|
298
|
-
},
|
|
299
|
-
],
|
|
300
|
-
});
|
|
301
|
-
observer.complete();
|
|
302
|
-
const { subscriptionFailedCallback } =
|
|
303
|
-
this.subscriptionObserverMap.get(subscriptionId) || {};
|
|
304
|
-
|
|
305
|
-
// Notify concurrent unsubscription
|
|
306
|
-
if (typeof subscriptionFailedCallback === 'function') {
|
|
307
|
-
subscriptionFailedCallback();
|
|
308
|
-
}
|
|
344
|
+
this._logStartSubscriptionError(subscriptionId, observer, err);
|
|
309
345
|
return;
|
|
310
346
|
}
|
|
311
347
|
|
|
@@ -333,6 +369,44 @@ export class AWSAppSyncRealTimeProvider extends AbstractPubSubProvider {
|
|
|
333
369
|
}
|
|
334
370
|
}
|
|
335
371
|
|
|
372
|
+
// Log logic for start subscription failures
|
|
373
|
+
private _logStartSubscriptionError(subscriptionId, observer, err) {
|
|
374
|
+
logger.debug({ err });
|
|
375
|
+
const message = err['message'] ?? '';
|
|
376
|
+
// Resolving to give the state observer time to propogate the update
|
|
377
|
+
Promise.resolve(
|
|
378
|
+
this.connectionStateMonitor.record(CONNECTION_CHANGE.CLOSED)
|
|
379
|
+
);
|
|
380
|
+
|
|
381
|
+
// Capture the error only when the network didn't cause disruption
|
|
382
|
+
if (
|
|
383
|
+
this.connectionState !== ConnectionState.ConnectionDisruptedPendingNetwork
|
|
384
|
+
) {
|
|
385
|
+
// When the error is non-retriable, error out the observable
|
|
386
|
+
if (isNonRetryableError(err)) {
|
|
387
|
+
observer.error({
|
|
388
|
+
errors: [
|
|
389
|
+
{
|
|
390
|
+
...new GraphQLError(
|
|
391
|
+
`${CONTROL_MSG.CONNECTION_FAILED}: ${message}`
|
|
392
|
+
),
|
|
393
|
+
},
|
|
394
|
+
],
|
|
395
|
+
});
|
|
396
|
+
} else {
|
|
397
|
+
logger.debug(`${CONTROL_MSG.CONNECTION_FAILED}: ${message}`);
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
const { subscriptionFailedCallback } =
|
|
401
|
+
this.subscriptionObserverMap.get(subscriptionId) || {};
|
|
402
|
+
|
|
403
|
+
// Notify concurrent unsubscription
|
|
404
|
+
if (typeof subscriptionFailedCallback === 'function') {
|
|
405
|
+
subscriptionFailedCallback();
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
|
|
336
410
|
// Waiting that subscription has been connected before trying to unsubscribe
|
|
337
411
|
private async _waitForSubscriptionToBeConnected(subscriptionId: string) {
|
|
338
412
|
const subscriptionObserver =
|
|
@@ -505,6 +579,10 @@ export class AWSAppSyncRealTimeProvider extends AbstractPubSubProvider {
|
|
|
505
579
|
subscriptionState,
|
|
506
580
|
});
|
|
507
581
|
|
|
582
|
+
logger.debug(
|
|
583
|
+
`${CONTROL_MSG.CONNECTION_FAILED}: ${JSON.stringify(payload)}`
|
|
584
|
+
);
|
|
585
|
+
|
|
508
586
|
observer.error({
|
|
509
587
|
errors: [
|
|
510
588
|
{
|
|
@@ -514,9 +592,9 @@ export class AWSAppSyncRealTimeProvider extends AbstractPubSubProvider {
|
|
|
514
592
|
},
|
|
515
593
|
],
|
|
516
594
|
});
|
|
595
|
+
|
|
517
596
|
if (startAckTimeoutId) clearTimeout(startAckTimeoutId);
|
|
518
597
|
|
|
519
|
-
observer.complete();
|
|
520
598
|
if (typeof subscriptionFailedCallback === 'function') {
|
|
521
599
|
subscriptionFailedCallback();
|
|
522
600
|
}
|
|
@@ -526,14 +604,7 @@ export class AWSAppSyncRealTimeProvider extends AbstractPubSubProvider {
|
|
|
526
604
|
|
|
527
605
|
private _errorDisconnect(msg: string) {
|
|
528
606
|
logger.debug(`Disconnect error: ${msg}`);
|
|
529
|
-
|
|
530
|
-
if (observer && !observer.closed) {
|
|
531
|
-
observer.error({
|
|
532
|
-
errors: [{ ...new GraphQLError(msg) }],
|
|
533
|
-
});
|
|
534
|
-
}
|
|
535
|
-
});
|
|
536
|
-
this.subscriptionObserverMap.clear();
|
|
607
|
+
|
|
537
608
|
if (this.awsRealTimeSocket) {
|
|
538
609
|
this.connectionStateMonitor.record(CONNECTION_CHANGE.CLOSED);
|
|
539
610
|
this.awsRealTimeSocket.close();
|
|
@@ -557,22 +628,7 @@ export class AWSAppSyncRealTimeProvider extends AbstractPubSubProvider {
|
|
|
557
628
|
subscriptionState: SUBSCRIPTION_STATUS.FAILED,
|
|
558
629
|
});
|
|
559
630
|
|
|
560
|
-
|
|
561
|
-
observer.error({
|
|
562
|
-
errors: [
|
|
563
|
-
{
|
|
564
|
-
...new GraphQLError(
|
|
565
|
-
`Subscription timeout ${JSON.stringify({
|
|
566
|
-
query,
|
|
567
|
-
variables,
|
|
568
|
-
})}`
|
|
569
|
-
),
|
|
570
|
-
},
|
|
571
|
-
],
|
|
572
|
-
});
|
|
573
|
-
// Cleanup will be automatically executed
|
|
574
|
-
observer.complete();
|
|
575
|
-
}
|
|
631
|
+
this.connectionStateMonitor.record(CONNECTION_CHANGE.CLOSED);
|
|
576
632
|
logger.debug(
|
|
577
633
|
'timeoutStartSubscription',
|
|
578
634
|
JSON.stringify({ query, variables })
|
|
@@ -641,6 +697,7 @@ export class AWSAppSyncRealTimeProvider extends AbstractPubSubProvider {
|
|
|
641
697
|
this.socketStatus = SOCKET_STATUS.READY;
|
|
642
698
|
this.promiseArray = [];
|
|
643
699
|
} catch (err) {
|
|
700
|
+
logger.debug('Connection exited with', err);
|
|
644
701
|
this.promiseArray.forEach(({ rej }) => rej(err));
|
|
645
702
|
this.promiseArray = [];
|
|
646
703
|
if (
|
|
@@ -677,9 +734,6 @@ export class AWSAppSyncRealTimeProvider extends AbstractPubSubProvider {
|
|
|
677
734
|
logger.debug(`WebSocket connection error`);
|
|
678
735
|
};
|
|
679
736
|
newSocket.onclose = () => {
|
|
680
|
-
this.connectionStateMonitor.record(
|
|
681
|
-
CONNECTION_CHANGE.CONNECTION_FAILED
|
|
682
|
-
);
|
|
683
737
|
rej(new Error('Connection handshake error'));
|
|
684
738
|
};
|
|
685
739
|
newSocket.onopen = () => {
|
|
@@ -688,7 +742,6 @@ export class AWSAppSyncRealTimeProvider extends AbstractPubSubProvider {
|
|
|
688
742
|
};
|
|
689
743
|
});
|
|
690
744
|
})();
|
|
691
|
-
|
|
692
745
|
// Step 2: wait for ack from AWS AppSyncReaTime after sending init
|
|
693
746
|
await (() => {
|
|
694
747
|
return new Promise((res, rej) => {
|
|
@@ -1,15 +1,5 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
*
|
|
4
|
-
* Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance with
|
|
5
|
-
* the License. A copy of the License is located at
|
|
6
|
-
*
|
|
7
|
-
* http://aws.amazon.com/apache2.0/
|
|
8
|
-
*
|
|
9
|
-
* or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
|
10
|
-
* CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions
|
|
11
|
-
* and limitations under the License.
|
|
12
|
-
*/
|
|
1
|
+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
13
3
|
import { MqttOverWSProvider, MqttProviderOptions } from './MqttOverWSProvider';
|
|
14
4
|
import { Signer, Credentials } from '@aws-amplify/core';
|
|
15
5
|
|
|
@@ -1,27 +1,21 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
*
|
|
4
|
-
* Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance with
|
|
5
|
-
* the License. A copy of the License is located at
|
|
6
|
-
*
|
|
7
|
-
* http://aws.amazon.com/apache2.0/
|
|
8
|
-
*
|
|
9
|
-
* or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
|
10
|
-
* CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions
|
|
11
|
-
* and limitations under the License.
|
|
12
|
-
*/
|
|
1
|
+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
13
3
|
import * as Paho from 'paho-mqtt';
|
|
14
4
|
import { v4 as uuid } from 'uuid';
|
|
15
|
-
import Observable from 'zen-observable-ts';
|
|
5
|
+
import Observable, { ZenObservable } from 'zen-observable-ts';
|
|
16
6
|
|
|
17
7
|
import { AbstractPubSubProvider } from './PubSubProvider';
|
|
18
|
-
import { SubscriptionObserver } from '../types/PubSub';
|
|
8
|
+
import { SubscriptionObserver, ConnectionState } from '../types/PubSub';
|
|
19
9
|
import { ProviderOptions } from '../types/Provider';
|
|
20
10
|
import { ConsoleLogger as Logger, Hub } from '@aws-amplify/core';
|
|
21
11
|
import {
|
|
22
12
|
ConnectionStateMonitor,
|
|
23
13
|
CONNECTION_CHANGE,
|
|
24
14
|
} from '../utils/ConnectionStateMonitor';
|
|
15
|
+
import {
|
|
16
|
+
ReconnectEvent,
|
|
17
|
+
ReconnectionMonitor,
|
|
18
|
+
} from '../utils/ReconnectionMonitor';
|
|
25
19
|
import { AMPLIFY_SYMBOL, CONNECTION_STATE_CHANGE } from './constants';
|
|
26
20
|
|
|
27
21
|
const logger = new Logger('MqttOverWSProvider');
|
|
@@ -45,27 +39,20 @@ export interface MqttProviderOptions extends ProviderOptions {
|
|
|
45
39
|
url?: string;
|
|
46
40
|
}
|
|
47
41
|
|
|
48
|
-
/**
|
|
49
|
-
* @deprecated Migrated to MqttProviderOptions
|
|
50
|
-
*/
|
|
51
|
-
export type MqttProvidertOptions = MqttProviderOptions;
|
|
52
|
-
|
|
53
42
|
class ClientsQueue {
|
|
54
43
|
private promises: Map<string, Promise<any>> = new Map();
|
|
55
44
|
|
|
56
45
|
async get(clientId: string, clientFactory?: (input: string) => Promise<any>) {
|
|
57
46
|
const cachedPromise = this.promises.get(clientId);
|
|
58
|
-
if (cachedPromise)
|
|
59
|
-
return cachedPromise;
|
|
60
|
-
}
|
|
47
|
+
if (cachedPromise) return cachedPromise;
|
|
61
48
|
|
|
62
49
|
if (clientFactory) {
|
|
63
50
|
const newPromise = clientFactory(clientId);
|
|
64
|
-
|
|
65
51
|
this.promises.set(clientId, newPromise);
|
|
66
|
-
|
|
52
|
+
newPromise.catch(v => this.promises.delete(clientId));
|
|
67
53
|
return newPromise;
|
|
68
54
|
}
|
|
55
|
+
|
|
69
56
|
return undefined;
|
|
70
57
|
}
|
|
71
58
|
|
|
@@ -86,7 +73,9 @@ const topicSymbol = typeof Symbol !== 'undefined' ? Symbol('topic') : '@@topic';
|
|
|
86
73
|
|
|
87
74
|
export class MqttOverWSProvider extends AbstractPubSubProvider {
|
|
88
75
|
private _clientsQueue = new ClientsQueue();
|
|
76
|
+
private connectionState: ConnectionState;
|
|
89
77
|
private readonly connectionStateMonitor = new ConnectionStateMonitor();
|
|
78
|
+
private readonly reconnectionMonitor = new ReconnectionMonitor();
|
|
90
79
|
|
|
91
80
|
constructor(options: MqttProviderOptions = {}) {
|
|
92
81
|
super({ ...options, clientId: options.clientId || uuid() });
|
|
@@ -102,6 +91,16 @@ export class MqttOverWSProvider extends AbstractPubSubProvider {
|
|
|
102
91
|
},
|
|
103
92
|
`Connection state is ${connectionStateChange}`
|
|
104
93
|
);
|
|
94
|
+
|
|
95
|
+
this.connectionState = connectionStateChange;
|
|
96
|
+
|
|
97
|
+
// Trigger reconnection when the connection is disrupted
|
|
98
|
+
if (connectionStateChange === ConnectionState.ConnectionDisrupted) {
|
|
99
|
+
this.reconnectionMonitor.record(ReconnectEvent.START_RECONNECT);
|
|
100
|
+
} else if (connectionStateChange !== ConnectionState.Connecting) {
|
|
101
|
+
// Trigger connected to halt reconnection attempts
|
|
102
|
+
this.reconnectionMonitor.record(ReconnectEvent.HALT_RECONNECT);
|
|
103
|
+
}
|
|
105
104
|
}
|
|
106
105
|
);
|
|
107
106
|
}
|
|
@@ -142,7 +141,6 @@ export class MqttOverWSProvider extends AbstractPubSubProvider {
|
|
|
142
141
|
if (errorCode !== 0) {
|
|
143
142
|
logger.warn(clientId, JSON.stringify({ errorCode, ...args }, null, 2));
|
|
144
143
|
|
|
145
|
-
const topicsToDelete: string[] = [];
|
|
146
144
|
if (!clientId) {
|
|
147
145
|
return;
|
|
148
146
|
}
|
|
@@ -150,24 +148,7 @@ export class MqttOverWSProvider extends AbstractPubSubProvider {
|
|
|
150
148
|
if (!clientIdObservers) {
|
|
151
149
|
return;
|
|
152
150
|
}
|
|
153
|
-
|
|
154
|
-
observer.error('Disconnected, error code: ' + errorCode);
|
|
155
|
-
// removing observers for disconnected clientId
|
|
156
|
-
this._topicObservers.forEach((observerForTopic, observerTopic) => {
|
|
157
|
-
observerForTopic.delete(observer);
|
|
158
|
-
if (observerForTopic.size === 0) {
|
|
159
|
-
topicsToDelete.push(observerTopic);
|
|
160
|
-
}
|
|
161
|
-
});
|
|
162
|
-
});
|
|
163
|
-
|
|
164
|
-
// forgiving any trace of clientId
|
|
165
|
-
this._clientIdObservers.delete(clientId);
|
|
166
|
-
|
|
167
|
-
// Removing topics that are not listen by an observer
|
|
168
|
-
topicsToDelete.forEach(topic => {
|
|
169
|
-
this._topicObservers.delete(topic);
|
|
170
|
-
});
|
|
151
|
+
this.disconnect(clientId);
|
|
171
152
|
}
|
|
172
153
|
}
|
|
173
154
|
|
|
@@ -177,7 +158,7 @@ export class MqttOverWSProvider extends AbstractPubSubProvider {
|
|
|
177
158
|
this.connectionStateMonitor.record(CONNECTION_CHANGE.OPENING_CONNECTION);
|
|
178
159
|
// @ts-ignore
|
|
179
160
|
const client = new Paho.Client(url, clientId);
|
|
180
|
-
|
|
161
|
+
|
|
181
162
|
client.onMessageArrived = ({
|
|
182
163
|
destinationName: topic,
|
|
183
164
|
payloadString: msg,
|
|
@@ -197,23 +178,24 @@ export class MqttOverWSProvider extends AbstractPubSubProvider {
|
|
|
197
178
|
this.connectionStateMonitor.record(CONNECTION_CHANGE.CLOSED);
|
|
198
179
|
};
|
|
199
180
|
|
|
200
|
-
await new Promise((resolve, reject) => {
|
|
181
|
+
const connected = await new Promise((resolve, reject) => {
|
|
201
182
|
client.connect({
|
|
202
183
|
useSSL: this.isSSLEnabled,
|
|
203
184
|
mqttVersion: 3,
|
|
204
|
-
onSuccess: () => resolve(
|
|
205
|
-
onFailure:
|
|
206
|
-
|
|
207
|
-
this.connectionStateMonitor.record(
|
|
208
|
-
|
|
209
|
-
);
|
|
185
|
+
onSuccess: () => resolve(true),
|
|
186
|
+
onFailure: x => {
|
|
187
|
+
if (clientId) this._clientsQueue.remove(clientId);
|
|
188
|
+
this.connectionStateMonitor.record(CONNECTION_CHANGE.CLOSED);
|
|
189
|
+
resolve(false);
|
|
210
190
|
},
|
|
211
191
|
});
|
|
212
192
|
});
|
|
213
193
|
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
194
|
+
if (connected) {
|
|
195
|
+
this.connectionStateMonitor.record(
|
|
196
|
+
CONNECTION_CHANGE.CONNECTION_ESTABLISHED
|
|
197
|
+
);
|
|
198
|
+
}
|
|
217
199
|
|
|
218
200
|
return client;
|
|
219
201
|
}
|
|
@@ -222,9 +204,19 @@ export class MqttOverWSProvider extends AbstractPubSubProvider {
|
|
|
222
204
|
clientId: string,
|
|
223
205
|
options: MqttProviderOptions = {}
|
|
224
206
|
): Promise<any> {
|
|
225
|
-
return await this.clientsQueue.get(clientId, clientId =>
|
|
226
|
-
this.newClient({ ...options, clientId })
|
|
227
|
-
|
|
207
|
+
return await this.clientsQueue.get(clientId, async clientId => {
|
|
208
|
+
const client = await this.newClient({ ...options, clientId });
|
|
209
|
+
|
|
210
|
+
if (client) {
|
|
211
|
+
// Once connected, subscribe to all topics registered observers
|
|
212
|
+
this._topicObservers.forEach(
|
|
213
|
+
(_value: Set<SubscriptionObserver<any>>, key: string) => {
|
|
214
|
+
client.subscribe(key);
|
|
215
|
+
}
|
|
216
|
+
);
|
|
217
|
+
}
|
|
218
|
+
return client;
|
|
219
|
+
});
|
|
228
220
|
}
|
|
229
221
|
|
|
230
222
|
protected async disconnect(clientId: string): Promise<void> {
|
|
@@ -232,21 +224,27 @@ export class MqttOverWSProvider extends AbstractPubSubProvider {
|
|
|
232
224
|
|
|
233
225
|
if (client && client.isConnected()) {
|
|
234
226
|
client.disconnect();
|
|
235
|
-
this.connectionStateMonitor.record(CONNECTION_CHANGE.CLOSED);
|
|
236
227
|
}
|
|
237
228
|
this.clientsQueue.remove(clientId);
|
|
229
|
+
this.connectionStateMonitor.record(CONNECTION_CHANGE.CLOSED);
|
|
238
230
|
}
|
|
239
231
|
|
|
240
232
|
async publish(topics: string[] | string, msg: any) {
|
|
241
233
|
const targetTopics = ([] as string[]).concat(topics);
|
|
242
234
|
const message = JSON.stringify(msg);
|
|
243
235
|
|
|
244
|
-
const
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
236
|
+
const client = await this.clientsQueue.get(this.clientId);
|
|
237
|
+
|
|
238
|
+
if (client) {
|
|
239
|
+
logger.debug('Publishing to topic(s)', targetTopics.join(','), message);
|
|
240
|
+
targetTopics.forEach(topic => client.send(topic, message));
|
|
241
|
+
} else {
|
|
242
|
+
logger.debug(
|
|
243
|
+
'Publishing to topic(s) failed',
|
|
244
|
+
targetTopics.join(','),
|
|
245
|
+
message
|
|
246
|
+
);
|
|
247
|
+
}
|
|
250
248
|
}
|
|
251
249
|
|
|
252
250
|
protected _topicObservers: Map<string, Set<SubscriptionObserver<any>>> =
|
|
@@ -283,6 +281,7 @@ export class MqttOverWSProvider extends AbstractPubSubProvider {
|
|
|
283
281
|
): Observable<any> {
|
|
284
282
|
const targetTopics = ([] as string[]).concat(topics);
|
|
285
283
|
logger.debug('Subscribing to topic(s)', targetTopics.join(','));
|
|
284
|
+
let reconnectSubscription: ZenObservable.Subscription;
|
|
286
285
|
|
|
287
286
|
return new Observable(observer => {
|
|
288
287
|
targetTopics.forEach(topic => {
|
|
@@ -298,8 +297,6 @@ export class MqttOverWSProvider extends AbstractPubSubProvider {
|
|
|
298
297
|
observersForTopic.add(observer);
|
|
299
298
|
});
|
|
300
299
|
|
|
301
|
-
// @ts-ignore
|
|
302
|
-
let client: Paho.Client;
|
|
303
300
|
const { clientId = this.clientId } = options;
|
|
304
301
|
|
|
305
302
|
// this._clientIdObservers is used to close observers when client gets disconnected
|
|
@@ -311,30 +308,44 @@ export class MqttOverWSProvider extends AbstractPubSubProvider {
|
|
|
311
308
|
this._clientIdObservers.set(clientId, observersForClientId);
|
|
312
309
|
|
|
313
310
|
(async () => {
|
|
314
|
-
const
|
|
311
|
+
const getClient = async () => {
|
|
312
|
+
try {
|
|
313
|
+
const { url = await this.endpoint } = options;
|
|
314
|
+
const client = await this.connect(clientId, { url });
|
|
315
|
+
if (client !== undefined) {
|
|
316
|
+
targetTopics.forEach(topic => {
|
|
317
|
+
client.subscribe(topic);
|
|
318
|
+
});
|
|
319
|
+
}
|
|
320
|
+
} catch (e) {
|
|
321
|
+
logger.debug('Error forming connection', e);
|
|
322
|
+
}
|
|
323
|
+
};
|
|
315
324
|
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
325
|
+
// Establish the initial connection
|
|
326
|
+
await getClient();
|
|
327
|
+
|
|
328
|
+
// Add an observable to the reconnection list to manage reconnection for this subscription
|
|
329
|
+
reconnectSubscription = new Observable(observer => {
|
|
330
|
+
this.reconnectionMonitor.addObserver(observer);
|
|
331
|
+
}).subscribe(() => {
|
|
332
|
+
getClient();
|
|
333
|
+
});
|
|
324
334
|
})();
|
|
325
335
|
|
|
326
|
-
return () => {
|
|
327
|
-
|
|
336
|
+
return async () => {
|
|
337
|
+
const client = await this.clientsQueue.get(clientId);
|
|
338
|
+
|
|
339
|
+
reconnectSubscription?.unsubscribe();
|
|
328
340
|
|
|
329
341
|
if (client) {
|
|
330
342
|
this._clientIdObservers.get(clientId)?.delete(observer);
|
|
331
343
|
// No more observers per client => client not needed anymore
|
|
332
344
|
if (this._clientIdObservers.get(clientId)?.size === 0) {
|
|
345
|
+
this.disconnect(clientId);
|
|
333
346
|
this.connectionStateMonitor.record(
|
|
334
347
|
CONNECTION_CHANGE.CLOSING_CONNECTION
|
|
335
348
|
);
|
|
336
|
-
|
|
337
|
-
this.disconnect(clientId);
|
|
338
349
|
this._clientIdObservers.delete(clientId);
|
|
339
350
|
}
|
|
340
351
|
|
|
@@ -1,15 +1,5 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
*
|
|
4
|
-
* Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance with
|
|
5
|
-
* the License. A copy of the License is located at
|
|
6
|
-
*
|
|
7
|
-
* http://aws.amazon.com/apache2.0/
|
|
8
|
-
*
|
|
9
|
-
* or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
|
10
|
-
* CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions
|
|
11
|
-
* and limitations under the License.
|
|
12
|
-
*/
|
|
1
|
+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
13
3
|
import Observable from 'zen-observable-ts';
|
|
14
4
|
import { PubSubProvider, ProviderOptions } from '../types/Provider';
|
|
15
5
|
import { ConsoleLogger as Logger } from '@aws-amplify/core';
|
|
@@ -100,3 +100,13 @@ export const DEFAULT_KEEP_ALIVE_TIMEOUT = 5 * 60 * 1000;
|
|
|
100
100
|
* Default Time in milleseconds to alert for missed GQL_CONNECTION_KEEP_ALIVE message
|
|
101
101
|
*/
|
|
102
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;
|