@scpxl/nodejs-framework 1.0.42 → 1.0.43
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/dist/cli/index.js +4 -2
- package/dist/cli/index.js.map +2 -2
- package/dist/websocket/define-subscriber.d.ts +8 -1
- package/dist/websocket/define-subscriber.d.ts.map +1 -1
- package/dist/websocket/define-subscriber.js +2 -1
- package/dist/websocket/define-subscriber.js.map +2 -2
- package/dist/websocket/index.d.ts +4 -0
- package/dist/websocket/index.d.ts.map +1 -1
- package/dist/websocket/index.js +43 -1
- package/dist/websocket/index.js.map +2 -2
- package/dist/websocket/subscriber-middleware.d.ts +52 -0
- package/dist/websocket/subscriber-middleware.d.ts.map +1 -0
- package/dist/websocket/subscriber-middleware.js +200 -0
- package/dist/websocket/subscriber-middleware.js.map +7 -0
- package/dist/websocket/subscriber-utils.d.ts +88 -0
- package/dist/websocket/subscriber-utils.d.ts.map +1 -0
- package/dist/websocket/subscriber-utils.js +227 -0
- package/dist/websocket/subscriber-utils.js.map +7 -0
- package/dist/websocket/websocket-server.d.ts +41 -1
- package/dist/websocket/websocket-server.d.ts.map +1 -1
- package/dist/websocket/websocket-server.js +90 -7
- package/dist/websocket/websocket-server.js.map +2 -2
- package/dist/websocket/websocket.interface.d.ts +7 -0
- package/dist/websocket/websocket.interface.d.ts.map +1 -1
- package/dist/websocket/websocket.interface.js.map +2 -2
- package/package.json +1 -1
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
|
|
3
|
+
import { Logger } from "../logger/index.js";
|
|
4
|
+
function matchByProperty(key, value) {
|
|
5
|
+
return (context) => {
|
|
6
|
+
try {
|
|
7
|
+
const messageValue = getNestedProperty(context.message, key);
|
|
8
|
+
return messageValue === value;
|
|
9
|
+
} catch {
|
|
10
|
+
return false;
|
|
11
|
+
}
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
__name(matchByProperty, "matchByProperty");
|
|
15
|
+
function matchByPropertyPredicate(key, predicate) {
|
|
16
|
+
return (context) => {
|
|
17
|
+
try {
|
|
18
|
+
const messageValue = getNestedProperty(context.message, key);
|
|
19
|
+
return predicate(messageValue);
|
|
20
|
+
} catch {
|
|
21
|
+
return false;
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
__name(matchByPropertyPredicate, "matchByPropertyPredicate");
|
|
26
|
+
function getNestedProperty(obj, path) {
|
|
27
|
+
const keys = path.split(".");
|
|
28
|
+
let current = obj;
|
|
29
|
+
for (const key of keys) {
|
|
30
|
+
if (current == null) {
|
|
31
|
+
return void 0;
|
|
32
|
+
}
|
|
33
|
+
current = current[key];
|
|
34
|
+
}
|
|
35
|
+
return current;
|
|
36
|
+
}
|
|
37
|
+
__name(getNestedProperty, "getNestedProperty");
|
|
38
|
+
function withErrorHandler(handler, onError, _throwError = false) {
|
|
39
|
+
return async (context) => {
|
|
40
|
+
try {
|
|
41
|
+
return await handler(context);
|
|
42
|
+
} catch (error) {
|
|
43
|
+
const err = error instanceof Error ? error : new Error(String(error));
|
|
44
|
+
if (onError) {
|
|
45
|
+
try {
|
|
46
|
+
await onError(err, context);
|
|
47
|
+
} catch (callbackError) {
|
|
48
|
+
Logger.error({
|
|
49
|
+
message: "Error handler callback failed",
|
|
50
|
+
meta: { originalError: err.message, callbackError }
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
if (_throwError) {
|
|
55
|
+
throw err;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
__name(withErrorHandler, "withErrorHandler");
|
|
61
|
+
function withLogging(handler, handlerName = "subscriber-handler") {
|
|
62
|
+
return async (context) => {
|
|
63
|
+
const startTime = Date.now();
|
|
64
|
+
const messageKeys = context.message && typeof context.message === "object" ? Object.keys(context.message).slice(0, 5) : [];
|
|
65
|
+
Logger.info({
|
|
66
|
+
message: `${handlerName}: Starting handler execution`,
|
|
67
|
+
meta: {
|
|
68
|
+
channel: context.channel,
|
|
69
|
+
messageKeys
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
try {
|
|
73
|
+
const result = await handler(context);
|
|
74
|
+
const duration = Date.now() - startTime;
|
|
75
|
+
Logger.info({
|
|
76
|
+
message: `${handlerName}: Handler completed successfully`,
|
|
77
|
+
meta: { channel: context.channel, durationMs: duration }
|
|
78
|
+
});
|
|
79
|
+
return result;
|
|
80
|
+
} catch (error) {
|
|
81
|
+
const duration = Date.now() - startTime;
|
|
82
|
+
Logger.error({
|
|
83
|
+
message: `${handlerName}: Handler failed`,
|
|
84
|
+
meta: {
|
|
85
|
+
channel: context.channel,
|
|
86
|
+
error: error instanceof Error ? error.message : String(error),
|
|
87
|
+
durationMs: duration
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
throw error;
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
__name(withLogging, "withLogging");
|
|
95
|
+
function withRateLimit(handler, maxExecutions, windowMs, onRateLimited) {
|
|
96
|
+
const executionTimes = [];
|
|
97
|
+
return async (context) => {
|
|
98
|
+
const now = Date.now();
|
|
99
|
+
while (executionTimes.length > 0 && executionTimes[0] < now - windowMs) {
|
|
100
|
+
executionTimes.shift();
|
|
101
|
+
}
|
|
102
|
+
if (executionTimes.length >= maxExecutions) {
|
|
103
|
+
if (onRateLimited) {
|
|
104
|
+
await onRateLimited(context);
|
|
105
|
+
}
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
executionTimes.push(now);
|
|
109
|
+
return await handler(context);
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
__name(withRateLimit, "withRateLimit");
|
|
113
|
+
function withRetry(handler, maxRetries, delayMs, backoffMultiplier = 1) {
|
|
114
|
+
return async (context) => {
|
|
115
|
+
let lastError = null;
|
|
116
|
+
let currentDelay = delayMs;
|
|
117
|
+
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
118
|
+
try {
|
|
119
|
+
return await handler(context);
|
|
120
|
+
} catch (error) {
|
|
121
|
+
lastError = error instanceof Error ? error : new Error(String(error));
|
|
122
|
+
if (attempt < maxRetries) {
|
|
123
|
+
await new Promise((resolve) => setTimeout(resolve, currentDelay));
|
|
124
|
+
currentDelay = Math.floor(currentDelay * backoffMultiplier);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
throw lastError;
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
__name(withRetry, "withRetry");
|
|
132
|
+
function composeHandlers(handlers) {
|
|
133
|
+
return async (context) => {
|
|
134
|
+
for (const handler of handlers) {
|
|
135
|
+
await handler(context);
|
|
136
|
+
}
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
__name(composeHandlers, "composeHandlers");
|
|
140
|
+
function withFilter(predicate, handler) {
|
|
141
|
+
return async (context) => {
|
|
142
|
+
const shouldExecute = await predicate(context);
|
|
143
|
+
if (shouldExecute) {
|
|
144
|
+
return await handler(context);
|
|
145
|
+
}
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
__name(withFilter, "withFilter");
|
|
149
|
+
function withValidation(validator, handler) {
|
|
150
|
+
return async (context) => {
|
|
151
|
+
try {
|
|
152
|
+
await validator(context.message);
|
|
153
|
+
return await handler(context);
|
|
154
|
+
} catch (error) {
|
|
155
|
+
Logger.warn({
|
|
156
|
+
message: "Message validation failed",
|
|
157
|
+
meta: {
|
|
158
|
+
channel: context.channel,
|
|
159
|
+
error: error instanceof Error ? error.message : String(error)
|
|
160
|
+
}
|
|
161
|
+
});
|
|
162
|
+
throw error;
|
|
163
|
+
}
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
__name(withValidation, "withValidation");
|
|
167
|
+
function withMetadata(metadata, handler) {
|
|
168
|
+
return async (context) => {
|
|
169
|
+
const enrichedContext = {
|
|
170
|
+
...context,
|
|
171
|
+
metadata
|
|
172
|
+
};
|
|
173
|
+
return await handler(enrichedContext);
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
__name(withMetadata, "withMetadata");
|
|
177
|
+
function withDebounce(handler, delayMs) {
|
|
178
|
+
const timers = /* @__PURE__ */ new Map();
|
|
179
|
+
return async (context) => {
|
|
180
|
+
const channel = context.channel;
|
|
181
|
+
const existingTimer = timers.get(channel);
|
|
182
|
+
if (existingTimer) {
|
|
183
|
+
clearTimeout(existingTimer);
|
|
184
|
+
}
|
|
185
|
+
return new Promise((resolve) => {
|
|
186
|
+
const timer = setTimeout(async () => {
|
|
187
|
+
try {
|
|
188
|
+
await handler(context);
|
|
189
|
+
} finally {
|
|
190
|
+
timers.delete(channel);
|
|
191
|
+
resolve();
|
|
192
|
+
}
|
|
193
|
+
}, delayMs);
|
|
194
|
+
timers.set(channel, timer);
|
|
195
|
+
});
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
__name(withDebounce, "withDebounce");
|
|
199
|
+
function withThrottle(handler, intervalMs) {
|
|
200
|
+
const lastExecutionTime = /* @__PURE__ */ new Map();
|
|
201
|
+
return async (context) => {
|
|
202
|
+
const channel = context.channel;
|
|
203
|
+
const now = Date.now();
|
|
204
|
+
const lastTime = lastExecutionTime.get(channel) ?? 0;
|
|
205
|
+
if (now - lastTime >= intervalMs) {
|
|
206
|
+
lastExecutionTime.set(channel, now);
|
|
207
|
+
return await handler(context);
|
|
208
|
+
}
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
__name(withThrottle, "withThrottle");
|
|
212
|
+
export {
|
|
213
|
+
composeHandlers,
|
|
214
|
+
getNestedProperty,
|
|
215
|
+
matchByProperty,
|
|
216
|
+
matchByPropertyPredicate,
|
|
217
|
+
withDebounce,
|
|
218
|
+
withErrorHandler,
|
|
219
|
+
withFilter,
|
|
220
|
+
withLogging,
|
|
221
|
+
withMetadata,
|
|
222
|
+
withRateLimit,
|
|
223
|
+
withRetry,
|
|
224
|
+
withThrottle,
|
|
225
|
+
withValidation
|
|
226
|
+
};
|
|
227
|
+
//# sourceMappingURL=subscriber-utils.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/websocket/subscriber-utils.ts"],
|
|
4
|
+
"sourcesContent": ["import type {\n WebSocketSubscriberHandler,\n WebSocketSubscriberHandlerContext,\n WebSocketSubscriberMatcher,\n} from './websocket.interface.js';\nimport { Logger } from '../logger/index.js';\n\n/**\n * Utility functions for building and composing WebSocket subscriber handlers\n */\n\n/**\n * Create a predicate matcher for message properties\n * @param key - The property key to check\n * @param value - The expected value\n */\nexport function matchByProperty(key: string, value: unknown): WebSocketSubscriberMatcher {\n return (context: WebSocketSubscriberHandlerContext) => {\n try {\n const messageValue = getNestedProperty(context.message, key);\n return messageValue === value;\n } catch {\n return false;\n }\n };\n}\n\n/**\n * Create a predicate matcher for message property patterns\n * @param key - The property key to check\n * @param predicate - Function to test the value\n */\nexport function matchByPropertyPredicate<_T = unknown>(\n key: string,\n predicate: (value: unknown) => boolean,\n): WebSocketSubscriberMatcher {\n return (context: WebSocketSubscriberHandlerContext) => {\n try {\n const messageValue = getNestedProperty(context.message, key);\n return predicate(messageValue);\n } catch {\n return false;\n }\n };\n}\n\n/**\n * Safely get nested property from an object\n * @param obj - The object to search\n * @param path - Dot-notation path (e.g., 'user.id' or 'data.items.0.name')\n */\nexport function getNestedProperty(obj: any, path: string): unknown {\n const keys = path.split('.');\n let current = obj;\n\n for (const key of keys) {\n if (current == null) {\n return undefined;\n }\n // eslint-disable-next-line security/detect-object-injection\n current = current[key];\n }\n\n return current;\n}\n\n/**\n * Wrap a handler with error handling\n * @param handler - The handler to wrap\n * @param onError - Error handler callback\n * @param throwError - Whether to rethrow the error after handling\n */\nexport function withErrorHandler<TMessage = any>(\n handler: WebSocketSubscriberHandler<TMessage>,\n onError?: (error: Error, context: WebSocketSubscriberHandlerContext<TMessage>) => void | Promise<void>,\n _throwError = false,\n): WebSocketSubscriberHandler<TMessage> {\n return async (context: WebSocketSubscriberHandlerContext<TMessage>) => {\n try {\n return await handler(context);\n } catch (error) {\n const err = error instanceof Error ? error : new Error(String(error));\n\n if (onError) {\n try {\n await onError(err, context);\n } catch (callbackError) {\n Logger.error({\n message: 'Error handler callback failed',\n meta: { originalError: err.message, callbackError },\n });\n }\n }\n\n if (_throwError) {\n throw err;\n }\n }\n };\n}\n\n/**\n * Wrap a handler with logging\n * @param handler - The handler to wrap\n * @param handlerName - Name for logging purposes\n */\nexport function withLogging<TMessage = any>(\n handler: WebSocketSubscriberHandler<TMessage>,\n handlerName = 'subscriber-handler',\n): WebSocketSubscriberHandler<TMessage> {\n return async (context: WebSocketSubscriberHandlerContext<TMessage>) => {\n const startTime = Date.now();\n const messageKeys =\n context.message && typeof context.message === 'object'\n ? Object.keys(context.message as Record<string, unknown>).slice(0, 5)\n : [];\n Logger.info({\n message: `${handlerName}: Starting handler execution`,\n meta: {\n channel: context.channel,\n messageKeys,\n },\n });\n\n try {\n const result = await handler(context);\n const duration = Date.now() - startTime;\n Logger.info({\n message: `${handlerName}: Handler completed successfully`,\n meta: { channel: context.channel, durationMs: duration },\n });\n return result;\n } catch (error) {\n const duration = Date.now() - startTime;\n Logger.error({\n message: `${handlerName}: Handler failed`,\n meta: {\n channel: context.channel,\n error: error instanceof Error ? error.message : String(error),\n durationMs: duration,\n },\n });\n throw error;\n }\n };\n}\n\n/**\n * Wrap a handler with rate limiting\n * @param handler - The handler to wrap\n * @param maxExecutions - Max executions allowed\n * @param windowMs - Time window in milliseconds\n * @param onRateLimited - Optional callback when rate limited\n */\nexport function withRateLimit<TMessage = any>(\n handler: WebSocketSubscriberHandler<TMessage>,\n maxExecutions: number,\n windowMs: number,\n onRateLimited?: (context: WebSocketSubscriberHandlerContext<TMessage>) => void | Promise<void>,\n): WebSocketSubscriberHandler<TMessage> {\n const executionTimes: number[] = [];\n\n return async (context: WebSocketSubscriberHandlerContext<TMessage>) => {\n const now = Date.now();\n\n // Remove old entries outside the window\n while (executionTimes.length > 0 && executionTimes[0] < now - windowMs) {\n executionTimes.shift();\n }\n\n if (executionTimes.length >= maxExecutions) {\n if (onRateLimited) {\n await onRateLimited(context);\n }\n return;\n }\n\n executionTimes.push(now);\n return await handler(context);\n };\n}\n\n/**\n * Wrap a handler with retry logic\n * @param handler - The handler to wrap\n * @param maxRetries - Maximum number of retries\n * @param delayMs - Delay between retries in milliseconds\n * @param backoffMultiplier - Multiplier for exponential backoff (default: 1, no backoff)\n */\nexport function withRetry<TMessage = any>(\n handler: WebSocketSubscriberHandler<TMessage>,\n maxRetries: number,\n delayMs: number,\n backoffMultiplier = 1,\n): WebSocketSubscriberHandler<TMessage> {\n return async (context: WebSocketSubscriberHandlerContext<TMessage>) => {\n let lastError: Error | null = null;\n let currentDelay = delayMs;\n\n for (let attempt = 0; attempt <= maxRetries; attempt++) {\n try {\n return await handler(context);\n } catch (error) {\n lastError = error instanceof Error ? error : new Error(String(error));\n\n if (attempt < maxRetries) {\n await new Promise(resolve => setTimeout(resolve, currentDelay));\n currentDelay = Math.floor(currentDelay * backoffMultiplier);\n }\n }\n }\n\n throw lastError;\n };\n}\n\n/**\n * Compose multiple handlers into a single handler\n * Executes handlers sequentially, passing context through each one\n * @param handlers - Array of handlers to compose\n */\nexport function composeHandlers<TMessage = any>(\n handlers: WebSocketSubscriberHandler<TMessage>[],\n): WebSocketSubscriberHandler<TMessage> {\n return async (context: WebSocketSubscriberHandlerContext<TMessage>) => {\n for (const handler of handlers) {\n await handler(context);\n }\n };\n}\n\n/**\n * Create a filter handler that conditionally executes based on a predicate\n * @param predicate - Function that returns true if handler should execute\n * @param handler - The handler to execute conditionally\n */\nexport function withFilter<TMessage = any>(\n predicate: (context: WebSocketSubscriberHandlerContext<TMessage>) => boolean | Promise<boolean>,\n handler: WebSocketSubscriberHandler<TMessage>,\n): WebSocketSubscriberHandler<TMessage> {\n return async (context: WebSocketSubscriberHandlerContext<TMessage>) => {\n const shouldExecute = await predicate(context);\n if (shouldExecute) {\n return await handler(context);\n }\n };\n}\n\n/**\n * Validate message structure before execution\n * @param validator - Function that validates the message and throws if invalid\n * @param handler - The handler to wrap\n */\nexport function withValidation<TMessage = any>(\n validator: (message: TMessage) => void | Promise<void>,\n handler: WebSocketSubscriberHandler<TMessage>,\n): WebSocketSubscriberHandler<TMessage> {\n return async (context: WebSocketSubscriberHandlerContext<TMessage>) => {\n try {\n await validator(context.message);\n return await handler(context);\n } catch (error) {\n Logger.warn({\n message: 'Message validation failed',\n meta: {\n channel: context.channel,\n error: error instanceof Error ? error.message : String(error),\n },\n });\n throw error;\n }\n };\n}\n\n/**\n * Add metadata/context to a handler execution\n * @param metadata - Metadata to add to context\n * @param handler - The handler to wrap\n */\nexport function withMetadata<TMessage = any>(\n metadata: Record<string, unknown>,\n handler: WebSocketSubscriberHandler<TMessage>,\n): WebSocketSubscriberHandler<TMessage> {\n return async (context: WebSocketSubscriberHandlerContext<TMessage>) => {\n const enrichedContext = {\n ...context,\n metadata,\n } as any;\n\n return await handler(enrichedContext);\n };\n}\n\n/**\n * Debounce handler execution by channel\n * @param handler - The handler to wrap\n * @param delayMs - Debounce delay in milliseconds\n */\nexport function withDebounce<TMessage = any>(\n handler: WebSocketSubscriberHandler<TMessage>,\n delayMs: number,\n): WebSocketSubscriberHandler<TMessage> {\n const timers = new Map<string, NodeJS.Timeout>();\n\n return async (context: WebSocketSubscriberHandlerContext<TMessage>) => {\n const channel = context.channel;\n\n // Cancel previous timer for this channel\n const existingTimer = timers.get(channel);\n if (existingTimer) {\n clearTimeout(existingTimer);\n }\n\n // Set new timer\n return new Promise<void>(resolve => {\n const timer = setTimeout(async () => {\n try {\n await handler(context);\n } finally {\n timers.delete(channel);\n resolve();\n }\n }, delayMs);\n\n timers.set(channel, timer);\n });\n };\n}\n\n/**\n * Throttle handler execution by channel\n * @param handler - The handler to wrap\n * @param intervalMs - Throttle interval in milliseconds\n */\nexport function withThrottle<TMessage = any>(\n handler: WebSocketSubscriberHandler<TMessage>,\n intervalMs: number,\n): WebSocketSubscriberHandler<TMessage> {\n const lastExecutionTime = new Map<string, number>();\n\n return async (context: WebSocketSubscriberHandlerContext<TMessage>) => {\n const channel = context.channel;\n const now = Date.now();\n const lastTime = lastExecutionTime.get(channel) ?? 0;\n\n if (now - lastTime >= intervalMs) {\n lastExecutionTime.set(channel, now);\n return await handler(context);\n }\n };\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -78,12 +78,52 @@ export default class WebSocketServer extends WebSocketBase {
|
|
|
78
78
|
* Broadcast a message to all connected WebSocket clients
|
|
79
79
|
* @param data - The data to broadcast (will be JSON stringified)
|
|
80
80
|
* @param excludeClientId - Optional client ID to exclude from broadcast
|
|
81
|
+
* @param predicate - Optional function to filter clients
|
|
81
82
|
*/
|
|
82
|
-
broadcastToAllClients({ data, excludeClientId, }: {
|
|
83
|
+
broadcastToAllClients({ data, excludeClientId, predicate, }: {
|
|
83
84
|
data: {
|
|
84
85
|
[key: string]: any;
|
|
85
86
|
};
|
|
86
87
|
excludeClientId?: string;
|
|
88
|
+
predicate?: (clientData: {
|
|
89
|
+
clientId: string;
|
|
90
|
+
userData: any;
|
|
91
|
+
}) => boolean;
|
|
92
|
+
}): void;
|
|
93
|
+
/**
|
|
94
|
+
* Broadcast a message to all clients in a specific room
|
|
95
|
+
* @param roomName - The room to broadcast to
|
|
96
|
+
* @param data - The data to broadcast
|
|
97
|
+
* @param excludeClientId - Optional client ID to exclude from broadcast
|
|
98
|
+
*/
|
|
99
|
+
broadcastToRoom({ roomName, data, excludeClientId, }: {
|
|
100
|
+
roomName: string;
|
|
101
|
+
data: {
|
|
102
|
+
[key: string]: any;
|
|
103
|
+
};
|
|
104
|
+
excludeClientId?: string;
|
|
105
|
+
}): void;
|
|
106
|
+
/**
|
|
107
|
+
* Broadcast a message to specific users
|
|
108
|
+
* @param userIds - Array of user IDs to broadcast to
|
|
109
|
+
* @param data - The data to broadcast
|
|
110
|
+
*/
|
|
111
|
+
broadcastToUsers({ userIds, data }: {
|
|
112
|
+
userIds: (string | number)[];
|
|
113
|
+
data: {
|
|
114
|
+
[key: string]: any;
|
|
115
|
+
};
|
|
116
|
+
}): void;
|
|
117
|
+
/**
|
|
118
|
+
* Broadcast a message to a specific client
|
|
119
|
+
* @param clientId - The client ID to send to
|
|
120
|
+
* @param data - The data to send
|
|
121
|
+
*/
|
|
122
|
+
broadcastToClient({ clientId, data }: {
|
|
123
|
+
clientId: string;
|
|
124
|
+
data: {
|
|
125
|
+
[key: string]: any;
|
|
126
|
+
};
|
|
87
127
|
}): void;
|
|
88
128
|
sendMessageError({ webSocketClientId, error }: {
|
|
89
129
|
webSocketClientId: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"websocket-server.d.ts","sourceRoot":"","sources":["../../src/websocket/websocket-server.ts"],"names":[],"mappings":"AAAA,OAAO,EAAgB,eAAe,IAAI,EAAE,EAAE,SAAS,EAAE,MAAM,IAAI,CAAC;AACpE,OAAO,EAGL,KAAK,cAAc,EAInB,KAAK,aAAa,EACnB,MAAM,0BAA0B,CAAC;AAClC,OAAO,KAAK,aAAa,MAAM,sBAAsB,CAAC;AACtD,OAAO,KAAK,YAAY,MAAM,qBAAqB,CAAC;AACpD,OAAO,KAAK,gBAAgB,MAAM,yBAAyB,CAAC;AAC5D,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,iCAAiC,CAAC;AAC5E,OAAO,sBAAsB,MAAM,+BAA+B,CAAC;AAEnE,OAAO,aAAa,MAAM,qBAAqB,CAAC;AAMhD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"websocket-server.d.ts","sourceRoot":"","sources":["../../src/websocket/websocket-server.ts"],"names":[],"mappings":"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"}
|
|
@@ -14,6 +14,7 @@ import WebSocketRoomManager from "./websocket-room-manager.js";
|
|
|
14
14
|
import logger from "../logger/logger.js";
|
|
15
15
|
import { WebSocketAuthService } from "./websocket-auth.js";
|
|
16
16
|
import { File, Loader } from "../util/index.js";
|
|
17
|
+
import { executeWithMiddleware } from "./subscriber-middleware.js";
|
|
17
18
|
class WebSocketServer extends WebSocketBase {
|
|
18
19
|
static {
|
|
19
20
|
__name(this, "WebSocketServer");
|
|
@@ -326,15 +327,19 @@ class WebSocketServer extends WebSocketBase {
|
|
|
326
327
|
return;
|
|
327
328
|
}
|
|
328
329
|
const orderedHandlers = Array.from(candidates).sort((a, b) => (b.priority ?? 0) - (a.priority ?? 0));
|
|
329
|
-
for (const
|
|
330
|
+
for (const handlerDef of orderedHandlers) {
|
|
330
331
|
try {
|
|
331
|
-
|
|
332
|
+
if (handlerDef.middleware && handlerDef.middleware.length > 0) {
|
|
333
|
+
await executeWithMiddleware(handlerDef.handle, handlerDef.middleware, context);
|
|
334
|
+
} else {
|
|
335
|
+
await handlerDef.handle(context);
|
|
336
|
+
}
|
|
332
337
|
} catch (error) {
|
|
333
338
|
logger.error({
|
|
334
339
|
error,
|
|
335
340
|
message: "WebSocket subscriber handler failed",
|
|
336
341
|
meta: {
|
|
337
|
-
Handler:
|
|
342
|
+
Handler: handlerDef.name ?? "(anonymous)",
|
|
338
343
|
Channel: channel
|
|
339
344
|
}
|
|
340
345
|
});
|
|
@@ -729,10 +734,12 @@ class WebSocketServer extends WebSocketBase {
|
|
|
729
734
|
* Broadcast a message to all connected WebSocket clients
|
|
730
735
|
* @param data - The data to broadcast (will be JSON stringified)
|
|
731
736
|
* @param excludeClientId - Optional client ID to exclude from broadcast
|
|
737
|
+
* @param predicate - Optional function to filter clients
|
|
732
738
|
*/
|
|
733
739
|
broadcastToAllClients({
|
|
734
740
|
data,
|
|
735
|
-
excludeClientId
|
|
741
|
+
excludeClientId,
|
|
742
|
+
predicate
|
|
736
743
|
}) {
|
|
737
744
|
if (!this.server) {
|
|
738
745
|
log("Server not started when broadcasting to all clients");
|
|
@@ -742,15 +749,91 @@ class WebSocketServer extends WebSocketBase {
|
|
|
742
749
|
if (client.readyState !== WebSocket.OPEN) {
|
|
743
750
|
continue;
|
|
744
751
|
}
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
752
|
+
const clientId = this.clientManager.getClientId({ ws: client });
|
|
753
|
+
if (excludeClientId && clientId === excludeClientId) {
|
|
754
|
+
continue;
|
|
755
|
+
}
|
|
756
|
+
if (predicate && clientId) {
|
|
757
|
+
const clientData = this.clientManager.getClient({ clientId });
|
|
758
|
+
if (!clientData || !predicate({ clientId, userData: clientData.user })) {
|
|
748
759
|
continue;
|
|
749
760
|
}
|
|
750
761
|
}
|
|
751
762
|
client.send(JSON.stringify(data));
|
|
752
763
|
}
|
|
753
764
|
}
|
|
765
|
+
/**
|
|
766
|
+
* Broadcast a message to all clients in a specific room
|
|
767
|
+
* @param roomName - The room to broadcast to
|
|
768
|
+
* @param data - The data to broadcast
|
|
769
|
+
* @param excludeClientId - Optional client ID to exclude from broadcast
|
|
770
|
+
*/
|
|
771
|
+
broadcastToRoom({
|
|
772
|
+
roomName,
|
|
773
|
+
data,
|
|
774
|
+
excludeClientId
|
|
775
|
+
}) {
|
|
776
|
+
if (!this.server) {
|
|
777
|
+
log("Server not started when broadcasting to room", { roomName });
|
|
778
|
+
return;
|
|
779
|
+
}
|
|
780
|
+
const room = this.roomManager.rooms.get(roomName);
|
|
781
|
+
if (!room) {
|
|
782
|
+
log("Room not found when broadcasting", { roomName });
|
|
783
|
+
return;
|
|
784
|
+
}
|
|
785
|
+
for (const clientId of room) {
|
|
786
|
+
if (excludeClientId && clientId === excludeClientId) {
|
|
787
|
+
continue;
|
|
788
|
+
}
|
|
789
|
+
const client = this.clientManager.getClient({ clientId });
|
|
790
|
+
if (!client?.ws || client.ws.readyState !== WebSocket.OPEN) {
|
|
791
|
+
continue;
|
|
792
|
+
}
|
|
793
|
+
client.ws.send(JSON.stringify(data));
|
|
794
|
+
}
|
|
795
|
+
}
|
|
796
|
+
/**
|
|
797
|
+
* Broadcast a message to specific users
|
|
798
|
+
* @param userIds - Array of user IDs to broadcast to
|
|
799
|
+
* @param data - The data to broadcast
|
|
800
|
+
*/
|
|
801
|
+
broadcastToUsers({ userIds, data }) {
|
|
802
|
+
if (!this.server) {
|
|
803
|
+
log("Server not started when broadcasting to users");
|
|
804
|
+
return;
|
|
805
|
+
}
|
|
806
|
+
const userIdSet = new Set(userIds.map((id) => String(id)));
|
|
807
|
+
for (const client of this.server.clients) {
|
|
808
|
+
if (client.readyState !== WebSocket.OPEN) {
|
|
809
|
+
continue;
|
|
810
|
+
}
|
|
811
|
+
const clientId = this.clientManager.getClientId({ ws: client });
|
|
812
|
+
const clientData = clientId ? this.clientManager.getClient({ clientId }) : null;
|
|
813
|
+
if (!clientData?.user) {
|
|
814
|
+
continue;
|
|
815
|
+
}
|
|
816
|
+
const clientUserId = String(clientData.user.id ?? clientData.user.userId);
|
|
817
|
+
if (userIdSet.has(clientUserId)) {
|
|
818
|
+
client.send(JSON.stringify(data));
|
|
819
|
+
}
|
|
820
|
+
}
|
|
821
|
+
}
|
|
822
|
+
/**
|
|
823
|
+
* Broadcast a message to a specific client
|
|
824
|
+
* @param clientId - The client ID to send to
|
|
825
|
+
* @param data - The data to send
|
|
826
|
+
*/
|
|
827
|
+
broadcastToClient({ clientId, data }) {
|
|
828
|
+
const client = this.clientManager.getClient({ clientId });
|
|
829
|
+
if (!client?.ws || client.ws.readyState !== WebSocket.OPEN) {
|
|
830
|
+
log("Client not found or not connected when broadcasting to specific client", {
|
|
831
|
+
clientId
|
|
832
|
+
});
|
|
833
|
+
return;
|
|
834
|
+
}
|
|
835
|
+
client.ws.send(JSON.stringify(data));
|
|
836
|
+
}
|
|
754
837
|
sendMessageError({ webSocketClientId, error }) {
|
|
755
838
|
const client = this.clientManager.getClient({
|
|
756
839
|
clientId: webSocketClientId
|