libshv-js 3.5.0 → 3.6.0
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/package.json +1 -1
- package/src/ws-client.ts +146 -86
package/package.json
CHANGED
package/src/ws-client.ts
CHANGED
|
@@ -36,21 +36,38 @@ type Subscription = {
|
|
|
36
36
|
callback: SubscriptionCallback;
|
|
37
37
|
};
|
|
38
38
|
|
|
39
|
-
type
|
|
39
|
+
type Login = {
|
|
40
|
+
type: 'PLAIN';
|
|
41
|
+
user: string;
|
|
42
|
+
password: string;
|
|
43
|
+
} | {
|
|
44
|
+
type: 'TOKEN';
|
|
45
|
+
token: string;
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
type WsClientOptionsCommon = {
|
|
40
49
|
logDebug: (...args: string[]) => void;
|
|
50
|
+
wsUri: string;
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
type WsClientOptionsLogin = WsClientOptionsCommon & {
|
|
41
54
|
mountPoint?: string;
|
|
42
|
-
|
|
43
|
-
password: string;
|
|
44
|
-
loginType?: 'PLAIN' | 'AZURE';
|
|
55
|
+
login: Login;
|
|
45
56
|
timeout?: number;
|
|
46
57
|
pingInterval?: number;
|
|
47
|
-
wsUri: string;
|
|
48
58
|
onConnected: () => void;
|
|
49
59
|
onConnectionFailure: (error: Error) => void;
|
|
50
60
|
onDisconnected: () => void;
|
|
51
61
|
onRequest: (rpc_msg: RpcRequest) => void;
|
|
52
62
|
};
|
|
53
63
|
|
|
64
|
+
type WsClientOptionsWorkflows = WsClientOptionsCommon & {
|
|
65
|
+
onWorkflows: (workflows: RpcValue) => void;
|
|
66
|
+
onWorkflowsFailed: () => void;
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
type WsClientOptions = WsClientOptionsLogin | WsClientOptionsWorkflows;
|
|
70
|
+
|
|
54
71
|
type LsResult = string[];
|
|
55
72
|
export enum DirFlags {
|
|
56
73
|
Reserved = 1,
|
|
@@ -103,99 +120,31 @@ class UserIDRequired extends RpcError {}
|
|
|
103
120
|
class NotImplemented extends RpcError {}
|
|
104
121
|
|
|
105
122
|
class WsClient {
|
|
106
|
-
requestId = 1;
|
|
107
|
-
pingTimerId = -1;
|
|
123
|
+
private requestId = 1;
|
|
124
|
+
private pingTimerId = -1;
|
|
108
125
|
|
|
109
|
-
rpcHandlers: Array<{
|
|
126
|
+
private rpcHandlers: Array<{
|
|
110
127
|
resolve: RpcResponseResolver;
|
|
111
128
|
timeout_handle: number;
|
|
112
129
|
}> = [];
|
|
113
130
|
|
|
114
|
-
subscriptions: Subscription[] = [];
|
|
115
|
-
websocket: WebSocket;
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
user?: WsClientOptions['user'];
|
|
120
|
-
password: WsClientOptions['password'];
|
|
121
|
-
loginType: WsClientOptions['loginType'];
|
|
122
|
-
onConnected: WsClientOptions['onConnected'];
|
|
123
|
-
onConnectionFailure: WsClientOptions['onConnectionFailure'];
|
|
124
|
-
onDisconnected: WsClientOptions['onDisconnected'];
|
|
125
|
-
onRequest: WsClientOptions['onRequest'];
|
|
126
|
-
timeout: WsClientOptions['timeout'];
|
|
127
|
-
pingInterval: WsClientOptions['pingInterval'];
|
|
131
|
+
private readonly subscriptions: Subscription[] = [];
|
|
132
|
+
private readonly websocket: WebSocket;
|
|
133
|
+
|
|
134
|
+
private readonly options: WsClientOptions;
|
|
135
|
+
private readonly timeout: number;
|
|
128
136
|
|
|
129
137
|
constructor(options: WsClientOptions) {
|
|
130
138
|
if (typeof options !== 'object') {
|
|
131
139
|
throw new TypeError('No options object supplied');
|
|
132
140
|
}
|
|
133
141
|
|
|
134
|
-
this.
|
|
135
|
-
this.
|
|
136
|
-
|
|
137
|
-
this.user = options.user ?? '';
|
|
138
|
-
this.password = options.password;
|
|
139
|
-
this.loginType = options.loginType ?? 'PLAIN';
|
|
142
|
+
this.options = options;
|
|
143
|
+
this.timeout = 'timeout' in options && options.timeout !== undefined ? options.timeout : DEFAULT_TIMEOUT;
|
|
140
144
|
|
|
141
145
|
this.websocket = new WebSocket(options.wsUri);
|
|
142
146
|
this.websocket.binaryType = 'arraybuffer';
|
|
143
147
|
|
|
144
|
-
this.pingInterval = options.pingInterval ?? DEFAULT_PING_INTERVAL;
|
|
145
|
-
this.onConnected = options.onConnected ?? (() => {/* nothing */});
|
|
146
|
-
this.onConnectionFailure = options.onConnectionFailure ?? (() => {/* nothing */});
|
|
147
|
-
this.onDisconnected = options.onDisconnected ?? (() => {/* nothing */});
|
|
148
|
-
this.onRequest = options.onRequest ?? (() => {/* nothing */});
|
|
149
|
-
|
|
150
|
-
this.timeout = options.timeout ?? DEFAULT_TIMEOUT;
|
|
151
|
-
|
|
152
|
-
this.websocket.addEventListener('open', () => {
|
|
153
|
-
this.logDebug('CONNECTED');
|
|
154
|
-
const handleConnectionError = (error: Error) => {
|
|
155
|
-
this.logDebug('FAILURE: couldn\'t perform initial handshake', error.message);
|
|
156
|
-
this.onConnectionFailure(error);
|
|
157
|
-
};
|
|
158
|
-
|
|
159
|
-
this.callRpcMethod(undefined, 'hello').then(response => {
|
|
160
|
-
if (response instanceof Error) {
|
|
161
|
-
handleConnectionError(response);
|
|
162
|
-
return;
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
const params = makeMap({
|
|
166
|
-
login: makeMap({
|
|
167
|
-
password: this.password,
|
|
168
|
-
type: this.loginType,
|
|
169
|
-
user: this.user,
|
|
170
|
-
}),
|
|
171
|
-
options: makeMap({
|
|
172
|
-
device: this.mountPoint === 'string' ? makeMap({mountPoint: this.mountPoint}) : undefined,
|
|
173
|
-
}),
|
|
174
|
-
});
|
|
175
|
-
return this.callRpcMethod(undefined, 'login', params);
|
|
176
|
-
}).then(response => {
|
|
177
|
-
if (response instanceof Error) {
|
|
178
|
-
handleConnectionError(response);
|
|
179
|
-
return;
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
this.logDebug('SUCCESS: connected to shv broker');
|
|
183
|
-
this.onConnected();
|
|
184
|
-
this.pingTimerId = window.setInterval(() => {
|
|
185
|
-
this.sendPing();
|
|
186
|
-
}, this.pingInterval);
|
|
187
|
-
}).catch(() => {
|
|
188
|
-
this.logDebug('FAILURE: couldn\' connected to shv broker');
|
|
189
|
-
});
|
|
190
|
-
});
|
|
191
|
-
|
|
192
|
-
this.websocket.addEventListener('close', () => {
|
|
193
|
-
this.logDebug('DISCONNECTED');
|
|
194
|
-
this.subscriptions.length = 0;
|
|
195
|
-
this.onDisconnected();
|
|
196
|
-
window.clearInterval(this.pingTimerId);
|
|
197
|
-
});
|
|
198
|
-
|
|
199
148
|
this.websocket.addEventListener('message', (evt: MessageEvent<ArrayBuffer>) => {
|
|
200
149
|
const rpcVal = dataToRpcValue(evt.data);
|
|
201
150
|
const rpcMsg = RpcMessageZod.parse(rpcVal);
|
|
@@ -211,7 +160,9 @@ class WsClient {
|
|
|
211
160
|
}
|
|
212
161
|
}
|
|
213
162
|
} else if (isRequest(rpcMsg)) {
|
|
214
|
-
this.
|
|
163
|
+
if ('onRequest' in this.options) {
|
|
164
|
+
this.options.onRequest(rpcMsg);
|
|
165
|
+
}
|
|
215
166
|
} else if (isResponse(rpcMsg)) {
|
|
216
167
|
const requestId = rpcMsg.meta[RPC_MESSAGE_REQUEST_ID];
|
|
217
168
|
|
|
@@ -258,6 +209,12 @@ class WsClient {
|
|
|
258
209
|
console.log('WebSocket ERROR', evt);
|
|
259
210
|
this.logDebug('WebSocket ERROR');
|
|
260
211
|
});
|
|
212
|
+
|
|
213
|
+
if ('onWorkflows' in this.options) {
|
|
214
|
+
this.workflowsProcedure(this.options);
|
|
215
|
+
} else {
|
|
216
|
+
this.loginProcedure(this.options);
|
|
217
|
+
}
|
|
261
218
|
}
|
|
262
219
|
|
|
263
220
|
callRpcMethod(shv_path: '.broker/currentClient', method: 'accessGrantForMethodCall', params: [string, string]): Promise<ResultOrError<string>>;
|
|
@@ -272,8 +229,7 @@ class WsClient {
|
|
|
272
229
|
[RPC_MESSAGE_SHV_PATH]: shv_path ?? '',
|
|
273
230
|
}), makeIMap({
|
|
274
231
|
[RPC_MESSAGE_PARAMS]: params,
|
|
275
|
-
})
|
|
276
|
-
);
|
|
232
|
+
}));
|
|
277
233
|
|
|
278
234
|
this.sendRpcMessage(rq);
|
|
279
235
|
|
|
@@ -370,6 +326,110 @@ class WsClient {
|
|
|
370
326
|
close() {
|
|
371
327
|
this.websocket.close();
|
|
372
328
|
}
|
|
329
|
+
|
|
330
|
+
private logDebug(...args: string[]) {
|
|
331
|
+
if (this.options.logDebug === undefined) {
|
|
332
|
+
return;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
this.options.logDebug(...args);
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
private workflowsProcedure(options: WsClientOptionsWorkflows) {
|
|
339
|
+
let workflowsObtained = false;
|
|
340
|
+
this.websocket.addEventListener('open', () => {
|
|
341
|
+
this.options.logDebug('CONNECTED');
|
|
342
|
+
const handleConnectionError = (error: Error) => {
|
|
343
|
+
this.logDebug('FAILURE: couldn\'t retrieve workflows', error.message);
|
|
344
|
+
options.onWorkflowsFailed();
|
|
345
|
+
};
|
|
346
|
+
|
|
347
|
+
this.callRpcMethod(undefined, 'workflows').then(response => {
|
|
348
|
+
if (response instanceof Error) {
|
|
349
|
+
handleConnectionError(response);
|
|
350
|
+
return;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
workflowsObtained = true;
|
|
354
|
+
options.onWorkflows(response);
|
|
355
|
+
|
|
356
|
+
this.close();
|
|
357
|
+
}).catch((_: unknown) => {
|
|
358
|
+
options.onWorkflowsFailed();
|
|
359
|
+
});
|
|
360
|
+
});
|
|
361
|
+
|
|
362
|
+
this.websocket.addEventListener('close', () => {
|
|
363
|
+
this.logDebug('DISCONNECTED');
|
|
364
|
+
if (!workflowsObtained) {
|
|
365
|
+
options.onWorkflowsFailed();
|
|
366
|
+
}
|
|
367
|
+
});
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
private loginProcedure(options: WsClientOptionsLogin) {
|
|
371
|
+
this.websocket.addEventListener('open', () => {
|
|
372
|
+
this.options.logDebug('CONNECTED');
|
|
373
|
+
const handleConnectionError = (error: Error) => {
|
|
374
|
+
this.logDebug('FAILURE: couldn\'t perform initial handshake', error.message);
|
|
375
|
+
options.onConnectionFailure(error);
|
|
376
|
+
};
|
|
377
|
+
|
|
378
|
+
const handleLoginResponse = (response: any) => {
|
|
379
|
+
if (response instanceof Error) {
|
|
380
|
+
handleConnectionError(response);
|
|
381
|
+
return;
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
this.logDebug('SUCCESS: connected to shv broker');
|
|
385
|
+
options.onConnected();
|
|
386
|
+
this.pingTimerId = window.setInterval(() => {
|
|
387
|
+
this.sendPing();
|
|
388
|
+
}, options.pingInterval ?? DEFAULT_PING_INTERVAL);
|
|
389
|
+
};
|
|
390
|
+
|
|
391
|
+
const makeLoginParams = (loginMap: ShvMap) => makeMap({
|
|
392
|
+
login: loginMap,
|
|
393
|
+
options: makeMap({
|
|
394
|
+
device: options.mountPoint === 'string' ? makeMap({mountPoint: options.mountPoint}) : undefined,
|
|
395
|
+
}),
|
|
396
|
+
});
|
|
397
|
+
|
|
398
|
+
this.callRpcMethod(undefined, 'hello').then(response => {
|
|
399
|
+
if (response instanceof Error) {
|
|
400
|
+
handleConnectionError(response);
|
|
401
|
+
return;
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
const makeLoginMap = () => {
|
|
405
|
+
switch (options.login.type) {
|
|
406
|
+
case 'PLAIN':
|
|
407
|
+
return makeMap({
|
|
408
|
+
password: options.login.password,
|
|
409
|
+
type: options.login.type,
|
|
410
|
+
user: options.login.user,
|
|
411
|
+
});
|
|
412
|
+
case 'TOKEN':
|
|
413
|
+
return makeMap({
|
|
414
|
+
token: options.login.token,
|
|
415
|
+
type: options.login.type,
|
|
416
|
+
});
|
|
417
|
+
}
|
|
418
|
+
};
|
|
419
|
+
|
|
420
|
+
return this.callRpcMethod(undefined, 'login', makeLoginParams(makeLoginMap()));
|
|
421
|
+
}).then(handleLoginResponse).catch(() => {
|
|
422
|
+
this.logDebug('FAILURE: couldn\' connected to shv broker');
|
|
423
|
+
});
|
|
424
|
+
});
|
|
425
|
+
|
|
426
|
+
this.websocket.addEventListener('close', () => {
|
|
427
|
+
this.logDebug('DISCONNECTED');
|
|
428
|
+
this.subscriptions.length = 0;
|
|
429
|
+
options.onDisconnected();
|
|
430
|
+
window.clearInterval(this.pingTimerId);
|
|
431
|
+
});
|
|
432
|
+
}
|
|
373
433
|
}
|
|
374
434
|
|
|
375
435
|
export {WsClient};
|