@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.
Files changed (102) hide show
  1. package/CHANGELOG.md +0 -8
  2. package/lib/.tsbuildinfo +3 -0
  3. package/lib/Providers/AWSAppSyncProvider.d.ts +21 -0
  4. package/lib/Providers/AWSAppSyncProvider.js +20 -102
  5. package/lib/Providers/AWSAppSyncProvider.js.map +1 -1
  6. package/lib/Providers/AWSAppSyncRealTimeProvider/index.d.ts +75 -0
  7. package/lib/Providers/AWSAppSyncRealTimeProvider/index.js +158 -210
  8. package/lib/Providers/AWSAppSyncRealTimeProvider/index.js.map +1 -1
  9. package/lib/Providers/AWSIotProvider.d.ts +11 -0
  10. package/lib/Providers/AWSIotProvider.js +6 -64
  11. package/lib/Providers/AWSIotProvider.js.map +1 -1
  12. package/lib/Providers/MqttOverWSProvider.d.ts +41 -0
  13. package/lib/Providers/MqttOverWSProvider.js +158 -204
  14. package/lib/Providers/MqttOverWSProvider.js.map +1 -1
  15. package/lib/Providers/PubSubProvider.d.ts +13 -0
  16. package/lib/Providers/PubSubProvider.js +3 -13
  17. package/lib/Providers/PubSubProvider.js.map +1 -1
  18. package/lib/Providers/constants.d.ts +95 -0
  19. package/lib/Providers/constants.js +8 -0
  20. package/lib/Providers/constants.js.map +1 -1
  21. package/lib/Providers/index.d.ts +5 -0
  22. package/lib/Providers/index.js +13 -20
  23. package/lib/Providers/index.js.map +1 -1
  24. package/lib/PubSub.d.ts +51 -0
  25. package/lib/PubSub.js +8 -56
  26. package/lib/PubSub.js.map +1 -1
  27. package/lib/index.d.ts +4 -0
  28. package/lib/index.js +9 -20
  29. package/lib/index.js.map +1 -1
  30. package/lib/types/Provider.d.ts +14 -0
  31. package/lib/types/PubSub.d.ts +24 -0
  32. package/lib/types/PubSub.js +2 -12
  33. package/lib/types/PubSub.js.map +1 -1
  34. package/lib/types/index.d.ts +2 -0
  35. package/lib/types/index.js +2 -4
  36. package/lib/types/index.js.map +1 -1
  37. package/lib/utils/ConnectionStateMonitor.d.ts +41 -0
  38. package/lib/utils/ConnectionStateMonitor.js +16 -29
  39. package/lib/utils/ConnectionStateMonitor.js.map +1 -1
  40. package/lib/utils/ReachabilityMonitor/index.d.ts +3 -0
  41. package/lib/utils/ReachabilityMonitor/index.native.d.ts +3 -0
  42. package/lib/utils/ReachabilityMonitor/index.native.js +2 -4
  43. package/lib/utils/ReachabilityMonitor/index.native.js.map +1 -1
  44. package/lib/utils/ReconnectionMonitor.d.ts +29 -0
  45. package/lib/utils/ReconnectionMonitor.js +73 -0
  46. package/lib/utils/ReconnectionMonitor.js.map +1 -0
  47. package/lib-esm/.tsbuildinfo +3 -0
  48. package/lib-esm/Providers/AWSAppSyncProvider.js +1 -80
  49. package/lib-esm/Providers/AWSAppSyncProvider.js.map +1 -1
  50. package/lib-esm/Providers/AWSAppSyncRealTimeProvider/index.d.ts +8 -0
  51. package/lib-esm/Providers/AWSAppSyncRealTimeProvider/index.js +126 -168
  52. package/lib-esm/Providers/AWSAppSyncRealTimeProvider/index.js.map +1 -1
  53. package/lib-esm/Providers/AWSIotProvider.js +3 -61
  54. package/lib-esm/Providers/AWSIotProvider.js.map +1 -1
  55. package/lib-esm/Providers/MqttOverWSProvider.d.ts +2 -4
  56. package/lib-esm/Providers/MqttOverWSProvider.js +139 -175
  57. package/lib-esm/Providers/MqttOverWSProvider.js.map +1 -1
  58. package/lib-esm/Providers/PubSubProvider.js +1 -11
  59. package/lib-esm/Providers/PubSubProvider.js.map +1 -1
  60. package/lib-esm/Providers/constants.d.ts +8 -0
  61. package/lib-esm/Providers/constants.js +8 -0
  62. package/lib-esm/Providers/constants.js.map +1 -1
  63. package/lib-esm/Providers/index.d.ts +5 -5
  64. package/lib-esm/Providers/index.js +7 -17
  65. package/lib-esm/Providers/index.js.map +1 -1
  66. package/lib-esm/PubSub.js +3 -48
  67. package/lib-esm/PubSub.js.map +1 -1
  68. package/lib-esm/index.d.ts +2 -7
  69. package/lib-esm/index.js +4 -19
  70. package/lib-esm/index.js.map +1 -1
  71. package/lib-esm/types/Provider.d.ts +0 -4
  72. package/lib-esm/types/PubSub.js +2 -12
  73. package/lib-esm/types/PubSub.js.map +1 -1
  74. package/lib-esm/types/index.js.map +1 -1
  75. package/lib-esm/utils/ConnectionStateMonitor.d.ts +1 -0
  76. package/lib-esm/utils/ConnectionStateMonitor.js +13 -23
  77. package/lib-esm/utils/ConnectionStateMonitor.js.map +1 -1
  78. package/lib-esm/utils/ReconnectionMonitor.d.ts +29 -0
  79. package/lib-esm/utils/ReconnectionMonitor.js +71 -0
  80. package/lib-esm/utils/ReconnectionMonitor.js.map +1 -0
  81. package/package.json +14 -10
  82. package/src/Providers/AWSAppSyncProvider.ts +2 -12
  83. package/src/Providers/AWSAppSyncRealTimeProvider/index.ts +142 -89
  84. package/src/Providers/AWSIotProvider.ts +2 -12
  85. package/src/Providers/MqttOverWSProvider.ts +90 -79
  86. package/src/Providers/PubSubProvider.ts +2 -12
  87. package/src/Providers/constants.ts +10 -0
  88. package/src/Providers/index.ts +15 -17
  89. package/src/PubSub.ts +2 -12
  90. package/src/index.ts +15 -23
  91. package/src/types/Provider.ts +2 -17
  92. package/src/types/PubSub.ts +2 -12
  93. package/src/types/index.ts +2 -12
  94. package/src/utils/ConnectionStateMonitor.ts +16 -12
  95. package/src/utils/ReconnectionMonitor.ts +74 -0
  96. package/build.js +0 -5
  97. package/dist/aws-amplify-pubsub.js +0 -10314
  98. package/dist/aws-amplify-pubsub.js.map +0 -1
  99. package/dist/aws-amplify-pubsub.min.js +0 -11
  100. package/dist/aws-amplify-pubsub.min.js.map +0 -1
  101. package/index.js +0 -7
  102. package/webpack.config.dev.js +0 -6
