@socket-mesh/server 18.0.6 → 18.1.1
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 +6 -6
- package/dist/broker/broker-events.d.ts +16 -0
- package/dist/broker/broker.d.ts +28 -0
- package/dist/broker/broker.js +17 -0
- package/dist/broker/exchange-client.d.ts +5 -0
- package/dist/broker/exchange.d.ts +4 -0
- package/dist/broker/exchange.js +3 -0
- package/dist/broker/simple-broker.d.ts +16 -0
- package/dist/broker/simple-broker.js +65 -0
- package/dist/broker/simple-exchange.d.ts +13 -0
- package/dist/broker/simple-exchange.js +33 -0
- package/dist/handlers/authenticate.d.ts +20 -0
- package/dist/handlers/authenticate.js +67 -0
- package/dist/handlers/handshake.d.ts +7 -0
- package/dist/handlers/handshake.js +61 -0
- package/dist/handlers/publish.d.ts +9 -0
- package/dist/handlers/publish.js +21 -0
- package/dist/handlers/remove-auth-token.d.ts +3 -0
- package/dist/handlers/remove-auth-token.js +7 -0
- package/dist/handlers/subscribe.d.ts +7 -0
- package/dist/handlers/subscribe.js +43 -0
- package/dist/handlers/unsubscribe.d.ts +6 -0
- package/dist/handlers/unsubscribe.js +9 -0
- package/dist/index.d.ts +30 -0
- package/{index.js → dist/index.js} +6 -6
- package/dist/maps/client-map.d.ts +10 -0
- package/dist/maps/client-map.js +1 -0
- package/dist/maps/server-map.d.ts +24 -0
- package/dist/maps/server-map.js +1 -0
- package/dist/maps/socket-map.d.ts +19 -0
- package/dist/maps/socket-map.js +1 -0
- package/dist/plugin/server-plugin.d.ts +33 -0
- package/dist/plugin/server-plugin.js +2 -0
- package/dist/server-event.d.ts +52 -0
- package/dist/server-event.js +1 -0
- package/dist/server-options.d.ts +25 -0
- package/dist/server-options.js +1 -0
- package/dist/server-socket-state.d.ts +6 -0
- package/dist/server-socket-state.js +1 -0
- package/dist/server-socket.d.ts +28 -0
- package/dist/server-socket.js +50 -0
- package/dist/server-transport.d.ts +37 -0
- package/dist/server-transport.js +247 -0
- package/dist/server.d.ts +119 -0
- package/dist/server.js +281 -0
- package/package.json +40 -47
- package/action.d.ts +0 -106
- package/action.js +0 -141
- package/auth-engine.d.ts +0 -13
- package/auth-engine.js +0 -32
- package/events.d.ts +0 -54
- package/inbound-packet.d.ts +0 -48
- package/inbound-packet.js +0 -18
- package/index.d.ts +0 -32
- package/middleware-stream.d.ts +0 -7
- package/middleware-stream.js +0 -8
- package/middleware-type.d.ts +0 -6
- package/middleware-type.js +0 -7
- package/outbound-packet.d.ts +0 -25
- package/outbound-packet.js +0 -3
- package/remote-procedure.d.ts +0 -13
- package/remote-procedure.js +0 -9
- package/request.d.ts +0 -24
- package/request.js +0 -16
- package/server-options.d.ts +0 -38
- package/server.d.ts +0 -113
- package/server.js +0 -361
- package/serversocket.d.ts +0 -182
- package/serversocket.js +0 -1262
- package/socket-state.d.ts +0 -5
- package/socket-state.js +0 -6
- /package/{events.js → dist/broker/broker-events.js} +0 -0
- /package/{server-options.js → dist/broker/exchange-client.js} +0 -0
package/serversocket.js
DELETED
|
@@ -1,1262 +0,0 @@
|
|
|
1
|
-
import cloneDeep from 'clone-deep';
|
|
2
|
-
import { AuthState } from "@socket-mesh/auth";
|
|
3
|
-
import { WritableConsumableStream } from "@socket-mesh/writable-consumable-stream";
|
|
4
|
-
import { AsyncStreamEmitter } from "@socket-mesh/async-stream-emitter";
|
|
5
|
-
import { StreamDemux, StreamDemuxWrapper } from "@socket-mesh/stream-demux";
|
|
6
|
-
import { Request, RequestWithResponse, createErrorResponse } from "@socket-mesh/request";
|
|
7
|
-
import { dehydrateError, hydrateError, socketProtocolIgnoreStatuses, socketProtocolErrorStatuses, InvalidArgumentsError, SocketProtocolError, TimeoutError, BadConnectionError, InvalidActionError, AuthError, AuthTokenExpiredError, AuthTokenInvalidError, AuthTokenNotBeforeError, AuthTokenError, BrokerError } from "@socket-mesh/errors";
|
|
8
|
-
import { SocketState } from "./socket-state.js";
|
|
9
|
-
import { MiddlewareType } from "./middleware-type.js";
|
|
10
|
-
import { ActionAuthenticate, ActionHandshakeSC, ActionInvoke, ActionMessage, ActionPublishIn, ActionPublishOut, ActionSubscribe, ActionTransmit } from "./action.js";
|
|
11
|
-
import { MiddlewareStream } from './middleware-stream.js';
|
|
12
|
-
import { isAuthenticatePacket, isHandshakePacket, isPublishPacket, isRemoveAuthTokenPacket, isSubscribePacket, isUnsubscribePacket } from './inbound-packet.js';
|
|
13
|
-
import { isPublishOutPacket } from './outbound-packet.js';
|
|
14
|
-
import { AuthenticateRequest, HandshakeRequest, UnsubscribeRequest } from './request.js';
|
|
15
|
-
import { RemoteProcedure } from './remote-procedure.js';
|
|
16
|
-
const HANDSHAKE_REJECTION_STATUS_CODE = 4008;
|
|
17
|
-
export class ServerSocket extends AsyncStreamEmitter {
|
|
18
|
-
authState;
|
|
19
|
-
authToken;
|
|
20
|
-
batchInterval;
|
|
21
|
-
batchOnHandshake;
|
|
22
|
-
batchOnHandshakeDuration;
|
|
23
|
-
cloneData;
|
|
24
|
-
channelSubscriptionsCount;
|
|
25
|
-
inboundReceivedMessageCount;
|
|
26
|
-
inboundProcessedMessageCount;
|
|
27
|
-
isBatching;
|
|
28
|
-
isBufferingBatch;
|
|
29
|
-
request;
|
|
30
|
-
id;
|
|
31
|
-
socket;
|
|
32
|
-
protocolVersion;
|
|
33
|
-
channelSubscriptions;
|
|
34
|
-
inboundMessageStream;
|
|
35
|
-
outboundPacketStream;
|
|
36
|
-
server;
|
|
37
|
-
middlewareHandshakeStream;
|
|
38
|
-
middlewareInboundRawStream;
|
|
39
|
-
middlewareInboundStream;
|
|
40
|
-
middlewareOutboundStream;
|
|
41
|
-
remoteAddress;
|
|
42
|
-
remoteFamily;
|
|
43
|
-
remotePort;
|
|
44
|
-
outboundPreparedMessageCount;
|
|
45
|
-
outboundSentMessageCount;
|
|
46
|
-
signedAuthToken;
|
|
47
|
-
state;
|
|
48
|
-
receiver;
|
|
49
|
-
procedure;
|
|
50
|
-
_cid;
|
|
51
|
-
_handshakeTimeoutRef;
|
|
52
|
-
_pingIntervalTicker;
|
|
53
|
-
_pingTimeoutTicker;
|
|
54
|
-
_batchBuffer;
|
|
55
|
-
_batchingIntervalId;
|
|
56
|
-
_callbackMap;
|
|
57
|
-
_receiverDemux;
|
|
58
|
-
_procedureDemux;
|
|
59
|
-
_sendPing;
|
|
60
|
-
constructor(id, server, socket, protocolVersion) {
|
|
61
|
-
super();
|
|
62
|
-
this.id = id;
|
|
63
|
-
this.server = server;
|
|
64
|
-
this.socket = socket;
|
|
65
|
-
this.state = SocketState.CONNECTING;
|
|
66
|
-
this.authState = AuthState.UNAUTHENTICATED;
|
|
67
|
-
this.protocolVersion = protocolVersion;
|
|
68
|
-
this._receiverDemux = new StreamDemux();
|
|
69
|
-
this.receiver = new StreamDemuxWrapper(this._receiverDemux);
|
|
70
|
-
this._procedureDemux = new StreamDemux();
|
|
71
|
-
this.procedure = new RemoteProcedure(this._procedureDemux);
|
|
72
|
-
this.request = this.socket.upgradeReq;
|
|
73
|
-
this.inboundReceivedMessageCount = 0;
|
|
74
|
-
this.inboundProcessedMessageCount = 0;
|
|
75
|
-
this.outboundPreparedMessageCount = 0;
|
|
76
|
-
this.outboundSentMessageCount = 0;
|
|
77
|
-
this.cloneData = this.server.options.cloneData;
|
|
78
|
-
this.inboundMessageStream = new WritableConsumableStream();
|
|
79
|
-
this.outboundPacketStream = new WritableConsumableStream();
|
|
80
|
-
this.middlewareHandshakeStream = this.request['handshakeStream'];
|
|
81
|
-
this.middlewareInboundRawStream = new MiddlewareStream(MiddlewareType.MIDDLEWARE_INBOUND_RAW);
|
|
82
|
-
this.middlewareInboundStream = new MiddlewareStream(MiddlewareType.MIDDLEWARE_INBOUND);
|
|
83
|
-
this.middlewareOutboundStream = new MiddlewareStream(MiddlewareType.MIDDLEWARE_OUTBOUND);
|
|
84
|
-
if (this.request.socket) {
|
|
85
|
-
this.remoteAddress = this.request.socket.remoteAddress;
|
|
86
|
-
this.remoteFamily = this.request.socket.remoteFamily;
|
|
87
|
-
this.remotePort = this.request.socket.remotePort;
|
|
88
|
-
}
|
|
89
|
-
// else {
|
|
90
|
-
// this.remoteAddress = this.request.remoteAddress;
|
|
91
|
-
// this.remoteFamily = this.request.remoteFamily;
|
|
92
|
-
// this.remotePort = this.request.remotePort;
|
|
93
|
-
//}
|
|
94
|
-
// if (this.request.forwardedForAddress) {
|
|
95
|
-
// this.forwardedForAddress = this.request.forwardedForAddress;
|
|
96
|
-
// }
|
|
97
|
-
this.isBufferingBatch = false;
|
|
98
|
-
this.isBatching = false;
|
|
99
|
-
this.batchOnHandshake = this.server.options.batchOnHandshake;
|
|
100
|
-
this.batchOnHandshakeDuration = this.server.options.batchOnHandshakeDuration;
|
|
101
|
-
this.batchInterval = this.server.options.batchInterval;
|
|
102
|
-
this._batchBuffer = [];
|
|
103
|
-
this._batchingIntervalId = null;
|
|
104
|
-
this._cid = 1;
|
|
105
|
-
this._callbackMap = {};
|
|
106
|
-
this.channelSubscriptions = {};
|
|
107
|
-
this.channelSubscriptionsCount = 0;
|
|
108
|
-
this.socket.on('error', (err) => {
|
|
109
|
-
this.emitError(err);
|
|
110
|
-
});
|
|
111
|
-
this.socket.on('close', (code, reasonBuffer) => {
|
|
112
|
-
let reason = reasonBuffer.toString();
|
|
113
|
-
this._destroy(code, reason);
|
|
114
|
-
});
|
|
115
|
-
let pongMessage;
|
|
116
|
-
if (this.protocolVersion === 1) {
|
|
117
|
-
pongMessage = '#2';
|
|
118
|
-
this._sendPing = () => {
|
|
119
|
-
if (this.state !== SocketState.CLOSED) {
|
|
120
|
-
this.send('#1');
|
|
121
|
-
}
|
|
122
|
-
};
|
|
123
|
-
}
|
|
124
|
-
else {
|
|
125
|
-
pongMessage = '';
|
|
126
|
-
this._sendPing = () => {
|
|
127
|
-
if (this.state !== SocketState.CLOSED) {
|
|
128
|
-
this.send('');
|
|
129
|
-
}
|
|
130
|
-
};
|
|
131
|
-
}
|
|
132
|
-
if (!this.server.pingTimeoutDisabled) {
|
|
133
|
-
this._pingIntervalTicker = setInterval(() => {
|
|
134
|
-
this._sendPing();
|
|
135
|
-
}, this.server.pingInterval);
|
|
136
|
-
}
|
|
137
|
-
this._resetPongTimeout();
|
|
138
|
-
this._handshakeTimeoutRef = setTimeout(() => {
|
|
139
|
-
this._handleHandshakeTimeout();
|
|
140
|
-
}, this.server.handshakeTimeout);
|
|
141
|
-
this.server.pendingClients[this.id] = this;
|
|
142
|
-
this.server.pendingClientsCount++;
|
|
143
|
-
this._handleInboundMessageStream(pongMessage);
|
|
144
|
-
this._handleOutboundPacketStream();
|
|
145
|
-
// Receive incoming raw messages
|
|
146
|
-
this.socket.on('message', async (messageBuffer, isBinary) => {
|
|
147
|
-
let message = isBinary ? messageBuffer : messageBuffer.toString();
|
|
148
|
-
this.inboundReceivedMessageCount++;
|
|
149
|
-
let isPong = message === pongMessage;
|
|
150
|
-
if (isPong) {
|
|
151
|
-
this._resetPongTimeout();
|
|
152
|
-
}
|
|
153
|
-
if (this.server.hasMiddleware(MiddlewareType.MIDDLEWARE_INBOUND_RAW)) {
|
|
154
|
-
const action = new ActionMessage(this, message);
|
|
155
|
-
try {
|
|
156
|
-
const { data } = await this.server._processMiddlewareAction(this.middlewareInboundRawStream, action, this);
|
|
157
|
-
message = data;
|
|
158
|
-
}
|
|
159
|
-
catch (error) {
|
|
160
|
-
this.inboundProcessedMessageCount++;
|
|
161
|
-
return;
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
this.inboundMessageStream.write(message);
|
|
165
|
-
this.emit('message', { message });
|
|
166
|
-
});
|
|
167
|
-
}
|
|
168
|
-
getBackpressure() {
|
|
169
|
-
return Math.max(this.getInboundBackpressure(), this.getOutboundBackpressure(), this.getListenerBackpressure(), this.receiver.getBackpressure(), this.procedure.getBackpressure());
|
|
170
|
-
}
|
|
171
|
-
get exchange() {
|
|
172
|
-
return this.server.exchange;
|
|
173
|
-
}
|
|
174
|
-
getInboundBackpressure() {
|
|
175
|
-
return this.inboundReceivedMessageCount - this.inboundProcessedMessageCount;
|
|
176
|
-
}
|
|
177
|
-
getOutboundBackpressure() {
|
|
178
|
-
return this.outboundPreparedMessageCount - this.outboundSentMessageCount;
|
|
179
|
-
}
|
|
180
|
-
_startBatchOnHandshake() {
|
|
181
|
-
this._startBatching();
|
|
182
|
-
setTimeout(() => {
|
|
183
|
-
if (!this.isBatching) {
|
|
184
|
-
this._stopBatching();
|
|
185
|
-
}
|
|
186
|
-
}, this.batchOnHandshakeDuration);
|
|
187
|
-
}
|
|
188
|
-
async _handleInboundMessageStream(pongMessage) {
|
|
189
|
-
for await (let message of this.inboundMessageStream) {
|
|
190
|
-
this.inboundProcessedMessageCount++;
|
|
191
|
-
let isPong = message === pongMessage;
|
|
192
|
-
if (isPong) {
|
|
193
|
-
if (this.server.strictHandshake && this.state === SocketState.CONNECTING) {
|
|
194
|
-
this._destroy(4009);
|
|
195
|
-
this.socket.close(4009);
|
|
196
|
-
continue;
|
|
197
|
-
}
|
|
198
|
-
let token = this.getAuthToken();
|
|
199
|
-
if (this.isAuthTokenExpired(token)) {
|
|
200
|
-
this.deauthenticate();
|
|
201
|
-
}
|
|
202
|
-
continue;
|
|
203
|
-
}
|
|
204
|
-
let packet;
|
|
205
|
-
try {
|
|
206
|
-
packet = this.decode(message);
|
|
207
|
-
}
|
|
208
|
-
catch (error) {
|
|
209
|
-
if (error.name === 'Error') {
|
|
210
|
-
error.name = 'InvalidMessageError';
|
|
211
|
-
}
|
|
212
|
-
this.emitError(error);
|
|
213
|
-
if (this.server.strictHandshake && this.state === SocketState.CONNECTING) {
|
|
214
|
-
this._destroy(4009);
|
|
215
|
-
this.socket.close(4009);
|
|
216
|
-
}
|
|
217
|
-
continue;
|
|
218
|
-
}
|
|
219
|
-
if (Array.isArray(packet)) {
|
|
220
|
-
let len = packet.length;
|
|
221
|
-
for (let i = 0; i < len; i++) {
|
|
222
|
-
await this._processInboundPacket(packet[i], message);
|
|
223
|
-
}
|
|
224
|
-
}
|
|
225
|
-
else {
|
|
226
|
-
await this._processInboundPacket(packet, message);
|
|
227
|
-
}
|
|
228
|
-
}
|
|
229
|
-
}
|
|
230
|
-
_handleHandshakeTimeout() {
|
|
231
|
-
this.disconnect(4005);
|
|
232
|
-
}
|
|
233
|
-
async _processHandshakeRequest(request) {
|
|
234
|
-
let data = request.data || { authToken: '' };
|
|
235
|
-
let signedAuthToken = data.authToken;
|
|
236
|
-
clearTimeout(this._handshakeTimeoutRef);
|
|
237
|
-
const authInfo = await this._validateAuthToken(signedAuthToken);
|
|
238
|
-
const action = new ActionHandshakeSC(this, request, authInfo);
|
|
239
|
-
try {
|
|
240
|
-
await this.server._processMiddlewareAction(this.middlewareHandshakeStream, action);
|
|
241
|
-
}
|
|
242
|
-
catch (error) {
|
|
243
|
-
if (error.statusCode == null) {
|
|
244
|
-
error.statusCode = HANDSHAKE_REJECTION_STATUS_CODE;
|
|
245
|
-
}
|
|
246
|
-
request.error(error);
|
|
247
|
-
this.disconnect(error.statusCode);
|
|
248
|
-
return;
|
|
249
|
-
}
|
|
250
|
-
const oldAuthState = this.authState;
|
|
251
|
-
let authError = undefined;
|
|
252
|
-
try {
|
|
253
|
-
await this._processAuthentication(authInfo);
|
|
254
|
-
if (this.state === SocketState.CLOSED) {
|
|
255
|
-
return;
|
|
256
|
-
}
|
|
257
|
-
}
|
|
258
|
-
catch (error) {
|
|
259
|
-
if (signedAuthToken != null) {
|
|
260
|
-
// Because the token is optional as part of the handshake, we don't count
|
|
261
|
-
// it as an error if the token wasn't provided.
|
|
262
|
-
authError = dehydrateError(error);
|
|
263
|
-
if (error.isBadToken) {
|
|
264
|
-
this.deauthenticate();
|
|
265
|
-
}
|
|
266
|
-
}
|
|
267
|
-
}
|
|
268
|
-
const clientSocketStatus = {
|
|
269
|
-
id: this.id,
|
|
270
|
-
pingTimeout: this.server.pingTimeout,
|
|
271
|
-
isAuthenticated: !!this.authToken,
|
|
272
|
-
authError: authError
|
|
273
|
-
};
|
|
274
|
-
const serverSocketStatus = {
|
|
275
|
-
id: this.id,
|
|
276
|
-
pingTimeout: this.server.pingTimeout,
|
|
277
|
-
isAuthenticated: !!this.authToken,
|
|
278
|
-
authError: authError
|
|
279
|
-
};
|
|
280
|
-
if (this.server.pendingClients[this.id]) {
|
|
281
|
-
delete this.server.pendingClients[this.id];
|
|
282
|
-
this.server.pendingClientsCount--;
|
|
283
|
-
}
|
|
284
|
-
this.server.clients[this.id] = this;
|
|
285
|
-
this.server.clientsCount++;
|
|
286
|
-
this.state = SocketState.OPEN;
|
|
287
|
-
if (clientSocketStatus.isAuthenticated) {
|
|
288
|
-
// Needs to be executed after the connection event to allow
|
|
289
|
-
// consumers to be setup from inside the connection loop.
|
|
290
|
-
(async () => {
|
|
291
|
-
await this.listen('connect').once();
|
|
292
|
-
this.triggerAuthenticationEvents(oldAuthState);
|
|
293
|
-
})();
|
|
294
|
-
}
|
|
295
|
-
// Treat authentication failure as a 'soft' error
|
|
296
|
-
request.end(clientSocketStatus);
|
|
297
|
-
if (this.batchOnHandshake) {
|
|
298
|
-
this._startBatchOnHandshake();
|
|
299
|
-
}
|
|
300
|
-
this.emit('connect', serverSocketStatus);
|
|
301
|
-
this.server.emit('connection', { socket: this, ...serverSocketStatus });
|
|
302
|
-
this.middlewareHandshakeStream.close();
|
|
303
|
-
}
|
|
304
|
-
async _processAuthenticateRequest(request) {
|
|
305
|
-
let signedAuthToken = request.data;
|
|
306
|
-
let oldAuthState = this.authState;
|
|
307
|
-
let authInfo = await this._validateAuthToken(signedAuthToken);
|
|
308
|
-
try {
|
|
309
|
-
await this._processAuthentication(authInfo);
|
|
310
|
-
}
|
|
311
|
-
catch (error) {
|
|
312
|
-
if (error instanceof AuthTokenError) {
|
|
313
|
-
this.deauthenticate();
|
|
314
|
-
request.error(error);
|
|
315
|
-
return;
|
|
316
|
-
}
|
|
317
|
-
request.end({
|
|
318
|
-
isAuthenticated: !!this.authToken,
|
|
319
|
-
authError: signedAuthToken == null ? null : dehydrateError(error)
|
|
320
|
-
});
|
|
321
|
-
return;
|
|
322
|
-
}
|
|
323
|
-
this.triggerAuthenticationEvents(oldAuthState);
|
|
324
|
-
request.end({
|
|
325
|
-
isAuthenticated: !!this.authToken,
|
|
326
|
-
authError: null
|
|
327
|
-
});
|
|
328
|
-
}
|
|
329
|
-
async _subscribeSocket(channelName, subscriptionOptions) {
|
|
330
|
-
if (this.server.socketChannelLimit && this.channelSubscriptionsCount >= this.server.socketChannelLimit) {
|
|
331
|
-
throw new InvalidActionError(`Socket ${this.id} tried to exceed the channel subscription limit of ${this.server.socketChannelLimit}`);
|
|
332
|
-
}
|
|
333
|
-
if (this.channelSubscriptionsCount == null) {
|
|
334
|
-
this.channelSubscriptionsCount = 0;
|
|
335
|
-
}
|
|
336
|
-
if (this.channelSubscriptions[channelName] == null) {
|
|
337
|
-
this.channelSubscriptions[channelName] = true;
|
|
338
|
-
this.channelSubscriptionsCount++;
|
|
339
|
-
}
|
|
340
|
-
try {
|
|
341
|
-
this.server.brokerEngine.subscribeSocket(this, channelName);
|
|
342
|
-
}
|
|
343
|
-
catch (error) {
|
|
344
|
-
delete this.channelSubscriptions[channelName];
|
|
345
|
-
this.channelSubscriptionsCount--;
|
|
346
|
-
throw error;
|
|
347
|
-
}
|
|
348
|
-
this.emit('subscribe', {
|
|
349
|
-
channel: channelName,
|
|
350
|
-
subscriptionOptions
|
|
351
|
-
});
|
|
352
|
-
this.server.emit('subscription', {
|
|
353
|
-
socket: this,
|
|
354
|
-
channel: channelName,
|
|
355
|
-
subscriptionOptions
|
|
356
|
-
});
|
|
357
|
-
}
|
|
358
|
-
async _processSubscribeRequest(request) {
|
|
359
|
-
if (this.state === SocketState.OPEN) {
|
|
360
|
-
let subscriptionOptions = Object.assign({}, request.data);
|
|
361
|
-
let channelName = subscriptionOptions.channel;
|
|
362
|
-
delete subscriptionOptions.channel;
|
|
363
|
-
try {
|
|
364
|
-
await this._subscribeSocket(channelName, subscriptionOptions);
|
|
365
|
-
}
|
|
366
|
-
catch (err) {
|
|
367
|
-
let error = new BrokerError(`Failed to subscribe socket to the ${channelName} channel - ${err}`);
|
|
368
|
-
this.emitError(error);
|
|
369
|
-
request.error(error);
|
|
370
|
-
return;
|
|
371
|
-
}
|
|
372
|
-
request.end();
|
|
373
|
-
return;
|
|
374
|
-
}
|
|
375
|
-
// This is an invalid state; it means the client tried to subscribe before
|
|
376
|
-
// having completed the handshake.
|
|
377
|
-
let error = new InvalidActionError('Cannot subscribe socket to a channel before it has completed the handshake');
|
|
378
|
-
this.emitError(error);
|
|
379
|
-
request.error(error);
|
|
380
|
-
}
|
|
381
|
-
async _unsubscribeFromAllChannels() {
|
|
382
|
-
const channels = Object.keys(this.channelSubscriptions);
|
|
383
|
-
await Promise.all(channels.map((channel) => this._unsubscribe(channel)));
|
|
384
|
-
}
|
|
385
|
-
async _unsubscribe(channel) {
|
|
386
|
-
if (typeof channel !== 'string') {
|
|
387
|
-
throw new InvalidActionError(`Socket ${this.id} tried to unsubscribe from an invalid channel name`);
|
|
388
|
-
}
|
|
389
|
-
if (!this.channelSubscriptions[channel]) {
|
|
390
|
-
throw new InvalidActionError(`Socket ${this.id} tried to unsubscribe from a channel which it is not subscribed to`);
|
|
391
|
-
}
|
|
392
|
-
try {
|
|
393
|
-
await this.server.brokerEngine.unsubscribeSocket(this, channel);
|
|
394
|
-
delete this.channelSubscriptions[channel];
|
|
395
|
-
if (this.channelSubscriptionsCount != null) {
|
|
396
|
-
this.channelSubscriptionsCount--;
|
|
397
|
-
}
|
|
398
|
-
this.emit('unsubscribe', { channel });
|
|
399
|
-
this.server.emit('unsubscription', { socket: this, channel });
|
|
400
|
-
}
|
|
401
|
-
catch (err) {
|
|
402
|
-
const error = new BrokerError(`Failed to unsubscribe socket from the ${channel} channel - ${err}`);
|
|
403
|
-
this.emitError(error);
|
|
404
|
-
}
|
|
405
|
-
}
|
|
406
|
-
async _processUnsubscribePacket(packet) {
|
|
407
|
-
let channel = packet.data;
|
|
408
|
-
try {
|
|
409
|
-
await this._unsubscribe(channel);
|
|
410
|
-
}
|
|
411
|
-
catch (err) {
|
|
412
|
-
let error = new BrokerError(`Failed to unsubscribe socket from the ${channel} channel - ${err}`);
|
|
413
|
-
this.emitError(error);
|
|
414
|
-
}
|
|
415
|
-
}
|
|
416
|
-
async _processUnsubscribeRequest(request) {
|
|
417
|
-
const channel = request.data;
|
|
418
|
-
try {
|
|
419
|
-
await this._unsubscribe(channel);
|
|
420
|
-
}
|
|
421
|
-
catch (err) {
|
|
422
|
-
let error = new BrokerError(`Failed to unsubscribe socket from the ${channel} channel - ${err}`);
|
|
423
|
-
this.emitError(error);
|
|
424
|
-
request.error(error);
|
|
425
|
-
return;
|
|
426
|
-
}
|
|
427
|
-
request.end();
|
|
428
|
-
}
|
|
429
|
-
async _processInboundPublishPacket(packet) {
|
|
430
|
-
try {
|
|
431
|
-
await this.server.exchange.invokePublish(packet.data.channel, packet.data.data);
|
|
432
|
-
}
|
|
433
|
-
catch (error) {
|
|
434
|
-
this.emitError(error);
|
|
435
|
-
}
|
|
436
|
-
}
|
|
437
|
-
async _processInboundPublishRequest(request) {
|
|
438
|
-
try {
|
|
439
|
-
await this.server.exchange.invokePublish(request.data.channel, request.data.data);
|
|
440
|
-
}
|
|
441
|
-
catch (error) {
|
|
442
|
-
this.emitError(error);
|
|
443
|
-
request.error(error);
|
|
444
|
-
return;
|
|
445
|
-
}
|
|
446
|
-
request.end();
|
|
447
|
-
}
|
|
448
|
-
async _processInboundPacket(packet, message) {
|
|
449
|
-
if (packet && typeof packet.event === 'string') {
|
|
450
|
-
let isRPC = ('cid' in packet) && typeof packet.cid === 'number';
|
|
451
|
-
if (isHandshakePacket(packet)) {
|
|
452
|
-
if (!isRPC) {
|
|
453
|
-
let error = new InvalidActionError('Handshake request was malformatted');
|
|
454
|
-
this.emitError(error);
|
|
455
|
-
this._destroy(HANDSHAKE_REJECTION_STATUS_CODE);
|
|
456
|
-
this.socket.close(HANDSHAKE_REJECTION_STATUS_CODE);
|
|
457
|
-
return;
|
|
458
|
-
}
|
|
459
|
-
const request = new HandshakeRequest(this, packet.cid, packet.event, packet.data);
|
|
460
|
-
await this._processHandshakeRequest(request);
|
|
461
|
-
this._procedureDemux.write(request.procedure, request);
|
|
462
|
-
return;
|
|
463
|
-
}
|
|
464
|
-
if (this.server.strictHandshake && this.state === SocketState.CONNECTING) {
|
|
465
|
-
this._destroy(4009);
|
|
466
|
-
this.socket.close(4009);
|
|
467
|
-
return;
|
|
468
|
-
}
|
|
469
|
-
if (isAuthenticatePacket(packet)) {
|
|
470
|
-
if (!isRPC) {
|
|
471
|
-
let error = new InvalidActionError('Authenticate request was malformatted');
|
|
472
|
-
this.emitError(error);
|
|
473
|
-
this._destroy(HANDSHAKE_REJECTION_STATUS_CODE);
|
|
474
|
-
this.socket.close(HANDSHAKE_REJECTION_STATUS_CODE);
|
|
475
|
-
return;
|
|
476
|
-
}
|
|
477
|
-
// Let AGServer handle these events.
|
|
478
|
-
const request = new AuthenticateRequest(this, packet.cid, packet.event, packet.data);
|
|
479
|
-
await this._processAuthenticateRequest(request);
|
|
480
|
-
this._procedureDemux.write(request.procedure, request);
|
|
481
|
-
return;
|
|
482
|
-
}
|
|
483
|
-
if (isRemoveAuthTokenPacket(packet)) {
|
|
484
|
-
this.deauthenticateSelf();
|
|
485
|
-
this._receiverDemux.write(packet.event, packet.data);
|
|
486
|
-
return;
|
|
487
|
-
}
|
|
488
|
-
if (isUnsubscribePacket(packet)) {
|
|
489
|
-
if (typeof packet.data !== 'string') {
|
|
490
|
-
const error = new InvalidActionError('Unsubscribe channel name was malformatted');
|
|
491
|
-
this.emitError(error);
|
|
492
|
-
if (isRPC) {
|
|
493
|
-
const request = new Request(this, packet.cid, packet.event, packet.data);
|
|
494
|
-
request.error(error);
|
|
495
|
-
}
|
|
496
|
-
return;
|
|
497
|
-
}
|
|
498
|
-
if (isRPC) {
|
|
499
|
-
const request = new UnsubscribeRequest(this, packet.cid, packet.event, packet.data);
|
|
500
|
-
await this._processUnsubscribeRequest(request);
|
|
501
|
-
this._procedureDemux.write(request.procedure, request);
|
|
502
|
-
return;
|
|
503
|
-
}
|
|
504
|
-
await this._processUnsubscribePacket(packet);
|
|
505
|
-
this._receiverDemux.write(packet.event, packet.data);
|
|
506
|
-
return;
|
|
507
|
-
}
|
|
508
|
-
let action;
|
|
509
|
-
if (isPublishPacket(packet)) {
|
|
510
|
-
if (!this.server.allowClientPublish) {
|
|
511
|
-
let error = new InvalidActionError('Client publish feature is disabled');
|
|
512
|
-
this.emitError(error);
|
|
513
|
-
if (isRPC) {
|
|
514
|
-
const request = new Request(this, packet.cid, packet.event, packet.data);
|
|
515
|
-
request.error(error);
|
|
516
|
-
}
|
|
517
|
-
return;
|
|
518
|
-
}
|
|
519
|
-
if (!packet.data || typeof packet.data.channel !== 'string') {
|
|
520
|
-
const error = new InvalidActionError('Publish channel name was malformatted');
|
|
521
|
-
this.emitError(error);
|
|
522
|
-
if (isRPC) {
|
|
523
|
-
const request = new Request(this, packet.cid, packet.event, packet.data);
|
|
524
|
-
request.error(error);
|
|
525
|
-
}
|
|
526
|
-
return;
|
|
527
|
-
}
|
|
528
|
-
action = new ActionPublishIn(this, packet.data);
|
|
529
|
-
}
|
|
530
|
-
else if (isSubscribePacket(packet)) {
|
|
531
|
-
if (!packet.data || typeof packet.data.channel !== 'string') {
|
|
532
|
-
const error = new InvalidActionError('Subscribe channel name was malformatted');
|
|
533
|
-
this.emitError(error);
|
|
534
|
-
if (isRPC) {
|
|
535
|
-
const request = new Request(this, packet.cid, packet.event, packet.data);
|
|
536
|
-
request.error(error);
|
|
537
|
-
}
|
|
538
|
-
return;
|
|
539
|
-
}
|
|
540
|
-
action = new ActionSubscribe(this, packet.data);
|
|
541
|
-
}
|
|
542
|
-
else if (isRPC) {
|
|
543
|
-
action = new ActionInvoke(this, packet.event, packet.data);
|
|
544
|
-
}
|
|
545
|
-
else {
|
|
546
|
-
action = new ActionTransmit(this, packet.event, packet.data);
|
|
547
|
-
}
|
|
548
|
-
const tokenExpiredError = this._processAuthTokenExpiry();
|
|
549
|
-
if (tokenExpiredError) {
|
|
550
|
-
action.authTokenExpiredError = tokenExpiredError;
|
|
551
|
-
}
|
|
552
|
-
let newData;
|
|
553
|
-
if (isRPC) {
|
|
554
|
-
try {
|
|
555
|
-
let { data } = await this.server._processMiddlewareAction(this.middlewareInboundStream, action, this);
|
|
556
|
-
newData = data;
|
|
557
|
-
}
|
|
558
|
-
catch (error) {
|
|
559
|
-
this.sendObject(createErrorResponse(packet.cid, error));
|
|
560
|
-
return;
|
|
561
|
-
}
|
|
562
|
-
let request;
|
|
563
|
-
if (isSubscribePacket(packet) || isPublishPacket(packet)) {
|
|
564
|
-
request = new Request(this, packet.cid, packet.event, packet.data);
|
|
565
|
-
request.data.data = newData;
|
|
566
|
-
if (isSubscribePacket(packet)) {
|
|
567
|
-
await this._processSubscribeRequest(request);
|
|
568
|
-
}
|
|
569
|
-
else {
|
|
570
|
-
await this._processInboundPublishRequest(request);
|
|
571
|
-
}
|
|
572
|
-
}
|
|
573
|
-
else {
|
|
574
|
-
request = new RequestWithResponse(this, packet.cid, packet.event, newData);
|
|
575
|
-
}
|
|
576
|
-
this._procedureDemux.write(request.procedure, request);
|
|
577
|
-
return;
|
|
578
|
-
}
|
|
579
|
-
try {
|
|
580
|
-
let { data } = await this.server._processMiddlewareAction(this.middlewareInboundStream, action, this);
|
|
581
|
-
newData = data;
|
|
582
|
-
}
|
|
583
|
-
catch (error) {
|
|
584
|
-
return;
|
|
585
|
-
}
|
|
586
|
-
if (isPublishPacket(packet)) {
|
|
587
|
-
packet.data.data = newData;
|
|
588
|
-
await this._processInboundPublishPacket(packet);
|
|
589
|
-
}
|
|
590
|
-
this._receiverDemux.write(packet.event, newData);
|
|
591
|
-
return;
|
|
592
|
-
}
|
|
593
|
-
if (this.server.strictHandshake && this.state === SocketState.CONNECTING) {
|
|
594
|
-
this._destroy(4009);
|
|
595
|
-
this.socket.close(4009);
|
|
596
|
-
return;
|
|
597
|
-
}
|
|
598
|
-
if (packet && 'rid' in packet && typeof packet.rid === 'number') {
|
|
599
|
-
// If incoming message is a response to a previously sent message
|
|
600
|
-
const ret = this._callbackMap[packet.rid];
|
|
601
|
-
if (ret) {
|
|
602
|
-
clearTimeout(ret.timeout);
|
|
603
|
-
delete this._callbackMap[packet.rid];
|
|
604
|
-
const rehydratedError = hydrateError(packet.error);
|
|
605
|
-
ret.callback(rehydratedError, packet.data);
|
|
606
|
-
}
|
|
607
|
-
return;
|
|
608
|
-
}
|
|
609
|
-
// The last remaining case is to treat the message as raw
|
|
610
|
-
this.emit('raw', { message });
|
|
611
|
-
}
|
|
612
|
-
_resetPongTimeout() {
|
|
613
|
-
if (this.server.pingTimeoutDisabled) {
|
|
614
|
-
return;
|
|
615
|
-
}
|
|
616
|
-
clearTimeout(this._pingTimeoutTicker);
|
|
617
|
-
this._pingTimeoutTicker = setTimeout(() => {
|
|
618
|
-
this._destroy(4001);
|
|
619
|
-
this.socket.close(4001);
|
|
620
|
-
}, this.server.pingTimeout);
|
|
621
|
-
}
|
|
622
|
-
_nextCallId() {
|
|
623
|
-
return this._cid++;
|
|
624
|
-
}
|
|
625
|
-
getState() {
|
|
626
|
-
return this.state;
|
|
627
|
-
}
|
|
628
|
-
getBytesReceived() {
|
|
629
|
-
return this.socket.bytesReceived;
|
|
630
|
-
}
|
|
631
|
-
emitError(error) {
|
|
632
|
-
this.emit('error', { error });
|
|
633
|
-
this.server.emitWarning(error);
|
|
634
|
-
}
|
|
635
|
-
_abortAllPendingEventsDueToBadConnection(failureType) {
|
|
636
|
-
Object.keys(this._callbackMap || {}).forEach((i) => {
|
|
637
|
-
const eventObject = this._callbackMap[i];
|
|
638
|
-
delete this._callbackMap[i];
|
|
639
|
-
clearTimeout(eventObject.timeout);
|
|
640
|
-
delete eventObject.timeout;
|
|
641
|
-
const errorMessage = `Event ${eventObject.event} was aborted due to a bad connection`;
|
|
642
|
-
const badConnectionError = new BadConnectionError(errorMessage, failureType);
|
|
643
|
-
const callback = eventObject.callback;
|
|
644
|
-
delete eventObject.callback;
|
|
645
|
-
callback.call(eventObject, badConnectionError, eventObject);
|
|
646
|
-
});
|
|
647
|
-
}
|
|
648
|
-
closeAllMiddlewares() {
|
|
649
|
-
this.middlewareHandshakeStream.close();
|
|
650
|
-
this.middlewareInboundRawStream.close();
|
|
651
|
-
this.middlewareInboundStream.close();
|
|
652
|
-
this.middlewareOutboundStream.close();
|
|
653
|
-
}
|
|
654
|
-
closeInput() {
|
|
655
|
-
this.inboundMessageStream.close();
|
|
656
|
-
}
|
|
657
|
-
closeOutput() {
|
|
658
|
-
this.outboundPacketStream.close();
|
|
659
|
-
}
|
|
660
|
-
closeIO() {
|
|
661
|
-
this.closeInput();
|
|
662
|
-
this.closeOutput();
|
|
663
|
-
}
|
|
664
|
-
closeAllStreams() {
|
|
665
|
-
this.closeAllMiddlewares();
|
|
666
|
-
this.closeIO();
|
|
667
|
-
this.receiver.close();
|
|
668
|
-
this.procedure.close();
|
|
669
|
-
this.closeListeners();
|
|
670
|
-
}
|
|
671
|
-
killAllMiddlewares() {
|
|
672
|
-
this.middlewareHandshakeStream.kill();
|
|
673
|
-
this.middlewareInboundRawStream.kill();
|
|
674
|
-
this.middlewareInboundStream.kill();
|
|
675
|
-
this.middlewareOutboundStream.kill();
|
|
676
|
-
}
|
|
677
|
-
killInput() {
|
|
678
|
-
this.inboundMessageStream.kill();
|
|
679
|
-
}
|
|
680
|
-
killOutput() {
|
|
681
|
-
this.outboundPacketStream.kill();
|
|
682
|
-
}
|
|
683
|
-
killIO() {
|
|
684
|
-
this.killInput();
|
|
685
|
-
this.killOutput();
|
|
686
|
-
}
|
|
687
|
-
killAllStreams() {
|
|
688
|
-
this.killAllMiddlewares();
|
|
689
|
-
this.killIO();
|
|
690
|
-
this.receiver.kill();
|
|
691
|
-
this.procedure.kill();
|
|
692
|
-
this.killListeners();
|
|
693
|
-
}
|
|
694
|
-
async _destroy(code, reason) {
|
|
695
|
-
clearInterval(this._pingIntervalTicker);
|
|
696
|
-
clearTimeout(this._pingTimeoutTicker);
|
|
697
|
-
this._cancelBatching();
|
|
698
|
-
if (this.state === SocketState.CLOSED) {
|
|
699
|
-
this._abortAllPendingEventsDueToBadConnection('connectAbort');
|
|
700
|
-
}
|
|
701
|
-
else {
|
|
702
|
-
if (!reason) {
|
|
703
|
-
reason = socketProtocolErrorStatuses[code];
|
|
704
|
-
}
|
|
705
|
-
const prevState = this.state;
|
|
706
|
-
this.state = SocketState.CLOSED;
|
|
707
|
-
if (prevState === SocketState.CONNECTING) {
|
|
708
|
-
this._abortAllPendingEventsDueToBadConnection('connectAbort');
|
|
709
|
-
this.emit('connectAbort', { code, reason });
|
|
710
|
-
this.server.emit('connectionAbort', { socket: this, code, reason });
|
|
711
|
-
}
|
|
712
|
-
else {
|
|
713
|
-
this._abortAllPendingEventsDueToBadConnection('disconnect');
|
|
714
|
-
this.emit('disconnect', { code, reason });
|
|
715
|
-
this.server.emit('disconnection', { socket: this, code, reason });
|
|
716
|
-
}
|
|
717
|
-
this.emit('close', { code, reason });
|
|
718
|
-
this.server.emit('closure', { socket: this, code, reason });
|
|
719
|
-
clearTimeout(this._handshakeTimeoutRef);
|
|
720
|
-
let isClientFullyConnected = !!this.server.clients[this.id];
|
|
721
|
-
if (isClientFullyConnected) {
|
|
722
|
-
delete this.server.clients[this.id];
|
|
723
|
-
this.server.clientsCount--;
|
|
724
|
-
}
|
|
725
|
-
let isClientPending = !!this.server.pendingClients[this.id];
|
|
726
|
-
if (isClientPending) {
|
|
727
|
-
delete this.server.pendingClients[this.id];
|
|
728
|
-
this.server.pendingClientsCount--;
|
|
729
|
-
}
|
|
730
|
-
if (!socketProtocolIgnoreStatuses[code]) {
|
|
731
|
-
let closeMessage;
|
|
732
|
-
if (typeof reason === 'string') {
|
|
733
|
-
closeMessage = `Socket connection closed with status code ${code} and reason: ${reason}`;
|
|
734
|
-
}
|
|
735
|
-
else {
|
|
736
|
-
closeMessage = `Socket connection closed with status code ${code}`;
|
|
737
|
-
}
|
|
738
|
-
let err = new SocketProtocolError(socketProtocolErrorStatuses[code] || closeMessage, code);
|
|
739
|
-
this.emitError(err);
|
|
740
|
-
}
|
|
741
|
-
await this._unsubscribeFromAllChannels();
|
|
742
|
-
let cleanupMode = this.server.options.socketStreamCleanupMode;
|
|
743
|
-
if (cleanupMode === 'kill') {
|
|
744
|
-
(async () => {
|
|
745
|
-
await this.listen('end').once();
|
|
746
|
-
this.killAllStreams();
|
|
747
|
-
})();
|
|
748
|
-
}
|
|
749
|
-
else if (cleanupMode === 'close') {
|
|
750
|
-
(async () => {
|
|
751
|
-
await this.listen('end').once();
|
|
752
|
-
this.closeAllStreams();
|
|
753
|
-
})();
|
|
754
|
-
}
|
|
755
|
-
this.emit('end');
|
|
756
|
-
}
|
|
757
|
-
}
|
|
758
|
-
disconnect(code, reason) {
|
|
759
|
-
code = code || 1000;
|
|
760
|
-
if (typeof code !== 'number') {
|
|
761
|
-
let err = new InvalidArgumentsError('If specified, the code argument must be a number');
|
|
762
|
-
this.emitError(err);
|
|
763
|
-
}
|
|
764
|
-
if (this.state !== SocketState.CLOSED) {
|
|
765
|
-
this._destroy(code, reason);
|
|
766
|
-
this.socket.close(code, reason);
|
|
767
|
-
}
|
|
768
|
-
}
|
|
769
|
-
terminate() {
|
|
770
|
-
this.socket.terminate();
|
|
771
|
-
}
|
|
772
|
-
send(data, options) {
|
|
773
|
-
const cb = (error) => {
|
|
774
|
-
if (error) {
|
|
775
|
-
this.emitError(error);
|
|
776
|
-
this._destroy(1006, error.toString());
|
|
777
|
-
}
|
|
778
|
-
};
|
|
779
|
-
if (options) {
|
|
780
|
-
this.socket.send(data, options, cb);
|
|
781
|
-
}
|
|
782
|
-
else {
|
|
783
|
-
this.socket.send(data, cb);
|
|
784
|
-
}
|
|
785
|
-
}
|
|
786
|
-
decode(message) {
|
|
787
|
-
return this.server.codec.decode(message);
|
|
788
|
-
}
|
|
789
|
-
encode(object) {
|
|
790
|
-
return this.server.codec.encode(object);
|
|
791
|
-
}
|
|
792
|
-
startBatch() {
|
|
793
|
-
this.isBufferingBatch = true;
|
|
794
|
-
this._batchBuffer = [];
|
|
795
|
-
}
|
|
796
|
-
flushBatch() {
|
|
797
|
-
this.isBufferingBatch = false;
|
|
798
|
-
if (!this._batchBuffer.length) {
|
|
799
|
-
return;
|
|
800
|
-
}
|
|
801
|
-
let serializedBatch = this.serializeObject(this._batchBuffer);
|
|
802
|
-
this._batchBuffer = [];
|
|
803
|
-
this.send(serializedBatch);
|
|
804
|
-
}
|
|
805
|
-
cancelBatch() {
|
|
806
|
-
this.isBufferingBatch = false;
|
|
807
|
-
this._batchBuffer = [];
|
|
808
|
-
}
|
|
809
|
-
_startBatching() {
|
|
810
|
-
if (this._batchingIntervalId != null) {
|
|
811
|
-
return;
|
|
812
|
-
}
|
|
813
|
-
this.startBatch();
|
|
814
|
-
this._batchingIntervalId = setInterval(() => {
|
|
815
|
-
this.flushBatch();
|
|
816
|
-
this.startBatch();
|
|
817
|
-
}, this.batchInterval);
|
|
818
|
-
}
|
|
819
|
-
startBatching() {
|
|
820
|
-
this.isBatching = true;
|
|
821
|
-
this._startBatching();
|
|
822
|
-
}
|
|
823
|
-
_stopBatching() {
|
|
824
|
-
if (this._batchingIntervalId != null) {
|
|
825
|
-
clearInterval(this._batchingIntervalId);
|
|
826
|
-
}
|
|
827
|
-
this._batchingIntervalId = null;
|
|
828
|
-
this.flushBatch();
|
|
829
|
-
}
|
|
830
|
-
stopBatching() {
|
|
831
|
-
this.isBatching = false;
|
|
832
|
-
this._stopBatching();
|
|
833
|
-
}
|
|
834
|
-
_cancelBatching() {
|
|
835
|
-
if (this._batchingIntervalId != null) {
|
|
836
|
-
clearInterval(this._batchingIntervalId);
|
|
837
|
-
}
|
|
838
|
-
this._batchingIntervalId = null;
|
|
839
|
-
this.cancelBatch();
|
|
840
|
-
}
|
|
841
|
-
cancelBatching() {
|
|
842
|
-
this.isBatching = false;
|
|
843
|
-
this._cancelBatching();
|
|
844
|
-
}
|
|
845
|
-
serializeObject(object) {
|
|
846
|
-
let str;
|
|
847
|
-
try {
|
|
848
|
-
str = this.encode(object);
|
|
849
|
-
}
|
|
850
|
-
catch (error) {
|
|
851
|
-
this.emitError(error);
|
|
852
|
-
return null;
|
|
853
|
-
}
|
|
854
|
-
return str;
|
|
855
|
-
}
|
|
856
|
-
sendObject(object) {
|
|
857
|
-
if (this.isBufferingBatch) {
|
|
858
|
-
this._batchBuffer.push(object);
|
|
859
|
-
return;
|
|
860
|
-
}
|
|
861
|
-
let str = this.serializeObject(object);
|
|
862
|
-
if (str != null) {
|
|
863
|
-
this.send(str);
|
|
864
|
-
}
|
|
865
|
-
}
|
|
866
|
-
async _handleOutboundPacketStream() {
|
|
867
|
-
for await (let packet of this.outboundPacketStream) {
|
|
868
|
-
if ('resolve' in packet) {
|
|
869
|
-
// Invoke has no middleware, so there is no need to await here.
|
|
870
|
-
(async () => {
|
|
871
|
-
let result;
|
|
872
|
-
try {
|
|
873
|
-
result = await this._invoke(packet);
|
|
874
|
-
}
|
|
875
|
-
catch (error) {
|
|
876
|
-
packet.reject(error);
|
|
877
|
-
return;
|
|
878
|
-
}
|
|
879
|
-
packet.resolve(result);
|
|
880
|
-
})();
|
|
881
|
-
this.outboundSentMessageCount++;
|
|
882
|
-
continue;
|
|
883
|
-
}
|
|
884
|
-
await this._processTransmit(packet);
|
|
885
|
-
this.outboundSentMessageCount++;
|
|
886
|
-
}
|
|
887
|
-
}
|
|
888
|
-
_transmit(event, data, options) {
|
|
889
|
-
if (this.cloneData) {
|
|
890
|
-
data = cloneDeep(data);
|
|
891
|
-
}
|
|
892
|
-
this.outboundPreparedMessageCount++;
|
|
893
|
-
this.outboundPacketStream.write({
|
|
894
|
-
event,
|
|
895
|
-
data,
|
|
896
|
-
options
|
|
897
|
-
});
|
|
898
|
-
}
|
|
899
|
-
transmit(event, data, options) {
|
|
900
|
-
if (this.state !== SocketState.OPEN) {
|
|
901
|
-
let error = new BadConnectionError(`Socket transmit ${event} event was aborted due to a bad connection`, 'connectAbort');
|
|
902
|
-
this.emitError(error);
|
|
903
|
-
return;
|
|
904
|
-
}
|
|
905
|
-
this._transmit(event, data, options);
|
|
906
|
-
}
|
|
907
|
-
async invoke(event, data, options) {
|
|
908
|
-
if (this.state !== SocketState.OPEN) {
|
|
909
|
-
const error = new BadConnectionError(`Socket invoke ${event} event was aborted due to a bad connection`, 'connectAbort');
|
|
910
|
-
this.emitError(error);
|
|
911
|
-
throw error;
|
|
912
|
-
}
|
|
913
|
-
if (this.cloneData) {
|
|
914
|
-
data = cloneDeep(data);
|
|
915
|
-
}
|
|
916
|
-
this.outboundPreparedMessageCount++;
|
|
917
|
-
return new Promise((resolve, reject) => {
|
|
918
|
-
this.outboundPacketStream.write({
|
|
919
|
-
event,
|
|
920
|
-
data,
|
|
921
|
-
options,
|
|
922
|
-
resolve,
|
|
923
|
-
reject
|
|
924
|
-
});
|
|
925
|
-
});
|
|
926
|
-
}
|
|
927
|
-
emit(eventName, data) {
|
|
928
|
-
super.emit(eventName, data);
|
|
929
|
-
}
|
|
930
|
-
listen(eventName) {
|
|
931
|
-
return super.listen(eventName);
|
|
932
|
-
}
|
|
933
|
-
async _processTransmit(packet) {
|
|
934
|
-
let newData;
|
|
935
|
-
let useCache = packet.options ? packet.options.useCache : false;
|
|
936
|
-
if (isPublishOutPacket(packet)) {
|
|
937
|
-
const action = new ActionPublishOut(this);
|
|
938
|
-
if (packet.data !== undefined) {
|
|
939
|
-
action.channel = packet.data.channel;
|
|
940
|
-
action.data = packet.data.data;
|
|
941
|
-
}
|
|
942
|
-
useCache = !this.server.hasMiddleware(this.middlewareOutboundStream.type);
|
|
943
|
-
try {
|
|
944
|
-
const { data, options } = await this.server._processMiddlewareAction(this.middlewareOutboundStream, action, this);
|
|
945
|
-
newData = data;
|
|
946
|
-
useCache = options == null ? useCache : options.useCache;
|
|
947
|
-
}
|
|
948
|
-
catch (error) {
|
|
949
|
-
return;
|
|
950
|
-
}
|
|
951
|
-
}
|
|
952
|
-
else {
|
|
953
|
-
newData = packet.data;
|
|
954
|
-
}
|
|
955
|
-
if (packet.options && useCache && packet.options.stringifiedData != null && !this.isBufferingBatch) {
|
|
956
|
-
// Optimized
|
|
957
|
-
this.send(packet.options.stringifiedData);
|
|
958
|
-
}
|
|
959
|
-
else {
|
|
960
|
-
const eventObject = {
|
|
961
|
-
event: packet.event
|
|
962
|
-
};
|
|
963
|
-
if (isPublishOutPacket(packet)) {
|
|
964
|
-
eventObject.data = packet.data || {};
|
|
965
|
-
eventObject.data.data = newData;
|
|
966
|
-
}
|
|
967
|
-
else {
|
|
968
|
-
eventObject.data = newData;
|
|
969
|
-
}
|
|
970
|
-
this.sendObject(eventObject);
|
|
971
|
-
}
|
|
972
|
-
}
|
|
973
|
-
async _invoke({ event, data, options }) {
|
|
974
|
-
options = options || {};
|
|
975
|
-
return new Promise((resolve, reject) => {
|
|
976
|
-
const eventObject = {
|
|
977
|
-
event,
|
|
978
|
-
cid: this._nextCallId()
|
|
979
|
-
};
|
|
980
|
-
if (data !== undefined) {
|
|
981
|
-
eventObject.data = data;
|
|
982
|
-
}
|
|
983
|
-
const ackTimeout = options.ackTimeout == null ? this.server.ackTimeout : options.ackTimeout;
|
|
984
|
-
const timeout = setTimeout(() => {
|
|
985
|
-
let error = new TimeoutError(`Event response for ${event} event timed out`);
|
|
986
|
-
delete this._callbackMap[eventObject.cid];
|
|
987
|
-
reject(error);
|
|
988
|
-
}, ackTimeout);
|
|
989
|
-
this._callbackMap[eventObject.cid] = {
|
|
990
|
-
event,
|
|
991
|
-
callback: (err, result) => {
|
|
992
|
-
if (err) {
|
|
993
|
-
reject(err);
|
|
994
|
-
return;
|
|
995
|
-
}
|
|
996
|
-
resolve(result);
|
|
997
|
-
},
|
|
998
|
-
timeout
|
|
999
|
-
};
|
|
1000
|
-
if (options.useCache && options.stringifiedData != null && !this.isBufferingBatch) {
|
|
1001
|
-
// Optimized
|
|
1002
|
-
this.send(options.stringifiedData);
|
|
1003
|
-
}
|
|
1004
|
-
else {
|
|
1005
|
-
this.sendObject(eventObject);
|
|
1006
|
-
}
|
|
1007
|
-
});
|
|
1008
|
-
}
|
|
1009
|
-
triggerAuthenticationEvents(oldAuthState) {
|
|
1010
|
-
if (oldAuthState !== AuthState.AUTHENTICATED) {
|
|
1011
|
-
let stateChangeData = {
|
|
1012
|
-
oldAuthState,
|
|
1013
|
-
newAuthState: this.authState,
|
|
1014
|
-
authToken: this.authToken
|
|
1015
|
-
};
|
|
1016
|
-
this.emit('authStateChange', stateChangeData);
|
|
1017
|
-
this.server.emit('authenticationStateChange', {
|
|
1018
|
-
socket: this,
|
|
1019
|
-
...stateChangeData
|
|
1020
|
-
});
|
|
1021
|
-
}
|
|
1022
|
-
this.emit('authenticate', { authToken: this.authToken });
|
|
1023
|
-
this.server.emit('authentication', {
|
|
1024
|
-
socket: this,
|
|
1025
|
-
authToken: this.authToken
|
|
1026
|
-
});
|
|
1027
|
-
}
|
|
1028
|
-
async setAuthToken(data, options) {
|
|
1029
|
-
if (this.state === SocketState.CONNECTING) {
|
|
1030
|
-
const err = new InvalidActionError('Cannot call setAuthToken before completing the handshake');
|
|
1031
|
-
this.emitError(err);
|
|
1032
|
-
throw err;
|
|
1033
|
-
}
|
|
1034
|
-
const authToken = cloneDeep(data);
|
|
1035
|
-
const oldAuthState = this.authState;
|
|
1036
|
-
this.authState = AuthState.AUTHENTICATED;
|
|
1037
|
-
if (options == null) {
|
|
1038
|
-
options = {};
|
|
1039
|
-
}
|
|
1040
|
-
else {
|
|
1041
|
-
options = { ...options };
|
|
1042
|
-
if (options.algorithm != null) {
|
|
1043
|
-
delete options.algorithm;
|
|
1044
|
-
let err = new InvalidArgumentsError('Cannot change auth token algorithm at runtime - It must be specified as a config option on launch');
|
|
1045
|
-
this.emitError(err);
|
|
1046
|
-
}
|
|
1047
|
-
}
|
|
1048
|
-
options.mutatePayload = true;
|
|
1049
|
-
const rejectOnFailedDelivery = options.rejectOnFailedDelivery;
|
|
1050
|
-
delete options.rejectOnFailedDelivery;
|
|
1051
|
-
const defaultSignatureOptions = this.server.defaultSignatureOptions;
|
|
1052
|
-
// We cannot have the exp claim on the token and the expiresIn option
|
|
1053
|
-
// set at the same time or else auth.signToken will throw an error.
|
|
1054
|
-
let expiresIn;
|
|
1055
|
-
if (options.expiresIn == null) {
|
|
1056
|
-
expiresIn = defaultSignatureOptions.expiresIn;
|
|
1057
|
-
}
|
|
1058
|
-
else {
|
|
1059
|
-
expiresIn = options.expiresIn;
|
|
1060
|
-
}
|
|
1061
|
-
if (authToken) {
|
|
1062
|
-
if (authToken.exp == null) {
|
|
1063
|
-
options.expiresIn = expiresIn;
|
|
1064
|
-
}
|
|
1065
|
-
else {
|
|
1066
|
-
delete options.expiresIn;
|
|
1067
|
-
}
|
|
1068
|
-
}
|
|
1069
|
-
else {
|
|
1070
|
-
options.expiresIn = expiresIn;
|
|
1071
|
-
}
|
|
1072
|
-
// Always use the default algorithm since it cannot be changed at runtime.
|
|
1073
|
-
if (defaultSignatureOptions.algorithm != null) {
|
|
1074
|
-
options.algorithm = defaultSignatureOptions.algorithm;
|
|
1075
|
-
}
|
|
1076
|
-
this.authToken = authToken;
|
|
1077
|
-
let signedAuthToken;
|
|
1078
|
-
try {
|
|
1079
|
-
signedAuthToken = await this.server.auth.signToken(authToken, this.server.signatureKey, options);
|
|
1080
|
-
}
|
|
1081
|
-
catch (error) {
|
|
1082
|
-
this.emitError(error);
|
|
1083
|
-
this._destroy(4002, error.toString());
|
|
1084
|
-
this.socket.close(4002);
|
|
1085
|
-
throw error;
|
|
1086
|
-
}
|
|
1087
|
-
if (this.authToken === authToken) {
|
|
1088
|
-
this.signedAuthToken = signedAuthToken;
|
|
1089
|
-
this.emit('authTokenSigned', { signedAuthToken });
|
|
1090
|
-
}
|
|
1091
|
-
this.triggerAuthenticationEvents(oldAuthState);
|
|
1092
|
-
const tokenData = {
|
|
1093
|
-
token: signedAuthToken
|
|
1094
|
-
};
|
|
1095
|
-
if (rejectOnFailedDelivery) {
|
|
1096
|
-
try {
|
|
1097
|
-
await this.invoke('#setAuthToken', tokenData);
|
|
1098
|
-
}
|
|
1099
|
-
catch (err) {
|
|
1100
|
-
let error;
|
|
1101
|
-
if (err && typeof err.message === 'string') {
|
|
1102
|
-
error = new AuthError(`Failed to deliver auth token to client - ${err.message}`);
|
|
1103
|
-
}
|
|
1104
|
-
else {
|
|
1105
|
-
error = new AuthError('Failed to confirm delivery of auth token to client due to malformatted error response');
|
|
1106
|
-
}
|
|
1107
|
-
this.emitError(error);
|
|
1108
|
-
throw error;
|
|
1109
|
-
}
|
|
1110
|
-
return;
|
|
1111
|
-
}
|
|
1112
|
-
this.transmit('#setAuthToken', tokenData);
|
|
1113
|
-
}
|
|
1114
|
-
getAuthToken() {
|
|
1115
|
-
return this.authToken;
|
|
1116
|
-
}
|
|
1117
|
-
deauthenticateSelf() {
|
|
1118
|
-
const oldAuthState = this.authState;
|
|
1119
|
-
const oldAuthToken = this.authToken;
|
|
1120
|
-
this.signedAuthToken = null;
|
|
1121
|
-
this.authToken = null;
|
|
1122
|
-
this.authState = AuthState.UNAUTHENTICATED;
|
|
1123
|
-
if (oldAuthState !== AuthState.UNAUTHENTICATED) {
|
|
1124
|
-
const stateChangeData = {
|
|
1125
|
-
oldAuthState,
|
|
1126
|
-
newAuthState: this.authState
|
|
1127
|
-
};
|
|
1128
|
-
this.emit('authStateChange', stateChangeData);
|
|
1129
|
-
this.server.emit('authenticationStateChange', {
|
|
1130
|
-
socket: this,
|
|
1131
|
-
...stateChangeData
|
|
1132
|
-
});
|
|
1133
|
-
}
|
|
1134
|
-
this.emit('deauthenticate', { oldAuthToken });
|
|
1135
|
-
this.server.emit('deauthentication', {
|
|
1136
|
-
socket: this,
|
|
1137
|
-
oldAuthToken
|
|
1138
|
-
});
|
|
1139
|
-
}
|
|
1140
|
-
async deauthenticate(options) {
|
|
1141
|
-
this.deauthenticateSelf();
|
|
1142
|
-
if (options && options.rejectOnFailedDelivery) {
|
|
1143
|
-
try {
|
|
1144
|
-
await this.invoke('#removeAuthToken');
|
|
1145
|
-
}
|
|
1146
|
-
catch (error) {
|
|
1147
|
-
this.emitError(error);
|
|
1148
|
-
if (options && options.rejectOnFailedDelivery) {
|
|
1149
|
-
throw error;
|
|
1150
|
-
}
|
|
1151
|
-
}
|
|
1152
|
-
return;
|
|
1153
|
-
}
|
|
1154
|
-
this._transmit('#removeAuthToken');
|
|
1155
|
-
}
|
|
1156
|
-
kickOut(channel, message) {
|
|
1157
|
-
const channels = channel ? [channel] : Object.keys(this.channelSubscriptions);
|
|
1158
|
-
return Promise.all(channels.map((channelName) => {
|
|
1159
|
-
this.transmit('#kickOut', { channel: channelName, message });
|
|
1160
|
-
return this._unsubscribe(channelName);
|
|
1161
|
-
}));
|
|
1162
|
-
}
|
|
1163
|
-
subscriptions() {
|
|
1164
|
-
return Object.keys(this.channelSubscriptions);
|
|
1165
|
-
}
|
|
1166
|
-
isSubscribed(channel) {
|
|
1167
|
-
return !!this.channelSubscriptions[channel];
|
|
1168
|
-
}
|
|
1169
|
-
_processAuthTokenExpiry() {
|
|
1170
|
-
const token = this.getAuthToken();
|
|
1171
|
-
if (this.isAuthTokenExpired(token)) {
|
|
1172
|
-
this.deauthenticate();
|
|
1173
|
-
return new AuthTokenExpiredError('The socket auth token has expired', new Date(token.exp));
|
|
1174
|
-
}
|
|
1175
|
-
return null;
|
|
1176
|
-
}
|
|
1177
|
-
isAuthTokenExpired(token) {
|
|
1178
|
-
if (token?.exp != null) {
|
|
1179
|
-
const currentTime = Date.now();
|
|
1180
|
-
const expiryMilliseconds = token.exp * 1000;
|
|
1181
|
-
return currentTime > expiryMilliseconds;
|
|
1182
|
-
}
|
|
1183
|
-
return false;
|
|
1184
|
-
}
|
|
1185
|
-
_processTokenError(err) {
|
|
1186
|
-
if (err.name === 'TokenExpiredError' && 'expiredAt' in err) {
|
|
1187
|
-
return new AuthTokenExpiredError(err.message, err.expiredAt);
|
|
1188
|
-
}
|
|
1189
|
-
if (err.name === 'JsonWebTokenError') {
|
|
1190
|
-
return new AuthTokenInvalidError(err.message);
|
|
1191
|
-
}
|
|
1192
|
-
if (err.name === 'NotBeforeError' && 'date' in err) {
|
|
1193
|
-
// In this case, the token is good; it's just not active yet.
|
|
1194
|
-
return new AuthTokenNotBeforeError(err.message, err.date);
|
|
1195
|
-
}
|
|
1196
|
-
return new AuthTokenError(err.message);
|
|
1197
|
-
}
|
|
1198
|
-
_emitBadAuthTokenError(error, signedAuthToken) {
|
|
1199
|
-
this.emit('badAuthToken', {
|
|
1200
|
-
authError: error,
|
|
1201
|
-
signedAuthToken
|
|
1202
|
-
});
|
|
1203
|
-
this.server.emit('badSocketAuthToken', {
|
|
1204
|
-
socket: this,
|
|
1205
|
-
authError: error,
|
|
1206
|
-
signedAuthToken
|
|
1207
|
-
});
|
|
1208
|
-
}
|
|
1209
|
-
async _validateAuthToken(signedAuthToken) {
|
|
1210
|
-
const verificationOptions = Object.assign({ socket: this }, this.server.defaultVerificationOptions);
|
|
1211
|
-
let authToken;
|
|
1212
|
-
try {
|
|
1213
|
-
authToken = await this.server.auth.verifyToken(signedAuthToken, this.server.verificationKey, verificationOptions);
|
|
1214
|
-
}
|
|
1215
|
-
catch (error) {
|
|
1216
|
-
const authTokenError = this._processTokenError(error);
|
|
1217
|
-
return {
|
|
1218
|
-
signedAuthToken,
|
|
1219
|
-
authTokenError,
|
|
1220
|
-
authToken: null,
|
|
1221
|
-
authState: AuthState.UNAUTHENTICATED
|
|
1222
|
-
};
|
|
1223
|
-
}
|
|
1224
|
-
return {
|
|
1225
|
-
signedAuthToken,
|
|
1226
|
-
authTokenError: null,
|
|
1227
|
-
authToken,
|
|
1228
|
-
authState: AuthState.AUTHENTICATED
|
|
1229
|
-
};
|
|
1230
|
-
}
|
|
1231
|
-
async _processAuthentication({ signedAuthToken, authTokenError, authToken }) {
|
|
1232
|
-
if (authTokenError) {
|
|
1233
|
-
this.signedAuthToken = null;
|
|
1234
|
-
this.authToken = null;
|
|
1235
|
-
this.authState = AuthState.UNAUTHENTICATED;
|
|
1236
|
-
// If the error is related to the JWT being badly formatted, then we will
|
|
1237
|
-
// treat the error as a socket error.
|
|
1238
|
-
if (signedAuthToken != null) {
|
|
1239
|
-
this.emitError(authTokenError);
|
|
1240
|
-
if (authTokenError instanceof AuthTokenError) {
|
|
1241
|
-
this._emitBadAuthTokenError(authTokenError, signedAuthToken);
|
|
1242
|
-
}
|
|
1243
|
-
}
|
|
1244
|
-
throw authTokenError;
|
|
1245
|
-
}
|
|
1246
|
-
this.signedAuthToken = signedAuthToken;
|
|
1247
|
-
this.authToken = authToken;
|
|
1248
|
-
this.authState = AuthState.AUTHENTICATED;
|
|
1249
|
-
const action = new ActionAuthenticate(this, authToken, signedAuthToken);
|
|
1250
|
-
try {
|
|
1251
|
-
await this.server._processMiddlewareAction(this.middlewareInboundStream, action, this);
|
|
1252
|
-
}
|
|
1253
|
-
catch (error) {
|
|
1254
|
-
this.authToken = null;
|
|
1255
|
-
this.authState = AuthState.UNAUTHENTICATED;
|
|
1256
|
-
if (error instanceof AuthTokenError) {
|
|
1257
|
-
this._emitBadAuthTokenError(error, signedAuthToken);
|
|
1258
|
-
}
|
|
1259
|
-
throw error;
|
|
1260
|
-
}
|
|
1261
|
-
}
|
|
1262
|
-
}
|