simple-support-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.
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/client/context.ts","../../src/client/useChatEngine.ts","../../src/client/ChatBubble.tsx","../../src/client/SupportChatModal.tsx","../../src/client/useSupportChat.ts"],"names":["useState","useCallback","useRef","useEffect","jsxs","Fragment","jsx"],"mappings":"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","file":"index.js","sourcesContent":["import type { AnonymousContext } from \"./types\";\r\n\r\nconst SESSION_KEY = \"support-chat-session-id\";\r\n\r\n/** Generate a random session ID */\r\nfunction generateSessionId(): string {\r\n const chars =\r\n \"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789\";\r\n let id = \"\";\r\n for (let i = 0; i < 16; i++) {\r\n id += chars.charAt(Math.floor(Math.random() * chars.length));\r\n }\r\n return id;\r\n}\r\n\r\n/** Get or create a persistent session ID from localStorage */\r\nexport function getSessionId(): string {\r\n if (typeof window === \"undefined\") return generateSessionId();\r\n\r\n try {\r\n const existing = localStorage.getItem(SESSION_KEY);\r\n if (existing) return existing;\r\n\r\n const newId = generateSessionId();\r\n localStorage.setItem(SESSION_KEY, newId);\r\n return newId;\r\n } catch {\r\n // localStorage may be blocked (private browsing, etc.)\r\n return generateSessionId();\r\n }\r\n}\r\n\r\n/** Collect anonymous browser context */\r\nexport function collectAnonymousContext(): AnonymousContext {\r\n const sessionId = getSessionId();\r\n\r\n if (typeof window === \"undefined\") {\r\n return {\r\n pageUrl: \"\",\r\n referrer: \"\",\r\n userAgent: \"\",\r\n screenSize: \"\",\r\n timezone: \"\",\r\n sessionId,\r\n };\r\n }\r\n\r\n return {\r\n pageUrl: window.location.href,\r\n referrer: document.referrer,\r\n userAgent: navigator.userAgent,\r\n screenSize: `${window.screen.width}x${window.screen.height}`,\r\n timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,\r\n sessionId,\r\n };\r\n}\r\n","import { useState, useCallback } from \"react\";\r\nimport type { ChatMessage, ChatUser } from \"./types\";\r\nimport { collectAnonymousContext, getSessionId } from \"./context\";\r\n\r\n/** Options for initializing the chat engine */\r\nexport interface ChatEngineOptions {\r\n /** URL of the support chat API endpoint */\r\n apiUrl: string;\r\n /** Authenticated user info (optional) */\r\n user?: ChatUser;\r\n}\r\n\r\n/** Return value from useChatEngine */\r\nexport interface ChatEngineState {\r\n /** All chat messages */\r\n messages: ChatMessage[];\r\n /** Current input value */\r\n input: string;\r\n /** Update the input value */\r\n setInput: (value: string) => void;\r\n /** Whether a message is currently being sent */\r\n sending: boolean;\r\n /** Send the current input as a message */\r\n sendMessage: () => Promise<void>;\r\n /** Handle keydown on the input (Enter to send) */\r\n handleKeyDown: (e: React.KeyboardEvent) => void;\r\n}\r\n\r\n/**\r\n * Shared chat engine hook used by both ChatBubble and SupportChatModal.\r\n * Manages message state, input, and API communication.\r\n */\r\nexport function useChatEngine({\r\n apiUrl,\r\n user,\r\n}: ChatEngineOptions): ChatEngineState {\r\n const [messages, setMessages] = useState<ChatMessage[]>([]);\r\n const [input, setInput] = useState(\"\");\r\n const [sending, setSending] = useState(false);\r\n\r\n const sendMessage = useCallback(async () => {\r\n const text = input.trim();\r\n if (!text || sending) return;\r\n\r\n const msg: ChatMessage = {\r\n id: `msg-${Date.now()}`,\r\n text,\r\n sender: \"user\",\r\n timestamp: Date.now(),\r\n };\r\n\r\n setMessages((prev) => [...prev, msg]);\r\n setInput(\"\");\r\n setSending(true);\r\n\r\n try {\r\n const context = collectAnonymousContext();\r\n const response = await fetch(apiUrl, {\r\n method: \"POST\",\r\n headers: { \"Content-Type\": \"application/json\" },\r\n body: JSON.stringify({\r\n message: text,\r\n user: user ?? undefined,\r\n sessionId: user?.id ?? getSessionId(),\r\n context,\r\n }),\r\n });\r\n\r\n if (!response.ok) {\r\n setMessages((prev) => [\r\n ...prev,\r\n {\r\n id: `err-${Date.now()}`,\r\n text: \"Failed to send message. Please try again.\",\r\n sender: \"system\",\r\n timestamp: Date.now(),\r\n },\r\n ]);\r\n }\r\n } catch {\r\n setMessages((prev) => [\r\n ...prev,\r\n {\r\n id: `err-${Date.now()}`,\r\n text: \"Connection error. Please try again.\",\r\n sender: \"system\",\r\n timestamp: Date.now(),\r\n },\r\n ]);\r\n } finally {\r\n setSending(false);\r\n }\r\n }, [input, sending, apiUrl, user]);\r\n\r\n const handleKeyDown = useCallback(\r\n (e: React.KeyboardEvent) => {\r\n if (e.key === \"Enter\" && !e.shiftKey) {\r\n e.preventDefault();\r\n void sendMessage();\r\n }\r\n },\r\n [sendMessage],\r\n );\r\n\r\n return { messages, input, setInput, sending, sendMessage, handleKeyDown };\r\n}\r\n","import { useState, useCallback, useRef, useEffect } from \"react\";\r\nimport type { ChatBubbleProps } from \"./types\";\r\nimport { useChatEngine } from \"./useChatEngine\";\r\n\r\n/**\r\n * Inject a <style> tag for keyframe animations.\r\n * Called once on first mount. Uses a data-attribute to avoid duplicates.\r\n */\r\nfunction injectKeyframes(): void {\r\n if (typeof document === \"undefined\") return;\r\n if (document.querySelector(\"[data-support-chat-keyframes]\")) return;\r\n\r\n const style = document.createElement(\"style\");\r\n style.setAttribute(\"data-support-chat-keyframes\", \"\");\r\n style.textContent = `\r\n @keyframes sc-slide-in {\r\n from { opacity: 0; transform: translateY(12px) scale(0.96); }\r\n to { opacity: 1; transform: translateY(0) scale(1); }\r\n }\r\n @keyframes sc-slide-out {\r\n from { opacity: 1; transform: translateY(0) scale(1); }\r\n to { opacity: 0; transform: translateY(12px) scale(0.96); }\r\n }\r\n `;\r\n document.head.appendChild(style);\r\n}\r\n\r\n/**\r\n * ChatBubble -- floating support chat widget.\r\n *\r\n * Renders a circular button that opens an inline chat panel.\r\n * Messages are sent via POST to the configured `apiUrl`.\r\n *\r\n * @example\r\n * ```tsx\r\n * <ChatBubble apiUrl=\"/api/support\" />\r\n * ```\r\n */\r\nexport function ChatBubble({\r\n apiUrl,\r\n position = \"bottom-right\",\r\n color = \"#2563eb\",\r\n title = \"Support\",\r\n placeholder = \"Type a message...\",\r\n show = true,\r\n user,\r\n}: ChatBubbleProps) {\r\n const [isOpen, setIsOpen] = useState(false);\r\n\r\n // Use shared chat engine for message logic\r\n const { messages, input, setInput, sending, sendMessage, handleKeyDown } =\r\n useChatEngine({ apiUrl, user });\r\n\r\n // Animation state: \"open\" | \"closing\" | \"closed\"\r\n const [panelState, setPanelState] = useState<\"open\" | \"closing\" | \"closed\">(\r\n \"closed\",\r\n );\r\n\r\n const panelRef = useRef<HTMLDivElement>(null);\r\n const messagesEndRef = useRef<HTMLDivElement>(null);\r\n const inputRef = useRef<HTMLInputElement>(null);\r\n\r\n // Inject keyframe animations on first render\r\n useEffect(() => {\r\n injectKeyframes();\r\n }, []);\r\n\r\n // Sync isOpen -> panelState\r\n useEffect(() => {\r\n if (isOpen) {\r\n setPanelState(\"open\");\r\n } else if (panelState === \"open\") {\r\n // Start closing animation\r\n setPanelState(\"closing\");\r\n }\r\n }, [isOpen]);\r\n\r\n // When closing animation ends, set to fully closed\r\n const handleAnimationEnd = useCallback(() => {\r\n if (panelState === \"closing\") {\r\n setPanelState(\"closed\");\r\n }\r\n }, [panelState]);\r\n\r\n // Focus input when panel opens\r\n useEffect(() => {\r\n if (panelState === \"open\" && inputRef.current) {\r\n inputRef.current.focus();\r\n }\r\n }, [panelState]);\r\n\r\n // Auto-scroll to latest message\r\n useEffect(() => {\r\n messagesEndRef.current?.scrollIntoView({ behavior: \"smooth\" });\r\n }, [messages]);\r\n\r\n // Focus trap: keep Tab cycling inside the dialog when open\r\n useEffect(() => {\r\n if (panelState !== \"open\" || !panelRef.current) return;\r\n\r\n const panel = panelRef.current;\r\n\r\n const handleTrapKeyDown = (e: KeyboardEvent) => {\r\n if (e.key === \"Escape\") {\r\n setIsOpen(false);\r\n return;\r\n }\r\n\r\n if (e.key !== \"Tab\") return;\r\n\r\n const focusable = panel.querySelectorAll<HTMLElement>(\r\n 'button:not([disabled]), [href], input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex=\"-1\"])',\r\n );\r\n if (focusable.length === 0) return;\r\n\r\n const first = focusable[0]!;\r\n const last = focusable[focusable.length - 1]!;\r\n\r\n if (e.shiftKey) {\r\n if (document.activeElement === first) {\r\n e.preventDefault();\r\n last.focus();\r\n }\r\n } else {\r\n if (document.activeElement === last) {\r\n e.preventDefault();\r\n first.focus();\r\n }\r\n }\r\n };\r\n\r\n document.addEventListener(\"keydown\", handleTrapKeyDown);\r\n return () => document.removeEventListener(\"keydown\", handleTrapKeyDown);\r\n }, [panelState]);\r\n\r\n const toggle = useCallback(() => setIsOpen((o) => !o), []);\r\n\r\n // Position styles for the bubble button\r\n const positionStyles: React.CSSProperties = {\r\n position: \"fixed\",\r\n zIndex: 99999,\r\n ...(position.includes(\"bottom\") ? { bottom: \"20px\" } : { top: \"20px\" }),\r\n ...(position.includes(\"right\") ? { right: \"20px\" } : { left: \"20px\" }),\r\n };\r\n\r\n // Panel position & responsive sizing\r\n // On mobile (<480px viewport), expand to near-full screen\r\n const panelPositionStyles: React.CSSProperties = {\r\n position: \"fixed\",\r\n zIndex: 99999,\r\n width: \"380px\",\r\n maxWidth: \"calc(100vw - 40px)\",\r\n height: \"500px\",\r\n maxHeight: \"calc(100vh - 120px)\",\r\n ...(position.includes(\"bottom\") ? { bottom: \"80px\" } : { top: \"80px\" }),\r\n ...(position.includes(\"right\") ? { right: \"20px\" } : { left: \"20px\" }),\r\n };\r\n\r\n // Check if panel is visible (open or animating out)\r\n const showPanel = panelState === \"open\" || panelState === \"closing\";\r\n\r\n return (\r\n <>\r\n {/* Responsive overrides injected as inline <style> */}\r\n {showPanel && (\r\n <style>{`\r\n @media (max-width: 479px) {\r\n [data-support-chat-panel] {\r\n width: calc(100vw - 16px) !important;\r\n height: calc(100vh - 100px) !important;\r\n max-width: none !important;\r\n max-height: none !important;\r\n left: 8px !important;\r\n right: 8px !important;\r\n bottom: 72px !important;\r\n border-radius: 12px !important;\r\n }\r\n }\r\n `}</style>\r\n )}\r\n\r\n {/* Floating bubble button */}\r\n {show && (\r\n <button\r\n onClick={toggle}\r\n aria-label={isOpen ? \"Close support chat\" : \"Open support chat\"}\r\n aria-expanded={isOpen}\r\n aria-haspopup=\"dialog\"\r\n style={{\r\n ...positionStyles,\r\n width: \"56px\",\r\n height: \"56px\",\r\n borderRadius: \"50%\",\r\n backgroundColor: color,\r\n border: \"none\",\r\n cursor: \"pointer\",\r\n display: \"flex\",\r\n alignItems: \"center\",\r\n justifyContent: \"center\",\r\n boxShadow: \"0 4px 12px rgba(0,0,0,0.15)\",\r\n transition: \"transform 0.2s ease, box-shadow 0.2s ease\",\r\n }}\r\n onMouseEnter={(e) => {\r\n e.currentTarget.style.transform = \"scale(1.1)\";\r\n e.currentTarget.style.boxShadow = \"0 6px 20px rgba(0,0,0,0.2)\";\r\n }}\r\n onMouseLeave={(e) => {\r\n e.currentTarget.style.transform = \"scale(1)\";\r\n e.currentTarget.style.boxShadow = \"0 4px 12px rgba(0,0,0,0.15)\";\r\n }}\r\n >\r\n <svg\r\n width=\"24\"\r\n height=\"24\"\r\n viewBox=\"0 0 24 24\"\r\n fill=\"none\"\r\n stroke=\"white\"\r\n strokeWidth=\"2\"\r\n strokeLinecap=\"round\"\r\n strokeLinejoin=\"round\"\r\n aria-hidden=\"true\"\r\n >\r\n {isOpen ? (\r\n <path d=\"M18 6L6 18M6 6l12 12\" />\r\n ) : (\r\n <path d=\"M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z\" />\r\n )}\r\n </svg>\r\n </button>\r\n )}\r\n\r\n {/* Chat panel */}\r\n {showPanel && (\r\n <div\r\n ref={panelRef}\r\n role=\"dialog\"\r\n aria-label=\"Support chat\"\r\n aria-modal=\"true\"\r\n data-support-chat-panel=\"\"\r\n onAnimationEnd={handleAnimationEnd}\r\n style={{\r\n ...panelPositionStyles,\r\n display: \"flex\",\r\n flexDirection: \"column\",\r\n borderRadius: \"12px\",\r\n overflow: \"hidden\",\r\n boxShadow: \"0 8px 30px rgba(0,0,0,0.12)\",\r\n fontFamily:\r\n '-apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, sans-serif',\r\n fontSize: \"14px\",\r\n backgroundColor: \"#fff\",\r\n border: \"1px solid #e5e7eb\",\r\n animation:\r\n panelState === \"open\"\r\n ? \"sc-slide-in 0.25s ease-out forwards\"\r\n : \"sc-slide-out 0.2s ease-in forwards\",\r\n }}\r\n >\r\n {/* Header */}\r\n <div\r\n style={{\r\n backgroundColor: color,\r\n color: \"#fff\",\r\n padding: \"16px\",\r\n display: \"flex\",\r\n alignItems: \"center\",\r\n justifyContent: \"space-between\",\r\n flexShrink: 0,\r\n }}\r\n >\r\n <span style={{ fontWeight: 600, fontSize: \"16px\" }}>{title}</span>\r\n <button\r\n onClick={() => setIsOpen(false)}\r\n aria-label=\"Close chat\"\r\n style={{\r\n background: \"none\",\r\n border: \"none\",\r\n color: \"#fff\",\r\n cursor: \"pointer\",\r\n padding: \"4px\",\r\n lineHeight: 1,\r\n fontSize: \"18px\",\r\n }}\r\n >\r\n &#x2715;\r\n </button>\r\n </div>\r\n\r\n {/* Messages */}\r\n <div\r\n role=\"log\"\r\n aria-live=\"polite\"\r\n aria-label=\"Chat messages\"\r\n style={{\r\n flex: 1,\r\n overflowY: \"auto\",\r\n padding: \"16px\",\r\n display: \"flex\",\r\n flexDirection: \"column\",\r\n gap: \"8px\",\r\n }}\r\n >\r\n {messages.length === 0 && (\r\n <div\r\n style={{\r\n color: \"#9ca3af\",\r\n textAlign: \"center\",\r\n marginTop: \"40px\",\r\n }}\r\n >\r\n Send us a message and we will get back to you!\r\n </div>\r\n )}\r\n {messages.map((msg) => (\r\n <div\r\n key={msg.id}\r\n style={{\r\n alignSelf:\r\n msg.sender === \"user\" ? \"flex-end\" : \"flex-start\",\r\n maxWidth: \"80%\",\r\n padding: \"10px 14px\",\r\n borderRadius:\r\n msg.sender === \"user\"\r\n ? \"16px 16px 4px 16px\"\r\n : \"16px 16px 16px 4px\",\r\n backgroundColor:\r\n msg.sender === \"user\" ? color : \"#f3f4f6\",\r\n color: msg.sender === \"user\" ? \"#fff\" : \"#1f2937\",\r\n wordBreak: \"break-word\",\r\n }}\r\n >\r\n {msg.text}\r\n </div>\r\n ))}\r\n <div ref={messagesEndRef} />\r\n </div>\r\n\r\n {/* Input */}\r\n <div\r\n style={{\r\n borderTop: \"1px solid #e5e7eb\",\r\n padding: \"12px\",\r\n display: \"flex\",\r\n gap: \"8px\",\r\n flexShrink: 0,\r\n }}\r\n >\r\n <input\r\n ref={inputRef}\r\n type=\"text\"\r\n value={input}\r\n onChange={(e) => setInput(e.target.value)}\r\n onKeyDown={handleKeyDown}\r\n placeholder={placeholder}\r\n aria-label=\"Type your message\"\r\n style={{\r\n flex: 1,\r\n border: \"1px solid #d1d5db\",\r\n borderRadius: \"8px\",\r\n padding: \"10px 12px\",\r\n fontSize: \"14px\",\r\n outline: \"none\",\r\n fontFamily: \"inherit\",\r\n }}\r\n />\r\n <button\r\n onClick={() => void sendMessage()}\r\n disabled={sending || !input.trim()}\r\n aria-label=\"Send message\"\r\n style={{\r\n backgroundColor: color,\r\n color: \"#fff\",\r\n border: \"none\",\r\n borderRadius: \"8px\",\r\n padding: \"10px 16px\",\r\n cursor:\r\n sending || !input.trim() ? \"not-allowed\" : \"pointer\",\r\n opacity: sending || !input.trim() ? 0.5 : 1,\r\n fontWeight: 600,\r\n fontSize: \"14px\",\r\n fontFamily: \"inherit\",\r\n transition: \"opacity 0.2s ease\",\r\n }}\r\n >\r\n Send\r\n </button>\r\n </div>\r\n </div>\r\n )}\r\n </>\r\n );\r\n}\r\n","import { useRef, useEffect, useCallback } from \"react\";\r\nimport type { SupportChatModalProps } from \"./types\";\r\nimport { useChatEngine } from \"./useChatEngine\";\r\n\r\n/**\r\n * Inject a <style> tag for modal keyframe animations.\r\n * Called once on first mount. Uses a data-attribute to avoid duplicates.\r\n */\r\nfunction injectModalKeyframes(): void {\r\n if (typeof document === \"undefined\") return;\r\n if (document.querySelector(\"[data-support-chat-modal-keyframes]\")) return;\r\n\r\n const style = document.createElement(\"style\");\r\n style.setAttribute(\"data-support-chat-modal-keyframes\", \"\");\r\n style.textContent = `\r\n @keyframes sc-modal-backdrop-in {\r\n from { opacity: 0; }\r\n to { opacity: 1; }\r\n }\r\n @keyframes sc-modal-backdrop-out {\r\n from { opacity: 1; }\r\n to { opacity: 0; }\r\n }\r\n @keyframes sc-modal-slide-in {\r\n from { opacity: 0; transform: translateY(20px) scale(0.96); }\r\n to { opacity: 1; transform: translateY(0) scale(1); }\r\n }\r\n @keyframes sc-modal-slide-out {\r\n from { opacity: 1; transform: translateY(0) scale(1); }\r\n to { opacity: 0; transform: translateY(20px) scale(0.96); }\r\n }\r\n `;\r\n document.head.appendChild(style);\r\n}\r\n\r\n/**\r\n * SupportChatModal -- modal-based support chat.\r\n *\r\n * Renders a centered modal dialog for support chat, designed to be\r\n * triggered from custom UI elements (e.g., \"Contact Us\" links).\r\n *\r\n * Desktop: centered modal, max-width ~500px, with backdrop overlay.\r\n * Mobile: full-screen modal.\r\n *\r\n * Use with the `useSupportChat()` hook:\r\n *\r\n * @example\r\n * ```tsx\r\n * import { SupportChatModal, useSupportChat } from 'support-chat';\r\n *\r\n * function App() {\r\n * const { open, close, isOpen } = useSupportChat();\r\n * return (\r\n * <>\r\n * <button onClick={open}>Contact Us</button>\r\n * <SupportChatModal\r\n * apiUrl=\"/api/support\"\r\n * isOpen={isOpen}\r\n * onClose={close}\r\n * />\r\n * </>\r\n * );\r\n * }\r\n * ```\r\n */\r\nexport function SupportChatModal({\r\n apiUrl,\r\n isOpen,\r\n onClose,\r\n color = \"#2563eb\",\r\n title = \"Contact Us\",\r\n placeholder = \"Type a message...\",\r\n user,\r\n}: SupportChatModalProps) {\r\n const { messages, input, setInput, sending, sendMessage, handleKeyDown } =\r\n useChatEngine({ apiUrl, user });\r\n\r\n const modalRef = useRef<HTMLDivElement>(null);\r\n const inputRef = useRef<HTMLInputElement>(null);\r\n const messagesEndRef = useRef<HTMLDivElement>(null);\r\n const closingRef = useRef(false);\r\n const backdropRef = useRef<HTMLDivElement>(null);\r\n\r\n // Inject modal keyframes on first render\r\n useEffect(() => {\r\n injectModalKeyframes();\r\n }, []);\r\n\r\n // Focus input when modal opens\r\n useEffect(() => {\r\n if (isOpen && inputRef.current) {\r\n // Small delay to let the modal render before focusing\r\n const timer = setTimeout(() => inputRef.current?.focus(), 50);\r\n return () => clearTimeout(timer);\r\n }\r\n }, [isOpen]);\r\n\r\n // Auto-scroll to latest message\r\n useEffect(() => {\r\n messagesEndRef.current?.scrollIntoView({ behavior: \"smooth\" });\r\n }, [messages]);\r\n\r\n // Prevent body scroll when modal is open\r\n useEffect(() => {\r\n if (isOpen) {\r\n const prev = document.body.style.overflow;\r\n document.body.style.overflow = \"hidden\";\r\n return () => {\r\n document.body.style.overflow = prev;\r\n };\r\n }\r\n }, [isOpen]);\r\n\r\n // Focus trap and Escape key handling\r\n useEffect(() => {\r\n if (!isOpen || !modalRef.current) return;\r\n\r\n const modal = modalRef.current;\r\n\r\n const handleTrapKeyDown = (e: KeyboardEvent) => {\r\n if (e.key === \"Escape\") {\r\n onClose();\r\n return;\r\n }\r\n\r\n if (e.key !== \"Tab\") return;\r\n\r\n const focusable = modal.querySelectorAll<HTMLElement>(\r\n 'button:not([disabled]), [href], input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex=\"-1\"])',\r\n );\r\n if (focusable.length === 0) return;\r\n\r\n const first = focusable[0]!;\r\n const last = focusable[focusable.length - 1]!;\r\n\r\n if (e.shiftKey) {\r\n if (document.activeElement === first) {\r\n e.preventDefault();\r\n last.focus();\r\n }\r\n } else {\r\n if (document.activeElement === last) {\r\n e.preventDefault();\r\n first.focus();\r\n }\r\n }\r\n };\r\n\r\n document.addEventListener(\"keydown\", handleTrapKeyDown);\r\n return () => document.removeEventListener(\"keydown\", handleTrapKeyDown);\r\n }, [isOpen, onClose]);\r\n\r\n // Handle close with animation\r\n const handleClose = useCallback(() => {\r\n closingRef.current = true;\r\n onClose();\r\n }, [onClose]);\r\n\r\n // Handle backdrop click\r\n const handleBackdropClick = useCallback(\r\n (e: React.MouseEvent) => {\r\n if (e.target === e.currentTarget) {\r\n handleClose();\r\n }\r\n },\r\n [handleClose],\r\n );\r\n\r\n if (!isOpen) return null;\r\n\r\n return (\r\n <>\r\n {/* Responsive style for full-screen on mobile */}\r\n <style>{`\r\n @media (max-width: 479px) {\r\n [data-support-chat-modal] {\r\n width: 100vw !important;\r\n height: 100vh !important;\r\n max-width: none !important;\r\n max-height: none !important;\r\n border-radius: 0 !important;\r\n margin: 0 !important;\r\n }\r\n [data-support-chat-modal-backdrop] {\r\n align-items: stretch !important;\r\n justify-content: stretch !important;\r\n }\r\n }\r\n `}</style>\r\n\r\n {/* Backdrop overlay */}\r\n <div\r\n ref={backdropRef}\r\n data-support-chat-modal-backdrop=\"\"\r\n onClick={handleBackdropClick}\r\n style={{\r\n position: \"fixed\",\r\n inset: 0,\r\n zIndex: 100000,\r\n backgroundColor: \"rgba(0, 0, 0, 0.5)\",\r\n display: \"flex\",\r\n alignItems: \"center\",\r\n justifyContent: \"center\",\r\n fontFamily:\r\n '-apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, sans-serif',\r\n fontSize: \"14px\",\r\n animation: \"sc-modal-backdrop-in 0.2s ease-out forwards\",\r\n }}\r\n >\r\n {/* Modal dialog */}\r\n <div\r\n ref={modalRef}\r\n role=\"dialog\"\r\n aria-label=\"Support chat\"\r\n aria-modal=\"true\"\r\n data-support-chat-modal=\"\"\r\n style={{\r\n width: \"500px\",\r\n maxWidth: \"calc(100vw - 32px)\",\r\n height: \"600px\",\r\n maxHeight: \"calc(100vh - 64px)\",\r\n backgroundColor: \"#fff\",\r\n borderRadius: \"16px\",\r\n overflow: \"hidden\",\r\n display: \"flex\",\r\n flexDirection: \"column\",\r\n boxShadow: \"0 20px 60px rgba(0, 0, 0, 0.2)\",\r\n animation: \"sc-modal-slide-in 0.3s ease-out forwards\",\r\n }}\r\n >\r\n {/* Header */}\r\n <div\r\n style={{\r\n backgroundColor: color,\r\n color: \"#fff\",\r\n padding: \"20px 24px\",\r\n display: \"flex\",\r\n alignItems: \"center\",\r\n justifyContent: \"space-between\",\r\n flexShrink: 0,\r\n }}\r\n >\r\n <span style={{ fontWeight: 600, fontSize: \"18px\" }}>{title}</span>\r\n <button\r\n onClick={handleClose}\r\n aria-label=\"Close chat\"\r\n style={{\r\n background: \"none\",\r\n border: \"none\",\r\n color: \"#fff\",\r\n cursor: \"pointer\",\r\n padding: \"4px\",\r\n lineHeight: 1,\r\n fontSize: \"20px\",\r\n }}\r\n >\r\n &#x2715;\r\n </button>\r\n </div>\r\n\r\n {/* Messages */}\r\n <div\r\n role=\"log\"\r\n aria-live=\"polite\"\r\n aria-label=\"Chat messages\"\r\n style={{\r\n flex: 1,\r\n overflowY: \"auto\",\r\n padding: \"20px\",\r\n display: \"flex\",\r\n flexDirection: \"column\",\r\n gap: \"8px\",\r\n }}\r\n >\r\n {messages.length === 0 && (\r\n <div\r\n style={{\r\n color: \"#9ca3af\",\r\n textAlign: \"center\",\r\n marginTop: \"60px\",\r\n }}\r\n >\r\n Send us a message and we will get back to you!\r\n </div>\r\n )}\r\n {messages.map((msg) => (\r\n <div\r\n key={msg.id}\r\n style={{\r\n alignSelf:\r\n msg.sender === \"user\" ? \"flex-end\" : \"flex-start\",\r\n maxWidth: \"80%\",\r\n padding: \"10px 14px\",\r\n borderRadius:\r\n msg.sender === \"user\"\r\n ? \"16px 16px 4px 16px\"\r\n : \"16px 16px 16px 4px\",\r\n backgroundColor:\r\n msg.sender === \"user\" ? color : \"#f3f4f6\",\r\n color: msg.sender === \"user\" ? \"#fff\" : \"#1f2937\",\r\n wordBreak: \"break-word\",\r\n }}\r\n >\r\n {msg.text}\r\n </div>\r\n ))}\r\n <div ref={messagesEndRef} />\r\n </div>\r\n\r\n {/* Input */}\r\n <div\r\n style={{\r\n borderTop: \"1px solid #e5e7eb\",\r\n padding: \"16px 20px\",\r\n display: \"flex\",\r\n gap: \"8px\",\r\n flexShrink: 0,\r\n }}\r\n >\r\n <input\r\n ref={inputRef}\r\n type=\"text\"\r\n value={input}\r\n onChange={(e) => setInput(e.target.value)}\r\n onKeyDown={handleKeyDown}\r\n placeholder={placeholder}\r\n aria-label=\"Type your message\"\r\n style={{\r\n flex: 1,\r\n border: \"1px solid #d1d5db\",\r\n borderRadius: \"8px\",\r\n padding: \"12px 14px\",\r\n fontSize: \"14px\",\r\n outline: \"none\",\r\n fontFamily: \"inherit\",\r\n }}\r\n />\r\n <button\r\n onClick={() => void sendMessage()}\r\n disabled={sending || !input.trim()}\r\n aria-label=\"Send message\"\r\n style={{\r\n backgroundColor: color,\r\n color: \"#fff\",\r\n border: \"none\",\r\n borderRadius: \"8px\",\r\n padding: \"12px 20px\",\r\n cursor:\r\n sending || !input.trim() ? \"not-allowed\" : \"pointer\",\r\n opacity: sending || !input.trim() ? 0.5 : 1,\r\n fontWeight: 600,\r\n fontSize: \"14px\",\r\n fontFamily: \"inherit\",\r\n transition: \"opacity 0.2s ease\",\r\n }}\r\n >\r\n Send\r\n </button>\r\n </div>\r\n </div>\r\n </div>\r\n </>\r\n );\r\n}\r\n","import { useState, useCallback } from \"react\";\r\nimport type { SupportChatState } from \"./types\";\r\n\r\n/**\r\n * Hook that returns controls for opening/closing the support chat.\r\n * Use with `<SupportChatModal />` for modal-triggered chat.\r\n *\r\n * @example\r\n * ```tsx\r\n * const { open, close, toggle, isOpen } = useSupportChat();\r\n * return <button onClick={open}>Contact Us</button>;\r\n * ```\r\n */\r\nexport function useSupportChat(): SupportChatState {\r\n const [isOpen, setIsOpen] = useState(false);\r\n\r\n const open = useCallback(() => setIsOpen(true), []);\r\n const close = useCallback(() => setIsOpen(false), []);\r\n const toggle = useCallback(() => setIsOpen((prev) => !prev), []);\r\n\r\n return { open, close, toggle, isOpen };\r\n}\r\n"]}
@@ -0,0 +1,153 @@
1
+ 'use strict';
2
+
3
+ var webApi = require('@slack/web-api');
4
+
5
+ // src/server/handler.ts
6
+ async function handleMessage(body, options) {
7
+ const { slack, slackChannel, botName, botIcon, onMessage, threadMap } = options;
8
+ if (!body.message || typeof body.message !== "string") {
9
+ return {
10
+ data: { success: false, error: "Missing required field: message" },
11
+ status: 400
12
+ };
13
+ }
14
+ if (!body.sessionId || typeof body.sessionId !== "string") {
15
+ return {
16
+ data: { success: false, error: "Missing required field: sessionId" },
17
+ status: 400
18
+ };
19
+ }
20
+ if (onMessage) {
21
+ try {
22
+ await onMessage(body);
23
+ } catch {
24
+ }
25
+ }
26
+ const lookupKey = body.user?.id ?? body.sessionId;
27
+ const existingThread = threadMap.get(lookupKey);
28
+ try {
29
+ const userLabel = body.user ? `${body.user.name ?? "User"} (${body.user.email ?? body.user.id})` : `Anonymous (session ${body.sessionId.substring(0, 8)})`;
30
+ const contextLines = [];
31
+ if (body.context?.pageUrl) contextLines.push(`Page: ${body.context.pageUrl}`);
32
+ if (body.context?.userAgent) contextLines.push(`Browser: ${body.context.userAgent}`);
33
+ if (body.context?.timezone) contextLines.push(`Timezone: ${body.context.timezone}`);
34
+ if (existingThread) {
35
+ await slack.chat.postMessage({
36
+ channel: slackChannel,
37
+ thread_ts: existingThread,
38
+ text: body.message,
39
+ ...botName ? { username: botName } : {},
40
+ ...botIcon ? { icon_emoji: botIcon } : {}
41
+ });
42
+ return {
43
+ data: { success: true, threadId: existingThread },
44
+ status: 200
45
+ };
46
+ } else {
47
+ const headerText = `*Support: ${userLabel}*
48
+ ${contextLines.join("\n")}`;
49
+ const result = await slack.chat.postMessage({
50
+ channel: slackChannel,
51
+ text: `${headerText}
52
+
53
+ ${body.message}`,
54
+ ...botName ? { username: botName } : {},
55
+ ...botIcon ? { icon_emoji: botIcon } : {}
56
+ });
57
+ const threadTs = result.ts;
58
+ if (!threadTs) {
59
+ return {
60
+ data: { success: false, error: "Slack did not return thread timestamp" },
61
+ status: 500
62
+ };
63
+ }
64
+ threadMap.set(lookupKey, threadTs);
65
+ return {
66
+ data: { success: true, threadId: threadTs },
67
+ status: 200
68
+ };
69
+ }
70
+ } catch (err) {
71
+ const message = err instanceof Error ? err.message : "Unknown Slack error";
72
+ return {
73
+ data: { success: false, error: `Slack API error: ${message}` },
74
+ status: 500
75
+ };
76
+ }
77
+ }
78
+ function createSupportHandler(options) {
79
+ const { slackBotToken, slackChannel, botName, botIcon, onMessage } = options;
80
+ const slack = new webApi.WebClient(slackBotToken);
81
+ const threadMap = /* @__PURE__ */ new Map();
82
+ return async (request) => {
83
+ if (request.method !== "POST") {
84
+ return jsonResponse(
85
+ { success: false, error: "Method not allowed" },
86
+ 405
87
+ );
88
+ }
89
+ let body;
90
+ try {
91
+ body = await request.json();
92
+ } catch {
93
+ return jsonResponse(
94
+ { success: false, error: "Invalid JSON body" },
95
+ 400
96
+ );
97
+ }
98
+ const result = await handleMessage(body, {
99
+ slack,
100
+ slackChannel,
101
+ botName,
102
+ botIcon,
103
+ onMessage,
104
+ threadMap
105
+ });
106
+ return jsonResponse(result.data, result.status);
107
+ };
108
+ }
109
+ function createExpressHandler(options) {
110
+ const { slackBotToken, slackChannel, botName, botIcon, onMessage } = options;
111
+ const slack = new webApi.WebClient(slackBotToken);
112
+ const threadMap = /* @__PURE__ */ new Map();
113
+ return async (req, res) => {
114
+ const body = req.body;
115
+ const result = await handleMessage(body, {
116
+ slack,
117
+ slackChannel,
118
+ botName,
119
+ botIcon,
120
+ onMessage,
121
+ threadMap
122
+ });
123
+ res.status(result.status).json(result.data);
124
+ };
125
+ }
126
+ async function validateSlackToken(token) {
127
+ try {
128
+ const slack = new webApi.WebClient(token);
129
+ const result = await slack.auth.test();
130
+ return {
131
+ ok: true,
132
+ team: result.team,
133
+ user: result.user
134
+ };
135
+ } catch (err) {
136
+ return {
137
+ ok: false,
138
+ error: err instanceof Error ? err.message : "Invalid token"
139
+ };
140
+ }
141
+ }
142
+ function jsonResponse(data, status = 200) {
143
+ return new Response(JSON.stringify(data), {
144
+ status,
145
+ headers: { "Content-Type": "application/json" }
146
+ });
147
+ }
148
+
149
+ exports.createExpressHandler = createExpressHandler;
150
+ exports.createSupportHandler = createSupportHandler;
151
+ exports.validateSlackToken = validateSlackToken;
152
+ //# sourceMappingURL=index.cjs.map
153
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/server/handler.ts"],"names":["WebClient"],"mappings":"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","file":"index.cjs","sourcesContent":["import { WebClient } from \"@slack/web-api\";\r\nimport type {\r\n SupportHandlerOptions,\r\n IncomingMessage,\r\n HandlerResponse,\r\n ExpressRequest,\r\n ExpressResponse,\r\n} from \"./types\";\r\n\r\n/**\r\n * Core logic shared by both the Web API handler and the Express handler.\r\n * Accepts a parsed body and returns the response data + status code.\r\n */\r\nasync function handleMessage(\r\n body: IncomingMessage,\r\n options: {\r\n slack: WebClient;\r\n slackChannel: string;\r\n botName?: string;\r\n botIcon?: string;\r\n onMessage?: (data: IncomingMessage) => void | Promise<void>;\r\n threadMap: Map<string, string>;\r\n },\r\n): Promise<{ data: HandlerResponse; status: number }> {\r\n const { slack, slackChannel, botName, botIcon, onMessage, threadMap } = options;\r\n\r\n // Validate required fields\r\n if (!body.message || typeof body.message !== \"string\") {\r\n return {\r\n data: { success: false, error: \"Missing required field: message\" },\r\n status: 400,\r\n };\r\n }\r\n\r\n if (!body.sessionId || typeof body.sessionId !== \"string\") {\r\n return {\r\n data: { success: false, error: \"Missing required field: sessionId\" },\r\n status: 400,\r\n };\r\n }\r\n\r\n // Fire onMessage callback\r\n if (onMessage) {\r\n try {\r\n await onMessage(body);\r\n } catch {\r\n // Don't block on callback errors\r\n }\r\n }\r\n\r\n const lookupKey = body.user?.id ?? body.sessionId;\r\n const existingThread = threadMap.get(lookupKey);\r\n\r\n try {\r\n // Build the Slack message\r\n const userLabel = body.user\r\n ? `${body.user.name ?? \"User\"} (${body.user.email ?? body.user.id})`\r\n : `Anonymous (session ${body.sessionId.substring(0, 8)})`;\r\n\r\n const contextLines: string[] = [];\r\n if (body.context?.pageUrl) contextLines.push(`Page: ${body.context.pageUrl}`);\r\n if (body.context?.userAgent) contextLines.push(`Browser: ${body.context.userAgent}`);\r\n if (body.context?.timezone) contextLines.push(`Timezone: ${body.context.timezone}`);\r\n\r\n if (existingThread) {\r\n // Reply to existing thread\r\n await slack.chat.postMessage({\r\n channel: slackChannel,\r\n thread_ts: existingThread,\r\n text: body.message,\r\n ...(botName ? { username: botName } : {}),\r\n ...(botIcon ? { icon_emoji: botIcon } : {}),\r\n });\r\n\r\n return {\r\n data: { success: true, threadId: existingThread },\r\n status: 200,\r\n };\r\n } else {\r\n // Create new thread\r\n const headerText = `*Support: ${userLabel}*\\n${contextLines.join(\"\\n\")}`;\r\n\r\n const result = await slack.chat.postMessage({\r\n channel: slackChannel,\r\n text: `${headerText}\\n\\n${body.message}`,\r\n ...(botName ? { username: botName } : {}),\r\n ...(botIcon ? { icon_emoji: botIcon } : {}),\r\n });\r\n\r\n const threadTs = result.ts;\r\n if (!threadTs) {\r\n return {\r\n data: { success: false, error: \"Slack did not return thread timestamp\" },\r\n status: 500,\r\n };\r\n }\r\n\r\n threadMap.set(lookupKey, threadTs);\r\n\r\n return {\r\n data: { success: true, threadId: threadTs },\r\n status: 200,\r\n };\r\n }\r\n } catch (err) {\r\n const message = err instanceof Error ? err.message : \"Unknown Slack error\";\r\n return {\r\n data: { success: false, error: `Slack API error: ${message}` },\r\n status: 500,\r\n };\r\n }\r\n}\r\n\r\n/**\r\n * Creates a support chat POST handler that routes messages to Slack threads.\r\n *\r\n * The returned function accepts a standard Web API `Request` and returns a `Response`.\r\n * This makes it compatible with Next.js App Router, Remix, Hono, and any framework\r\n * that supports the Web Request/Response standard.\r\n *\r\n * For Express/Connect-style frameworks, use `createExpressHandler()` instead.\r\n *\r\n * @example Next.js App Router\r\n * ```ts\r\n * // app/api/support/route.ts\r\n * import { createSupportHandler } from 'support-chat/server';\r\n *\r\n * export const POST = createSupportHandler({\r\n * slackBotToken: process.env.SLACK_BOT_TOKEN!,\r\n * slackChannel: process.env.SLACK_CHANNEL!,\r\n * });\r\n * ```\r\n */\r\nexport function createSupportHandler(options: SupportHandlerOptions) {\r\n const { slackBotToken, slackChannel, botName, botIcon, onMessage } = options;\r\n\r\n const slack = new WebClient(slackBotToken);\r\n const threadMap = new Map<string, string>();\r\n\r\n return async (request: Request): Promise<Response> => {\r\n // Only accept POST\r\n if (request.method !== \"POST\") {\r\n return jsonResponse(\r\n { success: false, error: \"Method not allowed\" } satisfies HandlerResponse,\r\n 405,\r\n );\r\n }\r\n\r\n let body: IncomingMessage;\r\n try {\r\n body = (await request.json()) as IncomingMessage;\r\n } catch {\r\n return jsonResponse(\r\n { success: false, error: \"Invalid JSON body\" } satisfies HandlerResponse,\r\n 400,\r\n );\r\n }\r\n\r\n const result = await handleMessage(body, {\r\n slack,\r\n slackChannel,\r\n botName,\r\n botIcon,\r\n onMessage,\r\n threadMap,\r\n });\r\n\r\n return jsonResponse(result.data, result.status);\r\n };\r\n}\r\n\r\n/**\r\n * Creates an Express/Connect-compatible support chat handler.\r\n *\r\n * The returned function uses the Express `(req, res)` signature.\r\n * The request body must be parsed (use `express.json()` middleware).\r\n *\r\n * @example Express\r\n * ```ts\r\n * import express from 'express';\r\n * import { createExpressHandler } from 'support-chat/server';\r\n *\r\n * const app = express();\r\n * app.use(express.json());\r\n *\r\n * app.post('/api/support', createExpressHandler({\r\n * slackBotToken: process.env.SLACK_BOT_TOKEN!,\r\n * slackChannel: process.env.SLACK_CHANNEL!,\r\n * }));\r\n * ```\r\n */\r\nexport function createExpressHandler(options: SupportHandlerOptions) {\r\n const { slackBotToken, slackChannel, botName, botIcon, onMessage } = options;\r\n\r\n const slack = new WebClient(slackBotToken);\r\n const threadMap = new Map<string, string>();\r\n\r\n return async (req: ExpressRequest, res: ExpressResponse): Promise<void> => {\r\n const body = req.body as IncomingMessage;\r\n\r\n const result = await handleMessage(body, {\r\n slack,\r\n slackChannel,\r\n botName,\r\n botIcon,\r\n onMessage,\r\n threadMap,\r\n });\r\n\r\n res.status(result.status).json(result.data);\r\n };\r\n}\r\n\r\n/** Validate a Slack bot token by calling auth.test */\r\nexport async function validateSlackToken(\r\n token: string,\r\n): Promise<{ ok: boolean; team?: string; user?: string; error?: string }> {\r\n try {\r\n const slack = new WebClient(token);\r\n const result = await slack.auth.test();\r\n return {\r\n ok: true,\r\n team: result.team,\r\n user: result.user,\r\n };\r\n } catch (err) {\r\n return {\r\n ok: false,\r\n error: err instanceof Error ? err.message : \"Invalid token\",\r\n };\r\n }\r\n}\r\n\r\nfunction jsonResponse(data: HandlerResponse, status = 200): Response {\r\n return new Response(JSON.stringify(data), {\r\n status,\r\n headers: { \"Content-Type\": \"application/json\" },\r\n });\r\n}\r\n"]}
@@ -0,0 +1,117 @@
1
+ /** Slack configuration for the support handler */
2
+ interface SlackConfig {
3
+ /** Slack Bot User OAuth Token (xoxb-...) */
4
+ slackBotToken: string;
5
+ /** Channel ID to post threads to */
6
+ slackChannel: string;
7
+ /** Optional custom bot display name */
8
+ botName?: string;
9
+ /** Optional custom bot icon emoji (e.g., ':speech_balloon:') */
10
+ botIcon?: string;
11
+ }
12
+ /** Options for createSupportHandler */
13
+ interface SupportHandlerOptions extends SlackConfig {
14
+ /** Optional callback fired on each message */
15
+ onMessage?: (data: IncomingMessage) => void | Promise<void>;
16
+ }
17
+ /** User info sent from the client */
18
+ interface MessageUser {
19
+ id: string;
20
+ name?: string;
21
+ email?: string;
22
+ avatar?: string;
23
+ }
24
+ /** Browser context collected by the client */
25
+ interface MessageContext {
26
+ pageUrl?: string;
27
+ referrer?: string;
28
+ userAgent?: string;
29
+ screenSize?: string;
30
+ timezone?: string;
31
+ sessionId?: string;
32
+ }
33
+ /** Incoming message payload from the client */
34
+ interface IncomingMessage {
35
+ message: string;
36
+ user?: MessageUser;
37
+ sessionId: string;
38
+ context?: MessageContext;
39
+ }
40
+ /** Successful response from the handler */
41
+ interface SuccessResponse {
42
+ success: true;
43
+ threadId: string;
44
+ }
45
+ /** Error response from the handler */
46
+ interface ErrorResponse {
47
+ success: false;
48
+ error: string;
49
+ }
50
+ type HandlerResponse = SuccessResponse | ErrorResponse;
51
+ /**
52
+ * Minimal Express-compatible request interface.
53
+ * We declare only what we need so that consumers don't have to install @types/express.
54
+ */
55
+ interface ExpressRequest {
56
+ body: unknown;
57
+ method?: string;
58
+ }
59
+ /**
60
+ * Minimal Express-compatible response interface.
61
+ */
62
+ interface ExpressResponse {
63
+ status(code: number): ExpressResponse;
64
+ json(data: unknown): void;
65
+ }
66
+
67
+ /**
68
+ * Creates a support chat POST handler that routes messages to Slack threads.
69
+ *
70
+ * The returned function accepts a standard Web API `Request` and returns a `Response`.
71
+ * This makes it compatible with Next.js App Router, Remix, Hono, and any framework
72
+ * that supports the Web Request/Response standard.
73
+ *
74
+ * For Express/Connect-style frameworks, use `createExpressHandler()` instead.
75
+ *
76
+ * @example Next.js App Router
77
+ * ```ts
78
+ * // app/api/support/route.ts
79
+ * import { createSupportHandler } from 'support-chat/server';
80
+ *
81
+ * export const POST = createSupportHandler({
82
+ * slackBotToken: process.env.SLACK_BOT_TOKEN!,
83
+ * slackChannel: process.env.SLACK_CHANNEL!,
84
+ * });
85
+ * ```
86
+ */
87
+ declare function createSupportHandler(options: SupportHandlerOptions): (request: Request) => Promise<Response>;
88
+ /**
89
+ * Creates an Express/Connect-compatible support chat handler.
90
+ *
91
+ * The returned function uses the Express `(req, res)` signature.
92
+ * The request body must be parsed (use `express.json()` middleware).
93
+ *
94
+ * @example Express
95
+ * ```ts
96
+ * import express from 'express';
97
+ * import { createExpressHandler } from 'support-chat/server';
98
+ *
99
+ * const app = express();
100
+ * app.use(express.json());
101
+ *
102
+ * app.post('/api/support', createExpressHandler({
103
+ * slackBotToken: process.env.SLACK_BOT_TOKEN!,
104
+ * slackChannel: process.env.SLACK_CHANNEL!,
105
+ * }));
106
+ * ```
107
+ */
108
+ declare function createExpressHandler(options: SupportHandlerOptions): (req: ExpressRequest, res: ExpressResponse) => Promise<void>;
109
+ /** Validate a Slack bot token by calling auth.test */
110
+ declare function validateSlackToken(token: string): Promise<{
111
+ ok: boolean;
112
+ team?: string;
113
+ user?: string;
114
+ error?: string;
115
+ }>;
116
+
117
+ export { type ErrorResponse, type ExpressRequest, type ExpressResponse, type HandlerResponse, type IncomingMessage, type MessageContext, type MessageUser, type SlackConfig, type SuccessResponse, type SupportHandlerOptions, createExpressHandler, createSupportHandler, validateSlackToken };
@@ -0,0 +1,117 @@
1
+ /** Slack configuration for the support handler */
2
+ interface SlackConfig {
3
+ /** Slack Bot User OAuth Token (xoxb-...) */
4
+ slackBotToken: string;
5
+ /** Channel ID to post threads to */
6
+ slackChannel: string;
7
+ /** Optional custom bot display name */
8
+ botName?: string;
9
+ /** Optional custom bot icon emoji (e.g., ':speech_balloon:') */
10
+ botIcon?: string;
11
+ }
12
+ /** Options for createSupportHandler */
13
+ interface SupportHandlerOptions extends SlackConfig {
14
+ /** Optional callback fired on each message */
15
+ onMessage?: (data: IncomingMessage) => void | Promise<void>;
16
+ }
17
+ /** User info sent from the client */
18
+ interface MessageUser {
19
+ id: string;
20
+ name?: string;
21
+ email?: string;
22
+ avatar?: string;
23
+ }
24
+ /** Browser context collected by the client */
25
+ interface MessageContext {
26
+ pageUrl?: string;
27
+ referrer?: string;
28
+ userAgent?: string;
29
+ screenSize?: string;
30
+ timezone?: string;
31
+ sessionId?: string;
32
+ }
33
+ /** Incoming message payload from the client */
34
+ interface IncomingMessage {
35
+ message: string;
36
+ user?: MessageUser;
37
+ sessionId: string;
38
+ context?: MessageContext;
39
+ }
40
+ /** Successful response from the handler */
41
+ interface SuccessResponse {
42
+ success: true;
43
+ threadId: string;
44
+ }
45
+ /** Error response from the handler */
46
+ interface ErrorResponse {
47
+ success: false;
48
+ error: string;
49
+ }
50
+ type HandlerResponse = SuccessResponse | ErrorResponse;
51
+ /**
52
+ * Minimal Express-compatible request interface.
53
+ * We declare only what we need so that consumers don't have to install @types/express.
54
+ */
55
+ interface ExpressRequest {
56
+ body: unknown;
57
+ method?: string;
58
+ }
59
+ /**
60
+ * Minimal Express-compatible response interface.
61
+ */
62
+ interface ExpressResponse {
63
+ status(code: number): ExpressResponse;
64
+ json(data: unknown): void;
65
+ }
66
+
67
+ /**
68
+ * Creates a support chat POST handler that routes messages to Slack threads.
69
+ *
70
+ * The returned function accepts a standard Web API `Request` and returns a `Response`.
71
+ * This makes it compatible with Next.js App Router, Remix, Hono, and any framework
72
+ * that supports the Web Request/Response standard.
73
+ *
74
+ * For Express/Connect-style frameworks, use `createExpressHandler()` instead.
75
+ *
76
+ * @example Next.js App Router
77
+ * ```ts
78
+ * // app/api/support/route.ts
79
+ * import { createSupportHandler } from 'support-chat/server';
80
+ *
81
+ * export const POST = createSupportHandler({
82
+ * slackBotToken: process.env.SLACK_BOT_TOKEN!,
83
+ * slackChannel: process.env.SLACK_CHANNEL!,
84
+ * });
85
+ * ```
86
+ */
87
+ declare function createSupportHandler(options: SupportHandlerOptions): (request: Request) => Promise<Response>;
88
+ /**
89
+ * Creates an Express/Connect-compatible support chat handler.
90
+ *
91
+ * The returned function uses the Express `(req, res)` signature.
92
+ * The request body must be parsed (use `express.json()` middleware).
93
+ *
94
+ * @example Express
95
+ * ```ts
96
+ * import express from 'express';
97
+ * import { createExpressHandler } from 'support-chat/server';
98
+ *
99
+ * const app = express();
100
+ * app.use(express.json());
101
+ *
102
+ * app.post('/api/support', createExpressHandler({
103
+ * slackBotToken: process.env.SLACK_BOT_TOKEN!,
104
+ * slackChannel: process.env.SLACK_CHANNEL!,
105
+ * }));
106
+ * ```
107
+ */
108
+ declare function createExpressHandler(options: SupportHandlerOptions): (req: ExpressRequest, res: ExpressResponse) => Promise<void>;
109
+ /** Validate a Slack bot token by calling auth.test */
110
+ declare function validateSlackToken(token: string): Promise<{
111
+ ok: boolean;
112
+ team?: string;
113
+ user?: string;
114
+ error?: string;
115
+ }>;
116
+
117
+ export { type ErrorResponse, type ExpressRequest, type ExpressResponse, type HandlerResponse, type IncomingMessage, type MessageContext, type MessageUser, type SlackConfig, type SuccessResponse, type SupportHandlerOptions, createExpressHandler, createSupportHandler, validateSlackToken };