@openfin/cloud-interop-core-api 0.0.1-alpha.e6793f0 → 0.0.1-alpha.fffeb9a
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/dist/api.d.ts +2 -9
- package/dist/index.cjs +121 -86
- package/dist/index.mjs +121 -86
- package/dist/interfaces.d.ts +9 -0
- package/package.json +1 -1
package/dist/api.d.ts
CHANGED
|
@@ -11,11 +11,12 @@ type CreateSessionResponse = {
|
|
|
11
11
|
sourceId: string;
|
|
12
12
|
};
|
|
13
13
|
type EventMap = {
|
|
14
|
-
|
|
14
|
+
reconnected: () => void;
|
|
15
15
|
disconnected: () => void;
|
|
16
16
|
context: (event: ContextEvent) => void;
|
|
17
17
|
reconnecting: (attemptNo: number) => void;
|
|
18
18
|
error: (error: Error) => void;
|
|
19
|
+
'session-expired': () => void;
|
|
19
20
|
};
|
|
20
21
|
/**
|
|
21
22
|
* Represents a single connection to a Cloud Interop service
|
|
@@ -26,14 +27,6 @@ type EventMap = {
|
|
|
26
27
|
*/
|
|
27
28
|
export declare class CloudInteropAPI {
|
|
28
29
|
#private;
|
|
29
|
-
private cloudInteropSettings;
|
|
30
|
-
private _sessionDetails?;
|
|
31
|
-
private _mqttClient?;
|
|
32
|
-
private reconnectRetryLimit;
|
|
33
|
-
private logger;
|
|
34
|
-
private reconnectRetries;
|
|
35
|
-
private connectionParams?;
|
|
36
|
-
private eventListeners;
|
|
37
30
|
constructor(cloudInteropSettings: CloudInteropSettings);
|
|
38
31
|
get sessionDetails(): CreateSessionResponse | undefined;
|
|
39
32
|
get mqttClient(): mqtt.MqttClient | undefined;
|
package/dist/index.cjs
CHANGED
|
@@ -17,6 +17,8 @@ class AuthorizationError extends CloudInteropAPIError {
|
|
|
17
17
|
}
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
+
// Error codes as defined in https://docs.emqx.com/en/cloud/latest/connect_to_deployments/mqtt_client_error_codes.html
|
|
21
|
+
const BadUserNamePasswordError = 134;
|
|
20
22
|
/**
|
|
21
23
|
* Represents a single connection to a Cloud Interop service
|
|
22
24
|
*
|
|
@@ -25,24 +27,25 @@ class AuthorizationError extends CloudInteropAPIError {
|
|
|
25
27
|
* @implements {Client}
|
|
26
28
|
*/
|
|
27
29
|
class CloudInteropAPI {
|
|
28
|
-
cloudInteropSettings;
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
reconnectRetryLimit = 30;
|
|
32
|
-
logger = (level, message) => {
|
|
30
|
+
#cloudInteropSettings;
|
|
31
|
+
#sessionDetails;
|
|
32
|
+
#mqttClient;
|
|
33
|
+
#reconnectRetryLimit = 30;
|
|
34
|
+
#logger = (level, message) => {
|
|
33
35
|
console[level](message);
|
|
34
36
|
};
|
|
35
|
-
reconnectRetries = 0;
|
|
36
|
-
connectionParams;
|
|
37
|
-
eventListeners = new Map();
|
|
37
|
+
#reconnectRetries = 0;
|
|
38
|
+
#connectionParams;
|
|
39
|
+
#eventListeners = new Map();
|
|
40
|
+
#attemptingToReconnect = false;
|
|
38
41
|
constructor(cloudInteropSettings) {
|
|
39
|
-
this
|
|
42
|
+
this.#cloudInteropSettings = cloudInteropSettings;
|
|
40
43
|
}
|
|
41
44
|
get sessionDetails() {
|
|
42
|
-
return this
|
|
45
|
+
return this.#sessionDetails;
|
|
43
46
|
}
|
|
44
47
|
get mqttClient() {
|
|
45
|
-
return this
|
|
48
|
+
return this.#mqttClient;
|
|
46
49
|
}
|
|
47
50
|
/**
|
|
48
51
|
* Connects and creates a session on the Cloud Interop service
|
|
@@ -55,71 +58,98 @@ class CloudInteropAPI {
|
|
|
55
58
|
*/
|
|
56
59
|
async connect(parameters) {
|
|
57
60
|
this.#validateConnectParams(parameters);
|
|
58
|
-
this
|
|
59
|
-
this
|
|
60
|
-
this
|
|
61
|
-
const { sourceId, platformId } = this
|
|
61
|
+
this.#connectionParams = parameters;
|
|
62
|
+
this.#reconnectRetryLimit = parameters.reconnectRetryLimit || this.#reconnectRetryLimit;
|
|
63
|
+
this.#logger = parameters.logger || this.#logger;
|
|
64
|
+
const { sourceId, platformId } = this.#connectionParams;
|
|
62
65
|
try {
|
|
63
|
-
const createSessionResponse = await axios.post(`${this
|
|
66
|
+
const createSessionResponse = await axios.post(`${this.#cloudInteropSettings.url}/api/sessions`, {
|
|
64
67
|
sourceId,
|
|
65
68
|
platformId,
|
|
66
69
|
}, {
|
|
67
70
|
headers: this.#getRequestHeaders(),
|
|
68
71
|
});
|
|
69
72
|
if (createSessionResponse.status !== 201) {
|
|
70
|
-
throw new CloudInteropAPIError(`Failed to connect to the Cloud Interop service: ${this
|
|
73
|
+
throw new CloudInteropAPIError(`Failed to connect to the Cloud Interop service: ${this.#cloudInteropSettings.url}`, 'ERR_CONNECT', createSessionResponse.status);
|
|
71
74
|
}
|
|
72
|
-
this
|
|
73
|
-
const sessionRootTopic = this.
|
|
75
|
+
this.#sessionDetails = createSessionResponse.data;
|
|
76
|
+
const sessionRootTopic = this.#sessionDetails.sessionRootTopic;
|
|
74
77
|
const clientOptions = {
|
|
75
|
-
clientId: this.
|
|
78
|
+
clientId: this.#sessionDetails.sessionId,
|
|
76
79
|
clean: true,
|
|
77
80
|
protocolVersion: 5,
|
|
78
81
|
// The "will" message will be published on an unexpected disconnection
|
|
79
82
|
// The server can then tidy up. So it needs every for this client to do that, the session details is perfect
|
|
80
83
|
will: {
|
|
81
84
|
topic: 'interop/lastwill',
|
|
82
|
-
payload: Buffer.from(JSON.stringify(this
|
|
85
|
+
payload: Buffer.from(JSON.stringify(this.#sessionDetails)),
|
|
83
86
|
qos: 0,
|
|
84
87
|
retain: false,
|
|
85
88
|
},
|
|
86
|
-
username: this.
|
|
89
|
+
username: this.#sessionDetails.token,
|
|
87
90
|
};
|
|
88
|
-
this
|
|
89
|
-
this
|
|
90
|
-
this.
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
this
|
|
91
|
+
this.#mqttClient = await mqtt.connectAsync(this.#sessionDetails.url, clientOptions);
|
|
92
|
+
this.#logger('log', `Cloud Interop successfully connected to ${this.#cloudInteropSettings.url}`);
|
|
93
|
+
this.#mqttClient.on('error', async (error) => {
|
|
94
|
+
// We will receive errors for each failed reconnection attempt
|
|
95
|
+
// We don't won't to disconnect on these else we will never reconnect
|
|
96
|
+
if (!this.#attemptingToReconnect) {
|
|
97
|
+
await this.#disconnect(false);
|
|
98
|
+
}
|
|
99
|
+
if (error instanceof mqtt.ErrorWithReasonCode) {
|
|
100
|
+
switch (error.code) {
|
|
101
|
+
case BadUserNamePasswordError: {
|
|
102
|
+
await this.#disconnect(false);
|
|
103
|
+
this.#logger('warn', `Session expired`);
|
|
104
|
+
this.#emitEvent('session-expired');
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
default: {
|
|
108
|
+
this.#logger('error', `Unknown Infrastructure Error Code ${error.code} : ${error.message}${this.#attemptingToReconnect ? ' during reconnection attempt' : ''}`);
|
|
109
|
+
// As we are in the middle of a reconnect, lets not emit an error to cut down on the event noise
|
|
110
|
+
if (!this.#attemptingToReconnect) {
|
|
111
|
+
this.#emitEvent('error', new CloudInteropAPIError(`Unknown Infrastructure Error Code ${error.code} : ${error.message}`, 'ERR_INFRASTRUCTURE', error));
|
|
112
|
+
break;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
else {
|
|
118
|
+
this.#logger('error', `Unknown Error${this.#attemptingToReconnect ? ' during reconnection attempt' : ''}: ${error}`);
|
|
119
|
+
// As we are in the middle of a reconnect, lets not emit an error to cut down on the event noise
|
|
120
|
+
if (!this.#attemptingToReconnect) {
|
|
121
|
+
this.#emitEvent('error', new CloudInteropAPIError(`Unknown Error`, 'ERR_UNKNOWN', error));
|
|
122
|
+
}
|
|
123
|
+
}
|
|
94
124
|
});
|
|
95
|
-
this.
|
|
96
|
-
this
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
this
|
|
100
|
-
|
|
101
|
-
this
|
|
102
|
-
this.disconnect();
|
|
125
|
+
this.#mqttClient.on('reconnect', () => {
|
|
126
|
+
this.#attemptingToReconnect = true;
|
|
127
|
+
this.#reconnectRetries += 1;
|
|
128
|
+
this.#logger('debug', `Cloud Interop attempting reconnection - ${this.#reconnectRetries}...`);
|
|
129
|
+
if (this.#reconnectRetries === this.#reconnectRetryLimit) {
|
|
130
|
+
this.#logger('warn', `Cloud Interop reached max reconnection attempts - ${this.#reconnectRetryLimit}...`);
|
|
131
|
+
this.#disconnect(true);
|
|
103
132
|
}
|
|
104
|
-
this.#emitEvent('reconnecting', this
|
|
133
|
+
this.#emitEvent('reconnecting', this.#reconnectRetries);
|
|
105
134
|
});
|
|
106
135
|
// Does not fire on initial connection, only successful reconnection attempts
|
|
107
|
-
this.
|
|
108
|
-
this
|
|
109
|
-
this
|
|
110
|
-
this.#
|
|
136
|
+
this.#mqttClient.on('connect', () => {
|
|
137
|
+
this.#logger('debug', `Cloud Interop successfully reconnected after ${this.#reconnectRetries} attempts`);
|
|
138
|
+
this.#reconnectRetries = 0;
|
|
139
|
+
this.#attemptingToReconnect = false;
|
|
140
|
+
this.#emitEvent('reconnected');
|
|
111
141
|
});
|
|
112
|
-
this.
|
|
113
|
-
if (!this
|
|
114
|
-
this
|
|
142
|
+
this.#mqttClient.on('message', (topic, message) => {
|
|
143
|
+
if (!this.#sessionDetails) {
|
|
144
|
+
this.#logger('warn', 'Received message when session not connected');
|
|
115
145
|
return;
|
|
116
146
|
}
|
|
117
|
-
this.#handleCommand(topic, message, this
|
|
147
|
+
this.#handleCommand(topic, message, this.#sessionDetails);
|
|
118
148
|
});
|
|
119
149
|
// Subscribe to all context groups
|
|
120
|
-
this.
|
|
150
|
+
this.#mqttClient.subscribe(`${sessionRootTopic}/context-groups/#`);
|
|
121
151
|
// Listen out for global commands
|
|
122
|
-
this.
|
|
152
|
+
this.#mqttClient.subscribe(`${sessionRootTopic}/commands`);
|
|
123
153
|
}
|
|
124
154
|
catch (error) {
|
|
125
155
|
if (axios.isAxiosError(error)) {
|
|
@@ -139,28 +169,7 @@ class CloudInteropAPI {
|
|
|
139
169
|
* @throws {CloudInteropAPIError} - If an error occurs during disconnection
|
|
140
170
|
*/
|
|
141
171
|
async disconnect() {
|
|
142
|
-
|
|
143
|
-
return;
|
|
144
|
-
}
|
|
145
|
-
try {
|
|
146
|
-
const disconnectResponse = await axios.delete(`${this.cloudInteropSettings.url}/api/sessions/${this._sessionDetails.sessionId}`, {
|
|
147
|
-
headers: this.#getRequestHeaders(),
|
|
148
|
-
});
|
|
149
|
-
if (disconnectResponse.status !== 200) {
|
|
150
|
-
throw new CloudInteropAPIError('Error during disconnection', 'ERR_DISCONNECT', disconnectResponse.status);
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
catch {
|
|
154
|
-
throw new CloudInteropAPIError('Error during disconnection', 'ERR_DISCONNECT');
|
|
155
|
-
}
|
|
156
|
-
finally {
|
|
157
|
-
this._mqttClient?.removeAllListeners();
|
|
158
|
-
this._mqttClient?.end(true);
|
|
159
|
-
this._sessionDetails = undefined;
|
|
160
|
-
this._mqttClient = undefined;
|
|
161
|
-
this.reconnectRetries = 0;
|
|
162
|
-
this.#emitEvent('disconnected');
|
|
163
|
-
}
|
|
172
|
+
await this.#disconnect(true);
|
|
164
173
|
}
|
|
165
174
|
/**
|
|
166
175
|
* Publishes a new context for the given context group to the other connected sessions
|
|
@@ -171,30 +180,56 @@ class CloudInteropAPI {
|
|
|
171
180
|
* @memberof CloudInteropAPI
|
|
172
181
|
*/
|
|
173
182
|
async setContext(contextGroup, context) {
|
|
174
|
-
if (!this
|
|
183
|
+
if (!this.#sessionDetails || !this.#connectionParams) {
|
|
175
184
|
throw new Error('Session not connected');
|
|
176
185
|
}
|
|
177
|
-
const { sourceId } = this.connectionParams;
|
|
178
186
|
const payload = {
|
|
179
|
-
sourceId,
|
|
180
187
|
context,
|
|
188
|
+
timestamp: Date.now(),
|
|
181
189
|
};
|
|
182
|
-
await axios.post(`${this
|
|
190
|
+
await axios.post(`${this.#cloudInteropSettings.url}/api/context-groups/${this.#sessionDetails.sessionId}/${contextGroup}`, payload, {
|
|
183
191
|
headers: this.#getRequestHeaders(),
|
|
184
192
|
});
|
|
185
193
|
}
|
|
186
194
|
addEventListener(type, callback) {
|
|
187
|
-
const listeners = this
|
|
195
|
+
const listeners = this.#eventListeners.get(type) || [];
|
|
188
196
|
listeners.push(callback);
|
|
189
|
-
this
|
|
197
|
+
this.#eventListeners.set(type, listeners);
|
|
190
198
|
}
|
|
191
199
|
removeEventListener(type, callback) {
|
|
192
|
-
const listeners = this
|
|
200
|
+
const listeners = this.#eventListeners.get(type) || [];
|
|
193
201
|
const index = listeners.indexOf(callback);
|
|
194
202
|
if (index !== -1) {
|
|
195
203
|
listeners.splice(index, 1);
|
|
196
204
|
}
|
|
197
|
-
this
|
|
205
|
+
this.#eventListeners.set(type, listeners);
|
|
206
|
+
}
|
|
207
|
+
async #disconnect(fireDisconnectedEvent) {
|
|
208
|
+
if (!this.#sessionDetails) {
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
try {
|
|
212
|
+
const disconnectResponse = await axios.delete(`${this.#cloudInteropSettings.url}/api/sessions/${this.#sessionDetails.sessionId}`, {
|
|
213
|
+
headers: this.#getRequestHeaders(),
|
|
214
|
+
});
|
|
215
|
+
if (disconnectResponse.status !== 200) {
|
|
216
|
+
throw new CloudInteropAPIError('Error during session tear down - unexpected status', 'ERR_DISCONNECT', disconnectResponse.status);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
catch {
|
|
220
|
+
throw new CloudInteropAPIError('Error during disconnection', 'ERR_DISCONNECT');
|
|
221
|
+
}
|
|
222
|
+
finally {
|
|
223
|
+
this.#mqttClient?.removeAllListeners();
|
|
224
|
+
await this.#mqttClient?.endAsync(true);
|
|
225
|
+
this.#sessionDetails = undefined;
|
|
226
|
+
this.#mqttClient = undefined;
|
|
227
|
+
this.#reconnectRetries = 0;
|
|
228
|
+
this.#attemptingToReconnect = false;
|
|
229
|
+
if (fireDisconnectedEvent) {
|
|
230
|
+
this.#emitEvent('disconnected');
|
|
231
|
+
}
|
|
232
|
+
}
|
|
198
233
|
}
|
|
199
234
|
#handleCommand(topic, message, sessionDetails) {
|
|
200
235
|
if (message.length === 0 || !sessionDetails) {
|
|
@@ -206,12 +241,12 @@ class CloudInteropAPI {
|
|
|
206
241
|
if (messageEnvelope.source.sessionId === sessionDetails.sessionId) {
|
|
207
242
|
return;
|
|
208
243
|
}
|
|
209
|
-
const { channelName: contextGroup, payload: context, source } = messageEnvelope;
|
|
210
|
-
this.#emitEvent('context', { contextGroup, context, source });
|
|
244
|
+
const { channelName: contextGroup, payload: context, source, history } = messageEnvelope;
|
|
245
|
+
this.#emitEvent('context', { contextGroup, context, source, history: { ...history, clientReceived: Date.now() } });
|
|
211
246
|
}
|
|
212
247
|
}
|
|
213
248
|
#emitEvent(type, ...args) {
|
|
214
|
-
const listeners = this
|
|
249
|
+
const listeners = this.#eventListeners.get(type) || [];
|
|
215
250
|
listeners.forEach((listener) => listener(...args));
|
|
216
251
|
}
|
|
217
252
|
#validateConnectParams = (parameters) => {
|
|
@@ -228,22 +263,22 @@ class CloudInteropAPI {
|
|
|
228
263
|
}
|
|
229
264
|
};
|
|
230
265
|
#getRequestHeaders = () => {
|
|
231
|
-
if (!this
|
|
266
|
+
if (!this.#connectionParams) {
|
|
232
267
|
throw new Error('Connect parameters must be provided');
|
|
233
268
|
}
|
|
234
269
|
const headers = new axios.AxiosHeaders();
|
|
235
270
|
headers['Content-Type'] = 'application/json';
|
|
236
|
-
if (this
|
|
237
|
-
const tokenResult = this
|
|
271
|
+
if (this.#connectionParams.authenticationType === 'jwt' && this.#connectionParams.jwtAuthenticationParameters) {
|
|
272
|
+
const tokenResult = this.#connectionParams.jwtAuthenticationParameters.jwtRequestCallback();
|
|
238
273
|
if (!tokenResult) {
|
|
239
274
|
throw new Error('jwtRequestCallback must return a token');
|
|
240
275
|
}
|
|
241
|
-
headers['x-of-auth-id'] = this
|
|
276
|
+
headers['x-of-auth-id'] = this.#connectionParams.jwtAuthenticationParameters.authenticationId;
|
|
242
277
|
headers['Authorization'] =
|
|
243
278
|
typeof tokenResult === 'string' ? `Bearer ${tokenResult}` : `Bearer ${Buffer.from(JSON.stringify(tokenResult)).toString('base64')}`;
|
|
244
279
|
}
|
|
245
|
-
if (this
|
|
246
|
-
const { username, password } = this
|
|
280
|
+
if (this.#connectionParams.authenticationType === 'basic' && this.#connectionParams.basicAuthenticationParameters) {
|
|
281
|
+
const { username, password } = this.#connectionParams.basicAuthenticationParameters;
|
|
247
282
|
headers['Authorization'] = `Basic ${Buffer.from(`${username}:${password}`).toString('base64')}`;
|
|
248
283
|
}
|
|
249
284
|
return headers;
|
package/dist/index.mjs
CHANGED
|
@@ -3781,6 +3781,8 @@ class AuthorizationError extends CloudInteropAPIError {
|
|
|
3781
3781
|
}
|
|
3782
3782
|
}
|
|
3783
3783
|
|
|
3784
|
+
// Error codes as defined in https://docs.emqx.com/en/cloud/latest/connect_to_deployments/mqtt_client_error_codes.html
|
|
3785
|
+
const BadUserNamePasswordError = 134;
|
|
3784
3786
|
/**
|
|
3785
3787
|
* Represents a single connection to a Cloud Interop service
|
|
3786
3788
|
*
|
|
@@ -3789,24 +3791,25 @@ class AuthorizationError extends CloudInteropAPIError {
|
|
|
3789
3791
|
* @implements {Client}
|
|
3790
3792
|
*/
|
|
3791
3793
|
class CloudInteropAPI {
|
|
3792
|
-
cloudInteropSettings;
|
|
3793
|
-
|
|
3794
|
-
|
|
3795
|
-
reconnectRetryLimit = 30;
|
|
3796
|
-
logger = (level, message) => {
|
|
3794
|
+
#cloudInteropSettings;
|
|
3795
|
+
#sessionDetails;
|
|
3796
|
+
#mqttClient;
|
|
3797
|
+
#reconnectRetryLimit = 30;
|
|
3798
|
+
#logger = (level, message) => {
|
|
3797
3799
|
console[level](message);
|
|
3798
3800
|
};
|
|
3799
|
-
reconnectRetries = 0;
|
|
3800
|
-
connectionParams;
|
|
3801
|
-
eventListeners = new Map();
|
|
3801
|
+
#reconnectRetries = 0;
|
|
3802
|
+
#connectionParams;
|
|
3803
|
+
#eventListeners = new Map();
|
|
3804
|
+
#attemptingToReconnect = false;
|
|
3802
3805
|
constructor(cloudInteropSettings) {
|
|
3803
|
-
this
|
|
3806
|
+
this.#cloudInteropSettings = cloudInteropSettings;
|
|
3804
3807
|
}
|
|
3805
3808
|
get sessionDetails() {
|
|
3806
|
-
return this
|
|
3809
|
+
return this.#sessionDetails;
|
|
3807
3810
|
}
|
|
3808
3811
|
get mqttClient() {
|
|
3809
|
-
return this
|
|
3812
|
+
return this.#mqttClient;
|
|
3810
3813
|
}
|
|
3811
3814
|
/**
|
|
3812
3815
|
* Connects and creates a session on the Cloud Interop service
|
|
@@ -3819,71 +3822,98 @@ class CloudInteropAPI {
|
|
|
3819
3822
|
*/
|
|
3820
3823
|
async connect(parameters) {
|
|
3821
3824
|
this.#validateConnectParams(parameters);
|
|
3822
|
-
this
|
|
3823
|
-
this
|
|
3824
|
-
this
|
|
3825
|
-
const { sourceId, platformId } = this
|
|
3825
|
+
this.#connectionParams = parameters;
|
|
3826
|
+
this.#reconnectRetryLimit = parameters.reconnectRetryLimit || this.#reconnectRetryLimit;
|
|
3827
|
+
this.#logger = parameters.logger || this.#logger;
|
|
3828
|
+
const { sourceId, platformId } = this.#connectionParams;
|
|
3826
3829
|
try {
|
|
3827
|
-
const createSessionResponse = await axios.post(`${this
|
|
3830
|
+
const createSessionResponse = await axios.post(`${this.#cloudInteropSettings.url}/api/sessions`, {
|
|
3828
3831
|
sourceId,
|
|
3829
3832
|
platformId,
|
|
3830
3833
|
}, {
|
|
3831
3834
|
headers: this.#getRequestHeaders(),
|
|
3832
3835
|
});
|
|
3833
3836
|
if (createSessionResponse.status !== 201) {
|
|
3834
|
-
throw new CloudInteropAPIError(`Failed to connect to the Cloud Interop service: ${this
|
|
3837
|
+
throw new CloudInteropAPIError(`Failed to connect to the Cloud Interop service: ${this.#cloudInteropSettings.url}`, 'ERR_CONNECT', createSessionResponse.status);
|
|
3835
3838
|
}
|
|
3836
|
-
this
|
|
3837
|
-
const sessionRootTopic = this.
|
|
3839
|
+
this.#sessionDetails = createSessionResponse.data;
|
|
3840
|
+
const sessionRootTopic = this.#sessionDetails.sessionRootTopic;
|
|
3838
3841
|
const clientOptions = {
|
|
3839
|
-
clientId: this.
|
|
3842
|
+
clientId: this.#sessionDetails.sessionId,
|
|
3840
3843
|
clean: true,
|
|
3841
3844
|
protocolVersion: 5,
|
|
3842
3845
|
// The "will" message will be published on an unexpected disconnection
|
|
3843
3846
|
// The server can then tidy up. So it needs every for this client to do that, the session details is perfect
|
|
3844
3847
|
will: {
|
|
3845
3848
|
topic: 'interop/lastwill',
|
|
3846
|
-
payload: Buffer.from(JSON.stringify(this
|
|
3849
|
+
payload: Buffer.from(JSON.stringify(this.#sessionDetails)),
|
|
3847
3850
|
qos: 0,
|
|
3848
3851
|
retain: false,
|
|
3849
3852
|
},
|
|
3850
|
-
username: this.
|
|
3853
|
+
username: this.#sessionDetails.token,
|
|
3851
3854
|
};
|
|
3852
|
-
this
|
|
3853
|
-
this
|
|
3854
|
-
this.
|
|
3855
|
-
|
|
3856
|
-
|
|
3857
|
-
this
|
|
3855
|
+
this.#mqttClient = await mqtt.connectAsync(this.#sessionDetails.url, clientOptions);
|
|
3856
|
+
this.#logger('log', `Cloud Interop successfully connected to ${this.#cloudInteropSettings.url}`);
|
|
3857
|
+
this.#mqttClient.on('error', async (error) => {
|
|
3858
|
+
// We will receive errors for each failed reconnection attempt
|
|
3859
|
+
// We don't won't to disconnect on these else we will never reconnect
|
|
3860
|
+
if (!this.#attemptingToReconnect) {
|
|
3861
|
+
await this.#disconnect(false);
|
|
3862
|
+
}
|
|
3863
|
+
if (error instanceof mqtt.ErrorWithReasonCode) {
|
|
3864
|
+
switch (error.code) {
|
|
3865
|
+
case BadUserNamePasswordError: {
|
|
3866
|
+
await this.#disconnect(false);
|
|
3867
|
+
this.#logger('warn', `Session expired`);
|
|
3868
|
+
this.#emitEvent('session-expired');
|
|
3869
|
+
return;
|
|
3870
|
+
}
|
|
3871
|
+
default: {
|
|
3872
|
+
this.#logger('error', `Unknown Infrastructure Error Code ${error.code} : ${error.message}${this.#attemptingToReconnect ? ' during reconnection attempt' : ''}`);
|
|
3873
|
+
// As we are in the middle of a reconnect, lets not emit an error to cut down on the event noise
|
|
3874
|
+
if (!this.#attemptingToReconnect) {
|
|
3875
|
+
this.#emitEvent('error', new CloudInteropAPIError(`Unknown Infrastructure Error Code ${error.code} : ${error.message}`, 'ERR_INFRASTRUCTURE', error));
|
|
3876
|
+
break;
|
|
3877
|
+
}
|
|
3878
|
+
}
|
|
3879
|
+
}
|
|
3880
|
+
}
|
|
3881
|
+
else {
|
|
3882
|
+
this.#logger('error', `Unknown Error${this.#attemptingToReconnect ? ' during reconnection attempt' : ''}: ${error}`);
|
|
3883
|
+
// As we are in the middle of a reconnect, lets not emit an error to cut down on the event noise
|
|
3884
|
+
if (!this.#attemptingToReconnect) {
|
|
3885
|
+
this.#emitEvent('error', new CloudInteropAPIError(`Unknown Error`, 'ERR_UNKNOWN', error));
|
|
3886
|
+
}
|
|
3887
|
+
}
|
|
3858
3888
|
});
|
|
3859
|
-
this.
|
|
3860
|
-
this
|
|
3861
|
-
|
|
3862
|
-
|
|
3863
|
-
this
|
|
3864
|
-
|
|
3865
|
-
this
|
|
3866
|
-
this.disconnect();
|
|
3889
|
+
this.#mqttClient.on('reconnect', () => {
|
|
3890
|
+
this.#attemptingToReconnect = true;
|
|
3891
|
+
this.#reconnectRetries += 1;
|
|
3892
|
+
this.#logger('debug', `Cloud Interop attempting reconnection - ${this.#reconnectRetries}...`);
|
|
3893
|
+
if (this.#reconnectRetries === this.#reconnectRetryLimit) {
|
|
3894
|
+
this.#logger('warn', `Cloud Interop reached max reconnection attempts - ${this.#reconnectRetryLimit}...`);
|
|
3895
|
+
this.#disconnect(true);
|
|
3867
3896
|
}
|
|
3868
|
-
this.#emitEvent('reconnecting', this
|
|
3897
|
+
this.#emitEvent('reconnecting', this.#reconnectRetries);
|
|
3869
3898
|
});
|
|
3870
3899
|
// Does not fire on initial connection, only successful reconnection attempts
|
|
3871
|
-
this.
|
|
3872
|
-
this
|
|
3873
|
-
this
|
|
3874
|
-
this.#
|
|
3900
|
+
this.#mqttClient.on('connect', () => {
|
|
3901
|
+
this.#logger('debug', `Cloud Interop successfully reconnected after ${this.#reconnectRetries} attempts`);
|
|
3902
|
+
this.#reconnectRetries = 0;
|
|
3903
|
+
this.#attemptingToReconnect = false;
|
|
3904
|
+
this.#emitEvent('reconnected');
|
|
3875
3905
|
});
|
|
3876
|
-
this.
|
|
3877
|
-
if (!this
|
|
3878
|
-
this
|
|
3906
|
+
this.#mqttClient.on('message', (topic, message) => {
|
|
3907
|
+
if (!this.#sessionDetails) {
|
|
3908
|
+
this.#logger('warn', 'Received message when session not connected');
|
|
3879
3909
|
return;
|
|
3880
3910
|
}
|
|
3881
|
-
this.#handleCommand(topic, message, this
|
|
3911
|
+
this.#handleCommand(topic, message, this.#sessionDetails);
|
|
3882
3912
|
});
|
|
3883
3913
|
// Subscribe to all context groups
|
|
3884
|
-
this.
|
|
3914
|
+
this.#mqttClient.subscribe(`${sessionRootTopic}/context-groups/#`);
|
|
3885
3915
|
// Listen out for global commands
|
|
3886
|
-
this.
|
|
3916
|
+
this.#mqttClient.subscribe(`${sessionRootTopic}/commands`);
|
|
3887
3917
|
}
|
|
3888
3918
|
catch (error) {
|
|
3889
3919
|
if (axios.isAxiosError(error)) {
|
|
@@ -3903,28 +3933,7 @@ class CloudInteropAPI {
|
|
|
3903
3933
|
* @throws {CloudInteropAPIError} - If an error occurs during disconnection
|
|
3904
3934
|
*/
|
|
3905
3935
|
async disconnect() {
|
|
3906
|
-
|
|
3907
|
-
return;
|
|
3908
|
-
}
|
|
3909
|
-
try {
|
|
3910
|
-
const disconnectResponse = await axios.delete(`${this.cloudInteropSettings.url}/api/sessions/${this._sessionDetails.sessionId}`, {
|
|
3911
|
-
headers: this.#getRequestHeaders(),
|
|
3912
|
-
});
|
|
3913
|
-
if (disconnectResponse.status !== 200) {
|
|
3914
|
-
throw new CloudInteropAPIError('Error during disconnection', 'ERR_DISCONNECT', disconnectResponse.status);
|
|
3915
|
-
}
|
|
3916
|
-
}
|
|
3917
|
-
catch {
|
|
3918
|
-
throw new CloudInteropAPIError('Error during disconnection', 'ERR_DISCONNECT');
|
|
3919
|
-
}
|
|
3920
|
-
finally {
|
|
3921
|
-
this._mqttClient?.removeAllListeners();
|
|
3922
|
-
this._mqttClient?.end(true);
|
|
3923
|
-
this._sessionDetails = undefined;
|
|
3924
|
-
this._mqttClient = undefined;
|
|
3925
|
-
this.reconnectRetries = 0;
|
|
3926
|
-
this.#emitEvent('disconnected');
|
|
3927
|
-
}
|
|
3936
|
+
await this.#disconnect(true);
|
|
3928
3937
|
}
|
|
3929
3938
|
/**
|
|
3930
3939
|
* Publishes a new context for the given context group to the other connected sessions
|
|
@@ -3935,30 +3944,56 @@ class CloudInteropAPI {
|
|
|
3935
3944
|
* @memberof CloudInteropAPI
|
|
3936
3945
|
*/
|
|
3937
3946
|
async setContext(contextGroup, context) {
|
|
3938
|
-
if (!this
|
|
3947
|
+
if (!this.#sessionDetails || !this.#connectionParams) {
|
|
3939
3948
|
throw new Error('Session not connected');
|
|
3940
3949
|
}
|
|
3941
|
-
const { sourceId } = this.connectionParams;
|
|
3942
3950
|
const payload = {
|
|
3943
|
-
sourceId,
|
|
3944
3951
|
context,
|
|
3952
|
+
timestamp: Date.now(),
|
|
3945
3953
|
};
|
|
3946
|
-
await axios.post(`${this
|
|
3954
|
+
await axios.post(`${this.#cloudInteropSettings.url}/api/context-groups/${this.#sessionDetails.sessionId}/${contextGroup}`, payload, {
|
|
3947
3955
|
headers: this.#getRequestHeaders(),
|
|
3948
3956
|
});
|
|
3949
3957
|
}
|
|
3950
3958
|
addEventListener(type, callback) {
|
|
3951
|
-
const listeners = this
|
|
3959
|
+
const listeners = this.#eventListeners.get(type) || [];
|
|
3952
3960
|
listeners.push(callback);
|
|
3953
|
-
this
|
|
3961
|
+
this.#eventListeners.set(type, listeners);
|
|
3954
3962
|
}
|
|
3955
3963
|
removeEventListener(type, callback) {
|
|
3956
|
-
const listeners = this
|
|
3964
|
+
const listeners = this.#eventListeners.get(type) || [];
|
|
3957
3965
|
const index = listeners.indexOf(callback);
|
|
3958
3966
|
if (index !== -1) {
|
|
3959
3967
|
listeners.splice(index, 1);
|
|
3960
3968
|
}
|
|
3961
|
-
this
|
|
3969
|
+
this.#eventListeners.set(type, listeners);
|
|
3970
|
+
}
|
|
3971
|
+
async #disconnect(fireDisconnectedEvent) {
|
|
3972
|
+
if (!this.#sessionDetails) {
|
|
3973
|
+
return;
|
|
3974
|
+
}
|
|
3975
|
+
try {
|
|
3976
|
+
const disconnectResponse = await axios.delete(`${this.#cloudInteropSettings.url}/api/sessions/${this.#sessionDetails.sessionId}`, {
|
|
3977
|
+
headers: this.#getRequestHeaders(),
|
|
3978
|
+
});
|
|
3979
|
+
if (disconnectResponse.status !== 200) {
|
|
3980
|
+
throw new CloudInteropAPIError('Error during session tear down - unexpected status', 'ERR_DISCONNECT', disconnectResponse.status);
|
|
3981
|
+
}
|
|
3982
|
+
}
|
|
3983
|
+
catch {
|
|
3984
|
+
throw new CloudInteropAPIError('Error during disconnection', 'ERR_DISCONNECT');
|
|
3985
|
+
}
|
|
3986
|
+
finally {
|
|
3987
|
+
this.#mqttClient?.removeAllListeners();
|
|
3988
|
+
await this.#mqttClient?.endAsync(true);
|
|
3989
|
+
this.#sessionDetails = undefined;
|
|
3990
|
+
this.#mqttClient = undefined;
|
|
3991
|
+
this.#reconnectRetries = 0;
|
|
3992
|
+
this.#attemptingToReconnect = false;
|
|
3993
|
+
if (fireDisconnectedEvent) {
|
|
3994
|
+
this.#emitEvent('disconnected');
|
|
3995
|
+
}
|
|
3996
|
+
}
|
|
3962
3997
|
}
|
|
3963
3998
|
#handleCommand(topic, message, sessionDetails) {
|
|
3964
3999
|
if (message.length === 0 || !sessionDetails) {
|
|
@@ -3970,12 +4005,12 @@ class CloudInteropAPI {
|
|
|
3970
4005
|
if (messageEnvelope.source.sessionId === sessionDetails.sessionId) {
|
|
3971
4006
|
return;
|
|
3972
4007
|
}
|
|
3973
|
-
const { channelName: contextGroup, payload: context, source } = messageEnvelope;
|
|
3974
|
-
this.#emitEvent('context', { contextGroup, context, source });
|
|
4008
|
+
const { channelName: contextGroup, payload: context, source, history } = messageEnvelope;
|
|
4009
|
+
this.#emitEvent('context', { contextGroup, context, source, history: { ...history, clientReceived: Date.now() } });
|
|
3975
4010
|
}
|
|
3976
4011
|
}
|
|
3977
4012
|
#emitEvent(type, ...args) {
|
|
3978
|
-
const listeners = this
|
|
4013
|
+
const listeners = this.#eventListeners.get(type) || [];
|
|
3979
4014
|
listeners.forEach((listener) => listener(...args));
|
|
3980
4015
|
}
|
|
3981
4016
|
#validateConnectParams = (parameters) => {
|
|
@@ -3992,22 +4027,22 @@ class CloudInteropAPI {
|
|
|
3992
4027
|
}
|
|
3993
4028
|
};
|
|
3994
4029
|
#getRequestHeaders = () => {
|
|
3995
|
-
if (!this
|
|
4030
|
+
if (!this.#connectionParams) {
|
|
3996
4031
|
throw new Error('Connect parameters must be provided');
|
|
3997
4032
|
}
|
|
3998
4033
|
const headers = new AxiosHeaders();
|
|
3999
4034
|
headers['Content-Type'] = 'application/json';
|
|
4000
|
-
if (this
|
|
4001
|
-
const tokenResult = this
|
|
4035
|
+
if (this.#connectionParams.authenticationType === 'jwt' && this.#connectionParams.jwtAuthenticationParameters) {
|
|
4036
|
+
const tokenResult = this.#connectionParams.jwtAuthenticationParameters.jwtRequestCallback();
|
|
4002
4037
|
if (!tokenResult) {
|
|
4003
4038
|
throw new Error('jwtRequestCallback must return a token');
|
|
4004
4039
|
}
|
|
4005
|
-
headers['x-of-auth-id'] = this
|
|
4040
|
+
headers['x-of-auth-id'] = this.#connectionParams.jwtAuthenticationParameters.authenticationId;
|
|
4006
4041
|
headers['Authorization'] =
|
|
4007
4042
|
typeof tokenResult === 'string' ? `Bearer ${tokenResult}` : `Bearer ${Buffer.from(JSON.stringify(tokenResult)).toString('base64')}`;
|
|
4008
4043
|
}
|
|
4009
|
-
if (this
|
|
4010
|
-
const { username, password } = this
|
|
4044
|
+
if (this.#connectionParams.authenticationType === 'basic' && this.#connectionParams.basicAuthenticationParameters) {
|
|
4045
|
+
const { username, password } = this.#connectionParams.basicAuthenticationParameters;
|
|
4011
4046
|
headers['Authorization'] = `Basic ${Buffer.from(`${username}:${password}`).toString('base64')}`;
|
|
4012
4047
|
}
|
|
4013
4048
|
return headers;
|
package/dist/interfaces.d.ts
CHANGED
|
@@ -127,7 +127,16 @@ export type IntentDetail = {
|
|
|
127
127
|
* @property {Source} source - The source of the context
|
|
128
128
|
*/
|
|
129
129
|
export type ContextEvent = {
|
|
130
|
+
/**
|
|
131
|
+
* The context group
|
|
132
|
+
*/
|
|
130
133
|
contextGroup: string;
|
|
134
|
+
/**
|
|
135
|
+
* The context object
|
|
136
|
+
*/
|
|
131
137
|
context: object;
|
|
138
|
+
/**
|
|
139
|
+
* The source of the context
|
|
140
|
+
*/
|
|
132
141
|
source: Source;
|
|
133
142
|
};
|