@socket-mesh/core 1.0.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/README.md +2 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.js +10 -0
- package/dist/maps/handler-map.d.ts +5 -0
- package/dist/maps/handler-map.js +1 -0
- package/dist/maps/index.d.ts +3 -0
- package/dist/maps/index.js +3 -0
- package/dist/maps/method-map.d.ts +22 -0
- package/dist/maps/method-map.js +1 -0
- package/dist/maps/socket-map.d.ts +15 -0
- package/dist/maps/socket-map.js +1 -0
- package/dist/packet.d.ts +27 -0
- package/dist/packet.js +3 -0
- package/dist/plugins/index.d.ts +1 -0
- package/dist/plugins/index.js +1 -0
- package/dist/plugins/plugin.d.ts +50 -0
- package/dist/plugins/plugin.js +1 -0
- package/dist/request-handler.d.ts +24 -0
- package/dist/request-handler.js +23 -0
- package/dist/request.d.ts +55 -0
- package/dist/request.js +56 -0
- package/dist/response.d.ts +22 -0
- package/dist/response.js +3 -0
- package/dist/socket-event.d.ts +76 -0
- package/dist/socket-event.js +1 -0
- package/dist/socket-transport.d.ts +93 -0
- package/dist/socket-transport.js +671 -0
- package/dist/socket.d.ts +86 -0
- package/dist/socket.js +54 -0
- package/dist/utils.d.ts +2 -0
- package/dist/utils.js +10 -0
- package/package.json +42 -0
|
@@ -0,0 +1,671 @@
|
|
|
1
|
+
import defaultCodec from "@socket-mesh/formatter";
|
|
2
|
+
import ws from "isomorphic-ws";
|
|
3
|
+
import { abortRequest, isRequestDone } from "./request.js";
|
|
4
|
+
import { isRequestPacket } from "./packet.js";
|
|
5
|
+
import { AbortError, BadConnectionError, InvalidActionError, InvalidArgumentsError, PluginBlockedError, SocketProtocolError, TimeoutError, dehydrateError, hydrateError, socketProtocolErrorStatuses, socketProtocolIgnoreStatuses } from "@socket-mesh/errors";
|
|
6
|
+
import { isResponsePacket } from "./response.js";
|
|
7
|
+
import { extractAuthTokenData } from "@socket-mesh/auth";
|
|
8
|
+
import { RequestHandlerArgs } from "./request-handler.js";
|
|
9
|
+
import { toArray, wait } from "./utils.js";
|
|
10
|
+
export class SocketTransport {
|
|
11
|
+
constructor(options) {
|
|
12
|
+
let cid = 1;
|
|
13
|
+
this.ackTimeoutMs = options?.ackTimeoutMs ?? 10000;
|
|
14
|
+
this._callIdGenerator = options?.callIdGenerator || (() => {
|
|
15
|
+
return cid++;
|
|
16
|
+
});
|
|
17
|
+
this._callbackMap = {};
|
|
18
|
+
this.codecEngine = options?.codecEngine || defaultCodec;
|
|
19
|
+
this._handlers = options?.handlers || {};
|
|
20
|
+
this.id = null;
|
|
21
|
+
this._inboundProcessedMessageCount = 0;
|
|
22
|
+
this._inboundReceivedMessageCount = 0;
|
|
23
|
+
this._outboundPreparedMessageCount = 0;
|
|
24
|
+
this._outboundSentMessageCount = 0;
|
|
25
|
+
this._pingTimeoutRef = null;
|
|
26
|
+
this.plugins = options?.plugins || [];
|
|
27
|
+
this.streamCleanupMode = options?.streamCleanupMode || 'kill';
|
|
28
|
+
}
|
|
29
|
+
abortAllPendingCallbacksDueToBadConnection(status) {
|
|
30
|
+
for (const cid in this._callbackMap) {
|
|
31
|
+
const map = this._callbackMap[cid];
|
|
32
|
+
const msg = `Event ${map.method} was aborted due to a bad connection`;
|
|
33
|
+
map.callback(new BadConnectionError(msg, status === 'ready' ? 'disconnect' : 'connectAbort'));
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
get authToken() {
|
|
37
|
+
return this._authToken;
|
|
38
|
+
}
|
|
39
|
+
async changeToUnauthenticatedState() {
|
|
40
|
+
if (this._signedAuthToken) {
|
|
41
|
+
const authToken = this._authToken;
|
|
42
|
+
const signedAuthToken = this._signedAuthToken;
|
|
43
|
+
this._authToken = null;
|
|
44
|
+
this._signedAuthToken = null;
|
|
45
|
+
this._socket.emit('authStateChange', { wasAuthenticated: true, isAuthenticated: false });
|
|
46
|
+
// In order for the events to trigger we need to wait for the next tick.
|
|
47
|
+
await wait(0);
|
|
48
|
+
this._socket.emit('deauthenticate', { signedAuthToken, authToken });
|
|
49
|
+
for (const plugin of this.plugins) {
|
|
50
|
+
if (plugin.onDeauthenticate) {
|
|
51
|
+
plugin.onDeauthenticate({ socket: this.socket, transport: this });
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return true;
|
|
55
|
+
}
|
|
56
|
+
return false;
|
|
57
|
+
}
|
|
58
|
+
decode(data) {
|
|
59
|
+
try {
|
|
60
|
+
return this.codecEngine.decode(data);
|
|
61
|
+
}
|
|
62
|
+
catch (err) {
|
|
63
|
+
if (err.name === 'Error') {
|
|
64
|
+
err.name = 'InvalidMessageError';
|
|
65
|
+
}
|
|
66
|
+
this.onError(err);
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
disconnect(code = 1000, reason) {
|
|
71
|
+
if (this.webSocket) {
|
|
72
|
+
this.webSocket.close(code, reason);
|
|
73
|
+
this.onClose(code, reason);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
getBackpressure() {
|
|
77
|
+
return Math.max(this.getInboundBackpressure(), this.getOutboundBackpressure());
|
|
78
|
+
}
|
|
79
|
+
getInboundBackpressure() {
|
|
80
|
+
return this._inboundReceivedMessageCount - this._inboundProcessedMessageCount;
|
|
81
|
+
}
|
|
82
|
+
getOutboundBackpressure() {
|
|
83
|
+
return this._outboundPreparedMessageCount - this._outboundSentMessageCount;
|
|
84
|
+
}
|
|
85
|
+
async handleInboudMessage({ data, timestamp }) {
|
|
86
|
+
let packet = this.decode(data);
|
|
87
|
+
if (packet === null) {
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
packet = toArray(packet);
|
|
91
|
+
for (let curPacket of packet) {
|
|
92
|
+
let pluginError;
|
|
93
|
+
try {
|
|
94
|
+
for (const plugin of this.plugins) {
|
|
95
|
+
if (plugin.onMessage) {
|
|
96
|
+
curPacket = await plugin.onMessage({ socket: this.socket, transport: this, packet: curPacket, timestamp: timestamp });
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
catch (err) {
|
|
101
|
+
pluginError = err;
|
|
102
|
+
}
|
|
103
|
+
// Check to see if it is a request or response packet.
|
|
104
|
+
if (isResponsePacket(curPacket)) {
|
|
105
|
+
this.onResponse(curPacket, pluginError);
|
|
106
|
+
}
|
|
107
|
+
else if (isRequestPacket(curPacket)) {
|
|
108
|
+
await this.onRequest(curPacket, timestamp, pluginError);
|
|
109
|
+
}
|
|
110
|
+
else {
|
|
111
|
+
// TODO: Handle non packets here (binary data)
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
onClose(code, reason) {
|
|
116
|
+
const prevStatus = this.status;
|
|
117
|
+
this.webSocket = null;
|
|
118
|
+
this._isReady = false;
|
|
119
|
+
clearTimeout(this._pingTimeoutRef);
|
|
120
|
+
this._pingTimeoutRef = null;
|
|
121
|
+
this.abortAllPendingCallbacksDueToBadConnection(prevStatus);
|
|
122
|
+
for (const plugin of this.plugins) {
|
|
123
|
+
if (plugin.onClose) {
|
|
124
|
+
plugin.onClose({ socket: this.socket, transport: this });
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
if (!socketProtocolIgnoreStatuses[code]) {
|
|
128
|
+
let closeMessage;
|
|
129
|
+
if (typeof reason === 'string') {
|
|
130
|
+
closeMessage = `Socket connection closed with status code ${code} and reason: ${reason}`;
|
|
131
|
+
}
|
|
132
|
+
else {
|
|
133
|
+
closeMessage = `Socket connection closed with status code ${code}`;
|
|
134
|
+
}
|
|
135
|
+
this.onError(new SocketProtocolError(socketProtocolErrorStatuses[code] || closeMessage, code));
|
|
136
|
+
}
|
|
137
|
+
const strReason = reason?.toString() || socketProtocolErrorStatuses[code];
|
|
138
|
+
this._socket.emit('close', { code, reason: strReason });
|
|
139
|
+
}
|
|
140
|
+
onDisconnect(status, code, reason) {
|
|
141
|
+
if (status === 'ready') {
|
|
142
|
+
this._socket.emit('disconnect', { code, reason });
|
|
143
|
+
}
|
|
144
|
+
else {
|
|
145
|
+
this._socket.emit('connectAbort', { code, reason });
|
|
146
|
+
}
|
|
147
|
+
for (const plugin of this.plugins) {
|
|
148
|
+
if (plugin.onDisconnected) {
|
|
149
|
+
plugin.onDisconnected({ socket: this.socket, transport: this, status, code, reason });
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
onError(error) {
|
|
154
|
+
this._socket.emit('error', { error });
|
|
155
|
+
}
|
|
156
|
+
onInvoke(request) {
|
|
157
|
+
this.sendRequest([request]);
|
|
158
|
+
}
|
|
159
|
+
onMessage(data, isBinary) {
|
|
160
|
+
const timestamp = new Date();
|
|
161
|
+
let p = Promise.resolve(isBinary ? data : data.toString());
|
|
162
|
+
let resolve;
|
|
163
|
+
let reject;
|
|
164
|
+
const promise = new Promise((res, rej) => {
|
|
165
|
+
resolve = res;
|
|
166
|
+
reject = rej;
|
|
167
|
+
});
|
|
168
|
+
this._inboundReceivedMessageCount++;
|
|
169
|
+
for (let i = 0; i < this.plugins.length; i++) {
|
|
170
|
+
const plugin = this.plugins[i];
|
|
171
|
+
if (plugin.onMessageRaw) {
|
|
172
|
+
p = p.then(message => {
|
|
173
|
+
return plugin.onMessageRaw({ socket: this.socket, transport: this, message, timestamp, promise });
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
p.then(data => {
|
|
178
|
+
this._socket.emit('message', { data, isBinary });
|
|
179
|
+
return this.handleInboudMessage({ data, timestamp });
|
|
180
|
+
})
|
|
181
|
+
.then(resolve)
|
|
182
|
+
.catch(err => {
|
|
183
|
+
reject(err);
|
|
184
|
+
if (!(err instanceof PluginBlockedError)) {
|
|
185
|
+
this.onError(err);
|
|
186
|
+
}
|
|
187
|
+
}).finally(() => {
|
|
188
|
+
this._inboundProcessedMessageCount++;
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
onOpen() {
|
|
192
|
+
// Placeholder for inherited classes
|
|
193
|
+
for (const plugin of this.plugins) {
|
|
194
|
+
if (plugin.onOpen) {
|
|
195
|
+
plugin.onOpen({ socket: this.socket, transport: this });
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
onPing(data) {
|
|
200
|
+
this._socket.emit('ping', { data });
|
|
201
|
+
}
|
|
202
|
+
onPong(data) {
|
|
203
|
+
this._socket.emit('pong', { data });
|
|
204
|
+
}
|
|
205
|
+
async onRequest(packet, timestamp, pluginError) {
|
|
206
|
+
this._socket.emit('request', { request: packet });
|
|
207
|
+
const timeoutAt = typeof packet.ackTimeoutMs === 'number' ? new Date(timestamp.valueOf() + packet.ackTimeoutMs) : null;
|
|
208
|
+
let wasHandled = false;
|
|
209
|
+
let response;
|
|
210
|
+
let error;
|
|
211
|
+
if (pluginError) {
|
|
212
|
+
wasHandled = true;
|
|
213
|
+
error = pluginError;
|
|
214
|
+
response = { rid: packet.cid, timeoutAt, error: pluginError };
|
|
215
|
+
}
|
|
216
|
+
else {
|
|
217
|
+
const handler = this._handlers[packet.method];
|
|
218
|
+
if (handler) {
|
|
219
|
+
wasHandled = true;
|
|
220
|
+
try {
|
|
221
|
+
const data = await handler(new RequestHandlerArgs({
|
|
222
|
+
isRpc: !!packet.cid,
|
|
223
|
+
method: packet.method.toString(),
|
|
224
|
+
timeoutMs: packet.ackTimeoutMs,
|
|
225
|
+
socket: this._socket,
|
|
226
|
+
transport: this,
|
|
227
|
+
options: packet.data
|
|
228
|
+
}));
|
|
229
|
+
if (packet.cid) {
|
|
230
|
+
response = { rid: packet.cid, timeoutAt, data };
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
catch (err) {
|
|
234
|
+
error = err;
|
|
235
|
+
if (packet.cid) {
|
|
236
|
+
response = { rid: packet.cid, timeoutAt, error };
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
if (response) {
|
|
242
|
+
this.sendResponse([response]);
|
|
243
|
+
}
|
|
244
|
+
if (error) {
|
|
245
|
+
this.onError(error);
|
|
246
|
+
}
|
|
247
|
+
if (!wasHandled) {
|
|
248
|
+
wasHandled = this.onUnhandledRequest(packet);
|
|
249
|
+
}
|
|
250
|
+
return wasHandled;
|
|
251
|
+
}
|
|
252
|
+
onResponse(response, pluginError) {
|
|
253
|
+
const map = this._callbackMap[response.rid];
|
|
254
|
+
if (map) {
|
|
255
|
+
if (map.timeoutId) {
|
|
256
|
+
clearTimeout(map.timeoutId);
|
|
257
|
+
delete map.timeoutId;
|
|
258
|
+
}
|
|
259
|
+
if (pluginError) {
|
|
260
|
+
map.callback(pluginError);
|
|
261
|
+
}
|
|
262
|
+
else if ('error' in response) {
|
|
263
|
+
map.callback(hydrateError(response.error));
|
|
264
|
+
}
|
|
265
|
+
else {
|
|
266
|
+
map.callback(null, 'data' in response ? response.data : undefined);
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
if (pluginError) {
|
|
270
|
+
this._socket.emit('response', { response: { rid: response.rid, error: pluginError } });
|
|
271
|
+
}
|
|
272
|
+
else {
|
|
273
|
+
this._socket.emit('response', { response });
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
onTransmit(request) {
|
|
277
|
+
this.sendRequest([request]);
|
|
278
|
+
}
|
|
279
|
+
onUnexpectedResponse(request, response) {
|
|
280
|
+
this._socket.emit('unexpectedResponse', { request, response });
|
|
281
|
+
}
|
|
282
|
+
onUnhandledRequest(packet) {
|
|
283
|
+
if (this._onUnhandledRequest) {
|
|
284
|
+
return this._onUnhandledRequest(this, packet);
|
|
285
|
+
}
|
|
286
|
+
return false;
|
|
287
|
+
}
|
|
288
|
+
onUpgrade(request) {
|
|
289
|
+
this._socket.emit('upgrade', { request });
|
|
290
|
+
}
|
|
291
|
+
ping() {
|
|
292
|
+
return new Promise((resolve, reject) => {
|
|
293
|
+
this.webSocket.ping(undefined, undefined, (err) => {
|
|
294
|
+
if (err) {
|
|
295
|
+
reject(err);
|
|
296
|
+
}
|
|
297
|
+
else {
|
|
298
|
+
resolve();
|
|
299
|
+
}
|
|
300
|
+
});
|
|
301
|
+
});
|
|
302
|
+
}
|
|
303
|
+
resetPingTimeout(timeoutMs, code) {
|
|
304
|
+
if (this._pingTimeoutRef) {
|
|
305
|
+
clearTimeout(this._pingTimeoutRef);
|
|
306
|
+
this._pingTimeoutRef = null;
|
|
307
|
+
}
|
|
308
|
+
if (timeoutMs !== false) {
|
|
309
|
+
// Use `WebSocket#terminate()`, which immediately destroys the connection,
|
|
310
|
+
// instead of `WebSocket#close()`, which waits for the close timer.
|
|
311
|
+
// Delay should be equal to the interval at which your server
|
|
312
|
+
// sends out pings plus a conservative assumption of the latency.
|
|
313
|
+
this._pingTimeoutRef = setTimeout(() => {
|
|
314
|
+
this.webSocket.close(code);
|
|
315
|
+
}, timeoutMs);
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
send(data) {
|
|
319
|
+
return new Promise((resolve, reject) => {
|
|
320
|
+
try {
|
|
321
|
+
this._webSocket.send(data, (err) => {
|
|
322
|
+
if (err) {
|
|
323
|
+
reject(err);
|
|
324
|
+
return;
|
|
325
|
+
}
|
|
326
|
+
resolve();
|
|
327
|
+
});
|
|
328
|
+
}
|
|
329
|
+
catch (err) {
|
|
330
|
+
throw err;
|
|
331
|
+
}
|
|
332
|
+
});
|
|
333
|
+
}
|
|
334
|
+
sendRequest(index, requests) {
|
|
335
|
+
if (typeof index === 'object') {
|
|
336
|
+
requests = index;
|
|
337
|
+
index = 0;
|
|
338
|
+
}
|
|
339
|
+
// Filter out any requests that have already timed out.
|
|
340
|
+
if (requests.some(request => isRequestDone(request))) {
|
|
341
|
+
requests = requests.filter(req => isRequestDone(req));
|
|
342
|
+
if (!requests.length) {
|
|
343
|
+
return;
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
for (; index < this.plugins.length; index++) {
|
|
347
|
+
const plugin = this.plugins[index];
|
|
348
|
+
if ('sendRequest' in plugin) {
|
|
349
|
+
index++;
|
|
350
|
+
try {
|
|
351
|
+
plugin.sendRequest({
|
|
352
|
+
socket: this.socket,
|
|
353
|
+
transport: this,
|
|
354
|
+
requests,
|
|
355
|
+
cont: this.sendRequest.bind(this, index)
|
|
356
|
+
});
|
|
357
|
+
}
|
|
358
|
+
catch (err) {
|
|
359
|
+
for (const req of requests) {
|
|
360
|
+
abortRequest(req, err);
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
return;
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
// If the socket is closed we need to call them back with an error.
|
|
367
|
+
if (this.status === 'closed') {
|
|
368
|
+
for (const req of requests) {
|
|
369
|
+
const err = new BadConnectionError(`Socket ${'callback' in req ? 'invoke' : 'transmit'} ${String(req.method)} event was aborted due to a bad connection`, 'connectAbort');
|
|
370
|
+
this.onError(err);
|
|
371
|
+
abortRequest(req, err);
|
|
372
|
+
}
|
|
373
|
+
return;
|
|
374
|
+
}
|
|
375
|
+
const encode = requests.map(req => {
|
|
376
|
+
if ('callback' in req) {
|
|
377
|
+
const { callback, promise, timeoutId, ...rest } = req;
|
|
378
|
+
this._callbackMap[req.cid] = {
|
|
379
|
+
method: ['service' in req ? req.service : '', req.method].filter(Boolean).join('.'),
|
|
380
|
+
timeoutId: req.timeoutId,
|
|
381
|
+
callback: req.callback
|
|
382
|
+
};
|
|
383
|
+
return rest;
|
|
384
|
+
}
|
|
385
|
+
const { promise, ...rest } = req;
|
|
386
|
+
return rest;
|
|
387
|
+
});
|
|
388
|
+
this._webSocket.send(this.codecEngine.encode(encode.length === 1 ? encode[0] : encode), (err) => {
|
|
389
|
+
for (const req of requests) {
|
|
390
|
+
if (err?.code === 'ECONNRESET') {
|
|
391
|
+
err = new BadConnectionError(`Socket ${'callback' in req ? 'invoke' : 'transmit'} ${String(req.method)} event was aborted due to a bad connection`, 'connectAbort');
|
|
392
|
+
}
|
|
393
|
+
if (req.sentCallback) {
|
|
394
|
+
req.sentCallback(err);
|
|
395
|
+
}
|
|
396
|
+
if (err && 'callback' in req) {
|
|
397
|
+
req.callback(err);
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
});
|
|
401
|
+
}
|
|
402
|
+
sendResponse(index, responses) {
|
|
403
|
+
if (typeof index === 'object') {
|
|
404
|
+
responses = index;
|
|
405
|
+
index = 0;
|
|
406
|
+
}
|
|
407
|
+
// Remove any response that has timed out
|
|
408
|
+
if (!(responses = responses.filter(item => !item.timeoutAt || item.timeoutAt > new Date())).length) {
|
|
409
|
+
return;
|
|
410
|
+
}
|
|
411
|
+
for (; index < this.plugins.length; index++) {
|
|
412
|
+
const plugin = this.plugins[index];
|
|
413
|
+
if ('sendResponse' in plugin) {
|
|
414
|
+
index++;
|
|
415
|
+
try {
|
|
416
|
+
plugin.sendResponse({
|
|
417
|
+
socket: this.socket,
|
|
418
|
+
transport: this,
|
|
419
|
+
responses,
|
|
420
|
+
cont: this.sendResponse.bind(this, index)
|
|
421
|
+
});
|
|
422
|
+
}
|
|
423
|
+
catch (err) {
|
|
424
|
+
this.sendResponse(index, responses.map(item => ({ rid: item.rid, timeoutAt: item.timeoutAt, error: err })));
|
|
425
|
+
}
|
|
426
|
+
return;
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
// If the socket is closed we need to call them back with an error.
|
|
430
|
+
if (this.status === 'closed') {
|
|
431
|
+
for (const response of responses) {
|
|
432
|
+
this.onError(new Error(`WebSocket is not open: readyState 3 (CLOSED)`));
|
|
433
|
+
}
|
|
434
|
+
return;
|
|
435
|
+
}
|
|
436
|
+
for (const response of responses) {
|
|
437
|
+
if ('error' in response) {
|
|
438
|
+
response.error = dehydrateError(response.error);
|
|
439
|
+
}
|
|
440
|
+
delete response.timeoutAt;
|
|
441
|
+
}
|
|
442
|
+
//timeoutId?: NodeJS.Timeout;
|
|
443
|
+
//callback: (err: Error, result?: U) => void | null
|
|
444
|
+
this._webSocket.send(this.codecEngine.encode(responses.length === 1 ? responses[0] : responses), (err) => {
|
|
445
|
+
if (err) {
|
|
446
|
+
this.onError(err);
|
|
447
|
+
}
|
|
448
|
+
});
|
|
449
|
+
}
|
|
450
|
+
async setAuthorization(signedAuthToken, authToken) {
|
|
451
|
+
if (typeof signedAuthToken !== 'string') {
|
|
452
|
+
throw new InvalidArgumentsError('SignedAuthToken must be type string.');
|
|
453
|
+
}
|
|
454
|
+
if (signedAuthToken !== this._signedAuthToken) {
|
|
455
|
+
if (!authToken) {
|
|
456
|
+
const extractedAuthToken = extractAuthTokenData(signedAuthToken);
|
|
457
|
+
if (typeof extractedAuthToken === 'string') {
|
|
458
|
+
throw new InvalidArgumentsError('Invalid authToken.');
|
|
459
|
+
}
|
|
460
|
+
authToken = extractedAuthToken;
|
|
461
|
+
}
|
|
462
|
+
this._authToken = authToken;
|
|
463
|
+
this._signedAuthToken = signedAuthToken;
|
|
464
|
+
return true;
|
|
465
|
+
}
|
|
466
|
+
return false;
|
|
467
|
+
}
|
|
468
|
+
setReadyStatus(pingTimeoutMs, authError) {
|
|
469
|
+
if (this._webSocket?.readyState !== ws.OPEN) {
|
|
470
|
+
throw new InvalidActionError('Cannot set status to OPEN before socket is connected.');
|
|
471
|
+
}
|
|
472
|
+
this._isReady = true;
|
|
473
|
+
for (const plugin of this.plugins) {
|
|
474
|
+
if (plugin.onReady) {
|
|
475
|
+
plugin.onReady({ socket: this.socket, transport: this });
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
this._socket.emit('connect', { id: this.id, pingTimeoutMs, isAuthenticated: !!this.signedAuthToken, authError });
|
|
479
|
+
}
|
|
480
|
+
get signedAuthToken() {
|
|
481
|
+
return this._signedAuthToken;
|
|
482
|
+
}
|
|
483
|
+
get socket() {
|
|
484
|
+
return this._socket;
|
|
485
|
+
}
|
|
486
|
+
set socket(value) {
|
|
487
|
+
this._socket = value;
|
|
488
|
+
}
|
|
489
|
+
get status() {
|
|
490
|
+
if (!this._webSocket) {
|
|
491
|
+
return 'closed';
|
|
492
|
+
}
|
|
493
|
+
if (this._isReady) {
|
|
494
|
+
return 'ready';
|
|
495
|
+
}
|
|
496
|
+
switch (this._webSocket.readyState) {
|
|
497
|
+
case ws.CONNECTING:
|
|
498
|
+
case ws.OPEN:
|
|
499
|
+
return 'connecting';
|
|
500
|
+
case ws.CLOSING:
|
|
501
|
+
return 'closing';
|
|
502
|
+
default:
|
|
503
|
+
return 'closed';
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
triggerAuthenticationEvents(wasSigned, wasAuthenticated) {
|
|
507
|
+
this._socket.emit('authStateChange', { wasAuthenticated, isAuthenticated: true, authToken: this._authToken, signedAuthToken: this._signedAuthToken });
|
|
508
|
+
this._socket.emit('authenticate', { wasSigned, signedAuthToken: this._signedAuthToken, authToken: this._authToken });
|
|
509
|
+
for (const plugin of this.plugins) {
|
|
510
|
+
if (plugin.onAuthenticated) {
|
|
511
|
+
plugin.onAuthenticated({ socket: this.socket, transport: this });
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
transmit(serviceAndMethod, arg) {
|
|
516
|
+
let service;
|
|
517
|
+
let serviceMethod;
|
|
518
|
+
let method;
|
|
519
|
+
if (Array.isArray(serviceAndMethod)) {
|
|
520
|
+
service = serviceAndMethod[0];
|
|
521
|
+
serviceMethod = serviceAndMethod[1];
|
|
522
|
+
}
|
|
523
|
+
else {
|
|
524
|
+
method = serviceAndMethod;
|
|
525
|
+
}
|
|
526
|
+
const request = service ? {
|
|
527
|
+
service,
|
|
528
|
+
method: serviceMethod,
|
|
529
|
+
promise: null,
|
|
530
|
+
data: arg
|
|
531
|
+
} : {
|
|
532
|
+
data: arg,
|
|
533
|
+
method,
|
|
534
|
+
promise: null
|
|
535
|
+
};
|
|
536
|
+
const promise = request.promise = new Promise((resolve, reject) => {
|
|
537
|
+
request.sentCallback = (err) => {
|
|
538
|
+
delete request.sentCallback;
|
|
539
|
+
this._outboundSentMessageCount++;
|
|
540
|
+
if (err) {
|
|
541
|
+
reject(err);
|
|
542
|
+
return;
|
|
543
|
+
}
|
|
544
|
+
resolve();
|
|
545
|
+
};
|
|
546
|
+
});
|
|
547
|
+
this._outboundPreparedMessageCount++;
|
|
548
|
+
this.onTransmit(request);
|
|
549
|
+
return promise;
|
|
550
|
+
}
|
|
551
|
+
invoke(methodOptions, arg) {
|
|
552
|
+
let service;
|
|
553
|
+
let serviceMethod;
|
|
554
|
+
let method;
|
|
555
|
+
let ackTimeoutMs;
|
|
556
|
+
if (typeof methodOptions === 'object') {
|
|
557
|
+
if (Array.isArray(methodOptions)) {
|
|
558
|
+
service = methodOptions[0];
|
|
559
|
+
serviceMethod = methodOptions[1];
|
|
560
|
+
ackTimeoutMs = methodOptions[2];
|
|
561
|
+
}
|
|
562
|
+
else {
|
|
563
|
+
if ('service' in methodOptions) {
|
|
564
|
+
service = methodOptions.service;
|
|
565
|
+
serviceMethod = methodOptions.method;
|
|
566
|
+
}
|
|
567
|
+
else {
|
|
568
|
+
method = methodOptions.method;
|
|
569
|
+
}
|
|
570
|
+
ackTimeoutMs = methodOptions.ackTimeoutMs;
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
else {
|
|
574
|
+
method = methodOptions;
|
|
575
|
+
}
|
|
576
|
+
let callbackMap = this._callbackMap;
|
|
577
|
+
const request = Object.assign({
|
|
578
|
+
cid: this._callIdGenerator(),
|
|
579
|
+
ackTimeoutMs: ackTimeoutMs ?? this.ackTimeoutMs,
|
|
580
|
+
callback: null,
|
|
581
|
+
promise: null
|
|
582
|
+
}, service ? {
|
|
583
|
+
service,
|
|
584
|
+
method: serviceMethod,
|
|
585
|
+
data: arg
|
|
586
|
+
} : {
|
|
587
|
+
method: method,
|
|
588
|
+
data: arg
|
|
589
|
+
});
|
|
590
|
+
let abort;
|
|
591
|
+
const promise = request.promise = new Promise((resolve, reject) => {
|
|
592
|
+
if (request.ackTimeoutMs) {
|
|
593
|
+
request.timeoutId = setTimeout(() => {
|
|
594
|
+
delete callbackMap[request.cid];
|
|
595
|
+
request.callback = null;
|
|
596
|
+
clearTimeout(request.timeoutId);
|
|
597
|
+
delete request.timeoutId;
|
|
598
|
+
reject(new TimeoutError(`Method \'${[service, request.method].filter(Boolean).join('.')}\' timed out.`));
|
|
599
|
+
}, request.ackTimeoutMs);
|
|
600
|
+
}
|
|
601
|
+
abort = () => {
|
|
602
|
+
delete callbackMap[request.cid];
|
|
603
|
+
if (request.timeoutId) {
|
|
604
|
+
clearTimeout(request.timeoutId);
|
|
605
|
+
delete request.timeoutId;
|
|
606
|
+
}
|
|
607
|
+
if (request.callback) {
|
|
608
|
+
request.callback = null;
|
|
609
|
+
reject(new AbortError(`Method \'${[service, request.method].filter(Boolean).join('.')}\' was aborted.`));
|
|
610
|
+
}
|
|
611
|
+
};
|
|
612
|
+
request.callback = (err, result) => {
|
|
613
|
+
delete callbackMap[request.cid];
|
|
614
|
+
request.callback = null;
|
|
615
|
+
if (request.timeoutId) {
|
|
616
|
+
clearTimeout(request.timeoutId);
|
|
617
|
+
delete request.timeoutId;
|
|
618
|
+
}
|
|
619
|
+
if (err) {
|
|
620
|
+
reject(err);
|
|
621
|
+
return;
|
|
622
|
+
}
|
|
623
|
+
resolve(result);
|
|
624
|
+
};
|
|
625
|
+
request.sentCallback = () => {
|
|
626
|
+
delete request.sentCallback;
|
|
627
|
+
this._outboundSentMessageCount++;
|
|
628
|
+
};
|
|
629
|
+
});
|
|
630
|
+
this._outboundPreparedMessageCount++;
|
|
631
|
+
this.onInvoke(request);
|
|
632
|
+
return [promise, abort];
|
|
633
|
+
}
|
|
634
|
+
get url() {
|
|
635
|
+
return this._webSocket.url;
|
|
636
|
+
}
|
|
637
|
+
get webSocket() {
|
|
638
|
+
return this._webSocket;
|
|
639
|
+
}
|
|
640
|
+
set webSocket(value) {
|
|
641
|
+
if (this._webSocket) {
|
|
642
|
+
this._webSocket.off('open', this.onOpen);
|
|
643
|
+
this._webSocket.off('close', this.onClose);
|
|
644
|
+
this._webSocket.off('error', this.onError);
|
|
645
|
+
this._webSocket.off('upgrade', this.onUpgrade);
|
|
646
|
+
this._webSocket.off('message', this.onMessage);
|
|
647
|
+
this._webSocket.off('ping', this.onPing);
|
|
648
|
+
this._webSocket.off('pong', this.onPong);
|
|
649
|
+
this._webSocket.off('unexpectedResponse', this.onUnexpectedResponse);
|
|
650
|
+
delete this.onOpen;
|
|
651
|
+
delete this.onClose;
|
|
652
|
+
delete this.onError;
|
|
653
|
+
delete this.onUpgrade;
|
|
654
|
+
delete this.onMessage;
|
|
655
|
+
delete this.onPing;
|
|
656
|
+
delete this.onPong;
|
|
657
|
+
delete this.onUnexpectedResponse;
|
|
658
|
+
}
|
|
659
|
+
this._webSocket = value;
|
|
660
|
+
if (value) {
|
|
661
|
+
this._webSocket.on('open', this.onOpen = this.onOpen.bind(this));
|
|
662
|
+
this._webSocket.on('close', this.onClose = this.onClose.bind(this));
|
|
663
|
+
this._webSocket.on('error', this.onError = this.onError.bind(this));
|
|
664
|
+
this._webSocket.on('upgrade', this.onUpgrade = this.onUpgrade.bind(this));
|
|
665
|
+
this._webSocket.on('message', this.onMessage = this.onMessage.bind(this));
|
|
666
|
+
this._webSocket.on('ping', this.onPing = this.onPing.bind(this));
|
|
667
|
+
this._webSocket.on('pong', this.onPong = this.onPong.bind(this));
|
|
668
|
+
this._webSocket.on('unexpectedResponse', this.onUnexpectedResponse = this.onUnexpectedResponse.bind(this));
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
}
|