@eleven-am/pondsocket 0.1.192 → 0.1.194

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.
@@ -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, _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;
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_stateSyncInterval, _RedisClient_ttlBuffer, _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_startPeriodicCleanup, _RedisClient_startHeartbeat, _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_getActiveInstances, _RedisClient_getCachedInstances, _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");
@@ -41,7 +41,7 @@ const ioredis_1 = __importDefault(require("ioredis"));
41
41
  const redisError_1 = require("../errors/redisError");
42
42
  class RedisClient {
43
43
  constructor(config) {
44
- var _a, _b, _c;
44
+ var _a;
45
45
  _RedisClient_instances.add(this);
46
46
  _RedisClient_heartbeatInterval.set(this, void 0);
47
47
  _RedisClient_cleanupInterval.set(this, void 0);
@@ -57,9 +57,14 @@ class RedisClient {
57
57
  _RedisClient_stateSyncPublisher.set(this, void 0);
58
58
  _RedisClient_heartbeatTimer.set(this, void 0);
59
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");
60
+ _RedisClient_stateSyncInterval.set(this, void 0);
61
+ _RedisClient_ttlBuffer.set(this, 5);
62
+ const baseTtl = (_a = config.instanceTtl) !== null && _a !== void 0 ? _a : 90;
63
+ const milliseconds = baseTtl * 1000;
64
+ __classPrivateFieldSet(this, _RedisClient_stateSyncInterval, milliseconds, "f");
65
+ __classPrivateFieldSet(this, _RedisClient_cleanupInterval, milliseconds * 2, "f");
66
+ __classPrivateFieldSet(this, _RedisClient_heartbeatInterval, milliseconds / 3, "f");
67
+ __classPrivateFieldSet(this, _RedisClient_instanceTtl, baseTtl + __classPrivateFieldGet(this, _RedisClient_ttlBuffer, "f"), "f");
63
68
  __classPrivateFieldSet(this, _RedisClient_redisClient, new ioredis_1.default(config), "f");
64
69
  __classPrivateFieldSet(this, _RedisClient_pubClient, new ioredis_1.default(config), "f");
65
70
  __classPrivateFieldSet(this, _RedisClient_subClient, new ioredis_1.default(config), "f");
@@ -73,15 +78,15 @@ class RedisClient {
73
78
  buildClient(endpointId) {
74
79
  return (channelId) => ({
75
80
  channelId,
76
- publishPresenceChange: __classPrivateFieldGet(this, _RedisClient_instances, "m", _RedisClient_publishPresenceChange).bind(this, endpointId, channelId),
81
+ publishUserLeave: __classPrivateFieldGet(this, _RedisClient_instances, "m", _RedisClient_publishUserLeave).bind(this, endpointId, channelId),
82
+ subscribeToStateSync: __classPrivateFieldGet(this, _RedisClient_instances, "m", _RedisClient_subscribeToStateSync).bind(this, endpointId, channelId),
77
83
  publishAssignsChange: __classPrivateFieldGet(this, _RedisClient_instances, "m", _RedisClient_publishAssignsChange).bind(this, endpointId, channelId),
84
+ publishPresenceChange: __classPrivateFieldGet(this, _RedisClient_instances, "m", _RedisClient_publishPresenceChange).bind(this, endpointId, channelId),
78
85
  publishChannelMessage: __classPrivateFieldGet(this, _RedisClient_instances, "m", _RedisClient_publishChannelMessage).bind(this, endpointId, channelId),
79
- publishUserLeave: __classPrivateFieldGet(this, _RedisClient_instances, "m", _RedisClient_publishUserLeave).bind(this, endpointId, channelId),
80
86
  subscribeToUserLeaves: __classPrivateFieldGet(this, _RedisClient_instances, "m", _RedisClient_subscribeToUserLeaves).bind(this, endpointId, channelId),
87
+ subscribeToChannelMessages: __classPrivateFieldGet(this, _RedisClient_instances, "m", _RedisClient_subscribeToChannelMessages).bind(this, endpointId, channelId),
81
88
  subscribeToPresenceChanges: __classPrivateFieldGet(this, _RedisClient_instances, "m", _RedisClient_subscribeToCacheChanges).bind(this, endpointId, channelId, true),
82
89
  subscribeToAssignsChanges: __classPrivateFieldGet(this, _RedisClient_instances, "m", _RedisClient_subscribeToCacheChanges).bind(this, endpointId, channelId, false),
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),
85
90
  });
86
91
  }
87
92
  initialize() {
@@ -105,7 +110,7 @@ class RedisClient {
105
110
  }
106
111
  }
107
112
  exports.RedisClient = RedisClient;
108
- _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() {
113
+ _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_stateSyncInterval = new WeakMap(), _RedisClient_ttlBuffer = new WeakMap(), _RedisClient_instances = new WeakSet(), _RedisClient_presence_changes_channel_get = function _RedisClient_presence_changes_channel_get() {
109
114
  return 'presence_changes';
110
115
  }, _RedisClient_assigns_changes_channel_get = function _RedisClient_assigns_changes_channel_get() {
111
116
  return 'assigns_changes';
@@ -119,31 +124,24 @@ _RedisClient_heartbeatInterval = new WeakMap(), _RedisClient_cleanupInterval = n
119
124
  __classPrivateFieldGet(this, _RedisClient_subClient, "f").on('error', (err) => console.error('Redis sub client error:', err));
120
125
  }, _RedisClient_registerInstance = function _RedisClient_registerInstance() {
121
126
  return __awaiter(this, void 0, void 0, function* () {
122
- const multi = __classPrivateFieldGet(this, _RedisClient_redisClient, "f").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);
128
127
  try {
129
- yield multi.exec();
128
+ const now = Date.now().toString();
129
+ yield __classPrivateFieldGet(this, _RedisClient_redisClient, "f").set(`heartbeat:${__classPrivateFieldGet(this, _RedisClient_instanceId, "f")}`, now, 'EX', __classPrivateFieldGet(this, _RedisClient_instanceTtl, "f"));
130
130
  }
131
131
  catch (error) {
132
- throw new redisError_1.RedisError('Error registering instance');
132
+ throw new redisError_1.RedisError(`Error registering instance: ${error}`);
133
133
  }
134
134
  });
135
135
  }, _RedisClient_unregisterInstance = function _RedisClient_unregisterInstance() {
136
136
  return __awaiter(this, arguments, void 0, function* (instanceId = __classPrivateFieldGet(this, _RedisClient_instanceId, "f")) {
137
137
  const script = `
138
- -- Remove instance registration
139
- redis.call('SREM', 'distributed_instances', ARGV[1])
138
+ -- Delete heartbeat
140
139
  redis.call('DEL', 'heartbeat:' .. ARGV[1])
141
- redis.call('DEL', 'instance_metadata:' .. ARGV[1])
142
140
 
143
141
  -- Delete all cache keys in one SCAN operation
144
142
  local cursor = '0'
145
143
  repeat
146
- local result = redis.call('SCAN', cursor, 'MATCH', '{presence_cache,assigns_cache}:' .. ARGV[1] .. ':*', 'COUNT', 100)
144
+ local result = redis.call('SCAN', cursor, 'MATCH', '*_cache:' .. ARGV[1] .. ':*', 'COUNT', 100)
147
145
  cursor = result[1]
148
146
  local keys = result[2]
149
147
  if #keys > 0 then
@@ -163,56 +161,37 @@ _RedisClient_heartbeatInterval = new WeakMap(), _RedisClient_cleanupInterval = n
163
161
  }, _RedisClient_cleanupDisconnectedClients = function _RedisClient_cleanupDisconnectedClients() {
164
162
  return __awaiter(this, void 0, void 0, function* () {
165
163
  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
164
+ -- Array of instance IDs that have cache data but no heartbeat
165
+ local dead_instances = cjson.decode(ARGV[1])
177
166
  local affected_channels = {}
178
167
  local keys_to_delete = {}
179
168
  local batch_size = 100
180
169
 
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)
170
+ -- Process each dead instance
171
+ for _, instance_id in ipairs(dead_instances) do
172
+ -- Find and process all cache keys for this instance
173
+ local cache_cursor = '0'
174
+ repeat
175
+ local result = redis.call('SCAN', cache_cursor, 'MATCH', '*_cache:' .. instance_id .. ':*', 'COUNT', batch_size)
176
+ cache_cursor = result[1]
188
177
 
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]
178
+ for _, key in ipairs(result[2]) do
179
+ -- Extract channel info before deletion
180
+ local _, _, endpoint_id, channel_id = string.match(key, '_cache:[^:]+:([^:]+):([^:]+)')
181
+ if endpoint_id and channel_id then
182
+ affected_channels[endpoint_id .. ":" .. channel_id] = true
183
+ end
197
184
 
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
185
+ -- Add to deletion batch
186
+ table.insert(keys_to_delete, key)
187
+
188
+ -- If batch is full, process it
189
+ if #keys_to_delete >= batch_size then
190
+ redis.call('DEL', unpack(keys_to_delete))
191
+ keys_to_delete = {}
213
192
  end
214
- until cache_cursor == '0'
215
- end
193
+ end
194
+ until cache_cursor == '0'
216
195
  end
217
196
 
218
197
  -- Delete any remaining keys
@@ -229,11 +208,15 @@ _RedisClient_heartbeatInterval = new WeakMap(), _RedisClient_cleanupInterval = n
229
208
  return channels
230
209
  `;
231
210
  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
211
+ const [activeInstances, cachedInstances] = yield Promise.all([
212
+ __classPrivateFieldGet(this, _RedisClient_instances, "m", _RedisClient_getActiveInstances).call(this),
213
+ __classPrivateFieldGet(this, _RedisClient_instances, "m", _RedisClient_getCachedInstances).call(this),
214
+ ]);
215
+ const deadInstances = cachedInstances.filter((id) => !activeInstances.includes(id));
216
+ if (deadInstances.length === 0) {
217
+ return;
218
+ }
219
+ const affectedChannels = yield __classPrivateFieldGet(this, _RedisClient_redisClient, "f").eval(script, 0, JSON.stringify(deadInstances));
237
220
  if (affectedChannels.length > 0) {
238
221
  const promises = affectedChannels.map((pair) => {
239
222
  const [endpointId, channelId] = pair.split(':');
@@ -246,80 +229,6 @@ _RedisClient_heartbeatInterval = new WeakMap(), _RedisClient_cleanupInterval = n
246
229
  console.error('Error cleaning up disconnected clients:', error);
247
230
  }
248
231
  });
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 = [];
315
- try {
316
- response = (yield __classPrivateFieldGet(this, _RedisClient_redisClient, "f").eval(script, 0));
317
- }
318
- catch (error) {
319
- console.error('Error getting all instance IDs:', error);
320
- }
321
- return response;
322
- });
323
232
  }, _RedisClient_startPeriodicCleanup = function _RedisClient_startPeriodicCleanup() {
324
233
  __classPrivateFieldSet(this, _RedisClient_cleanupTimer, setInterval(() => __awaiter(this, void 0, void 0, function* () {
325
234
  try {
@@ -332,19 +241,12 @@ _RedisClient_heartbeatInterval = new WeakMap(), _RedisClient_cleanupInterval = n
332
241
  }, _RedisClient_startHeartbeat = function _RedisClient_startHeartbeat() {
333
242
  __classPrivateFieldSet(this, _RedisClient_heartbeatTimer, setInterval(() => __awaiter(this, void 0, void 0, function* () {
334
243
  try {
335
- yield __classPrivateFieldGet(this, _RedisClient_instances, "m", _RedisClient_performHeartbeat).call(this);
244
+ yield __classPrivateFieldGet(this, _RedisClient_instances, "m", _RedisClient_registerInstance).call(this);
336
245
  }
337
246
  catch (error) {
338
247
  console.error('Error performing heartbeat:', error);
339
248
  }
340
249
  }), __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
250
  }, _RedisClient_unsubscribeFromChannels = function _RedisClient_unsubscribeFromChannels() {
349
251
  return __awaiter(this, void 0, void 0, function* () {
350
252
  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));
@@ -404,7 +306,7 @@ _RedisClient_heartbeatInterval = new WeakMap(), _RedisClient_cleanupInterval = n
404
306
  }
405
307
  });
406
308
  }, _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);
309
+ const interval = setInterval(() => __classPrivateFieldGet(this, _RedisClient_instances, "m", _RedisClient_emitStateSyncEvent).call(this, endpoint, channel), __classPrivateFieldGet(this, _RedisClient_stateSyncInterval, "f"));
408
310
  const subscription = __classPrivateFieldGet(this, _RedisClient_stateSyncPublisher, "f").subscribe((_a) => {
409
311
  var { endpointId, channelId } = _a, data = __rest(_a, ["endpointId", "channelId"]);
410
312
  if (endpointId === endpoint && channelId === channel) {
@@ -463,8 +365,6 @@ _RedisClient_heartbeatInterval = new WeakMap(), _RedisClient_cleanupInterval = n
463
365
 
464
366
  if state ~= '' then
465
367
  redis.call('HSET', KEYS[1], userId, state)
466
- -- Set TTL on cache key
467
- redis.call('EXPIRE', KEYS[1], ${__classPrivateFieldGet(this, _RedisClient_instanceTtl, "f")})
468
368
  else
469
369
  redis.call('HDEL', KEYS[1], userId)
470
370
  end
@@ -480,59 +380,107 @@ _RedisClient_heartbeatInterval = new WeakMap(), _RedisClient_cleanupInterval = n
480
380
  }, _RedisClient_emitStateSyncEvent = function _RedisClient_emitStateSyncEvent(endpointId_1, channelId_1) {
481
381
  return __awaiter(this, arguments, void 0, function* (endpointId, channelId, initialFetch = false) {
482
382
  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
383
+ local instances = ARGV[1]
384
+ local presence_data = {}
385
+ local assigns_data = {}
386
+
387
+ -- Parse instance IDs array
388
+ local instance_ids = cjson.decode(instances)
389
+
390
+ for _, instance in ipairs(instance_ids) do
391
+ local presence_key = 'presence_cache:' .. instance .. ':' .. ARGV[2] .. ':' .. ARGV[3]
392
+ local assigns_key = 'assigns_cache:' .. instance .. ':' .. ARGV[2] .. ':' .. ARGV[3]
393
+
394
+ -- Get all presence data for this instance and channel
395
+ local presence = redis.call('HGETALL', presence_key)
396
+ for i = 1, #presence, 2 do
397
+ presence_data[presence[i]] = presence[i + 1]
492
398
  end
493
399
 
494
- local is_registered = redis.call('SISMEMBER', 'distributed_instances', instance_id)
495
- return is_registered == 1
400
+ -- Get all assigns data for this instance and channel
401
+ local assigns = redis.call('HGETALL', assigns_key)
402
+ for i = 1, #assigns, 2 do
403
+ assigns_data[assigns[i]] = assigns[i + 1]
404
+ end
496
405
  end
497
-
498
- -- Get all registered instances
499
- local active_instances = redis.call('SMEMBERS', 'distributed_instances')
500
- local presence_data = {}
501
- local assigns_data = {}
502
406
 
503
- for _, instance in ipairs(active_instances) do
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]
407
+ return {cjson.encode(presence_data), cjson.encode(assigns_data)}
408
+ `;
409
+ try {
410
+ // Get active instances
411
+ const activeInstances = yield __classPrivateFieldGet(this, _RedisClient_instances, "m", _RedisClient_getActiveInstances).call(this);
412
+ // Get the data using our active instances
413
+ const [presenceData, assignsData] = yield __classPrivateFieldGet(this, _RedisClient_redisClient, "f").eval(script, 0, JSON.stringify(activeInstances), endpointId, channelId);
414
+ const event = {
415
+ endpointId,
416
+ channelId,
417
+ initialFetch,
418
+ presence: __classPrivateFieldGet(this, _RedisClient_instances, "m", _RedisClient_generateCache).call(this, presenceData),
419
+ assigns: __classPrivateFieldGet(this, _RedisClient_instances, "m", _RedisClient_generateCache).call(this, assignsData),
420
+ };
421
+ __classPrivateFieldGet(this, _RedisClient_stateSyncPublisher, "f").publish(event);
422
+ }
423
+ catch (error) {
424
+ console.error('Error emitting state sync event:', error);
425
+ }
426
+ });
427
+ }, _RedisClient_getActiveInstances = function _RedisClient_getActiveInstances() {
428
+ return __awaiter(this, void 0, void 0, function* () {
429
+ const script = `
430
+ local active_instances = {}
431
+
432
+ local cursor = '0'
433
+ repeat
434
+ local result = redis.call('SCAN', cursor, 'MATCH', 'heartbeat:*', 'COUNT', 100)
435
+ cursor = result[1]
436
+
437
+ for _, key in ipairs(result[2]) do
438
+ local id = string.match(key, 'heartbeat:([^:]+)')
439
+ if id then
440
+ table.insert(active_instances, id)
516
441
  end
517
-
518
- -- Process assigns data
519
- for i = 1, #assigns, 2 do
520
- assigns_data[assigns[i]] = assigns[i+1]
442
+ end
443
+ until cursor == '0'
444
+
445
+ return active_instances
446
+ `;
447
+ try {
448
+ return yield __classPrivateFieldGet(this, _RedisClient_redisClient, "f").eval(script, 0);
449
+ }
450
+ catch (error) {
451
+ console.error('Error getting active instances:', error);
452
+ return [];
453
+ }
454
+ });
455
+ }, _RedisClient_getCachedInstances = function _RedisClient_getCachedInstances() {
456
+ return __awaiter(this, void 0, void 0, function* () {
457
+ const script = `
458
+ local seen = {}
459
+ local cached_instances = {}
460
+
461
+ local cursor = '0'
462
+ repeat
463
+ local result = redis.call('SCAN', cursor, 'MATCH', '*_cache:*', 'COUNT', 100)
464
+ cursor = result[1]
465
+
466
+ for _, key in ipairs(result[2]) do
467
+ local id = string.match(key, '_cache:([^:]+)')
468
+ if id and not seen[id] then
469
+ seen[id] = true
470
+ table.insert(cached_instances, id)
521
471
  end
522
472
  end
523
- end
473
+ until cursor == '0'
524
474
 
525
- return {cjson.encode(presence_data), cjson.encode(assigns_data)}
475
+ return cached_instances
526
476
  `;
527
- const [presenceData, assignsData] = yield __classPrivateFieldGet(this, _RedisClient_redisClient, "f").eval(script, 0, endpointId, channelId);
528
- const event = {
529
- endpointId,
530
- channelId,
531
- initialFetch,
532
- presence: __classPrivateFieldGet(this, _RedisClient_instances, "m", _RedisClient_generateCache).call(this, presenceData),
533
- assigns: __classPrivateFieldGet(this, _RedisClient_instances, "m", _RedisClient_generateCache).call(this, assignsData),
534
- };
535
- __classPrivateFieldGet(this, _RedisClient_stateSyncPublisher, "f").publish(event);
477
+ try {
478
+ return yield __classPrivateFieldGet(this, _RedisClient_redisClient, "f").eval(script, 0);
479
+ }
480
+ catch (error) {
481
+ console.error('Error getting cached instances:', error);
482
+ return [];
483
+ }
536
484
  });
537
485
  }, _RedisClient_generateCache = function _RedisClient_generateCache(data) {
538
486
  const first = Object.entries(JSON.parse(data));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@eleven-am/pondsocket",
3
- "version": "0.1.192",
3
+ "version": "0.1.194",
4
4
  "description": "PondSocket is a fast simple socket server",
5
5
  "keywords": [
6
6
  "socket",
@@ -60,7 +60,7 @@
60
60
  "prettier": "^3.5.2",
61
61
  "source-map-support": "^0.5.21",
62
62
  "supertest": "^7.0.0",
63
- "ts-jest": "^29.2.5",
63
+ "ts-jest": "^29.2.6",
64
64
  "ts-loader": "^9.5.2",
65
65
  "ts-node": "^10.9.2",
66
66
  "tsconfig-paths": "^4.2.0",
package/types.d.ts CHANGED
@@ -24,8 +24,6 @@ export interface RedisOptions {
24
24
  username?: string;
25
25
  password?: string;
26
26
  instanceTtl?: number;
27
- heartbeatInterval?: number;
28
- cleanupInterval?: number;
29
27
  }
30
28
 
31
29
  export interface LeaveEvent<EventTypes extends PondEventMap = PondEventMap, PresenceType extends PondPresence = PondPresence, AssignType extends PondAssigns = PondAssigns> {