@syncar/server 1.0.0-alpha.2 → 1.0.0-alpha.3

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.
@@ -0,0 +1,160 @@
1
+ import { d as IContext, e as IMiddlewareAction, b as IMiddleware, C as ChannelName, I as IClientConnection } from '../types-DeE5vqYX.js';
2
+ import 'ws';
3
+
4
+ /**
5
+ * Auth middleware options
6
+ */
7
+ interface AuthOptions {
8
+ /**
9
+ * Verify and decode a token
10
+ * Returns the user data to attach to the client
11
+ */
12
+ verifyToken: (token: string) => Promise<unknown> | unknown;
13
+ /**
14
+ * Extract token from the middleware context
15
+ */
16
+ getToken?: (c: IContext) => string | undefined;
17
+ /**
18
+ * Property name to attach verified user data
19
+ * @default 'user'
20
+ */
21
+ attachProperty?: string;
22
+ /**
23
+ * Actions to require authentication
24
+ * @default All actions require auth
25
+ */
26
+ actions?: IMiddlewareAction[];
27
+ }
28
+ /**
29
+ * Create an authentication middleware
30
+ *
31
+ * This middleware verifies tokens and attaches user data to clients.
32
+ * Rejects connections that fail authentication.
33
+ */
34
+ declare function authenticate(options: AuthOptions): IMiddleware;
35
+
36
+ /**
37
+ * Logging middleware options
38
+ */
39
+ interface LoggingOptions {
40
+ /**
41
+ * Logger instance to use
42
+ * @default console
43
+ */
44
+ logger?: Pick<Console, 'log' | 'info' | 'warn' | 'error'>;
45
+ /**
46
+ * Log level
47
+ * @default 'info'
48
+ */
49
+ logLevel?: 'log' | 'info' | 'warn' | 'error';
50
+ /**
51
+ * Whether to include message data in logs
52
+ * @default false
53
+ */
54
+ includeMessageData?: boolean;
55
+ /**
56
+ * Custom format function for log output
57
+ */
58
+ format?: (context: {
59
+ action: string;
60
+ clientId?: string;
61
+ channel?: string;
62
+ message?: unknown;
63
+ duration?: number;
64
+ }) => string;
65
+ /**
66
+ * Actions to log
67
+ * @default All actions are logged
68
+ */
69
+ actions?: IMiddlewareAction[];
70
+ }
71
+ /**
72
+ * Create a logging middleware
73
+ *
74
+ * Logs all middleware actions with client and action information.
75
+ */
76
+ declare function logger(options?: LoggingOptions): IMiddleware;
77
+
78
+ /**
79
+ * Rate limit middleware options
80
+ */
81
+ interface RateLimitOptions {
82
+ /**
83
+ * Maximum number of requests allowed per window
84
+ * @default 100
85
+ */
86
+ maxRequests?: number;
87
+ /**
88
+ * Time window in milliseconds
89
+ * @default 60000 (1 minute)
90
+ */
91
+ windowMs?: number;
92
+ /**
93
+ * Extract a unique identifier for rate limiting
94
+ * Defaults to client ID
95
+ */
96
+ getMessageId?: (c: IContext) => string;
97
+ /**
98
+ * Actions to rate limit
99
+ * @default 'message' only
100
+ */
101
+ actions?: IMiddlewareAction[];
102
+ }
103
+ /**
104
+ * Rate limit state for each client
105
+ */
106
+ interface RateLimitState {
107
+ count: number;
108
+ resetTime: number;
109
+ }
110
+ /**
111
+ * Create a rate limiting middleware
112
+ *
113
+ * Limits the rate of requests per client within a time window.
114
+ */
115
+ declare function rateLimit(options?: RateLimitOptions): IMiddleware;
116
+ /**
117
+ * Clear the rate limit store
118
+ * Useful for testing or manual reset
119
+ */
120
+ declare function clearRateLimitStore(): void;
121
+ /**
122
+ * Get rate limit state for a specific client
123
+ *
124
+ * @param id - Client or message ID
125
+ * @returns Rate limit state or undefined
126
+ */
127
+ declare function getRateLimitState(id: string): RateLimitState | undefined;
128
+
129
+ /**
130
+ * Channel whitelist middleware options
131
+ */
132
+ interface ChannelWhitelistOptions {
133
+ /**
134
+ * List of allowed channels
135
+ * If isDynamic is true, this is used as a fallback
136
+ */
137
+ allowedChannels?: ChannelName[];
138
+ /**
139
+ * Dynamic check function for channel access
140
+ * If provided, this takes precedence over allowedChannels
141
+ *
142
+ * @param channel - The channel name to check
143
+ * @param client - The client attempting to access the channel
144
+ * @returns true if channel is allowed
145
+ */
146
+ isDynamic?: (channel: ChannelName, client?: IClientConnection) => boolean;
147
+ /**
148
+ * Whether to also check unsubscribe actions
149
+ * @default false (only restrict subscribe)
150
+ */
151
+ restrictUnsubscribe?: boolean;
152
+ }
153
+ /**
154
+ * Create a channel whitelist middleware
155
+ *
156
+ * Restricts which channels clients can subscribe to.
157
+ */
158
+ declare function channelWhitelist(options?: ChannelWhitelistOptions): IMiddleware;
159
+
160
+ export { type AuthOptions, type ChannelWhitelistOptions, type LoggingOptions, type RateLimitOptions, authenticate, channelWhitelist, clearRateLimitStore, getRateLimitState, logger, rateLimit };
@@ -0,0 +1,2 @@
1
+ function f(s){let{verifyToken:c,getToken:a=e=>e.req.message?.data?.token,attachProperty:d="user",actions:t}=s;return async(e,i)=>{if(t&&!t.includes(e.req.action))return i();let n=a(e);if(!n)return e.reject("Authentication token required");try{let r=await c(n);e.req.client&&(e.req.client[d]=r),e.set(d,r),await i()}catch{e.reject("Authentication failed: Invalid token")}}}function p(s={}){let{logger:c=console,logLevel:a="info",includeMessageData:d=!1,format:t,actions:e}=s;return async(i,n)=>{if(e&&!e.includes(i.req.action))return n();let r=Date.now();await n();let o=Date.now()-r,u={action:i.req.action,clientId:i.req.client?.id,channel:i.req.channel,message:d?i.req.message:void 0,duration:o},m=t?t(u):`[${u.action}] Client: ${u.clientId??"unknown"}${u.channel?` Channel: ${u.channel}`:""} (${o}ms)`;c[a](m)}}var l=new Map;function w(s={}){let{maxRequests:c=100,windowMs:a=6e4,getMessageId:d=n=>n.req.client?.id??"",actions:t=["message"]}=s,e=setInterval(()=>{let n=Date.now();for(let[r,o]of l)o.resetTime<n&&l.delete(r)},a*10),i=async(n,r)=>{if(!t.includes(n.req.action))return r();let o=d(n);if(!o)return r();let u=Date.now(),m=l.get(o);m&&m.resetTime<u&&l.delete(o);let g=l.get(o);if(g||(g={count:0,resetTime:u+a},l.set(o,g)),g.count>=c)return n.reject(`Rate limit exceeded. Max ${c} requests per ${a}ms`);g.count++,await r()};return i.cleanup=()=>{clearInterval(e),l.clear()},i}function h(){l.clear()}function I(s){return l.get(s)}function q(s={}){let{allowedChannels:c=[],isDynamic:a,restrictUnsubscribe:d=!1}=s;return async(t,e)=>{if(t.req.action!=="subscribe"&&t.req.action!=="unsubscribe"||t.req.action==="unsubscribe"&&!d||!t.req.channel)return e();if(a)return a(t.req.channel,t.req.client)?e():t.reject(`Channel '${t.req.channel}' is not allowed`);if(!c.includes(t.req.channel))return t.reject(`Channel '${t.req.channel}' is not allowed`);await e()}}export{f as authenticate,q as channelWhitelist,h as clearRateLimitStore,I as getRateLimitState,p as logger,w as rateLimit};
2
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/middleware/authenticate.ts","../../src/middleware/logger.ts","../../src/middleware/rate-limit.ts","../../src/middleware/channel-whitelist.ts"],"sourcesContent":["import type { IMiddleware, IContext, IMiddlewareAction } from '../types'\n\n/**\n * Auth middleware options\n */\nexport interface AuthOptions {\n /**\n * Verify and decode a token\n * Returns the user data to attach to the client\n */\n verifyToken: (token: string) => Promise<unknown> | unknown\n\n /**\n * Extract token from the middleware context\n */\n getToken?: (c: IContext) => string | undefined\n\n /**\n * Property name to attach verified user data\n * @default 'user'\n */\n attachProperty?: string\n\n /**\n * Actions to require authentication\n * @default All actions require auth\n */\n actions?: IMiddlewareAction[]\n}\n\n/**\n * Create an authentication middleware\n *\n * This middleware verifies tokens and attaches user data to clients.\n * Rejects connections that fail authentication.\n */\nexport function authenticate(options: AuthOptions): IMiddleware {\n const {\n verifyToken,\n getToken = (c) => {\n // Default: extract token from message.data.token\n const msg = c.req.message as\n | { data?: { token?: string } }\n | undefined\n return msg?.data?.token\n },\n attachProperty = 'user',\n actions,\n } = options\n\n return async (c, next) => {\n // Check if this action requires auth\n if (actions && !actions.includes(c.req.action)) {\n return next()\n }\n\n // Extract token\n const token = getToken(c)\n if (!token) {\n return c.reject('Authentication token required')\n }\n\n // Verify token\n try {\n const userData = await verifyToken(token!)\n\n // Attach user data to client (LEGACY - for compatibility)\n if (c.req.client) {\n ;(c.req.client as unknown as Record<string, unknown>)[\n attachProperty\n ] = userData\n }\n\n // Attach to STATE (Hono-style)\n c.set(attachProperty, userData)\n\n // PASS TO NEXT LAYER\n await next()\n } catch (error) {\n c.reject('Authentication failed: Invalid token')\n }\n }\n}\n","import type { IMiddleware, IMiddlewareAction } from '../types'\n\n/**\n * Logging middleware options\n */\nexport interface LoggingOptions {\n /**\n * Logger instance to use\n * @default console\n */\n logger?: Pick<Console, 'log' | 'info' | 'warn' | 'error'>\n\n /**\n * Log level\n * @default 'info'\n */\n logLevel?: 'log' | 'info' | 'warn' | 'error'\n\n /**\n * Whether to include message data in logs\n * @default false\n */\n includeMessageData?: boolean\n\n /**\n * Custom format function for log output\n */\n format?: (context: {\n action: string\n clientId?: string\n channel?: string\n message?: unknown\n duration?: number\n }) => string\n\n /**\n * Actions to log\n * @default All actions are logged\n */\n actions?: IMiddlewareAction[]\n}\n\n/**\n * Create a logging middleware\n *\n * Logs all middleware actions with client and action information.\n */\nexport function logger(options: LoggingOptions = {}): IMiddleware {\n const {\n logger = console,\n logLevel = 'info',\n includeMessageData = false,\n format,\n actions,\n } = options\n\n return async (c, next) => {\n // Check if this action should be logged\n if (actions && !actions.includes(c.req.action)) {\n return next()\n }\n\n const start = Date.now()\n\n await next() // Wait for downstream layers\n\n const duration = Date.now() - start\n\n const logData = {\n action: c.req.action,\n clientId: c.req.client?.id,\n channel: c.req.channel,\n message: includeMessageData ? c.req.message : undefined,\n duration,\n }\n\n const logMessage = format\n ? format(logData)\n : `[${logData.action}] Client: ${logData.clientId ?? 'unknown'}${logData.channel ? ` Channel: ${logData.channel}` : ''} (${duration}ms)`\n\n logger[logLevel](logMessage)\n }\n}\n","import type { IMiddleware, IContext, IMiddlewareAction } from '../types'\n\n/**\n * Rate limit middleware options\n */\nexport interface RateLimitOptions {\n /**\n * Maximum number of requests allowed per window\n * @default 100\n */\n maxRequests?: number\n\n /**\n * Time window in milliseconds\n * @default 60000 (1 minute)\n */\n windowMs?: number\n\n /**\n * Extract a unique identifier for rate limiting\n * Defaults to client ID\n */\n getMessageId?: (c: IContext) => string\n\n /**\n * Actions to rate limit\n * @default 'message' only\n */\n actions?: IMiddlewareAction[]\n}\n\n/**\n * Rate limit state for each client\n */\nexport interface RateLimitState {\n count: number\n resetTime: number\n}\n\n/**\n * Rate limit storage\n * Maps client ID to rate limit state\n */\nconst rateLimitStore = new Map<string, RateLimitState>()\n\n/**\n * Create a rate limiting middleware\n *\n * Limits the rate of requests per client within a time window.\n */\nexport function rateLimit(options: RateLimitOptions = {}): IMiddleware {\n const {\n maxRequests = 100,\n windowMs = 60000,\n getMessageId = (c) => c.req.client?.id ?? '',\n actions = ['message'],\n } = options\n\n // Clean up expired entries periodically (every 10 windows)\n const cleanupInterval = setInterval(() => {\n const now = Date.now()\n for (const [id, state] of rateLimitStore) {\n if (state.resetTime < now) {\n rateLimitStore.delete(id)\n }\n }\n }, windowMs * 10)\n\n // Return middleware with cleanup\n const middleware: IMiddleware = async (c, next) => {\n // Check if this action should be rate limited\n if (!actions.includes(c.req.action)) {\n return next()\n }\n\n const id = getMessageId(c)\n if (!id) {\n return next() // No ID to rate limit\n }\n\n const now = Date.now()\n const state = rateLimitStore.get(id)\n\n // Check if window has expired\n if (state && state.resetTime < now) {\n rateLimitStore.delete(id)\n }\n\n // Get or create state\n let currentState = rateLimitStore.get(id)\n if (!currentState) {\n currentState = {\n count: 0,\n resetTime: now + windowMs,\n }\n rateLimitStore.set(id, currentState)\n }\n\n // Check limit\n if (currentState.count >= maxRequests) {\n return c.reject(\n `Rate limit exceeded. Max ${maxRequests} requests per ${windowMs}ms`,\n )\n }\n\n // Increment counter\n currentState.count++\n\n // CONTINUE\n await next()\n }\n\n // Attach cleanup method\n ;(middleware as { cleanup?: () => void }).cleanup = () => {\n clearInterval(cleanupInterval)\n rateLimitStore.clear()\n }\n\n return middleware\n}\n\n/**\n * Clear the rate limit store\n * Useful for testing or manual reset\n */\nexport function clearRateLimitStore(): void {\n rateLimitStore.clear()\n}\n\n/**\n * Get rate limit state for a specific client\n *\n * @param id - Client or message ID\n * @returns Rate limit state or undefined\n */\nexport function getRateLimitState(id: string): RateLimitState | undefined {\n return rateLimitStore.get(id)\n}\n","import type { IMiddleware, IClientConnection, ChannelName } from '../types'\n\n/**\n * Channel whitelist middleware options\n */\nexport interface ChannelWhitelistOptions {\n /**\n * List of allowed channels\n * If isDynamic is true, this is used as a fallback\n */\n allowedChannels?: ChannelName[]\n\n /**\n * Dynamic check function for channel access\n * If provided, this takes precedence over allowedChannels\n *\n * @param channel - The channel name to check\n * @param client - The client attempting to access the channel\n * @returns true if channel is allowed\n */\n isDynamic?: (channel: ChannelName, client?: IClientConnection) => boolean\n\n /**\n * Whether to also check unsubscribe actions\n * @default false (only restrict subscribe)\n */\n restrictUnsubscribe?: boolean\n}\n\n/**\n * Create a channel whitelist middleware\n *\n * Restricts which channels clients can subscribe to.\n */\nexport function channelWhitelist(\n options: ChannelWhitelistOptions = {},\n): IMiddleware {\n const {\n allowedChannels = [],\n isDynamic,\n restrictUnsubscribe = false,\n } = options\n\n return async (c, next) => {\n // Only check subscribe/unsubscribe actions\n if (c.req.action !== 'subscribe' && c.req.action !== 'unsubscribe') {\n return next()\n }\n\n // Skip unsubscribe if not restricted\n if (c.req.action === 'unsubscribe' && !restrictUnsubscribe) {\n return next()\n }\n\n if (!c.req.channel) {\n return next() // No channel to check\n }\n\n // Check dynamic function first\n if (isDynamic) {\n if (!isDynamic(c.req.channel, c.req.client)) {\n return c.reject(`Channel '${c.req.channel}' is not allowed`)\n }\n return next()\n }\n\n // Check static whitelist\n if (!allowedChannels.includes(c.req.channel)) {\n return c.reject(`Channel '${c.req.channel}' is not allowed`)\n }\n\n await next()\n }\n}\n"],"mappings":"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","names":["authenticate","options","verifyToken","getToken","c","attachProperty","actions","next","token","userData","logger","options","logLevel","includeMessageData","format","actions","c","next","start","duration","logData","logMessage","rateLimitStore","rateLimit","options","maxRequests","windowMs","getMessageId","c","actions","cleanupInterval","now","id","state","middleware","next","currentState","clearRateLimitStore","getRateLimitState","channelWhitelist","options","allowedChannels","isDynamic","restrictUnsubscribe","c","next"]}