@eleven-am/pondsocket 0.1.191 → 0.1.192
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 +325 -151
- package/package.json +20 -16
package/abstracts/redisClient.js
CHANGED
|
@@ -33,7 +33,7 @@ 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_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_handleErrors,
|
|
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_handleErrors, _RedisClient_registerInstance, _RedisClient_unregisterInstance, _RedisClient_cleanupDisconnectedClients, _RedisClient_getAllInstanceIds, _RedisClient_startPeriodicCleanup, _RedisClient_startHeartbeat, _RedisClient_performHeartbeat, _RedisClient_unsubscribeFromChannels, _RedisClient_subscribeToChannels, _RedisClient_handleRedisMessage, _RedisClient_getPresenceCacheChannel, _RedisClient_getAssignsCacheChannel, _RedisClient_subscribeToCacheChanges, _RedisClient_subscribeToChannelMessages, _RedisClient_subscribeToUserLeaves, _RedisClient_subscribeToStateSync, _RedisClient_publishPresenceChange, _RedisClient_publishAssignsChange, _RedisClient_publishChannelMessage, _RedisClient_publishUserLeave, _RedisClient_publishCacheMessage, _RedisClient_emitStateSyncEvent, _RedisClient_generateCache, _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");
|
|
@@ -97,12 +97,10 @@ class RedisClient {
|
|
|
97
97
|
}
|
|
98
98
|
shutdown() {
|
|
99
99
|
return __awaiter(this, void 0, void 0, function* () {
|
|
100
|
-
const batchSize = 1000;
|
|
101
100
|
clearInterval(__classPrivateFieldGet(this, _RedisClient_heartbeatTimer, "f"));
|
|
102
101
|
clearInterval(__classPrivateFieldGet(this, _RedisClient_cleanupTimer, "f"));
|
|
103
102
|
yield __classPrivateFieldGet(this, _RedisClient_instances, "m", _RedisClient_unsubscribeFromChannels).call(this);
|
|
104
103
|
yield __classPrivateFieldGet(this, _RedisClient_instances, "m", _RedisClient_unregisterInstance).call(this);
|
|
105
|
-
yield __classPrivateFieldGet(this, _RedisClient_instances, "m", _RedisClient_deleteKeysByPattern).call(this, `*_cache:${__classPrivateFieldGet(this, _RedisClient_instanceId, "f")}:*`, batchSize);
|
|
106
104
|
});
|
|
107
105
|
}
|
|
108
106
|
}
|
|
@@ -119,53 +117,14 @@ _RedisClient_heartbeatInterval = new WeakMap(), _RedisClient_cleanupInterval = n
|
|
|
119
117
|
__classPrivateFieldGet(this, _RedisClient_redisClient, "f").on('error', (err) => console.error('Redis client error:', err));
|
|
120
118
|
__classPrivateFieldGet(this, _RedisClient_pubClient, "f").on('error', (err) => console.error('Redis pub client error:', err));
|
|
121
119
|
__classPrivateFieldGet(this, _RedisClient_subClient, "f").on('error', (err) => console.error('Redis sub client error:', err));
|
|
122
|
-
}, _RedisClient_getPresenceCacheChannel = function _RedisClient_getPresenceCacheChannel(endpointId, channelId) {
|
|
123
|
-
return `presence_cache:${__classPrivateFieldGet(this, _RedisClient_instanceId, "f")}:${endpointId}:${channelId}`;
|
|
124
|
-
}, _RedisClient_getAssignsCacheChannel = function _RedisClient_getAssignsCacheChannel(endpointId, channelId) {
|
|
125
|
-
return `assigns_cache:${__classPrivateFieldGet(this, _RedisClient_instanceId, "f")}:${endpointId}:${channelId}`;
|
|
126
|
-
}, _RedisClient_publishPresenceChange = function _RedisClient_publishPresenceChange(endpointId, channelId, userId, state) {
|
|
127
|
-
const message = {
|
|
128
|
-
userId,
|
|
129
|
-
channelId,
|
|
130
|
-
endpointId,
|
|
131
|
-
state,
|
|
132
|
-
};
|
|
133
|
-
const key = __classPrivateFieldGet(this, _RedisClient_instances, "a", _RedisClient_presence_changes_channel_get);
|
|
134
|
-
const cacheKey = __classPrivateFieldGet(this, _RedisClient_instances, "m", _RedisClient_getPresenceCacheChannel).call(this, message.endpointId, message.channelId);
|
|
135
|
-
return __classPrivateFieldGet(this, _RedisClient_instances, "m", _RedisClient_publishCacheMessage).call(this, key, cacheKey, message);
|
|
136
|
-
}, _RedisClient_publishAssignsChange = function _RedisClient_publishAssignsChange(endpointId, channelId, userId, state) {
|
|
137
|
-
const message = {
|
|
138
|
-
userId,
|
|
139
|
-
channelId,
|
|
140
|
-
endpointId,
|
|
141
|
-
state,
|
|
142
|
-
};
|
|
143
|
-
const key = __classPrivateFieldGet(this, _RedisClient_instances, "a", _RedisClient_assigns_changes_channel_get);
|
|
144
|
-
const cacheKey = __classPrivateFieldGet(this, _RedisClient_instances, "m", _RedisClient_getAssignsCacheChannel).call(this, message.endpointId, message.channelId);
|
|
145
|
-
return __classPrivateFieldGet(this, _RedisClient_instances, "m", _RedisClient_publishCacheMessage).call(this, key, cacheKey, message);
|
|
146
|
-
}, _RedisClient_publishChannelMessage = function _RedisClient_publishChannelMessage(endpointId, channelId, message) {
|
|
147
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
148
|
-
const messageData = JSON.stringify({
|
|
149
|
-
endpointId,
|
|
150
|
-
channelId,
|
|
151
|
-
message,
|
|
152
|
-
});
|
|
153
|
-
yield __classPrivateFieldGet(this, _RedisClient_pubClient, "f").publish(__classPrivateFieldGet(this, _RedisClient_instances, "a", _RedisClient_channel_messages_channel_get), messageData);
|
|
154
|
-
});
|
|
155
|
-
}, _RedisClient_publishUserLeave = function _RedisClient_publishUserLeave(endpointId, channelId, userId) {
|
|
156
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
157
|
-
const message = JSON.stringify({
|
|
158
|
-
endpointId,
|
|
159
|
-
channelId,
|
|
160
|
-
userId,
|
|
161
|
-
});
|
|
162
|
-
yield __classPrivateFieldGet(this, _RedisClient_pubClient, "f").publish(__classPrivateFieldGet(this, _RedisClient_instances, "a", _RedisClient_user_leaves_channel_get), message);
|
|
163
|
-
});
|
|
164
120
|
}, _RedisClient_registerInstance = function _RedisClient_registerInstance() {
|
|
165
121
|
return __awaiter(this, void 0, void 0, function* () {
|
|
166
122
|
const multi = __classPrivateFieldGet(this, _RedisClient_redisClient, "f").multi();
|
|
167
|
-
|
|
168
|
-
multi
|
|
123
|
+
const now = Date.now().toString();
|
|
124
|
+
multi
|
|
125
|
+
.sadd('distributed_instances', __classPrivateFieldGet(this, _RedisClient_instanceId, "f"))
|
|
126
|
+
.set(`heartbeat:${__classPrivateFieldGet(this, _RedisClient_instanceId, "f")}`, now, 'EX', __classPrivateFieldGet(this, _RedisClient_instanceTtl, "f"))
|
|
127
|
+
.hset(`instance_metadata:${__classPrivateFieldGet(this, _RedisClient_instanceId, "f")}`, 'last_seen', now);
|
|
169
128
|
try {
|
|
170
129
|
yield multi.exec();
|
|
171
130
|
}
|
|
@@ -174,73 +133,233 @@ _RedisClient_heartbeatInterval = new WeakMap(), _RedisClient_cleanupInterval = n
|
|
|
174
133
|
}
|
|
175
134
|
});
|
|
176
135
|
}, _RedisClient_unregisterInstance = function _RedisClient_unregisterInstance() {
|
|
177
|
-
return __awaiter(this,
|
|
178
|
-
const
|
|
179
|
-
|
|
180
|
-
|
|
136
|
+
return __awaiter(this, arguments, void 0, function* (instanceId = __classPrivateFieldGet(this, _RedisClient_instanceId, "f")) {
|
|
137
|
+
const script = `
|
|
138
|
+
-- Remove instance registration
|
|
139
|
+
redis.call('SREM', 'distributed_instances', ARGV[1])
|
|
140
|
+
redis.call('DEL', 'heartbeat:' .. ARGV[1])
|
|
141
|
+
redis.call('DEL', 'instance_metadata:' .. ARGV[1])
|
|
142
|
+
|
|
143
|
+
-- Delete all cache keys in one SCAN operation
|
|
144
|
+
local cursor = '0'
|
|
145
|
+
repeat
|
|
146
|
+
local result = redis.call('SCAN', cursor, 'MATCH', '{presence_cache,assigns_cache}:' .. ARGV[1] .. ':*', 'COUNT', 100)
|
|
147
|
+
cursor = result[1]
|
|
148
|
+
local keys = result[2]
|
|
149
|
+
if #keys > 0 then
|
|
150
|
+
redis.call('DEL', unpack(keys))
|
|
151
|
+
end
|
|
152
|
+
until cursor == '0'
|
|
153
|
+
|
|
154
|
+
return 1
|
|
155
|
+
`;
|
|
181
156
|
try {
|
|
182
|
-
yield
|
|
157
|
+
yield __classPrivateFieldGet(this, _RedisClient_redisClient, "f").eval(script, 0, instanceId);
|
|
183
158
|
}
|
|
184
159
|
catch (_a) {
|
|
185
160
|
// no-op as we're shutting down
|
|
186
161
|
}
|
|
187
162
|
});
|
|
188
|
-
},
|
|
163
|
+
}, _RedisClient_cleanupDisconnectedClients = function _RedisClient_cleanupDisconnectedClients() {
|
|
189
164
|
return __awaiter(this, void 0, void 0, function* () {
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
165
|
+
const script = `
|
|
166
|
+
-- Helper function to check if instance is alive
|
|
167
|
+
local function is_instance_alive(instance_id)
|
|
168
|
+
local heartbeat = redis.call('GET', 'heartbeat:' .. instance_id)
|
|
169
|
+
local in_set = redis.call('SISMEMBER', 'distributed_instances', instance_id)
|
|
170
|
+
return heartbeat and in_set == 1
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
-- Get array of instance IDs passed as argument
|
|
174
|
+
local instance_ids = cjson.decode(ARGV[1])
|
|
175
|
+
|
|
176
|
+
-- Track affected channels and keys to delete
|
|
177
|
+
local affected_channels = {}
|
|
178
|
+
local keys_to_delete = {}
|
|
179
|
+
local batch_size = 100
|
|
180
|
+
|
|
181
|
+
-- Process each instance
|
|
182
|
+
for _, instance_id in ipairs(instance_ids) do
|
|
183
|
+
-- If instance is not alive, clean it up
|
|
184
|
+
if not is_instance_alive(instance_id) then
|
|
185
|
+
-- Queue instance metadata keys for deletion
|
|
186
|
+
table.insert(keys_to_delete, 'heartbeat:' .. instance_id)
|
|
187
|
+
table.insert(keys_to_delete, 'instance_metadata:' .. instance_id)
|
|
188
|
+
|
|
189
|
+
-- Remove from distributed_instances
|
|
190
|
+
redis.call('SREM', 'distributed_instances', instance_id)
|
|
191
|
+
|
|
192
|
+
-- Find and process all cache keys for this instance
|
|
193
|
+
local cache_cursor = '0'
|
|
194
|
+
repeat
|
|
195
|
+
local result = redis.call('SCAN', cache_cursor, 'MATCH', '*_cache:' .. instance_id .. ':*', 'COUNT', batch_size)
|
|
196
|
+
cache_cursor = result[1]
|
|
197
|
+
|
|
198
|
+
for _, key in ipairs(result[2]) do
|
|
199
|
+
-- Extract channel info before deletion
|
|
200
|
+
local _, _, endpoint_id, channel_id = string.match(key, '_cache:[^:]+:([^:]+):([^:]+)')
|
|
201
|
+
if endpoint_id and channel_id then
|
|
202
|
+
affected_channels[endpoint_id .. ":" .. channel_id] = true
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
-- Add to deletion batch
|
|
206
|
+
table.insert(keys_to_delete, key)
|
|
207
|
+
|
|
208
|
+
-- If batch is full, process it
|
|
209
|
+
if #keys_to_delete >= batch_size then
|
|
210
|
+
redis.call('DEL', unpack(keys_to_delete))
|
|
211
|
+
keys_to_delete = {}
|
|
212
|
+
end
|
|
213
|
+
end
|
|
214
|
+
until cache_cursor == '0'
|
|
215
|
+
end
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
-- Delete any remaining keys
|
|
219
|
+
if #keys_to_delete > 0 then
|
|
220
|
+
redis.call('DEL', unpack(keys_to_delete))
|
|
221
|
+
end
|
|
222
|
+
|
|
223
|
+
-- Convert affected_channels to array
|
|
224
|
+
local channels = {}
|
|
225
|
+
for pair in pairs(affected_channels) do
|
|
226
|
+
table.insert(channels, pair)
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
return channels
|
|
230
|
+
`;
|
|
231
|
+
try {
|
|
232
|
+
// Get all instance IDs first
|
|
233
|
+
const instanceIds = yield __classPrivateFieldGet(this, _RedisClient_instances, "m", _RedisClient_getAllInstanceIds).call(this);
|
|
234
|
+
// Run cleanup script with the instance IDs
|
|
235
|
+
const affectedChannels = yield __classPrivateFieldGet(this, _RedisClient_redisClient, "f").eval(script, 0, JSON.stringify(instanceIds));
|
|
236
|
+
// Process affected channels
|
|
237
|
+
if (affectedChannels.length > 0) {
|
|
238
|
+
const promises = affectedChannels.map((pair) => {
|
|
239
|
+
const [endpointId, channelId] = pair.split(':');
|
|
240
|
+
return __classPrivateFieldGet(this, _RedisClient_instances, "m", _RedisClient_emitStateSyncEvent).call(this, endpointId, channelId);
|
|
241
|
+
});
|
|
242
|
+
yield Promise.all(promises);
|
|
200
243
|
}
|
|
201
|
-
}
|
|
202
|
-
|
|
244
|
+
}
|
|
245
|
+
catch (error) {
|
|
246
|
+
console.error('Error cleaning up disconnected clients:', error);
|
|
247
|
+
}
|
|
203
248
|
});
|
|
204
|
-
},
|
|
205
|
-
|
|
249
|
+
}, _RedisClient_getAllInstanceIds = function _RedisClient_getAllInstanceIds() {
|
|
250
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
251
|
+
const script = `
|
|
252
|
+
local instance_ids = {}
|
|
253
|
+
local seen = {} -- For deduplication
|
|
254
|
+
|
|
255
|
+
-- 1. Get instances from distributed_instances set
|
|
256
|
+
local registered = redis.call('SMEMBERS', 'distributed_instances')
|
|
257
|
+
for _, id in ipairs(registered) do
|
|
258
|
+
if not seen[id] then
|
|
259
|
+
seen[id] = true
|
|
260
|
+
table.insert(instance_ids, id)
|
|
261
|
+
end
|
|
262
|
+
end
|
|
263
|
+
|
|
264
|
+
-- 2. Get instances from heartbeat keys
|
|
265
|
+
local cursor = '0'
|
|
266
|
+
repeat
|
|
267
|
+
local result = redis.call('SCAN', cursor, 'MATCH', 'heartbeat:*', 'COUNT', 100)
|
|
268
|
+
cursor = result[1]
|
|
269
|
+
local keys = result[2]
|
|
270
|
+
|
|
271
|
+
for _, key in ipairs(keys) do
|
|
272
|
+
local id = string.match(key, 'heartbeat:([^:]+)')
|
|
273
|
+
if id and not seen[id] then
|
|
274
|
+
seen[id] = true
|
|
275
|
+
table.insert(instance_ids, id)
|
|
276
|
+
end
|
|
277
|
+
end
|
|
278
|
+
until cursor == '0'
|
|
279
|
+
|
|
280
|
+
-- 3. Get instances from metadata keys
|
|
281
|
+
cursor = '0'
|
|
282
|
+
repeat
|
|
283
|
+
local result = redis.call('SCAN', cursor, 'MATCH', 'instance_metadata:*', 'COUNT', 100)
|
|
284
|
+
cursor = result[1]
|
|
285
|
+
local keys = result[2]
|
|
286
|
+
|
|
287
|
+
for _, key in ipairs(keys) do
|
|
288
|
+
local id = string.match(key, 'instance_metadata:([^:]+)')
|
|
289
|
+
if id and not seen[id] then
|
|
290
|
+
seen[id] = true
|
|
291
|
+
table.insert(instance_ids, id)
|
|
292
|
+
end
|
|
293
|
+
end
|
|
294
|
+
until cursor == '0'
|
|
295
|
+
|
|
296
|
+
-- 4. Get instances from cache keys
|
|
297
|
+
cursor = '0'
|
|
298
|
+
repeat
|
|
299
|
+
local result = redis.call('SCAN', cursor, 'MATCH', '*_cache:*', 'COUNT', 100)
|
|
300
|
+
cursor = result[1]
|
|
301
|
+
local keys = result[2]
|
|
302
|
+
|
|
303
|
+
for _, key in ipairs(keys) do
|
|
304
|
+
local id = string.match(key, '_cache:([^:]+)')
|
|
305
|
+
if id and not seen[id] then
|
|
306
|
+
seen[id] = true
|
|
307
|
+
table.insert(instance_ids, id)
|
|
308
|
+
end
|
|
309
|
+
end
|
|
310
|
+
until cursor == '0'
|
|
311
|
+
|
|
312
|
+
return instance_ids
|
|
313
|
+
`;
|
|
314
|
+
let response = [];
|
|
206
315
|
try {
|
|
207
|
-
yield __classPrivateFieldGet(this, _RedisClient_redisClient, "f").
|
|
316
|
+
response = (yield __classPrivateFieldGet(this, _RedisClient_redisClient, "f").eval(script, 0));
|
|
208
317
|
}
|
|
209
318
|
catch (error) {
|
|
210
|
-
|
|
319
|
+
console.error('Error getting all instance IDs:', error);
|
|
211
320
|
}
|
|
212
|
-
|
|
321
|
+
return response;
|
|
322
|
+
});
|
|
213
323
|
}, _RedisClient_startPeriodicCleanup = function _RedisClient_startPeriodicCleanup() {
|
|
214
324
|
__classPrivateFieldSet(this, _RedisClient_cleanupTimer, setInterval(() => __awaiter(this, void 0, void 0, function* () {
|
|
215
325
|
try {
|
|
216
|
-
yield __classPrivateFieldGet(this, _RedisClient_instances, "m",
|
|
326
|
+
yield __classPrivateFieldGet(this, _RedisClient_instances, "m", _RedisClient_cleanupDisconnectedClients).call(this);
|
|
217
327
|
}
|
|
218
328
|
catch (error) {
|
|
219
|
-
|
|
329
|
+
console.error('Error in periodic cleanup:', error);
|
|
220
330
|
}
|
|
221
331
|
}), __classPrivateFieldGet(this, _RedisClient_cleanupInterval, "f")), "f");
|
|
222
|
-
},
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
const activeSet = new Set(activeInstances);
|
|
227
|
-
const inactiveKeys = [];
|
|
228
|
-
const uniquePairs = new Set();
|
|
229
|
-
for (const key of allKeys) {
|
|
230
|
-
const [, instanceId, endpointId, channelId] = key.split(':');
|
|
231
|
-
if (!activeSet.has(instanceId)) {
|
|
232
|
-
inactiveKeys.push(key);
|
|
233
|
-
uniquePairs.add(`${endpointId}:${channelId}`);
|
|
234
|
-
}
|
|
332
|
+
}, _RedisClient_startHeartbeat = function _RedisClient_startHeartbeat() {
|
|
333
|
+
__classPrivateFieldSet(this, _RedisClient_heartbeatTimer, setInterval(() => __awaiter(this, void 0, void 0, function* () {
|
|
334
|
+
try {
|
|
335
|
+
yield __classPrivateFieldGet(this, _RedisClient_instances, "m", _RedisClient_performHeartbeat).call(this);
|
|
235
336
|
}
|
|
236
|
-
|
|
237
|
-
|
|
337
|
+
catch (error) {
|
|
338
|
+
console.error('Error performing heartbeat:', error);
|
|
238
339
|
}
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
340
|
+
}), __classPrivateFieldGet(this, _RedisClient_heartbeatInterval, "f")), "f");
|
|
341
|
+
}, _RedisClient_performHeartbeat = function _RedisClient_performHeartbeat() {
|
|
342
|
+
const now = Date.now().toString();
|
|
343
|
+
const multi = __classPrivateFieldGet(this, _RedisClient_redisClient, "f").multi();
|
|
344
|
+
multi
|
|
345
|
+
.set(`heartbeat:${__classPrivateFieldGet(this, _RedisClient_instanceId, "f")}`, now, 'EX', __classPrivateFieldGet(this, _RedisClient_instanceTtl, "f"))
|
|
346
|
+
.hset(`instance_metadata:${__classPrivateFieldGet(this, _RedisClient_instanceId, "f")}`, 'last_seen', now);
|
|
347
|
+
return multi.exec();
|
|
348
|
+
}, _RedisClient_unsubscribeFromChannels = function _RedisClient_unsubscribeFromChannels() {
|
|
349
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
350
|
+
yield __classPrivateFieldGet(this, _RedisClient_subClient, "f").unsubscribe(__classPrivateFieldGet(this, _RedisClient_instances, "a", _RedisClient_presence_changes_channel_get), __classPrivateFieldGet(this, _RedisClient_instances, "a", _RedisClient_assigns_changes_channel_get), __classPrivateFieldGet(this, _RedisClient_instances, "a", _RedisClient_channel_messages_channel_get), __classPrivateFieldGet(this, _RedisClient_instances, "a", _RedisClient_user_leaves_channel_get));
|
|
351
|
+
});
|
|
352
|
+
}, _RedisClient_subscribeToChannels = function _RedisClient_subscribeToChannels() {
|
|
353
|
+
return new Promise((resolve, reject) => {
|
|
354
|
+
__classPrivateFieldGet(this, _RedisClient_subClient, "f").subscribe(__classPrivateFieldGet(this, _RedisClient_instances, "a", _RedisClient_presence_changes_channel_get), __classPrivateFieldGet(this, _RedisClient_instances, "a", _RedisClient_assigns_changes_channel_get), __classPrivateFieldGet(this, _RedisClient_instances, "a", _RedisClient_channel_messages_channel_get), __classPrivateFieldGet(this, _RedisClient_instances, "a", _RedisClient_user_leaves_channel_get), (err) => {
|
|
355
|
+
if (err) {
|
|
356
|
+
reject(err);
|
|
357
|
+
}
|
|
358
|
+
else {
|
|
359
|
+
resolve();
|
|
360
|
+
}
|
|
242
361
|
});
|
|
243
|
-
|
|
362
|
+
__classPrivateFieldGet(this, _RedisClient_subClient, "f").on('message', __classPrivateFieldGet(this, _RedisClient_instances, "m", _RedisClient_handleRedisMessage).bind(this));
|
|
244
363
|
});
|
|
245
364
|
}, _RedisClient_handleRedisMessage = function _RedisClient_handleRedisMessage(channel, message) {
|
|
246
365
|
const data = JSON.parse(message);
|
|
@@ -260,6 +379,81 @@ _RedisClient_heartbeatInterval = new WeakMap(), _RedisClient_cleanupInterval = n
|
|
|
260
379
|
default:
|
|
261
380
|
throw new Error(`Unknown channel: ${channel}`);
|
|
262
381
|
}
|
|
382
|
+
}, _RedisClient_getPresenceCacheChannel = function _RedisClient_getPresenceCacheChannel(endpointId, channelId) {
|
|
383
|
+
return `presence_cache:${__classPrivateFieldGet(this, _RedisClient_instanceId, "f")}:${endpointId}:${channelId}`;
|
|
384
|
+
}, _RedisClient_getAssignsCacheChannel = function _RedisClient_getAssignsCacheChannel(endpointId, channelId) {
|
|
385
|
+
return `assigns_cache:${__classPrivateFieldGet(this, _RedisClient_instanceId, "f")}:${endpointId}:${channelId}`;
|
|
386
|
+
}, _RedisClient_subscribeToCacheChanges = function _RedisClient_subscribeToCacheChanges(endpoint, channel, presence, callback) {
|
|
387
|
+
const subject = presence ? __classPrivateFieldGet(this, _RedisClient_presencePublisher, "f") : __classPrivateFieldGet(this, _RedisClient_assignsPublisher, "f");
|
|
388
|
+
return subject.subscribe((_a) => {
|
|
389
|
+
var { endpointId, channelId } = _a, data = __rest(_a, ["endpointId", "channelId"]);
|
|
390
|
+
if (endpointId === endpoint && channelId === channel) {
|
|
391
|
+
return callback(data);
|
|
392
|
+
}
|
|
393
|
+
});
|
|
394
|
+
}, _RedisClient_subscribeToChannelMessages = function _RedisClient_subscribeToChannelMessages(endpoint, channel, callback) {
|
|
395
|
+
return __classPrivateFieldGet(this, _RedisClient_channelMessagePublisher, "f").subscribe(({ endpointId, channelId, message }) => {
|
|
396
|
+
if (endpointId === endpoint && channelId === channel) {
|
|
397
|
+
return callback(message);
|
|
398
|
+
}
|
|
399
|
+
});
|
|
400
|
+
}, _RedisClient_subscribeToUserLeaves = function _RedisClient_subscribeToUserLeaves(endpoint, channel, callback) {
|
|
401
|
+
return __classPrivateFieldGet(this, _RedisClient_userLeavesPublisher, "f").subscribe(({ endpointId, channelId, userId }) => {
|
|
402
|
+
if (endpointId === endpoint && channelId === channel) {
|
|
403
|
+
return callback(userId);
|
|
404
|
+
}
|
|
405
|
+
});
|
|
406
|
+
}, _RedisClient_subscribeToStateSync = function _RedisClient_subscribeToStateSync(endpoint, channel, callback) {
|
|
407
|
+
const interval = setInterval(() => __classPrivateFieldGet(this, _RedisClient_instances, "m", _RedisClient_emitStateSyncEvent).call(this, endpoint, channel), __classPrivateFieldGet(this, _RedisClient_heartbeatInterval, "f") * 10);
|
|
408
|
+
const subscription = __classPrivateFieldGet(this, _RedisClient_stateSyncPublisher, "f").subscribe((_a) => {
|
|
409
|
+
var { endpointId, channelId } = _a, data = __rest(_a, ["endpointId", "channelId"]);
|
|
410
|
+
if (endpointId === endpoint && channelId === channel) {
|
|
411
|
+
return callback(data);
|
|
412
|
+
}
|
|
413
|
+
});
|
|
414
|
+
void __classPrivateFieldGet(this, _RedisClient_instances, "m", _RedisClient_emitStateSyncEvent).call(this, endpoint, channel, true);
|
|
415
|
+
return () => {
|
|
416
|
+
clearInterval(interval);
|
|
417
|
+
subscription();
|
|
418
|
+
};
|
|
419
|
+
}, _RedisClient_publishPresenceChange = function _RedisClient_publishPresenceChange(endpointId, channelId, userId, state) {
|
|
420
|
+
const message = {
|
|
421
|
+
userId,
|
|
422
|
+
channelId,
|
|
423
|
+
endpointId,
|
|
424
|
+
state,
|
|
425
|
+
};
|
|
426
|
+
const key = __classPrivateFieldGet(this, _RedisClient_instances, "a", _RedisClient_presence_changes_channel_get);
|
|
427
|
+
const cacheKey = __classPrivateFieldGet(this, _RedisClient_instances, "m", _RedisClient_getPresenceCacheChannel).call(this, message.endpointId, message.channelId);
|
|
428
|
+
return __classPrivateFieldGet(this, _RedisClient_instances, "m", _RedisClient_publishCacheMessage).call(this, key, cacheKey, message);
|
|
429
|
+
}, _RedisClient_publishAssignsChange = function _RedisClient_publishAssignsChange(endpointId, channelId, userId, state) {
|
|
430
|
+
const message = {
|
|
431
|
+
userId,
|
|
432
|
+
channelId,
|
|
433
|
+
endpointId,
|
|
434
|
+
state,
|
|
435
|
+
};
|
|
436
|
+
const key = __classPrivateFieldGet(this, _RedisClient_instances, "a", _RedisClient_assigns_changes_channel_get);
|
|
437
|
+
const cacheKey = __classPrivateFieldGet(this, _RedisClient_instances, "m", _RedisClient_getAssignsCacheChannel).call(this, message.endpointId, message.channelId);
|
|
438
|
+
return __classPrivateFieldGet(this, _RedisClient_instances, "m", _RedisClient_publishCacheMessage).call(this, key, cacheKey, message);
|
|
439
|
+
}, _RedisClient_publishChannelMessage = function _RedisClient_publishChannelMessage(endpointId, channelId, message) {
|
|
440
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
441
|
+
const messageData = JSON.stringify({
|
|
442
|
+
endpointId,
|
|
443
|
+
channelId,
|
|
444
|
+
message,
|
|
445
|
+
});
|
|
446
|
+
yield __classPrivateFieldGet(this, _RedisClient_pubClient, "f").publish(__classPrivateFieldGet(this, _RedisClient_instances, "a", _RedisClient_channel_messages_channel_get), messageData);
|
|
447
|
+
});
|
|
448
|
+
}, _RedisClient_publishUserLeave = function _RedisClient_publishUserLeave(endpointId, channelId, userId) {
|
|
449
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
450
|
+
const message = JSON.stringify({
|
|
451
|
+
endpointId,
|
|
452
|
+
channelId,
|
|
453
|
+
userId,
|
|
454
|
+
});
|
|
455
|
+
yield __classPrivateFieldGet(this, _RedisClient_pubClient, "f").publish(__classPrivateFieldGet(this, _RedisClient_instances, "a", _RedisClient_user_leaves_channel_get), message);
|
|
456
|
+
});
|
|
263
457
|
}, _RedisClient_publishCacheMessage = function _RedisClient_publishCacheMessage(key, cacheKey, message) {
|
|
264
458
|
return __awaiter(this, void 0, void 0, function* () {
|
|
265
459
|
const script = `
|
|
@@ -269,6 +463,8 @@ _RedisClient_heartbeatInterval = new WeakMap(), _RedisClient_cleanupInterval = n
|
|
|
269
463
|
|
|
270
464
|
if state ~= '' then
|
|
271
465
|
redis.call('HSET', KEYS[1], userId, state)
|
|
466
|
+
-- Set TTL on cache key
|
|
467
|
+
redis.call('EXPIRE', KEYS[1], ${__classPrivateFieldGet(this, _RedisClient_instanceTtl, "f")})
|
|
272
468
|
else
|
|
273
469
|
redis.call('HDEL', KEYS[1], userId)
|
|
274
470
|
end
|
|
@@ -284,23 +480,45 @@ _RedisClient_heartbeatInterval = new WeakMap(), _RedisClient_cleanupInterval = n
|
|
|
284
480
|
}, _RedisClient_emitStateSyncEvent = function _RedisClient_emitStateSyncEvent(endpointId_1, channelId_1) {
|
|
285
481
|
return __awaiter(this, arguments, void 0, function* (endpointId, channelId, initialFetch = false) {
|
|
286
482
|
const script = `
|
|
483
|
+
-- Helper function to check if an instance is truly alive
|
|
484
|
+
-- Returns true only if:
|
|
485
|
+
-- 1. Instance has a valid heartbeat AND
|
|
486
|
+
-- 2. Instance is in the distributed_instances set
|
|
487
|
+
local function is_instance_alive(instance_id)
|
|
488
|
+
-- Both checks in one atomic operation
|
|
489
|
+
local exists = redis.call('EXISTS', 'heartbeat:' .. instance_id)
|
|
490
|
+
if exists == 0 then
|
|
491
|
+
return false
|
|
492
|
+
end
|
|
493
|
+
|
|
494
|
+
local is_registered = redis.call('SISMEMBER', 'distributed_instances', instance_id)
|
|
495
|
+
return is_registered == 1
|
|
496
|
+
end
|
|
497
|
+
|
|
498
|
+
-- Get all registered instances
|
|
287
499
|
local active_instances = redis.call('SMEMBERS', 'distributed_instances')
|
|
288
500
|
local presence_data = {}
|
|
289
501
|
local assigns_data = {}
|
|
290
502
|
|
|
291
503
|
for _, instance in ipairs(active_instances) do
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
504
|
+
-- Only process data from instances that are truly alive
|
|
505
|
+
if is_instance_alive(instance) then
|
|
506
|
+
local presence_key = 'presence_cache:' .. instance .. ':' .. ARGV[1] .. ':' .. ARGV[2]
|
|
507
|
+
local assigns_key = 'assigns_cache:' .. instance .. ':' .. ARGV[1] .. ':' .. ARGV[2]
|
|
508
|
+
|
|
509
|
+
-- Fetch data atomically for this instance
|
|
510
|
+
local presence = redis.call('HGETALL', presence_key)
|
|
511
|
+
local assigns = redis.call('HGETALL', assigns_key)
|
|
512
|
+
|
|
513
|
+
-- Process presence data
|
|
514
|
+
for i = 1, #presence, 2 do
|
|
515
|
+
presence_data[presence[i]] = presence[i+1]
|
|
516
|
+
end
|
|
517
|
+
|
|
518
|
+
-- Process assigns data
|
|
519
|
+
for i = 1, #assigns, 2 do
|
|
520
|
+
assigns_data[assigns[i]] = assigns[i+1]
|
|
521
|
+
end
|
|
304
522
|
end
|
|
305
523
|
end
|
|
306
524
|
|
|
@@ -316,39 +534,10 @@ _RedisClient_heartbeatInterval = new WeakMap(), _RedisClient_cleanupInterval = n
|
|
|
316
534
|
};
|
|
317
535
|
__classPrivateFieldGet(this, _RedisClient_stateSyncPublisher, "f").publish(event);
|
|
318
536
|
});
|
|
319
|
-
},
|
|
320
|
-
const
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
if (endpointId === endpoint && channelId === channel) {
|
|
324
|
-
return callback(data);
|
|
325
|
-
}
|
|
326
|
-
});
|
|
327
|
-
}, _RedisClient_subscribeToChannelMessages = function _RedisClient_subscribeToChannelMessages(endpoint, channel, callback) {
|
|
328
|
-
return __classPrivateFieldGet(this, _RedisClient_channelMessagePublisher, "f").subscribe(({ endpointId, channelId, message }) => {
|
|
329
|
-
if (endpointId === endpoint && channelId === channel) {
|
|
330
|
-
return callback(message);
|
|
331
|
-
}
|
|
332
|
-
});
|
|
333
|
-
}, _RedisClient_subscribeToUserLeaves = function _RedisClient_subscribeToUserLeaves(endpoint, channel, callback) {
|
|
334
|
-
return __classPrivateFieldGet(this, _RedisClient_userLeavesPublisher, "f").subscribe(({ endpointId, channelId, userId }) => {
|
|
335
|
-
if (endpointId === endpoint && channelId === channel) {
|
|
336
|
-
return callback(userId);
|
|
337
|
-
}
|
|
338
|
-
});
|
|
339
|
-
}, _RedisClient_subscribeToStateSync = function _RedisClient_subscribeToStateSync(endpoint, channel, callback) {
|
|
340
|
-
const interval = setInterval(() => __classPrivateFieldGet(this, _RedisClient_instances, "m", _RedisClient_emitStateSyncEvent).call(this, endpoint, channel), __classPrivateFieldGet(this, _RedisClient_heartbeatInterval, "f") * 10);
|
|
341
|
-
const subscription = __classPrivateFieldGet(this, _RedisClient_stateSyncPublisher, "f").subscribe((_a) => {
|
|
342
|
-
var { endpointId, channelId } = _a, data = __rest(_a, ["endpointId", "channelId"]);
|
|
343
|
-
if (endpointId === endpoint && channelId === channel) {
|
|
344
|
-
return callback(data);
|
|
345
|
-
}
|
|
346
|
-
});
|
|
347
|
-
void __classPrivateFieldGet(this, _RedisClient_instances, "m", _RedisClient_emitStateSyncEvent).call(this, endpoint, channel, true);
|
|
348
|
-
return () => {
|
|
349
|
-
clearInterval(interval);
|
|
350
|
-
subscription();
|
|
351
|
-
};
|
|
537
|
+
}, _RedisClient_generateCache = function _RedisClient_generateCache(data) {
|
|
538
|
+
const first = Object.entries(JSON.parse(data));
|
|
539
|
+
const second = first.map(([key, value]) => [key, JSON.parse(String(value))]);
|
|
540
|
+
return new Map(second);
|
|
352
541
|
}, _RedisClient_handleExit = function _RedisClient_handleExit() {
|
|
353
542
|
return __awaiter(this, void 0, void 0, function* () {
|
|
354
543
|
try {
|
|
@@ -359,19 +548,4 @@ _RedisClient_heartbeatInterval = new WeakMap(), _RedisClient_cleanupInterval = n
|
|
|
359
548
|
process.exit(1);
|
|
360
549
|
}
|
|
361
550
|
});
|
|
362
|
-
}, _RedisClient_deleteKeysByPattern = function _RedisClient_deleteKeysByPattern(pattern, batchSize) {
|
|
363
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
364
|
-
let cursor = '0';
|
|
365
|
-
do {
|
|
366
|
-
const [newCursor, keys] = yield __classPrivateFieldGet(this, _RedisClient_redisClient, "f").scan(cursor, 'MATCH', pattern, 'COUNT', batchSize);
|
|
367
|
-
cursor = newCursor;
|
|
368
|
-
if (keys.length > 0) {
|
|
369
|
-
yield __classPrivateFieldGet(this, _RedisClient_redisClient, "f").del(...keys);
|
|
370
|
-
}
|
|
371
|
-
} while (cursor !== '0');
|
|
372
|
-
});
|
|
373
|
-
}, _RedisClient_generateCache = function _RedisClient_generateCache(data) {
|
|
374
|
-
const first = Object.entries(JSON.parse(data));
|
|
375
|
-
const second = first.map(([key, value]) => [key, JSON.parse(String(value))]);
|
|
376
|
-
return new Map(second);
|
|
377
551
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@eleven-am/pondsocket",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.192",
|
|
4
4
|
"description": "PondSocket is a fast simple socket server",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"socket",
|
|
@@ -35,32 +35,36 @@
|
|
|
35
35
|
},
|
|
36
36
|
"dependencies": {
|
|
37
37
|
"@eleven-am/pondsocket-common": "^0.0.26",
|
|
38
|
-
"ioredis": "^5.
|
|
39
|
-
"ws": "^8.18.
|
|
38
|
+
"ioredis": "^5.5.0",
|
|
39
|
+
"ws": "^8.18.1"
|
|
40
40
|
},
|
|
41
41
|
"devDependencies": {
|
|
42
|
-
"@
|
|
42
|
+
"@eslint/compat": "^1.2.7",
|
|
43
|
+
"@eslint/eslintrc": "^3.3.0",
|
|
44
|
+
"@eslint/js": "^9.21.0",
|
|
45
|
+
"@stylistic/eslint-plugin-ts": "^4.0.1",
|
|
43
46
|
"@types/jest": "^29.5.14",
|
|
44
|
-
"@types/node": "^22.
|
|
45
|
-
"@types/ws": "^8.5.
|
|
46
|
-
"@typescript-eslint/eslint-plugin": "^8.
|
|
47
|
-
"@typescript-eslint/parser": "^8.
|
|
48
|
-
"eslint": "^
|
|
49
|
-
"eslint-config-prettier": "^
|
|
47
|
+
"@types/node": "^22.13.5",
|
|
48
|
+
"@types/ws": "^8.5.14",
|
|
49
|
+
"@typescript-eslint/eslint-plugin": "^8.24.1",
|
|
50
|
+
"@typescript-eslint/parser": "^8.24.1",
|
|
51
|
+
"eslint": "^9.21.0",
|
|
52
|
+
"eslint-config-prettier": "^10.0.1",
|
|
50
53
|
"eslint-import-resolver-node": "^0.3.9",
|
|
51
|
-
"eslint-plugin-file-progress": "^
|
|
54
|
+
"eslint-plugin-file-progress": "^3.0.1",
|
|
52
55
|
"eslint-plugin-import": "^2.31.0",
|
|
53
|
-
"eslint-plugin-prettier": "^5.2.
|
|
56
|
+
"eslint-plugin-prettier": "^5.2.3",
|
|
57
|
+
"globals": "^16.0.0",
|
|
54
58
|
"jest": "^29.7.0",
|
|
55
|
-
"nodemon": "^3.1.
|
|
56
|
-
"prettier": "^3.
|
|
59
|
+
"nodemon": "^3.1.9",
|
|
60
|
+
"prettier": "^3.5.2",
|
|
57
61
|
"source-map-support": "^0.5.21",
|
|
58
62
|
"supertest": "^7.0.0",
|
|
59
63
|
"ts-jest": "^29.2.5",
|
|
60
|
-
"ts-loader": "^9.5.
|
|
64
|
+
"ts-loader": "^9.5.2",
|
|
61
65
|
"ts-node": "^10.9.2",
|
|
62
66
|
"tsconfig-paths": "^4.2.0",
|
|
63
|
-
"typescript": "^5.
|
|
67
|
+
"typescript": "^5.7.3"
|
|
64
68
|
},
|
|
65
69
|
"jest": {
|
|
66
70
|
"moduleFileExtensions": [
|