@eleven-am/pondsocket 0.1.55 → 0.1.57
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/.eslintrc.json +387 -0
- package/dist/LICENSE +674 -0
- package/dist/README.md +139 -0
- package/{channel → dist/channel}/channel.js +5 -7
- package/{lobby → dist/lobby}/joinResponse.js +2 -2
- package/dist/package.json +51 -0
- package/{presence → dist/presence}/presence.js +16 -18
- package/{server → dist/server}/pondSocket.js +1 -1
- package/{subjects → dist/subjects}/subject.js +44 -0
- package/{types.d.ts → dist/types.d.ts} +0 -5
- package/jest.config.js +11 -0
- package/package.json +3 -3
- package/src/abstracts/abstractRequest.test.ts +49 -0
- package/src/abstracts/abstractRequest.ts +56 -0
- package/src/abstracts/abstractResponse.ts +26 -0
- package/src/abstracts/middleware.test.ts +75 -0
- package/src/abstracts/middleware.ts +50 -0
- package/src/channel/channel.test.ts +501 -0
- package/src/channel/channel.ts +305 -0
- package/src/channel/eventRequest.test.ts +37 -0
- package/src/channel/eventRequest.ts +27 -0
- package/src/channel/eventResponse.test.ts +249 -0
- package/src/channel/eventResponse.ts +172 -0
- package/src/client/channel.test.ts +799 -0
- package/src/client/channel.ts +342 -0
- package/src/client.ts +124 -0
- package/src/endpoint/endpoint.test.ts +825 -0
- package/src/endpoint/endpoint.ts +304 -0
- package/src/endpoint/response.ts +106 -0
- package/src/enums.ts +52 -0
- package/src/errors/pondError.ts +32 -0
- package/src/express.ts +58 -0
- package/src/index.ts +3 -0
- package/src/lobby/JoinRequest.test.ts +48 -0
- package/src/lobby/JoinResponse.test.ts +162 -0
- package/src/lobby/joinRequest.ts +32 -0
- package/src/lobby/joinResponse.ts +146 -0
- package/src/lobby/lobby.ts +182 -0
- package/src/matcher/matcher.test.ts +103 -0
- package/src/matcher/matcher.ts +105 -0
- package/src/node.ts +33 -0
- package/src/presence/presence.ts +127 -0
- package/src/presence/presenceEngine.test.ts +143 -0
- package/src/server/pondSocket.ts +153 -0
- package/src/subjects/subject.test.ts +163 -0
- package/src/subjects/subject.ts +137 -0
- package/src/typedefs.d.ts +451 -0
- package/src/types.d.ts +89 -0
- package/tsconfig.build.json +7 -0
- package/tsconfig.json +12 -0
- /package/{abstracts → dist/abstracts}/abstractRequest.js +0 -0
- /package/{abstracts → dist/abstracts}/abstractResponse.js +0 -0
- /package/{abstracts → dist/abstracts}/middleware.js +0 -0
- /package/{channel → dist/channel}/eventRequest.js +0 -0
- /package/{channel → dist/channel}/eventResponse.js +0 -0
- /package/{client → dist/client}/channel.js +0 -0
- /package/{client.d.ts → dist/client.d.ts} +0 -0
- /package/{client.js → dist/client.js} +0 -0
- /package/{endpoint → dist/endpoint}/endpoint.js +0 -0
- /package/{endpoint → dist/endpoint}/response.js +0 -0
- /package/{enums.js → dist/enums.js} +0 -0
- /package/{errors → dist/errors}/pondError.js +0 -0
- /package/{express.d.ts → dist/express.d.ts} +0 -0
- /package/{express.js → dist/express.js} +0 -0
- /package/{index.d.ts → dist/index.d.ts} +0 -0
- /package/{index.js → dist/index.js} +0 -0
- /package/{lobby → dist/lobby}/joinRequest.js +0 -0
- /package/{lobby → dist/lobby}/lobby.js +0 -0
- /package/{matcher → dist/matcher}/matcher.js +0 -0
- /package/{node.d.ts → dist/node.d.ts} +0 -0
- /package/{node.js → dist/node.js} +0 -0
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
import { WebSocket, WebSocketServer } from 'ws';
|
|
2
|
+
|
|
3
|
+
import { Middleware } from '../abstracts/middleware';
|
|
4
|
+
import { ChannelEngine } from '../channel/channel';
|
|
5
|
+
import { ServerActions, SystemSender, ErrorTypes, ClientActions } from '../enums';
|
|
6
|
+
import { EndpointError, PresenceError, ChannelError } from '../errors/pondError';
|
|
7
|
+
import { JoinRequest } from '../lobby/joinRequest';
|
|
8
|
+
import { JoinResponse } from '../lobby/joinResponse';
|
|
9
|
+
import { LobbyEngine, PondChannel } from '../lobby/lobby';
|
|
10
|
+
import { parseAddress } from '../matcher/matcher';
|
|
11
|
+
// eslint-disable-next-line import/no-unresolved
|
|
12
|
+
import { PondAssigns, ClientMessage, PondMessage, ChannelEvent, JoinParams, PondPath } from '../types';
|
|
13
|
+
|
|
14
|
+
type AuthorizationHandler<Event extends string> = (request: JoinRequest<Event>, response: JoinResponse) => void | Promise<void>;
|
|
15
|
+
|
|
16
|
+
export interface SocketCache {
|
|
17
|
+
clientId: string;
|
|
18
|
+
socket: WebSocket;
|
|
19
|
+
assigns: PondAssigns;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface RequestCache extends SocketCache {
|
|
23
|
+
channelName: string;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export class Endpoint {
|
|
27
|
+
readonly #middleware: Middleware<RequestCache, JoinParams>;
|
|
28
|
+
|
|
29
|
+
readonly #server: WebSocketServer;
|
|
30
|
+
|
|
31
|
+
readonly #channels: Set<LobbyEngine>;
|
|
32
|
+
|
|
33
|
+
readonly #sockets: Set<SocketCache>;
|
|
34
|
+
|
|
35
|
+
constructor (server: WebSocketServer) {
|
|
36
|
+
this.#server = server;
|
|
37
|
+
this.#sockets = new Set();
|
|
38
|
+
this.#middleware = new Middleware();
|
|
39
|
+
this.#channels = new Set();
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* @desc Adds a new PondChannel to this path on this endpoint
|
|
44
|
+
* @param path - The path to add the channel to
|
|
45
|
+
* @param handler - The handler to use to authenticate the client
|
|
46
|
+
*
|
|
47
|
+
* @example
|
|
48
|
+
* const channel = endpoint.createChannel('/chat', (request, response) => {
|
|
49
|
+
* if (request.user.assigns.admin)
|
|
50
|
+
* response.accept();
|
|
51
|
+
*
|
|
52
|
+
* else
|
|
53
|
+
* response.reject('You are not an admin', 403);
|
|
54
|
+
* });
|
|
55
|
+
*/
|
|
56
|
+
public createChannel<Path extends string> (path: PondPath<Path>, handler: AuthorizationHandler<Path>) {
|
|
57
|
+
const pondChannel = new LobbyEngine();
|
|
58
|
+
|
|
59
|
+
this.#middleware.use((user, joinParams, next) => {
|
|
60
|
+
const event = parseAddress(path, user.channelName);
|
|
61
|
+
|
|
62
|
+
if (event) {
|
|
63
|
+
const newChannel = pondChannel.getChannel(user.channelName) || pondChannel.createChannel(user.channelName);
|
|
64
|
+
const request = new JoinRequest(user, joinParams, newChannel);
|
|
65
|
+
const response = new JoinResponse(user, newChannel);
|
|
66
|
+
|
|
67
|
+
if (request._parseQueries(path)) {
|
|
68
|
+
return handler(request as JoinRequest<Path>, response);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
next();
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
this.#channels.add(pondChannel);
|
|
76
|
+
|
|
77
|
+
return new PondChannel(pondChannel);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* @desc List all clients connected to this endpoint
|
|
82
|
+
*/
|
|
83
|
+
public listConnections () {
|
|
84
|
+
return [...this.#sockets].map(({ clientId }) => clientId);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* @desc Gets all clients connected to this endpoint
|
|
89
|
+
*/
|
|
90
|
+
public getClients () {
|
|
91
|
+
return [...this.#sockets];
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* @desc Broadcasts a message to all clients connected to this endpoint
|
|
96
|
+
* @param event - The event to broadcast
|
|
97
|
+
* @param payload - The payload to broadcast
|
|
98
|
+
*/
|
|
99
|
+
public broadcast (event: string, payload: PondMessage) {
|
|
100
|
+
this.#sockets.forEach(({ socket }) => {
|
|
101
|
+
const message: ChannelEvent = {
|
|
102
|
+
event,
|
|
103
|
+
payload,
|
|
104
|
+
action: ServerActions.BROADCAST,
|
|
105
|
+
channelName: SystemSender.ENDPOINT,
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
this.#sendMessage(socket, message);
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* @desc Closes specific clients connected to this endpoint
|
|
114
|
+
* @param clientIds - The id for the client / clients to close
|
|
115
|
+
*/
|
|
116
|
+
public closeConnection (clientIds: string | string[]) {
|
|
117
|
+
const clients = typeof clientIds === 'string' ? [clientIds] : clientIds;
|
|
118
|
+
|
|
119
|
+
this.getClients()
|
|
120
|
+
.forEach(({ clientId, socket }) => {
|
|
121
|
+
if (clients.includes(clientId)) {
|
|
122
|
+
socket.close();
|
|
123
|
+
}
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* @desc Manages a new socket connection
|
|
129
|
+
* @param cache - The socket cache
|
|
130
|
+
*/
|
|
131
|
+
public manageSocket (cache: SocketCache) {
|
|
132
|
+
this.#sockets.add(cache);
|
|
133
|
+
const socket = cache.socket;
|
|
134
|
+
|
|
135
|
+
socket.addEventListener('message', (message) => {
|
|
136
|
+
this.#readMessage(cache, message.data as string);
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
socket.addEventListener('close', () => {
|
|
140
|
+
this.#channels
|
|
141
|
+
.forEach((manager) => manager.removeUser(cache.clientId, true));
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
socket.addEventListener('error', () => {
|
|
145
|
+
this.#channels
|
|
146
|
+
.forEach((manager) => manager.removeUser(cache.clientId, true));
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* @desc Sends a message to a client
|
|
152
|
+
* @param socket - The socket to send the message to
|
|
153
|
+
* @param message - The message to send
|
|
154
|
+
*/
|
|
155
|
+
#sendMessage (socket: WebSocket, message: ChannelEvent) {
|
|
156
|
+
socket.send(JSON.stringify(message));
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* @desc Adds a new client to a channel on this endpoint
|
|
161
|
+
* @param channel - The channel to add the client to
|
|
162
|
+
* @param socket - The client to add to the channel
|
|
163
|
+
* @param joinParams - The parameters to pass to the channel
|
|
164
|
+
* @private
|
|
165
|
+
*/
|
|
166
|
+
#joinChannel (channel: string, socket: SocketCache, joinParams: Record<string, any>) {
|
|
167
|
+
const cache: RequestCache = {
|
|
168
|
+
...socket,
|
|
169
|
+
channelName: channel,
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
this.#middleware.run(cache, joinParams, () => {
|
|
173
|
+
throw new EndpointError(`GatewayEngine: Channel ${channel} does not exist`, 404);
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* @desc Executes a function on a channel
|
|
179
|
+
* @param channel - The channel to execute the function on
|
|
180
|
+
* @param handler - The function to execute
|
|
181
|
+
* @private
|
|
182
|
+
*/
|
|
183
|
+
#execute<ReturnType> (channel: string, handler: ((manager: ChannelEngine) => ReturnType)): ReturnType {
|
|
184
|
+
for (const manager of this.#channels) {
|
|
185
|
+
const isPresent = manager.listChannels()
|
|
186
|
+
.includes(channel);
|
|
187
|
+
|
|
188
|
+
if (isPresent) {
|
|
189
|
+
return manager.execute(channel, handler);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
throw new Error(`GatewayEngine: Channel ${channel} does not exist`);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* @desc Deals with a message sent from a client
|
|
198
|
+
* @param cache - The socket cache of the client
|
|
199
|
+
* @param message - The message to handle
|
|
200
|
+
*/
|
|
201
|
+
#handleMessage (cache: SocketCache, message: ClientMessage) {
|
|
202
|
+
switch (message.action) {
|
|
203
|
+
case ClientActions.JOIN_CHANNEL:
|
|
204
|
+
this.#joinChannel(message.channelName, cache, message.payload);
|
|
205
|
+
break;
|
|
206
|
+
|
|
207
|
+
case ClientActions.LEAVE_CHANNEL:
|
|
208
|
+
this.#execute(message.channelName, (channel) => {
|
|
209
|
+
channel.removeUser(cache.clientId);
|
|
210
|
+
});
|
|
211
|
+
break;
|
|
212
|
+
|
|
213
|
+
case ClientActions.BROADCAST:
|
|
214
|
+
this.#execute(message.channelName, (channel) => {
|
|
215
|
+
channel.broadcastMessage(cache.clientId, message);
|
|
216
|
+
});
|
|
217
|
+
break;
|
|
218
|
+
default:
|
|
219
|
+
throw new Error(`GatewayEngine: Action ${message.action} does not exist`);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* @desc Handles a message sent from a client
|
|
225
|
+
* @param cache - The socket cache of the client
|
|
226
|
+
* @param message - The message to handle
|
|
227
|
+
* @private
|
|
228
|
+
*/
|
|
229
|
+
#readMessage (cache: SocketCache, message: string) {
|
|
230
|
+
const errorMessage: ChannelEvent = {
|
|
231
|
+
event: ErrorTypes.INVALID_MESSAGE,
|
|
232
|
+
action: ServerActions.ERROR,
|
|
233
|
+
channelName: SystemSender.ENDPOINT,
|
|
234
|
+
payload: {},
|
|
235
|
+
};
|
|
236
|
+
|
|
237
|
+
try {
|
|
238
|
+
const data = JSON.parse(message) as ClientMessage;
|
|
239
|
+
|
|
240
|
+
if (!data.action) {
|
|
241
|
+
errorMessage.payload = {
|
|
242
|
+
message: 'No action provided',
|
|
243
|
+
};
|
|
244
|
+
} else if (!data.channelName) {
|
|
245
|
+
errorMessage.payload = {
|
|
246
|
+
message: 'No channel name provided',
|
|
247
|
+
};
|
|
248
|
+
} else if (!data.payload) {
|
|
249
|
+
errorMessage.payload = {
|
|
250
|
+
message: 'No payload provided',
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
if (!this.#isObjectEmpty(errorMessage.payload)) {
|
|
255
|
+
this.#sendMessage(cache.socket, errorMessage);
|
|
256
|
+
} else {
|
|
257
|
+
this.#handleMessage(cache, data);
|
|
258
|
+
}
|
|
259
|
+
} catch (e) {
|
|
260
|
+
if (e instanceof SyntaxError) {
|
|
261
|
+
errorMessage.payload = {
|
|
262
|
+
message: 'Invalid JSON',
|
|
263
|
+
};
|
|
264
|
+
} else if (e instanceof Error) {
|
|
265
|
+
errorMessage.event = ErrorTypes.INTERNAL_SERVER_ERROR;
|
|
266
|
+
errorMessage.payload = {
|
|
267
|
+
message: e.message,
|
|
268
|
+
};
|
|
269
|
+
} else if (e instanceof PresenceError) {
|
|
270
|
+
errorMessage.event = ErrorTypes.PRESENCE_ERROR;
|
|
271
|
+
errorMessage.channelName = e.channel;
|
|
272
|
+
errorMessage.payload = {
|
|
273
|
+
message: e.message,
|
|
274
|
+
code: e.code,
|
|
275
|
+
action: e.event,
|
|
276
|
+
};
|
|
277
|
+
} else if (e instanceof ChannelError) {
|
|
278
|
+
errorMessage.event = ErrorTypes.CHANNEL_ERROR;
|
|
279
|
+
errorMessage.channelName = e.channel;
|
|
280
|
+
errorMessage.payload = {
|
|
281
|
+
message: e.message,
|
|
282
|
+
code: e.code,
|
|
283
|
+
};
|
|
284
|
+
} else if (e instanceof EndpointError) {
|
|
285
|
+
errorMessage.event = ErrorTypes.ENDPOINT_ERROR;
|
|
286
|
+
errorMessage.payload = {
|
|
287
|
+
message: e.message,
|
|
288
|
+
code: e.code,
|
|
289
|
+
};
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
this.#sendMessage(cache.socket, errorMessage);
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/**
|
|
297
|
+
* @desc Checks if an object is empty
|
|
298
|
+
* @param obj - The object to check
|
|
299
|
+
* @private
|
|
300
|
+
*/
|
|
301
|
+
#isObjectEmpty (obj: Record<string, any>) {
|
|
302
|
+
return Object.keys(obj).length === 0;
|
|
303
|
+
}
|
|
304
|
+
}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { WebSocket } from 'ws';
|
|
2
|
+
|
|
3
|
+
import { Endpoint, SocketCache } from './endpoint';
|
|
4
|
+
import { PondResponse } from '../abstracts/abstractResponse';
|
|
5
|
+
import { ServerActions, ErrorTypes, SystemSender } from '../enums';
|
|
6
|
+
import { EndpointError } from '../errors/pondError';
|
|
7
|
+
// eslint-disable-next-line import/no-unresolved
|
|
8
|
+
import { PondAssigns, PondMessage } from '../types';
|
|
9
|
+
|
|
10
|
+
export class ConnectionResponse extends PondResponse {
|
|
11
|
+
readonly #webSocket: WebSocket;
|
|
12
|
+
|
|
13
|
+
readonly #engine: Endpoint;
|
|
14
|
+
|
|
15
|
+
readonly #clientId: string;
|
|
16
|
+
|
|
17
|
+
#executed: boolean;
|
|
18
|
+
|
|
19
|
+
constructor (webSocket: WebSocket, engine: Endpoint, clientId: string) {
|
|
20
|
+
super();
|
|
21
|
+
this.#webSocket = webSocket;
|
|
22
|
+
this.#engine = engine;
|
|
23
|
+
this.#clientId = clientId;
|
|
24
|
+
this.#executed = false;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* @desc Accepts the request and optionally assigns data to the client
|
|
29
|
+
* @param assigns - the data to assign to the client
|
|
30
|
+
*/
|
|
31
|
+
public accept (assigns?: PondAssigns) {
|
|
32
|
+
this.#performChecks();
|
|
33
|
+
const cache: SocketCache = {
|
|
34
|
+
clientId: this.#clientId,
|
|
35
|
+
socket: this.#webSocket,
|
|
36
|
+
assigns: assigns || {},
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
this.#engine.manageSocket(cache);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* @desc Rejects the request with the given error message
|
|
44
|
+
* @param message - the error message
|
|
45
|
+
* @param errorCode - the error code
|
|
46
|
+
*/
|
|
47
|
+
public reject (message?: string, errorCode?: number) {
|
|
48
|
+
this.#performChecks();
|
|
49
|
+
const payload = {
|
|
50
|
+
message: message || 'Unauthorized connection',
|
|
51
|
+
code: errorCode || 401,
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
this.#sendMessage(
|
|
55
|
+
ServerActions.ERROR,
|
|
56
|
+
ErrorTypes.UNAUTHORIZED_CONNECTION,
|
|
57
|
+
payload,
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
this.#webSocket.close();
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* @desc Emits a direct message to the client
|
|
65
|
+
* @param event - the event name
|
|
66
|
+
* @param payload - the payload to send
|
|
67
|
+
* @param assigns - the data to assign to the client
|
|
68
|
+
*/
|
|
69
|
+
public send (event: string, payload: PondMessage, assigns?: PondAssigns) {
|
|
70
|
+
this.accept(assigns);
|
|
71
|
+
this.#sendMessage(ServerActions.BROADCAST, event, payload);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* @desc Emits a direct message to the client
|
|
76
|
+
* @param action - the action to perform
|
|
77
|
+
* @param event - the event name
|
|
78
|
+
* @param payload - the payload to send
|
|
79
|
+
* @private
|
|
80
|
+
*/
|
|
81
|
+
#sendMessage (action: ServerActions, event: string, payload: PondMessage) {
|
|
82
|
+
const message = {
|
|
83
|
+
action,
|
|
84
|
+
event,
|
|
85
|
+
payload,
|
|
86
|
+
channelName: SystemSender.ENDPOINT,
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
this.#webSocket.send(JSON.stringify(message));
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* @desc Performs checks to ensure the response has not been executed
|
|
94
|
+
* @private
|
|
95
|
+
*/
|
|
96
|
+
#performChecks (): void {
|
|
97
|
+
if (this.#executed) {
|
|
98
|
+
const message = 'Cannot execute response more than once';
|
|
99
|
+
const code = 403;
|
|
100
|
+
|
|
101
|
+
throw new EndpointError(message, code);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
this.#executed = true;
|
|
105
|
+
}
|
|
106
|
+
}
|
package/src/enums.ts
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
export enum PresenceEventTypes {
|
|
2
|
+
JOIN = 'JOIN',
|
|
3
|
+
LEAVE = 'LEAVE',
|
|
4
|
+
UPDATE = 'UPDATE'
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export enum ServerActions {
|
|
8
|
+
PRESENCE = 'PRESENCE',
|
|
9
|
+
SYSTEM = 'SYSTEM',
|
|
10
|
+
BROADCAST = 'BROADCAST',
|
|
11
|
+
ERROR = 'ERROR',
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export enum ClientActions {
|
|
15
|
+
JOIN_CHANNEL = 'JOIN_CHANNEL',
|
|
16
|
+
LEAVE_CHANNEL = 'LEAVE_CHANNEL',
|
|
17
|
+
BROADCAST = 'BROADCAST',
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export enum ChannelState {
|
|
21
|
+
IDLE = 'IDLE',
|
|
22
|
+
JOINING = 'JOINING',
|
|
23
|
+
JOINED = 'JOINED',
|
|
24
|
+
STALLED = 'STALLED',
|
|
25
|
+
CLOSED = 'CLOSED',
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export enum ErrorTypes {
|
|
29
|
+
UNAUTHORIZED_CONNECTION = 'UNAUTHORIZED_CONNECTION',
|
|
30
|
+
UNAUTHORIZED_JOIN_REQUEST = 'UNAUTHORIZED_JOIN_REQUEST',
|
|
31
|
+
UNAUTHORIZED_BROADCAST = 'UNAUTHORIZED_BROADCAST',
|
|
32
|
+
INVALID_MESSAGE = 'INVALID_MESSAGE',
|
|
33
|
+
HANDLER_NOT_FOUND = 'HANDLER_NOT_FOUND',
|
|
34
|
+
PRESENCE_ERROR = 'PRESENCE_ERROR',
|
|
35
|
+
CHANNEL_ERROR = 'CHANNEL_ERROR',
|
|
36
|
+
ENDPOINT_ERROR = 'ENDPOINT_ERROR',
|
|
37
|
+
INTERNAL_SERVER_ERROR = 'INTERNAL_SERVER_ERROR',
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export enum SystemSender {
|
|
41
|
+
ENDPOINT = 'ENDPOINT',
|
|
42
|
+
CHANNEL = 'CHANNEL',
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export enum ChannelReceiver {
|
|
46
|
+
ALL_USERS = 'ALL_USERS',
|
|
47
|
+
ALL_EXCEPT_SENDER = 'ALL_EXCEPT_SENDER',
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export enum Events {
|
|
51
|
+
ACKNOWLEDGE = 'ACKNOWLEDGE',
|
|
52
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { PresenceEventTypes } from '../enums';
|
|
2
|
+
|
|
3
|
+
export class PondError {
|
|
4
|
+
public message: string;
|
|
5
|
+
|
|
6
|
+
public code: number;
|
|
7
|
+
|
|
8
|
+
constructor (message: string, code: number) {
|
|
9
|
+
this.code = code;
|
|
10
|
+
this.message = message;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export class EndpointError extends PondError {}
|
|
15
|
+
|
|
16
|
+
export class ChannelError extends EndpointError {
|
|
17
|
+
public channel: string;
|
|
18
|
+
|
|
19
|
+
constructor (message: string, code: number, channel: string) {
|
|
20
|
+
super(message, code);
|
|
21
|
+
this.channel = channel;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export class PresenceError extends ChannelError {
|
|
26
|
+
public event: PresenceEventTypes;
|
|
27
|
+
|
|
28
|
+
constructor (message: string, code: number, channel: string, event: PresenceEventTypes) {
|
|
29
|
+
super(message, code, channel);
|
|
30
|
+
this.event = event;
|
|
31
|
+
}
|
|
32
|
+
}
|
package/src/express.ts
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { createServer } from 'http';
|
|
2
|
+
|
|
3
|
+
// eslint-disable-next-line import/no-unresolved
|
|
4
|
+
import { Express } from 'express';
|
|
5
|
+
|
|
6
|
+
import { Endpoint } from './endpoint/endpoint';
|
|
7
|
+
import { ConnectionResponse } from './endpoint/response';
|
|
8
|
+
import { PondSocket } from './server/pondSocket';
|
|
9
|
+
// eslint-disable-next-line import/no-unresolved
|
|
10
|
+
import { PondPath, IncomingConnection } from './types';
|
|
11
|
+
|
|
12
|
+
declare global {
|
|
13
|
+
// eslint-disable-next-line @typescript-eslint/no-namespace
|
|
14
|
+
namespace Express {
|
|
15
|
+
export interface Application {
|
|
16
|
+
upgrade<Path extends string>(path: PondPath<Path>, handler: (request: IncomingConnection<Path>, response: ConnectionResponse) => void | Promise<void>): Endpoint;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
interface PondSocketExpressApp extends Express {
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* @desc Accepts a new socket upgrade request on the provided endpoint using the handler function to authenticate the socket
|
|
26
|
+
* @param path - the pattern to accept || can also be a regex
|
|
27
|
+
* @param handler - the handler function to authenticate the socket
|
|
28
|
+
* @example
|
|
29
|
+
* const endpoint = pond.createEndpoint('/api/socket', (req, res) => {
|
|
30
|
+
* const token = req.query.token;
|
|
31
|
+
* if (!token)
|
|
32
|
+
* return res.reject("No token provided");
|
|
33
|
+
* res.accept({
|
|
34
|
+
* assign: {
|
|
35
|
+
* token
|
|
36
|
+
* }
|
|
37
|
+
* });
|
|
38
|
+
* })
|
|
39
|
+
*/
|
|
40
|
+
upgrade<Path extends string>(path: PondPath<Path>, handler: (request: IncomingConnection<Path>, response: ConnectionResponse) => void | Promise<void>): Endpoint;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* @desc Creates a pond socket server
|
|
45
|
+
* @param app - The Express app to be used by the server
|
|
46
|
+
* @constructor
|
|
47
|
+
*/
|
|
48
|
+
const pondSocket = (app: Express): PondSocketExpressApp => {
|
|
49
|
+
const server = createServer(app);
|
|
50
|
+
const pondSocket = new PondSocket(server);
|
|
51
|
+
|
|
52
|
+
app.upgrade = (path, handler) => pondSocket.createEndpoint(path, handler);
|
|
53
|
+
app.listen = (...args: any[]) => pondSocket.listen(...args);
|
|
54
|
+
|
|
55
|
+
return app as PondSocketExpressApp;
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
export default pondSocket;
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { JoinRequest } from './joinRequest';
|
|
2
|
+
import { createChannelEngine } from '../channel/eventResponse.test';
|
|
3
|
+
import { RequestCache } from '../endpoint/endpoint';
|
|
4
|
+
|
|
5
|
+
const createMockSocket = (params = {}) => {
|
|
6
|
+
const channelEngine = createChannelEngine();
|
|
7
|
+
|
|
8
|
+
const socket: RequestCache = {
|
|
9
|
+
clientId: 'sender',
|
|
10
|
+
assigns: { assign: 'assign' },
|
|
11
|
+
channelName: 'channel',
|
|
12
|
+
socket: {
|
|
13
|
+
send: jest.fn(),
|
|
14
|
+
} as any,
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
const request = new JoinRequest(socket, params, channelEngine);
|
|
18
|
+
|
|
19
|
+
return {
|
|
20
|
+
channelEngine,
|
|
21
|
+
socket,
|
|
22
|
+
request,
|
|
23
|
+
};
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
describe('JoinRequest', () => {
|
|
27
|
+
it('should create a new PondChannelResponse', () => {
|
|
28
|
+
const { request } = createMockSocket();
|
|
29
|
+
|
|
30
|
+
expect(request).toBeDefined();
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it('should return the join params', () => {
|
|
34
|
+
const { request } = createMockSocket({ params: 'params' });
|
|
35
|
+
|
|
36
|
+
expect(request.joinParams).toEqual({ params: 'params' });
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it('should return the user data', () => {
|
|
40
|
+
const { request } = createMockSocket();
|
|
41
|
+
|
|
42
|
+
expect(request.user).toEqual({
|
|
43
|
+
id: 'sender',
|
|
44
|
+
assigns: { assign: 'assign' },
|
|
45
|
+
presence: {},
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
});
|