@colyseus/core 0.17.0 → 0.17.1

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.
@@ -0,0 +1,263 @@
1
+ import type { Room } from "../Room.ts";
2
+
3
+ /**
4
+ * When you need to scale your server on multiple processes and/or machines, you'd need to provide
5
+ * the Presence option to the Server. The purpose of Presence is to allow communicating and
6
+ * sharing data between different processes, specially during match-making.
7
+ *
8
+ * - Local presence - This is the default option. It's meant to be used when you're running Colyseus in
9
+ * a single process.
10
+ * - Redis presence - Use this option when you're running Colyseus on multiple processes and/or machines.
11
+ *
12
+ * @default Local presence
13
+ */
14
+ export interface Presence {
15
+ /**
16
+ * Subscribes to the given topic. The callback will be triggered whenever a message is published on topic.
17
+ *
18
+ * @param topic - Topic name.
19
+ * @param callback - Callback to trigger on subscribing.
20
+ */
21
+ subscribe(topic: string, callback: Function): Promise<this>;
22
+
23
+ /**
24
+ * Unsubscribe from given topic.
25
+ *
26
+ * @param topic - Topic name.
27
+ * @param callback - Callback to trigger on topic unsubscribing.
28
+ */
29
+ unsubscribe(topic: string, callback?: Function);
30
+
31
+ /**
32
+ * Lists the currently active channels / subscriptions
33
+ * @param pattern
34
+ */
35
+ channels(pattern?: string): Promise<string[]>;
36
+
37
+ /**
38
+ * Posts a message to given topic.
39
+ *
40
+ * @param topic - Topic name.
41
+ * @param data - Message body/object.
42
+ */
43
+ publish(topic: string, data: any);
44
+
45
+ /**
46
+ * Returns if key exists.
47
+ *
48
+ * @param key
49
+ */
50
+ exists(key: string): Promise<boolean>;
51
+
52
+ /**
53
+ * Set key to hold the string value.
54
+ *
55
+ * @param key - Identifier.
56
+ * @param value - Message body/object.
57
+ */
58
+ set(key: string, value: string);
59
+
60
+ /**
61
+ * Set key to hold the string value and set key to timeout after a given number of seconds.
62
+ *
63
+ * @param key - Identifier.
64
+ * @param value - Message body/object.
65
+ * @param seconds - Timeout value.
66
+ */
67
+ setex(key: string, value: string, seconds: number);
68
+
69
+ /**
70
+ * Expire the key in seconds.
71
+ *
72
+ * @param key - Identifier.
73
+ * @param seconds - Seconds to expire the key.
74
+ */
75
+ expire(key: string, seconds: number);
76
+
77
+ /**
78
+ * Get the value of key.
79
+ *
80
+ * @param key - Identifier.
81
+ */
82
+ get(key: string);
83
+
84
+ /**
85
+ * Removes the specified key.
86
+ *
87
+ * @param key - Identifier of the object to removed.
88
+ */
89
+ del(key: string): void;
90
+
91
+ /**
92
+ * Add the specified members to the set stored at key. Specified members that are already
93
+ * a member of this set are ignored. If key does not exist, a new set is created before
94
+ * adding the specified members.
95
+ *
96
+ * @param key - Name/Identifier of the set.
97
+ * @param value - Message body/object.
98
+ */
99
+ sadd(key: string, value: any);
100
+
101
+ /**
102
+ * Returns all the members of the set value stored at key.
103
+ *
104
+ * @param key - Name/Identifier of the set.
105
+ */
106
+ smembers(key: string): Promise<string[]>;
107
+
108
+ /**
109
+ * Returns if member is a member of the set stored at key.
110
+ *
111
+ * @param key - Name/Identifier of the set.
112
+ * @param field - Key value within the set.
113
+ * @returns `1` if the element is a member of the set else `0`.
114
+ */
115
+ sismember(key: string, field: string);
116
+
117
+ /**
118
+ * Remove the specified members from the set stored at key. Specified members that are not a
119
+ * member of this set are ignored. If key does not exist, it is treated as an empty set
120
+ * and this command returns 0.
121
+ *
122
+ * @param key - Name/Identifier of the set.
123
+ * @param value - Key value within the set.
124
+ */
125
+ srem(key: string, value: any);
126
+
127
+ /**
128
+ * Returns the set cardinality (number of elements) of the set stored at key.
129
+ *
130
+ * @param key - Name/Identifier of the set.
131
+ */
132
+ scard(key: string);
133
+
134
+ /**
135
+ * Returns the members of the set resulting from the intersection of all the given sets.
136
+ *
137
+ * @param keys - Key values within the set.
138
+ */
139
+ sinter(...keys: string[]): Promise<string[]>;
140
+
141
+ /**
142
+ * Sets field in the hash stored at key to value. If key does not exist, a new key holding a hash is created.
143
+ * If field already exists in the hash, it is overwritten.
144
+ */
145
+ hset(key: string, field: string, value: string): Promise<boolean>;
146
+
147
+ /**
148
+ * Increments the number stored at field in the hash stored at key by increment. If key does not exist, a new key
149
+ * holding a hash is created. If field does not exist the value is set to 0 before the operation is performed.
150
+ */
151
+ hincrby(key: string, field: string, value: number): Promise<number>;
152
+
153
+ /**
154
+ * WARNING: DO NOT USE THIS METHOD. It is meant for internal use only.
155
+ * @private
156
+ */
157
+ hincrbyex(key: string, field: string, value: number, expireInSeconds: number): Promise<number>;
158
+
159
+ /**
160
+ * Returns the value associated with field in the hash stored at key.
161
+ */
162
+ hget(key: string, field: string): Promise<string | null>;
163
+
164
+ /**
165
+ * Returns all fields and values of the hash stored at key.
166
+ */
167
+ hgetall(key: string): Promise<{ [key: string]: string }>;
168
+
169
+ /**
170
+ * Removes the specified fields from the hash stored at key. Specified fields that do not exist within
171
+ * this hash are ignored. If key does not exist, it is treated as an empty hash and this command returns 0.
172
+ */
173
+ hdel(key: string, field: string): Promise<boolean>;
174
+
175
+ /**
176
+ * Returns the number of fields contained in the hash stored at key
177
+ */
178
+ hlen(key: string): Promise<number>;
179
+
180
+ /**
181
+ * Increments the number stored at key by one. If the key does not exist, it is set to 0 before performing
182
+ * the operation. An error is returned if the key contains a value of the wrong type or
183
+ * contains a string that can not be represented as integer. This operation is limited to 64-bit signed integers.
184
+ */
185
+ incr(key: string): Promise<number>;
186
+
187
+ /**
188
+ * Decrements the number stored at key by one. If the key does not exist, it is set to 0 before performing
189
+ * the operation. An error is returned if the key contains a value of the wrong type or contains a string
190
+ * that can not be represented as integer. This operation is limited to 64-bit signed integers.
191
+ */
192
+ decr(key: string): Promise<number>;
193
+
194
+ /**
195
+ * Returns the length of the list stored at key.
196
+ */
197
+ llen(key: string): Promise<number>;
198
+
199
+ /**
200
+ * Adds the string value to the end of the list stored at key. If key does not exist, it is created as empty list before performing the push operation.
201
+ */
202
+ rpush(key: string, ...values: string[]): Promise<number>;
203
+
204
+ /**
205
+ * Adds the string value to the begginning of the list stored at key. If key does not exist, it is created as empty list before performing the push operation.
206
+ */
207
+ lpush(key: string, ...values: string[]): Promise<number>;
208
+
209
+ /**
210
+ * Removes and returns the last element of the list stored at key.
211
+ */
212
+ rpop(key: string): Promise<string | null>;
213
+
214
+ /**
215
+ * Removes and returns the first element of the list stored at key.
216
+ */
217
+ lpop(key: string): Promise<string | null>;
218
+
219
+ /**
220
+ * Removes and returns the last element of the list stored at key. If the list is empty, the execution is halted until an element is available or the timeout is reached.
221
+ */
222
+ brpop(...args: [...keys: string[], timeoutInSeconds: number]): Promise<[string, string] | null>;
223
+
224
+ setMaxListeners(number: number): void;
225
+
226
+ shutdown(): void;
227
+ }
228
+
229
+ export function createScopedPresence(room: Room, presence: Presence): Presence {
230
+ // Keep a local copy of all subscriptions made through this scoped presence
231
+ const subscriptions: Array<{ topic: string; callback: Function }> = [];
232
+
233
+ // Create a copy of the presence object
234
+ const scopedPresence = Object.create(presence) as Presence;
235
+
236
+ // Override subscribe method to track subscriptions
237
+ scopedPresence.subscribe = async function (topic: string, callback: Function): Promise<Presence> {
238
+ subscriptions.push({ topic, callback });
239
+ await presence.subscribe(topic, callback);
240
+ return scopedPresence;
241
+ };
242
+
243
+ // Override unsubscribe method to remove from tracking
244
+ scopedPresence.unsubscribe = function (topic: string, callback?: Function) {
245
+ const index = subscriptions.findIndex(
246
+ (sub) => sub.topic === topic && (!callback || sub.callback === callback)
247
+ );
248
+ if (index !== -1) {
249
+ subscriptions.splice(index, 1);
250
+ }
251
+ presence.unsubscribe(topic, callback);
252
+ };
253
+
254
+ // Clean up all subscriptions when the room is disposed
255
+ room['_events'].on('dispose', () => {
256
+ for (const { topic, callback } of subscriptions) {
257
+ presence.unsubscribe(topic, callback);
258
+ }
259
+ subscriptions.length = 0;
260
+ });
261
+
262
+ return scopedPresence;
263
+ }
@@ -0,0 +1,135 @@
1
+
2
+ import * as matchMaker from '../MatchMaker.ts';
3
+ import type { IRoomCache } from '../matchmaker/LocalDriver/LocalDriver.ts';
4
+ import type { Client } from '../Transport.ts';
5
+ import { subscribeLobby } from '../matchmaker/Lobby.ts';
6
+ import { Room } from '../Room.ts';
7
+
8
+ // TODO: use Schema state & filters on version 1.0.0
9
+
10
+ // class DummyLobbyState extends Schema { // tslint:disable-line
11
+ // @type("number") public _: number;
12
+ // }
13
+
14
+ export interface FilterInput {
15
+ name?: string;
16
+ metadata?: any;
17
+ }
18
+
19
+ export interface LobbyOptions {
20
+ filter?: FilterInput;
21
+ }
22
+
23
+ export class LobbyRoom extends Room {
24
+ public rooms: IRoomCache[] = [];
25
+ public unsubscribeLobby: () => void;
26
+
27
+ public clientOptions: { [sessionId: string]: LobbyOptions } = {};
28
+
29
+ public async onCreate(options: any) {
30
+ // prevent LobbyRoom to notify itself
31
+ this['_listing'].unlisted = true;
32
+
33
+ this.unsubscribeLobby = await subscribeLobby((roomId, data) => {
34
+ const roomIndex = this.rooms.findIndex((room) => room.roomId === roomId);
35
+ const clients = this.clients.filter((client) => this.clientOptions[client.sessionId]);
36
+
37
+ if (!data) {
38
+ // remove room listing data
39
+ if (roomIndex !== -1) {
40
+ const previousData = this.rooms[roomIndex];
41
+
42
+ this.rooms.splice(roomIndex, 1);
43
+
44
+ clients.forEach((client) => {
45
+ if (this.filterItemForClient(previousData, this.clientOptions[client.sessionId].filter)) {
46
+ client.send('-', roomId);
47
+ }
48
+ });
49
+ }
50
+
51
+ } else if (roomIndex === -1) {
52
+ // append room listing data
53
+ this.rooms.push(data);
54
+
55
+ clients.forEach((client) => {
56
+ if (this.filterItemForClient(data, this.clientOptions[client.sessionId].filter)) {
57
+ client.send('+', [roomId, data]);
58
+ }
59
+ });
60
+
61
+ } else {
62
+ const previousData = this.rooms[roomIndex];
63
+
64
+ // replace room listing data
65
+ this.rooms[roomIndex] = data;
66
+
67
+ clients.forEach((client) => {
68
+ const hadData = this.filterItemForClient(previousData, this.clientOptions[client.sessionId].filter);
69
+ const hasData = this.filterItemForClient(data, this.clientOptions[client.sessionId].filter);
70
+
71
+ if (hadData && !hasData) {
72
+ client.send('-', roomId);
73
+
74
+ } else if (hasData) {
75
+ client.send('+', [roomId, data]);
76
+ }
77
+ });
78
+ }
79
+ });
80
+
81
+ this.rooms = await matchMaker.query({ private: false, unlisted: false });
82
+
83
+ this.onMessage('filter', (client: Client, filter: FilterInput) => {
84
+ this.clientOptions[client.sessionId].filter = filter;
85
+ client.send('rooms', this.filterItemsForClient(this.clientOptions[client.sessionId]));
86
+ });
87
+ }
88
+
89
+ public onJoin(client: Client, options: LobbyOptions) {
90
+ this.clientOptions[client.sessionId] = options || {};
91
+ client.send('rooms', this.filterItemsForClient(this.clientOptions[client.sessionId]));
92
+ }
93
+
94
+ public onLeave(client: Client) {
95
+ delete this.clientOptions[client.sessionId];
96
+ }
97
+
98
+ public onDispose() {
99
+ if (this.unsubscribeLobby) {
100
+ this.unsubscribeLobby();
101
+ }
102
+ }
103
+
104
+ protected filterItemsForClient(options: LobbyOptions) {
105
+ const filter = options.filter;
106
+
107
+ return (filter)
108
+ ? this.rooms.filter((room) => this.filterItemForClient(room, filter))
109
+ : this.rooms;
110
+ }
111
+
112
+ protected filterItemForClient(room: IRoomCache, filter?: LobbyOptions['filter']) {
113
+ if (!filter) {
114
+ return true;
115
+ }
116
+
117
+ let isAllowed = true;
118
+
119
+ if (filter.name !== room.name) {
120
+ isAllowed = false;
121
+ }
122
+
123
+ if (filter.metadata) {
124
+ for (const field in filter.metadata) {
125
+ if (room.metadata[field] !== filter.metadata[field]) {
126
+ isAllowed = false;
127
+ break;
128
+ }
129
+ }
130
+ }
131
+
132
+ return isAllowed;
133
+ }
134
+
135
+ }