@snakechat/commskit-node 0.0.0-stage → 0.1.0
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/LICENSE +21 -0
- package/README.md +87 -2
- package/dist/adapters-8ztjEYtz.d.ts +83 -0
- package/dist/call.d.ts +34 -0
- package/dist/call.js +51 -0
- package/dist/call.js.map +1 -0
- package/dist/chat.d.ts +64 -0
- package/dist/chat.js +30 -0
- package/dist/chat.js.map +1 -0
- package/dist/chunk-5BJ2SNM5.js +26 -0
- package/dist/chunk-5BJ2SNM5.js.map +1 -0
- package/dist/chunk-6I27CI32.js +586 -0
- package/dist/chunk-6I27CI32.js.map +1 -0
- package/dist/chunk-BBFVG7QU.js +52 -0
- package/dist/chunk-BBFVG7QU.js.map +1 -0
- package/dist/chunk-GR4PMGCS.js +82 -0
- package/dist/chunk-GR4PMGCS.js.map +1 -0
- package/dist/chunk-KL4WRZKZ.js +799 -0
- package/dist/chunk-KL4WRZKZ.js.map +1 -0
- package/dist/chunk-KLKRK6OG.js +107 -0
- package/dist/chunk-KLKRK6OG.js.map +1 -0
- package/dist/chunk-LY2GJWN7.js +708 -0
- package/dist/chunk-LY2GJWN7.js.map +1 -0
- package/dist/chunk-SEDRF62Q.js +39 -0
- package/dist/chunk-SEDRF62Q.js.map +1 -0
- package/dist/chunk-U4Z5INIW.js +1192 -0
- package/dist/chunk-U4Z5INIW.js.map +1 -0
- package/dist/chunk-XOXZZYSW.js +247 -0
- package/dist/chunk-XOXZZYSW.js.map +1 -0
- package/dist/chunk-XQ6FLRX7.js +179 -0
- package/dist/chunk-XQ6FLRX7.js.map +1 -0
- package/dist/chunk-XQFQN73U.js +38 -0
- package/dist/chunk-XQFQN73U.js.map +1 -0
- package/dist/connections-CGEPiZPF.d.ts +128 -0
- package/dist/core.d.ts +26 -0
- package/dist/core.js +89 -0
- package/dist/core.js.map +1 -0
- package/dist/devices-oLIB_M19.d.ts +97 -0
- package/dist/express.d.ts +19 -0
- package/dist/express.js +34 -0
- package/dist/express.js.map +1 -0
- package/dist/fastify.d.ts +22 -0
- package/dist/fastify.js +42 -0
- package/dist/fastify.js.map +1 -0
- package/dist/feature-B2bEXLZG.d.ts +233 -0
- package/dist/feature-DQUbnYE-.d.ts +284 -0
- package/dist/feature-DwKkuOBh.d.ts +195 -0
- package/dist/hub-CPrWmIvB.d.ts +174 -0
- package/dist/index.d.ts +33 -0
- package/dist/index.js +138 -0
- package/dist/index.js.map +1 -0
- package/dist/integration.d.ts +97 -0
- package/dist/integration.js +151 -0
- package/dist/integration.js.map +1 -0
- package/dist/models-D1lehXIu.d.ts +31 -0
- package/dist/models-DTXEX1Q8.d.ts +34 -0
- package/dist/moderation.d.ts +9 -0
- package/dist/moderation.js +48 -0
- package/dist/moderation.js.map +1 -0
- package/dist/push.d.ts +198 -0
- package/dist/push.js +475 -0
- package/dist/push.js.map +1 -0
- package/dist/redis.d.ts +103 -0
- package/dist/redis.js +296 -0
- package/dist/redis.js.map +1 -0
- package/dist/s3.d.ts +75 -0
- package/dist/s3.js +315 -0
- package/dist/s3.js.map +1 -0
- package/dist/util-B32rIBVA.d.ts +22 -0
- package/package.json +148 -6
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/core/compat.ts","../src/core/connections.ts","../src/core/events.ts","../src/core/websocket.ts","../src/core/hub.ts","../src/core/http.ts"],"sourcesContent":["/** Legacy wire quirks kept for installed app versions (PROTOCOL.md section 6). */\n// Installed apps match the heartbeat reply on the upper-case name.\nexport const PONG_TYPE = \"PONG\";\n","/**\n * Sockets per user, presence, rooms and fan-out (port of commskit.core.connections).\n *\n * In-memory by default (single worker). A `PresenceBridge` (Redis) makes it multi-worker.\n * Only \"user\" role connections take part in presence and `user.came_online`; other roles (see the\n * hub's `roleResolver`) get frames and rooms only.\n */\nimport { randomBytes } from \"node:crypto\";\nimport { logger } from \"./logger.js\";\nimport { LRU, TaskSet, formatIso, utcnow } from \"./util.js\";\n\nexport const USER_ROLE = \"user\";\n\n/** The minimal socket surface the kit needs (a `ws` WebSocket satisfies it). */\nexport interface SocketLike {\n readyState: number;\n send(data: string, cb?: (err?: Error) => void): void;\n close(code?: number, reason?: string): void;\n}\n\nexport type OfflineHook = (userId: string) => Promise<void> | void;\nexport type PartnersLookup = (userId: string) => Promise<Iterable<string>>;\nexport type Deliver = (userId: string, envelope: any) => Promise<void>;\n\n/**\n * Cross-worker presence + delivery. `RedisBridge` implements this.\n * Optional members: setRoomDeliver(fn), publishRoom(room, payload, origin),\n * claimCameOnline(userId, ttlSeconds) -> boolean, releaseCameOnline(userId).\n */\nexport interface PresenceBridge {\n heartbeatSeconds: number;\n start(deliver: Deliver, serverId: string): Promise<void>;\n stop(): Promise<void>;\n setOnline(userId: string, serverId: string): Promise<void>;\n setOffline(userId: string, serverId: string): Promise<void>;\n setAway(userId: string, away: boolean): Promise<void>;\n getUserServers(userId: string): Promise<string[]>;\n isOnline(userId: string): Promise<boolean>;\n getOnlineUsers(userIds: string[]): Promise<Set<string>>;\n publish(userId: string, payload: unknown, origin: string): Promise<void>;\n setRoomDeliver?(fn: (room: string, envelope: any) => Promise<void>): void;\n publishRoom?(room: string, payload: unknown, origin: string): Promise<void>;\n claimCameOnline?(userId: string, ttlSeconds: number): Promise<boolean>;\n releaseCameOnline?(userId: string): Promise<void>;\n}\n\nexport interface UserConnection {\n userId: string;\n status: string; // \"online\" (foreground) or anything else (= away)\n lastSeen: Date;\n}\n\nconst OPEN = 1;\n\nfunction sendText(ws: SocketLike, text: string): Promise<void> {\n return new Promise((resolve, reject) => {\n if (ws.readyState !== OPEN) return reject(new Error(\"socket not open\"));\n try {\n ws.send(text, (err) => (err ? reject(err) : resolve()));\n } catch (err) {\n reject(err);\n }\n });\n}\n\nexport class ConnectionManager {\n bridge: PresenceBridge | null;\n serverId: string;\n /** A user may hold several sockets (devices / screens); all get every frame. */\n userSockets = new Map<string, Set<SocketLike>>();\n activeConnections = new Map<string, UserConnection>();\n roles = new Map<string, string>();\n rooms = new Map<string, Set<string>>();\n private userRooms = new Map<string, Set<string>>();\n /** user.came_online: 0 = only dedupe concurrent connects; > 0 = at most once per cooldown (seconds). */\n cameOnlineCooldown: number;\n cameOnlineDedupeSeconds: number;\n private cameOnlineAt = new LRU<string, number>(100_000);\n private offlineHooks: Array<[OfflineHook, boolean]> = [];\n private partnersLookup: PartnersLookup | null = null;\n private tasks = new TaskSet();\n\n constructor(\n opts: {\n bridge?: PresenceBridge | null;\n serverId?: string;\n cameOnlineCooldown?: number;\n cameOnlineDedupeSeconds?: number;\n } = {},\n ) {\n this.bridge = opts.bridge ?? null;\n this.serverId = opts.serverId ?? `${process.env.HOSTNAME ?? \"server\"}-${process.pid}-${randomBytes(3).toString(\"hex\")}`;\n this.cameOnlineCooldown = opts.cameOnlineCooldown ?? 0;\n this.cameOnlineDedupeSeconds = opts.cameOnlineDedupeSeconds ?? 10;\n }\n\n // ----------------------------------------------------------- lifecycle\n\n /** Start the bridge consumer (call at app start). */\n async start(): Promise<void> {\n if (this.bridge) {\n this.bridge.setRoomDeliver?.((room, env) => this.deliverRoomFromBridge(room, env));\n await this.bridge.start((uid, env) => this.deliverFromBridge(uid, env), this.serverId);\n }\n }\n\n async stop(): Promise<void> {\n await this.tasks.settle();\n if (this.bridge) await this.bridge.stop();\n }\n\n /** `hook(userId)` runs when the user's last socket on this worker closes (users only unless `allRoles`). */\n addOfflineHook(hook: OfflineHook, allRoles = false): void {\n this.offlineHooks.push([hook, allRoles]);\n }\n\n /** Who should receive this user's `user_status` (chat installs this). */\n setPartnersLookup(lookup: PartnersLookup | null): void {\n this.partnersLookup = lookup;\n }\n\n roleOf(userId: string): string {\n return this.roles.get(userId) ?? USER_ROLE;\n }\n\n // ---------------------------------------------------------- connections\n\n /**\n * Register an already-accepted socket. Returns true when this connect is the user's\n * offline -> online transition across all workers; always false for non-user roles.\n */\n async register(userId: string, ws: SocketLike, role: string = USER_ROLE): Promise<boolean> {\n const firstSocket = !this.userSockets.get(userId)?.size;\n let set = this.userSockets.get(userId);\n if (!set) this.userSockets.set(userId, (set = new Set()));\n set.add(ws);\n this.roles.set(userId, role);\n this.activeConnections.set(userId, { userId, status: \"online\", lastSeen: utcnow() });\n let cameOnline = false;\n if (firstSocket && role === USER_ROLE) cameOnline = await this.claimCameOnline(userId);\n if (this.bridge) {\n await this.bridge.setOnline(userId, this.serverId);\n await this.bridge.setAway(userId, false);\n }\n if (firstSocket) await this.broadcastUserStatus(userId, \"online\");\n return cameOnline;\n }\n\n private async claimCameOnline(userId: string): Promise<boolean> {\n if (this.bridge) {\n let others: string[] = [];\n try {\n others = (await this.bridge.getUserServers(userId)).filter((s) => s !== this.serverId);\n } catch {\n others = [];\n }\n if (others.length) return false; // already online on another worker\n if (this.bridge.claimCameOnline) {\n const ttl = this.cameOnlineCooldown || this.cameOnlineDedupeSeconds;\n try {\n return Boolean(await this.bridge.claimCameOnline(userId, ttl));\n } catch (err) {\n logger.error(`claimCameOnline(${userId}) failed`, err);\n }\n }\n }\n if (this.cameOnlineCooldown > 0) {\n const now = performance.now() / 1000;\n const last = this.cameOnlineAt.get(userId);\n if (last !== undefined && now - last < this.cameOnlineCooldown) return false;\n this.cameOnlineAt.put(userId, now);\n }\n return true;\n }\n\n async heartbeat(userId: string): Promise<void> {\n if (this.bridge && this.userSockets.get(userId)?.size) await this.bridge.setOnline(userId, this.serverId);\n }\n\n /** Remove one socket (or all). The user stays online while any socket remains. Idempotent. */\n disconnect(userId: string, ws?: SocketLike): void {\n const sockets = this.userSockets.get(userId);\n if (sockets && ws) {\n sockets.delete(ws);\n if (sockets.size) return;\n }\n this.userSockets.delete(userId);\n const role = this.roles.get(userId) ?? USER_ROLE;\n this.roles.delete(userId);\n for (const room of this.userRooms.get(userId) ?? []) {\n const members = this.rooms.get(room);\n if (members) {\n members.delete(userId);\n if (!members.size) this.rooms.delete(room);\n }\n }\n this.userRooms.delete(userId);\n if (!this.activeConnections.delete(userId)) return;\n this.tasks.spawn(() => this.wentOffline(userId, role));\n for (const [hook, allRoles] of this.offlineHooks) {\n if (role === USER_ROLE || allRoles) this.tasks.spawn(async () => hook(userId));\n }\n }\n\n private async wentOffline(userId: string, role: string): Promise<void> {\n // Reconnected here before this ran: don't wipe presence or announce offline.\n if (this.userSockets.get(userId)?.size) return;\n if (this.bridge) await this.bridge.setOffline(userId, this.serverId);\n if (await this.isConnected(userId)) return; // still connected on another worker\n if (role !== USER_ROLE) return;\n if (this.bridge?.releaseCameOnline && !this.cameOnlineCooldown) {\n try {\n await this.bridge.releaseCameOnline(userId);\n } catch (err) {\n logger.error(`releaseCameOnline(${userId}) failed`, err);\n }\n }\n await this.broadcastStatus(userId, \"offline\");\n }\n\n /** Client-reported foreground (\"online\") / background (anything else). */\n async setStatus(userId: string, status: string): Promise<void> {\n const now = utcnow();\n const conn = this.activeConnections.get(userId);\n if (conn) {\n conn.status = status;\n conn.lastSeen = now;\n }\n if (this.bridge) await this.bridge.setAway(userId, status !== \"online\");\n await this.broadcastUserStatus(userId, status, now);\n }\n\n // ------------------------------------------------------------- presence\n\n /** Local only: foreground socket on this worker. */\n isUserOnline(userId: string): boolean {\n return this.activeConnections.get(userId)?.status === \"online\";\n }\n\n /** Cluster-wide: connected somewhere with the app in the foreground. */\n async isOnline(userId: string): Promise<boolean> {\n if (this.isUserOnline(userId)) return true;\n return this.bridge ? this.bridge.isOnline(userId) : false;\n }\n\n /** Cluster-wide: any open socket (foreground or background). */\n async isConnected(userId: string): Promise<boolean> {\n if (this.userSockets.get(userId)?.size) return true;\n return this.bridge ? (await this.bridge.getUserServers(userId)).length > 0 : false;\n }\n\n async getOnlineUsers(userIds: string[]): Promise<Set<string>> {\n const local = new Set(userIds.filter((u) => this.isUserOnline(u)));\n if (!this.bridge) return local;\n const remote = await this.bridge.getOnlineUsers(userIds.filter((u) => !local.has(u)));\n return new Set([...local, ...remote]);\n }\n\n // ------------------------------------------------------------- delivery\n\n private async sendLocal(userId: string, text: string): Promise<boolean> {\n let delivered = false;\n const dead: SocketLike[] = [];\n for (const ws of [...(this.userSockets.get(userId) ?? [])]) {\n try {\n await sendText(ws, text);\n delivered = true;\n } catch (err) {\n logger.warn(`Send to ${userId} failed: ${(err as Error).message}`);\n dead.push(ws);\n }\n }\n for (const ws of dead) this.disconnect(userId, ws);\n return delivered;\n }\n\n /** Deliver to every socket of the user on any worker. True if at least one got it. */\n async send(userId: string, payload: unknown): Promise<boolean> {\n let delivered = await this.sendLocal(userId, JSON.stringify(payload));\n if (this.bridge) {\n const remote = (await this.bridge.getUserServers(userId)).filter((s) => s !== this.serverId);\n if (remote.length) {\n await this.bridge.publish(userId, payload, this.serverId);\n delivered = true;\n }\n }\n return delivered;\n }\n\n async sendToSocket(ws: SocketLike, payload: unknown): Promise<void> {\n await sendText(ws, JSON.stringify(payload));\n }\n\n /** Every socket on this worker (e.g. host community events). */\n async broadcast(payload: unknown): Promise<void> {\n const text = JSON.stringify(payload);\n for (const userId of [...this.userSockets.keys()]) await this.sendLocal(userId, text);\n }\n\n private async deliverFromBridge(userId: string, envelope: any): Promise<void> {\n if (envelope && \"origin\" in envelope && \"data\" in envelope) {\n if (envelope.origin === this.serverId) return; // we published it and already delivered locally\n envelope = envelope.data;\n }\n await this.sendLocal(userId, JSON.stringify(envelope));\n }\n\n // ---------------------------------------------------------------- rooms\n\n /** Add the user's sockets on this worker to `room` (left automatically on disconnect). False when none. */\n joinRoom(userId: string, room: string): boolean {\n if (!this.userSockets.get(userId)?.size) return false;\n if (!this.rooms.has(room)) this.rooms.set(room, new Set());\n this.rooms.get(room)!.add(userId);\n if (!this.userRooms.has(userId)) this.userRooms.set(userId, new Set());\n this.userRooms.get(userId)!.add(room);\n return true;\n }\n\n leaveRoom(userId: string, room: string): void {\n const members = this.rooms.get(room);\n if (members) {\n members.delete(userId);\n if (!members.size) this.rooms.delete(room);\n }\n const rooms = this.userRooms.get(userId);\n if (rooms) {\n rooms.delete(room);\n if (!rooms.size) this.userRooms.delete(userId);\n }\n }\n\n /** Members on this worker. */\n roomMembers(room: string): Set<string> {\n return new Set(this.rooms.get(room) ?? []);\n }\n\n private async sendRoomLocal(room: string, payload: unknown): Promise<number> {\n const text = JSON.stringify(payload);\n let reached = 0;\n for (const userId of [...(this.rooms.get(room) ?? [])]) if (await this.sendLocal(userId, text)) reached += 1;\n return reached;\n }\n\n /** Send `payload` unchanged to every member on every worker. Returns how many local members got it. */\n async broadcastRoom(room: string, payload: unknown): Promise<number> {\n const reached = await this.sendRoomLocal(room, payload);\n if (this.bridge?.publishRoom) await this.bridge.publishRoom(room, payload, this.serverId);\n return reached;\n }\n\n private async deliverRoomFromBridge(room: string, envelope: any): Promise<void> {\n if (envelope && \"origin\" in envelope && \"data\" in envelope) {\n if (envelope.origin === this.serverId) return;\n envelope = envelope.data;\n }\n await this.sendRoomLocal(room, envelope);\n }\n\n // ------------------------------------------------------------- presence\n\n /** Presence goes only to the user's conversation partners (never for non-user roles). */\n async broadcastUserStatus(userId: string, status: string, timestamp?: Date): Promise<void> {\n if (this.roleOf(userId) !== USER_ROLE) return;\n await this.broadcastStatus(userId, status, timestamp);\n }\n\n private async broadcastStatus(userId: string, status: string, timestamp?: Date): Promise<void> {\n if (!this.partnersLookup) return;\n const payload = { type: \"user_status\", user_id: userId, status, timestamp: formatIso(timestamp ?? utcnow()) };\n let partners: string[];\n try {\n partners = [...(await this.partnersLookup(userId))];\n } catch (err) {\n logger.error(`Partner lookup for ${userId} failed`, err);\n return;\n }\n for (const partnerId of partners) await this.send(partnerId, payload);\n }\n}\n","/**\n * Tiny in-process async event bus.\n *\n * Events emitted by the kit: user.connected(userId), user.disconnected(userId),\n * user.came_online(userId), user.deleted(userId), chat.message_sent(message),\n * chat.system_message_sent(message), chat.message_deleted({chat_id, message_id, for_everyone, user_id}),\n * call.finalized(info), moderation.blocked / unblocked(blockerId, blockedId),\n * moderation.reported(report), moderation.report_resolved(report).\n */\nimport { logger } from \"./logger.js\";\n\nexport type Listener = (...args: any[]) => unknown | Promise<unknown>;\n\nexport class EventBus {\n private listeners = new Map<string, Listener[]>();\n\n on(name: string, listener: Listener): Listener {\n const list = this.listeners.get(name) ?? [];\n list.push(listener);\n this.listeners.set(name, list);\n return listener;\n }\n\n off(name: string, listener: Listener): void {\n const list = this.listeners.get(name);\n if (!list) return;\n const i = list.indexOf(listener);\n if (i >= 0) list.splice(i, 1);\n }\n\n /** Run listeners in registration order; a failing listener is logged, not thrown. */\n async emit(name: string, ...args: unknown[]): Promise<void> {\n for (const listener of [...(this.listeners.get(name) ?? [])]) {\n try {\n await listener(...args);\n } catch (err) {\n logger.error(`Listener for ${name} failed`, err);\n }\n }\n }\n}\n","/** The single `/ws/:token` endpoint (port of commskit.core.websocket). */\nimport type { IncomingMessage } from \"node:http\";\nimport type { Duplex } from \"node:stream\";\nimport { type RawData, type WebSocket, WebSocketServer } from \"ws\";\nimport { resolveUserId } from \"./adapters.js\";\nimport type { CommsHub } from \"./hub.js\";\nimport { logger } from \"./logger.js\";\n\nexport class WsEndpoint {\n readonly wss = new WebSocketServer({ noServer: true });\n\n constructor(private hub: CommsHub) {}\n\n /** Token from `/ws/<token>` (query string ignored), or null. */\n match(url: string | undefined): string | null {\n if (!url) return null;\n const pathOnly = url.split(\"?\", 1)[0]!;\n const m = /^\\/ws\\/([^/]+)$/.exec(pathOnly);\n if (!m) return null;\n try {\n return decodeURIComponent(m[1]!);\n } catch {\n return m[1]!;\n }\n }\n\n handleUpgrade(req: IncomingMessage, socket: Duplex, head: Buffer): void {\n const token = this.match(req.url);\n if (token === null) {\n socket.destroy();\n return;\n }\n // Accept first, then authenticate: clients expect close code 1008 on a bad token.\n this.wss.handleUpgrade(req, socket, head, (ws) => this.onSocket(ws, token));\n }\n\n closeAll(): void {\n for (const ws of this.wss.clients) ws.terminate();\n }\n\n private onSocket(ws: WebSocket, token: string): void {\n const hub = this.hub;\n let userId: string | null = null;\n let heartbeat: NodeJS.Timeout | null = null;\n\n // Frames are processed one at a time, in arrival order (like the Python receive loop).\n let chain: Promise<void> = (async () => {\n try {\n userId = await resolveUserId(hub.auth, token);\n } catch (err) {\n logger.info(`WS auth failed: ${(err as Error).message}`);\n ws.close(1008, \"Invalid token\");\n return;\n }\n try {\n const role = await hub.roleOf(userId);\n const cameOnline = await hub.connections.register(userId, ws, role);\n await hub.runConnectHooks(userId, ws, role, cameOnline);\n const bridge = hub.connections.bridge;\n if (bridge) {\n const uid = userId;\n heartbeat = setInterval(() => void hub.connections.heartbeat(uid).catch(() => undefined), bridge.heartbeatSeconds * 1000);\n heartbeat.unref();\n }\n } catch (err) {\n logger.error(`WS setup for ${userId} crashed`, err);\n ws.close(1011, \"Internal server error\");\n }\n })();\n\n ws.on(\"message\", (data: RawData) => {\n chain = chain.then(async () => {\n if (userId !== null) await this.handleFrame(userId, ws, rawToString(data));\n });\n });\n ws.on(\"error\", (err) => logger.warn(`WS error: ${err.message}`));\n ws.on(\"close\", () => {\n chain = chain.then(() => {\n if (heartbeat) clearInterval(heartbeat);\n if (userId !== null) hub.connections.disconnect(userId, ws);\n });\n });\n }\n\n private async handleFrame(userId: string, ws: WebSocket, raw: string): Promise<void> {\n const hub = this.hub;\n const reply = (payload: unknown): Promise<void> => hub.connections.sendToSocket(ws, payload).catch(() => undefined);\n let message: unknown;\n try {\n message = JSON.parse(raw);\n } catch (err) {\n await hub.reportHandlerError(userId, raw, err);\n await reply({ type: \"error\", error: \"Invalid JSON\" });\n return;\n }\n if (message === null || typeof message !== \"object\" || Array.isArray(message)) {\n await hub.reportHandlerError(userId, message, new TypeError(\"Message must be a JSON object\"));\n await reply({ type: \"error\", error: \"Message must be a JSON object\" });\n return;\n }\n const frame = message as Record<string, any>;\n try {\n await hub.dispatch(userId, frame);\n } catch (err) {\n logger.error(`Handler for ${JSON.stringify(frame.type)} failed`, err);\n await hub.reportHandlerError(userId, frame, err);\n const text = err instanceof Error ? err.message : String(err);\n await hub.send(userId, { type: \"error\", error: `Message processing failed: ${text}` }).catch(() => undefined);\n }\n }\n}\n\nfunction rawToString(data: RawData): string {\n if (typeof data === \"string\") return data;\n if (Array.isArray(data)) return Buffer.concat(data).toString(\"utf8\");\n return Buffer.from(data as ArrayBuffer).toString(\"utf8\");\n}\n","/** CommsHub: WS handler registry, lifecycle hooks, event bus, REST routes (port of commskit.core.hub). */\nimport type { IncomingMessage, Server } from \"node:http\";\nimport type { Duplex } from \"node:stream\";\nimport type { WebSocketServer } from \"ws\";\nimport {\n type AuthAdapter,\n NoopPush,\n NullUserDirectory,\n type PushSender,\n type UserDirectory,\n type UserProfile,\n resolveUserId,\n} from \"./adapters.js\";\nimport { PONG_TYPE } from \"./compat.js\";\nimport { USER_ROLE, ConnectionManager, type SocketLike } from \"./connections.js\";\nimport { type Db, type Dialect, detectDialect } from \"./db.js\";\nimport { EventBus } from \"./events.js\";\nimport { type Logger, logger, setLogger } from \"./logger.js\";\nimport { type Limiter, RateLimiter } from \"./ratelimit.js\";\nimport { HttpError, type RestContext, type RestResult, type RestRoute } from \"./rest.js\";\nimport { TaskSet, nowIso } from \"./util.js\";\nimport { WsEndpoint } from \"./websocket.js\";\n\n/** Handler(userId, message, db). Errors propagate to the socket loop, which replies `Message processing failed: ...`. */\nexport type Handler = (userId: string, message: Record<string, any>, db: Db) => Promise<void> | void;\nexport type ConnectHook = (userId: string, ws: SocketLike) => Promise<void> | void;\nexport type DisconnectHook = (userId: string) => Promise<void> | void;\n/** roleResolver(userId) -> \"user\" | any other role (sync or async). Non-user roles get no presence, delivery flush or user hooks. */\nexport type RoleResolver = (userId: string) => string | Promise<string>;\n/** onHandlerError(userId, frame, err): frame is the parsed message, or the raw text for bad JSON. */\nexport type HandlerErrorHook = (userId: string, frame: unknown, err: unknown) => Promise<void> | void;\n\n/** A feature installs itself on a hub: registers handlers, routes, hooks and tables' services. */\nexport interface Feature {\n readonly name: string;\n install(hub: CommsHub): unknown;\n}\n\nexport interface CommsHubOptions {\n auth: AuthAdapter;\n /** Kysely instance (better-sqlite3 for tests/demo, pg for production). Create tables with `ensureSchema`. */\n db: Db;\n /** Override dialect detection. */\n dialect?: Dialect;\n users?: UserDirectory;\n push?: PushSender;\n connections?: ConnectionManager;\n rateLimiter?: Limiter;\n roleResolver?: RoleResolver;\n onHandlerError?: HandlerErrorHook;\n /** Seconds; at most one `user.came_online` per user per cooldown. */\n cameOnlineCooldown?: number;\n logger?: Logger;\n}\n\nexport class CommsHub {\n readonly auth: AuthAdapter;\n readonly db: Db;\n readonly dialect: Dialect;\n users: UserDirectory;\n pushSender: PushSender;\n connections: ConnectionManager;\n rateLimiter: Limiter;\n roleResolver?: RoleResolver;\n onHandlerError?: HandlerErrorHook;\n readonly events = new EventBus();\n readonly features: Record<string, unknown> = {};\n\n private handlers = new Map<string, Handler>();\n private connectHooks: Array<[ConnectHook, boolean]> = [];\n private routes: RestRoute[] = [];\n private tasks = new TaskSet();\n private ws: WsEndpoint;\n\n constructor(opts: CommsHubOptions) {\n this.auth = opts.auth;\n this.db = opts.db;\n this.dialect = opts.dialect ?? detectDialect(opts.db);\n this.users = opts.users ?? new NullUserDirectory();\n this.pushSender = opts.push ?? new NoopPush();\n this.connections = opts.connections ?? new ConnectionManager();\n if (opts.cameOnlineCooldown !== undefined) this.connections.cameOnlineCooldown = Number(opts.cameOnlineCooldown);\n this.rateLimiter = opts.rateLimiter ?? new RateLimiter();\n this.roleResolver = opts.roleResolver;\n this.onHandlerError = opts.onHandlerError;\n if (opts.logger) setLogger(opts.logger);\n this.connections.addOfflineHook((uid) => this.events.emit(\"user.disconnected\", uid));\n this.on(\"ping\", async (userId) => {\n await this.send(userId, { type: PONG_TYPE, timestamp: nowIso() });\n });\n this.on(\"user_status\", (userId, message) =>\n this.connections.setStatus(userId, String(message.status ?? \"online\")),\n );\n this.ws = new WsEndpoint(this);\n }\n\n // ------------------------------------------------------------ registry\n\n /** Install a feature (`hub.install(new ChatFeature({...}))`). Returns the feature. */\n install<F extends Feature>(feature: F): F {\n feature.install(this);\n return feature;\n }\n\n registerFeature(name: string, feature: unknown): void {\n if (name in this.features) throw new Error(`Feature '${name}' is already installed on this hub`);\n this.features[name] = feature;\n }\n\n /** Register a WS handler for frames of `type`. Registering again replaces the previous handler. */\n on(type: string, handler: Handler): Handler {\n this.handlers.set(type, handler);\n return handler;\n }\n\n handles(type: string): boolean {\n return this.handlers.has(type);\n }\n\n /** Run the handler for `message.type`. Unknown types are ignored (false). */\n async dispatch(userId: string, message: Record<string, any>): Promise<boolean> {\n const handler = this.handlers.get(message.type);\n if (!handler) return false;\n await handler(userId, message, this.db);\n return true;\n }\n\n /** `hook(userId, ws)` after a socket is registered (runs before frames). Users only unless `allRoles`. */\n onConnect(hook: ConnectHook, allRoles = false): ConnectHook {\n this.connectHooks.push([hook, allRoles]);\n return hook;\n }\n\n /** `hook(userId)` when the user's last socket on this worker closes (users only unless `allRoles`). */\n onDisconnect(hook: DisconnectHook, allRoles = false): DisconnectHook {\n this.connections.addOfflineHook(hook, allRoles);\n return hook;\n }\n\n /** `roleResolver(userId)`; \"user\" without a resolver or when it fails. */\n async roleOf(userId: string): Promise<string> {\n if (!this.roleResolver) return USER_ROLE;\n try {\n return String((await this.roleResolver(userId)) || USER_ROLE);\n } catch (err) {\n logger.error(`roleResolver failed for ${userId}`, err);\n return USER_ROLE;\n }\n }\n\n /** Call `onHandlerError`; never throws (hook failures are logged). */\n async reportHandlerError(userId: string, frame: unknown, err: unknown): Promise<void> {\n if (!this.onHandlerError) return;\n try {\n await this.onHandlerError(userId, frame, err);\n } catch (e) {\n logger.error(\"onHandlerError hook failed\", e);\n }\n }\n\n // ---------------------------------------------------------------- rooms\n\n joinRoom(userId: string, room: string): boolean {\n return this.connections.joinRoom(userId, room);\n }\n leaveRoom(userId: string, room: string): void {\n this.connections.leaveRoom(userId, room);\n }\n /** Send `frame` as given to every member (every worker, with a room-capable bridge). */\n broadcastRoom(room: string, frame: unknown): Promise<number> {\n return this.connections.broadcastRoom(room, frame);\n }\n\n // ------------------------------------------------------------------ REST\n\n /** Features call this from `install` to contribute routes. */\n includeRoutes(routes: RestRoute[]): void {\n this.routes.push(...routes);\n }\n\n /** `Authorization: Bearer <token>` -> user id (401 otherwise). */\n async authenticate(headers: RestContext[\"headers\"]): Promise<string> {\n const raw = headers[\"authorization\"];\n const value = Array.isArray(raw) ? raw[0] : raw;\n const m = value ? /^bearer\\s+(.+)$/i.exec(value.trim()) : null;\n if (!m || !m[1]) throw new HttpError(401, \"Not authenticated\");\n try {\n return await resolveUserId(this.auth, m[1].trim());\n } catch {\n throw new HttpError(401, \"Invalid token\");\n }\n }\n\n /** All routes, ready for an adapter: auth is resolved and HttpError / crashes become `{status, body}`. */\n restRoutes(): RestRoute[] {\n return this.routes.map((route) => ({\n ...route,\n handler: async (ctx: RestContext): Promise<RestResult> => {\n try {\n const userId = route.auth === false ? null : await this.authenticate(ctx.headers);\n return await route.handler({ ...ctx, userId });\n } catch (err) {\n if (err instanceof HttpError) return { status: err.status, headers: err.headers, body: { detail: err.detail } };\n logger.error(`REST ${route.method} ${route.path} failed`, err);\n return { status: 500, body: { detail: \"Internal Server Error\" } };\n }\n },\n }));\n }\n\n // -------------------------------------------------------------------- WS\n\n /** True when `url` is a `/ws/<token>` request. */\n matchesWs(url: string | undefined): boolean {\n return this.ws.match(url) !== null;\n }\n\n /** Handle an HTTP upgrade for `/ws/:token` (accept first, then authenticate: 1008 on a bad token). */\n handleUpgrade(req: IncomingMessage, socket: Duplex, head: Buffer): void {\n this.ws.handleUpgrade(req, socket, head);\n }\n\n /** Listen for `/ws/:token` upgrades on an http.Server (other upgrade paths are left to other listeners). */\n attach(server: Server): void {\n server.on(\"upgrade\", (req, socket, head) => {\n if (this.matchesWs(req.url)) this.handleUpgrade(req, socket, head);\n });\n }\n\n get wsServer(): WebSocketServer {\n return this.ws.wss;\n }\n\n // ------------------------------------------------------------- services\n\n spawn(task: Promise<unknown> | (() => Promise<unknown>)): void {\n this.tasks.spawn(task);\n }\n\n /** Close sockets and wait briefly for background work. */\n async shutdown(): Promise<void> {\n this.ws.closeAll();\n await this.tasks.settle();\n await this.connections.stop();\n }\n\n async send(userId: string, payload: unknown): Promise<boolean> {\n return this.connections.send(userId, payload);\n }\n\n async runConnectHooks(userId: string, ws: SocketLike, role: string = USER_ROLE, cameOnline = false): Promise<void> {\n const isUser = role === USER_ROLE;\n for (const [hook, allRoles] of [...this.connectHooks]) {\n if (!(isUser || allRoles)) continue;\n try {\n await hook(userId, ws);\n } catch (err) {\n logger.error(`onConnect hook failed for ${userId}`, err);\n }\n }\n if (!isUser) return;\n await this.events.emit(\"user.connected\", userId);\n if (cameOnline) await this.events.emit(\"user.came_online\", userId);\n }\n\n async profiles(userIds: readonly string[]): Promise<Record<string, UserProfile>> {\n const ids = [...new Set(userIds)].filter(Boolean);\n if (!ids.length) return {};\n try {\n return await this.users.getProfiles(ids);\n } catch (err) {\n logger.error(\"UserDirectory.getProfiles failed\", err);\n return {};\n }\n }\n\n async profile(userId: string): Promise<UserProfile | undefined> {\n return (await this.profiles([userId]))[userId];\n }\n\n async displayName(userId: string, fallback = \"Someone\"): Promise<string> {\n const p = await this.profile(userId);\n return p?.name ? p.name : fallback;\n }\n\n /** Never throws: push failures are logged. */\n async push(\n userId: string,\n title: string,\n body: string,\n data: Record<string, unknown>,\n channel = \"default\",\n ttl: number | null = null,\n ): Promise<void> {\n try {\n await this.pushSender.send(userId, title, body, data, channel, ttl);\n } catch (err) {\n logger.error(`Push to ${userId} failed`, err);\n }\n }\n}\n","/** Built-in `node:http` request listener for the hub's REST routes (no framework needed; used by tests/demo). */\nimport type { IncomingMessage, ServerResponse } from \"node:http\";\nimport type { CommsHub } from \"./hub.js\";\nimport { type RestContext, matchPath } from \"./rest.js\";\n\nasync function readBody(req: IncomingMessage): Promise<unknown> {\n const chunks: Buffer[] = [];\n for await (const c of req) chunks.push(c as Buffer);\n const text = Buffer.concat(chunks).toString(\"utf8\");\n if (!text) return undefined;\n try {\n return JSON.parse(text);\n } catch {\n return undefined;\n }\n}\n\n/**\n * Returns `(req, res) => Promise<boolean>`: true when a route matched and the response was written,\n * false when nothing matched (the caller may serve other things or answer 404).\n */\nexport function restListener(hub: CommsHub): (req: IncomingMessage, res: ServerResponse) => Promise<boolean> {\n const routes = hub.restRoutes();\n return async (req, res) => {\n const url = new URL(req.url ?? \"/\", \"http://localhost\");\n const method = (req.method ?? \"GET\").toUpperCase();\n for (const route of routes) {\n if (route.method !== method) continue;\n const params = matchPath(route.path, url.pathname);\n if (!params) continue;\n const query: RestContext[\"query\"] = {};\n for (const key of new Set(url.searchParams.keys())) {\n const all = url.searchParams.getAll(key);\n query[key] = all.length > 1 ? all : all[0];\n }\n const result = await route.handler({\n method,\n path: route.path,\n params,\n query,\n headers: req.headers,\n body: await readBody(req),\n userId: null,\n });\n res.writeHead(result.status, { \"content-type\": \"application/json\", ...result.headers });\n res.end(JSON.stringify(result.body));\n return true;\n }\n return false;\n };\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ddlTypes
|
|
3
|
+
} from "./chunk-GR4PMGCS.js";
|
|
4
|
+
|
|
5
|
+
// src/moderation/models.ts
|
|
6
|
+
function moderationDdl(dialect) {
|
|
7
|
+
const t = ddlTypes(dialect);
|
|
8
|
+
return [
|
|
9
|
+
`CREATE TABLE IF NOT EXISTS comms_blocks (
|
|
10
|
+
id ${t.pk},
|
|
11
|
+
blocker_id VARCHAR(50) NOT NULL,
|
|
12
|
+
blocked_id VARCHAR(50) NOT NULL,
|
|
13
|
+
created_at ${t.ts} NOT NULL,
|
|
14
|
+
CONSTRAINT uq_comms_blocks_pair UNIQUE (blocker_id, blocked_id)
|
|
15
|
+
)`,
|
|
16
|
+
`CREATE TABLE IF NOT EXISTS comms_reports (
|
|
17
|
+
id ${t.pk},
|
|
18
|
+
reporter_id VARCHAR(50),
|
|
19
|
+
reported_id VARCHAR(50),
|
|
20
|
+
reason TEXT,
|
|
21
|
+
category VARCHAR(64),
|
|
22
|
+
message_id INTEGER,
|
|
23
|
+
status VARCHAR(20) NOT NULL,
|
|
24
|
+
created_at ${t.ts} NOT NULL,
|
|
25
|
+
resolved_at ${t.ts},
|
|
26
|
+
resolved_by VARCHAR(50),
|
|
27
|
+
note TEXT
|
|
28
|
+
)`,
|
|
29
|
+
"CREATE INDEX IF NOT EXISTS ix_comms_blocks_blocker_id ON comms_blocks (blocker_id)",
|
|
30
|
+
"CREATE INDEX IF NOT EXISTS ix_comms_blocks_blocked_id ON comms_blocks (blocked_id)",
|
|
31
|
+
"CREATE INDEX IF NOT EXISTS ix_comms_reports_reporter_id ON comms_reports (reporter_id)",
|
|
32
|
+
"CREATE INDEX IF NOT EXISTS ix_comms_reports_reported_id ON comms_reports (reported_id)"
|
|
33
|
+
];
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export {
|
|
37
|
+
moderationDdl
|
|
38
|
+
};
|
|
39
|
+
//# sourceMappingURL=chunk-SEDRF62Q.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/moderation/models.ts"],"sourcesContent":["/**\n * Moderation tables `comms_blocks` and `comms_reports` (same columns as the Python models; no FK to a users table).\n * ModerationFeature / service / policy wrappers build on these.\n */\nimport type { Generated } from \"kysely\";\nimport type { Dialect } from \"../core/db.js\";\nimport { ddlTypes } from \"../core/ddl.js\";\n\ntype Ts = string | Date;\n\nexport interface BlocksTable {\n id: Generated<number>;\n blocker_id: string;\n blocked_id: string;\n created_at: Ts;\n}\n\nexport interface ReportsTable {\n id: Generated<number>;\n reporter_id: string | null;\n reported_id: string | null;\n reason: string | null;\n category: string | null;\n message_id: number | null;\n status: string; // open | resolved | dismissed\n created_at: Ts;\n resolved_at: Ts | null;\n resolved_by: string | null;\n note: string | null;\n}\n\nexport function moderationDdl(dialect: Dialect): string[] {\n const t = ddlTypes(dialect);\n return [\n `CREATE TABLE IF NOT EXISTS comms_blocks (\n id ${t.pk},\n blocker_id VARCHAR(50) NOT NULL,\n blocked_id VARCHAR(50) NOT NULL,\n created_at ${t.ts} NOT NULL,\n CONSTRAINT uq_comms_blocks_pair UNIQUE (blocker_id, blocked_id)\n)`,\n `CREATE TABLE IF NOT EXISTS comms_reports (\n id ${t.pk},\n reporter_id VARCHAR(50),\n reported_id VARCHAR(50),\n reason TEXT,\n category VARCHAR(64),\n message_id INTEGER,\n status VARCHAR(20) NOT NULL,\n created_at ${t.ts} NOT NULL,\n resolved_at ${t.ts},\n resolved_by VARCHAR(50),\n note TEXT\n)`,\n \"CREATE INDEX IF NOT EXISTS ix_comms_blocks_blocker_id ON comms_blocks (blocker_id)\",\n \"CREATE INDEX IF NOT EXISTS ix_comms_blocks_blocked_id ON comms_blocks (blocked_id)\",\n \"CREATE INDEX IF NOT EXISTS ix_comms_reports_reporter_id ON comms_reports (reporter_id)\",\n \"CREATE INDEX IF NOT EXISTS ix_comms_reports_reported_id ON comms_reports (reported_id)\",\n ];\n}\n"],"mappings":";;;;;AA+BO,SAAS,cAAc,SAA4B;AACxD,QAAM,IAAI,SAAS,OAAO;AAC1B,SAAO;AAAA,IACL;AAAA,OACG,EAAE,EAAE;AAAA;AAAA;AAAA,eAGI,EAAE,EAAE;AAAA;AAAA;AAAA,IAGf;AAAA,OACG,EAAE,EAAE;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,eAOI,EAAE,EAAE;AAAA,gBACH,EAAE,EAAE;AAAA;AAAA;AAAA;AAAA,IAIhB;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACF;","names":[]}
|