@eleven-am/pondsocket 0.1.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.
Files changed (64) hide show
  1. package/.eslintrc.js +28 -0
  2. package/.idea/modules.xml +8 -0
  3. package/.idea/pondsocket.iml +12 -0
  4. package/LICENSE +674 -0
  5. package/base.d.ts +1 -0
  6. package/base.js +17 -0
  7. package/client.d.ts +1 -0
  8. package/client.js +17 -0
  9. package/index.d.ts +1 -0
  10. package/index.js +17 -0
  11. package/jest.config.js +11 -0
  12. package/package.json +48 -0
  13. package/pondBase/baseClass.d.ts +37 -0
  14. package/pondBase/baseClass.js +111 -0
  15. package/pondBase/baseClass.test.js +73 -0
  16. package/pondBase/enums.d.ts +9 -0
  17. package/pondBase/enums.js +14 -0
  18. package/pondBase/index.d.ts +6 -0
  19. package/pondBase/index.js +22 -0
  20. package/pondBase/pondBase.d.ts +41 -0
  21. package/pondBase/pondBase.js +60 -0
  22. package/pondBase/pondBase.test.js +101 -0
  23. package/pondBase/pubSub.d.ts +73 -0
  24. package/pondBase/pubSub.js +138 -0
  25. package/pondBase/pubSub.test.js +309 -0
  26. package/pondBase/simpleBase.d.ts +131 -0
  27. package/pondBase/simpleBase.js +211 -0
  28. package/pondBase/simpleBase.test.js +153 -0
  29. package/pondBase/types.d.ts +2 -0
  30. package/pondBase/types.js +2 -0
  31. package/pondClient/channel.d.ts +66 -0
  32. package/pondClient/channel.js +152 -0
  33. package/pondClient/index.d.ts +2 -0
  34. package/pondClient/index.js +18 -0
  35. package/pondClient/socket.d.ts +42 -0
  36. package/pondClient/socket.js +116 -0
  37. package/pondSocket/channel.d.ts +134 -0
  38. package/pondSocket/channel.js +287 -0
  39. package/pondSocket/channel.test.js +377 -0
  40. package/pondSocket/channelMiddleWare.d.ts +26 -0
  41. package/pondSocket/channelMiddleWare.js +36 -0
  42. package/pondSocket/endpoint.d.ts +90 -0
  43. package/pondSocket/endpoint.js +323 -0
  44. package/pondSocket/endpoint.test.js +513 -0
  45. package/pondSocket/enums.d.ts +19 -0
  46. package/pondSocket/enums.js +25 -0
  47. package/pondSocket/index.d.ts +7 -0
  48. package/pondSocket/index.js +23 -0
  49. package/pondSocket/pondChannel.d.ts +79 -0
  50. package/pondSocket/pondChannel.js +219 -0
  51. package/pondSocket/pondChannel.test.js +430 -0
  52. package/pondSocket/pondResponse.d.ts +25 -0
  53. package/pondSocket/pondResponse.js +120 -0
  54. package/pondSocket/pondSocket.d.ts +47 -0
  55. package/pondSocket/pondSocket.js +94 -0
  56. package/pondSocket/server.test.js +136 -0
  57. package/pondSocket/socketMiddleWare.d.ts +6 -0
  58. package/pondSocket/socketMiddleWare.js +32 -0
  59. package/pondSocket/types.d.ts +74 -0
  60. package/pondSocket/types.js +2 -0
  61. package/socket.d.ts +1 -0
  62. package/socket.js +17 -0
  63. package/tsconfig.eslint.json +5 -0
  64. package/tsconfig.json +90 -0