@@ -1,15 +1,5 @@
1
- /*
2
- * Copyright 2017-2021 Amazon.com, Inc. or its affiliates. All Rights Reserved.
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, { GRAPHQL_AUTH_MODE } from '@aws-amplify/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.connectionStateMonitor.connectionStateObservable.subscribe(
108
- ConnectionState => {
109
- dispatchApiEvent(
110
- CONNECTION_STATE_CHANGE,
111
- {
112
- provider: this,
113
- connectionState: ConnectionState,
114
- },
115
- `Connection state is ${ConnectionState}`
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
- this._startSubscriptionWithAWSAppSyncRealTime({
163
- options,
164
- observer,
165
- subscriptionId,
166
- }).catch<any>(err => {
167
- observer.error({
168
- errors: [
169
- {
170
- ...new GraphQLError(
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
- this.connectionStateMonitor.record(CONNECTION_CHANGE.CLOSED);
177
- observer.complete();
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
- logger.debug({ err });
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
- this.subscriptionObserverMap.forEach(({ observer }) => {
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
- if (observer && !observer.closed) {
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
- * Copyright 2017-2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
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
- * Copyright 2017-2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
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
- clientIdObservers.forEach(observer => {
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
- // client.trace = (args) => logger.debug(clientId, JSON.stringify(args, null, 2));
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(client),
205
- onFailure: () => {
206
- reject();
207
- this.connectionStateMonitor.record(
208
- CONNECTION_CHANGE.CONNECTION_FAILED
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
- this.connectionStateMonitor.record(
215
- CONNECTION_CHANGE.CONNECTION_ESTABLISHED
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 url = await this.endpoint;
245
-
246
- const client = await this.connect(this.clientId, { url });
247
-
248
- logger.debug('Publishing to topic(s)', targetTopics.join(','), message);
249
- targetTopics.forEach(topic => client.send(topic, message));
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 { url = await this.endpoint } = options;
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
- try {
317
- client = await this.connect(clientId, { url });
318
- targetTopics.forEach(topic => {
319
- client.subscribe(topic);
320
- });
321
- } catch (e) {
322
- observer.error(e);
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
- logger.debug('Unsubscribing from topic(s)', targetTopics.join(','));
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
- * Copyright 2017-2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
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;