@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.
- package/abstracts/redisClient.js +281 -159
- package/package.json +21 -17
package/abstracts/redisClient.js
CHANGED
|
@@ -33,7 +33,7 @@ var __rest = (this && this.__rest) || function (s, e) {
|
|
|
33
33
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
34
34
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
35
35
|
};
|
|
36
|
-
var _RedisClient_instances, _RedisClient_heartbeatInterval, _RedisClient_cleanupInterval, _RedisClient_instanceTtl, _RedisClient_redisClient, _RedisClient_pubClient, _RedisClient_subClient, _RedisClient_instanceId, _RedisClient_assignsPublisher, _RedisClient_presencePublisher, _RedisClient_userLeavesPublisher, _RedisClient_channelMessagePublisher, _RedisClient_stateSyncPublisher, _RedisClient_heartbeatTimer, _RedisClient_cleanupTimer, _RedisClient_presence_changes_channel_get, _RedisClient_assigns_changes_channel_get, _RedisClient_channel_messages_channel_get, _RedisClient_user_leaves_channel_get, _RedisClient_handleErrors,
|
|
36
|
+
var _RedisClient_instances, _RedisClient_heartbeatInterval, _RedisClient_cleanupInterval, _RedisClient_instanceTtl, _RedisClient_redisClient, _RedisClient_pubClient, _RedisClient_subClient, _RedisClient_instanceId, _RedisClient_assignsPublisher, _RedisClient_presencePublisher, _RedisClient_userLeavesPublisher, _RedisClient_channelMessagePublisher, _RedisClient_stateSyncPublisher, _RedisClient_heartbeatTimer, _RedisClient_cleanupTimer, _RedisClient_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
|
|
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
|
-
|
|
61
|
-
|
|
62
|
-
|
|
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
|
-
|
|
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(
|
|
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,
|
|
178
|
-
const
|
|
179
|
-
|
|
180
|
-
|
|
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
|
|
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
|
-
},
|
|
161
|
+
}, _RedisClient_cleanupDisconnectedClients = function _RedisClient_cleanupDisconnectedClients() {
|
|
189
162
|
return __awaiter(this, void 0, void 0, function* () {
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
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
|
-
|
|
199
|
-
|
|
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
|
-
|
|
229
|
+
console.error('Error cleaning up disconnected clients:', error);
|
|
211
230
|
}
|
|
212
|
-
})
|
|
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",
|
|
235
|
+
yield __classPrivateFieldGet(this, _RedisClient_instances, "m", _RedisClient_cleanupDisconnectedClients).call(this);
|
|
217
236
|
}
|
|
218
237
|
catch (error) {
|
|
219
|
-
|
|
238
|
+
console.error('Error in periodic cleanup:', error);
|
|
220
239
|
}
|
|
221
240
|
}), __classPrivateFieldGet(this, _RedisClient_cleanupInterval, "f")), "f");
|
|
222
|
-
},
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
const activeSet = new Set(activeInstances);
|
|
227
|
-
const inactiveKeys = [];
|
|
228
|
-
const uniquePairs = new Set();
|
|
229
|
-
for (const key of allKeys) {
|
|
230
|
-
const [, instanceId, endpointId, channelId] = key.split(':');
|
|
231
|
-
if (!activeSet.has(instanceId)) {
|
|
232
|
-
inactiveKeys.push(key);
|
|
233
|
-
uniquePairs.add(`${endpointId}:${channelId}`);
|
|
234
|
-
}
|
|
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
|
-
|
|
237
|
-
|
|
246
|
+
catch (error) {
|
|
247
|
+
console.error('Error performing heartbeat:', error);
|
|
238
248
|
}
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
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
|
-
|
|
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
|
|
383
|
+
local instances = ARGV[1]
|
|
288
384
|
local presence_data = {}
|
|
289
385
|
local assigns_data = {}
|
|
290
386
|
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
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
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
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
|
-
|
|
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
|
-
},
|
|
334
|
-
return
|
|
335
|
-
|
|
336
|
-
|
|
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
|
-
},
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
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
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
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.
|
|
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.
|
|
39
|
-
"ws": "^8.18.
|
|
38
|
+
"ioredis": "^5.5.0",
|
|
39
|
+
"ws": "^8.18.1"
|
|
40
40
|
},
|
|
41
41
|
"devDependencies": {
|
|
42
|
-
"@
|
|
42
|
+
"@eslint/compat": "^1.2.7",
|
|
43
|
+
"@eslint/eslintrc": "^3.3.0",
|
|
44
|
+
"@eslint/js": "^9.21.0",
|
|
45
|
+
"@stylistic/eslint-plugin-ts": "^4.0.1",
|
|
43
46
|
"@types/jest": "^29.5.14",
|
|
44
|
-
"@types/node": "^22.
|
|
45
|
-
"@types/ws": "^8.5.
|
|
46
|
-
"@typescript-eslint/eslint-plugin": "^8.
|
|
47
|
-
"@typescript-eslint/parser": "^8.
|
|
48
|
-
"eslint": "^
|
|
49
|
-
"eslint-config-prettier": "^
|
|
47
|
+
"@types/node": "^22.13.5",
|
|
48
|
+
"@types/ws": "^8.5.14",
|
|
49
|
+
"@typescript-eslint/eslint-plugin": "^8.24.1",
|
|
50
|
+
"@typescript-eslint/parser": "^8.24.1",
|
|
51
|
+
"eslint": "^9.21.0",
|
|
52
|
+
"eslint-config-prettier": "^10.0.1",
|
|
50
53
|
"eslint-import-resolver-node": "^0.3.9",
|
|
51
|
-
"eslint-plugin-file-progress": "^
|
|
54
|
+
"eslint-plugin-file-progress": "^3.0.1",
|
|
52
55
|
"eslint-plugin-import": "^2.31.0",
|
|
53
|
-
"eslint-plugin-prettier": "^5.2.
|
|
56
|
+
"eslint-plugin-prettier": "^5.2.3",
|
|
57
|
+
"globals": "^16.0.0",
|
|
54
58
|
"jest": "^29.7.0",
|
|
55
|
-
"nodemon": "^3.1.
|
|
56
|
-
"prettier": "^3.
|
|
59
|
+
"nodemon": "^3.1.9",
|
|
60
|
+
"prettier": "^3.5.2",
|
|
57
61
|
"source-map-support": "^0.5.21",
|
|
58
62
|
"supertest": "^7.0.0",
|
|
59
|
-
"ts-jest": "^29.2.
|
|
60
|
-
"ts-loader": "^9.5.
|
|
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.
|
|
67
|
+
"typescript": "^5.7.3"
|
|
64
68
|
},
|
|
65
69
|
"jest": {
|
|
66
70
|
"moduleFileExtensions": [
|