@rizvanua/contact-chat 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.
Files changed (47) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +496 -0
  3. package/dist/index.cjs +704 -0
  4. package/dist/index.cjs.map +1 -0
  5. package/dist/index.d.cts +4 -0
  6. package/dist/index.d.ts +4 -0
  7. package/dist/index.js +688 -0
  8. package/dist/index.js.map +1 -0
  9. package/dist/react/index.cjs +326 -0
  10. package/dist/react/index.cjs.map +1 -0
  11. package/dist/react/index.d.cts +118 -0
  12. package/dist/react/index.d.ts +118 -0
  13. package/dist/react/index.js +306 -0
  14. package/dist/react/index.js.map +1 -0
  15. package/dist/server/index.cjs +704 -0
  16. package/dist/server/index.cjs.map +1 -0
  17. package/dist/server/index.d.cts +98 -0
  18. package/dist/server/index.d.ts +98 -0
  19. package/dist/server/index.js +688 -0
  20. package/dist/server/index.js.map +1 -0
  21. package/dist/server/nextjs.cjs +10 -0
  22. package/dist/server/nextjs.cjs.map +1 -0
  23. package/dist/server/nextjs.d.cts +17 -0
  24. package/dist/server/nextjs.d.ts +17 -0
  25. package/dist/server/nextjs.js +8 -0
  26. package/dist/server/nextjs.js.map +1 -0
  27. package/dist/stores/index.cjs +244 -0
  28. package/dist/stores/index.cjs.map +1 -0
  29. package/dist/stores/index.d.cts +58 -0
  30. package/dist/stores/index.d.ts +58 -0
  31. package/dist/stores/index.js +241 -0
  32. package/dist/stores/index.js.map +1 -0
  33. package/dist/transports/index.cjs +156 -0
  34. package/dist/transports/index.cjs.map +1 -0
  35. package/dist/transports/index.d.cts +48 -0
  36. package/dist/transports/index.d.ts +48 -0
  37. package/dist/transports/index.js +151 -0
  38. package/dist/transports/index.js.map +1 -0
  39. package/dist/types-BcoqxSLg.d.cts +42 -0
  40. package/dist/types-BcoqxSLg.d.ts +42 -0
  41. package/dist/ui/index.cjs +609 -0
  42. package/dist/ui/index.cjs.map +1 -0
  43. package/dist/ui/index.d.cts +127 -0
  44. package/dist/ui/index.d.ts +127 -0
  45. package/dist/ui/index.js +604 -0
  46. package/dist/ui/index.js.map +1 -0
  47. package/package.json +146 -0
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/core/types.ts","../../src/core/config.ts","../../src/core/ratelimit.ts","../../src/core/name.ts","../../src/core/handlers.ts","../../src/core/index.ts","../../src/stores/upstash-redis.ts","../../src/stores/memory.ts","../../src/transports/telegram.ts"],"names":["createHash","redis","Redis","ttlSeconds"],"mappings":"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","file":"index.cjs","sourcesContent":["export type ChatAuthor = 'visitor' | 'owner';\n\nexport interface ChatMessage {\n id: string;\n from: ChatAuthor;\n text: string;\n ts: number;\n}\n\nexport interface ChatSession {\n topicId: number;\n createdAt: number;\n msgCount: number;\n // Optional because sessions created before the name gate shipped have none.\n name?: string;\n}\n\nexport interface SendRequestBody {\n sessionId: string;\n text: string;\n name?: string;\n honeypot?: string;\n}\n\nexport interface SendResponseBody {\n message: ChatMessage;\n cursor: number;\n}\n\nexport interface PollResponseBody {\n messages: ChatMessage[];\n cursor: number;\n}\n\nexport type ChatErrorCode =\n | 'validation'\n | 'rate_limited'\n | 'store_unavailable'\n | 'telegram_unavailable';\n\nexport interface ChatErrorBody {\n code: ChatErrorCode;\n error: string;\n}\n\n// @deprecated Use DEFAULT_CHAT_LIMITS.maxMessageLength instead.\nexport const MAX_MESSAGE_LENGTH = 1000;\n// @deprecated Use DEFAULT_CHAT_LIMITS.maxMessagesPerSession instead.\nexport const MAX_MESSAGES_PER_SESSION = 40;\n// @deprecated Use DEFAULT_CHAT_LIMITS.sessionTtlSeconds instead.\nexport const SESSION_TTL_SECONDS = 604800; // 7 days\n// @deprecated Use DEFAULT_CHAT_LIMITS.ratePerMinute instead.\nexport const RATE_PER_MINUTE = 5;\n// @deprecated Use DEFAULT_CHAT_LIMITS.ratePerDay instead.\nexport const RATE_PER_DAY = 30;\n\nexport interface ChatLimits {\n maxMessageLength: number;\n maxMessagesPerSession: number;\n sessionTtlSeconds: number;\n ratePerMinute: number;\n ratePerDay: number;\n}\n\nexport const DEFAULT_CHAT_LIMITS: ChatLimits = {\n maxMessageLength: MAX_MESSAGE_LENGTH,\n maxMessagesPerSession: MAX_MESSAGES_PER_SESSION,\n sessionTtlSeconds: SESSION_TTL_SECONDS,\n ratePerMinute: RATE_PER_MINUTE,\n ratePerDay: RATE_PER_DAY,\n};\n","import type { ChatStore } from './store-interface.js';\nimport type { ChatTransport } from './transport-interface.js';\nimport { DEFAULT_CHAT_LIMITS } from './types.js';\nimport type { ChatLimits } from './types.js';\n\n/**\n * Visitor-facing HTTP error strings this package emits in ChatErrorBody.error fields.\n * Override individual strings via config.copy to localise or customise them.\n */\nexport interface ChatServerCopy {\n validation: {\n /** Returned when the request body cannot be parsed as JSON. */\n malformedBody: string;\n /** Returned when sessionId is missing or too short. */\n missingSession: string;\n /** Returned when the honeypot field is filled (bot detected). */\n rejected: string;\n /** Returned when the text field is missing or not a string. */\n messageRequired: string;\n /** Returned when the trimmed text is empty. */\n messageEmpty: string;\n /** Returned when the trimmed text exceeds the configured limit. Takes the resolved limit. */\n messageTooLong: (limit: number) => string;\n /** Returned when a new session is requested but no name was provided. */\n nameRequired: string;\n };\n /** Returned when the rate limit is exceeded (HTTP 429). */\n rateLimited: string;\n /** Returned when the session has reached its message limit. */\n messageLimitReached: string;\n /** Returned when the store (Redis) is unavailable (HTTP 503). */\n storeUnavailable: string;\n /** Returned when Telegram delivery fails (HTTP 502). */\n telegramUnavailable: string;\n}\n\nexport const DEFAULT_CHAT_SERVER_COPY: ChatServerCopy = {\n validation: {\n malformedBody: 'Malformed request body.',\n missingSession: 'Missing session.',\n rejected: 'Rejected.',\n messageRequired: 'Message is required.',\n messageEmpty: 'Message is empty.',\n messageTooLong: (limit: number) => `Please keep it under ${limit} characters.`,\n nameRequired: 'Please tell me your name first.',\n },\n rateLimited: 'That is a lot of messages — please wait a moment.',\n messageLimitReached: 'This conversation has reached its message limit.',\n storeUnavailable: 'Chat is unavailable right now.',\n telegramUnavailable: 'Could not deliver that message.',\n};\n\nexport type ChatServerCopyPartial = Partial<Omit<ChatServerCopy, 'validation'>> & {\n validation?: Partial<ChatServerCopy['validation']>;\n};\n\nexport interface ChatServerConfig {\n store: ChatStore;\n transport: ChatTransport;\n limits?: Partial<ChatLimits>;\n ipHashSalt: string;\n copy?: ChatServerCopyPartial;\n onError?: (scope: string, error: unknown) => void;\n}\n\nexport interface ResolvedChatServerConfig {\n store: ChatStore;\n transport: ChatTransport;\n limits: ChatLimits;\n ipHashSalt: string;\n copy: ChatServerCopy;\n onError: (scope: string, error: unknown) => void;\n}\n\nexport function resolveConfig(config: ChatServerConfig): ResolvedChatServerConfig {\n return {\n store: config.store,\n transport: config.transport,\n ipHashSalt: config.ipHashSalt,\n limits: { ...DEFAULT_CHAT_LIMITS, ...config.limits },\n copy: {\n ...DEFAULT_CHAT_SERVER_COPY,\n ...config.copy,\n validation: {\n ...DEFAULT_CHAT_SERVER_COPY.validation,\n ...(config.copy?.validation ?? {}),\n },\n },\n onError: config.onError ?? (() => {}),\n };\n}\n","import { createHash } from 'node:crypto';\nimport type { ChatStore } from './store-interface.js';\nimport type { ChatLimits } from './types.js';\n\nconst MINUTE_SECONDS = 60;\nconst DAY_SECONDS = 86400;\n\nexport function getClientIp(headers: Headers): string {\n const forwarded = headers.get('x-forwarded-for');\n if (forwarded) {\n const first = forwarded.split(',')[0]?.trim();\n if (first) return first;\n }\n return headers.get('x-real-ip')?.trim() || 'unknown';\n}\n\n/** The raw IP is never stored — only this salted digest. */\nexport function hashIp(ip: string, salt: string): string {\n return createHash('sha256').update(`${ip}:${salt}`).digest('hex').slice(0, 32);\n}\n\nexport interface RateLimitDeps {\n store: ChatStore;\n ipHashSalt: string;\n limits: Pick<ChatLimits, 'ratePerMinute' | 'ratePerDay'>;\n}\n\nexport interface RateLimitResult {\n allowed: boolean;\n retryAfterSeconds: number;\n}\n\n/**\n * INCR returns the post-increment value, so a fresh key returns 1 and EXPIRE is\n * set only on that first hit. That keeps the window fixed rather than sliding\n * forward on every request, which would make the limit unreachable.\n * The store honours this fixed-window semantic inside incrementCounter.\n */\nexport async function checkRateLimit(\n ip: string,\n deps: RateLimitDeps,\n): Promise<RateLimitResult> {\n const id = hashIp(ip, deps.ipHashSalt);\n\n // Store honours fixed-window semantics: TTL is set only on the first hit (count === 1).\n const perMinute = await deps.store.incrementCounter(`chat:rate:${id}:min`, MINUTE_SECONDS);\n if (perMinute > deps.limits.ratePerMinute) {\n return { allowed: false, retryAfterSeconds: MINUTE_SECONDS };\n }\n\n const perDay = await deps.store.incrementCounter(`chat:rate:${id}:day`, DAY_SECONDS);\n if (perDay > deps.limits.ratePerDay) {\n return { allowed: false, retryAfterSeconds: DAY_SECONDS };\n }\n\n return { allowed: true, retryAfterSeconds: 0 };\n}\n","export const MAX_NAME_LENGTH = 60;\n\n/**\n * Normalises a visitor-supplied name for display in the owner's Telegram.\n * Returns '' when nothing usable remains — emptiness is the only failure.\n *\n * Not an injection defence: Telegram does not parse topic names as markup.\n * This exists so control characters and runaway length cannot reach the\n * owner's topic list.\n */\nexport function sanitizeName(raw: unknown): string {\n if (typeof raw !== 'string') return '';\n return raw\n // Strip control characters, including newlines and tabs.\n .replace(/[\\u0000-\\u001F\\u007F]/g, ' ')\n .replace(/\\s+/g, ' ')\n .trim()\n .slice(0, MAX_NAME_LENGTH);\n}\n","import { checkRateLimit, getClientIp } from './ratelimit.js';\nimport { sanitizeName } from './name.js';\nimport type { ResolvedChatServerConfig } from './config.js';\nimport type { ChatMessage, SendResponseBody, PollResponseBody, ChatErrorBody } from './types.js';\n\n// ---------------------------------------------------------------------------\n// Private helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Wraps a transport call so its failure can be identified separately from\n * store failures. The outer catch in createSendHandler unwraps this to decide\n * between 502 (transport) and 503 (store/unexpected).\n */\nclass TransportFailure extends Error {\n constructor(readonly cause: unknown) {\n super('transport failure');\n }\n}\n\nasync function callTransport<T>(op: () => Promise<T>): Promise<T> {\n try {\n return await op();\n } catch (error) {\n throw new TransportFailure(error);\n }\n}\n\nfunction fail(\n status: number,\n body: ChatErrorBody,\n headers?: Record<string, string>,\n): Response {\n return Response.json(body, { status, headers });\n}\n\n// ---------------------------------------------------------------------------\n// createSendHandler\n// ---------------------------------------------------------------------------\n\nexport function createSendHandler(\n resolved: ResolvedChatServerConfig,\n): (request: Request) => Promise<Response> {\n const { store, transport, limits, copy, ipHashSalt, onError } = resolved;\n\n return async function sendHandler(request: Request): Promise<Response> {\n let payload: unknown;\n try {\n payload = await request.json();\n } catch {\n return fail(400, { code: 'validation', error: copy.validation.malformedBody });\n }\n\n const { sessionId, text, name, honeypot } = (payload ?? {}) as Record<\n string,\n unknown\n >;\n\n // Validation runs before Redis so bad input costs no round trips.\n if (typeof sessionId !== 'string' || sessionId.length < 8) {\n return fail(400, { code: 'validation', error: copy.validation.missingSession });\n }\n if (typeof honeypot === 'string' && honeypot.length > 0) {\n return fail(400, { code: 'validation', error: copy.validation.rejected });\n }\n if (typeof text !== 'string') {\n return fail(400, { code: 'validation', error: copy.validation.messageRequired });\n }\n\n const trimmed = text.trim();\n if (trimmed.length === 0) {\n return fail(400, { code: 'validation', error: copy.validation.messageEmpty });\n }\n if (trimmed.length > limits.maxMessageLength) {\n return fail(400, {\n code: 'validation',\n error: copy.validation.messageTooLong(limits.maxMessageLength),\n });\n }\n\n try {\n const limit = await checkRateLimit(getClientIp(request.headers), {\n store,\n ipHashSalt,\n limits,\n });\n if (!limit.allowed) {\n return fail(\n 429,\n {\n code: 'rate_limited',\n error: copy.rateLimited,\n },\n { 'Retry-After': String(limit.retryAfterSeconds) },\n );\n }\n\n let session = await store.getSession(sessionId);\n\n if (session && session.msgCount >= limits.maxMessagesPerSession) {\n return fail(400, {\n code: 'validation',\n error: copy.messageLimitReached,\n });\n }\n\n const userAgent = request.headers.get('user-agent');\n\n if (!session) {\n // The name is required only here. A returning visitor whose stored name\n // was cleared must not be rejected: their topic is already titled.\n const visitorName = sanitizeName(name);\n if (!visitorName) {\n return fail(400, {\n code: 'validation',\n error: copy.validation.nameRequired,\n });\n }\n const topicId = await callTransport(() =>\n transport.createThread({ name: visitorName, userAgent, now: new Date() }),\n );\n session = await store.createSession(sessionId, topicId, visitorName);\n } else if (!session.name) {\n // Sessions created before the name gate shipped carry no name. Adopt the\n // one now supplied and retitle the topic, rather than discarding it —\n // asking for a name and then ignoring it is worse than never asking.\n const visitorName = sanitizeName(name);\n if (visitorName) {\n const updated = await store.setSessionName(sessionId, visitorName);\n if (updated) session = updated;\n try {\n await callTransport(() =>\n transport.renameThread(session!.topicId, {\n name: visitorName,\n userAgent,\n // First contact, matching how a fresh topic is stamped.\n now: new Date(session!.createdAt),\n }),\n );\n } catch (error) {\n // Cosmetic: the name is stored either way.\n onError(\n 'chat.send.topicRename',\n error instanceof TransportFailure ? error.cause : error,\n );\n }\n }\n }\n\n try {\n await callTransport(() => transport.send(session!.topicId, trimmed));\n } catch (error) {\n // Unwrap the TransportFailure to get the inner error for isMissingThreadError.\n const inner = error instanceof TransportFailure ? error.cause : error;\n if (!transport.isMissingThreadError(inner)) throw error;\n // The owner deleted the topic, which would otherwise brick this session\n // forever. Create a replacement, remap, and retry once.\n onError(\n 'chat.send.topicRecreate',\n `topic ${session!.topicId} is gone, recreating for session ${sessionId}`,\n );\n const topicId = await callTransport(() =>\n transport.createThread({\n name: session!.name ?? 'Visitor',\n userAgent,\n now: new Date(),\n }),\n );\n session = await store.retopicSession(sessionId, session!, topicId);\n await callTransport(() => transport.send(topicId, trimmed));\n }\n\n const message: ChatMessage = {\n id: crypto.randomUUID(),\n from: 'visitor',\n text: trimmed,\n ts: Date.now(),\n };\n const cursor = await store.appendMessage(sessionId, message);\n\n const body: SendResponseBody = { message, cursor };\n return Response.json(body);\n } catch (error) {\n if (error instanceof TransportFailure) {\n // Log Telegram's own description. The visitor-facing message stays\n // generic, but without this the real cause is invisible in the logs.\n onError('chat.send.telegram', error.cause);\n return fail(502, {\n code: 'telegram_unavailable',\n error: copy.telegramUnavailable,\n });\n }\n onError(\n 'chat.send',\n error instanceof Error ? error.message : error,\n );\n return fail(503, {\n code: 'store_unavailable',\n error: copy.storeUnavailable,\n });\n }\n };\n}\n\n// ---------------------------------------------------------------------------\n// createPollHandler\n// ---------------------------------------------------------------------------\n\nexport function createPollHandler(\n resolved: ResolvedChatServerConfig,\n): (request: Request) => Promise<Response> {\n const { store, copy } = resolved;\n\n return async function pollHandler(request: Request): Promise<Response> {\n const url = new URL(request.url);\n const sessionId = url.searchParams.get('sessionId');\n const since = Number(url.searchParams.get('since') ?? '0');\n\n if (!sessionId || sessionId.length < 8) {\n const body: ChatErrorBody = {\n code: 'validation',\n error: copy.validation.missingSession,\n };\n return Response.json(body, { status: 400 });\n }\n\n try {\n // An unknown or expired session yields an empty transcript rather than a\n // 404: the visitor should see an empty panel, not an error.\n const { messages, cursor } = await store.getMessagesSince(\n sessionId,\n Number.isFinite(since) ? since : 0,\n );\n const body: PollResponseBody = { messages, cursor };\n return Response.json(body, {\n headers: { 'Cache-Control': 'no-store' },\n });\n } catch {\n const body: ChatErrorBody = {\n code: 'store_unavailable',\n error: copy.storeUnavailable,\n };\n return Response.json(body, { status: 503 });\n }\n };\n}\n\n// ---------------------------------------------------------------------------\n// createWebhookHandler\n// ---------------------------------------------------------------------------\n\nexport function createWebhookHandler(\n resolved: ResolvedChatServerConfig,\n): (request: Request) => Promise<Response> {\n const { store, transport } = resolved;\n\n const ok = () => Response.json({ ok: true });\n\n return async function webhookHandler(request: Request): Promise<Response> {\n if (!transport.verifyWebhook(request)) {\n return Response.json({ ok: false }, { status: 401 });\n }\n\n let update: unknown;\n try {\n update = await request.json();\n } catch {\n return ok();\n }\n\n const reply = transport.parseInboundReply(update);\n if (!reply) return ok();\n\n try {\n const sessionId = await store.resolveTopic(reply.threadId);\n if (!sessionId) return ok();\n\n const message: ChatMessage = {\n id: `tg-${reply.messageId}`,\n from: 'owner',\n text: reply.text,\n ts: Date.now(),\n };\n await store.appendMessage(sessionId, message);\n } catch {\n // Swallowed deliberately: any non-2xx makes Telegram retry this same\n // update indefinitely.\n }\n\n return ok();\n };\n}\n","import type { ChatServerConfig } from './config.js';\nimport { resolveConfig } from './config.js';\nimport {\n createSendHandler,\n createPollHandler,\n createWebhookHandler,\n} from './handlers.js';\n\nexport interface ChatServer {\n send: (request: Request) => Promise<Response>;\n poll: (request: Request) => Promise<Response>;\n webhook: (request: Request) => Promise<Response>;\n}\n\nexport function createChatServer(config: ChatServerConfig): ChatServer {\n const resolved = resolveConfig(config);\n return {\n send: createSendHandler(resolved),\n poll: createPollHandler(resolved),\n webhook: createWebhookHandler(resolved),\n };\n}\n","import { Redis } from '@upstash/redis';\nimport type { ChatStore } from '../core/store-interface.js';\nimport type { ChatMessage, ChatSession } from '../core/types.js';\nimport { DEFAULT_CHAT_LIMITS } from '../core/types.js';\n\nexport interface UpstashRedisStoreOptions {\n url: string;\n token: string;\n /**\n * Sliding TTL applied to session, topic-map, and message-list writes.\n * Default: 604800 (7 days).\n */\n ttlSeconds?: number;\n}\n\n/**\n * Creates a ChatStore backed by Upstash Redis.\n *\n * The consumer resolves credentials. `Redis.fromEnv()` only reads\n * `UPSTASH_REDIS_REST_URL` / `UPSTASH_REDIS_REST_TOKEN`, but the Vercel\n * Upstash marketplace provisions `KV_REST_API_URL` / `KV_REST_API_TOKEN`.\n * Consumers should fall back between the two pairs (using `||`, not `??`,\n * since `vercel env pull` can write blank entries) and pass the resolved\n * `{ url, token }` here.\n */\nexport function upstashRedisStore(opts: UpstashRedisStoreOptions): ChatStore {\n const redis = new Redis({ url: opts.url, token: opts.token });\n const ttlSeconds = opts.ttlSeconds ?? DEFAULT_CHAT_LIMITS.sessionTtlSeconds;\n\n const sessionKey = (sessionId: string) => `chat:sess:${sessionId}`;\n const topicKey = (topicId: number) => `chat:topic:${topicId}`;\n const messagesKey = (sessionId: string) => `chat:msgs:${sessionId}`;\n\n /**\n * Writes the session and its reverse topic mapping, refreshing both TTLs.\n * Called on every session touch, which is what makes the 7-day window sliding.\n */\n async function writeSession(\n sessionId: string,\n session: ChatSession,\n ): Promise<void> {\n await redis.set(sessionKey(sessionId), session, { ex: ttlSeconds });\n await redis.set(topicKey(session.topicId), sessionId, {\n ex: ttlSeconds,\n });\n }\n\n return {\n async getSession(sessionId: string): Promise<ChatSession | null> {\n const raw = await redis.get<ChatSession>(sessionKey(sessionId));\n return raw ?? null;\n },\n\n async createSession(\n sessionId: string,\n topicId: number,\n name: string,\n ): Promise<ChatSession> {\n const session: ChatSession = {\n topicId,\n createdAt: Date.now(),\n msgCount: 0,\n name,\n };\n await writeSession(sessionId, session);\n return session;\n },\n\n /**\n * Adopts a name onto an existing session. Returns null when the session has\n * expired between the caller's read and this write.\n */\n async setSessionName(\n sessionId: string,\n name: string,\n ): Promise<ChatSession | null> {\n const session = await this.getSession(sessionId);\n if (!session) return null;\n const next: ChatSession = { ...session, name };\n await writeSession(sessionId, next);\n return next;\n },\n\n /**\n * Points a session at a replacement topic, dropping the reverse mapping for\n * the old one so a recycled thread id can never resolve to a stale session.\n */\n async retopicSession(\n sessionId: string,\n session: ChatSession,\n topicId: number,\n ): Promise<ChatSession> {\n if (session.topicId !== topicId) {\n await redis.del(topicKey(session.topicId));\n }\n const next: ChatSession = { ...session, topicId };\n await writeSession(sessionId, next);\n return next;\n },\n\n async resolveTopic(topicId: number): Promise<string | null> {\n return (await redis.get<string>(topicKey(topicId))) ?? null;\n },\n\n /**\n * Appends a message and returns the new cursor (the list length).\n *\n * msgCount is incremented only for visitor messages, so the owner's replies\n * never consume the visitor's per-session message budget.\n */\n async appendMessage(\n sessionId: string,\n message: ChatMessage,\n ): Promise<number> {\n const cursor = await redis.rpush(messagesKey(sessionId), message);\n await redis.expire(messagesKey(sessionId), ttlSeconds);\n\n const session = await this.getSession(sessionId);\n if (session) {\n await writeSession(sessionId, {\n ...session,\n msgCount:\n message.from === 'visitor' ? session.msgCount + 1 : session.msgCount,\n });\n }\n\n return cursor;\n },\n\n async getMessagesSince(\n sessionId: string,\n since: number,\n ): Promise<{ messages: ChatMessage[]; cursor: number }> {\n const start = Number.isFinite(since) && since > 0 ? Math.floor(since) : 0;\n const messages = await redis.lrange<ChatMessage>(\n messagesKey(sessionId),\n start,\n -1,\n );\n return { messages, cursor: start + messages.length };\n },\n\n /**\n * INCR returns the post-increment value, so a fresh key returns 1 and EXPIRE is\n * set only on that first hit. That keeps the window fixed rather than sliding\n * forward on every request, which would make the limit unreachable.\n */\n async incrementCounter(key: string, ttlSeconds: number): Promise<number> {\n const count = await redis.incr(key);\n if (count === 1) await redis.expire(key, ttlSeconds);\n return count;\n },\n };\n}\n","import type { ChatStore } from '../core/store-interface.js';\nimport type { ChatMessage, ChatSession } from '../core/types.js';\nimport { DEFAULT_CHAT_LIMITS } from '../core/types.js';\n\nexport interface InMemoryStoreOptions {\n /**\n * Sliding TTL applied to session, topic-map, and message-list writes.\n * Default: 604800 (7 days).\n */\n ttlSeconds?: number;\n /** Injectable clock for deterministic tests. Default: Date.now. */\n now?: () => number;\n}\n\ninterface StoredSession {\n session: ChatSession;\n expiresAt: number;\n}\n\ninterface StoredMessages {\n messages: ChatMessage[];\n expiresAt: number;\n}\n\ninterface StoredCounter {\n count: number;\n expiresAt: number;\n}\n\n/** Symbol for accessing internal key names in tests. */\nexport const inMemoryStoreKeysSymbol = Symbol.for('contact-chat.inMemoryStore.keys');\n\n/**\n * Creates a deterministic in-memory ChatStore, suitable for tests and the demo app.\n *\n * Each call returns a new, fully independent store instance with no shared state.\n */\nexport function inMemoryStore(opts?: InMemoryStoreOptions): ChatStore & {\n [inMemoryStoreKeysSymbol]: () => string[];\n} {\n const ttlSeconds = opts?.ttlSeconds ?? DEFAULT_CHAT_LIMITS.sessionTtlSeconds;\n const now = opts?.now ?? Date.now;\n\n // Internal maps using the same key namespace as UpstashRedisStore.\n const sessions = new Map<string, StoredSession>();\n const topics = new Map<string, string>(); // topicKey -> sessionId\n const messages = new Map<string, StoredMessages>();\n const counters = new Map<string, StoredCounter>();\n\n const sessionKey = (sessionId: string) => `chat:sess:${sessionId}`;\n const topicKey = (topicId: number) => `chat:topic:${topicId}`;\n const messagesKey = (sessionId: string) => `chat:msgs:${sessionId}`;\n\n /**\n * Writes the session and its reverse topic mapping, refreshing both TTLs.\n * Called on every session touch, which is what makes the 7-day window sliding.\n */\n function writeSession(sessionId: string, session: ChatSession): void {\n const expiresAt = now() + ttlSeconds * 1000;\n sessions.set(sessionKey(sessionId), {\n session: structuredClone(session),\n expiresAt,\n });\n topics.set(topicKey(session.topicId), sessionId);\n }\n\n function getStoredSession(sessionId: string): ChatSession | null {\n const stored = sessions.get(sessionKey(sessionId));\n if (!stored) return null;\n if (stored.expiresAt <= now()) {\n sessions.delete(sessionKey(sessionId));\n return null;\n }\n return structuredClone(stored.session);\n }\n\n const store = {\n async getSession(sessionId: string): Promise<ChatSession | null> {\n return getStoredSession(sessionId);\n },\n\n async createSession(\n sessionId: string,\n topicId: number,\n name: string,\n ): Promise<ChatSession> {\n const session: ChatSession = {\n topicId,\n createdAt: now(),\n msgCount: 0,\n name,\n };\n writeSession(sessionId, session);\n return structuredClone(session);\n },\n\n /**\n * Adopts a name onto an existing session. Returns null when the session has\n * expired between the caller's read and this write.\n */\n async setSessionName(\n sessionId: string,\n name: string,\n ): Promise<ChatSession | null> {\n const session = getStoredSession(sessionId);\n if (!session) return null;\n const next: ChatSession = { ...session, name };\n writeSession(sessionId, next);\n return structuredClone(next);\n },\n\n /**\n * Points a session at a replacement topic, dropping the reverse mapping for\n * the old one so a recycled thread id can never resolve to a stale session.\n */\n async retopicSession(\n sessionId: string,\n session: ChatSession,\n topicId: number,\n ): Promise<ChatSession> {\n if (session.topicId !== topicId) {\n topics.delete(topicKey(session.topicId));\n }\n const next: ChatSession = { ...session, topicId };\n writeSession(sessionId, next);\n return structuredClone(next);\n },\n\n async resolveTopic(topicId: number): Promise<string | null> {\n const key = topicKey(topicId);\n const sessionId = topics.get(key);\n if (!sessionId) return null;\n // Verify the session hasn't expired.\n const session = getStoredSession(sessionId);\n if (!session) {\n topics.delete(key);\n return null;\n }\n return sessionId;\n },\n\n /**\n * Appends a message and returns the new cursor (the list length).\n *\n * msgCount is incremented only for visitor messages, so the owner's replies\n * never consume the visitor's per-session message budget.\n */\n async appendMessage(\n sessionId: string,\n message: ChatMessage,\n ): Promise<number> {\n const key = messagesKey(sessionId);\n const expiresAt = now() + ttlSeconds * 1000;\n const stored = messages.get(key);\n const existing = stored && stored.expiresAt > now() ? stored : null;\n const list: ChatMessage[] = existing ? [...existing.messages] : [];\n list.push(structuredClone(message));\n messages.set(key, { messages: list, expiresAt });\n\n const session = getStoredSession(sessionId);\n if (session) {\n writeSession(sessionId, {\n ...session,\n msgCount:\n message.from === 'visitor' ? session.msgCount + 1 : session.msgCount,\n });\n }\n\n return list.length;\n },\n\n async getMessagesSince(\n sessionId: string,\n since: number,\n ): Promise<{ messages: ChatMessage[]; cursor: number }> {\n const start = Number.isFinite(since) && since > 0 ? Math.floor(since) : 0;\n const key = messagesKey(sessionId);\n const stored = messages.get(key);\n if (!stored || stored.expiresAt <= now()) {\n return { messages: [], cursor: start };\n }\n const slice = stored.messages.slice(start);\n return { messages: structuredClone(slice), cursor: start + slice.length };\n },\n\n /**\n * INCR returns the post-increment value, so a fresh key returns 1 and EXPIRE is\n * set only on that first hit. That keeps the window fixed rather than sliding\n * forward on every request, which would make the limit unreachable.\n */\n async incrementCounter(key: string, ttlSeconds: number): Promise<number> {\n const existing = counters.get(key);\n if (!existing || existing.expiresAt <= now()) {\n counters.set(key, { count: 1, expiresAt: now() + ttlSeconds * 1000 });\n return 1;\n }\n existing.count++;\n return existing.count;\n },\n\n /** Internal accessor for tests — not part of the ChatStore interface. */\n [inMemoryStoreKeysSymbol](): string[] {\n return [\n ...sessions.keys(),\n ...topics.keys(),\n ...messages.keys(),\n ...counters.keys(),\n ];\n },\n };\n\n return store;\n}\n","import type { ChatTransport, TopicNameInput } from '../core/transport-interface.js';\n\nexport class TelegramError extends Error {\n constructor(\n message: string,\n /** Telegram's own `description`, kept so callers can classify the failure. */\n readonly description?: string\n ) {\n super(message);\n }\n}\n\n/**\n * True when Telegram rejected the send because the forum topic no longer\n * exists — typically because the owner deleted it while tidying the group.\n * The session's threadId is then permanently dead and must be replaced.\n */\nexport function isMissingThreadError(error: unknown): boolean {\n if (!(error instanceof TelegramError)) return false;\n const detail = (error.description ?? error.message).toLowerCase();\n return (\n detail.includes('thread not found') ||\n detail.includes('topic_deleted') ||\n detail.includes('topic was deleted') ||\n detail.includes('thread_not_found')\n );\n}\n\nconst API_BASE = 'https://api.telegram.org';\n\nexport function describeBrowser(userAgent: string | null): string {\n if (!userAgent) return 'Unknown browser';\n if (/edg\\//i.test(userAgent)) return 'Edge';\n if (/opr\\//i.test(userAgent)) return 'Opera';\n if (/chrome\\//i.test(userAgent)) return 'Chrome';\n if (/firefox\\//i.test(userAgent)) return 'Firefox';\n if (/safari\\//i.test(userAgent)) return 'Safari';\n return 'Unknown browser';\n}\n\n/**\n * Produces e.g. \"Roman-04-08-17-42-Chrome\".\n *\n * Date and time come from Intl with an explicit timeZone: the server runs UTC,\n * so reading Date methods directly would stamp titles three hours off Kyiv.\n */\nexport function buildTopicName(input: TopicNameInput): string {\n const parts = new Intl.DateTimeFormat('en-GB', {\n timeZone: 'Europe/Kyiv',\n day: '2-digit',\n month: '2-digit',\n hour: '2-digit',\n minute: '2-digit',\n hour12: false,\n }).formatToParts(input.now);\n\n const at = (type: Intl.DateTimeFormatPartTypes) =>\n parts.find((part) => part.type === type)?.value ?? '00';\n\n return [\n input.name,\n at('day'),\n at('month'),\n at('hour'),\n at('minute'),\n describeBrowser(input.userAgent),\n ]\n .join('-')\n .slice(0, 128);\n}\n\nexport interface TelegramTransportOptions {\n botToken: string;\n chatId: string;\n /** The transport verifies inbound webhooks by comparing this against the x-telegram-bot-api-secret-token header. */\n webhookSecret: string;\n /** Telegram's fixed six-colour palette. Default 0xcb86db (purple). */\n topicIconColor?: number;\n /** Override the default topic-name format. Default: `${name}-DD-MM-HH-MM-${browser}` in Europe/Kyiv, sliced to 128 chars. */\n buildTopicName?: (input: TopicNameInput) => string;\n /** Optional fetch override for tests / edge runtimes. Default: globalThis.fetch. */\n fetch?: typeof fetch;\n}\n\nexport function telegramTransport(opts: TelegramTransportOptions): ChatTransport {\n const {\n botToken,\n chatId,\n webhookSecret,\n topicIconColor = 0xcb86db,\n buildTopicName: buildTopicNameFn = buildTopicName,\n fetch: fetchFn = globalThis.fetch,\n } = opts;\n\n if (!botToken) {\n throw new Error('telegramTransport: botToken is required.');\n }\n if (!chatId) {\n throw new Error('telegramTransport: chatId is required.');\n }\n if (!webhookSecret) {\n throw new Error(\n 'telegramTransport: webhookSecret is required. Pass the same value you configured with Telegram via /setWebhook.',\n );\n }\n\n async function callApi<T>(\n method: string,\n body: Record<string, unknown>\n ): Promise<T> {\n const response = await fetchFn(`${API_BASE}/bot${botToken}/${method}`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(body),\n cache: 'no-store',\n });\n\n const payload = (await response.json()) as {\n ok: boolean;\n result?: T;\n description?: string;\n };\n\n if (!response.ok || !payload.ok) {\n throw new TelegramError(\n `${method} failed: ${payload.description ?? response.status}`,\n payload.description\n );\n }\n\n return payload.result as T;\n }\n\n return {\n async createThread(input: TopicNameInput): Promise<number> {\n const name = buildTopicNameFn(input);\n const result = await callApi<{ message_thread_id: number }>(\n 'createForumTopic',\n {\n chat_id: chatId,\n name: name.slice(0, 128),\n // Telegram snaps icon_color to a fixed six-colour palette and silently\n // substitutes the nearest allowed value. 0xCB86DB is the purple one;\n // an arbitrary brand hex such as 0x9333EA comes back as blue.\n icon_color: topicIconColor,\n }\n );\n return result.message_thread_id;\n },\n\n async send(threadId: number, text: string): Promise<void> {\n // No parse_mode: visitor text must never be parsed as Markdown or HTML.\n await callApi('sendMessage', {\n chat_id: chatId,\n message_thread_id: threadId,\n text,\n });\n },\n\n /**\n * Retitles an existing topic. Needed when a session created before the name\n * gate shipped supplies a name for the first time — the topic already exists,\n * so it has to be renamed rather than created.\n */\n async renameThread(threadId: number, input: TopicNameInput): Promise<void> {\n const name = buildTopicNameFn(input);\n await callApi('editForumTopic', {\n chat_id: chatId,\n message_thread_id: threadId,\n name: name.slice(0, 128),\n });\n },\n\n isMissingThreadError,\n\n /**\n * Narrows an inbound webhook update to an owner reply, or null.\n *\n * Every inbound filter lives here: not a message, sent by a bot, missing a\n * message_thread_id (i.e. posted in the group's General area rather than a\n * topic), or from an unexpected chat all return null.\n */\n parseInboundReply(\n update: unknown\n ): { threadId: number; text: string; messageId: number } | null {\n if (typeof update !== 'object' || update === null) return null;\n\n const message = (update as { message?: unknown }).message;\n if (typeof message !== 'object' || message === null) return null;\n\n const {\n message_id: messageId,\n message_thread_id: threadId,\n text,\n chat,\n from,\n } = message as {\n message_id?: unknown;\n message_thread_id?: unknown;\n text?: unknown;\n chat?: { id?: unknown };\n from?: { is_bot?: unknown };\n };\n\n if (from?.is_bot === true) return null;\n if (typeof threadId !== 'number') return null;\n if (typeof messageId !== 'number') return null;\n if (typeof text !== 'string' || text.trim().length === 0) return null;\n\n if (!chatId || String(chat?.id) !== chatId) return null;\n\n return { threadId, text, messageId };\n },\n\n verifyWebhook(request: Request): boolean {\n const header = request.headers.get('x-telegram-bot-api-secret-token');\n if (!header) return false;\n return header === webhookSecret;\n },\n };\n}\n"]}
@@ -0,0 +1,98 @@
1
+ import { b as ChatLimits } from '../types-BcoqxSLg.cjs';
2
+ export { c as ChatAuthor, d as ChatErrorBody, e as ChatErrorCode, C as ChatMessage, a as ChatSession, D as DEFAULT_CHAT_LIMITS, P as PollResponseBody, S as SendRequestBody, f as SendResponseBody } from '../types-BcoqxSLg.cjs';
3
+ import { ChatStore } from '../stores/index.cjs';
4
+ export { InMemoryStoreOptions, UpstashRedisStoreOptions, inMemoryStore, upstashRedisStore } from '../stores/index.cjs';
5
+ import { ChatTransport } from '../transports/index.cjs';
6
+ export { TelegramError, TelegramTransportOptions, TopicNameInput, buildTopicName, describeBrowser, telegramTransport } from '../transports/index.cjs';
7
+
8
+ /**
9
+ * Visitor-facing HTTP error strings this package emits in ChatErrorBody.error fields.
10
+ * Override individual strings via config.copy to localise or customise them.
11
+ */
12
+ interface ChatServerCopy {
13
+ validation: {
14
+ /** Returned when the request body cannot be parsed as JSON. */
15
+ malformedBody: string;
16
+ /** Returned when sessionId is missing or too short. */
17
+ missingSession: string;
18
+ /** Returned when the honeypot field is filled (bot detected). */
19
+ rejected: string;
20
+ /** Returned when the text field is missing or not a string. */
21
+ messageRequired: string;
22
+ /** Returned when the trimmed text is empty. */
23
+ messageEmpty: string;
24
+ /** Returned when the trimmed text exceeds the configured limit. Takes the resolved limit. */
25
+ messageTooLong: (limit: number) => string;
26
+ /** Returned when a new session is requested but no name was provided. */
27
+ nameRequired: string;
28
+ };
29
+ /** Returned when the rate limit is exceeded (HTTP 429). */
30
+ rateLimited: string;
31
+ /** Returned when the session has reached its message limit. */
32
+ messageLimitReached: string;
33
+ /** Returned when the store (Redis) is unavailable (HTTP 503). */
34
+ storeUnavailable: string;
35
+ /** Returned when Telegram delivery fails (HTTP 502). */
36
+ telegramUnavailable: string;
37
+ }
38
+ declare const DEFAULT_CHAT_SERVER_COPY: ChatServerCopy;
39
+ type ChatServerCopyPartial = Partial<Omit<ChatServerCopy, 'validation'>> & {
40
+ validation?: Partial<ChatServerCopy['validation']>;
41
+ };
42
+ interface ChatServerConfig {
43
+ store: ChatStore;
44
+ transport: ChatTransport;
45
+ limits?: Partial<ChatLimits>;
46
+ ipHashSalt: string;
47
+ copy?: ChatServerCopyPartial;
48
+ onError?: (scope: string, error: unknown) => void;
49
+ }
50
+ interface ResolvedChatServerConfig {
51
+ store: ChatStore;
52
+ transport: ChatTransport;
53
+ limits: ChatLimits;
54
+ ipHashSalt: string;
55
+ copy: ChatServerCopy;
56
+ onError: (scope: string, error: unknown) => void;
57
+ }
58
+ declare function resolveConfig(config: ChatServerConfig): ResolvedChatServerConfig;
59
+
60
+ interface ChatServer {
61
+ send: (request: Request) => Promise<Response>;
62
+ poll: (request: Request) => Promise<Response>;
63
+ webhook: (request: Request) => Promise<Response>;
64
+ }
65
+ declare function createChatServer(config: ChatServerConfig): ChatServer;
66
+
67
+ declare const MAX_NAME_LENGTH = 60;
68
+ /**
69
+ * Normalises a visitor-supplied name for display in the owner's Telegram.
70
+ * Returns '' when nothing usable remains — emptiness is the only failure.
71
+ *
72
+ * Not an injection defence: Telegram does not parse topic names as markup.
73
+ * This exists so control characters and runaway length cannot reach the
74
+ * owner's topic list.
75
+ */
76
+ declare function sanitizeName(raw: unknown): string;
77
+
78
+ declare function getClientIp(headers: Headers): string;
79
+ /** The raw IP is never stored — only this salted digest. */
80
+ declare function hashIp(ip: string, salt: string): string;
81
+ interface RateLimitDeps {
82
+ store: ChatStore;
83
+ ipHashSalt: string;
84
+ limits: Pick<ChatLimits, 'ratePerMinute' | 'ratePerDay'>;
85
+ }
86
+ interface RateLimitResult {
87
+ allowed: boolean;
88
+ retryAfterSeconds: number;
89
+ }
90
+ /**
91
+ * INCR returns the post-increment value, so a fresh key returns 1 and EXPIRE is
92
+ * set only on that first hit. That keeps the window fixed rather than sliding
93
+ * forward on every request, which would make the limit unreachable.
94
+ * The store honours this fixed-window semantic inside incrementCounter.
95
+ */
96
+ declare function checkRateLimit(ip: string, deps: RateLimitDeps): Promise<RateLimitResult>;
97
+
98
+ export { ChatLimits, type ChatServer, type ChatServerConfig, type ChatServerCopy, type ChatServerCopyPartial, ChatStore, ChatTransport, DEFAULT_CHAT_SERVER_COPY, MAX_NAME_LENGTH, type RateLimitDeps, type RateLimitResult, type ResolvedChatServerConfig, checkRateLimit, createChatServer, getClientIp, hashIp, resolveConfig, sanitizeName };
@@ -0,0 +1,98 @@
1
+ import { b as ChatLimits } from '../types-BcoqxSLg.js';
2
+ export { c as ChatAuthor, d as ChatErrorBody, e as ChatErrorCode, C as ChatMessage, a as ChatSession, D as DEFAULT_CHAT_LIMITS, P as PollResponseBody, S as SendRequestBody, f as SendResponseBody } from '../types-BcoqxSLg.js';
3
+ import { ChatStore } from '../stores/index.js';
4
+ export { InMemoryStoreOptions, UpstashRedisStoreOptions, inMemoryStore, upstashRedisStore } from '../stores/index.js';
5
+ import { ChatTransport } from '../transports/index.js';
6
+ export { TelegramError, TelegramTransportOptions, TopicNameInput, buildTopicName, describeBrowser, telegramTransport } from '../transports/index.js';
7
+
8
+ /**
9
+ * Visitor-facing HTTP error strings this package emits in ChatErrorBody.error fields.
10
+ * Override individual strings via config.copy to localise or customise them.
11
+ */
12
+ interface ChatServerCopy {
13
+ validation: {
14
+ /** Returned when the request body cannot be parsed as JSON. */
15
+ malformedBody: string;
16
+ /** Returned when sessionId is missing or too short. */
17
+ missingSession: string;
18
+ /** Returned when the honeypot field is filled (bot detected). */
19
+ rejected: string;
20
+ /** Returned when the text field is missing or not a string. */
21
+ messageRequired: string;
22
+ /** Returned when the trimmed text is empty. */
23
+ messageEmpty: string;
24
+ /** Returned when the trimmed text exceeds the configured limit. Takes the resolved limit. */
25
+ messageTooLong: (limit: number) => string;
26
+ /** Returned when a new session is requested but no name was provided. */
27
+ nameRequired: string;
28
+ };
29
+ /** Returned when the rate limit is exceeded (HTTP 429). */
30
+ rateLimited: string;
31
+ /** Returned when the session has reached its message limit. */
32
+ messageLimitReached: string;
33
+ /** Returned when the store (Redis) is unavailable (HTTP 503). */
34
+ storeUnavailable: string;
35
+ /** Returned when Telegram delivery fails (HTTP 502). */
36
+ telegramUnavailable: string;
37
+ }
38
+ declare const DEFAULT_CHAT_SERVER_COPY: ChatServerCopy;
39
+ type ChatServerCopyPartial = Partial<Omit<ChatServerCopy, 'validation'>> & {
40
+ validation?: Partial<ChatServerCopy['validation']>;
41
+ };
42
+ interface ChatServerConfig {
43
+ store: ChatStore;
44
+ transport: ChatTransport;
45
+ limits?: Partial<ChatLimits>;
46
+ ipHashSalt: string;
47
+ copy?: ChatServerCopyPartial;
48
+ onError?: (scope: string, error: unknown) => void;
49
+ }
50
+ interface ResolvedChatServerConfig {
51
+ store: ChatStore;
52
+ transport: ChatTransport;
53
+ limits: ChatLimits;
54
+ ipHashSalt: string;
55
+ copy: ChatServerCopy;
56
+ onError: (scope: string, error: unknown) => void;
57
+ }
58
+ declare function resolveConfig(config: ChatServerConfig): ResolvedChatServerConfig;
59
+
60
+ interface ChatServer {
61
+ send: (request: Request) => Promise<Response>;
62
+ poll: (request: Request) => Promise<Response>;
63
+ webhook: (request: Request) => Promise<Response>;
64
+ }
65
+ declare function createChatServer(config: ChatServerConfig): ChatServer;
66
+
67
+ declare const MAX_NAME_LENGTH = 60;
68
+ /**
69
+ * Normalises a visitor-supplied name for display in the owner's Telegram.
70
+ * Returns '' when nothing usable remains — emptiness is the only failure.
71
+ *
72
+ * Not an injection defence: Telegram does not parse topic names as markup.
73
+ * This exists so control characters and runaway length cannot reach the
74
+ * owner's topic list.
75
+ */
76
+ declare function sanitizeName(raw: unknown): string;
77
+
78
+ declare function getClientIp(headers: Headers): string;
79
+ /** The raw IP is never stored — only this salted digest. */
80
+ declare function hashIp(ip: string, salt: string): string;
81
+ interface RateLimitDeps {
82
+ store: ChatStore;
83
+ ipHashSalt: string;
84
+ limits: Pick<ChatLimits, 'ratePerMinute' | 'ratePerDay'>;
85
+ }
86
+ interface RateLimitResult {
87
+ allowed: boolean;
88
+ retryAfterSeconds: number;
89
+ }
90
+ /**
91
+ * INCR returns the post-increment value, so a fresh key returns 1 and EXPIRE is
92
+ * set only on that first hit. That keeps the window fixed rather than sliding
93
+ * forward on every request, which would make the limit unreachable.
94
+ * The store honours this fixed-window semantic inside incrementCounter.
95
+ */
96
+ declare function checkRateLimit(ip: string, deps: RateLimitDeps): Promise<RateLimitResult>;
97
+
98
+ export { ChatLimits, type ChatServer, type ChatServerConfig, type ChatServerCopy, type ChatServerCopyPartial, ChatStore, ChatTransport, DEFAULT_CHAT_SERVER_COPY, MAX_NAME_LENGTH, type RateLimitDeps, type RateLimitResult, type ResolvedChatServerConfig, checkRateLimit, createChatServer, getClientIp, hashIp, resolveConfig, sanitizeName };