@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,219 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.PondChannel = void 0;
4
+ const pondBase_1 = require("../pondBase");
5
+ const channel_1 = require("./channel");
6
+ const enums_1 = require("./enums");
7
+ const pondResponse_1 = require("./pondResponse");
8
+ const channelMiddleWare_1 = require("./channelMiddleWare");
9
+ class PondChannel extends pondBase_1.BaseClass {
10
+ constructor(path, handler) {
11
+ super();
12
+ this._channels = new pondBase_1.PondBase();
13
+ this._handler = handler;
14
+ this.path = path;
15
+ this._subscriptions = {};
16
+ this._middleware = new channelMiddleWare_1.ChannelMiddleware();
17
+ }
18
+ /**
19
+ * @desc Gets a list of all the channels in the endpoint.
20
+ */
21
+ get info() {
22
+ return this._channels.map(channel => channel.info);
23
+ }
24
+ /**
25
+ * @desc Sends a message to a client
26
+ * @param socket - The socket to send the message to
27
+ * @param message - The message to send
28
+ */
29
+ static _sendMessage(socket, message) {
30
+ socket.send(JSON.stringify(message));
31
+ }
32
+ /**
33
+ * @desc A listener for a channel event
34
+ * @param event - The event to listen for, can be a regex
35
+ * @param callback - The callback to call when the event is received
36
+ */
37
+ on(event, callback) {
38
+ this._buildHandler(event, callback);
39
+ }
40
+ /**
41
+ * @desc Add new user to channel
42
+ * @param user - The user to add to the channel
43
+ * @param channelName - The name of the channel
44
+ * @param joinParams - The params to join the channel with
45
+ */
46
+ addUser(user, channelName, joinParams) {
47
+ const document = this._getChannel(channelName);
48
+ const channel = document.doc;
49
+ const resolver = (newAssigns, message) => {
50
+ const { assigns, presence, channelData } = newAssigns;
51
+ this._subscriptions[user.clientId] = this._subscriptions[user.clientId] || [];
52
+ const sub = channel.subscribeToMessages(user.clientId, (event) => {
53
+ PondChannel._sendMessage(user.socket, event);
54
+ });
55
+ this._subscriptions[user.clientId].push({ name: channelName, sub });
56
+ channel.addUser({
57
+ presence: presence,
58
+ assigns: assigns,
59
+ channelData: channelData,
60
+ client: user
61
+ });
62
+ if (message)
63
+ channel.sendTo(message.event, message.payload, enums_1.PondSenders.POND_CHANNEL, [user.clientId]);
64
+ };
65
+ const response = new pondResponse_1.PondChannelResponse(user, resolver);
66
+ const resolved = this.generateEventRequest(this.path, channelName);
67
+ if (resolved === null) {
68
+ document.removeDoc();
69
+ return response.reject(`Invalid channel name: ${channelName}`);
70
+ }
71
+ const request = {
72
+ joinParams, ...resolved,
73
+ clientId: user.clientId, channelName,
74
+ clientAssigns: user.assigns
75
+ };
76
+ this._handler(request, response, channel);
77
+ if (channel.presence.length === 0)
78
+ document.removeDoc();
79
+ if (!response.isResolved)
80
+ throw new Error("PondChannel: Response was not resolved");
81
+ }
82
+ /**
83
+ * @desc Sends a message to a channel in the endpoint.
84
+ * @param channelName - The name of the channel to send the message to.
85
+ * @param event - The event to send the message with.
86
+ * @param message - The message to send.
87
+ */
88
+ broadcastToChannel(channelName, event, message) {
89
+ this._execute(channelName, channel => {
90
+ channel.broadcast(event, message, enums_1.PondSenders.POND_CHANNEL);
91
+ });
92
+ }
93
+ /**
94
+ * @desc Closes a client connection to a channel in the endpoint.
95
+ * @param channelName - The name of the channel to close the connection to.
96
+ * @param clientId - The id of the client to close the connection to.
97
+ */
98
+ closeFromChannel(channelName, clientId) {
99
+ this._execute(channelName, channel => {
100
+ this._removeSubscriptions(clientId, channelName);
101
+ channel.removeUser(clientId);
102
+ });
103
+ }
104
+ /**
105
+ * @desc Modify the presence of a client in a channel on the endpoint.
106
+ * @param channelName - The name of the channel to modify the presence of.
107
+ * @param clientId - The id of the client to modify the presence of.
108
+ * @param assigns - The assigns to modify the presence with.
109
+ */
110
+ modifyPresence(channelName, clientId, assigns) {
111
+ this._execute(channelName, channel => {
112
+ channel.updateUser(clientId, assigns.presence || {}, assigns.assigns || {});
113
+ });
114
+ }
115
+ /**
116
+ * @desc Gets the information of the channel
117
+ * @param channelName - The name of the channel to get the information of.
118
+ */
119
+ getChannelInfo(channelName) {
120
+ return this._execute(channelName, channel => {
121
+ return channel.info;
122
+ });
123
+ }
124
+ /**
125
+ * @desc Sends a message to the channel
126
+ * @param channelName - The name of the channel to send the message to.
127
+ * @param clientId - The clientId to send the message to, can be an array of clientIds
128
+ * @param event - The event to send the message to
129
+ * @param message - The message to send
130
+ */
131
+ send(channelName, clientId, event, message) {
132
+ const clients = Array.isArray(clientId) ? clientId : [clientId];
133
+ this._execute(channelName, channel => {
134
+ channel.sendTo(event, message, enums_1.PondSenders.POND_CHANNEL, clients);
135
+ });
136
+ }
137
+ /**
138
+ * @desc Searches for a channel in the endpoint.
139
+ * @param channelName - The name of the channel to search for.
140
+ */
141
+ getChannel(channelName) {
142
+ var _a;
143
+ return ((_a = this._channels.get(channelName)) === null || _a === void 0 ? void 0 : _a.doc) || null;
144
+ }
145
+ /**
146
+ * @desc removes a user from all channels
147
+ * @param clientId - The id of the client to remove
148
+ */
149
+ removeUser(clientId) {
150
+ if (this._subscriptions[clientId]) {
151
+ this._subscriptions[clientId].forEach(doc => doc.sub.unsubscribe());
152
+ delete this._subscriptions[clientId];
153
+ for (const channel of this._channels)
154
+ if (channel.doc.hasUser(clientId))
155
+ channel.doc.removeUser(clientId);
156
+ }
157
+ }
158
+ /**
159
+ * @desc Executes a function on a channel in the endpoint.
160
+ * @param channelName - The name of the channel to execute the function on.
161
+ * @param handler - The function to execute on the channel.
162
+ * @private
163
+ */
164
+ _execute(channelName, handler) {
165
+ const newChannel = this.getChannel(channelName);
166
+ if (newChannel)
167
+ return handler(newChannel);
168
+ throw new Error(`Channel ${channelName} does not exist`);
169
+ }
170
+ /**
171
+ * @desc Creates a new channel in the endpoint.
172
+ * @param channelName - The name of the channel to create.
173
+ * @private
174
+ */
175
+ _getChannel(channelName) {
176
+ return this._channels.getOrCreate(channelName, doc => {
177
+ return new channel_1.Channel(channelName, this._middleware, doc.removeDoc.bind(doc));
178
+ });
179
+ }
180
+ /**
181
+ * @desc Removes a subscription from a user
182
+ * @param clientId - The id of the client to remove the subscription from
183
+ * @param channelName - The name of the channel to remove the subscription from
184
+ * @private
185
+ */
186
+ _removeSubscriptions(clientId, channelName) {
187
+ const clients = Array.isArray(clientId) ? clientId : [clientId];
188
+ clients.forEach(client => {
189
+ const subs = this._subscriptions[client];
190
+ if (subs) {
191
+ const sub = subs.find(s => s.name === channelName);
192
+ if (sub) {
193
+ sub.sub.unsubscribe();
194
+ subs.splice(subs.indexOf(sub), 1);
195
+ }
196
+ }
197
+ });
198
+ }
199
+ /**
200
+ * @desc Builds an event handler for a channel
201
+ * @param event - The event to build the handler for
202
+ * @param callback - The callback to build the handler for
203
+ * @private
204
+ */
205
+ _buildHandler(event, callback) {
206
+ this._middleware.use((data, res, channel) => {
207
+ const info = this.generateEventRequest(event, data.event);
208
+ if (!info)
209
+ return;
210
+ const req = {
211
+ ...data,
212
+ params: info.params,
213
+ query: info.query,
214
+ };
215
+ callback(req, res, channel);
216
+ });
217
+ }
218
+ }
219
+ exports.PondChannel = PondChannel;
@@ -0,0 +1,430 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const pondChannel_1 = require("./pondChannel");
4
+ const channel_test_1 = require("./channel.test");
5
+ const enums_1 = require("./enums");
6
+ const channel_1 = require("./channel");
7
+ const pondBase_1 = require("../pondBase");
8
+ const createPondChannel = (path, handler) => {
9
+ path = path || "/pond";
10
+ handler = handler || jest.fn();
11
+ return new pondChannel_1.PondChannel(path, handler);
12
+ };
13
+ describe('PondChannel', () => {
14
+ it('should exists', () => {
15
+ expect(pondChannel_1.PondChannel).toBeDefined();
16
+ });
17
+ it('should be a class', () => {
18
+ expect(pondChannel_1.PondChannel).toBeInstanceOf(Function);
19
+ });
20
+ it('should be able to get the info of a channel', () => {
21
+ const pondChannel = createPondChannel();
22
+ const { channel } = (0, channel_test_1.createChannel)('/channel');
23
+ // Because we want to get the info of the channel, we need to add it to the pondChannel
24
+ // for testing purposes
25
+ pondChannel['_channels'].set(channel.name, channel);
26
+ expect(pondChannel.info).toEqual([channel.info]);
27
+ });
28
+ it('should be able ot add a user', () => {
29
+ const pondChannel = createPondChannel('/test');
30
+ expect(() => pondChannel.getChannelInfo('/test')).toThrow(); // This throws an error because the channel does not exist anymore
31
+ const testUser = {
32
+ clientId: 'test', assigns: {}, socket: {
33
+ send: jest.fn(), on: jest.fn(),
34
+ }
35
+ };
36
+ pondChannel.addUser(testUser, '/balls', {}); // When joining a channel the channel name must be matchable by the PondPath
37
+ // /balls !== /test so we expect an error message to be sent to the client from the pond channel
38
+ expect(testUser.socket.send).toBeCalledWith(JSON.stringify({
39
+ action: enums_1.ServerActions.ERROR, event: "JOIN_REQUEST_ERROR", channelName: enums_1.PondSenders.POND_CHANNEL, payload: {
40
+ message: `Invalid channel name: /balls`, code: 403,
41
+ }
42
+ }));
43
+ expect(pondChannel.getChannel('/test')).toBeNull(); // The channel does not exist yet
44
+ expect(() => pondChannel.addUser(testUser, '/test', {}))
45
+ .toThrow(); // handler function does not act on the incoming connection, it is just a jest mock function
46
+ // /test === /test so we expect the user to be added to the channel
47
+ // if the channel does not exist, it will be created
48
+ expect(pondChannel.getChannel('/test')).toBeNull(); // The channel does not exist yet
49
+ const newPondChannel = new pondChannel_1.PondChannel('/test', (_, res) => {
50
+ res.accept(); // here we accept the connection
51
+ });
52
+ newPondChannel.addUser(testUser, '/test', {});
53
+ expect(newPondChannel.getChannel('/test')).not.toBeNull(); // The channel does exist now
54
+ const newChannel = newPondChannel.getChannel('/test');
55
+ expect(newChannel).toBeDefined();
56
+ expect(newChannel).toBeInstanceOf(channel_1.Channel);
57
+ expect(newChannel === null || newChannel === void 0 ? void 0 : newChannel.name).toBe('/test');
58
+ const rejectPondChannel = new pondChannel_1.PondChannel('/:test', (req, res) => {
59
+ if (req.params.test === 'balls')
60
+ res.reject(); // here we reject the connection
61
+ else if (req.params.test === 'rejectWithMessage')
62
+ res.reject('test', 69420); // here we reject the connection with a message and a status code
63
+ });
64
+ testUser.socket.send.mockClear(); // Clear the mock function
65
+ rejectPondChannel.addUser(testUser, '/rejectWithMessage', {});
66
+ expect(rejectPondChannel.getChannel('/rejectWithMessage')).toBeNull(); // The channel does not exist as it rejects the connection and deletes itself
67
+ expect(testUser.socket.send).toBeCalledWith(JSON.stringify({
68
+ action: enums_1.ServerActions.ERROR, event: "JOIN_REQUEST_ERROR", channelName: enums_1.PondSenders.POND_CHANNEL, payload: {
69
+ message: `test`, code: 69420,
70
+ }
71
+ }));
72
+ rejectPondChannel.addUser(testUser, '/balls', {});
73
+ expect(rejectPondChannel.getChannel('/balls')).toBeNull(); // The channel does not exist as it rejects the connection and deletes itself
74
+ expect(testUser.socket.send).toBeCalledWith(JSON.stringify({
75
+ action: enums_1.ServerActions.ERROR, event: "JOIN_REQUEST_ERROR", channelName: enums_1.PondSenders.POND_CHANNEL, payload: {
76
+ message: `Unauthorized join request`, code: 403,
77
+ }
78
+ }));
79
+ const acceptPondChannel = new pondChannel_1.PondChannel('/:test', (_, res) => {
80
+ res.send('test', {
81
+ test: 'test'
82
+ }); // here we send a message after accepting the connection
83
+ });
84
+ testUser.socket.send.mockClear(); // Clear the mock function
85
+ const messages = [];
86
+ testUser.socket.send = (message) => {
87
+ messages.push(JSON.parse(message));
88
+ };
89
+ acceptPondChannel.addUser(testUser, '/test', {});
90
+ expect(messages).toEqual([{
91
+ action: enums_1.ServerActions.PRESENCE, event: pondBase_1.PondBaseActions.ADD_TO_POND, channelName: '/test', payload: {
92
+ presence: [{ id: 'test' }], change: { id: 'test' }
93
+ }
94
+ }, {
95
+ action: enums_1.ServerActions.MESSAGE, event: "test", channelName: '/test', payload: {
96
+ test: 'test'
97
+ }
98
+ }]);
99
+ });
100
+ it('should be able to receive subscriptions', () => {
101
+ const pond = new pondChannel_1.PondChannel('/:test', (_, res) => {
102
+ res.accept();
103
+ });
104
+ //when events are added, they are added sequentially
105
+ // this means the first handler is called first, and the second handler is called second...
106
+ // so if a regex handler should be added last so everything else can be tried first
107
+ let narrowedMessageCount = 0;
108
+ pond.on('event:test', (req, res) => {
109
+ narrowedMessageCount++;
110
+ if (req.params.test === 'balls')
111
+ expect(() => res.reject()).toThrow(); // this would throw an error because the sender is the pond channel itself
112
+ // we know this because we wrote the test that way, to confirm who the sender is you can check the req.client property
113
+ else if (req.params.test === 'rejectWithMessage')
114
+ res.reject('test', 69420); // here we reject the connection with a message and a status code
115
+ else if (req.params.test === 'accept')
116
+ res.accept();
117
+ else if (req.params.test === 'send')
118
+ res.send('test', {
119
+ test: 'test'
120
+ });
121
+ else if (req.params.test === 'acceptWithPresence')
122
+ res.accept({
123
+ presence: {
124
+ status: 'online'
125
+ }
126
+ });
127
+ else if (req.params.test === 'acceptWithAssigns')
128
+ res.accept({
129
+ assigns: {
130
+ test: 'test'
131
+ }
132
+ });
133
+ else if (req.params.test === 'acceptWithChannelData')
134
+ res.accept({
135
+ channelData: {
136
+ test: 'test'
137
+ }
138
+ });
139
+ else if (req.params.test === 'acceptWithAll')
140
+ res.accept({
141
+ presence: {
142
+ status: 'offline'
143
+ },
144
+ assigns: {
145
+ test: 'test'
146
+ },
147
+ channelData: {
148
+ test: 'test2'
149
+ }
150
+ });
151
+ });
152
+ let encompassingMessageCount = 0;
153
+ pond.on(/(.*?)/, (req, res) => {
154
+ console.log(req.event);
155
+ expect(req.params).toEqual({});
156
+ // with all encompassing regex, the params should be empty
157
+ // also if there are no other matching handlers, they would be called
158
+ // hey can be used for loggers or actions that should be done on all events
159
+ encompassingMessageCount++;
160
+ res.send('fallback', {
161
+ message: 'This is a fallback route'
162
+ });
163
+ });
164
+ let userMessageCount = 0;
165
+ const sender = () => {
166
+ userMessageCount++;
167
+ };
168
+ // we add two users to the pond on ethe channel /pond
169
+ pond.addUser({
170
+ clientId: 'test1',
171
+ assigns: {},
172
+ socket: {
173
+ send: sender,
174
+ on: jest.fn(),
175
+ }
176
+ }, '/pond', {});
177
+ pond.addUser({
178
+ clientId: 'test2',
179
+ assigns: {},
180
+ socket: {
181
+ send: sender,
182
+ on: jest.fn(),
183
+ }
184
+ }, '/pond', {});
185
+ expect(pond.getChannelInfo('/pond')).toEqual({
186
+ name: '/pond',
187
+ channelData: {},
188
+ presence: [{
189
+ id: 'test1',
190
+ }, {
191
+ id: 'test2',
192
+ }],
193
+ assigns: [{}, {}],
194
+ });
195
+ expect(encompassingMessageCount).toBe(0); // The on function is only called when a message is sent
196
+ expect(userMessageCount).toBe(3); // first user gets their join event and the seconds join,
197
+ encompassingMessageCount = 0;
198
+ userMessageCount = 0;
199
+ pond.broadcastToChannel('/pond', 'eventballs', {
200
+ test: 'test'
201
+ }); // When we broadcast to a channel, the pondChannel itself is the sender of the message
202
+ // so if we try to act on the connection, it would throw an error as there is no one to send the rejection to
203
+ // act on the connection = accept, reject, send
204
+ expect(narrowedMessageCount).toBe(1); // the broadcast event is caught by the narrowed handler
205
+ expect(encompassingMessageCount).toBe(0); // the broadcast event is not caught by the encompassing handler
206
+ expect(userMessageCount).toBe(0); // because the :test param is balls which was rejected by the handler, the message is not sent to the client
207
+ encompassingMessageCount = 0;
208
+ userMessageCount = 0;
209
+ narrowedMessageCount = 0;
210
+ const channel = pond.getChannel('/pond');
211
+ expect(channel).toBeInstanceOf(channel_1.Channel);
212
+ expect(channel).not.toBeNull();
213
+ // since the PondChannel has no way to send messages on behalf of a client we get the channel from the pond and send the message directly to the channel
214
+ channel === null || channel === void 0 ? void 0 : channel.broadcast('eventrejectWithMessage', {}, 'test1'); // here we broadcast to the channel, but we specify the sender as test1
215
+ expect(narrowedMessageCount).toBe(1); // the send event is caught by the narrowed handler
216
+ expect(encompassingMessageCount).toBe(0); // the send event is not caught by the encompassing handler
217
+ expect(userMessageCount).toBe(1); // because the :test param is rejectWithMessage which was rejected by the handler with a message, the message is not sent to the client
218
+ // a rejection message should be sent back to the emitter of the event in this case, the user test1
219
+ encompassingMessageCount = 0;
220
+ userMessageCount = 0;
221
+ narrowedMessageCount = 0;
222
+ channel === null || channel === void 0 ? void 0 : channel.broadcast('eventaccept', {
223
+ test: 'test'
224
+ });
225
+ expect(narrowedMessageCount).toBe(1); // the broadcast event is caught by the narrowed handler
226
+ expect(encompassingMessageCount).toBe(0); // the broadcast event is not caught by the encompassing handler
227
+ expect(userMessageCount).toBe(2); // because the :test param = accept which was accepted by the handler, the message is sent to the client
228
+ encompassingMessageCount = 0;
229
+ userMessageCount = 0;
230
+ narrowedMessageCount = 0;
231
+ channel === null || channel === void 0 ? void 0 : channel.broadcast('eventsend', {
232
+ test: 'test'
233
+ }, 'test2'); // the message is sent on behalf of test2
234
+ expect(narrowedMessageCount).toBe(1); // the broadcast event is caught by the narrowed handler
235
+ expect(encompassingMessageCount).toBe(0); // messages sent in the handlers are sent directly to the client and are not considered broadcasts
236
+ expect(userMessageCount).toBe(3); // since we broadcast a message to all users = 2 we have 2 messages + the message sent to test2 from the narrowed handler
237
+ encompassingMessageCount = 0;
238
+ userMessageCount = 0;
239
+ narrowedMessageCount = 0;
240
+ channel === null || channel === void 0 ? void 0 : channel.sendTo('eventacceptWithPresence', {
241
+ test: 'test'
242
+ }, 'test2', 'test1');
243
+ expect(narrowedMessageCount).toBe(1); // the event is caught by the narrowed handler
244
+ expect(encompassingMessageCount).toBe(0); // the event is not caught by the encompassing handler because a response has already been sent
245
+ expect(userMessageCount).toBe(3); // the message is sent since it was accepted in the handler
246
+ // the user's presence is modified and sent to all users 1 message + 2 presence update messages
247
+ // however we have also modified the presence of the sender
248
+ expect(pond.getChannelInfo('/pond')).toEqual({
249
+ name: '/pond',
250
+ channelData: {},
251
+ presence: [{
252
+ id: 'test1',
253
+ }, {
254
+ id: 'test2',
255
+ status: 'online'
256
+ }],
257
+ assigns: [{}, {}],
258
+ });
259
+ encompassingMessageCount = 0;
260
+ userMessageCount = 0;
261
+ narrowedMessageCount = 0;
262
+ channel === null || channel === void 0 ? void 0 : channel.sendTo('eventacceptWithChannelData', {
263
+ test: 'test'
264
+ }, 'test2', 'test1');
265
+ expect(narrowedMessageCount).toBe(1); // the send event is caught by the narrowed handler
266
+ expect(encompassingMessageCount).toBe(0); // the (send) message does not modify the user presence, which is not caught by the encompassing handler
267
+ expect(userMessageCount).toBe(1); // the message is sent since it was accepted in the handler
268
+ // however we have also modified the channel data
269
+ expect(pond.getChannelInfo('/pond')).toEqual({
270
+ name: '/pond',
271
+ channelData: { test: 'test' },
272
+ presence: [{
273
+ id: 'test1',
274
+ }, {
275
+ id: 'test2',
276
+ status: 'online'
277
+ }],
278
+ assigns: [{}, {}],
279
+ });
280
+ encompassingMessageCount = 0;
281
+ userMessageCount = 0;
282
+ narrowedMessageCount = 0;
283
+ channel === null || channel === void 0 ? void 0 : channel.sendTo('eventacceptWithAssigns', {
284
+ test: 'test',
285
+ }, 'test2', 'test1');
286
+ expect(narrowedMessageCount).toBe(1); // the send event is caught by the narrowed handler
287
+ expect(encompassingMessageCount).toBe(0); // the (send) message does not modify the user presence, which is not caught by the encompassing handler
288
+ expect(userMessageCount).toBe(1); // the message is sent since it was accepted in the handler
289
+ // however we have also modified the assigns
290
+ expect(pond.getChannelInfo('/pond')).toEqual({
291
+ name: '/pond',
292
+ channelData: { test: 'test' },
293
+ presence: [{
294
+ id: 'test1',
295
+ }, {
296
+ id: 'test2',
297
+ status: 'online'
298
+ }],
299
+ assigns: [{}, { test: 'test' }],
300
+ });
301
+ encompassingMessageCount = 0;
302
+ userMessageCount = 0;
303
+ narrowedMessageCount = 0;
304
+ pond.modifyPresence('/pond', 'test2', {
305
+ presence: {}
306
+ });
307
+ // No messages are sent because the presence is not modified
308
+ expect(narrowedMessageCount).toBe(0);
309
+ expect(encompassingMessageCount).toBe(0);
310
+ expect(userMessageCount).toBe(0);
311
+ expect(pond.getChannelInfo('/pond')).toEqual({
312
+ name: '/pond',
313
+ channelData: { test: 'test' },
314
+ presence: [{
315
+ id: 'test1',
316
+ }, {
317
+ id: 'test2',
318
+ status: 'online'
319
+ }],
320
+ assigns: [{}, { test: 'test' }],
321
+ });
322
+ encompassingMessageCount = 0;
323
+ userMessageCount = 0;
324
+ narrowedMessageCount = 0;
325
+ pond.closeFromChannel('/pond', 'test2');
326
+ // since a user has left the channel a message is sent to all users
327
+ expect(narrowedMessageCount).toBe(0); // The on handler does not listen to presence change events
328
+ expect(encompassingMessageCount).toBe(0); // The on handler does not listen to presence change events
329
+ expect(userMessageCount).toBe(1); //only the user left in the channel receives the presence change message
330
+ encompassingMessageCount = 0;
331
+ userMessageCount = 0;
332
+ narrowedMessageCount = 0;
333
+ expect(pond.getChannelInfo('/pond')).toEqual({
334
+ name: '/pond',
335
+ channelData: { test: 'test' },
336
+ presence: [{
337
+ id: 'test1',
338
+ }],
339
+ assigns: [{}],
340
+ });
341
+ });
342
+ it('should be capable of removing a user from a channel', () => {
343
+ const pond = new pondChannel_1.PondChannel('/test', (_, res) => {
344
+ res.accept(); // here we accept the connection
345
+ });
346
+ let userMessageCount = 0;
347
+ const sender = () => {
348
+ userMessageCount++;
349
+ };
350
+ pond.addUser({
351
+ clientId: 'test',
352
+ assigns: {},
353
+ socket: {
354
+ send: sender,
355
+ on: jest.fn(),
356
+ }
357
+ }, '/test', {});
358
+ pond.addUser({
359
+ clientId: 'test2',
360
+ assigns: {},
361
+ socket: {
362
+ send: sender,
363
+ on: jest.fn(),
364
+ }
365
+ }, '/test', {});
366
+ pond.addUser({
367
+ clientId: 'test3',
368
+ assigns: {},
369
+ socket: {
370
+ send: sender,
371
+ on: jest.fn(),
372
+ }
373
+ }, '/test', {});
374
+ expect(pond.info).toHaveLength(1);
375
+ expect(pond.getChannelInfo('/test')).toEqual({
376
+ name: '/test',
377
+ channelData: {},
378
+ presence: [{
379
+ id: 'test',
380
+ }, {
381
+ id: 'test2',
382
+ }, {
383
+ id: 'test3',
384
+ }],
385
+ assigns: [{}, {}, {}],
386
+ });
387
+ expect(pond['_subscriptions']['test']).toHaveLength(1); // the subscription holds all subscriptions a user has in multiple channels
388
+ pond.modifyPresence('/test', 'test', {
389
+ assigns: {
390
+ name: 'test'
391
+ }
392
+ });
393
+ expect(pond.getChannelInfo('/test')).toEqual({
394
+ name: '/test',
395
+ channelData: {},
396
+ presence: [{
397
+ id: 'test',
398
+ }, {
399
+ id: 'test2',
400
+ }, {
401
+ id: 'test3',
402
+ }],
403
+ assigns: [{
404
+ name: 'test'
405
+ }, {}, {}]
406
+ });
407
+ userMessageCount = 0;
408
+ pond.send('/test', ['test', 'test2', 'test3'], 'test', {
409
+ test: 'test'
410
+ });
411
+ expect(userMessageCount).toBe(3);
412
+ // when you remove a user from a pond channel the user is removed from the pond channel and all the channels within the pond
413
+ pond.removeUser('test');
414
+ expect(pond['_subscriptions']['test']).toBeUndefined();
415
+ expect(pond.info).toHaveLength(1);
416
+ pond.addUser({
417
+ clientId: 'test',
418
+ assigns: {},
419
+ socket: {
420
+ send: sender,
421
+ on: jest.fn(),
422
+ }
423
+ }, '/test', {});
424
+ expect(pond['_subscriptions']['test']).toHaveLength(1); // the subscription holds all subscriptions a user has in multiple channels
425
+ pond['_removeSubscriptions'](['test', 'test2', 'test3'], '/test');
426
+ expect(pond['_subscriptions']['test']).toHaveLength(0); // the subscription is empty but the user is still in the channel and pond
427
+ expect(pond.info).toHaveLength(1); // since the user is still in the channel the pond is still active with the channel
428
+ // it should be noted that while the user has unsubscribed they are still in the channel, they would not receive any messages
429
+ });
430
+ });
@@ -0,0 +1,25 @@
1
+ import {ResponsePicker} from "../pondBase";
2
+ import {PondMessage, SendResponse} from "./types";
3
+
4
+ export declare abstract class PondResponse<T extends ResponsePicker = ResponsePicker.CHANNEL> {
5
+ /**
6
+ * @desc Rejects the request with the given error message
7
+ * @param message - the error message
8
+ * @param errorCode - the error code
9
+ */
10
+ abstract reject(message?: string, errorCode?: number): void;
11
+
12
+ /**
13
+ * @desc Emits a direct message to the client
14
+ * @param event - the event name
15
+ * @param payload - the payload to send
16
+ * @param assigns - the data to assign to the client
17
+ */
18
+ abstract send(event: string, payload: PondMessage, assigns?: Partial<SendResponse<T>>): void;
19
+
20
+ /**
21
+ * @desc Accepts the request and optionally assigns data to the client
22
+ * @param assigns - the data to assign to the client
23
+ */
24
+ abstract accept(assigns?: Partial<SendResponse<T>>): void;
25
+ }