@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/call/config.ts","../src/call/payloads.ts","../src/call/service.ts","../src/call/handlers.ts","../src/call/lifecycle.ts","../src/call/policy.ts","../src/call/turn.ts","../src/call/router.ts","../src/call/feature.ts","../src/call/uuid.ts"],"sourcesContent":["/** CallConfig: timings (seconds), push channels and TURN settings (port of commskit.call.config). */\nimport { RateLimit } from \"../core/ratelimit.js\";\n\nexport interface CallConfig {\n ringTimeout: number; // ringing -> missed (no_answer)\n ringingDisconnectGrace: number; // socket loss while ringing -> missed (disconnected)\n answeredDisconnectGrace: number; // socket loss while answered -> ended (disconnected)\n maxCallHours: number; // answered calls older than this no longer count as live\n ringWindowSlack: number; // extra seconds a ringing row still counts as live\n ringtoneChannel: string; // Android channel for incoming-call pushes\n missedChannel: string;\n /** coturn REST-API credentials; the route answers 503 while turnSecret is unset. */\n turnSecret: string | null;\n turnUris: string[];\n turnTtl: number;\n /** call_initiate per caller (PROTOCOL.md section 9 \"Limits\"); null disables it. */\n initiateRate: RateLimit | null;\n}\n\nexport const defaultCallConfig = (): CallConfig => ({\n ringTimeout: 45,\n ringingDisconnectGrace: 20,\n answeredDisconnectGrace: 90,\n maxCallHours: 4,\n ringWindowSlack: 15,\n ringtoneChannel: \"call_ringtone\",\n missedChannel: \"messages\",\n turnSecret: null,\n turnUris: [],\n turnTtl: 3600,\n initiateRate: new RateLimit(5, 60),\n});\n","/** Call frame builders, including legacy quirks (PROTOCOL.md section 6). */\nimport { nowIso } from \"../core/util.js\";\nimport type { CallInfo } from \"./service.js\";\n\nexport function callPayload(event: string, info: CallInfo, extra: Record<string, unknown> = {}): Record<string, any> {\n return {\n type: event,\n call_id: info.callId,\n chat_id: info.chatId,\n caller_id: info.callerId,\n receiver_id: info.receiverId,\n call_type: info.callType,\n webrtc_session_id: info.callId,\n timestamp: nowIso(),\n ...extra,\n };\n}\n\n/** Legacy: caller-side call_initiated / call_ringing carry the *receiver* in caller_id / caller_name. */\nexport const callerView = (info: CallInfo): Record<string, unknown> => ({\n caller_id: info.receiverId,\n caller_name: info.receiverId,\n});\n\n/** Legacy: busy is a call_rejected with call_id null. */\nexport function busyPayload(callerId: string, receiverId: string, callType: string): Record<string, unknown> {\n return {\n type: \"call_rejected\",\n call_id: null,\n status: \"busy\",\n reason: \"busy\",\n caller_id: callerId,\n receiver_id: receiverId,\n call_type: callType,\n timestamp: nowIso(),\n };\n}\n\n/** Tagged so clients only tear down call UI for call errors. */\nexport const callError = (error: string, extra: Record<string, unknown> = {}): Record<string, unknown> => ({\n type: \"error\",\n error,\n ...extra,\n context: \"call\",\n});\n","/**\n * CallService: call rows and their state machine (port of commskit.call.service).\n * State changes are conditional UPDATEs (`WHERE call_status = <expected>`) so hangup / timeout / disconnect\n * racing on different workers finalize a call once. Timestamps come from the app clock (`dbNow`).\n */\nimport { randomUUID } from \"node:crypto\";\nimport { type Db, type Dialect, dbBool, dbNow, dbTimestamp, detectDialect } from \"../core/db.js\";\nimport { parseDbDate } from \"../core/util.js\";\nimport type { CallChatBridge } from \"./bridge.js\";\nimport { type CallConfig, defaultCallConfig } from \"./config.js\";\n\nexport const ACTIVE_CALL_STATUSES = [\"ringing\", \"answered\"] as const;\n\nexport class CallInfo {\n /** Filled by a chat bridge during finalize: the call's chat entry. */\n messageId: number | null = null;\n logText: string | null = null;\n\n constructor(\n public callId: string,\n public chatId: number | null,\n public callerId: string,\n public receiverId: string,\n public isVideoCall: boolean,\n public status: string,\n public duration = 0,\n public endReason: string | null = null,\n ) {}\n\n static fromRow(row: Record<string, any>): CallInfo {\n return new CallInfo(\n row.call_id, row.chat_id === null || row.chat_id === undefined ? null : Number(row.chat_id), row.caller_id,\n row.receiver_id, Boolean(row.is_video_call), row.call_status, row.duration ?? 0, row.end_reason ?? null,\n );\n }\n\n get callType(): \"video\" | \"audio\" {\n return this.isVideoCall ? \"video\" : \"audio\";\n }\n\n otherParty(userId: string): string | null {\n if (userId === this.callerId) return this.receiverId;\n if (userId === this.receiverId) return this.callerId;\n return null;\n }\n\n clone(patch: Partial<CallInfo> = {}): CallInfo {\n return Object.assign(Object.create(CallInfo.prototype), this, patch);\n }\n}\n\n/** \"Voice call · 05:42\", \"Missed voice call\", \"Declined voice call\" (caller-cancelled is logged as Missed). */\nexport function callLogText(status: string, isVideoCall: boolean, duration: number, _endReason: string | null = null): string {\n const kind = isVideoCall ? \"Video call\" : \"Voice call\";\n if (status === \"missed\") return `Missed ${kind.toLowerCase()}`;\n if (status === \"rejected\") return `Declined ${kind.toLowerCase()}`;\n const total = Math.max(0, Math.trunc(duration));\n const hours = Math.floor(total / 3600);\n const mins = Math.floor((total % 3600) / 60);\n const secs = total % 60;\n const p = (n: number) => String(n).padStart(2, \"0\");\n return `${kind} · ${hours ? `${hours}:${p(mins)}:${p(secs)}` : `${p(mins)}:${p(secs)}`}`;\n}\n\nexport class CallService {\n private dialect: Dialect;\n config: CallConfig;\n\n constructor(\n private db: Db,\n config?: CallConfig,\n private bridge: CallChatBridge | null = null,\n dialect?: Dialect,\n ) {\n this.config = config ?? defaultCallConfig();\n this.dialect = dialect ?? detectDialect(db);\n }\n\n private ts(d: Date = dbNow()): string | Date {\n return dbTimestamp(this.dialect, d);\n }\n\n async createCall(chatId: number | null, callerId: string, receiverId: string, isVideoCall = false): Promise<CallInfo> {\n const callId = randomUUID();\n await this.db\n .insertInto(\"calls\")\n .values({\n call_id: callId, chat_id: chatId, caller_id: callerId, receiver_id: receiverId, call_status: \"ringing\",\n is_video_call: dbBool(this.dialect, isVideoCall), started_at: this.ts(),\n })\n .execute();\n return new CallInfo(callId, chatId, callerId, receiverId, isVideoCall, \"ringing\");\n }\n\n async getCallById(callId: string): Promise<Record<string, any> | undefined> {\n return this.db.selectFrom(\"calls\").selectAll().where(\"call_id\", \"=\", callId).executeTakeFirst();\n }\n\n async getCallInfo(callId: string): Promise<CallInfo | null> {\n const row = await this.getCallById(callId);\n return row ? CallInfo.fromRow(row) : null;\n }\n\n async saveOfferSdp(callId: string, offerSdp: string): Promise<void> {\n await this.db.updateTable(\"calls\").set({ offer_sdp: offerSdp }).where(\"call_id\", \"=\", callId).execute();\n }\n\n async getOfferSdp(callId: string): Promise<string | null> {\n const r = await this.db.selectFrom(\"calls\").select(\"offer_sdp\").where(\"call_id\", \"=\", callId).executeTakeFirst();\n return r?.offer_sdp ?? null;\n }\n\n private liveCallsQuery(userId: string) {\n const now = dbNow();\n const cfg = this.config;\n const ringCutoff = this.ts(new Date(now.getTime() - (cfg.ringTimeout + cfg.ringWindowSlack) * 1000));\n const answeredCutoff = this.ts(new Date(now.getTime() - cfg.maxCallHours * 3600 * 1000));\n return this.db\n .selectFrom(\"calls\")\n .selectAll()\n .where((eb: any) =>\n eb.and([\n eb.or([eb(\"caller_id\", \"=\", userId), eb(\"receiver_id\", \"=\", userId)]),\n eb.or([\n eb.and([eb(\"call_status\", \"=\", \"ringing\"), eb(\"started_at\", \">=\", ringCutoff)]),\n eb.and([eb(\"call_status\", \"=\", \"answered\"), eb(\"answered_at\", \">=\", answeredCutoff)]),\n ]),\n ]),\n )\n .orderBy(\"started_at\", \"desc\")\n .orderBy(\"id\", \"desc\");\n }\n\n async getActiveCallForUser(userId: string): Promise<CallInfo | null> {\n const row = await this.liveCallsQuery(userId).limit(1).executeTakeFirst();\n return row ? CallInfo.fromRow(row) : null;\n }\n\n async getActiveCallsForUsers(userId: string): Promise<CallInfo[]> {\n return (await this.liveCallsQuery(userId).execute()).map((r: any) => CallInfo.fromRow(r));\n }\n\n /** ringing -> answered. Only the receiver can answer, and only once. */\n async markAnswered(callId: string, receiverId: string): Promise<CallInfo | null> {\n const res = await this.db\n .updateTable(\"calls\")\n .set({ call_status: \"answered\", answered_at: this.ts() })\n .where(\"call_id\", \"=\", callId)\n .where(\"receiver_id\", \"=\", receiverId)\n .where(\"call_status\", \"=\", \"ringing\")\n .executeTakeFirst();\n if (Number(res.numUpdatedRows) !== 1) return null;\n return this.getCallInfo(callId);\n }\n\n /** Move a live call to a terminal status. null if another path already did. */\n async finalizeCall(\n callId: string,\n status: string,\n endReason: string,\n onlyFrom: readonly string[] = ACTIVE_CALL_STATUSES,\n ): Promise<CallInfo | null> {\n const call = await this.getCallById(callId);\n if (!call || !onlyFrom.includes(call.call_status)) return null;\n const fromStatus: string = call.call_status;\n const snapshot = CallInfo.fromRow(call);\n const now = dbNow();\n let duration = 0;\n const answeredAt = parseDbDate(call.answered_at);\n if (fromStatus === \"answered\" && answeredAt) duration = Math.max(0, Math.trunc((now.getTime() - answeredAt.getTime()) / 1000));\n\n const values: Record<string, unknown> = { call_status: status, end_reason: endReason, ended_at: this.ts(now), duration };\n if (status === \"missed\") values.missed_at = this.ts(now);\n const info = snapshot.clone({ status, duration, endReason });\n const bridge = this.bridge;\n const done = await this.db.transaction().execute(async (trx) => {\n const res = await trx\n .updateTable(\"calls\")\n .set(values)\n .where(\"call_id\", \"=\", callId)\n .where(\"call_status\", \"=\", fromStatus)\n .executeTakeFirst();\n if (Number(res.numUpdatedRows) !== 1) return false;\n // Same transaction: the chat entry flips to the log text with the status.\n if (bridge) await bridge.onCallFinalizing(trx, info);\n return true;\n });\n return done ? info : null;\n }\n}\n","/** Call WebSocket handlers (PROTOCOL.md section 4). Port of commskit.call.handlers. */\nimport type { Db } from \"../core/db.js\";\nimport type { Handler } from \"../core/hub.js\";\nimport { logger } from \"../core/logger.js\";\nimport { retryAfterSeconds } from \"../core/ratelimit.js\";\nimport { nowIso, pyBool, pyStr } from \"../core/util.js\";\nimport type { CallFeature } from \"./feature.js\";\nimport { busyPayload, callError, callPayload, callerView } from \"./payloads.js\";\nimport { ACTIVE_CALL_STATUSES } from \"./service.js\";\n\nexport class CallHandlers {\n constructor(private feature: CallFeature) {}\n\n private get hub() {\n return this.feature.hub;\n }\n private get lifecycle() {\n return this.feature.lifecycle;\n }\n\n register(): void {\n const on = this.hub.on.bind(this.hub);\n on(\"call_initiate\", this.guard((u, m, d) => this.callInitiate(u, m, d), \"Failed to initiate call\"));\n on(\"answer_call\", this.guard((u, m, d) => this.answerCall(u, m, d), \"Failed to answer call\"));\n // reject_call and end_call are equivalent: the outcome comes from call state.\n on(\"reject_call\", this.guard((u, m, d) => this.endCall(u, m, d), \"Failed to reject call\"));\n on(\"end_call\", this.guard((u, m, d) => this.endCall(u, m, d), \"Failed to end call\"));\n on(\"sdp_offer\", this.guard((u, m, d) => this.relaySdp(u, m, d, \"sdp_offer\"), null));\n on(\"sdp_answer\", this.guard((u, m, d) => this.relaySdp(u, m, d, \"sdp_answer\"), null));\n on(\"ice_candidate\", this.guard((u, m, d) => this.iceCandidate(u, m, d), null));\n on(\"start_recording\", async () => undefined); // accepted and ignored (PROTOCOL.md)\n }\n\n /** Crash -> call-tagged error (label) or logged only (signalling relays). */\n private guard(fn: Handler, label: string | null): Handler {\n return async (userId, message, db) => {\n try {\n await fn(userId, message, db);\n } catch (err) {\n logger.error(`${message.type} handler failed`, err);\n await this.hub.reportHandlerError(userId, message, err);\n if (label) await this.error(userId, `${label}: ${err instanceof Error ? err.message : String(err)}`);\n }\n };\n }\n\n private async error(userId: string, error: string, extra: Record<string, unknown> = {}): Promise<void> {\n await this.hub.send(userId, callError(error, extra));\n }\n\n // ------------------------------------------------------------ initiate\n\n async callInitiate(userId: string, message: Record<string, any>, db: Db): Promise<void> {\n const lifecycle = this.lifecycle;\n const connections = this.hub.connections;\n const rawReceiver = message.receiver_id;\n const callType = message.call_type === \"video\" ? \"video\" : \"audio\";\n if (!pyBool(rawReceiver)) {\n await this.error(userId, \"Missing required fields: receiver_id\");\n return;\n }\n const receiverId = pyStr(rawReceiver);\n if (receiverId === userId) {\n await this.error(userId, \"You cannot call yourself.\");\n return;\n }\n const limit = this.feature.config.initiateRate;\n const wait = limit ? await this.hub.rateLimiter.hit(`call.initiate:${userId}`, limit) : 0;\n if (wait) {\n const seconds = retryAfterSeconds(wait);\n await this.error(userId, `Too many calls. Try again in ${seconds} s.`, { code: \"rate_limited\", retry_after: seconds });\n return;\n }\n\n const service = lifecycle.service(db);\n const ownCall = await service.getActiveCallForUser(userId);\n if (ownCall && ownCall.status === \"ringing\") {\n await this.error(userId, \"You are already in a call.\");\n return;\n }\n if (ownCall) {\n // Starting a new call means they left their answered one; its hangup was lost. Close it out.\n await lifecycle.finishCall(db, ownCall, userId);\n }\n\n let receiverCall = await service.getActiveCallForUser(receiverId);\n if (\n receiverCall &&\n !((await connections.isConnected(receiverCall.callerId)) || (await connections.isConnected(receiverCall.receiverId)))\n ) {\n // Nobody on that call has a socket anywhere: a leftover, not a real call.\n const stale = await service.finalizeCall(\n receiverCall.callId,\n receiverCall.status === \"answered\" ? \"ended\" : \"missed\",\n \"stale\",\n [receiverCall.status],\n );\n if (stale) await lifecycle.finalized(stale);\n receiverCall = await service.getActiveCallForUser(receiverId);\n }\n if (receiverCall) {\n await this.hub.send(userId, busyPayload(userId, receiverId, callType));\n return;\n }\n\n const decision = await this.feature.policy.authorize(db, userId, receiverId, message);\n if (!decision.allowed) {\n const error: Record<string, unknown> = { ...(decision.error ?? { error: \"Call not allowed.\" }) };\n if (!(\"error\" in error)) error.error = \"Call not allowed.\";\n await this.hub.send(userId, { type: \"error\", ...error, context: \"call\" });\n return;\n }\n\n const bridge = this.feature.chatBridge;\n const chatId = bridge ? await bridge.resolveChatId(db, userId, receiverId, message.chat_id) : null;\n const info = await service.createCall(chatId, userId, receiverId, callType === \"video\");\n lifecycle.scheduleRingTimeout(info.callId);\n if (bridge) await bridge.onCallCreated(db, info);\n lifecycle.remember(info);\n\n await this.hub.send(userId, callPayload(\"call_initiated\", info, { status: \"calling\", ...callerView(info) }));\n\n const incoming = await lifecycle.incomingPayload(info);\n const delivered = await this.hub.send(receiverId, incoming);\n if (delivered) {\n await this.hub.send(userId, callPayload(\"call_ringing\", info, { status: \"ringing\", ...callerView(info) }));\n } else {\n // Push only when no socket got it: a connected app rings by itself and a push on top would ring twice.\n this.hub.spawn(lifecycle.pushIncoming(info, incoming.caller_name, incoming.caller_avatar));\n }\n logger.info(`Call ${info.callId} initiated ${userId} -> ${receiverId}`);\n }\n\n // -------------------------------------------------------------- answer\n\n async answerCall(userId: string, message: Record<string, any>, db: Db): Promise<void> {\n if (!pyBool(message.call_id)) {\n await this.error(userId, \"Missing required fields: call_id\");\n return;\n }\n const callId = pyStr(message.call_id);\n const lifecycle = this.lifecycle;\n const info = await lifecycle.service(db).markAnswered(callId, userId);\n if (!info) {\n const call = await lifecycle.loadCallFor(db, callId, userId);\n if (!call) {\n await this.error(userId, \"Call not found\", { call_id: callId });\n return;\n }\n if (call.status === \"answered\") return; // duplicate answer (double tap / second socket)\n // Cancelled or timed out meanwhile: stop ringing on every socket.\n await this.hub.send(\n userId,\n callPayload(\"call_ended\", call, { status: \"call_ended\", end_reason: call.status, duration_seconds: call.duration }),\n );\n return;\n }\n lifecycle.remember(info);\n const payload = callPayload(\"call_answered\", info, { status: \"answered\", answerer_id: userId });\n await this.hub.send(info.callerId, payload);\n // Also the answerer: stops ringing on their other devices (\"answered elsewhere\").\n await this.hub.send(userId, payload);\n }\n\n // ------------------------------------------------------- reject / end\n\n async endCall(userId: string, message: Record<string, any>, db: Db): Promise<void> {\n if (!pyBool(message.call_id)) {\n await this.error(userId, \"Missing required fields: call_id\");\n return;\n }\n const call = await this.lifecycle.loadCallFor(db, pyStr(message.call_id), userId);\n if (!call) {\n await this.error(userId, \"Call not found\", { call_id: message.call_id });\n return;\n }\n await this.lifecycle.finishCall(db, call, userId);\n }\n\n // ---------------------------------------------------------- signalling\n\n private async relaySdp(userId: string, message: Record<string, any>, db: Db, event: string): Promise<void> {\n if (!pyBool(message.call_id) || !pyBool(message.sdp)) {\n await this.error(userId, \"Missing required fields: sdp, call_id\");\n return;\n }\n const callId = pyStr(message.call_id);\n const sdp = message.sdp;\n const call = await this.lifecycle.loadCallFor(db, callId, userId);\n if (!call || !(ACTIVE_CALL_STATUSES as readonly string[]).includes(call.status)) {\n await this.error(userId, \"No active call found.\", { call_id: callId });\n return;\n }\n if (event === \"sdp_offer\" && userId === call.callerId) {\n // Kept so a receiver who connects later (e.g. from the push) still gets it.\n await this.lifecycle.service(db).saveOfferSdp(callId, JSON.stringify(sdp));\n }\n const other = call.otherParty(userId);\n if (other) await this.hub.send(other, { type: event, call_id: callId, from_user_id: userId, sdp, timestamp: nowIso() });\n }\n\n private async iceCandidate(userId: string, message: Record<string, any>, db: Db): Promise<void> {\n if (!pyBool(message.call_id) || !pyBool(message.candidate)) return;\n const callId = pyStr(message.call_id);\n let parties = this.lifecycle.parties.get(callId);\n if (!parties) {\n const call = await this.lifecycle.loadCallFor(db, callId, userId);\n if (!call) return;\n parties = [call.callerId, call.receiverId];\n }\n const [callerId, receiverId] = parties;\n if (!parties.includes(userId)) return;\n await this.hub.send(userId === callerId ? receiverId : callerId, {\n type: \"ice_candidate\",\n call_id: callId,\n from_user_id: userId,\n candidate: message.candidate,\n timestamp: nowIso(),\n });\n }\n}\n","/** Call lifecycle: finishing calls, ring timeout, disconnect grace, re-ring, pushes (port of commskit.call.lifecycle). */\nimport type { SocketLike } from \"../core/connections.js\";\nimport type { Db } from \"../core/db.js\";\nimport { logger } from \"../core/logger.js\";\nimport { LRU } from \"../core/util.js\";\nimport type { CallFeature } from \"./feature.js\";\nimport { callPayload } from \"./payloads.js\";\nimport { ACTIVE_CALL_STATUSES, type CallInfo, CallService } from \"./service.js\";\n\n/** (terminal status, end_reason) for a hangup/reject by `fromUser`. */\nexport function endStatus(call: CallInfo, fromUser: string): [string, string] {\n if (call.status === \"answered\") return [\"ended\", \"hangup\"];\n if (fromUser === call.callerId) return [\"missed\", \"caller_cancelled\"];\n return [\"rejected\", \"declined\"];\n}\n\nexport class CallLifecycle {\n /** callId -> [callerId, receiverId]: participants never change, so this per-worker cache keeps ICE off the DB. */\n readonly parties = new LRU<string, [string, string]>(5000);\n private timers = new Set<NodeJS.Timeout>();\n private stopped = false;\n\n constructor(readonly feature: CallFeature) {}\n\n private get hub() {\n return this.feature.hub;\n }\n private get config() {\n return this.feature.config;\n }\n\n service(db: Db): CallService {\n return new CallService(db, this.config, this.feature.chatBridge, this.hub.dialect);\n }\n\n remember(info: CallInfo): void {\n this.parties.put(info.callId, [info.callerId, info.receiverId]);\n }\n\n /** Emit `call.finalized` (the chat integration sends `call_log` from it). */\n async finalized(info: CallInfo): Promise<void> {\n await this.hub.events.emit(\"call.finalized\", info);\n }\n\n /** The call, only if `userId` is a participant. */\n async loadCallFor(db: Db, callId: string, userId: string): Promise<CallInfo | null> {\n const call = await this.service(db).getCallInfo(String(callId));\n if (!call || call.otherParty(userId) === null) return null;\n this.remember(call);\n return call;\n }\n\n // ----------------------------------------------------------- finishing\n\n /** Hangup / reject / cancel by `fromUser`; outcome decided from call state. */\n async finishCall(db: Db, callIn: CallInfo, fromUser: string): Promise<void> {\n const service = this.service(db);\n let call = callIn;\n let info: CallInfo | null = null;\n let reason = \"\";\n // Two attempts: if the call changed state underneath us (caller cancels as the receiver answers),\n // re-read it and end it from its real state.\n for (let i = 0; i < 2; i++) {\n if (!(ACTIVE_CALL_STATUSES as readonly string[]).includes(call.status)) break;\n let status: string;\n [status, reason] = endStatus(call, fromUser);\n info = await service.finalizeCall(call.callId, status, reason, [call.status]);\n if (info) break;\n call = (await service.getCallInfo(call.callId)) ?? call;\n }\n\n if (!info) {\n // Already over: just tell this user's sockets so their UI stops.\n await this.hub.send(\n fromUser,\n callPayload(\"call_ended\", call, {\n status: \"call_ended\",\n end_reason: call.status,\n ended_by: fromUser,\n duration_seconds: call.duration,\n caller_name: call.callerId,\n }),\n );\n return;\n }\n\n const rejected = info.status === \"rejected\";\n // Legacy: caller_name carries the caller's id in these frames.\n const payload = callPayload(rejected ? \"call_rejected\" : \"call_ended\", info, {\n status: rejected ? \"call_rejected\" : \"call_ended\",\n end_reason: reason,\n duration_seconds: info.duration,\n caller_name: info.callerId,\n ...(rejected ? { rejector_id: fromUser } : { ended_by: fromUser }),\n });\n const other = info.otherParty(fromUser);\n if (other) await this.hub.send(other, payload);\n await this.hub.send(fromUser, payload);\n await this.finalized(info);\n if (info.status === \"missed\" && fromUser === info.callerId) {\n // Caller hung up while ringing: stop the ringing on a closed app.\n this.hub.spawn(this.pushMissedCall(info));\n }\n }\n\n /**\n * End every live call between two users with `endReason` (e.g. \"blocked\"). Answered calls become `ended`,\n * ringing ones `missed`. Both parties get `call_ended`; `call.finalized` is emitted.\n */\n async endCallsBetween(userA: string, userB: string, endReason: string, endedBy: string | null = null): Promise<CallInfo[]> {\n const ended: CallInfo[] = [];\n const service = this.service(this.hub.db);\n for (const call of await service.getActiveCallsForUsers(userA)) {\n if (call.otherParty(userA) !== userB) continue;\n const toStatus = call.status === \"answered\" ? \"ended\" : \"missed\";\n const info = await service.finalizeCall(call.callId, toStatus, endReason, [call.status]);\n if (info) ended.push(info);\n }\n for (const info of ended) {\n const payload = callPayload(\"call_ended\", info, {\n status: \"call_ended\",\n end_reason: endReason,\n ended_by: endedBy ?? userA,\n duration_seconds: info.duration,\n caller_name: info.callerId,\n });\n await this.hub.send(info.callerId, payload);\n await this.hub.send(info.receiverId, payload);\n await this.finalized(info);\n if (info.status === \"missed\") this.hub.spawn(this.pushMissedCall(info));\n }\n return ended;\n }\n\n async rejectFromNotification(db: Db, callId: string, userId: string): Promise<boolean> {\n const call = await this.loadCallFor(db, callId, userId);\n if (!call) return false;\n await this.finishCall(db, call, userId);\n return true;\n }\n\n // -------------------------------------------------------------- timers\n\n private later(seconds: number, fn: () => Promise<void>): void {\n if (this.stopped) return;\n const t = setTimeout(() => {\n this.timers.delete(t);\n this.hub.spawn(fn());\n }, seconds * 1000);\n t.unref?.();\n this.timers.add(t);\n }\n\n /** Cancel every pending ring-timeout / disconnect-grace timer (hub shutdown). */\n cancelAll(): void {\n this.stopped = true;\n for (const t of this.timers) clearTimeout(t);\n this.timers.clear();\n }\n\n scheduleRingTimeout(callId: string): void {\n this.later(this.config.ringTimeout, () => this.expireUnanswered(callId));\n }\n\n /** Ring timeout: ringing -> missed (no_answer). */\n async expireUnanswered(callId: string): Promise<void> {\n try {\n const info = await this.service(this.hub.db).finalizeCall(callId, \"missed\", \"no_answer\", [\"ringing\"]);\n if (!info) return;\n const payload = callPayload(\"call_missed\", info, { status: \"missed\", end_reason: \"no_answer\" });\n await this.hub.send(info.callerId, payload);\n await this.hub.send(info.receiverId, payload);\n await this.finalized(info);\n await this.pushMissedCall(info);\n } catch (err) {\n logger.error(`Ring timeout handling failed for ${callId}`, err);\n }\n }\n\n private async endUserCalls(userId: string, fromStatus: string, toStatus: string): Promise<void> {\n const ended: CallInfo[] = [];\n const service = this.service(this.hub.db);\n for (const call of await service.getActiveCallsForUsers(userId)) {\n if (call.status !== fromStatus) continue;\n const info = await service.finalizeCall(call.callId, toStatus, \"disconnected\", [fromStatus]);\n if (info) ended.push(info);\n }\n for (const info of ended) {\n const other = info.otherParty(userId);\n if (other) {\n await this.hub.send(\n other,\n callPayload(\"call_ended\", info, {\n status: \"call_ended\",\n ended_by: userId,\n end_reason: \"disconnected\",\n duration_seconds: info.duration,\n }),\n );\n }\n await this.finalized(info);\n }\n }\n\n /** Offline hook: after a grace period end calls the user is still in (answered ones get longer). */\n endCallsAfterDisconnect(userId: string): void {\n const cfg = this.config;\n this.later(cfg.ringingDisconnectGrace, async () => {\n try {\n if (await this.hub.connections.isConnected(userId)) return;\n await this.endUserCalls(userId, \"ringing\", \"missed\");\n this.later(Math.max(0, cfg.answeredDisconnectGrace - cfg.ringingDisconnectGrace), async () => {\n try {\n if (await this.hub.connections.isConnected(userId)) return;\n await this.endUserCalls(userId, \"answered\", \"ended\");\n } catch (err) {\n logger.error(`Ending calls for disconnected ${userId} failed`, err);\n }\n });\n } catch (err) {\n logger.error(`Ending calls for disconnected ${userId} failed`, err);\n }\n });\n }\n\n // ------------------------------------------------------------- connect\n\n /** Re-ring a call still waiting for this user, on the new socket only, with the stored offer SDP. */\n async onConnect(userId: string, ws: SocketLike): Promise<void> {\n const service = this.service(this.hub.db);\n const call = await service.getActiveCallForUser(userId);\n if (!call || call.status !== \"ringing\" || call.receiverId !== userId) return;\n const offerSdp = await service.getOfferSdp(call.callId);\n this.remember(call);\n const payload = await this.incomingPayload(call);\n if (offerSdp) {\n try {\n payload.sdp = JSON.parse(offerSdp);\n } catch {\n /* keep the frame without sdp */\n }\n }\n await this.hub.connections.sendToSocket(ws, payload);\n }\n\n async incomingPayload(info: CallInfo): Promise<Record<string, any>> {\n const profile = await this.hub.profile(info.callerId);\n return callPayload(\"incoming_call\", info, {\n caller_name: profile?.name ? profile.name : \"Someone\",\n caller_avatar: profile ? (profile.avatar ?? null) : null,\n });\n }\n\n // --------------------------------------------------------------- push\n\n async pushIncoming(info: CallInfo, callerName: string, callerAvatar: string | null): Promise<void> {\n await this.hub.push(\n info.receiverId,\n callerName,\n `Incoming ${info.callType} call`,\n {\n type: \"incoming_call\",\n call_id: info.callId,\n chat_id: info.chatId,\n caller_id: info.callerId,\n caller_name: callerName,\n caller_avatar: callerAvatar,\n call_type: info.callType,\n },\n this.config.ringtoneChannel,\n Math.trunc(this.config.ringTimeout),\n );\n }\n\n /** Receiver's app is not in the foreground: leave a missed-call notification (also stops a ringing one). */\n async pushMissedCall(info: CallInfo): Promise<void> {\n if (await this.hub.connections.isOnline(info.receiverId)) return;\n const callerName = await this.hub.displayName(info.callerId);\n await this.hub.push(\n info.receiverId,\n callerName,\n `Missed ${info.callType} call`,\n { type: \"missed_call\", call_id: info.callId, chat_id: info.chatId, caller_id: info.callerId, call_type: info.callType },\n this.config.missedChannel,\n );\n }\n}\n","/** CallPolicy: host gate for starting a call (blocking, credits...). */\nimport type { Db } from \"../core/db.js\";\n\nexport interface CallDecision {\n allowed: boolean;\n /** On deny, sent as {\"type\":\"error\", ...error, \"context\":\"call\"}; e.g. {error, status: 402, message, info}. */\n error?: Record<string, unknown> | null;\n}\n\nexport const allow = (): CallDecision => ({ allowed: true });\nexport const deny = (error: string, fields: Record<string, unknown> = {}): CallDecision => ({\n allowed: false,\n error: { error, ...fields },\n});\n\nexport interface CallPolicy {\n /**\n * Runs after busy detection, right before the call row is created. `message` is the raw `call_initiate`\n * frame (host fields such as `credit_acknowledged` are available). Charging here is safe: busy receivers\n * never reach this point.\n */\n authorize(db: Db, callerId: string, receiverId: string, message: Record<string, any>): Promise<CallDecision> | CallDecision;\n}\n\nexport class AllowAllCallPolicy implements CallPolicy {\n authorize(): CallDecision {\n return allow();\n }\n}\n","/** TURN REST-API credentials (coturn `use-auth-secret` scheme). */\nimport { createHmac } from \"node:crypto\";\n\nexport interface TurnCredentials {\n username: string;\n password: string;\n ttl: number;\n uris: string[];\n}\n\n/** username = \"<expiry>[:<userId>]\", password = base64(HMAC-SHA1(secret, username)). */\nexport function turnCredentials(\n secret: string,\n uris: readonly string[],\n ttl = 3600,\n userId: string | null = null,\n now: number | null = null,\n): TurnCredentials {\n const expiry = Math.trunc(now ?? Date.now() / 1000) + ttl;\n const username = userId ? `${expiry}:${userId}` : String(expiry);\n return {\n username,\n password: createHmac(\"sha1\", secret).update(username).digest(\"base64\"),\n ttl,\n uris: [...uris],\n };\n}\n","/** Call REST routes: notification decline and TURN credentials. */\nimport { HttpError, type RestRoute, ok } from \"../core/rest.js\";\nimport type { CallFeature } from \"./feature.js\";\nimport { turnCredentials } from \"./turn.js\";\n\nexport function buildCallRoutes(feature: CallFeature): RestRoute[] {\n const hub = feature.hub;\n return [\n {\n method: \"POST\",\n path: \"/call/turn-credentials\",\n handler() {\n const cfg = feature.config;\n if (!cfg.turnSecret) throw new HttpError(503, \"TURN is not configured\");\n return ok(turnCredentials(cfg.turnSecret, cfg.turnUris, cfg.turnTtl));\n },\n },\n {\n // Decline from the call notification while the app has no socket.\n method: \"POST\",\n path: \"/call/:callId/reject\",\n async handler(ctx) {\n const found = await feature.lifecycle.rejectFromNotification(hub.db, ctx.params.callId!, ctx.userId!);\n if (!found) return { status: 404, body: { status: false, message: \"Call not found\" } };\n return ok({ status: true });\n },\n },\n ];\n}\n","/** `hub.install(new CallFeature({...}))` (port of commskit.call.feature). */\nimport type { CommsHub, Feature } from \"../core/hub.js\";\nimport type { CallChatBridge } from \"./bridge.js\";\nimport { type CallConfig, defaultCallConfig } from \"./config.js\";\nimport { CallHandlers } from \"./handlers.js\";\nimport { CallLifecycle } from \"./lifecycle.js\";\nimport { AllowAllCallPolicy, type CallPolicy } from \"./policy.js\";\nimport { buildCallRoutes } from \"./router.js\";\nimport type { CallInfo } from \"./service.js\";\n\nexport interface CallFeatureOptions {\n policy?: CallPolicy;\n config?: Partial<CallConfig>;\n}\n\n/**\n * P2P WebRTC call signalling. Works standalone (chat_id null, no call_log); `linkChatAndCall` (integration)\n * sets `chatBridge`.\n */\nexport class CallFeature implements Feature {\n readonly name = \"call\";\n policy: CallPolicy;\n config: CallConfig;\n chatBridge: CallChatBridge | null = null;\n lifecycle!: CallLifecycle;\n handlers!: CallHandlers;\n private _hub: CommsHub | null = null;\n\n constructor(opts: CallFeatureOptions = {}) {\n this.policy = opts.policy ?? new AllowAllCallPolicy();\n this.config = { ...defaultCallConfig(), ...(opts.config ?? {}) };\n }\n\n get hub(): CommsHub {\n if (!this._hub) throw new Error(\"CallFeature is not installed; call hub.install(feature) first\");\n return this._hub;\n }\n\n install(hub: CommsHub): this {\n hub.registerFeature(this.name, this);\n this._hub = hub;\n this.lifecycle = new CallLifecycle(this);\n this.handlers = new CallHandlers(this);\n this.handlers.register();\n hub.onConnect((uid, ws) => this.lifecycle.onConnect(uid, ws));\n hub.onDisconnect((uid) => this.lifecycle.endCallsAfterDisconnect(uid));\n hub.includeRoutes(buildCallRoutes(this));\n // Pending ring / grace timers must not outlive the hub.\n const shutdown = hub.shutdown.bind(hub);\n hub.shutdown = async () => {\n this.lifecycle.cancelAll();\n await shutdown();\n };\n return this;\n }\n\n /** End every live call between two users (e.g. on block). Both get `call_ended`; `call.finalized` is emitted. */\n endCallsBetween(userA: string, userB: string, endReason: string, endedBy: string | null = null): Promise<CallInfo[]> {\n return this.lifecycle.endCallsBetween(userA, userB, endReason, endedBy);\n }\n\n /**\n * Account deletion: remove every calls row where the user is caller or receiver. Live calls are not\n * signalled; disconnect the user first (or end their calls) if needed.\n */\n async deleteUserData(userId: string): Promise<{ calls: number }> {\n const uid = String(userId);\n const res = await this.hub.db\n .deleteFrom(\"calls\")\n .where((eb: any) => eb.or([eb(\"caller_id\", \"=\", uid), eb(\"receiver_id\", \"=\", uid)]))\n .executeTakeFirst();\n return { calls: Number(res.numDeletedRows) };\n }\n}\n","/** UUIDv5 and the CallKit UUID of a call (matches Python `uuid.uuid5` and the mobile kit). */\nimport { createHash } from \"node:crypto\";\n\nconst NAMESPACE_URL = \"6ba7b811-9dad-11d1-80b4-00c04fd430c8\";\n\nexport function uuidv5(name: string, namespace: string = NAMESPACE_URL): string {\n const ns = Buffer.from(namespace.replace(/-/g, \"\"), \"hex\");\n const h = createHash(\"sha1\").update(ns).update(Buffer.from(name, \"utf8\")).digest().subarray(0, 16);\n h[6] = (h[6]! & 0x0f) | 0x50;\n h[8] = (h[8]! & 0x3f) | 0x80;\n const x = h.toString(\"hex\");\n return `${x.slice(0, 8)}-${x.slice(8, 12)}-${x.slice(12, 16)}-${x.slice(16, 20)}-${x.slice(20)}`;\n}\n\n/** CallKit UUID for a call: UUIDv5(NAMESPACE_URL, \"commskit:call:<callId>\"). */\nexport const callkitUuid = (callId: string): string => uuidv5(`commskit:call:${callId}`);\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import {
|
|
2
|
+
logger
|
|
3
|
+
} from "./chunk-5BJ2SNM5.js";
|
|
4
|
+
|
|
5
|
+
// src/core/util.ts
|
|
6
|
+
var pad = (n, w = 2) => String(n).padStart(w, "0");
|
|
7
|
+
function formatIso(d) {
|
|
8
|
+
const ms = d.getUTCMilliseconds();
|
|
9
|
+
const base = `${d.getUTCFullYear()}-${pad(d.getUTCMonth() + 1)}-${pad(d.getUTCDate())}T${pad(d.getUTCHours())}:${pad(d.getUTCMinutes())}:${pad(d.getUTCSeconds())}`;
|
|
10
|
+
return ms ? `${base}.${pad(ms, 3)}000` : base;
|
|
11
|
+
}
|
|
12
|
+
function utcnow() {
|
|
13
|
+
return /* @__PURE__ */ new Date();
|
|
14
|
+
}
|
|
15
|
+
function nowIso() {
|
|
16
|
+
return formatIso(utcnow());
|
|
17
|
+
}
|
|
18
|
+
function iso(value) {
|
|
19
|
+
if (value === null || value === void 0) return null;
|
|
20
|
+
if (value instanceof Date) return formatIso(value);
|
|
21
|
+
if (typeof value === "number") return formatIso(new Date(value));
|
|
22
|
+
let s = String(value).trim().replace(" ", "T");
|
|
23
|
+
s = s.replace(/(Z|[+-]00:?00)$/i, "");
|
|
24
|
+
const m = /^(\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2})(?:\.(\d+))?$/.exec(s);
|
|
25
|
+
if (!m) return s;
|
|
26
|
+
const frac = (m[2] ?? "").padEnd(6, "0").slice(0, 6);
|
|
27
|
+
return /^0*$/.test(frac) ? m[1] : `${m[1]}.${frac}`;
|
|
28
|
+
}
|
|
29
|
+
function parseDbDate(value) {
|
|
30
|
+
if (value === null || value === void 0) return null;
|
|
31
|
+
if (value instanceof Date) return value;
|
|
32
|
+
let s = String(value).trim().replace(" ", "T");
|
|
33
|
+
if (!/(Z|[+-]\d{2}:?\d{2})$/i.test(s)) s += "Z";
|
|
34
|
+
const d = new Date(s);
|
|
35
|
+
return Number.isNaN(d.getTime()) ? null : d;
|
|
36
|
+
}
|
|
37
|
+
function pyBool(v) {
|
|
38
|
+
if (v === null || v === void 0 || v === false || v === 0 || v === "") return false;
|
|
39
|
+
if (Array.isArray(v)) return v.length > 0;
|
|
40
|
+
if (typeof v === "object") return Object.keys(v).length > 0;
|
|
41
|
+
return true;
|
|
42
|
+
}
|
|
43
|
+
function pyStr(v) {
|
|
44
|
+
if (typeof v === "string") return v;
|
|
45
|
+
if (v === true) return "True";
|
|
46
|
+
if (v === false) return "False";
|
|
47
|
+
if (v === null || v === void 0) return "None";
|
|
48
|
+
if (typeof v === "object") return JSON.stringify(v);
|
|
49
|
+
return String(v);
|
|
50
|
+
}
|
|
51
|
+
var codePointLength = (s) => Array.from(s).length;
|
|
52
|
+
var truncateCodePoints = (s, n) => Array.from(s).slice(0, n).join("");
|
|
53
|
+
var LRU = class {
|
|
54
|
+
constructor(limit) {
|
|
55
|
+
this.limit = limit;
|
|
56
|
+
}
|
|
57
|
+
limit;
|
|
58
|
+
data = /* @__PURE__ */ new Map();
|
|
59
|
+
get(key) {
|
|
60
|
+
return this.data.get(key);
|
|
61
|
+
}
|
|
62
|
+
put(key, value) {
|
|
63
|
+
this.data.delete(key);
|
|
64
|
+
this.data.set(key, value);
|
|
65
|
+
while (this.data.size > this.limit) this.data.delete(this.data.keys().next().value);
|
|
66
|
+
}
|
|
67
|
+
pop(key) {
|
|
68
|
+
const v = this.data.get(key);
|
|
69
|
+
this.data.delete(key);
|
|
70
|
+
return v;
|
|
71
|
+
}
|
|
72
|
+
clear() {
|
|
73
|
+
this.data.clear();
|
|
74
|
+
}
|
|
75
|
+
discardWhere(predicate) {
|
|
76
|
+
for (const k of [...this.data.keys()]) if (predicate(k)) this.data.delete(k);
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
var TaskSet = class {
|
|
80
|
+
tasks = /* @__PURE__ */ new Set();
|
|
81
|
+
spawn(task) {
|
|
82
|
+
const p = (typeof task === "function" ? Promise.resolve().then(task) : task).catch(
|
|
83
|
+
(err) => logger.error("Background task failed", err)
|
|
84
|
+
);
|
|
85
|
+
this.tasks.add(p);
|
|
86
|
+
void p.finally(() => this.tasks.delete(p));
|
|
87
|
+
}
|
|
88
|
+
async settle(timeoutMs = 2e3) {
|
|
89
|
+
if (!this.tasks.size) return;
|
|
90
|
+
await Promise.race([Promise.allSettled([...this.tasks]), new Promise((r) => setTimeout(r, timeoutMs).unref())]);
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
export {
|
|
95
|
+
formatIso,
|
|
96
|
+
utcnow,
|
|
97
|
+
nowIso,
|
|
98
|
+
iso,
|
|
99
|
+
parseDbDate,
|
|
100
|
+
pyBool,
|
|
101
|
+
pyStr,
|
|
102
|
+
codePointLength,
|
|
103
|
+
truncateCodePoints,
|
|
104
|
+
LRU,
|
|
105
|
+
TaskSet
|
|
106
|
+
};
|
|
107
|
+
//# sourceMappingURL=chunk-KLKRK6OG.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/core/util.ts"],"sourcesContent":["/** Small shared helpers (port of commskit.core.util). */\nimport { logger } from \"./logger.js\";\n\nconst pad = (n: number, w = 2): string => String(n).padStart(w, \"0\");\n\n/** Python-style naive UTC isoformat: no offset suffix, fraction only when non-zero (6 digits). */\nexport function formatIso(d: Date): string {\n const ms = d.getUTCMilliseconds();\n const base =\n `${d.getUTCFullYear()}-${pad(d.getUTCMonth() + 1)}-${pad(d.getUTCDate())}` +\n `T${pad(d.getUTCHours())}:${pad(d.getUTCMinutes())}:${pad(d.getUTCSeconds())}`;\n return ms ? `${base}.${pad(ms, 3)}000` : base;\n}\n\nexport function utcnow(): Date {\n return new Date();\n}\n\nexport function nowIso(): string {\n return formatIso(utcnow());\n}\n\n/**\n * ISO string for a value read from the database: a Date (pg), or a string\n * (\"YYYY-MM-DD HH:MM:SS[.ffffff]\" from sqlite, or an ISO string). Mirrors Python's `iso()`.\n */\nexport function iso(value: unknown): string | null {\n if (value === null || value === undefined) return null;\n if (value instanceof Date) return formatIso(value);\n if (typeof value === \"number\") return formatIso(new Date(value));\n let s = String(value).trim().replace(\" \", \"T\");\n s = s.replace(/(Z|[+-]00:?00)$/i, \"\");\n const m = /^(\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2})(?:\\.(\\d+))?$/.exec(s);\n if (!m) return s;\n const frac = (m[2] ?? \"\").padEnd(6, \"0\").slice(0, 6);\n return /^0*$/.test(frac) ? m[1]! : `${m[1]}.${frac}`;\n}\n\n/** Parse a database timestamp (naive = UTC) to a Date. */\nexport function parseDbDate(value: unknown): Date | null {\n if (value === null || value === undefined) return null;\n if (value instanceof Date) return value;\n let s = String(value).trim().replace(\" \", \"T\");\n if (!/(Z|[+-]\\d{2}:?\\d{2})$/i.test(s)) s += \"Z\";\n const d = new Date(s);\n return Number.isNaN(d.getTime()) ? null : d;\n}\n\n/** Python truthiness for JSON values (None, False, 0, \"\", [], {} are falsy). */\nexport function pyBool(v: unknown): boolean {\n if (v === null || v === undefined || v === false || v === 0 || v === \"\") return false;\n if (Array.isArray(v)) return v.length > 0;\n if (typeof v === \"object\") return Object.keys(v as object).length > 0;\n return true;\n}\n\n/** Python `str()` for the JSON scalars the protocol carries. */\nexport function pyStr(v: unknown): string {\n if (typeof v === \"string\") return v;\n if (v === true) return \"True\";\n if (v === false) return \"False\";\n if (v === null || v === undefined) return \"None\";\n if (typeof v === \"object\") return JSON.stringify(v);\n return String(v);\n}\n\n/** Python len()/slicing work on code points; JS strings are UTF-16. */\nexport const codePointLength = (s: string): number => Array.from(s).length;\nexport const truncateCodePoints = (s: string, n: number): string => Array.from(s).slice(0, n).join(\"\");\n\n/** Bounded map; oldest entries are evicted first. */\nexport class LRU<K, V> {\n private data = new Map<K, V>();\n constructor(private limit: number) {}\n get(key: K): V | undefined {\n return this.data.get(key);\n }\n put(key: K, value: V): void {\n this.data.delete(key);\n this.data.set(key, value);\n while (this.data.size > this.limit) this.data.delete(this.data.keys().next().value as K);\n }\n pop(key: K): V | undefined {\n const v = this.data.get(key);\n this.data.delete(key);\n return v;\n }\n clear(): void {\n this.data.clear();\n }\n discardWhere(predicate: (key: K) => boolean): void {\n for (const k of [...this.data.keys()]) if (predicate(k)) this.data.delete(k);\n }\n}\n\n/** Keeps track of fire-and-forget promises so shutdown can wait for them. */\nexport class TaskSet {\n private tasks = new Set<Promise<unknown>>();\n\n spawn(task: Promise<unknown> | (() => Promise<unknown>)): void {\n const p = (typeof task === \"function\" ? Promise.resolve().then(task) : task).catch((err) =>\n logger.error(\"Background task failed\", err),\n );\n this.tasks.add(p);\n void p.finally(() => this.tasks.delete(p));\n }\n\n async settle(timeoutMs = 2000): Promise<void> {\n if (!this.tasks.size) return;\n await Promise.race([Promise.allSettled([...this.tasks]), new Promise((r) => setTimeout(r, timeoutMs).unref())]);\n }\n}\n"],"mappings":"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","names":[]}
|