@eleven-am/pondsocket 0.1.177 → 0.1.179

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