@reactoo/watchtogether-sdk-js 2.8.48 → 2.8.52

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.
@@ -1,210 +1,226 @@
1
- import { mqtt5, iot } from 'aws-iot-device-sdk-v2';
2
- console.log('Worker: Starting up');
1
+ import { mqtt5, iot, auth } from 'aws-iot-device-sdk-v2';
2
+
3
+ class StaticCredentialsProvider extends auth.CredentialsProvider {
4
+ constructor({ accessKeyId, secretAccessKey, sessionToken, region }) {
5
+ super();
6
+ this.creds = { accessKeyId, secretAccessKey, sessionToken, region };
7
+ }
8
+ getCredentials() {
9
+ return {
10
+ aws_access_id: this.creds.accessKeyId ?? "",
11
+ aws_secret_key: this.creds.secretAccessKey ?? "",
12
+ aws_sts_token: this.creds.sessionToken,
13
+ aws_region: this.creds.region
14
+ };
15
+ }
16
+ }
17
+
18
+ console.log('Worker: Starting up with MQTT5');
3
19
 
4
20
  let client = null;
5
- let currentConnectionId = 1;
21
+ let isConnectedVal = false
22
+ let listenersAttached = false;
23
+ let terminateOnDisconnect = false;
24
+
25
+ const normalizeError = (err) => {
26
+ if (!err) return 'Unknown error';
27
+ if (typeof err === 'string') return err;
28
+ if (err.message) return err.message;
29
+ try { return JSON.stringify(err); } catch { return String(err); }
30
+ };
31
+
32
+ const cleanupClient = () => {
33
+ try { client?.removeAllListeners?.(); } catch {}
34
+ try { client?.close?.(); } catch {}
35
+ client = null;
36
+ listenersAttached = false;
37
+ isConnectedVal = false;
38
+ if(terminateOnDisconnect) {
39
+ self.close();
40
+ }
41
+ };
6
42
 
7
43
  self.onmessage = function(event) {
8
- const { type, params, topic, message, stamp } = event.data;
9
- console.log(`Worker: Received message of type: ${type}`);
10
-
11
- switch (type) {
12
- case 'connect':
13
- connect(params, stamp);
14
- break;
15
- case 'disconnect':
16
- disconnect(stamp);
17
- break;
18
- case 'is_connected':
19
- isConnected();
20
- break;
21
- case 'clear_topics':
22
- // No action needed in the worker
23
- break;
24
- case 'subscribe':
25
- subscribe(topic, stamp);
26
- break;
27
- case 'unsubscribe':
28
- unsubscribe(topic, stamp);
29
- break;
30
- case 'send':
31
- send(topic, message);
32
- break;
33
- }
44
+ const { type, params, topic, message, stamp } = event.data;
45
+ console.log(`Worker: Received message of type: ${type}`);
46
+
47
+ switch (type) {
48
+ case 'connect':
49
+ connect(params);
50
+ break;
51
+ case 'disconnect':
52
+ disconnect();
53
+ break;
54
+ case 'isConnected':
55
+ isConnected(stamp);
56
+ break;
57
+ case 'clearTopics':
58
+ // No action needed in the worker
59
+ break;
60
+ case 'subscribe':
61
+ subscribe(topic, stamp);
62
+ break;
63
+ case 'unsubscribe':
64
+ unsubscribe(topic, stamp);
65
+ break;
66
+ case 'send':
67
+ send(topic, message);
68
+ break;
69
+ }
34
70
  };
35
71
 