@@ -0,0 +1,377 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createChannel = void 0;
4
+ const channel_1 = require("./channel");
5
+ const channelMiddleWare_1 = require("./channelMiddleWare");
6
+ const pondBase_1 = require("../pondBase");
7
+ const enums_1 = require("./enums");
8
+ const createChannel = (name) => {
9
+ const user = {
10
+ client: {
11
+ clientId: 'test',
12
+ socket: {
13
+ send: jest.fn(),
14
+ on: jest.fn(),
15
+ }
16
+ },
17
+ presence: {
18
+ status: 'online',
19
+ lastSeenDate: new Date(),
20
+ },
21
+ assigns: {},
22
+ channelData: {}
23
+ };
24
+ const removeDoc = jest.fn();
25
+ const middleware = new channelMiddleWare_1.ChannelMiddleware();
26
+ const channel = new channel_1.Channel(name, middleware, removeDoc);
27
+ return { channel, removeDoc, user };
28
+ };
29
+ exports.createChannel = createChannel;
30
+ describe('Channel', () => {
31
+ it('should exist', () => {
32
+ expect(channel_1.Channel).toBeDefined();
33
+ });
34
+ it('should be a class', () => {
35
+ expect(channel_1.Channel).toBeInstanceOf(Function);
36
+ });
37
+ // Functionality tests
38
+ it('should add a user to the channel', () => {
39
+ const lastSeenDate = new Date();
40
+ const { channel } = (0, exports.createChannel)('test');
41
+ const user = {
42
+ client: {
43
+ clientId: 'test',
44
+ socket: {
45
+ send: jest.fn(),
46
+ on: jest.fn(),
47
+ }
48
+ },
49
+ presence: {
50
+ status: 'online',
51
+ lastSeen: lastSeenDate
52
+ },
53
+ assigns: {},
54
+ channelData: {}
55
+ };
56
+ channel.addUser(user);
57
+ expect(channel.getUserInfo('test')).toEqual({
58
+ presence: {
59
+ id: 'test',
60
+ status: 'online',
61
+ lastSeen: lastSeenDate
62
+ },
63
+ assigns: {},
64
+ });
65
+ });
66
+ it('should remove a user from the channel', () => {
67
+ const lastSeenDate = new Date();
68
+ const { channel, user } = (0, exports.createChannel)('test');
69
+ user.presence.lastSeenDate = lastSeenDate;
70
+ channel.addUser(user);
71
+ channel.removeUser('test');
72
+ expect(channel.getUserInfo('test')).toBeNull();
73
+ });
74
+ it('should update a user in the channel', () => {
75
+ const lastSeenDate = new Date();
76
+ const { channel, user } = (0, exports.createChannel)('test');
77
+ user.presence.lastSeenDate = lastSeenDate;
78
+ channel.addUser(user);
79
+ expect(channel.getUserInfo('test')).toEqual({
80
+ presence: {
81
+ id: 'test',
82
+ status: 'online',
83
+ lastSeenDate: lastSeenDate
84
+ },
85
+ assigns: {},
86
+ });
87
+ // replace both presence and assigns
88
+ channel.updateUser('test', {
89
+ status: 'offline'
90
+ }, {
91
+ test: 'test'
92
+ });
93
+ expect(channel.getUserInfo('test')).toEqual({
94
+ presence: {
95
+ id: 'test',
96
+ status: 'offline',
97
+ lastSeenDate: lastSeenDate
98
+ },
99
+ assigns: {
100
+ test: 'test'
101
+ },
102
+ });
103
+ // replace only presence
104
+ channel.updateUser('test', {
105
+ status: 'online'
106
+ }, {});
107
+ expect(channel.getUserInfo('test')).toEqual({
108
+ presence: {
109
+ id: 'test',
110
+ status: 'online',
111
+ lastSeenDate: lastSeenDate
112
+ },
113
+ assigns: {
114
+ test: 'test'
115
+ },
116
+ });
117
+ // replace only assigns
118
+ channel.updateUser('test', {}, {
119
+ test: 'test2'
120
+ });
121
+ expect(channel.getUserInfo('test')).toEqual({
122
+ presence: {
123
+ id: 'test',
124
+ status: 'online',
125
+ lastSeenDate: lastSeenDate
126
+ },
127
+ assigns: {
128
+ test: 'test2'
129
+ },
130
+ });
131
+ });
132
+ it('should throw when you add a user with an existing id', () => {
133
+ const lastSeenDate = new Date();
134
+ const { channel, user } = (0, exports.createChannel)('test');
135
+ user.presence.lastSeenDate = lastSeenDate;
136
+ channel.addUser(user);
137
+ expect(() => channel.addUser(user)).toThrow();
138
+ });
139
+ it('should broadcast a message when a user is added', () => {
140
+ const lastSeenDate = new Date();
141
+ const { channel, user } = (0, exports.createChannel)('test');
142
+ user.presence.lastSeenDate = lastSeenDate.toString();
143
+ const messages = [];
144
+ channel.onPresenceChange((message) => {
145
+ messages.push(message.event);
146
+ });
147
+ channel.addUser(user);
148
+ channel.removeUser('test'); // this broadcast will not be received by the subscription as it unsubscribes after the first message
149
+ expect(channel.getUserInfo('test')).toBeNull();
150
+ expect(messages).toHaveLength(2);
151
+ expect(messages).toEqual([pondBase_1.PondBaseActions.ADD_TO_POND, pondBase_1.PondBaseActions.REMOVE_FROM_POND]);
152
+ });
153
+ it('should set and get the channel data', () => {
154
+ const { channel } = (0, exports.createChannel)('test');
155
+ channel.data = {
156
+ test: 'test'
157
+ };
158
+ expect(channel.data).toStrictEqual({
159
+ test: 'test'
160
+ });
161
+ });
162
+ it('should get channel info', () => {
163
+ const { channel } = (0, exports.createChannel)('test');
164
+ channel.addUser({
165
+ client: {
166
+ clientId: 'test1',
167
+ },
168
+ presence: { status: 'online' },
169
+ assigns: {},
170
+ channelData: { name: 'test1' }
171
+ });
172
+ expect(channel.info).toStrictEqual({
173
+ name: 'test',
174
+ presence: [
175
+ {
176
+ id: 'test1',
177
+ status: 'online',
178
+ }
179
+ ],
180
+ assigns: [{}],
181
+ channelData: {
182
+ name: 'test1'
183
+ }
184
+ });
185
+ channel.data = {
186
+ test: 'test'
187
+ };
188
+ expect(channel.info).toStrictEqual({
189
+ name: 'test',
190
+ presence: [
191
+ {
192
+ id: 'test1',
193
+ status: 'online',
194
+ }
195
+ ],
196
+ assigns: [{}],
197
+ channelData: {
198
+ name: 'test1',
199
+ test: 'test'
200
+ }
201
+ });
202
+ });
203
+ it('should get the presence of all the users in the class', () => {
204
+ const { channel } = (0, exports.createChannel)('test');
205
+ expect(channel.presence).toStrictEqual([]);
206
+ channel.addUser({
207
+ client: {
208
+ clientId: 'test1',
209
+ },
210
+ presence: { status: 'online' },
211
+ assigns: {},
212
+ channelData: { name: 'test1' }
213
+ });
214
+ expect(channel.presence).toStrictEqual([
215
+ {
216
+ id: 'test1',
217
+ status: 'online',
218
+ }
219
+ ]);
220
+ });
221
+ it('should be possible to remove multiple users at once', () => {
222
+ const { channel, user } = (0, exports.createChannel)('test');
223
+ const user1 = { ...user };
224
+ user1.client.clientId = 'test1';
225
+ channel.addUser(user1);
226
+ const user2 = { ...user };
227
+ user2.client.clientId = 'test2';
228
+ channel.addUser(user2);
229
+ expect(channel.info.presence.length).toBe(2);
230
+ channel.removeUser(['test1', 'test2']);
231
+ expect(channel.info.presence.length).toBe(0);
232
+ });
233
+ it('should broadcast a message to all users in the channel except the sender', () => {
234
+ const { channel } = (0, exports.createChannel)('test');
235
+ let addresses = [];
236
+ // mock of a socket connection subscribed to the channel
237
+ // in a real world scenario, addresses.push = socket.send
238
+ // _message is the message to be JSON.stringified and sent to the socket
239
+ const sub = channel.subscribeToMessages('test1', (_message) => {
240
+ addresses.push('test1');
241
+ });
242
+ const user1 = {
243
+ client: {
244
+ clientId: 'test1',
245
+ },
246
+ presence: {},
247
+ assigns: {},
248
+ channelData: {}
249
+ };
250
+ const user2 = {
251
+ client: {
252
+ clientId: 'test2',
253
+ },
254
+ presence: {},
255
+ assigns: {},
256
+ channelData: {}
257
+ };
258
+ // we add the listener first to make sure that the user gets updates on their own join
259
+ channel.addUser(user1);
260
+ // unlike a normal subscription, this subscription will receive messages when the channel's presence is updated
261
+ // this is useful for the client to know when a user joins / leaves the channel
262
+ // This subscribers cannot block the channel from broadcasting messages
263
+ const sub2 = channel.subscribeToMessages('test2', () => {
264
+ addresses.push('test2');
265
+ });
266
+ channel.addUser(user2);
267
+ expect(channel.info.presence.length).toBe(2);
268
+ expect(addresses.length).toBe(3); // 3 joins messages because the first user receives the join message from the second user as well as their own
269
+ addresses = [];
270
+ channel.broadcastFrom('testEvent', { test: 'test' }, 'test1');
271
+ expect(addresses.length).toBe(1);
272
+ expect(addresses[0]).toBe('test2'); // the message was sent to the second user only: test1
273
+ // when your broadcastFrom is called with a user that is not in the channel,
274
+ // An error will be thrown
275
+ expect(() => {
276
+ channel.broadcastFrom('testEvent', { test: 'test' }, 'test3');
277
+ }).toThrowError(`Client with clientId test3 does not exist in channel test`);
278
+ // when your broadcast is called with a user that is not in the channel,
279
+ // An error will be thrown
280
+ expect(() => {
281
+ channel.broadcast('testEvent', { test: 'test' }, 'test3');
282
+ }).toThrowError(`Client with clientId test3 does not exist in channel test`);
283
+ sub.unsubscribe();
284
+ sub2.unsubscribe();
285
+ });
286
+ it('should provide an onMessage callback', () => {
287
+ const { channel } = (0, exports.createChannel)('test');
288
+ const messages = [];
289
+ channel.onMessage((message) => {
290
+ messages.push(message.event);
291
+ });
292
+ expect(() => channel.broadcast('testEvent', { test: 'test' }, 'test1')).toThrow();
293
+ expect(messages).toHaveLength(0);
294
+ });
295
+ it('should be able to hook up to the onMessage callback', () => {
296
+ const { channel, user } = (0, exports.createChannel)('test');
297
+ const messages = [];
298
+ channel.addUser(user);
299
+ channel.onMessage((req, res) => {
300
+ expect(req.client.clientId).toEqual(enums_1.PondSenders.POND_CHANNEL);
301
+ if (req.event === 'testEvent') {
302
+ messages.push(req.event);
303
+ res.send('test', { test: 'test' });
304
+ }
305
+ });
306
+ channel.subscribeToMessages('test1', (message) => {
307
+ messages.push(message.event);
308
+ });
309
+ expect(() => channel.broadcast('testEvent', { test: 'test' })).toThrow(); // the handler always replies to a testEvent message
310
+ // since the sender is the channel itself, the send function throws an error
311
+ expect(messages).toHaveLength(1);
312
+ expect(messages).toEqual(['testEvent']); // this is because the broadcaster is the channel itself
313
+ });
314
+ it('should be able to hook up to the onMessage callback and send a message to the sender', () => {
315
+ const { channel, user } = (0, exports.createChannel)('test');
316
+ const messages = [];
317
+ channel.addUser(user);
318
+ channel.onMessage((req, res) => {
319
+ expect(req.client.clientId).toEqual('test');
320
+ if (req.event === 'testEvent')
321
+ res.send('test', { test: 'test' });
322
+ });
323
+ channel.subscribeToMessages('test', (message) => {
324
+ messages.push(message.event);
325
+ });
326
+ channel.broadcast('testEvent', { test: 'test' }, 'test');
327
+ expect(messages).toHaveLength(2);
328
+ expect(messages).toEqual(['test', 'testEvent']); // this is because the broadcast is made on behalf of the user
329
+ // the message sent in the handler always gets to the user before the message sent by the broadcaster
330
+ });
331
+ it('should not call further onMessage callbacks if the callback rejects', (done) => {
332
+ const { channel, user } = (0, exports.createChannel)('test');
333
+ let messages = [];
334
+ channel.addUser(user);
335
+ channel.onMessage((req, res) => {
336
+ if (req.event === 'testEvent')
337
+ return;
338
+ messages.push(req.event);
339
+ res.reject('error message', 213);
340
+ });
341
+ channel.onMessage((req, res) => {
342
+ if (req.event === 'testEvent') {
343
+ messages.push(req.event);
344
+ res.send('test', { test: 'onMessage' });
345
+ }
346
+ });
347
+ channel.subscribeToMessages('test', (message) => {
348
+ messages.push(message.event);
349
+ });
350
+ channel.sendTo('test', { test: 'test' }, 'test', ['test']);
351
+ expect(messages).toHaveLength(2);
352
+ // The first callback rejects the message, so the second callback is never called
353
+ // The user does not receive the message either because the message was rejected
354
+ // however, the user does receive the rejection message so
355
+ // 1 rejection callback receives the message + 1 rejection message
356
+ expect(messages).toEqual(['test', 'error']);
357
+ messages = [];
358
+ channel.sendTo('testEvent', { test: 'broadcast' }, 'test', ['test']);
359
+ // The first callback does not reject the message, so the second callback is called
360
+ // The user receives the message because the message was not rejected
361
+ // however, the user also receives a second message because the second callback sends a message as well
362
+ // 1 on message callback , 1 initial message, 1 message from the callback
363
+ setTimeout(() => {
364
+ expect(messages).toHaveLength(3); // Sometimes because of the async nature of the tests, the messages are not received in the correct order
365
+ expect(messages).toEqual(['testEvent', 'test', 'testEvent']);
366
+ done();
367
+ }, 10);
368
+ // when sendTo is called for a user that is not in the channel,
369
+ // An error will be thrown
370
+ expect(() => {
371
+ channel.sendTo('testEvent', { test: 'test' }, 'test3', ['test']);
372
+ }).toThrow();
373
+ expect(() => {
374
+ channel.sendTo('testEvent', { test: 'test' }, 'test', ['test3']);
375
+ }).toThrow();
376
+ });
377
+ });
@@ -0,0 +1,26 @@
1
+ import {PondAssigns, PondPresence} from "./types";
2
+ import {Channel} from "./channel";
3
+ import {PondResponse} from "./pondResponse";
4
+ import {default_t} from "../pondBase";
5
+
6
+ export declare type ChannelEvent = {
7
+ client: {
8
+ clientId: string;
9
+ clientAssigns: PondAssigns;
10
+ clientPresence: PondPresence;
11
+ };
12
+ channel: Channel;
13
+ payload: default_t;
14
+ event: string;
15
+ };
16
+ export declare type IncomingMiddlewareRequest = {
17
+ channelName: string;
18
+ event: string;
19
+ message: default_t;
20
+ client: {
21
+ clientId: string;
22
+ clientAssigns: PondAssigns;
23
+ clientPresence: PondPresence;
24
+ };
25
+ };
26
+ export declare type ChannelHandler = (req: IncomingMiddlewareRequest, res: PondResponse, channel: Channel) => void | Promise<void>;
@@ -0,0 +1,36 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ChannelMiddleware = void 0;
4
+ const pondResponse_1 = require("./pondResponse");
5
+ class ChannelMiddleware {
6
+ constructor() {
7
+ this._handlers = [];
8
+ }
9
+ use(handler) {
10
+ this._handlers.push(handler);
11
+ }
12
+ merge(second) {
13
+ this._handlers.push(...second._handlers);
14
+ }
15
+ run(data, action) {
16
+ const middlewareFunctions = this._handlers.concat();
17
+ const request = {
18
+ channelName: data.channel.name,
19
+ message: data.payload,
20
+ event: data.event,
21
+ client: data.client
22
+ };
23
+ const response = new pondResponse_1.ChannelResponse(data.client.clientId, data.channel, action);
24
+ const next = () => {
25
+ const handler = middlewareFunctions.shift();
26
+ if (!handler)
27
+ return action(false);
28
+ void handler(request, response, data.channel);
29
+ if (response.hasExecuted)
30
+ return;
31
+ next();
32
+ };
33
+ next();
34
+ }
35
+ }
36
+ exports.ChannelMiddleware = ChannelMiddleware;
@@ -0,0 +1,90 @@
1
+ /// <reference types="node" />
2
+ /// <reference types="node" />
3
+ /// <reference types="node" />
4
+ import {IncomingConnection} from "./types";
5
+ import internal from "stream";
6
+ import {WebSocket, WebSocketServer} from "ws";
7
+ import {IncomingMessage} from "http";
8
+ import {BaseClass, default_t, PondPath, Resolver, ResponsePicker} from "../pondBase";
9
+ import {PondChannel, PondChannelHandler} from "./pondChannel";
10
+ import {ChannelInfo} from "./channel";
11
+ import {PondResponse} from "./pondResponse";
12
+
13
+ export declare type EndpointHandler = (req: IncomingConnection, res: PondResponse<ResponsePicker.POND>, endpoint: Endpoint) => void;
14
+
15
+ export declare class Endpoint extends BaseClass {
16
+ constructor(server: WebSocketServer, handler: EndpointHandler);
17
+
18
+ /**
19
+ * @desc Accepts a new socket join request to the room provided using the handler function to authorise the socket
20
+ * @param path - the pattern to accept || can also be a regex
21
+ * @param handler - the handler function to authenticate the socket
22
+ *
23
+ * @example
24
+ * const channel = endpoint.createChannel('channel:*', (req, res) => {
25
+ * const isAdmin = req.clientAssigns.admin;
26
+ * if (!isAdmin)
27
+ * return res.reject("You are not an admin");
28
+ *
29
+ * res.accept({
30
+ * assign: {
31
+ * admin: true,
32
+ * joinedDate: new Date()
33
+ * },
34
+ * presence: {state: 'online'},
35
+ * channelData: {private: true}
36
+ * });
37
+ * });
38
+ *
39
+ * channel.on('ping', (req, res, channel) => {
40
+ * const users = channel.getPresence();
41
+ * res.assign({
42
+ * assign: {
43
+ * pingDate: new Date(),
44
+ * users: users.length
45
+ * }
46
+ * });
47
+ * })
48
+ */
49
+ createChannel(path: PondPath, handler: PondChannelHandler): PondChannel;
50
+
51
+ /**
52
+ * @desc Authenticates the client to the endpoint
53
+ * @param request - Incoming request
54
+ * @param socket - Incoming socket
55
+ * @param head - Incoming head
56
+ * @param data - Incoming the data resolved from the handler
57
+ */
58
+ authoriseConnection(request: IncomingMessage, socket: internal.Duplex, head: Buffer, data: Resolver): void;
59
+
60
+ /**
61
+ * @desc Closes a client connection to the endpoint.
62
+ * @param clientId - The id of the client to close the connection to.
63
+ */
64
+ closeConnection(clientId: string): void;
65
+
66
+ /**
67
+ * @desc Sends a message to a client on the endpoint.
68
+ * @param clientId - The id of the client to send the message to.
69
+ * @param event - The event to send the message with.
70
+ * @param message - The message to send.
71
+ */
72
+ send(clientId: string | string[], event: string, message: default_t): void;
73
+
74
+ /**
75
+ * @desc lists all the channels in the endpoint
76
+ */
77
+ listChannels(): ChannelInfo[];
78
+
79
+ /**
80
+ * @desc lists all the clients in the endpoint
81
+ */
82
+ listConnections(): WebSocket[];
83
+
84
+ /**
85
+ * @desc Broadcasts a message to all clients in the endpoint.
86
+ * @param event - The event to broadcast.
87
+ * @param message - The message to broadcast.
88
+ */
89
+ broadcast(event: string, message: default_t): void;
90
+ }