svelte-adapter-uws-extensions 0.1.8 → 0.1.9
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/package.json +1 -1
- package/redis/presence.js +21 -68
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "svelte-adapter-uws-extensions",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.9",
|
|
4
4
|
"description": "Redis and Postgres extensions for svelte-adapter-uws - distributed pub/sub, replay buffers, presence tracking, rate limiting, groups, and DB change notifications",
|
|
5
5
|
"author": "Kevin Radziszewski",
|
|
6
6
|
"license": "MIT",
|
package/redis/presence.js
CHANGED
|
@@ -20,57 +20,32 @@
|
|
|
20
20
|
import { randomBytes } from 'node:crypto';
|
|
21
21
|
|
|
22
22
|
/**
|
|
23
|
-
* Lua script for atomic join: set this instance's field
|
|
24
|
-
* check if the user already exists on another instance (non-stale),
|
|
25
|
-
* and return all hash entries so the caller can skip a separate HGETALL.
|
|
23
|
+
* Lua script for atomic join: set this instance's field and expire the key.
|
|
26
24
|
*
|
|
27
25
|
* KEYS[1] = hash key
|
|
28
26
|
* ARGV[1] = field to set (instanceId|userKey)
|
|
29
27
|
* ARGV[2] = field value (JSON with data and ts)
|
|
30
|
-
* ARGV[3] =
|
|
31
|
-
* ARGV[4] = now (ms)
|
|
32
|
-
* ARGV[5] = presenceTtlMs
|
|
33
|
-
* ARGV[6] = presenceTtl (seconds, for EXPIRE)
|
|
28
|
+
* ARGV[3] = presenceTtl (seconds, for EXPIRE)
|
|
34
29
|
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
30
|
+
* Cross-instance dedup is intentionally omitted. The local localCounts
|
|
31
|
+
* map already prevents duplicate joins from the same user on the same
|
|
32
|
+
* instance (the script only runs when prevCount === 0). If the same
|
|
33
|
+
* user joins on a second instance, both broadcast "join" -- the client
|
|
34
|
+
* handles this as an idempotent Map.set on the same key.
|
|
35
|
+
*
|
|
36
|
+
* Removing the HGETALL + O(N) scan that was here before drops per-join
|
|
37
|
+
* Redis work from O(N) to O(1), fixing the O(N^2) total cost that
|
|
38
|
+
* killed the server at 2000+ concurrent joins.
|
|
37
39
|
*/
|
|
38
40
|
const JOIN_SCRIPT = `
|
|
39
41
|
local key = KEYS[1]
|
|
40
42
|
local field = ARGV[1]
|
|
41
43
|
local value = ARGV[2]
|
|
42
|
-
local
|
|
43
|
-
local now = tonumber(ARGV[4])
|
|
44
|
-
local ttlMs = tonumber(ARGV[5])
|
|
45
|
-
local ttlSec = tonumber(ARGV[6])
|
|
44
|
+
local ttlSec = tonumber(ARGV[3])
|
|
46
45
|
|
|
47
46
|
redis.call('hset', key, field, value)
|
|
48
47
|
redis.call('expire', key, ttlSec)
|
|
49
|
-
|
|
50
|
-
local all = redis.call('hgetall', key)
|
|
51
|
-
local isFirst = 1
|
|
52
|
-
for i = 1, #all, 2 do
|
|
53
|
-
local f = all[i]
|
|
54
|
-
if f ~= field and #f >= #suffix and string.sub(f, -#suffix) == suffix then
|
|
55
|
-
local ok, parsed = pcall(cjson.decode, all[i+1])
|
|
56
|
-
if ok and parsed.ts and (now - parsed.ts) <= ttlMs then
|
|
57
|
-
isFirst = 0
|
|
58
|
-
break
|
|
59
|
-
end
|
|
60
|
-
end
|
|
61
|
-
end
|
|
62
|
-
|
|
63
|
-
-- For small hashes return entries inline (saves a round trip).
|
|
64
|
-
-- For large hashes return only the flag; the caller fetches the
|
|
65
|
-
-- list via a coalesced HGETALL shared across concurrent joiners.
|
|
66
|
-
if #all <= 200 then
|
|
67
|
-
local result = {isFirst}
|
|
68
|
-
for i = 1, #all do
|
|
69
|
-
result[#result + 1] = all[i]
|
|
70
|
-
end
|
|
71
|
-
return result
|
|
72
|
-
end
|
|
73
|
-
return {isFirst}
|
|
48
|
+
return 1
|
|
74
49
|
`;
|
|
75
50
|
|
|
76
51
|
/**
|
|
@@ -416,21 +391,14 @@ export function createPresence(client, options = {}) {
|
|
|
416
391
|
// cleaned local state -- undo any Redis write and bail out.
|
|
417
392
|
if (!wsTopics.has(ws)) return;
|
|
418
393
|
|
|
419
|
-
let all;
|
|
420
|
-
|
|
421
394
|
if (prevCount === 0) {
|
|
422
|
-
// New user on this instance --
|
|
423
|
-
// HSET + EXPIRE + dedup check. For small hashes the
|
|
424
|
-
// script returns all entries inline (1 round trip).
|
|
425
|
-
// For large hashes it returns only the dedup flag and
|
|
426
|
-
// we fetch the list via coalesceHgetall below.
|
|
395
|
+
// New user on this instance -- HSET + EXPIRE in one Lua call.
|
|
427
396
|
const now = Date.now();
|
|
428
397
|
const field = compoundField(key);
|
|
429
398
|
const value = JSON.stringify({ data, ts: now });
|
|
430
|
-
|
|
431
|
-
const result = await redis.eval(
|
|
399
|
+
await redis.eval(
|
|
432
400
|
JOIN_SCRIPT, 1, hashKey(topic),
|
|
433
|
-
field, value,
|
|
401
|
+
field, value, presenceTtl
|
|
434
402
|
);
|
|
435
403
|
|
|
436
404
|
// Guard: if ws closed while awaiting Redis, remove the field
|
|
@@ -440,29 +408,14 @@ export function createPresence(client, options = {}) {
|
|
|
440
408
|
return;
|
|
441
409
|
}
|
|
442
410
|
|
|
443
|
-
const
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
// Small hash: entries returned inline
|
|
447
|
-
all = {};
|
|
448
|
-
for (let i = 1; i < result.length; i += 2) {
|
|
449
|
-
all[result[i]] = result[i + 1];
|
|
450
|
-
}
|
|
451
|
-
}
|
|
452
|
-
|
|
453
|
-
if (isFirstGlobally === 1) {
|
|
454
|
-
const payload = { key, data };
|
|
455
|
-
platform.publish('__presence:' + topic, 'join', payload);
|
|
456
|
-
await publishEvent(topic, 'join', payload);
|
|
457
|
-
}
|
|
411
|
+
const payload = { key, data };
|
|
412
|
+
platform.publish('__presence:' + topic, 'join', payload);
|
|
413
|
+
await publishEvent(topic, 'join', payload);
|
|
458
414
|
}
|
|
459
415
|
|
|
460
|
-
// Fetch initial list via coalesced HGETALL
|
|
461
|
-
// did not return entries inline (large hash or dedup join).
|
|
416
|
+
// Fetch initial list via coalesced HGETALL.
|
|
462
417
|
// Concurrent joiners share a single Redis round trip.
|
|
463
|
-
|
|
464
|
-
all = await coalesceHgetall(topic);
|
|
465
|
-
}
|
|
418
|
+
const all = await coalesceHgetall(topic);
|
|
466
419
|
|
|
467
420
|
// Subscribe ws to presence channel (may have closed during async gap)
|
|
468
421
|
try {
|