36
- function connect(params, stamp) {
37
- console.log('Worker: Attempting to connect');
38
- const { apiMqttUrl, apiMqttClientId, region, accessKeyId, secretAccessKey, sessionToken } = params;
39
-
40
- const configBuilder = iot.AwsIotMqtt5ClientConfigBuilder.new_with_websockets();
41
-
42
- configBuilder.with_clean_session(true);
43
- configBuilder.with_client_id(apiMqttClientId);
44
- configBuilder.with_endpoint(apiMqttUrl);
45
- configBuilder.with_credentials(region, accessKeyId, secretAccessKey, sessionToken);
46
- configBuilder.with_ping_timeout_ms(3000);
47
- configBuilder.with_connect_properties({
48
- keepAliveIntervalSeconds: 30,
49
- sessionExpiryIntervalSeconds: 300
50
- });
51
-
52
- const config = configBuilder.build();
53
-
54
- // MQTT5 uses a single client instance
55
- client = new mqtt5.Mqtt5Client(config);
56
- currentConnectionId++;
57
-
58
- setupConnectionListeners(currentConnectionId);
59
-
60
- client.start()
61
- .then(() => {
62
- console.log('Worker: Connection successful');
63
- self.postMessage({ type: 'connect_result', data: {success: true, connectionId: currentConnectionId, stamp} });
64
- })
65
- .catch((error) => {
66
- console.error('Worker: Connection failed', error);
67
- self.postMessage({ type: 'connect_result', data: {success: false, error: error.message, stamp} });
68
- });
72
+ function connect(params) {
73
+ if (client) return; // guard duplicate connects
74
+
75
+ console.log('Worker: Attempting to connect with MQTT5');
76
+ const { apiMqttUrl, apiMqttClientId, region, accessKeyId, secretAccessKey, sessionToken } = params;
77
+
78
+ const credentialsProvider = new StaticCredentialsProvider({
79
+ accessKeyId,
80
+ secretAccessKey,
81
+ sessionToken,
82
+ region
83
+ });
84
+
85
+ const configBuilder = iot.AwsIotMqtt5ClientConfigBuilder.newWebsocketMqttBuilderWithSigv4Auth(
86
+ apiMqttUrl,
87
+ { region, credentialsProvider }
88
+ );
89
+
90
+ configBuilder.withConnectProperties({
91
+ clientId: apiMqttClientId,
92
+ keepAliveIntervalSeconds: 30,
93
+ sessionExpiryIntervalSeconds: 3600
94
+ });
95
+
96
+ const config = configBuilder.build();
97
+ client = new mqtt5.Mqtt5Client(config);
98
+ attachConnectionListeners();
99
+ client.start();
69
100
  }
70
101
 
71
- function disconnect(stamp) {
72
- if (client) {
73
- const connectionId = currentConnectionId;
74
- client.stop()
75
- .then(() => {
76
- if(connectionId !== currentConnectionId) {
77
- console.log('Worker: Connection Id mismatch, ignoring disconnect result', connectionId, currentConnectionId);
78
- return;
79
- }
80
- self.postMessage({ type: 'disconnect_result', data: {success: true, stamp} });
81
- })
82
- .catch((error) => {
83
- if(connectionId !== currentConnectionId) {
84
- console.log('Worker: Connection Id mismatch, ignoring disconnect result', connectionId, currentConnectionId);
85
- return;
86
- }
87
- self.postMessage({ type: 'disconnect_result', data: { success: false, error: error.message, stamp} });
88
- });
89
- client = null;
90
- } else {
91
- self.postMessage({ type: 'disconnect_result', data: {success: true, stamp} });
92
- }
102
+ function disconnect(terminate = true) {
103
+ terminateOnDisconnect = terminate;
104
+ if (client) {
105
+ client.stop();
106
+ }
93
107
  }
94
108
 
95
- function isConnected() {
96
- const connected = client && client.getIsConnected();
97
- self.postMessage({ type: 'is_connected_result', data:{connected} });
109
+ function isConnected(stamp) {
110
+ self.postMessage({ type: 'is_connected_result', data:{connected: isConnectedVal, stamp} });
98
111
  }
99
112
 
100
113
  function subscribe(topic, stamp) {
101
- if (client && client.getIsConnected()) {
102
- const subscription = {
103
- qos: mqtt5.QoS.AtLeastOnce,
104
- topicFilter: topic
105
- };
106
-
107
- client.subscribe(subscription)
108
- .then(() => {
109
- self.postMessage({ type: 'subscribe_result', data: {success: true, stamp} });
110
- })
111
- .catch((error) => {
112
- self.postMessage({ type: 'subscribe_result', data: {success: false, error: error.message, stamp} });
113
- });
114
- } else {
115
- self.postMessage({ type: 'subscribe_result', data: {success: false, error: 'Not connected', stamp} });
116
- }
114
+ if (client && isConnectedVal) {
115
+ const subscribePacket = {
116
+ subscriptions: [{
117
+ topicFilter: topic,
118
+ qos: mqtt5.QoS.AtLeastOnce
119
+ }]
120
+ };
121
+ client.subscribe(subscribePacket)
122
+ .then(() => {
123
+ self.postMessage({ type: 'subscribe_result', data: {success: true, stamp} });
124
+ })
125
+ .catch((error) => {
126
+ self.postMessage({ type: 'subscribe_result', data: {success: false, error: normalizeError(error), stamp} });
127
+ });
128
+ } else {
129
+ self.postMessage({ type: 'subscribe_result', data: {success: false, error: 'Not connected', stamp} });
130
+ }
117
131
  }
118
132
 
119
133
  function unsubscribe(topic, stamp) {
120
- if (client && client.getIsConnected()) {
121
- client.unsubscribe({ topicFilter: topic })
122
- .then(() => {
123
- self.postMessage({ type: 'unsubscribe_result', data: {success: true, stamp} });
124
- })
125
- .catch((error) => {
126
- self.postMessage({ type: 'unsubscribe_result', data: {success: false, error: error.message, stamp} });
127
- });
128
- } else {
129
- self.postMessage({ type: 'unsubscribe_result', data: {success: false, error: 'Not connected', stamp} });
130
- }
134
+ if (client && isConnectedVal) {
135
+ const unsubscribePacket = {
136
+ topicFilters: [topic]
137
+ };
138
+ client.unsubscribe(unsubscribePacket)
139
+ .then(() => {
140
+ self.postMessage({ type: 'unsubscribe_result', data: {success: true, stamp} });
141
+ })
142
+ .catch((error) => {
143
+ self.postMessage({ type: 'unsubscribe_result', data: {success: false, error: normalizeError(error), stamp} });
144
+ });
145
+ } else {
146
+ self.postMessage({ type: 'unsubscribe_result', data: {success: false, error: 'Not connected', stamp} });
147
+ }
131
148
  }
132
149
 
133
150
  function send(topic, message) {
134
- if (client && client.getIsConnected()) {
135
- const publish = {
136
- qos: mqtt5.QoS.AtLeastOnce,
137
- topicName: topic,
138
- payload: message,
139
- retain: false
140
- };
141
- client.publish(publish);
142
- } else {
143
- console.error('Cannot send message: Not connected');
144
- }
151
+ if (client && isConnectedVal) {
152
+ const publishPacket = {
153
+ topicName: topic,
154
+ payload: message,
155
+ qos: mqtt5.QoS.AtLeastOnce
156
+ };
157
+ client.publish(publishPacket).catch((e) => {
158
+ console.error('Publish failed:', normalizeError(e));
159
+ });
160
+ } else {
161
+ console.error('Cannot send message: Not connected');
162
+ }
145
163
  }
146
164
 
147
- function setupConnectionListeners(connectionId) {
148
- client.on('connectionSuccess', (eventData) => {
149
- if(connectionId !== currentConnectionId) {
150
- console.log('Worker: Connection Id mismatch, ignoring connectionSuccess event', connectionId, currentConnectionId);
151
- return;
152
- }
153
- self.postMessage({
154
- type: 'connection_success',
155
- data: {
156
- sessionPresent: eventData.sessionPresent,
157
- negotiatedSettings: eventData.negotiatedSettings
158
- },
159
- connectionId: connectionId
160
- });
161
- });
162
-
163
- client.on('connectionFailure', (error) => {
164
- if(connectionId !== currentConnectionId) {
165
- console.log('Worker: Connection Id mismatch, ignoring connectionFailure event', connectionId, currentConnectionId);
166
- return;
167
- }
168
- self.postMessage({ type: 'connection_failure', data: error, connectionId: connectionId });
169
- });
170
-
171
- client.on('disconnection', (eventData) => {
172
- if(connectionId !== currentConnectionId) {
173
- console.log('Worker: Connection Id mismatch, ignoring disconnect event', connectionId, currentConnectionId);
174
- return;
175
- }
176
- self.postMessage({
177
- type: 'disconnect',
178
- data: { reason: eventData.errorCode },
179
- connectionId: connectionId
180
- });
181
- });
182
-
183
- client.on('messageReceived', (eventData) => {
184
- if(connectionId !== currentConnectionId) {
185
- console.log('Worker: Connection Id mismatch, ignoring message event', connectionId, currentConnectionId);
186
- return;
187
- }
188
- self.postMessage({
189
- type: 'message',
190
- data: {
191
- topic: eventData.message.topicName,
192
- payload: eventData.message.payload,
193
- qos: eventData.message.qos,
194
- retain: eventData.message.retain,
195
- properties: eventData.message.properties
196
- },
197
- connectionId: connectionId
198
- });
199
- });
200
-
201
- client.on('error', (error) => {
202
- if(connectionId !== currentConnectionId) {
203
- console.log('Worker: Connection Id mismatch, ignoring error event', connectionId, currentConnectionId);
204
- return;
205
- }
206
- self.postMessage({ type: 'error', data: error });
207
- });
165
+ function attachConnectionListeners() {
166
+
167
+ if(listenersAttached || !client) {
168
+ return;
169
+ }
170
+
171
+ // not sure if all errors are unrecoverable, but we will treat them as such for now
172
+
173
+ client.on('error', (error) => {
174
+ isConnectedVal = false;
175
+ self.postMessage({ type: 'error', data: error });
176
+ });
177
+
178
+ client.on('messageReceived', (eventData) => {
179
+ const { message } = eventData;
180
+ self.postMessage({
181
+ type: 'message',
182
+ data: {
183
+ topic: message.topicName,
184
+ payload: message.payload
185
+ }
186
+ });
187
+ });
188
+
189
+ client.on('attemptingConnect', () => {
190
+ self.postMessage({ type: 'attemptingConnect'});
191
+ })
192
+
193
+ client.on('connectionSuccess', () => {
194
+ isConnectedVal = true;
195
+ self.postMessage({ type: 'connectionSuccess'});
196
+ });
197
+
198
+ client.on('connectionFailure', (eventData) => {
199
+ isConnectedVal = false;
200
+ self.postMessage({
201
+ type: 'connectionFailure',
202
+ data: {
203
+ error: normalizeError(eventData?.error),
204
+ connack: eventData?.connack
205
+ }});
206
+ });
207
+
208
+ client.on('disconnection', (eventData) => {
209
+ isConnectedVal = false;
210
+ self.postMessage({
211
+ type: 'disconnection',
212
+ data: { error: normalizeError(eventData?.error), disconnect: eventData?.disconnect }
213
+ });
214
+ });
215
+
216
+ client.on('stopped', () => {
217
+ // free native resources and reset state
218
+ cleanupClient();
219
+ self.postMessage({ type: 'stopped'});
220
+ });
221
+
222
+ listenersAttached = true;
208
223
  }
209
224
 
210
- console.log('Worker: Setup complete');
225
+
226
+ console.log('Worker: Setup complete with MQTT5');