@eleven-am/pondsocket 0.1.177 → 0.1.179
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/abstracts/redisClient.js +159 -51
- package/engines/endpointEngine.js +3 -12
- package/engines/lobbyEngine.js +5 -16
- package/errors/httpError.js +33 -0
- package/errors/redisError.js +10 -0
- package/index.js +3 -0
- package/managers/distributedManager.js +34 -45
- package/managers/manager.js +0 -1
- package/managers/managerFactory.js +10 -21
- package/package.json +2 -2
- package/types.d.ts +7 -0
package/abstracts/redisClient.js
CHANGED
|
@@ -33,16 +33,19 @@ var __rest = (this && this.__rest) || function (s, e) {
|
|
|
33
33
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
34
34
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
35
35
|
};
|
|
36
|
-
var _RedisClient_instances, _RedisClient_redisClient, _RedisClient_pubClient, _RedisClient_subClient, _RedisClient_instanceId, _RedisClient_assignsPublisher, _RedisClient_presencePublisher, _RedisClient_userLeavesPublisher, _RedisClient_channelMessagePublisher,
|
|
36
|
+
var _RedisClient_instances, _RedisClient_heartbeatInterval, _RedisClient_cleanupInterval, _RedisClient_instanceTtl, _RedisClient_redisClient, _RedisClient_pubClient, _RedisClient_subClient, _RedisClient_instanceId, _RedisClient_assignsPublisher, _RedisClient_presencePublisher, _RedisClient_userLeavesPublisher, _RedisClient_channelMessagePublisher, _RedisClient_stateSyncPublisher, _RedisClient_heartbeatTimer, _RedisClient_cleanupTimer, _RedisClient_presence_changes_channel_get, _RedisClient_assigns_changes_channel_get, _RedisClient_channel_messages_channel_get, _RedisClient_user_leaves_channel_get, _RedisClient_cleanup, _RedisClient_handleErrors, _RedisClient_getPresenceCacheChannel, _RedisClient_getAssignsCacheChannel, _RedisClient_publishPresenceChange, _RedisClient_publishAssignsChange, _RedisClient_publishChannelMessage, _RedisClient_publishUserLeave, _RedisClient_registerInstance, _RedisClient_unregisterInstance, _RedisClient_unsubscribeFromChannels, _RedisClient_subscribeToChannels, _RedisClient_startHeartbeat, _RedisClient_startPeriodicCleanup, _RedisClient_performConsistencyCheck, _RedisClient_handleRedisMessage, _RedisClient_publishCacheMessage, _RedisClient_emitStateSyncEvent, _RedisClient_subscribeToCacheChanges, _RedisClient_subscribeToChannelMessages, _RedisClient_subscribeToUserLeaves, _RedisClient_subscribeToStateSync, _RedisClient_handleExit;
|
|
37
37
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
38
38
|
exports.RedisClient = void 0;
|
|
39
39
|
const pondsocket_common_1 = require("@eleven-am/pondsocket-common");
|
|
40
40
|
const ioredis_1 = __importDefault(require("ioredis"));
|
|
41
|
+
const redisError_1 = require("../errors/redisError");
|
|
41
42
|
class RedisClient {
|
|
42
|
-
constructor(
|
|
43
|
+
constructor(config) {
|
|
44
|
+
var _a, _b, _c;
|
|
43
45
|
_RedisClient_instances.add(this);
|
|
44
|
-
this
|
|
45
|
-
this
|
|
46
|
+
_RedisClient_heartbeatInterval.set(this, void 0);
|
|
47
|
+
_RedisClient_cleanupInterval.set(this, void 0);
|
|
48
|
+
_RedisClient_instanceTtl.set(this, void 0);
|
|
46
49
|
_RedisClient_redisClient.set(this, void 0);
|
|
47
50
|
_RedisClient_pubClient.set(this, void 0);
|
|
48
51
|
_RedisClient_subClient.set(this, void 0);
|
|
@@ -51,21 +54,25 @@ class RedisClient {
|
|
|
51
54
|
_RedisClient_presencePublisher.set(this, void 0);
|
|
52
55
|
_RedisClient_userLeavesPublisher.set(this, void 0);
|
|
53
56
|
_RedisClient_channelMessagePublisher.set(this, void 0);
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
__classPrivateFieldSet(this,
|
|
57
|
+
_RedisClient_stateSyncPublisher.set(this, void 0);
|
|
58
|
+
_RedisClient_heartbeatTimer.set(this, void 0);
|
|
59
|
+
_RedisClient_cleanupTimer.set(this, void 0);
|
|
60
|
+
__classPrivateFieldSet(this, _RedisClient_instanceTtl, (_a = config.instanceTtl) !== null && _a !== void 0 ? _a : 90, "f");
|
|
61
|
+
__classPrivateFieldSet(this, _RedisClient_heartbeatInterval, (_b = config.heartbeatInterval) !== null && _b !== void 0 ? _b : 10 * 1000, "f");
|
|
62
|
+
__classPrivateFieldSet(this, _RedisClient_cleanupInterval, (_c = config.cleanupInterval) !== null && _c !== void 0 ? _c : 60 * 1000, "f");
|
|
63
|
+
__classPrivateFieldSet(this, _RedisClient_redisClient, new ioredis_1.default(config), "f");
|
|
64
|
+
__classPrivateFieldSet(this, _RedisClient_pubClient, new ioredis_1.default(config), "f");
|
|
65
|
+
__classPrivateFieldSet(this, _RedisClient_subClient, new ioredis_1.default(config), "f");
|
|
58
66
|
__classPrivateFieldSet(this, _RedisClient_assignsPublisher, new pondsocket_common_1.Subject(), "f");
|
|
59
67
|
__classPrivateFieldSet(this, _RedisClient_presencePublisher, new pondsocket_common_1.Subject(), "f");
|
|
60
68
|
__classPrivateFieldSet(this, _RedisClient_userLeavesPublisher, new pondsocket_common_1.Subject(), "f");
|
|
61
69
|
__classPrivateFieldSet(this, _RedisClient_channelMessagePublisher, new pondsocket_common_1.Subject(), "f");
|
|
70
|
+
__classPrivateFieldSet(this, _RedisClient_stateSyncPublisher, new pondsocket_common_1.Subject(), "f");
|
|
62
71
|
__classPrivateFieldSet(this, _RedisClient_instanceId, (0, pondsocket_common_1.uuid)(), "f");
|
|
63
72
|
}
|
|
64
73
|
buildClient(endpointId) {
|
|
65
74
|
return (channelId) => ({
|
|
66
75
|
channelId,
|
|
67
|
-
getPresenceCache: __classPrivateFieldGet(this, _RedisClient_instances, "m", _RedisClient_getPresenceCache).bind(this, endpointId, channelId),
|
|
68
|
-
getAssignsCache: __classPrivateFieldGet(this, _RedisClient_instances, "m", _RedisClient_getAssignsCache).bind(this, endpointId, channelId),
|
|
69
76
|
publishPresenceChange: __classPrivateFieldGet(this, _RedisClient_instances, "m", _RedisClient_publishPresenceChange).bind(this, endpointId, channelId),
|
|
70
77
|
publishAssignsChange: __classPrivateFieldGet(this, _RedisClient_instances, "m", _RedisClient_publishAssignsChange).bind(this, endpointId, channelId),
|
|
71
78
|
publishChannelMessage: __classPrivateFieldGet(this, _RedisClient_instances, "m", _RedisClient_publishChannelMessage).bind(this, endpointId, channelId),
|
|
@@ -74,13 +81,15 @@ class RedisClient {
|
|
|
74
81
|
subscribeToPresenceChanges: __classPrivateFieldGet(this, _RedisClient_instances, "m", _RedisClient_subscribeToCacheChanges).bind(this, endpointId, channelId, true),
|
|
75
82
|
subscribeToAssignsChanges: __classPrivateFieldGet(this, _RedisClient_instances, "m", _RedisClient_subscribeToCacheChanges).bind(this, endpointId, channelId, false),
|
|
76
83
|
subscribeToChannelMessages: __classPrivateFieldGet(this, _RedisClient_instances, "m", _RedisClient_subscribeToChannelMessages).bind(this, endpointId, channelId),
|
|
84
|
+
subscribeToStateSync: __classPrivateFieldGet(this, _RedisClient_instances, "m", _RedisClient_subscribeToStateSync).bind(this, endpointId, channelId),
|
|
77
85
|
});
|
|
78
86
|
}
|
|
79
87
|
initialize() {
|
|
80
88
|
return __awaiter(this, void 0, void 0, function* () {
|
|
81
89
|
__classPrivateFieldGet(this, _RedisClient_instances, "m", _RedisClient_handleErrors).call(this);
|
|
82
|
-
__classPrivateFieldGet(this, _RedisClient_instances, "m", _RedisClient_startTTLRefresh).call(this);
|
|
83
90
|
yield __classPrivateFieldGet(this, _RedisClient_instances, "m", _RedisClient_registerInstance).call(this);
|
|
91
|
+
__classPrivateFieldGet(this, _RedisClient_instances, "m", _RedisClient_startHeartbeat).call(this);
|
|
92
|
+
__classPrivateFieldGet(this, _RedisClient_instances, "m", _RedisClient_startPeriodicCleanup).call(this);
|
|
84
93
|
yield __classPrivateFieldGet(this, _RedisClient_instances, "m", _RedisClient_subscribeToChannels).call(this);
|
|
85
94
|
process.on('SIGINT', __classPrivateFieldGet(this, _RedisClient_instances, "m", _RedisClient_handleExit).bind(this));
|
|
86
95
|
process.on('SIGTERM', __classPrivateFieldGet(this, _RedisClient_instances, "m", _RedisClient_handleExit).bind(this));
|
|
@@ -88,7 +97,8 @@ class RedisClient {
|
|
|
88
97
|
}
|
|
89
98
|
shutdown() {
|
|
90
99
|
return __awaiter(this, void 0, void 0, function* () {
|
|
91
|
-
clearInterval(__classPrivateFieldGet(this,
|
|
100
|
+
clearInterval(__classPrivateFieldGet(this, _RedisClient_heartbeatTimer, "f"));
|
|
101
|
+
clearInterval(__classPrivateFieldGet(this, _RedisClient_cleanupTimer, "f"));
|
|
92
102
|
yield __classPrivateFieldGet(this, _RedisClient_instances, "m", _RedisClient_unsubscribeFromChannels).call(this);
|
|
93
103
|
yield __classPrivateFieldGet(this, _RedisClient_instances, "m", _RedisClient_unregisterInstance).call(this);
|
|
94
104
|
yield __classPrivateFieldGet(this, _RedisClient_instances, "m", _RedisClient_cleanup).call(this);
|
|
@@ -96,7 +106,7 @@ class RedisClient {
|
|
|
96
106
|
}
|
|
97
107
|
}
|
|
98
108
|
exports.RedisClient = RedisClient;
|
|
99
|
-
_RedisClient_redisClient = new WeakMap(), _RedisClient_pubClient = new WeakMap(), _RedisClient_subClient = new WeakMap(), _RedisClient_instanceId = new WeakMap(), _RedisClient_assignsPublisher = new WeakMap(), _RedisClient_presencePublisher = new WeakMap(), _RedisClient_userLeavesPublisher = new WeakMap(), _RedisClient_channelMessagePublisher = new WeakMap(),
|
|
109
|
+
_RedisClient_heartbeatInterval = new WeakMap(), _RedisClient_cleanupInterval = new WeakMap(), _RedisClient_instanceTtl = new WeakMap(), _RedisClient_redisClient = new WeakMap(), _RedisClient_pubClient = new WeakMap(), _RedisClient_subClient = new WeakMap(), _RedisClient_instanceId = new WeakMap(), _RedisClient_assignsPublisher = new WeakMap(), _RedisClient_presencePublisher = new WeakMap(), _RedisClient_userLeavesPublisher = new WeakMap(), _RedisClient_channelMessagePublisher = new WeakMap(), _RedisClient_stateSyncPublisher = new WeakMap(), _RedisClient_heartbeatTimer = new WeakMap(), _RedisClient_cleanupTimer = new WeakMap(), _RedisClient_instances = new WeakSet(), _RedisClient_presence_changes_channel_get = function _RedisClient_presence_changes_channel_get() {
|
|
100
110
|
return 'presence_changes';
|
|
101
111
|
}, _RedisClient_assigns_changes_channel_get = function _RedisClient_assigns_changes_channel_get() {
|
|
102
112
|
return 'assigns_changes';
|
|
@@ -118,9 +128,9 @@ _RedisClient_redisClient = new WeakMap(), _RedisClient_pubClient = new WeakMap()
|
|
|
118
128
|
__classPrivateFieldGet(this, _RedisClient_pubClient, "f").on('error', (err) => console.error('Redis pub client error:', err));
|
|
119
129
|
__classPrivateFieldGet(this, _RedisClient_subClient, "f").on('error', (err) => console.error('Redis sub client error:', err));
|
|
120
130
|
}, _RedisClient_getPresenceCacheChannel = function _RedisClient_getPresenceCacheChannel(endpointId, channelId) {
|
|
121
|
-
return `presence_cache:${endpointId}:${channelId}`;
|
|
131
|
+
return `presence_cache:${__classPrivateFieldGet(this, _RedisClient_instanceId, "f")}:${endpointId}:${channelId}`;
|
|
122
132
|
}, _RedisClient_getAssignsCacheChannel = function _RedisClient_getAssignsCacheChannel(endpointId, channelId) {
|
|
123
|
-
return `assigns_cache:${endpointId}:${channelId}`;
|
|
133
|
+
return `assigns_cache:${__classPrivateFieldGet(this, _RedisClient_instanceId, "f")}:${endpointId}:${channelId}`;
|
|
124
134
|
}, _RedisClient_publishPresenceChange = function _RedisClient_publishPresenceChange(endpointId, channelId, userId, state) {
|
|
125
135
|
const message = {
|
|
126
136
|
userId,
|
|
@@ -159,34 +169,28 @@ _RedisClient_redisClient = new WeakMap(), _RedisClient_pubClient = new WeakMap()
|
|
|
159
169
|
});
|
|
160
170
|
yield __classPrivateFieldGet(this, _RedisClient_pubClient, "f").publish(__classPrivateFieldGet(this, _RedisClient_instances, "a", _RedisClient_user_leaves_channel_get), message);
|
|
161
171
|
});
|
|
162
|
-
}, _RedisClient_getPresenceCache = function _RedisClient_getPresenceCache(endpointId, channelId) {
|
|
163
|
-
const cacheKey = __classPrivateFieldGet(this, _RedisClient_instances, "m", _RedisClient_getPresenceCacheChannel).call(this, endpointId, channelId);
|
|
164
|
-
return __classPrivateFieldGet(this, _RedisClient_instances, "m", _RedisClient_readCachedData).call(this, cacheKey);
|
|
165
|
-
}, _RedisClient_getAssignsCache = function _RedisClient_getAssignsCache(endpointId, channelId) {
|
|
166
|
-
const cacheKey = __classPrivateFieldGet(this, _RedisClient_instances, "m", _RedisClient_getAssignsCacheChannel).call(this, endpointId, channelId);
|
|
167
|
-
return __classPrivateFieldGet(this, _RedisClient_instances, "m", _RedisClient_readCachedData).call(this, cacheKey);
|
|
168
|
-
}, _RedisClient_sendHeartbeat = function _RedisClient_sendHeartbeat() {
|
|
169
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
170
|
-
yield __classPrivateFieldGet(this, _RedisClient_redisClient, "f").expire(`instance:${__classPrivateFieldGet(this, _RedisClient_instanceId, "f")}`, this.INSTANCE_TTL);
|
|
171
|
-
});
|
|
172
|
-
}, _RedisClient_startTTLRefresh = function _RedisClient_startTTLRefresh() {
|
|
173
|
-
__classPrivateFieldSet(this, _RedisClient_ttlRefreshInterval, setInterval(() => __classPrivateFieldGet(this, _RedisClient_instances, "m", _RedisClient_sendHeartbeat).call(this), this.TTL_REFRESH_INTERVAL), "f");
|
|
174
172
|
}, _RedisClient_registerInstance = function _RedisClient_registerInstance() {
|
|
175
173
|
return __awaiter(this, void 0, void 0, function* () {
|
|
176
174
|
const multi = __classPrivateFieldGet(this, _RedisClient_redisClient, "f").multi();
|
|
177
|
-
multi.sadd('distributed_instances
|
|
178
|
-
multi.
|
|
179
|
-
|
|
175
|
+
multi.sadd('distributed_instances', __classPrivateFieldGet(this, _RedisClient_instanceId, "f"));
|
|
176
|
+
multi.set(`heartbeat:${__classPrivateFieldGet(this, _RedisClient_instanceId, "f")}`, Date.now().toString(), 'EX', __classPrivateFieldGet(this, _RedisClient_instanceTtl, "f"));
|
|
177
|
+
try {
|
|
178
|
+
yield multi.exec();
|
|
179
|
+
}
|
|
180
|
+
catch (error) {
|
|
181
|
+
throw new redisError_1.RedisError('Error registering instance');
|
|
182
|
+
}
|
|
180
183
|
});
|
|
181
184
|
}, _RedisClient_unregisterInstance = function _RedisClient_unregisterInstance() {
|
|
182
185
|
return __awaiter(this, void 0, void 0, function* () {
|
|
183
186
|
const multi = __classPrivateFieldGet(this, _RedisClient_redisClient, "f").multi();
|
|
184
187
|
multi.srem('distributed_instances', __classPrivateFieldGet(this, _RedisClient_instanceId, "f"));
|
|
185
|
-
multi.del(`
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
188
|
+
multi.del(`heartbeat:${__classPrivateFieldGet(this, _RedisClient_instanceId, "f")}`);
|
|
189
|
+
try {
|
|
190
|
+
yield multi.exec();
|
|
191
|
+
}
|
|
192
|
+
catch (_a) {
|
|
193
|
+
// no-op as we're shutting down
|
|
190
194
|
}
|
|
191
195
|
});
|
|
192
196
|
}, _RedisClient_unsubscribeFromChannels = function _RedisClient_unsubscribeFromChannels() {
|
|
@@ -205,6 +209,64 @@ _RedisClient_redisClient = new WeakMap(), _RedisClient_pubClient = new WeakMap()
|
|
|
205
209
|
});
|
|
206
210
|
__classPrivateFieldGet(this, _RedisClient_subClient, "f").on('message', __classPrivateFieldGet(this, _RedisClient_instances, "m", _RedisClient_handleRedisMessage).bind(this));
|
|
207
211
|
});
|
|
212
|
+
}, _RedisClient_startHeartbeat = function _RedisClient_startHeartbeat() {
|
|
213
|
+
__classPrivateFieldSet(this, _RedisClient_heartbeatTimer, setInterval(() => __awaiter(this, void 0, void 0, function* () {
|
|
214
|
+
try {
|
|
215
|
+
yield __classPrivateFieldGet(this, _RedisClient_redisClient, "f").set(`heartbeat:${__classPrivateFieldGet(this, _RedisClient_instanceId, "f")}`, Date.now().toString(), 'EX', __classPrivateFieldGet(this, _RedisClient_instanceTtl, "f"));
|
|
216
|
+
}
|
|
217
|
+
catch (error) {
|
|
218
|
+
throw new redisError_1.RedisError('Error setting heartbeat');
|
|
219
|
+
}
|
|
220
|
+
}), __classPrivateFieldGet(this, _RedisClient_heartbeatInterval, "f")), "f");
|
|
221
|
+
}, _RedisClient_startPeriodicCleanup = function _RedisClient_startPeriodicCleanup() {
|
|
222
|
+
__classPrivateFieldSet(this, _RedisClient_cleanupTimer, setInterval(() => __awaiter(this, void 0, void 0, function* () {
|
|
223
|
+
try {
|
|
224
|
+
yield __classPrivateFieldGet(this, _RedisClient_instances, "m", _RedisClient_performConsistencyCheck).call(this);
|
|
225
|
+
}
|
|
226
|
+
catch (error) {
|
|
227
|
+
throw new redisError_1.RedisError('Error performing consistency check');
|
|
228
|
+
}
|
|
229
|
+
}), __classPrivateFieldGet(this, _RedisClient_cleanupInterval, "f")), "f");
|
|
230
|
+
}, _RedisClient_performConsistencyCheck = function _RedisClient_performConsistencyCheck() {
|
|
231
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
232
|
+
const consistencyCheckScript = `
|
|
233
|
+
local active_instances = redis.call('SMEMBERS', 'distributed_instances')
|
|
234
|
+
local all_keys = redis.call('KEYS', '*_cache:*')
|
|
235
|
+
local inactive_keys = {}
|
|
236
|
+
local unique_pairs = {}
|
|
237
|
+
|
|
238
|
+
for _, key in ipairs(all_keys) do
|
|
239
|
+
local parts = {}
|
|
240
|
+
for part in string.gmatch(key, '[^:]+') do
|
|
241
|
+
table.insert(parts, part)
|
|
242
|
+
end
|
|
243
|
+
local instance_id, endpoint_id, channel_id = parts[2], parts[3], parts[4]
|
|
244
|
+
|
|
245
|
+
if not (active_instances[instance_id]) then
|
|
246
|
+
table.insert(inactive_keys, key)
|
|
247
|
+
local pair = endpoint_id .. ':' .. channel_id
|
|
248
|
+
unique_pairs[pair] = true
|
|
249
|
+
end
|
|
250
|
+
end
|
|
251
|
+
|
|
252
|
+
if #inactive_keys > 0 then
|
|
253
|
+
redis.call('DEL', unpack(inactive_keys))
|
|
254
|
+
end
|
|
255
|
+
|
|
256
|
+
local unique_pairs_list = {}
|
|
257
|
+
for pair in pairs(unique_pairs) do
|
|
258
|
+
table.insert(unique_pairs_list, pair)
|
|
259
|
+
end
|
|
260
|
+
|
|
261
|
+
return unique_pairs_list
|
|
262
|
+
`;
|
|
263
|
+
const [uniquePairs] = yield __classPrivateFieldGet(this, _RedisClient_redisClient, "f").eval(consistencyCheckScript, 0);
|
|
264
|
+
const promises = uniquePairs.map((pair) => {
|
|
265
|
+
const [endpointId, channelId] = pair.split(':');
|
|
266
|
+
return __classPrivateFieldGet(this, _RedisClient_instances, "m", _RedisClient_emitStateSyncEvent).call(this, endpointId, channelId);
|
|
267
|
+
});
|
|
268
|
+
yield Promise.all(promises);
|
|
269
|
+
});
|
|
208
270
|
}, _RedisClient_handleRedisMessage = function _RedisClient_handleRedisMessage(channel, message) {
|
|
209
271
|
const data = JSON.parse(message);
|
|
210
272
|
switch (channel) {
|
|
@@ -225,25 +287,58 @@ _RedisClient_redisClient = new WeakMap(), _RedisClient_pubClient = new WeakMap()
|
|
|
225
287
|
}
|
|
226
288
|
}, _RedisClient_publishCacheMessage = function _RedisClient_publishCacheMessage(key, cacheKey, message) {
|
|
227
289
|
return __awaiter(this, void 0, void 0, function* () {
|
|
290
|
+
const script = `
|
|
291
|
+
local messageData = ARGV[1]
|
|
292
|
+
local userId = ARGV[2]
|
|
293
|
+
local state = ARGV[3]
|
|
294
|
+
|
|
295
|
+
if state ~= '' then
|
|
296
|
+
redis.call('HSET', KEYS[1], userId, state)
|
|
297
|
+
else
|
|
298
|
+
redis.call('HDEL', KEYS[1], userId)
|
|
299
|
+
end
|
|
300
|
+
|
|
301
|
+
redis.call('PUBLISH', KEYS[2], messageData)
|
|
302
|
+
|
|
303
|
+
return 1
|
|
304
|
+
`;
|
|
228
305
|
const messageData = JSON.stringify(message);
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
}
|
|
232
|
-
else {
|
|
233
|
-
yield __classPrivateFieldGet(this, _RedisClient_redisClient, "f").hdel(cacheKey, message.userId);
|
|
234
|
-
}
|
|
235
|
-
yield __classPrivateFieldGet(this, _RedisClient_pubClient, "f").publish(key, messageData);
|
|
306
|
+
const state = message.state ? JSON.stringify(message.state) : '';
|
|
307
|
+
yield __classPrivateFieldGet(this, _RedisClient_redisClient, "f").eval(script, 2, cacheKey, key, messageData, message.userId, state);
|
|
236
308
|
});
|
|
237
|
-
},
|
|
309
|
+
}, _RedisClient_emitStateSyncEvent = function _RedisClient_emitStateSyncEvent(endpointId, channelId) {
|
|
238
310
|
return __awaiter(this, void 0, void 0, function* () {
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
311
|
+
const script = `
|
|
312
|
+
local active_instances = redis.call('SMEMBERS', 'distributed_instances')
|
|
313
|
+
local presence_data = {}
|
|
314
|
+
local assigns_data = {}
|
|
315
|
+
|
|
316
|
+
for _, instance in ipairs(active_instances) do
|
|
317
|
+
local presence_key = 'presence_cache:' .. instance .. ':' .. ARGV[1] .. ':' .. ARGV[2]
|
|
318
|
+
local assigns_key = 'assigns_cache:' .. instance .. ':' .. ARGV[1] .. ':' .. ARGV[2]
|
|
319
|
+
|
|
320
|
+
local presence = redis.call('HGETALL', presence_key)
|
|
321
|
+
local assigns = redis.call('HGETALL', assigns_key)
|
|
322
|
+
|
|
323
|
+
for i = 1, #presence, 2 do
|
|
324
|
+
presence_data[presence[i]] = presence[i+1]
|
|
325
|
+
end
|
|
326
|
+
|
|
327
|
+
for i = 1, #assigns, 2 do
|
|
328
|
+
assigns_data[assigns[i]] = assigns[i+1]
|
|
329
|
+
end
|
|
330
|
+
end
|
|
331
|
+
|
|
332
|
+
return {cjson.encode(presence_data), cjson.encode(assigns_data)}
|
|
333
|
+
`;
|
|
334
|
+
const [presenceData, assignsData] = yield __classPrivateFieldGet(this, _RedisClient_redisClient, "f").eval(script, 0, endpointId, channelId);
|
|
335
|
+
const event = {
|
|
336
|
+
endpointId,
|
|
337
|
+
channelId,
|
|
338
|
+
presence: new Map(Object.entries(JSON.parse(presenceData))),
|
|
339
|
+
assigns: new Map(Object.entries(JSON.parse(assignsData))),
|
|
340
|
+
};
|
|
341
|
+
__classPrivateFieldGet(this, _RedisClient_stateSyncPublisher, "f").publish(event);
|
|
247
342
|
});
|
|
248
343
|
}, _RedisClient_subscribeToCacheChanges = function _RedisClient_subscribeToCacheChanges(endpoint, channel, presence, callback) {
|
|
249
344
|
const subject = presence ? __classPrivateFieldGet(this, _RedisClient_presencePublisher, "f") : __classPrivateFieldGet(this, _RedisClient_assignsPublisher, "f");
|
|
@@ -265,6 +360,19 @@ _RedisClient_redisClient = new WeakMap(), _RedisClient_pubClient = new WeakMap()
|
|
|
265
360
|
return callback(userId);
|
|
266
361
|
}
|
|
267
362
|
});
|
|
363
|
+
}, _RedisClient_subscribeToStateSync = function _RedisClient_subscribeToStateSync(endpoint, channel, callback) {
|
|
364
|
+
const interval = setInterval(() => __classPrivateFieldGet(this, _RedisClient_instances, "m", _RedisClient_emitStateSyncEvent).call(this, endpoint, channel), __classPrivateFieldGet(this, _RedisClient_heartbeatInterval, "f") * 10);
|
|
365
|
+
const subscription = __classPrivateFieldGet(this, _RedisClient_stateSyncPublisher, "f").subscribe((_a) => {
|
|
366
|
+
var { endpointId, channelId } = _a, data = __rest(_a, ["endpointId", "channelId"]);
|
|
367
|
+
if (endpointId === endpoint && channelId === channel) {
|
|
368
|
+
return callback(data);
|
|
369
|
+
}
|
|
370
|
+
});
|
|
371
|
+
void __classPrivateFieldGet(this, _RedisClient_instances, "m", _RedisClient_emitStateSyncEvent).call(this, endpoint, channel);
|
|
372
|
+
return () => {
|
|
373
|
+
clearInterval(interval);
|
|
374
|
+
subscription();
|
|
375
|
+
};
|
|
268
376
|
}, _RedisClient_handleExit = function _RedisClient_handleExit() {
|
|
269
377
|
return __awaiter(this, void 0, void 0, function* () {
|
|
270
378
|
try {
|
|
@@ -1,13 +1,4 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
-
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
-
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
-
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
-
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
-
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
-
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
-
});
|
|
10
|
-
};
|
|
11
2
|
var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
|
|
12
3
|
if (kind === "m") throw new TypeError("Private method is not writable");
|
|
13
4
|
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
|
|
@@ -45,7 +36,7 @@ class EndpointEngine {
|
|
|
45
36
|
}
|
|
46
37
|
createChannel(path, handler) {
|
|
47
38
|
const pondChannel = new lobbyEngine_1.LobbyEngine(this);
|
|
48
|
-
__classPrivateFieldGet(this, _EndpointEngine_middleware, "f").use((user, joinParams, next) =>
|
|
39
|
+
__classPrivateFieldGet(this, _EndpointEngine_middleware, "f").use((user, joinParams, next) => {
|
|
49
40
|
const event = (0, matcher_1.parseAddress)(path, user.channelName);
|
|
50
41
|
if (event) {
|
|
51
42
|
const options = {
|
|
@@ -54,13 +45,13 @@ class EndpointEngine {
|
|
|
54
45
|
params: event,
|
|
55
46
|
joinParams,
|
|
56
47
|
};
|
|
57
|
-
const channel =
|
|
48
|
+
const channel = pondChannel.getOrCreateChannel(user.channelName);
|
|
58
49
|
const request = new joinRequest_1.JoinRequest(options, channel);
|
|
59
50
|
const response = new joinResponse_1.JoinResponse(user, channel);
|
|
60
51
|
return handler(request, response, next);
|
|
61
52
|
}
|
|
62
53
|
next();
|
|
63
|
-
})
|
|
54
|
+
});
|
|
64
55
|
__classPrivateFieldGet(this, _EndpointEngine_lobbyEngines, "f").set(path, pondChannel);
|
|
65
56
|
return new pondChannel_1.PondChannel(pondChannel);
|
|
66
57
|
}
|
package/engines/lobbyEngine.js
CHANGED
|
@@ -1,13 +1,4 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
-
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
-
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
-
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
-
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
-
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
-
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
-
});
|
|
10
|
-
};
|
|
11
2
|
var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
|
|
12
3
|
if (kind === "m") throw new TypeError("Private method is not writable");
|
|
13
4
|
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
|
|
@@ -69,11 +60,9 @@ exports.LobbyEngine = LobbyEngine;
|
|
|
69
60
|
_LobbyEngine_channels = new WeakMap(), _LobbyEngine_instances = new WeakSet(), _LobbyEngine_getChannel = function _LobbyEngine_getChannel(channelName) {
|
|
70
61
|
return __classPrivateFieldGet(this, _LobbyEngine_channels, "f").get(channelName) || null;
|
|
71
62
|
}, _LobbyEngine_createChannel = function _LobbyEngine_createChannel(channelName) {
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
return channel;
|
|
78
|
-
});
|
|
63
|
+
const onManagerClose = __classPrivateFieldGet(this, _LobbyEngine_channels, "f").delete.bind(__classPrivateFieldGet(this, _LobbyEngine_channels, "f"), channelName);
|
|
64
|
+
const manager = this.parent.createManager(channelName, onManagerClose);
|
|
65
|
+
const channel = new channelEngine_1.ChannelEngine(this, channelName, manager);
|
|
66
|
+
__classPrivateFieldGet(this, _LobbyEngine_channels, "f").set(channelName, channel);
|
|
67
|
+
return channel;
|
|
79
68
|
};
|
package/errors/httpError.js
CHANGED
|
@@ -6,6 +6,39 @@ class HttpError extends Error {
|
|
|
6
6
|
super(message);
|
|
7
7
|
this.statusCode = statusCode;
|
|
8
8
|
this.name = 'HttpError';
|
|
9
|
+
this.statusText = HttpError.getStatusText(statusCode);
|
|
10
|
+
}
|
|
11
|
+
static getStatusText(statusCode) {
|
|
12
|
+
switch (statusCode) {
|
|
13
|
+
case 400:
|
|
14
|
+
return 'Bad Request';
|
|
15
|
+
case 401:
|
|
16
|
+
return 'Unauthorized';
|
|
17
|
+
case 403:
|
|
18
|
+
return 'Forbidden';
|
|
19
|
+
case 404:
|
|
20
|
+
return 'Not Found';
|
|
21
|
+
case 405:
|
|
22
|
+
return 'Method Not Allowed';
|
|
23
|
+
case 406:
|
|
24
|
+
return 'Not Acceptable';
|
|
25
|
+
case 409:
|
|
26
|
+
return 'Conflict';
|
|
27
|
+
case 429:
|
|
28
|
+
return 'Too Many Requests';
|
|
29
|
+
case 500:
|
|
30
|
+
return 'Internal Server Error';
|
|
31
|
+
case 501:
|
|
32
|
+
return 'Not Implemented';
|
|
33
|
+
case 502:
|
|
34
|
+
return 'Bad Gateway';
|
|
35
|
+
case 503:
|
|
36
|
+
return 'Service Unavailable';
|
|
37
|
+
case 504:
|
|
38
|
+
return 'Gateway Timeout';
|
|
39
|
+
default:
|
|
40
|
+
return 'Unknown Error';
|
|
41
|
+
}
|
|
9
42
|
}
|
|
10
43
|
}
|
|
11
44
|
exports.HttpError = HttpError;
|
package/index.js
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.RedisError = void 0;
|
|
3
4
|
const server_1 = require("./server/server");
|
|
5
|
+
var redisError_1 = require("./errors/redisError");
|
|
6
|
+
Object.defineProperty(exports, "RedisError", { enumerable: true, get: function () { return redisError_1.RedisError; } });
|
|
4
7
|
exports.default = server_1.PondSocket;
|
|
@@ -1,13 +1,4 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
-
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
-
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
-
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
-
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
-
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
-
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
-
});
|
|
10
|
-
};
|
|
11
2
|
var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
|
|
12
3
|
if (kind === "m") throw new TypeError("Private method is not writable");
|
|
13
4
|
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
|
|
@@ -66,44 +57,42 @@ class DistributedManager extends manager_1.Manager {
|
|
|
66
57
|
__classPrivateFieldGet(this, _DistributedManager_client, "f").publishChannelMessage(message);
|
|
67
58
|
}
|
|
68
59
|
initialize(unsubscribe) {
|
|
69
|
-
|
|
70
|
-
|
|
60
|
+
super.initialize(unsubscribe);
|
|
61
|
+
const leaveSubscription = __classPrivateFieldGet(this, _DistributedManager_client, "f").subscribeToUserLeaves((userId) => {
|
|
62
|
+
var _a;
|
|
63
|
+
(_a = this.userSubscriptions.get(userId)) === null || _a === void 0 ? void 0 : _a();
|
|
64
|
+
this.userSubscriptions.delete(userId);
|
|
65
|
+
});
|
|
66
|
+
const presenceSubscription = __classPrivateFieldGet(this, _DistributedManager_client, "f").subscribeToPresenceChanges(({ userId, state }) => {
|
|
67
|
+
if (state) {
|
|
68
|
+
this.presenceCache.set(userId, state);
|
|
69
|
+
}
|
|
70
|
+
else {
|
|
71
|
+
this.presenceCache.delete(userId);
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
const assignSubscription = __classPrivateFieldGet(this, _DistributedManager_client, "f").subscribeToAssignsChanges(({ userId, state }) => {
|
|
75
|
+
if (state) {
|
|
76
|
+
this.assignsCache.set(userId, state);
|
|
77
|
+
}
|
|
78
|
+
else {
|
|
79
|
+
this.assignsCache.delete(userId);
|
|
80
|
+
}
|
|
81
|
+
});
|
|
82
|
+
const messageSubscription = __classPrivateFieldGet(this, _DistributedManager_client, "f").subscribeToChannelMessages((message) => {
|
|
83
|
+
this.publisher.publish(message);
|
|
71
84
|
});
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
this.presenceCache =
|
|
75
|
-
this.assignsCache = yield __classPrivateFieldGet(this, _DistributedManager_client, "f").getAssignsCache();
|
|
76
|
-
const leaveSubscription = __classPrivateFieldGet(this, _DistributedManager_client, "f").subscribeToUserLeaves((userId) => {
|
|
77
|
-
var _a;
|
|
78
|
-
(_a = this.userSubscriptions.get(userId)) === null || _a === void 0 ? void 0 : _a();
|
|
79
|
-
this.userSubscriptions.delete(userId);
|
|
80
|
-
});
|
|
81
|
-
const presenceSubscription = __classPrivateFieldGet(this, _DistributedManager_client, "f").subscribeToPresenceChanges(({ userId, state }) => {
|
|
82
|
-
if (state) {
|
|
83
|
-
this.presenceCache.set(userId, state);
|
|
84
|
-
}
|
|
85
|
-
else {
|
|
86
|
-
this.presenceCache.delete(userId);
|
|
87
|
-
}
|
|
88
|
-
});
|
|
89
|
-
const assignSubscription = __classPrivateFieldGet(this, _DistributedManager_client, "f").subscribeToAssignsChanges(({ userId, state }) => {
|
|
90
|
-
if (state) {
|
|
91
|
-
this.assignsCache.set(userId, state);
|
|
92
|
-
}
|
|
93
|
-
else {
|
|
94
|
-
this.assignsCache.delete(userId);
|
|
95
|
-
}
|
|
96
|
-
});
|
|
97
|
-
const messageSubscription = __classPrivateFieldGet(this, _DistributedManager_client, "f").subscribeToChannelMessages((message) => {
|
|
98
|
-
this.publisher.publish(message);
|
|
99
|
-
});
|
|
100
|
-
__classPrivateFieldSet(this, _DistributedManager_subscriptions, () => {
|
|
101
|
-
leaveSubscription();
|
|
102
|
-
presenceSubscription();
|
|
103
|
-
assignSubscription();
|
|
104
|
-
messageSubscription();
|
|
105
|
-
}, "f");
|
|
85
|
+
const stateSyncSubscription = __classPrivateFieldGet(this, _DistributedManager_client, "f").subscribeToStateSync((state) => {
|
|
86
|
+
this.assignsCache = new Map(state.assigns);
|
|
87
|
+
this.presenceCache = new Map(state.presence);
|
|
106
88
|
});
|
|
89
|
+
__classPrivateFieldSet(this, _DistributedManager_subscriptions, () => {
|
|
90
|
+
leaveSubscription();
|
|
91
|
+
presenceSubscription();
|
|
92
|
+
assignSubscription();
|
|
93
|
+
messageSubscription();
|
|
94
|
+
stateSyncSubscription();
|
|
95
|
+
}, "f");
|
|
107
96
|
}
|
|
108
97
|
close() {
|
|
109
98
|
var _a;
|
package/managers/manager.js
CHANGED
|
@@ -1,31 +1,20 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
-
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
-
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
-
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
-
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
-
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
-
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
-
});
|
|
10
|
-
};
|
|
11
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
3
|
exports.ManagerFactory = void 0;
|
|
13
4
|
const distributedManager_1 = require("./distributedManager");
|
|
14
5
|
const localManager_1 = require("./localManager");
|
|
15
6
|
class ManagerFactory {
|
|
16
7
|
static create(channelId, clientFactory, onClose) {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
return manager;
|
|
28
|
-
});
|
|
8
|
+
let manager;
|
|
9
|
+
if (clientFactory) {
|
|
10
|
+
const client = clientFactory(channelId);
|
|
11
|
+
manager = new distributedManager_1.DistributedManager(client);
|
|
12
|
+
}
|
|
13
|
+
else {
|
|
14
|
+
manager = new localManager_1.LocalManager(channelId);
|
|
15
|
+
}
|
|
16
|
+
manager.initialize(onClose);
|
|
17
|
+
return manager;
|
|
29
18
|
}
|
|
30
19
|
}
|
|
31
20
|
exports.ManagerFactory = ManagerFactory;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@eleven-am/pondsocket",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.179",
|
|
4
4
|
"description": "PondSocket is a fast simple socket server",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"socket",
|
|
@@ -49,7 +49,7 @@
|
|
|
49
49
|
"eslint-config-prettier": "^9.1.0",
|
|
50
50
|
"eslint-import-resolver-node": "^0.3.9",
|
|
51
51
|
"eslint-plugin-file-progress": "^1.5.0",
|
|
52
|
-
"eslint-plugin-import": "^2.
|
|
52
|
+
"eslint-plugin-import": "^2.31.0",
|
|
53
53
|
"eslint-plugin-prettier": "^5.2.1",
|
|
54
54
|
"jest": "^29.7.0",
|
|
55
55
|
"nodemon": "^3.1.7",
|
package/types.d.ts
CHANGED
|
@@ -23,6 +23,9 @@ export interface RedisOptions {
|
|
|
23
23
|
db?: number;
|
|
24
24
|
username?: string;
|
|
25
25
|
password?: string;
|
|
26
|
+
instanceTtl?: number;
|
|
27
|
+
heartbeatInterval?: number;
|
|
28
|
+
cleanupInterval?: number;
|
|
26
29
|
}
|
|
27
30
|
|
|
28
31
|
export interface LeaveEvent<EventTypes extends PondEventMap = PondEventMap, PresenceType extends PondPresence = PondPresence, AssignType extends PondAssigns = PondAssigns> {
|
|
@@ -472,3 +475,7 @@ export declare class EventResponse<EventType extends PondEventMap = PondEventMap
|
|
|
472
475
|
*/
|
|
473
476
|
evictUser(reason: string, userId?: string): EventResponse;
|
|
474
477
|
}
|
|
478
|
+
|
|
479
|
+
export declare class RedisError extends Error {
|
|
480
|
+
constructor(message: string);
|
|
481
|
+
}
|