@eleven-am/pondsocket 0.1.11 → 0.1.13
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/dist/client/channel.js +200 -0
- package/dist/client/index.d.ts +122 -0
- package/{pondClient/socket.js → dist/client/index.js} +30 -44
- package/dist/express/index.d.ts +36 -0
- package/dist/express/index.js +16 -0
- package/dist/index.d.ts +340 -0
- package/dist/index.js +4 -0
- package/dist/server/abstracts/abstractRequest.js +40 -0
- package/dist/server/abstracts/abstractRequest.test.js +41 -0
- package/dist/server/abstracts/abstractResponse.js +6 -0
- package/dist/server/abstracts/middleware.js +38 -0
- package/dist/server/abstracts/middleware.test.js +70 -0
- package/dist/server/channel/channelEngine.js +279 -0
- package/dist/server/channel/channelEngine.test.js +377 -0
- package/dist/server/channel/channelRequest.test.js +29 -0
- package/dist/server/channel/channelResponse.test.js +134 -0
- package/dist/server/channel/eventRequest.js +18 -0
- package/dist/server/channel/eventResponse.js +141 -0
- package/dist/server/endpoint/connectionResponse.js +50 -0
- package/dist/server/endpoint/endpoint.js +269 -0
- package/dist/server/endpoint/endpoint.test.js +406 -0
- package/dist/server/endpoint/endpointResponse.test.js +43 -0
- package/dist/server/pondChannel/joinRequest.js +29 -0
- package/dist/server/pondChannel/joinResponse.js +96 -0
- package/dist/server/pondChannel/pondChannel.js +161 -0
- package/dist/server/pondChannel/pondChannelResponse.test.js +108 -0
- package/dist/server/presence/presenceEngine.js +112 -0
- package/dist/server/presence/presenceEngine.test.js +104 -0
- package/dist/server/server/pondSocket.js +122 -0
- package/{pondSocket → dist/server/server}/server.test.js +7 -21
- package/{pondBase/baseClass.js → dist/server/utils/matchPattern.js} +33 -43
- package/{pondBase/baseClass.test.js → dist/server/utils/matchPattern.test.js} +16 -25
- package/dist/server/utils/subjectUtils.js +68 -0
- package/package.json +31 -12
- package/.eslintrc.js +0 -28
- package/base.d.ts +0 -1
- package/base.js +0 -17
- package/client.d.ts +0 -1
- package/client.js +0 -17
- package/index.d.ts +0 -1
- package/index.js +0 -17
- package/jest.config.js +0 -11
- package/pondBase/baseClass.d.ts +0 -55
- package/pondBase/enums.d.ts +0 -9
- package/pondBase/enums.js +0 -14
- package/pondBase/index.d.ts +0 -6
- package/pondBase/index.js +0 -22
- package/pondBase/pondBase.d.ts +0 -41
- package/pondBase/pondBase.js +0 -60
- package/pondBase/pondBase.test.js +0 -101
- package/pondBase/pubSub.d.ts +0 -82
- package/pondBase/pubSub.js +0 -158
- package/pondBase/pubSub.test.js +0 -332
- package/pondBase/simpleBase.d.ts +0 -126
- package/pondBase/simpleBase.js +0 -211
- package/pondBase/simpleBase.test.js +0 -153
- package/pondBase/types.d.ts +0 -2
- package/pondBase/types.js +0 -2
- package/pondClient/channel.d.ts +0 -77
- package/pondClient/channel.js +0 -167
- package/pondClient/index.d.ts +0 -2
- package/pondClient/index.js +0 -18
- package/pondClient/socket.d.ts +0 -41
- package/pondSocket/channel.d.ts +0 -129
- package/pondSocket/channel.js +0 -287
- package/pondSocket/channel.test.js +0 -377
- package/pondSocket/channelMiddleWare.d.ts +0 -28
- package/pondSocket/channelMiddleWare.js +0 -36
- package/pondSocket/endpoint.d.ts +0 -90
- package/pondSocket/endpoint.js +0 -320
- package/pondSocket/endpoint.test.js +0 -490
- package/pondSocket/enums.d.ts +0 -19
- package/pondSocket/enums.js +0 -25
- package/pondSocket/index.d.ts +0 -7
- package/pondSocket/index.js +0 -23
- package/pondSocket/pondChannel.d.ts +0 -79
- package/pondSocket/pondChannel.js +0 -211
- package/pondSocket/pondChannel.test.js +0 -430
- package/pondSocket/pondResponse.d.ts +0 -25
- package/pondSocket/pondResponse.js +0 -120
- package/pondSocket/pondSocket.d.ts +0 -47
- package/pondSocket/pondSocket.js +0 -94
- package/pondSocket/socketMiddleWare.d.ts +0 -6
- package/pondSocket/socketMiddleWare.js +0 -32
- package/pondSocket/types.d.ts +0 -74
- package/pondSocket/types.js +0 -2
- package/socket.d.ts +0 -1
- package/socket.js +0 -17
- package/tsconfig.eslint.json +0 -5
- package/tsconfig.json +0 -90
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.PondChannel = void 0;
|
|
4
|
+
const joinRequest_1 = require("./joinRequest");
|
|
5
|
+
const joinResponse_1 = require("./joinResponse");
|
|
6
|
+
const middleware_1 = require("../abstracts/middleware");
|
|
7
|
+
const channelEngine_1 = require("../channel/channelEngine");
|
|
8
|
+
class PondChannel {
|
|
9
|
+
constructor() {
|
|
10
|
+
this._authorize = undefined;
|
|
11
|
+
this._channels = new Set();
|
|
12
|
+
this._middleware = new middleware_1.Middleware();
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* @desc Authorize a user to join a channel
|
|
16
|
+
* @param handler - The handler to authorize the user
|
|
17
|
+
* @example
|
|
18
|
+
* const pond = new PondChannelEngine();
|
|
19
|
+
* pond.onJoinRequest((request, response) => {
|
|
20
|
+
* if (request.event.assigns.admin)
|
|
21
|
+
* response.accept();
|
|
22
|
+
* else
|
|
23
|
+
* response.reject('You are not an admin', 403);
|
|
24
|
+
* });
|
|
25
|
+
*/
|
|
26
|
+
onJoinRequest(handler) {
|
|
27
|
+
this._authorize = handler;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* @desc Handles an event request made by a user
|
|
31
|
+
* @param event - The event to listen for
|
|
32
|
+
* @param handler - The handler to execute when the event is received
|
|
33
|
+
* @example
|
|
34
|
+
* pond.onEvent('echo', (request, response) => {
|
|
35
|
+
* response.send('echo', {
|
|
36
|
+
* message: request.payload,
|
|
37
|
+
* });
|
|
38
|
+
* });
|
|
39
|
+
*/
|
|
40
|
+
onEvent(event, handler) {
|
|
41
|
+
this._middleware.use((request, response, next) => {
|
|
42
|
+
if (request._parseQueries(event)) {
|
|
43
|
+
return handler(request, response);
|
|
44
|
+
}
|
|
45
|
+
next();
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* @desc Builds a PondChannelManager from the current engine
|
|
50
|
+
*/
|
|
51
|
+
_buildManager() {
|
|
52
|
+
return {
|
|
53
|
+
addUser: this._addUser.bind(this),
|
|
54
|
+
execute: this._execute.bind(this),
|
|
55
|
+
removeUser: this._removeUser.bind(this),
|
|
56
|
+
getChannels: this._getChannels.bind(this),
|
|
57
|
+
listChannels: this._listChannels.bind(this),
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* @desc Adds a user to a channel
|
|
62
|
+
* @param user - The user to add
|
|
63
|
+
* @private
|
|
64
|
+
*/
|
|
65
|
+
_addUser(user) {
|
|
66
|
+
const newChannel = this._getChannel(user.channelName) || this._createChannel(user.channelName);
|
|
67
|
+
if (this._authorize) {
|
|
68
|
+
const socketCache = {
|
|
69
|
+
clientId: user.clientId,
|
|
70
|
+
socket: user.socket,
|
|
71
|
+
assigns: user.assigns,
|
|
72
|
+
};
|
|
73
|
+
const request = new joinRequest_1.JoinRequest(user, newChannel);
|
|
74
|
+
const response = new joinResponse_1.JoinResponse(socketCache, newChannel);
|
|
75
|
+
void this._authorize(request, response);
|
|
76
|
+
if (!response.responseSent) {
|
|
77
|
+
throw new Error('PondChannelEngine: Response was not resolved');
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
else {
|
|
81
|
+
newChannel.addUser(user.clientId, user.assigns, (event) => {
|
|
82
|
+
user.socket.send(JSON.stringify(event));
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* @desc Removes a user from all channels
|
|
88
|
+
* @param clientId - The client id of the user to remove
|
|
89
|
+
* @private
|
|
90
|
+
*/
|
|
91
|
+
_removeUser(clientId) {
|
|
92
|
+
this._channels.forEach((channel) => {
|
|
93
|
+
channel.removeUser(clientId, true);
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* @desc Creates a new channel
|
|
98
|
+
* @param channelName - The name of the channel to create
|
|
99
|
+
* @private
|
|
100
|
+
*/
|
|
101
|
+
_createChannel(channelName) {
|
|
102
|
+
const destroyChannel = this._destroyChannel.bind(this, channelName);
|
|
103
|
+
const execute = this._middleware.run.bind(this._middleware);
|
|
104
|
+
const parentEngine = {
|
|
105
|
+
execute,
|
|
106
|
+
destroyChannel,
|
|
107
|
+
};
|
|
108
|
+
const newChannel = new channelEngine_1.ChannelEngine(channelName, parentEngine);
|
|
109
|
+
this._channels.add(newChannel);
|
|
110
|
+
return newChannel;
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* @desc Executes a function on a channel
|
|
114
|
+
* @param channelName - The name of the channel to execute the function on
|
|
115
|
+
* @param handler - The function to execute
|
|
116
|
+
* @private
|
|
117
|
+
*/
|
|
118
|
+
_execute(channelName, handler) {
|
|
119
|
+
const newChannel = this._getChannel(channelName);
|
|
120
|
+
if (newChannel) {
|
|
121
|
+
return handler(newChannel);
|
|
122
|
+
}
|
|
123
|
+
throw new Error(`GatewayEngine: Channel ${channelName} does not exist`);
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* @desc Gets a channel by name
|
|
127
|
+
* @param channelName - The name of the channel to get
|
|
128
|
+
* @private
|
|
129
|
+
*/
|
|
130
|
+
_getChannel(channelName) {
|
|
131
|
+
return Array.from(this._channels)
|
|
132
|
+
.find((channel) => channel.name === channelName);
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* @desc Destroys a channel
|
|
136
|
+
* @param channel - The name of the channel to destroy
|
|
137
|
+
* @private
|
|
138
|
+
*/
|
|
139
|
+
_destroyChannel(channel) {
|
|
140
|
+
const newChannel = this._getChannel(channel);
|
|
141
|
+
if (newChannel) {
|
|
142
|
+
this._channels.delete(newChannel);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* @desc Lists all channels
|
|
147
|
+
* @private
|
|
148
|
+
*/
|
|
149
|
+
_listChannels() {
|
|
150
|
+
return Array.from(this._channels)
|
|
151
|
+
.map((channel) => channel.name);
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* @desc Gets all channels
|
|
155
|
+
* @private
|
|
156
|
+
*/
|
|
157
|
+
_getChannels() {
|
|
158
|
+
return Array.from(this._channels);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
exports.PondChannel = PondChannel;
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const joinResponse_1 = require("./joinResponse");
|
|
4
|
+
const channelEngine_1 = require("../channel/channelEngine");
|
|
5
|
+
const channelResponse_test_1 = require("../channel/channelResponse.test");
|
|
6
|
+
const createPondResponse = () => {
|
|
7
|
+
const channelEngine = (0, channelResponse_test_1.createChannelEngine)();
|
|
8
|
+
const socket = {
|
|
9
|
+
clientId: 'sender',
|
|
10
|
+
assigns: { assign: 'assign' },
|
|
11
|
+
socket: {
|
|
12
|
+
send: jest.fn(),
|
|
13
|
+
},
|
|
14
|
+
};
|
|
15
|
+
const response = new joinResponse_1.JoinResponse(socket, channelEngine);
|
|
16
|
+
return { channelEngine,
|
|
17
|
+
socket,
|
|
18
|
+
response };
|
|
19
|
+
};
|
|
20
|
+
/* eslint-disable line-comment-position, no-inline-comments */
|
|
21
|
+
describe('pondChannelResponse', () => {
|
|
22
|
+
it('should create a new PondChannelResponse', () => {
|
|
23
|
+
const { response } = createPondResponse();
|
|
24
|
+
expect(response).toBeDefined();
|
|
25
|
+
});
|
|
26
|
+
it('should return the responseSent', () => {
|
|
27
|
+
const { response } = createPondResponse();
|
|
28
|
+
expect(response.responseSent).toEqual(false);
|
|
29
|
+
});
|
|
30
|
+
it('should accept the request', () => {
|
|
31
|
+
const { response, channelEngine, socket } = createPondResponse();
|
|
32
|
+
// spy on the channelEngine to see if the user was added
|
|
33
|
+
jest.spyOn(channelEngine, 'addUser');
|
|
34
|
+
response.accept();
|
|
35
|
+
// check if the response was sent
|
|
36
|
+
expect(response.responseSent).toEqual(true);
|
|
37
|
+
expect(response.responseSent).toEqual(true);
|
|
38
|
+
expect(channelEngine.addUser).toHaveBeenCalledWith(socket.clientId, socket.assigns, expect.any(Function));
|
|
39
|
+
expect(channelEngine.getUserData(socket.clientId)).not.toBeNull();
|
|
40
|
+
});
|
|
41
|
+
it('should reject the request', () => {
|
|
42
|
+
const { response, channelEngine, socket } = createPondResponse();
|
|
43
|
+
// spy on the channelEngine to see if the user was added
|
|
44
|
+
jest.spyOn(channelEngine, 'addUser');
|
|
45
|
+
response.reject();
|
|
46
|
+
// check if the response was sent
|
|
47
|
+
expect(response.responseSent).toEqual(true);
|
|
48
|
+
expect(channelEngine.addUser).not.toHaveBeenCalled();
|
|
49
|
+
expect(channelEngine.getUserData(socket.clientId)).toBeUndefined();
|
|
50
|
+
// also check if the socket was sent a message
|
|
51
|
+
expect(socket.socket.send).toHaveBeenCalledWith(JSON.stringify({
|
|
52
|
+
event: 'POND_ERROR',
|
|
53
|
+
payload: {
|
|
54
|
+
message: 'Request to join channel test rejected: Unauthorized request',
|
|
55
|
+
code: 403,
|
|
56
|
+
},
|
|
57
|
+
channelName: 'test',
|
|
58
|
+
action: channelEngine_1.ServerActions.ERROR,
|
|
59
|
+
}));
|
|
60
|
+
});
|
|
61
|
+
it('should send a direct message', () => {
|
|
62
|
+
const { response, channelEngine, socket } = createPondResponse();
|
|
63
|
+
// spy on the channelEngine to see if the user was added
|
|
64
|
+
jest.spyOn(channelEngine, 'addUser');
|
|
65
|
+
response.send('POND_MESSAGE', { message: 'message' });
|
|
66
|
+
// check if the response was sent
|
|
67
|
+
expect(response.responseSent).toEqual(true);
|
|
68
|
+
expect(channelEngine.addUser).toHaveBeenCalled();
|
|
69
|
+
expect(channelEngine.getUserData(socket.clientId)).toStrictEqual({ assigns: { assign: 'assign' },
|
|
70
|
+
id: 'sender',
|
|
71
|
+
presence: {} });
|
|
72
|
+
// also check if the socket was sent a message
|
|
73
|
+
expect(socket.socket.send).toHaveBeenCalledWith(JSON.stringify({
|
|
74
|
+
action: channelEngine_1.ServerActions.SYSTEM,
|
|
75
|
+
event: 'POND_MESSAGE',
|
|
76
|
+
payload: {
|
|
77
|
+
message: 'message',
|
|
78
|
+
},
|
|
79
|
+
channelName: 'test',
|
|
80
|
+
}));
|
|
81
|
+
});
|
|
82
|
+
// auxiliary functions
|
|
83
|
+
it('should send messages to different users', () => {
|
|
84
|
+
const { response, channelEngine } = createPondResponse();
|
|
85
|
+
// spy on the channelEngine to see if any messages were published
|
|
86
|
+
const broadcast = jest.spyOn(channelEngine, 'sendMessage');
|
|
87
|
+
// add a second user to the channel
|
|
88
|
+
channelEngine.addUser('user2', { assign: 'assign' }, () => { });
|
|
89
|
+
// send a message to a single user
|
|
90
|
+
expect(() => response.sendToUsers('hello_everyone', { message: 'hello' }, ['user2'])).toThrow(); // this is because the sender does not exist in the channel yet
|
|
91
|
+
// clear the spy
|
|
92
|
+
broadcast.mockClear();
|
|
93
|
+
// add the sender to the channel by using the response.accept() method
|
|
94
|
+
response.accept().sendToUsers('hello_everyone', { message: 'hello' }, ['user2']);
|
|
95
|
+
// check if the message was sent
|
|
96
|
+
expect(broadcast).toHaveBeenCalledWith('sender', ['user2'], channelEngine_1.ServerActions.BROADCAST, 'hello_everyone', { message: 'hello' });
|
|
97
|
+
// clear the spy
|
|
98
|
+
broadcast.mockClear();
|
|
99
|
+
// send a message to all users
|
|
100
|
+
response.broadcast('hello_everyone', { message: 'hello' });
|
|
101
|
+
expect(broadcast).toHaveBeenCalledWith('sender', 'all_users', channelEngine_1.ServerActions.BROADCAST, 'hello_everyone', { message: 'hello' });
|
|
102
|
+
// clear the spy
|
|
103
|
+
broadcast.mockClear();
|
|
104
|
+
// send a message to all users except the sender
|
|
105
|
+
response.broadcastFromUser('hello_everyone', { message: 'hello' });
|
|
106
|
+
expect(broadcast).toHaveBeenCalledWith('sender', 'all_except_sender', channelEngine_1.ServerActions.BROADCAST, 'hello_everyone', { message: 'hello' });
|
|
107
|
+
});
|
|
108
|
+
});
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.PresenceEngine = exports.PresenceEventTypes = void 0;
|
|
4
|
+
const subjectUtils_1 = require("../utils/subjectUtils");
|
|
5
|
+
var PresenceEventTypes;
|
|
6
|
+
(function (PresenceEventTypes) {
|
|
7
|
+
PresenceEventTypes["JOIN"] = "JOIN";
|
|
8
|
+
PresenceEventTypes["LEAVE"] = "LEAVE";
|
|
9
|
+
PresenceEventTypes["UPDATE"] = "UPDATE";
|
|
10
|
+
})(PresenceEventTypes = exports.PresenceEventTypes || (exports.PresenceEventTypes = {}));
|
|
11
|
+
class PresenceEngine {
|
|
12
|
+
constructor() {
|
|
13
|
+
this._presence = new subjectUtils_1.BehaviorSubject();
|
|
14
|
+
this._presenceMap = new Map();
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* @desc Lists all the presence of the users
|
|
18
|
+
*/
|
|
19
|
+
getPresence() {
|
|
20
|
+
return Array.from(this._presenceMap.entries())
|
|
21
|
+
.reduce((acc, [key, value]) => {
|
|
22
|
+
acc[key] = value;
|
|
23
|
+
return acc;
|
|
24
|
+
}, {});
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* @desc Returns the presence of a user
|
|
28
|
+
* @param userId - The id of the user
|
|
29
|
+
*/
|
|
30
|
+
getUserPresence(userId) {
|
|
31
|
+
return this._presenceMap.get(userId);
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* @desc Removes a presence from the presence engine
|
|
35
|
+
* @param presenceKey - The key of the presence
|
|
36
|
+
*/
|
|
37
|
+
removePresence(presenceKey) {
|
|
38
|
+
const presence = this._presenceMap.get(presenceKey);
|
|
39
|
+
if (presence) {
|
|
40
|
+
this._presence.unsubscribe(presenceKey);
|
|
41
|
+
this._presenceMap.delete(presenceKey);
|
|
42
|
+
if (this._presenceMap.size > 0) {
|
|
43
|
+
this._presence.next({
|
|
44
|
+
type: PresenceEventTypes.LEAVE,
|
|
45
|
+
changed: presence,
|
|
46
|
+
presence: Array.from(this._presenceMap.values()),
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
else {
|
|
51
|
+
throw new Error(`PresenceEngine: Presence with key ${presenceKey} does not exist`);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* @desc Updates a presence
|
|
56
|
+
* @param presenceKey - The key of the presence
|
|
57
|
+
* @param presence - The new presence
|
|
58
|
+
*/
|
|
59
|
+
updatePresence(presenceKey, presence) {
|
|
60
|
+
const oldPresence = this._presenceMap.get(presenceKey);
|
|
61
|
+
if (oldPresence) {
|
|
62
|
+
this._presenceMap.set(presenceKey, presence);
|
|
63
|
+
this._presence.next({
|
|
64
|
+
type: PresenceEventTypes.UPDATE,
|
|
65
|
+
changed: Object.assign(Object.assign({}, oldPresence), presence),
|
|
66
|
+
presence: Array.from(this._presenceMap.values()),
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
else {
|
|
70
|
+
throw new Error(`PresenceEngine: Presence with key ${presenceKey} does not exist`);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* @desc Tracks a presence
|
|
75
|
+
* @param presenceKey - The key of the presence
|
|
76
|
+
* @param presence - The presence
|
|
77
|
+
* @param onPresenceChange - The callback to be called when the presence changes
|
|
78
|
+
*/
|
|
79
|
+
trackPresence(presenceKey, presence, onPresenceChange) {
|
|
80
|
+
this._insertPresence(presenceKey, presence);
|
|
81
|
+
return this._subscribe(presenceKey, onPresenceChange);
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* @desc Inserts a presence into the presence engine
|
|
85
|
+
* @param presenceKey - The key of the presence
|
|
86
|
+
* @param presence - The presence
|
|
87
|
+
* @private
|
|
88
|
+
*/
|
|
89
|
+
_insertPresence(presenceKey, presence) {
|
|
90
|
+
if (!this._presenceMap.has(presenceKey)) {
|
|
91
|
+
this._presenceMap.set(presenceKey, presence);
|
|
92
|
+
this._presence.next({
|
|
93
|
+
type: PresenceEventTypes.JOIN,
|
|
94
|
+
changed: presence,
|
|
95
|
+
presence: Array.from(this._presenceMap.values()),
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
else {
|
|
99
|
+
throw new Error(`PresenceEngine: Presence with key ${presenceKey} already exists`);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* @desc Subscribes to the presence engine
|
|
104
|
+
* @param identifier - The identifier of the observer
|
|
105
|
+
* @param observer - The observer
|
|
106
|
+
* @private
|
|
107
|
+
*/
|
|
108
|
+
_subscribe(identifier, observer) {
|
|
109
|
+
return this._presence.subscribe(identifier, observer);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
exports.PresenceEngine = PresenceEngine;
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const presenceEngine_1 = require("./presenceEngine");
|
|
4
|
+
/* eslint-disable @typescript-eslint/ban-ts-comment */
|
|
5
|
+
describe('PresenceEngine', () => {
|
|
6
|
+
let presenceEngine;
|
|
7
|
+
let presence;
|
|
8
|
+
let presenceKey;
|
|
9
|
+
let onPresenceChange;
|
|
10
|
+
beforeEach(() => {
|
|
11
|
+
presenceEngine = new presenceEngine_1.PresenceEngine();
|
|
12
|
+
presence = {
|
|
13
|
+
id: 'id',
|
|
14
|
+
name: 'name',
|
|
15
|
+
color: 'color',
|
|
16
|
+
type: 'type',
|
|
17
|
+
location: 'location',
|
|
18
|
+
};
|
|
19
|
+
presenceKey = 'presenceKey';
|
|
20
|
+
onPresenceChange = jest.fn();
|
|
21
|
+
});
|
|
22
|
+
describe('trackPresence', () => {
|
|
23
|
+
it('should insert a presence into the presence engine', () => {
|
|
24
|
+
// spy on the private method _insertPresence
|
|
25
|
+
// @ts-ignore
|
|
26
|
+
jest.spyOn(presenceEngine, '_insertPresence');
|
|
27
|
+
presenceEngine.trackPresence(presenceKey, presence, onPresenceChange);
|
|
28
|
+
// @ts-ignore
|
|
29
|
+
expect(presenceEngine._insertPresence).toHaveBeenCalledWith(presenceKey, presence);
|
|
30
|
+
expect(onPresenceChange).toHaveBeenCalledWith({
|
|
31
|
+
type: presenceEngine_1.PresenceEventTypes.JOIN,
|
|
32
|
+
changed: presence,
|
|
33
|
+
presence: [presence],
|
|
34
|
+
});
|
|
35
|
+
});
|
|
36
|
+
it('should subscribe to the presence engine', () => {
|
|
37
|
+
// spy on the private method _subscribe
|
|
38
|
+
// @ts-ignore
|
|
39
|
+
jest.spyOn(presenceEngine, '_subscribe');
|
|
40
|
+
presenceEngine.trackPresence(presenceKey, presence, onPresenceChange);
|
|
41
|
+
// @ts-ignore
|
|
42
|
+
expect(presenceEngine._subscribe).toHaveBeenCalledWith(presenceKey, onPresenceChange);
|
|
43
|
+
expect(onPresenceChange).toHaveBeenCalledWith({
|
|
44
|
+
type: presenceEngine_1.PresenceEventTypes.JOIN,
|
|
45
|
+
changed: presence,
|
|
46
|
+
presence: [presence],
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
it('should throw an error if the presence already exists', () => {
|
|
50
|
+
presenceEngine.trackPresence(presenceKey, presence, onPresenceChange);
|
|
51
|
+
expect(() => presenceEngine.trackPresence(presenceKey, presence, onPresenceChange)).toThrowError(`PresenceEngine: Presence with key ${presenceKey} already exists`);
|
|
52
|
+
});
|
|
53
|
+
});
|
|
54
|
+
describe('updatePresence', () => {
|
|
55
|
+
it('should update a presence', () => {
|
|
56
|
+
presenceEngine.trackPresence(presenceKey, presence, onPresenceChange);
|
|
57
|
+
const newPresence = {
|
|
58
|
+
id: 'id',
|
|
59
|
+
name: 'name',
|
|
60
|
+
color: 'color',
|
|
61
|
+
type: 'type',
|
|
62
|
+
location: 'location',
|
|
63
|
+
};
|
|
64
|
+
presenceEngine.updatePresence(presenceKey, newPresence);
|
|
65
|
+
expect(onPresenceChange).toHaveBeenCalledWith({
|
|
66
|
+
type: presenceEngine_1.PresenceEventTypes.UPDATE,
|
|
67
|
+
changed: Object.assign(Object.assign({}, presence), newPresence),
|
|
68
|
+
presence: [newPresence],
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
it('should throw an error if the presence does not exist', () => {
|
|
72
|
+
expect(() => presenceEngine.updatePresence(presenceKey, presence)).toThrowError(`PresenceEngine: Presence with key ${presenceKey} does not exist`);
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
describe('removePresence', () => {
|
|
76
|
+
it('should remove a presence from the presence engine', () => {
|
|
77
|
+
presenceEngine.trackPresence(presenceKey, presence, onPresenceChange);
|
|
78
|
+
presenceEngine.trackPresence('presenceKey2', Object.assign(Object.assign({}, presence), { key: 'presence2' }), onPresenceChange);
|
|
79
|
+
presenceEngine.removePresence(presenceKey);
|
|
80
|
+
expect(onPresenceChange).toHaveBeenCalledWith({
|
|
81
|
+
type: presenceEngine_1.PresenceEventTypes.LEAVE,
|
|
82
|
+
changed: presence,
|
|
83
|
+
presence: [
|
|
84
|
+
Object.assign(Object.assign({}, presence), { key: 'presence2' }),
|
|
85
|
+
],
|
|
86
|
+
});
|
|
87
|
+
// @ts-ignore
|
|
88
|
+
onPresenceChange.mockClear();
|
|
89
|
+
presenceEngine.removePresence('presenceKey2');
|
|
90
|
+
expect(onPresenceChange).not.toHaveBeenCalled();
|
|
91
|
+
});
|
|
92
|
+
it('should throw an error if the presence does not exist', () => {
|
|
93
|
+
expect(() => presenceEngine.removePresence(presenceKey)).toThrowError(`PresenceEngine: Presence with key ${presenceKey} does not exist`);
|
|
94
|
+
});
|
|
95
|
+
});
|
|
96
|
+
describe('getPresence', () => {
|
|
97
|
+
it('should return the presence', () => {
|
|
98
|
+
presenceEngine.trackPresence(presenceKey, presence, onPresenceChange);
|
|
99
|
+
const data = {};
|
|
100
|
+
data[presenceKey] = presence;
|
|
101
|
+
expect(presenceEngine.getPresence()).toEqual(data);
|
|
102
|
+
});
|
|
103
|
+
});
|
|
104
|
+
});
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.PondSocket = void 0;
|
|
4
|
+
const http_1 = require("http");
|
|
5
|
+
const ws_1 = require("ws");
|
|
6
|
+
const endpoint_1 = require("../endpoint/endpoint");
|
|
7
|
+
const matchPattern_1 = require("../utils/matchPattern");
|
|
8
|
+
class PondSocket {
|
|
9
|
+
constructor(server, socketServer) {
|
|
10
|
+
this._middleware = [];
|
|
11
|
+
this._matcher = new matchPattern_1.MatchPattern();
|
|
12
|
+
this._server = server || new http_1.Server();
|
|
13
|
+
this._socketServer = socketServer || new ws_1.WebSocketServer({ noServer: true });
|
|
14
|
+
this._init();
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* @desc Specifies the port to listen on
|
|
18
|
+
* @param port - the port to listen on
|
|
19
|
+
* @param callback - the callback to call when the server is listening
|
|
20
|
+
*/
|
|
21
|
+
listen(port, callback) {
|
|
22
|
+
return this._server.listen(port, callback);
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* @desc Closes the server
|
|
26
|
+
* @param callback - the callback to call when the server is closed
|
|
27
|
+
*/
|
|
28
|
+
close(callback) {
|
|
29
|
+
return this._server.close(callback);
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* @desc Accepts a new socket upgrade request on the provided endpoint using the handler function to authenticate the socket
|
|
33
|
+
* @param path - the pattern to accept || can also be a regex
|
|
34
|
+
* @param handler - the handler function to authenticate the socket
|
|
35
|
+
* @example
|
|
36
|
+
* const endpoint = pond.createEndpoint('/api/socket', (req, res) => {
|
|
37
|
+
* const token = req.query.token;
|
|
38
|
+
* if (!token)
|
|
39
|
+
* return res.reject("No token provided");
|
|
40
|
+
* res.accept({
|
|
41
|
+
* assign: {
|
|
42
|
+
* token
|
|
43
|
+
* }
|
|
44
|
+
* });
|
|
45
|
+
* })
|
|
46
|
+
*/
|
|
47
|
+
createEndpoint(path, handler) {
|
|
48
|
+
const endpoint = new endpoint_1.Endpoint(this._socketServer);
|
|
49
|
+
this._middleware.push((req, socket, head, next) => {
|
|
50
|
+
const address = req.url || '';
|
|
51
|
+
const dataEndpoint = this._matcher.parseEvent(path, address);
|
|
52
|
+
if (!dataEndpoint) {
|
|
53
|
+
return next();
|
|
54
|
+
}
|
|
55
|
+
endpoint._authoriseConnection(req, socket, head, dataEndpoint, handler);
|
|
56
|
+
});
|
|
57
|
+
return endpoint;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* @desc Adds a middleware function to the socket server
|
|
61
|
+
* @param middleware - the middleware function to add
|
|
62
|
+
*/
|
|
63
|
+
use(middleware) {
|
|
64
|
+
this._middleware.push(middleware);
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* @desc managed the heartbeat of the socket server
|
|
68
|
+
* @private
|
|
69
|
+
*/
|
|
70
|
+
_manageHeartbeat() {
|
|
71
|
+
this._socketServer.on('connection', (socket) => {
|
|
72
|
+
socket.on('pong', () => {
|
|
73
|
+
socket.isAlive = true;
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
const interval = setInterval(() => {
|
|
77
|
+
this._socketServer.clients.forEach((socket) => {
|
|
78
|
+
if (socket.isAlive === false) {
|
|
79
|
+
return socket.terminate();
|
|
80
|
+
}
|
|
81
|
+
socket.isAlive = false;
|
|
82
|
+
socket.ping(() => { });
|
|
83
|
+
});
|
|
84
|
+
}, 30000);
|
|
85
|
+
this._socketServer.on('close', () => clearInterval(interval));
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* @desc executes the middleware functions
|
|
89
|
+
* @param req - the request object
|
|
90
|
+
* @param socket - the socket object
|
|
91
|
+
* @param head - the head buffer
|
|
92
|
+
* @private
|
|
93
|
+
*/
|
|
94
|
+
_execute(req, socket, head) {
|
|
95
|
+
const temp = this._middleware.concat();
|
|
96
|
+
const next = () => {
|
|
97
|
+
const middleware = temp.shift();
|
|
98
|
+
if (middleware) {
|
|
99
|
+
middleware(req, socket, head, next);
|
|
100
|
+
}
|
|
101
|
+
else {
|
|
102
|
+
socket.write('HTTP/1.1 400 Bad Request\r\n\r');
|
|
103
|
+
socket.destroy();
|
|
104
|
+
}
|
|
105
|
+
};
|
|
106
|
+
next();
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* @desc initialises the socket server
|
|
110
|
+
* @private
|
|
111
|
+
*/
|
|
112
|
+
_init() {
|
|
113
|
+
this._manageHeartbeat();
|
|
114
|
+
this._server.on('error', (error) => {
|
|
115
|
+
throw new Error(error.message);
|
|
116
|
+
});
|
|
117
|
+
this._server.on('upgrade', (req, socket, head) => {
|
|
118
|
+
this._execute(req, socket, head);
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
exports.PondSocket = PondSocket;
|
|
@@ -12,24 +12,13 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
12
12
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
13
13
|
};
|
|
14
14
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
-
const endpoint_1 = require("./endpoint");
|
|
16
|
-
const pondSocket_1 = require("./pondSocket");
|
|
17
|
-
const superwstest_1 = __importDefault(require("superwstest"));
|
|
18
|
-
const enums_1 = require("./enums");
|
|
19
15
|
const http_1 = require("http");
|
|
16
|
+
const superwstest_1 = __importDefault(require("superwstest"));
|
|
20
17
|
const ws_1 = require("ws");
|
|
18
|
+
const pondSocket_1 = require("./pondSocket");
|
|
19
|
+
const channelEngine_1 = require("../channel/channelEngine");
|
|
20
|
+
const endpoint_1 = require("../endpoint/endpoint");
|
|
21
21
|
describe('server', () => {
|
|
22
|
-
it('should be defined', () => {
|
|
23
|
-
expect(pondSocket_1.PondSocket).toBeDefined();
|
|
24
|
-
});
|
|
25
|
-
it('should be instantiable', () => {
|
|
26
|
-
expect(new pondSocket_1.PondSocket()).toBeInstanceOf(pondSocket_1.PondSocket);
|
|
27
|
-
});
|
|
28
|
-
it('should ba able to create its own server and websocket server if none is provided', () => {
|
|
29
|
-
const socket = new pondSocket_1.PondSocket();
|
|
30
|
-
expect(socket['_server']).toBeDefined();
|
|
31
|
-
expect(socket['_socketServer']).toBeDefined();
|
|
32
|
-
});
|
|
33
22
|
it('should take a server and websocket server if provided', () => {
|
|
34
23
|
const server = (0, http_1.createServer)();
|
|
35
24
|
const socketServer = new ws_1.Server({ noServer: true });
|
|
@@ -50,7 +39,6 @@ describe('server', () => {
|
|
|
50
39
|
console.log('socket');
|
|
51
40
|
});
|
|
52
41
|
expect(endpoint).toBeInstanceOf(endpoint_1.Endpoint);
|
|
53
|
-
expect(endpoint['_handler']).toEqual(expect.any(Function));
|
|
54
42
|
});
|
|
55
43
|
it('should be able to create multiple endpoints', () => {
|
|
56
44
|
const server = (0, http_1.createServer)();
|
|
@@ -63,9 +51,7 @@ describe('server', () => {
|
|
|
63
51
|
console.log('socket2');
|
|
64
52
|
});
|
|
65
53
|
expect(endpoint).toBeInstanceOf(endpoint_1.Endpoint);
|
|
66
|
-
expect(endpoint['_handler']).toEqual(expect.any(Function));
|
|
67
54
|
expect(endpoint2).toBeInstanceOf(endpoint_1.Endpoint);
|
|
68
|
-
expect(endpoint2['_handler']).toEqual(expect.any(Function));
|
|
69
55
|
});
|
|
70
56
|
it('should be able to reject a socket', () => {
|
|
71
57
|
const server = (0, http_1.createServer)();
|
|
@@ -99,7 +85,7 @@ describe('server', () => {
|
|
|
99
85
|
});
|
|
100
86
|
yield (0, superwstest_1.default)(server)
|
|
101
87
|
.ws('/api/socket')
|
|
102
|
-
.expectUpgrade(res => expect(res.statusCode).toBe(101))
|
|
88
|
+
.expectUpgrade((res) => expect(res.statusCode).toBe(101))
|
|
103
89
|
.close()
|
|
104
90
|
.expectClosed();
|
|
105
91
|
server.close();
|
|
@@ -131,12 +117,12 @@ describe('server', () => {
|
|
|
131
117
|
});
|
|
132
118
|
yield (0, superwstest_1.default)(server)
|
|
133
119
|
.ws('/api/socket')
|
|
134
|
-
.expectUpgrade(res => expect(res.statusCode).toBe(101))
|
|
120
|
+
.expectUpgrade((res) => expect(res.statusCode).toBe(101))
|
|
135
121
|
.expectJson({
|
|
136
|
-
action: enums_1.ServerActions.MESSAGE,
|
|
137
122
|
event: 'testEvent',
|
|
138
123
|
channelName: 'SERVER',
|
|
139
124
|
payload: { test: 'test' },
|
|
125
|
+
action: channelEngine_1.ServerActions.SYSTEM,
|
|
140
126
|
})
|
|
141
127
|
.close()
|
|
142
128
|
.expectClosed();
|