@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.
- package/.eslintrc.js +28 -0
- package/.idea/modules.xml +8 -0
- package/.idea/pondsocket.iml +12 -0
- package/LICENSE +674 -0
- package/base.d.ts +1 -0
- package/base.js +17 -0
- package/client.d.ts +1 -0
- package/client.js +17 -0
- package/index.d.ts +1 -0
- package/index.js +17 -0
- package/jest.config.js +11 -0
- package/package.json +48 -0
- package/pondBase/baseClass.d.ts +37 -0
- package/pondBase/baseClass.js +111 -0
- package/pondBase/baseClass.test.js +73 -0
- package/pondBase/enums.d.ts +9 -0
- package/pondBase/enums.js +14 -0
- package/pondBase/index.d.ts +6 -0
- package/pondBase/index.js +22 -0
- package/pondBase/pondBase.d.ts +41 -0
- package/pondBase/pondBase.js +60 -0
- package/pondBase/pondBase.test.js +101 -0
- package/pondBase/pubSub.d.ts +73 -0
- package/pondBase/pubSub.js +138 -0
- package/pondBase/pubSub.test.js +309 -0
- package/pondBase/simpleBase.d.ts +131 -0
- package/pondBase/simpleBase.js +211 -0
- package/pondBase/simpleBase.test.js +153 -0
- package/pondBase/types.d.ts +2 -0
- package/pondBase/types.js +2 -0
- package/pondClient/channel.d.ts +66 -0
- package/pondClient/channel.js +152 -0
- package/pondClient/index.d.ts +2 -0
- package/pondClient/index.js +18 -0
- package/pondClient/socket.d.ts +42 -0
- package/pondClient/socket.js +116 -0
- package/pondSocket/channel.d.ts +134 -0
- package/pondSocket/channel.js +287 -0
- package/pondSocket/channel.test.js +377 -0
- package/pondSocket/channelMiddleWare.d.ts +26 -0
- package/pondSocket/channelMiddleWare.js +36 -0
- package/pondSocket/endpoint.d.ts +90 -0
- package/pondSocket/endpoint.js +323 -0
- package/pondSocket/endpoint.test.js +513 -0
- package/pondSocket/enums.d.ts +19 -0
- package/pondSocket/enums.js +25 -0
- package/pondSocket/index.d.ts +7 -0
- package/pondSocket/index.js +23 -0
- package/pondSocket/pondChannel.d.ts +79 -0
- package/pondSocket/pondChannel.js +219 -0
- package/pondSocket/pondChannel.test.js +430 -0
- package/pondSocket/pondResponse.d.ts +25 -0
- package/pondSocket/pondResponse.js +120 -0
- package/pondSocket/pondSocket.d.ts +47 -0
- package/pondSocket/pondSocket.js +94 -0
- package/pondSocket/server.test.js +136 -0
- package/pondSocket/socketMiddleWare.d.ts +6 -0
- package/pondSocket/socketMiddleWare.js +32 -0
- package/pondSocket/types.d.ts +74 -0
- package/pondSocket/types.js +2 -0
- package/socket.d.ts +1 -0
- package/socket.js +17 -0
- package/tsconfig.eslint.json +5 -0
- 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
|
+
}
|