@eleven-am/pondsocket 0.1.191 → 0.1.193

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.
Files changed (2) hide show
  1. package/abstracts/redisClient.js +281 -159
  2. package/package.json +21 -17
@@ -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_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, _RedisClient_deleteKeysByPattern, _RedisClient_generateCache;
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");
@@ -97,17 +102,15 @@ class RedisClient {
97
102
  }
98
103
  shutdown() {
99
104
  return __awaiter(this, void 0, void 0, function* () {
100
- const batchSize = 1000;
101
105
  clearInterval(__classPrivateFieldGet(this, _RedisClient_heartbeatTimer, "f"));
102
106
  clearInterval(__classPrivateFieldGet(this, _RedisClient_cleanupTimer, "f"));
103
107
  yield __classPrivateFieldGet(this, _RedisClient_instances, "m", _RedisClient_unsubscribeFromChannels).call(this);
104
108
  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
109
  });
107
110
  }
108
111
  }
109
112
  exports.RedisClient = RedisClient;
110
- _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() {
111
114
  return 'presence_changes';
112
115
  }, _RedisClient_assigns_changes_channel_get = function _RedisClient_assigns_changes_channel_get() {
113
116
  return 'assigns_changes';
@@ -119,128 +122,146 @@ _RedisClient_heartbeatInterval = new WeakMap(), _RedisClient_cleanupInterval = n
119
122
  __classPrivateFieldGet(this, _RedisClient_redisClient, "f").on('error', (err) => console.error('Redis client error:', err));
120
123
  __classPrivateFieldGet(this, _RedisClient_pubClient, "f").on('error', (err) => console.error('Redis pub client error:', err));
121
124
  __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
125
  }, _RedisClient_registerInstance = function _RedisClient_registerInstance() {
165
126
  return __awaiter(this, void 0, void 0, function* () {
166
- const multi = __classPrivateFieldGet(this, _RedisClient_redisClient, "f").multi();
167
- multi.sadd('distributed_instances', __classPrivateFieldGet(this, _RedisClient_instanceId, "f"));
168
- multi.set(`heartbeat:${__classPrivateFieldGet(this, _RedisClient_instanceId, "f")}`, Date.now().toString(), 'EX', __classPrivateFieldGet(this, _RedisClient_instanceTtl, "f"));
169
127
  try {
170
- 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"));
171
130
  }
172
131
  catch (error) {
173
- throw new redisError_1.RedisError('Error registering instance');
132
+ throw new redisError_1.RedisError(`Error registering instance: ${error}`);
174
133
  }
175
134
  });
176
135
  }, _RedisClient_unregisterInstance = function _RedisClient_unregisterInstance() {
177
- return __awaiter(this, void 0, void 0, function* () {
178
- const multi = __classPrivateFieldGet(this, _RedisClient_redisClient, "f").multi();
179
- multi.srem('distributed_instances', __classPrivateFieldGet(this, _RedisClient_instanceId, "f"));
180
- multi.del(`heartbeat:${__classPrivateFieldGet(this, _RedisClient_instanceId, "f")}`);
136
+ return __awaiter(this, arguments, void 0, function* (instanceId = __classPrivateFieldGet(this, _RedisClient_instanceId, "f")) {
137
+ const script = `
138
+ -- Delete heartbeat
139
+ redis.call('DEL', 'heartbeat:' .. ARGV[1])
140
+
141
+ -- Delete all cache keys in one SCAN operation
142
+ local cursor = '0'
143
+ repeat
144
+ local result = redis.call('SCAN', cursor, 'MATCH', '*_cache:' .. ARGV[1] .. ':*', 'COUNT', 100)
145
+ cursor = result[1]
146
+ local keys = result[2]
147
+ if #keys > 0 then
148
+ redis.call('DEL', unpack(keys))
149
+ end
150
+ until cursor == '0'
151
+
152
+ return 1
153
+ `;
181
154
  try {
182
- yield multi.exec();
155
+ yield __classPrivateFieldGet(this, _RedisClient_redisClient, "f").eval(script, 0, instanceId);
183
156
  }
184
157
  catch (_a) {
185
158
  // no-op as we're shutting down
186
159
  }
187
160
  });
188
- }, _RedisClient_unsubscribeFromChannels = function _RedisClient_unsubscribeFromChannels() {
161
+ }, _RedisClient_cleanupDisconnectedClients = function _RedisClient_cleanupDisconnectedClients() {
189
162
  return __awaiter(this, void 0, void 0, function* () {
190
- 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));
191
- });
192
- }, _RedisClient_subscribeToChannels = function _RedisClient_subscribeToChannels() {
193
- return new Promise((resolve, reject) => {
194
- __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) => {
195
- if (err) {
196
- reject(err);
163
+ const script = `
164
+ -- Array of instance IDs that have cache data but no heartbeat
165
+ local dead_instances = cjson.decode(ARGV[1])
166
+ local affected_channels = {}
167
+ local keys_to_delete = {}
168
+ local batch_size = 100
169
+
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]
177
+
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
184
+
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 = {}
192
+ end
193
+ end
194
+ until cache_cursor == '0'
195
+ end
196
+
197
+ -- Delete any remaining keys
198
+ if #keys_to_delete > 0 then
199
+ redis.call('DEL', unpack(keys_to_delete))
200
+ end
201
+
202
+ -- Convert affected_channels to array
203
+ local channels = {}
204
+ for pair in pairs(affected_channels) do
205
+ table.insert(channels, pair)
206
+ end
207
+
208
+ return channels
209
+ `;
210
+ try {
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;
197
218
  }
198
- else {
199
- resolve();
219
+ const affectedChannels = yield __classPrivateFieldGet(this, _RedisClient_redisClient, "f").eval(script, 0, JSON.stringify(deadInstances));
220
+ if (affectedChannels.length > 0) {
221
+ const promises = affectedChannels.map((pair) => {
222
+ const [endpointId, channelId] = pair.split(':');
223
+ return __classPrivateFieldGet(this, _RedisClient_instances, "m", _RedisClient_emitStateSyncEvent).call(this, endpointId, channelId);
224
+ });
225
+ yield Promise.all(promises);
200
226
  }
201
- });
202
- __classPrivateFieldGet(this, _RedisClient_subClient, "f").on('message', __classPrivateFieldGet(this, _RedisClient_instances, "m", _RedisClient_handleRedisMessage).bind(this));
203
- });
204
- }, _RedisClient_startHeartbeat = function _RedisClient_startHeartbeat() {
205
- __classPrivateFieldSet(this, _RedisClient_heartbeatTimer, setInterval(() => __awaiter(this, void 0, void 0, function* () {
206
- try {
207
- yield __classPrivateFieldGet(this, _RedisClient_redisClient, "f").set(`heartbeat:${__classPrivateFieldGet(this, _RedisClient_instanceId, "f")}`, Date.now().toString(), 'EX', __classPrivateFieldGet(this, _RedisClient_instanceTtl, "f"));
208
227
  }
209
228
  catch (error) {
210
- throw new redisError_1.RedisError('Error setting heartbeat');
229
+ console.error('Error cleaning up disconnected clients:', error);
211
230
  }
212
- }), __classPrivateFieldGet(this, _RedisClient_heartbeatInterval, "f")), "f");
231
+ });
213
232
  }, _RedisClient_startPeriodicCleanup = function _RedisClient_startPeriodicCleanup() {
214
233
  __classPrivateFieldSet(this, _RedisClient_cleanupTimer, setInterval(() => __awaiter(this, void 0, void 0, function* () {
215
234
  try {
216
- yield __classPrivateFieldGet(this, _RedisClient_instances, "m", _RedisClient_performConsistencyCheck).call(this);
235
+ yield __classPrivateFieldGet(this, _RedisClient_instances, "m", _RedisClient_cleanupDisconnectedClients).call(this);
217
236
  }
218
237
  catch (error) {
219
- throw new redisError_1.RedisError('Error performing consistency check');
238
+ console.error('Error in periodic cleanup:', error);
220
239
  }
221
240
  }), __classPrivateFieldGet(this, _RedisClient_cleanupInterval, "f")), "f");
222
- }, _RedisClient_performConsistencyCheck = function _RedisClient_performConsistencyCheck() {
223
- return __awaiter(this, void 0, void 0, function* () {
224
- const activeInstances = yield __classPrivateFieldGet(this, _RedisClient_redisClient, "f").smembers('distributed_instances');
225
- const allKeys = yield __classPrivateFieldGet(this, _RedisClient_redisClient, "f").keys('*_cache:*');
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
- }
241
+ }, _RedisClient_startHeartbeat = function _RedisClient_startHeartbeat() {
242
+ __classPrivateFieldSet(this, _RedisClient_heartbeatTimer, setInterval(() => __awaiter(this, void 0, void 0, function* () {
243
+ try {
244
+ yield __classPrivateFieldGet(this, _RedisClient_instances, "m", _RedisClient_registerInstance).call(this);
235
245
  }
236
- if (inactiveKeys.length > 0) {
237
- yield __classPrivateFieldGet(this, _RedisClient_redisClient, "f").del(...inactiveKeys);
246
+ catch (error) {
247
+ console.error('Error performing heartbeat:', error);
238
248
  }
239
- const promises = Array.from(uniquePairs).map((pair) => {
240
- const [endpointId, channelId] = pair.split(':');
241
- return __classPrivateFieldGet(this, _RedisClient_instances, "m", _RedisClient_emitStateSyncEvent).call(this, endpointId, channelId);
249
+ }), __classPrivateFieldGet(this, _RedisClient_heartbeatInterval, "f")), "f");
250
+ }, _RedisClient_unsubscribeFromChannels = function _RedisClient_unsubscribeFromChannels() {
251
+ return __awaiter(this, void 0, void 0, function* () {
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));
253
+ });
254
+ }, _RedisClient_subscribeToChannels = function _RedisClient_subscribeToChannels() {
255
+ return new Promise((resolve, reject) => {
256
+ __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) => {
257
+ if (err) {
258
+ reject(err);
259
+ }
260
+ else {
261
+ resolve();
262
+ }
242
263
  });
243
- yield Promise.all(promises);
264
+ __classPrivateFieldGet(this, _RedisClient_subClient, "f").on('message', __classPrivateFieldGet(this, _RedisClient_instances, "m", _RedisClient_handleRedisMessage).bind(this));
244
265
  });
245
266
  }, _RedisClient_handleRedisMessage = function _RedisClient_handleRedisMessage(channel, message) {
246
267
  const data = JSON.parse(message);
@@ -260,6 +281,81 @@ _RedisClient_heartbeatInterval = new WeakMap(), _RedisClient_cleanupInterval = n
260
281
  default:
261
282
  throw new Error(`Unknown channel: ${channel}`);
262
283
  }
