@eleven-am/pondsocket 0.1.56 → 0.1.57
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.eslintrc.json +387 -0
- package/dist/LICENSE +674 -0
- package/dist/README.md +139 -0
- package/dist/package.json +51 -0
- package/{types.d.ts → dist/types.d.ts} +0 -5
- package/jest.config.js +11 -0
- package/package.json +3 -3
- package/src/abstracts/abstractRequest.test.ts +49 -0
- package/src/abstracts/abstractRequest.ts +56 -0
- package/src/abstracts/abstractResponse.ts +26 -0
- package/src/abstracts/middleware.test.ts +75 -0
- package/src/abstracts/middleware.ts +50 -0
- package/src/channel/channel.test.ts +501 -0
- package/src/channel/channel.ts +305 -0
- package/src/channel/eventRequest.test.ts +37 -0
- package/src/channel/eventRequest.ts +27 -0
- package/src/channel/eventResponse.test.ts +249 -0
- package/src/channel/eventResponse.ts +172 -0
- package/src/client/channel.test.ts +799 -0
- package/src/client/channel.ts +342 -0
- package/src/client.ts +124 -0
- package/src/endpoint/endpoint.test.ts +825 -0
- package/src/endpoint/endpoint.ts +304 -0
- package/src/endpoint/response.ts +106 -0
- package/src/enums.ts +52 -0
- package/src/errors/pondError.ts +32 -0
- package/src/express.ts +58 -0
- package/src/index.ts +3 -0
- package/src/lobby/JoinRequest.test.ts +48 -0
- package/src/lobby/JoinResponse.test.ts +162 -0
- package/src/lobby/joinRequest.ts +32 -0
- package/src/lobby/joinResponse.ts +146 -0
- package/src/lobby/lobby.ts +182 -0
- package/src/matcher/matcher.test.ts +103 -0
- package/src/matcher/matcher.ts +105 -0
- package/src/node.ts +33 -0
- package/src/presence/presence.ts +127 -0
- package/src/presence/presenceEngine.test.ts +143 -0
- package/src/server/pondSocket.ts +153 -0
- package/src/subjects/subject.test.ts +163 -0
- package/src/subjects/subject.ts +137 -0
- package/src/typedefs.d.ts +451 -0
- package/src/types.d.ts +89 -0
- package/tsconfig.build.json +7 -0
- package/tsconfig.json +12 -0
- /package/{abstracts → dist/abstracts}/abstractRequest.js +0 -0
- /package/{abstracts → dist/abstracts}/abstractResponse.js +0 -0
- /package/{abstracts → dist/abstracts}/middleware.js +0 -0
- /package/{channel → dist/channel}/channel.js +0 -0
- /package/{channel → dist/channel}/eventRequest.js +0 -0
- /package/{channel → dist/channel}/eventResponse.js +0 -0
- /package/{client → dist/client}/channel.js +0 -0
- /package/{client.d.ts → dist/client.d.ts} +0 -0
- /package/{client.js → dist/client.js} +0 -0
- /package/{endpoint → dist/endpoint}/endpoint.js +0 -0
- /package/{endpoint → dist/endpoint}/response.js +0 -0
- /package/{enums.js → dist/enums.js} +0 -0
- /package/{errors → dist/errors}/pondError.js +0 -0
- /package/{express.d.ts → dist/express.d.ts} +0 -0
- /package/{express.js → dist/express.js} +0 -0
- /package/{index.d.ts → dist/index.d.ts} +0 -0
- /package/{index.js → dist/index.js} +0 -0
- /package/{lobby → dist/lobby}/joinRequest.js +0 -0
- /package/{lobby → dist/lobby}/joinResponse.js +0 -0
- /package/{lobby → dist/lobby}/lobby.js +0 -0
- /package/{matcher → dist/matcher}/matcher.js +0 -0
- /package/{node.d.ts → dist/node.d.ts} +0 -0
- /package/{node.js → dist/node.js} +0 -0
- /package/{presence → dist/presence}/presence.js +0 -0
- /package/{server → dist/server}/pondSocket.js +0 -0
- /package/{subjects → dist/subjects}/subject.js +0 -0
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import { JoinResponse } from './joinResponse';
|
|
2
|
+
import { createChannelEngine } from '../channel/eventResponse.test';
|
|
3
|
+
import { RequestCache } from '../endpoint/endpoint';
|
|
4
|
+
import { ErrorTypes, ServerActions, ChannelReceiver } from '../enums';
|
|
5
|
+
|
|
6
|
+
const createPondResponse = () => {
|
|
7
|
+
const channelEngine = createChannelEngine();
|
|
8
|
+
|
|
9
|
+
const socket: RequestCache = {
|
|
10
|
+
clientId: 'sender',
|
|
11
|
+
assigns: { assign: 'assign' },
|
|
12
|
+
channelName: 'channel',
|
|
13
|
+
socket: {
|
|
14
|
+
send: jest.fn(),
|
|
15
|
+
} as any,
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
const response = new JoinResponse(socket, channelEngine);
|
|
19
|
+
|
|
20
|
+
return {
|
|
21
|
+
channelEngine,
|
|
22
|
+
socket,
|
|
23
|
+
response,
|
|
24
|
+
};
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
describe('JoinResponse', () => {
|
|
29
|
+
it('should create a new PondChannelResponse', () => {
|
|
30
|
+
const { response } = createPondResponse();
|
|
31
|
+
|
|
32
|
+
expect(response).toBeDefined();
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it('should accept the request', () => {
|
|
36
|
+
const { response, channelEngine, socket } = createPondResponse();
|
|
37
|
+
// spy on the channelEngine to see if the user was added
|
|
38
|
+
|
|
39
|
+
jest.spyOn(channelEngine, 'addUser');
|
|
40
|
+
response.accept();
|
|
41
|
+
|
|
42
|
+
// check if the response was sent
|
|
43
|
+
expect(channelEngine.addUser).toHaveBeenCalledWith(socket.clientId, socket.assigns, expect.any(Function));
|
|
44
|
+
expect(channelEngine.getUserData(socket.clientId)).not.toBeNull();
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it('should reject the request', () => {
|
|
48
|
+
const { response, channelEngine, socket } = createPondResponse();
|
|
49
|
+
// spy on the channelEngine to see if the user was added
|
|
50
|
+
|
|
51
|
+
jest.spyOn(channelEngine, 'addUser');
|
|
52
|
+
response.reject();
|
|
53
|
+
|
|
54
|
+
// check if the response was sent
|
|
55
|
+
expect(channelEngine.addUser).not.toHaveBeenCalled();
|
|
56
|
+
expect(channelEngine.getUserData(socket.clientId)).toBeUndefined();
|
|
57
|
+
|
|
58
|
+
// also check if the socket was sent a message
|
|
59
|
+
expect(socket.socket.send).toHaveBeenCalledWith(JSON.stringify({
|
|
60
|
+
event: ErrorTypes.UNAUTHORIZED_JOIN_REQUEST,
|
|
61
|
+
payload: {
|
|
62
|
+
message: 'Request to join channel test rejected: Unauthorized request',
|
|
63
|
+
code: 403,
|
|
64
|
+
},
|
|
65
|
+
channelName: 'test',
|
|
66
|
+
action: ServerActions.ERROR,
|
|
67
|
+
}));
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it('should send a direct message', () => {
|
|
71
|
+
const { response, channelEngine, socket } = createPondResponse();
|
|
72
|
+
// spy on the channelEngine to see if the user was added
|
|
73
|
+
|
|
74
|
+
jest.spyOn(channelEngine, 'addUser');
|
|
75
|
+
response.send('POND_MESSAGE', { message: 'message' });
|
|
76
|
+
|
|
77
|
+
// check if the response was sent
|
|
78
|
+
expect(channelEngine.addUser).toHaveBeenCalled();
|
|
79
|
+
expect(channelEngine.getUserData(socket.clientId)).toStrictEqual({
|
|
80
|
+
assigns: {
|
|
81
|
+
assign: 'assign',
|
|
82
|
+
},
|
|
83
|
+
id: 'sender',
|
|
84
|
+
presence: {},
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
// also check if the socket was sent a message
|
|
88
|
+
expect(socket.socket.send).toHaveBeenCalledWith(JSON.stringify({
|
|
89
|
+
channelName: 'test',
|
|
90
|
+
action: ServerActions.SYSTEM,
|
|
91
|
+
payload: {
|
|
92
|
+
message: 'message',
|
|
93
|
+
},
|
|
94
|
+
event: 'POND_MESSAGE',
|
|
95
|
+
}));
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
// auxiliary functions
|
|
99
|
+
it('should send messages to different users', () => {
|
|
100
|
+
const { response, channelEngine } = createPondResponse();
|
|
101
|
+
// spy on the channelEngine to see if any messages were published
|
|
102
|
+
const broadcast = jest.spyOn(channelEngine, 'sendMessage');
|
|
103
|
+
|
|
104
|
+
// add a second user to the channel
|
|
105
|
+
channelEngine.addUser('user2', { assign: 'assign' }, () => {});
|
|
106
|
+
|
|
107
|
+
// send a message to a single user
|
|
108
|
+
// this is because the sender does not exist in the channel yet
|
|
109
|
+
expect(() => response.sendToUsers('hello_everyone', { message: 'hello' }, ['user2'])).toThrow();
|
|
110
|
+
|
|
111
|
+
// clear the spy
|
|
112
|
+
broadcast.mockClear();
|
|
113
|
+
|
|
114
|
+
// add the sender to the channel by using the response.accept() method
|
|
115
|
+
response.accept().sendToUsers('hello_everyone', { message: 'hello' }, ['user2']);
|
|
116
|
+
|
|
117
|
+
// check if the message was sent
|
|
118
|
+
expect(broadcast).toHaveBeenCalledWith('sender', ['user2'], ServerActions.BROADCAST, 'hello_everyone', { message: 'hello' });
|
|
119
|
+
|
|
120
|
+
// clear the spy
|
|
121
|
+
broadcast.mockClear();
|
|
122
|
+
|
|
123
|
+
// send a message to all users
|
|
124
|
+
response.broadcast('hello_everyone', { message: 'hello' });
|
|
125
|
+
expect(broadcast).toHaveBeenCalledWith('sender', ChannelReceiver.ALL_USERS, ServerActions.BROADCAST, 'hello_everyone', { message: 'hello' });
|
|
126
|
+
|
|
127
|
+
// clear the spy
|
|
128
|
+
broadcast.mockClear();
|
|
129
|
+
|
|
130
|
+
// send a message to all users except the sender
|
|
131
|
+
response.broadcastFromUser('hello_everyone', { message: 'hello' });
|
|
132
|
+
expect(broadcast).toHaveBeenCalledWith('sender', ChannelReceiver.ALL_EXCEPT_SENDER, ServerActions.BROADCAST, 'hello_everyone', { message: 'hello' });
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
it('should be able to track the presence of users', () => {
|
|
136
|
+
const { response, channelEngine } = createPondResponse();
|
|
137
|
+
|
|
138
|
+
// spy on the channelEngine to see if the trackPresence method was called
|
|
139
|
+
const trackPresence = jest.spyOn(channelEngine, 'trackPresence');
|
|
140
|
+
|
|
141
|
+
// add a second user to the channel
|
|
142
|
+
response.accept()
|
|
143
|
+
.trackPresence({
|
|
144
|
+
status: 'online',
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
// check if the trackPresence method was called
|
|
148
|
+
expect(trackPresence).toHaveBeenCalledWith('sender', { status: 'online' });
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
it('should throw an error if accept, reject / send is called more than once', () => {
|
|
152
|
+
const { response, channelEngine, socket } = createPondResponse();
|
|
153
|
+
|
|
154
|
+
jest.spyOn(channelEngine, 'addUser');
|
|
155
|
+
expect(channelEngine.addUser).not.toHaveBeenCalled();
|
|
156
|
+
response.accept();
|
|
157
|
+
expect(channelEngine.addUser).toHaveBeenCalledWith(socket.clientId, socket.assigns, expect.any(Function));
|
|
158
|
+
expect(() => response.accept()).toThrowError('Request to join channel test rejected: Request already executed');
|
|
159
|
+
expect(() => response.reject()).toThrowError('Request to join channel test rejected: Request already executed');
|
|
160
|
+
expect(() => response.send('event', { payload: 'payload' })).toThrowError('Request to join channel test rejected: Request already executed');
|
|
161
|
+
});
|
|
162
|
+
});
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { AbstractRequest } from '../abstracts/abstractRequest';
|
|
2
|
+
import { ChannelEngine } from '../channel/channel';
|
|
3
|
+
import { RequestCache } from '../endpoint/endpoint';
|
|
4
|
+
// eslint-disable-next-line import/no-unresolved
|
|
5
|
+
import { JoinParams, UserData, PondAssigns } from '../types';
|
|
6
|
+
|
|
7
|
+
export class JoinRequest<Path extends string> extends AbstractRequest<Path> {
|
|
8
|
+
readonly #params: JoinParams;
|
|
9
|
+
|
|
10
|
+
readonly #clientId: string;
|
|
11
|
+
|
|
12
|
+
readonly #assigns: PondAssigns;
|
|
13
|
+
|
|
14
|
+
constructor (event: RequestCache, params: JoinParams, engine: ChannelEngine) {
|
|
15
|
+
super(engine.name, engine, params);
|
|
16
|
+
this.#params = params;
|
|
17
|
+
this.#clientId = event.clientId;
|
|
18
|
+
this.#assigns = event.assigns;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
public get joinParams (): JoinParams {
|
|
22
|
+
return this.#params;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
public get user (): UserData {
|
|
26
|
+
return {
|
|
27
|
+
id: this.#clientId,
|
|
28
|
+
assigns: this.#assigns,
|
|
29
|
+
presence: {},
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
}
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import { PondResponse } from '../abstracts/abstractResponse';
|
|
2
|
+
import { ChannelEngine } from '../channel/channel';
|
|
3
|
+
import { RequestCache } from '../endpoint/endpoint';
|
|
4
|
+
import { ErrorTypes, ServerActions, SystemSender, ChannelReceiver, Events } from '../enums';
|
|
5
|
+
import { ChannelError } from '../errors/pondError';
|
|
6
|
+
// eslint-disable-next-line import/no-unresolved
|
|
7
|
+
import { PondAssigns, ChannelEvent, PondMessage, PondPresence } from '../types';
|
|
8
|
+
|
|
9
|
+
export class JoinResponse extends PondResponse {
|
|
10
|
+
readonly #user: RequestCache;
|
|
11
|
+
|
|
12
|
+
readonly #engine: ChannelEngine;
|
|
13
|
+
|
|
14
|
+
#executed: boolean;
|
|
15
|
+
|
|
16
|
+
constructor (user: RequestCache, engine: ChannelEngine) {
|
|
17
|
+
super();
|
|
18
|
+
this.#user = user;
|
|
19
|
+
this.#engine = engine;
|
|
20
|
+
this.#executed = false;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* @desc Accepts the request and optionally assigns data to the client
|
|
25
|
+
* @param assigns - the data to assign to the client
|
|
26
|
+
*/
|
|
27
|
+
public accept (assigns?: PondAssigns): JoinResponse {
|
|
28
|
+
this.#performChecks();
|
|
29
|
+
|
|
30
|
+
const newAssigns = {
|
|
31
|
+
...assigns || {},
|
|
32
|
+
...this.#user.assigns,
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
const acknowledgement: ChannelEvent = {
|
|
36
|
+
action: ServerActions.SYSTEM,
|
|
37
|
+
channelName: this.#engine.name,
|
|
38
|
+
event: Events.ACKNOWLEDGE,
|
|
39
|
+
payload: {},
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
this.#user.socket.send(JSON.stringify(acknowledgement));
|
|
43
|
+
this.#engine.addUser(this.#user.clientId, newAssigns, (event) => {
|
|
44
|
+
this.#user.socket.send(JSON.stringify(event));
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
return this;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* @desc Rejects the request and optionally assigns data to the client
|
|
52
|
+
* @param message - the error message
|
|
53
|
+
* @param errorCode - the error code
|
|
54
|
+
*/
|
|
55
|
+
public reject (message?: string, errorCode?: number): JoinResponse {
|
|
56
|
+
this.#performChecks();
|
|
57
|
+
|
|
58
|
+
const text = `Request to join channel ${this.#engine.name} rejected: ${message || 'Unauthorized request'}`;
|
|
59
|
+
|
|
60
|
+
const errorMessage: ChannelEvent = {
|
|
61
|
+
event: ErrorTypes.UNAUTHORIZED_JOIN_REQUEST,
|
|
62
|
+
payload: {
|
|
63
|
+
message: text,
|
|
64
|
+
code: errorCode || 403,
|
|
65
|
+
},
|
|
66
|
+
channelName: this.#engine.name,
|
|
67
|
+
action: ServerActions.ERROR,
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
this.#user.socket.send(JSON.stringify(errorMessage));
|
|
71
|
+
|
|
72
|
+
return this;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* @desc Emits a direct message to the client
|
|
77
|
+
* @param event - the event name
|
|
78
|
+
* @param payload - the payload to send
|
|
79
|
+
* @param assigns - the data to assign to the client
|
|
80
|
+
*/
|
|
81
|
+
public send (event: string, payload: PondMessage, assigns?: PondAssigns) {
|
|
82
|
+
this.accept(assigns);
|
|
83
|
+
this.#engine.sendMessage(SystemSender.CHANNEL, [this.#user.clientId], ServerActions.SYSTEM, event, payload);
|
|
84
|
+
|
|
85
|
+
return this;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* @desc Emits a message to all clients in the channel
|
|
90
|
+
* @param event - the event name
|
|
91
|
+
* @param payload - the payload to send
|
|
92
|
+
*/
|
|
93
|
+
public broadcast (event: string, payload: PondMessage): JoinResponse {
|
|
94
|
+
this.#engine.sendMessage(this.#user.clientId, ChannelReceiver.ALL_USERS, ServerActions.BROADCAST, event, payload);
|
|
95
|
+
|
|
96
|
+
return this;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* @desc Emits a message to all clients in the channel except the sender
|
|
101
|
+
* @param event - the event name
|
|
102
|
+
* @param payload - the payload to send
|
|
103
|
+
*/
|
|
104
|
+
public broadcastFromUser (event: string, payload: PondMessage): JoinResponse {
|
|
105
|
+
this.#engine.sendMessage(this.#user.clientId, ChannelReceiver.ALL_EXCEPT_SENDER, ServerActions.BROADCAST, event, payload);
|
|
106
|
+
|
|
107
|
+
return this;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* @desc Emits a message to a specific set of clients
|
|
112
|
+
* @param event - the event name
|
|
113
|
+
* @param payload - the payload to send
|
|
114
|
+
* @param userIds - the ids of the clients to send the message to
|
|
115
|
+
*/
|
|
116
|
+
public sendToUsers (event: string, payload: PondMessage, userIds: string[]): JoinResponse {
|
|
117
|
+
this.#engine.sendMessage(this.#user.clientId, userIds, ServerActions.BROADCAST, event, payload);
|
|
118
|
+
|
|
119
|
+
return this;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* @desc tracks the presence of a client
|
|
124
|
+
* @param presence - the presence data to track
|
|
125
|
+
*/
|
|
126
|
+
public trackPresence (presence: PondPresence): JoinResponse {
|
|
127
|
+
this.#engine.trackPresence(this.#user.clientId, presence);
|
|
128
|
+
|
|
129
|
+
return this;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* @desc Performs checks to ensure the response has not been executed
|
|
134
|
+
* @private
|
|
135
|
+
*/
|
|
136
|
+
#performChecks (): void {
|
|
137
|
+
if (this.#executed) {
|
|
138
|
+
const message = `Request to join channel ${this.#engine.name} rejected: Request already executed`;
|
|
139
|
+
const code = 403;
|
|
140
|
+
|
|
141
|
+
throw new ChannelError(message, code, this.#engine.name);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
this.#executed = true;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
import { Middleware } from '../abstracts/middleware';
|
|
2
|
+
import { ChannelEngine, ParentEngine } from '../channel/channel';
|
|
3
|
+
import { EventRequest } from '../channel/eventRequest';
|
|
4
|
+
import { EventResponse } from '../channel/eventResponse';
|
|
5
|
+
import { ServerActions, ChannelReceiver, SystemSender } from '../enums';
|
|
6
|
+
// eslint-disable-next-line import/no-unresolved
|
|
7
|
+
import { PondPath, PondMessage } from '../types';
|
|
8
|
+
|
|
9
|
+
export class LobbyEngine {
|
|
10
|
+
readonly #channels: Set<ChannelEngine>;
|
|
11
|
+
|
|
12
|
+
readonly #middleware: Middleware<EventRequest<string>, EventResponse>;
|
|
13
|
+
|
|
14
|
+
constructor () {
|
|
15
|
+
this.#channels = new Set<ChannelEngine>();
|
|
16
|
+
this.#middleware = new Middleware();
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* @desc Handles an event request made by a user
|
|
21
|
+
* @param event - The event to listen for
|
|
22
|
+
* @param handler - The handler to execute when the event is received
|
|
23
|
+
* @example
|
|
24
|
+
* pond.onEvent('echo', (request, response) => {
|
|
25
|
+
* response.send('echo', {
|
|
26
|
+
* message: request.event.payload,
|
|
27
|
+
* });
|
|
28
|
+
* });
|
|
29
|
+
*/
|
|
30
|
+
public onEvent<Event extends string> (event: PondPath<Event>, handler: (request: EventRequest<Event>, response: EventResponse) => void | Promise<void>) {
|
|
31
|
+
this.#middleware.use((request, response, next) => {
|
|
32
|
+
if (request._parseQueries(event)) {
|
|
33
|
+
return handler(request as EventRequest<Event>, response);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
next();
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* @desc Broadcasts a message to all users in a channel
|
|
42
|
+
* @param event - The event to broadcast
|
|
43
|
+
* @param payload - The payload to send
|
|
44
|
+
* @param channelName - The channel to broadcast to (if not specified, broadcast to all channels)
|
|
45
|
+
* @example
|
|
46
|
+
* pond.broadcast('echo', {
|
|
47
|
+
* message: 'Hello World',
|
|
48
|
+
* timestamp: Date.now(),
|
|
49
|
+
* channel: 'my_channel',
|
|
50
|
+
*});
|
|
51
|
+
*/
|
|
52
|
+
public broadcast (event: string, payload: PondMessage, channelName?: string) {
|
|
53
|
+
if (channelName) {
|
|
54
|
+
const channel = this.getChannel(channelName) || this.createChannel(channelName);
|
|
55
|
+
|
|
56
|
+
channel.sendMessage(SystemSender.CHANNEL, ChannelReceiver.ALL_USERS, ServerActions.SYSTEM, event, payload);
|
|
57
|
+
} else {
|
|
58
|
+
this.#channels.forEach((channel) => {
|
|
59
|
+
channel.sendMessage(SystemSender.CHANNEL, ChannelReceiver.ALL_USERS, ServerActions.SYSTEM, event, payload);
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* @desc Removes a user from all channels
|
|
66
|
+
* @param clientId - The client id of the user to remove
|
|
67
|
+
* @param graceful - Whether to gracefully remove the user or not
|
|
68
|
+
*/
|
|
69
|
+
public removeUser (clientId: string, graceful = false) {
|
|
70
|
+
this.#channels.forEach((channel) => {
|
|
71
|
+
channel.removeUser(clientId, graceful);
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* @desc Executes a function on a channel
|
|
77
|
+
* @param channelName - The name of the channel to execute the function on
|
|
78
|
+
* @param handler - The function to execute
|
|
79
|
+
* @private
|
|
80
|
+
*/
|
|
81
|
+
public execute<Return> (channelName: string, handler: ((channel: ChannelEngine) => Return)) {
|
|
82
|
+
const newChannel = this.getChannel(channelName);
|
|
83
|
+
|
|
84
|
+
if (newChannel) {
|
|
85
|
+
return handler(newChannel);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
throw new Error(`GatewayEngine: Channel ${channelName} does not exist`);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* @desc Gets a channel by name
|
|
93
|
+
* @param channelName - The name of the channel to get
|
|
94
|
+
* @private
|
|
95
|
+
*/
|
|
96
|
+
public getChannel (channelName: string): ChannelEngine | undefined {
|
|
97
|
+
return Array.from(this.#channels)
|
|
98
|
+
.find((channel) => channel.name === channelName);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* @desc Destroys a channel
|
|
103
|
+
* @param channel - The name of the channel to destroy
|
|
104
|
+
* @private
|
|
105
|
+
*/
|
|
106
|
+
public destroyChannel (channel: string) {
|
|
107
|
+
const newChannel = this.getChannel(channel);
|
|
108
|
+
|
|
109
|
+
if (newChannel) {
|
|
110
|
+
this.#channels.delete(newChannel);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* @desc Lists all channels
|
|
116
|
+
* @private
|
|
117
|
+
*/
|
|
118
|
+
public listChannels () {
|
|
119
|
+
return Array.from(this.#channels)
|
|
120
|
+
.map((channel) => channel.name);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* @desc Creates a new channel
|
|
125
|
+
* @param channelName - The name of the channel to create
|
|
126
|
+
* @private
|
|
127
|
+
*/
|
|
128
|
+
public createChannel (channelName: string): ChannelEngine {
|
|
129
|
+
const destroyChannel = this.destroyChannel.bind(this, channelName);
|
|
130
|
+
const execute = this.#middleware.run.bind(this.#middleware);
|
|
131
|
+
|
|
132
|
+
const parentEngine: ParentEngine = {
|
|
133
|
+
execute,
|
|
134
|
+
destroyChannel,
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
const newChannel: ChannelEngine = new ChannelEngine(channelName, parentEngine);
|
|
138
|
+
|
|
139
|
+
this.#channels.add(newChannel);
|
|
140
|
+
|
|
141
|
+
return newChannel;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
export class PondChannel {
|
|
146
|
+
readonly #lobby: LobbyEngine;
|
|
147
|
+
|
|
148
|
+
constructor (lobby: LobbyEngine) {
|
|
149
|
+
this.#lobby = lobby;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* @desc Handles an event request made by a user
|
|
154
|
+
* @param event - The event to listen for
|
|
155
|
+
* @param handler - The handler to execute when the event is received
|
|
156
|
+
* @example
|
|
157
|
+
* pond.onEvent('echo', (request, response) => {
|
|
158
|
+
* response.send('echo', {
|
|
159
|
+
* message: request.event.payload,
|
|
160
|
+
* });
|
|
161
|
+
* });
|
|
162
|
+
*/
|
|
163
|
+
public onEvent<Event extends string> (event: PondPath<Event>, handler: (request: EventRequest<Event>, response: EventResponse) => void | Promise<void>) {
|
|
164
|
+
this.#lobby.onEvent(event, handler);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* @desc Broadcasts a message to all users in a channel
|
|
169
|
+
* @param event - The event to broadcast
|
|
170
|
+
* @param payload - The payload to send
|
|
171
|
+
* @param channelName - The channel to broadcast to (if not specified, broadcast to all channels)
|
|
172
|
+
* @example
|
|
173
|
+
* pond.broadcast('echo', {
|
|
174
|
+
* message: 'Hello World',
|
|
175
|
+
* timestamp: Date.now(),
|
|
176
|
+
* channel: 'my_channel',
|
|
177
|
+
*});
|
|
178
|
+
*/
|
|
179
|
+
public broadcast (event: string, payload: PondMessage, channelName?: string) {
|
|
180
|
+
this.#lobby.broadcast(event, payload, channelName);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { parseAddress } from './matcher';
|
|
2
|
+
|
|
3
|
+
describe('parseAddress', () => {
|
|
4
|
+
it('should return null if a string does not match a regex and {} if it does', () => {
|
|
5
|
+
const regex = new RegExp(/^test/);
|
|
6
|
+
|
|
7
|
+
expect(parseAddress(regex, 'test')).toStrictEqual({
|
|
8
|
+
params: {},
|
|
9
|
+
query: {},
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
const regex2 = new RegExp(/^test2/);
|
|
13
|
+
|
|
14
|
+
expect(parseAddress(regex2, 'test')).toBe(null);
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it('should return the params of a string matching the pattern', () => {
|
|
18
|
+
const pattern = '/test/:id';
|
|
19
|
+
const secondPattern = '/test/:id/:id2';
|
|
20
|
+
const string = '/test/5';
|
|
21
|
+
const secondString = '/test/5/6';
|
|
22
|
+
|
|
23
|
+
expect(parseAddress(pattern, string))
|
|
24
|
+
.toStrictEqual({
|
|
25
|
+
params: {
|
|
26
|
+
id: '5',
|
|
27
|
+
},
|
|
28
|
+
query: {},
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
expect(parseAddress(secondPattern, secondString))
|
|
32
|
+
.toStrictEqual({
|
|
33
|
+
params: {
|
|
34
|
+
id: '5',
|
|
35
|
+
id2: '6',
|
|
36
|
+
},
|
|
37
|
+
query: {},
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
expect(parseAddress(pattern, secondString))
|
|
41
|
+
.toBe(null);
|
|
42
|
+
|
|
43
|
+
expect(parseAddress(secondPattern, string))
|
|
44
|
+
.toBe(null);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it('should not match colons without slashes', () => {
|
|
48
|
+
const pattern = '/test:id';
|
|
49
|
+
const string = '/test5';
|
|
50
|
+
|
|
51
|
+
expect(parseAddress(pattern, string))
|
|
52
|
+
.toBe(null);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it('should match when there is a *', () => {
|
|
56
|
+
const pattern = '/test/:id/*';
|
|
57
|
+
const string = '/test/5/6';
|
|
58
|
+
|
|
59
|
+
expect(parseAddress(pattern, string))
|
|
60
|
+
.toStrictEqual({
|
|
61
|
+
params: {
|
|
62
|
+
id: '5',
|
|
63
|
+
},
|
|
64
|
+
query: {},
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
const secondPattern = '*';
|
|
68
|
+
const secondString = '/test/5/6';
|
|
69
|
+
|
|
70
|
+
expect(parseAddress(secondPattern, secondString))
|
|
71
|
+
.toStrictEqual({
|
|
72
|
+
params: {},
|
|
73
|
+
query: {},
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it('should return the query of string', () => {
|
|
78
|
+
const pattern = '/test/:id';
|
|
79
|
+
const string = '/test/5?test=5';
|
|
80
|
+
const secondString = '/test/5?test=5&test2=6';
|
|
81
|
+
|
|
82
|
+
expect(parseAddress(pattern, string))
|
|
83
|
+
.toStrictEqual({
|
|
84
|
+
params: {
|
|
85
|
+
id: '5',
|
|
86
|
+
},
|
|
87
|
+
query: {
|
|
88
|
+
test: '5',
|
|
89
|
+
},
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
expect(parseAddress(pattern, secondString))
|
|
93
|
+
.toStrictEqual({
|
|
94
|
+
params: {
|
|
95
|
+
id: '5',
|
|
96
|
+
},
|
|
97
|
+
query: {
|
|
98
|
+
test: '5',
|
|
99
|
+
test2: '6',
|
|
100
|
+
},
|
|
101
|
+
});
|
|
102
|
+
});
|
|
103
|
+
});
|