@jskit-ai/realtime 0.1.28 → 0.1.29
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.descriptor.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export default Object.freeze({
|
|
2
2
|
packageVersion: 1,
|
|
3
3
|
packageId: "@jskit-ai/realtime",
|
|
4
|
-
version: "0.1.
|
|
4
|
+
version: "0.1.29",
|
|
5
5
|
kind: "runtime",
|
|
6
6
|
description: "Thin, generic realtime runtime wrappers for socket.io server and client.",
|
|
7
7
|
options: {
|
|
@@ -95,7 +95,7 @@ export default Object.freeze({
|
|
|
95
95
|
mutations: {
|
|
96
96
|
dependencies: {
|
|
97
97
|
runtime: {
|
|
98
|
-
"@jskit-ai/kernel": "0.1.
|
|
98
|
+
"@jskit-ai/kernel": "0.1.30",
|
|
99
99
|
"@socket.io/redis-adapter": "^8.3.0",
|
|
100
100
|
"redis": "^5.8.2",
|
|
101
101
|
"socket.io": "^4.8.3",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jskit-ai/realtime",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.29",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"test": "node --test"
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
},
|
|
17
17
|
"dependencies": {
|
|
18
18
|
"@socket.io/redis-adapter": "^8.3.0",
|
|
19
|
-
"@jskit-ai/kernel": "0.1.
|
|
19
|
+
"@jskit-ai/kernel": "0.1.30",
|
|
20
20
|
"redis": "^5.8.2",
|
|
21
21
|
"socket.io": "^4.8.3",
|
|
22
22
|
"socket.io-client": "^4.8.3"
|
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
createSocketIoServer,
|
|
9
9
|
closeSocketIoServer,
|
|
10
10
|
resolveRealtimeRedisUrl,
|
|
11
|
+
resolveRealtimeRedisNamespace,
|
|
11
12
|
configureSocketIoRedisAdapter,
|
|
12
13
|
closeSocketIoRedisConnections
|
|
13
14
|
} from "./runtime.js";
|
|
@@ -684,8 +685,10 @@ class RealtimeServiceProvider {
|
|
|
684
685
|
|
|
685
686
|
const env = typeof app.has === "function" && app.has("jskit.env") ? app.make("jskit.env") : {};
|
|
686
687
|
const redisUrl = resolveRealtimeRedisUrl(env);
|
|
688
|
+
const redisNamespace = resolveRealtimeRedisNamespace(env);
|
|
687
689
|
this.redisConnection = await configureSocketIoRedisAdapter(this.socketIoServer, {
|
|
688
|
-
redisUrl
|
|
690
|
+
redisUrl,
|
|
691
|
+
redisNamespace
|
|
689
692
|
});
|
|
690
693
|
}
|
|
691
694
|
|
package/src/server/runtime.js
CHANGED
|
@@ -4,7 +4,8 @@ import { createClient as createRedisClient } from "redis";
|
|
|
4
4
|
import { normalizeText } from "@jskit-ai/kernel/shared/support/normalize";
|
|
5
5
|
|
|
6
6
|
const SOCKET_IO_PATH = "/socket.io";
|
|
7
|
-
const
|
|
7
|
+
const REDIS_URL_ENV_KEY = "REDIS_URL";
|
|
8
|
+
const REDIS_NAMESPACE_ENV_KEY = "REDIS_NAMESPACE";
|
|
8
9
|
|
|
9
10
|
function resolveHttpServer({ httpServer = null, fastify = null } = {}) {
|
|
10
11
|
if (httpServer && typeof httpServer === "object") {
|
|
@@ -59,18 +60,39 @@ async function closeSocketIoServer(io) {
|
|
|
59
60
|
|
|
60
61
|
function resolveRealtimeRedisUrl(env = {}) {
|
|
61
62
|
const source = env && typeof env === "object" && !Array.isArray(env) ? env : {};
|
|
62
|
-
return normalizeText(source[
|
|
63
|
+
return normalizeText(source[REDIS_URL_ENV_KEY]);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function resolveRealtimeRedisNamespace(env = {}) {
|
|
67
|
+
const source = env && typeof env === "object" && !Array.isArray(env) ? env : {};
|
|
68
|
+
return normalizeText(source[REDIS_NAMESPACE_ENV_KEY]);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function buildSocketIoRedisAdapterKey(redisNamespace = "") {
|
|
72
|
+
const normalizedRedisNamespace = normalizeText(redisNamespace).replace(/:+$/g, "");
|
|
73
|
+
if (!normalizedRedisNamespace) {
|
|
74
|
+
return "";
|
|
75
|
+
}
|
|
76
|
+
return `${normalizedRedisNamespace}:socket.io`;
|
|
63
77
|
}
|
|
64
78
|
|
|
65
79
|
async function configureSocketIoRedisAdapter(
|
|
66
80
|
io,
|
|
67
|
-
{
|
|
81
|
+
{
|
|
82
|
+
redisUrl = "",
|
|
83
|
+
redisNamespace = "",
|
|
84
|
+
createRedisAdapter = createSocketIoRedisAdapter,
|
|
85
|
+
createRedisConnection = createRedisClient
|
|
86
|
+
} = {}
|
|
68
87
|
) {
|
|
69
88
|
const normalizedRedisUrl = normalizeText(redisUrl);
|
|
89
|
+
const adapterKey = buildSocketIoRedisAdapterKey(redisNamespace);
|
|
70
90
|
if (!normalizedRedisUrl) {
|
|
71
91
|
return Object.freeze({
|
|
72
92
|
enabled: false,
|
|
73
93
|
redisUrl: "",
|
|
94
|
+
redisNamespace: "",
|
|
95
|
+
adapterKey: "",
|
|
74
96
|
pubClient: null,
|
|
75
97
|
subClient: null
|
|
76
98
|
});
|
|
@@ -79,7 +101,7 @@ async function configureSocketIoRedisAdapter(
|
|
|
79
101
|
throw new Error("configureSocketIoRedisAdapter requires socket.io server instance with adapter().");
|
|
80
102
|
}
|
|
81
103
|
|
|
82
|
-
const pubClient =
|
|
104
|
+
const pubClient = createRedisConnection({
|
|
83
105
|
url: normalizedRedisUrl
|
|
84
106
|
});
|
|
85
107
|
const subClient = pubClient.duplicate();
|
|
@@ -87,7 +109,8 @@ async function configureSocketIoRedisAdapter(
|
|
|
87
109
|
try {
|
|
88
110
|
await pubClient.connect();
|
|
89
111
|
await subClient.connect();
|
|
90
|
-
|
|
112
|
+
const adapterOptions = adapterKey ? { key: adapterKey } : undefined;
|
|
113
|
+
io.adapter(createRedisAdapter(pubClient, subClient, adapterOptions));
|
|
91
114
|
} catch (error) {
|
|
92
115
|
await closeSocketIoRedisConnections({
|
|
93
116
|
pubClient,
|
|
@@ -99,6 +122,8 @@ async function configureSocketIoRedisAdapter(
|
|
|
99
122
|
return Object.freeze({
|
|
100
123
|
enabled: true,
|
|
101
124
|
redisUrl: normalizedRedisUrl,
|
|
125
|
+
redisNamespace: normalizeText(redisNamespace),
|
|
126
|
+
adapterKey,
|
|
102
127
|
pubClient,
|
|
103
128
|
subClient
|
|
104
129
|
});
|
|
@@ -127,8 +152,10 @@ async function closeSocketIoRedisConnections({ pubClient = null, subClient = nul
|
|
|
127
152
|
export {
|
|
128
153
|
createSocketIoServer,
|
|
129
154
|
closeSocketIoServer,
|
|
130
|
-
|
|
155
|
+
REDIS_URL_ENV_KEY,
|
|
156
|
+
REDIS_NAMESPACE_ENV_KEY,
|
|
131
157
|
resolveRealtimeRedisUrl,
|
|
158
|
+
resolveRealtimeRedisNamespace,
|
|
132
159
|
configureSocketIoRedisAdapter,
|
|
133
160
|
closeSocketIoRedisConnections
|
|
134
161
|
};
|
|
@@ -2,10 +2,12 @@ import assert from "node:assert/strict";
|
|
|
2
2
|
import test from "node:test";
|
|
3
3
|
|
|
4
4
|
import {
|
|
5
|
-
|
|
5
|
+
REDIS_URL_ENV_KEY,
|
|
6
|
+
REDIS_NAMESPACE_ENV_KEY,
|
|
6
7
|
createSocketIoServer,
|
|
7
8
|
closeSocketIoServer,
|
|
8
9
|
resolveRealtimeRedisUrl,
|
|
10
|
+
resolveRealtimeRedisNamespace,
|
|
9
11
|
configureSocketIoRedisAdapter,
|
|
10
12
|
closeSocketIoRedisConnections
|
|
11
13
|
} from "../src/server/runtime.js";
|
|
@@ -108,7 +110,15 @@ test("closeSocketIoServer swallows ERR_SERVER_NOT_RUNNING", async () => {
|
|
|
108
110
|
|
|
109
111
|
test("resolveRealtimeRedisUrl reads and normalizes env URL", () => {
|
|
110
112
|
assert.equal(resolveRealtimeRedisUrl({}), "");
|
|
111
|
-
assert.equal(resolveRealtimeRedisUrl({ [
|
|
113
|
+
assert.equal(resolveRealtimeRedisUrl({ [REDIS_URL_ENV_KEY]: " redis://localhost:6379 " }), "redis://localhost:6379");
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
test("resolveRealtimeRedisNamespace reads and normalizes env namespace", () => {
|
|
117
|
+
assert.equal(resolveRealtimeRedisNamespace({}), "");
|
|
118
|
+
assert.equal(
|
|
119
|
+
resolveRealtimeRedisNamespace({ [REDIS_NAMESPACE_ENV_KEY]: " app:production " }),
|
|
120
|
+
"app:production"
|
|
121
|
+
);
|
|
112
122
|
});
|
|
113
123
|
|
|
114
124
|
test("configureSocketIoRedisAdapter keeps memory mode when URL is empty", async () => {
|
|
@@ -147,3 +157,49 @@ test("closeSocketIoRedisConnections closes both clients when present", async ()
|
|
|
147
157
|
|
|
148
158
|
assert.deepEqual(calls, ["sub.quit", "pub.quit"]);
|
|
149
159
|
});
|
|
160
|
+
|
|
161
|
+
test("configureSocketIoRedisAdapter applies namespaced adapter key when redis namespace is set", async () => {
|
|
162
|
+
const adapterCalls = [];
|
|
163
|
+
const io = {
|
|
164
|
+
adapter(adapter) {
|
|
165
|
+
adapterCalls.push(adapter);
|
|
166
|
+
}
|
|
167
|
+
};
|
|
168
|
+
const connectionCalls = [];
|
|
169
|
+
const createRedisConnection = ({ url }) => {
|
|
170
|
+
const client = {
|
|
171
|
+
url,
|
|
172
|
+
async connect() {
|
|
173
|
+
connectionCalls.push(`${url}:connect`);
|
|
174
|
+
},
|
|
175
|
+
async quit() {},
|
|
176
|
+
duplicate() {
|
|
177
|
+
return {
|
|
178
|
+
async connect() {
|
|
179
|
+
connectionCalls.push(`${url}:duplicate.connect`);
|
|
180
|
+
},
|
|
181
|
+
async quit() {}
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
};
|
|
185
|
+
return client;
|
|
186
|
+
};
|
|
187
|
+
const createRedisAdapter = (pubClient, subClient, options) => ({
|
|
188
|
+
pubClient,
|
|
189
|
+
subClient,
|
|
190
|
+
options
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
const result = await configureSocketIoRedisAdapter(io, {
|
|
194
|
+
redisUrl: "redis://localhost:6379",
|
|
195
|
+
redisNamespace: "my-app:production",
|
|
196
|
+
createRedisConnection,
|
|
197
|
+
createRedisAdapter
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
assert.equal(connectionCalls.length, 2);
|
|
201
|
+
assert.equal(adapterCalls.length, 1);
|
|
202
|
+
assert.equal(adapterCalls[0].options?.key, "my-app:production:socket.io");
|
|
203
|
+
assert.equal(result.adapterKey, "my-app:production:socket.io");
|
|
204
|
+
assert.equal(result.redisNamespace, "my-app:production");
|
|
205
|
+
});
|