284
+ }, _RedisClient_getPresenceCacheChannel = function _RedisClient_getPresenceCacheChannel(endpointId, channelId) {
285
+ return `presence_cache:${__classPrivateFieldGet(this, _RedisClient_instanceId, "f")}:${endpointId}:${channelId}`;
286
+ }, _RedisClient_getAssignsCacheChannel = function _RedisClient_getAssignsCacheChannel(endpointId, channelId) {
287
+ return `assigns_cache:${__classPrivateFieldGet(this, _RedisClient_instanceId, "f")}:${endpointId}:${channelId}`;
288
+ }, _RedisClient_subscribeToCacheChanges = function _RedisClient_subscribeToCacheChanges(endpoint, channel, presence, callback) {
289
+ const subject = presence ? __classPrivateFieldGet(this, _RedisClient_presencePublisher, "f") : __classPrivateFieldGet(this, _RedisClient_assignsPublisher, "f");
290
+ return subject.subscribe((_a) => {
291
+ var { endpointId, channelId } = _a, data = __rest(_a, ["endpointId", "channelId"]);
292
+ if (endpointId === endpoint && channelId === channel) {
293
+ return callback(data);
294
+ }
295
+ });
296
+ }, _RedisClient_subscribeToChannelMessages = function _RedisClient_subscribeToChannelMessages(endpoint, channel, callback) {
297
+ return __classPrivateFieldGet(this, _RedisClient_channelMessagePublisher, "f").subscribe(({ endpointId, channelId, message }) => {
298
+ if (endpointId === endpoint && channelId === channel) {
299
+ return callback(message);
300
+ }
301
+ });
302
+ }, _RedisClient_subscribeToUserLeaves = function _RedisClient_subscribeToUserLeaves(endpoint, channel, callback) {
303
+ return __classPrivateFieldGet(this, _RedisClient_userLeavesPublisher, "f").subscribe(({ endpointId, channelId, userId }) => {
304
+ if (endpointId === endpoint && channelId === channel) {
305
+ return callback(userId);
306
+ }
307
+ });
308
+ }, _RedisClient_subscribeToStateSync = function _RedisClient_subscribeToStateSync(endpoint, channel, callback) {
309
+ const interval = setInterval(() => __classPrivateFieldGet(this, _RedisClient_instances, "m", _RedisClient_emitStateSyncEvent).call(this, endpoint, channel), __classPrivateFieldGet(this, _RedisClient_stateSyncInterval, "f"));
310
+ const subscription = __classPrivateFieldGet(this, _RedisClient_stateSyncPublisher, "f").subscribe((_a) => {
311
+ var { endpointId, channelId } = _a, data = __rest(_a, ["endpointId", "channelId"]);
312
+ if (endpointId === endpoint && channelId === channel) {
313
+ return callback(data);
314
+ }
315
+ });
316
+ void __classPrivateFieldGet(this, _RedisClient_instances, "m", _RedisClient_emitStateSyncEvent).call(this, endpoint, channel, true);
317
+ return () => {
318
+ clearInterval(interval);
319
+ subscription();
320
+ };
321
+ }, _RedisClient_publishPresenceChange = function _RedisClient_publishPresenceChange(endpointId, channelId, userId, state) {
322
+ const message = {
323
+ userId,
324
+ channelId,
325
+ endpointId,
326
+ state,
327
+ };
328
+ const key = __classPrivateFieldGet(this, _RedisClient_instances, "a", _RedisClient_presence_changes_channel_get);
329
+ const cacheKey = __classPrivateFieldGet(this, _RedisClient_instances, "m", _RedisClient_getPresenceCacheChannel).call(this, message.endpointId, message.channelId);
330
+ return __classPrivateFieldGet(this, _RedisClient_instances, "m", _RedisClient_publishCacheMessage).call(this, key, cacheKey, message);
331
+ }, _RedisClient_publishAssignsChange = function _RedisClient_publishAssignsChange(endpointId, channelId, userId, state) {
332
+ const message = {
333
+ userId,
334
+ channelId,
335
+ endpointId,
336
+ state,
337
+ };
338
+ const key = __classPrivateFieldGet(this, _RedisClient_instances, "a", _RedisClient_assigns_changes_channel_get);
339
+ const cacheKey = __classPrivateFieldGet(this, _RedisClient_instances, "m", _RedisClient_getAssignsCacheChannel).call(this, message.endpointId, message.channelId);
340
+ return __classPrivateFieldGet(this, _RedisClient_instances, "m", _RedisClient_publishCacheMessage).call(this, key, cacheKey, message);
341
+ }, _RedisClient_publishChannelMessage = function _RedisClient_publishChannelMessage(endpointId, channelId, message) {
342
+ return __awaiter(this, void 0, void 0, function* () {
343
+ const messageData = JSON.stringify({
344
+ endpointId,
345
+ channelId,
346
+ message,
347
+ });
348
+ yield __classPrivateFieldGet(this, _RedisClient_pubClient, "f").publish(__classPrivateFieldGet(this, _RedisClient_instances, "a", _RedisClient_channel_messages_channel_get), messageData);
349
+ });
350
+ }, _RedisClient_publishUserLeave = function _RedisClient_publishUserLeave(endpointId, channelId, userId) {
351
+ return __awaiter(this, void 0, void 0, function* () {
352
+ const message = JSON.stringify({
353
+ endpointId,
354
+ channelId,
355
+ userId,
356
+ });
357
+ yield __classPrivateFieldGet(this, _RedisClient_pubClient, "f").publish(__classPrivateFieldGet(this, _RedisClient_instances, "a", _RedisClient_user_leaves_channel_get), message);
358
+ });
263
359
  }, _RedisClient_publishCacheMessage = function _RedisClient_publishCacheMessage(key, cacheKey, message) {
264
360
  return __awaiter(this, void 0, void 0, function* () {
265
361
  const script = `
@@ -284,71 +380,112 @@ _RedisClient_heartbeatInterval = new WeakMap(), _RedisClient_cleanupInterval = n
284
380
  }, _RedisClient_emitStateSyncEvent = function _RedisClient_emitStateSyncEvent(endpointId_1, channelId_1) {
285
381
  return __awaiter(this, arguments, void 0, function* (endpointId, channelId, initialFetch = false) {
286
382
  const script = `
287
- local active_instances = redis.call('SMEMBERS', 'distributed_instances')
383
+ local instances = ARGV[1]
288
384
  local presence_data = {}
289
385
  local assigns_data = {}
290
386
 
291
- for _, instance in ipairs(active_instances) do
292
- local presence_key = 'presence_cache:' .. instance .. ':' .. ARGV[1] .. ':' .. ARGV[2]
293
- local assigns_key = 'assigns_cache:' .. instance .. ':' .. ARGV[1] .. ':' .. ARGV[2]
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]
294
393
 
394
+ -- Get all presence data for this instance and channel
295
395
  local presence = redis.call('HGETALL', presence_key)
296
- local assigns = redis.call('HGETALL', assigns_key)
297
-
298
396
  for i = 1, #presence, 2 do
299
- presence_data[presence[i]] = presence[i+1]
397
+ presence_data[presence[i]] = presence[i + 1]
300
398
  end
301
399
 
400
+ -- Get all assigns data for this instance and channel
401
+ local assigns = redis.call('HGETALL', assigns_key)
302
402
  for i = 1, #assigns, 2 do
303
- assigns_data[assigns[i]] = assigns[i+1]
403
+ assigns_data[assigns[i]] = assigns[i + 1]
304
404
  end
305
405
  end
306
406
 
307
407
  return {cjson.encode(presence_data), cjson.encode(assigns_data)}
308
408
  `;
309
- const [presenceData, assignsData] = yield __classPrivateFieldGet(this, _RedisClient_redisClient, "f").eval(script, 0, endpointId, channelId);
310
- const event = {
311
- endpointId,
312
- channelId,
313
- initialFetch,
314
- presence: __classPrivateFieldGet(this, _RedisClient_instances, "m", _RedisClient_generateCache).call(this, presenceData),
315
- assigns: __classPrivateFieldGet(this, _RedisClient_instances, "m", _RedisClient_generateCache).call(this, assignsData),
316
- };
317
- __classPrivateFieldGet(this, _RedisClient_stateSyncPublisher, "f").publish(event);
318
- });
319
- }, _RedisClient_subscribeToCacheChanges = function _RedisClient_subscribeToCacheChanges(endpoint, channel, presence, callback) {
320
- const subject = presence ? __classPrivateFieldGet(this, _RedisClient_presencePublisher, "f") : __classPrivateFieldGet(this, _RedisClient_assignsPublisher, "f");
321
- return subject.subscribe((_a) => {
322
- var { endpointId, channelId } = _a, data = __rest(_a, ["endpointId", "channelId"]);
323
- if (endpointId === endpoint && channelId === channel) {
324
- return callback(data);
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);
325
422
  }
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);
423
+ catch (error) {
424
+ console.error('Error emitting state sync event:', error);
331
425
  }
332
426
  });
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);
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)
441
+ end
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 [];
337
453
  }
338
454
  });
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);
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)
471
+ end
472
+ end
473
+ until cursor == '0'
474
+
475
+ return cached_instances
476
+ `;
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 [];
345
483
  }
346
484
  });
347
- void __classPrivateFieldGet(this, _RedisClient_instances, "m", _RedisClient_emitStateSyncEvent).call(this, endpoint, channel, true);
348
- return () => {
349
- clearInterval(interval);
350
- subscription();
351
- };
485
+ }, _RedisClient_generateCache = function _RedisClient_generateCache(data) {
486
+ const first = Object.entries(JSON.parse(data));
487
+ const second = first.map(([key, value]) => [key, JSON.parse(String(value))]);
488
+ return new Map(second);
352
489
  }, _RedisClient_handleExit = function _RedisClient_handleExit() {
353
490
  return __awaiter(this, void 0, void 0, function* () {
354
491
  try {
@@ -359,19 +496,4 @@ _RedisClient_heartbeatInterval = new WeakMap(), _RedisClient_cleanupInterval = n
359
496
  process.exit(1);
360
497
  }
361
498
  });
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
499
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@eleven-am/pondsocket",
3
- "version": "0.1.191",
3
+ "version": "0.1.193",
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.4.2",
39
- "ws": "^8.18.0"
38
+ "ioredis": "^5.5.0",
39
+ "ws": "^8.18.1"
40
40
  },
41
41
  "devDependencies": {
42
- "@stylistic/eslint-plugin-ts": "^2.10.1",
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.8.7",
45
- "@types/ws": "^8.5.13",
46
- "@typescript-eslint/eslint-plugin": "^8.11.0",
47
- "@typescript-eslint/parser": "^8.11.0",
48
- "eslint": "^8.57.1",
49
- "eslint-config-prettier": "^9.1.0",
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": "^1.5.0",
54
+ "eslint-plugin-file-progress": "^3.0.1",
52
55
  "eslint-plugin-import": "^2.31.0",
53
- "eslint-plugin-prettier": "^5.2.1",
56
+ "eslint-plugin-prettier": "^5.2.3",
57
+ "globals": "^16.0.0",
54
58
  "jest": "^29.7.0",
55
- "nodemon": "^3.1.7",
56
- "prettier": "^3.3.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
- "ts-jest": "^29.2.5",
60
- "ts-loader": "^9.5.1",
63
+ "ts-jest": "^29.2.6",
64
+ "ts-loader": "^9.5.2",
61
65
  "ts-node": "^10.9.2",
62
66
  "tsconfig-paths": "^4.2.0",
63
- "typescript": "^5.6.3"
67
+ "typescript": "^5.7.3"
64
68
  },
65
69
  "jest": {
66
70
  "moduleFileExtensions": [