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.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Indigo AI
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,307 @@
1
+ # simple-support-chat
2
+
3
+ Embeddable chat widget SDK that routes customer messages to Slack threads. Open-source alternative to Crisp, Intercom, and Drift.
4
+
5
+ - Zero cost, self-hosted
6
+ - Slack-native: messages appear as threads in your workspace
7
+ - Works with any React app (Next.js, Remix, Vite, etc.)
8
+ - Anonymous and authenticated user support
9
+ - No external CSS or Tailwind dependency
10
+
11
+ ## Installation
12
+
13
+ ```bash
14
+ pnpm add simple-support-chat
15
+ ```
16
+
17
+ React 18+ is a peer dependency for the client widget. The server handler works without React.
18
+
19
+ ## Slack App Setup
20
+
21
+ Follow these steps to create a Slack App for your workspace. Each open-source user creates their own app -- there is no shared OAuth install flow.
22
+
23
+ ### Step 1: Create the App
24
+
25
+ 1. Go to [api.slack.com/apps](https://api.slack.com/apps) and click **Create New App**
26
+ 2. Choose **From scratch**
27
+ 3. Name it (e.g., "Support Chat") and select your workspace
28
+ 4. Click **Create App**
29
+
30
+ ### Step 2: Configure Bot Token Scopes
31
+
32
+ Navigate to **OAuth & Permissions** in the sidebar and scroll to **Bot Token Scopes**. Add these three scopes:
33
+
34
+ | Scope | Purpose |
35
+ |-------|---------|
36
+ | `chat:write` | Post messages to channels |
37
+ | `chat:write.customize` | Customize the bot's display name and icon per message |
38
+ | `users:read` | Read basic user profile information |
39
+
40
+ These are the minimum required scopes. You do not need any User Token Scopes.
41
+
42
+ ### Step 3: Install to Workspace
43
+
44
+ 1. Scroll to the top of **OAuth & Permissions** and click **Install to Workspace**
45
+ 2. Review the permissions and click **Allow**
46
+ 3. Copy the **Bot User OAuth Token** -- it starts with `xoxb-`
47
+
48
+ ### Step 4: Get Your Channel ID
49
+
50
+ You need the channel ID (not the channel name) for configuration:
51
+
52
+ 1. Open Slack and navigate to the channel you want to use for support
53
+ 2. Click the channel name at the top to open channel details
54
+ 3. Scroll to the bottom of the details panel -- the Channel ID is displayed there (e.g., `C0123ABCDEF`)
55
+
56
+ Alternatively, right-click the channel name, click **Copy link**, and extract the ID from the URL.
57
+
58
+ ### Step 5: Invite the Bot
59
+
60
+ The bot must be a member of the channel to post messages. In Slack, type:
61
+
62
+ ```
63
+ /invite @YourBotName
64
+ ```
65
+
66
+ ### Step 6: Set Environment Variables
67
+
68
+ Add these to your `.env` file:
69
+
70
+ ```bash
71
+ SLACK_BOT_TOKEN=xoxb-your-token-here
72
+ SLACK_CHANNEL_ID=C0123ABCDEF
73
+ ```
74
+
75
+ ### Validate Your Token
76
+
77
+ Use the built-in `validateSlackToken` utility to verify your token works before going live:
78
+
79
+ ```ts
80
+ import { validateSlackToken } from "simple-support-chat/server";
81
+
82
+ const result = await validateSlackToken(process.env.SLACK_BOT_TOKEN!);
83
+ if (result.ok) {
84
+ console.log(`Connected to workspace: ${result.team} as ${result.user}`);
85
+ } else {
86
+ console.error(`Token validation failed: ${result.error}`);
87
+ }
88
+ ```
89
+
90
+ ### Troubleshooting
91
+
92
+ | Problem | Solution |
93
+ |---------|----------|
94
+ | `not_in_channel` error | Invite the bot to the channel with `/invite @BotName` |
95
+ | `invalid_auth` error | Check that your token starts with `xoxb-` and has not been revoked |
96
+ | `channel_not_found` error | Verify you are using the Channel ID (e.g., `C0123ABCDEF`), not the channel name |
97
+ | Messages not appearing | Confirm the bot has `chat:write` scope and the channel ID is correct |
98
+ | Bot name/icon not customizing | Ensure `chat:write.customize` scope is added |
99
+
100
+ ## Quick Start
101
+
102
+ ### 1. Server: Create API Route
103
+
104
+ ```ts
105
+ // app/api/support/route.ts (Next.js App Router)
106
+ import { createSupportHandler } from "simple-support-chat/server";
107
+
108
+ export const POST = createSupportHandler({
109
+ slackBotToken: process.env.SLACK_BOT_TOKEN!,
110
+ slackChannel: process.env.SLACK_CHANNEL_ID!,
111
+ });
112
+ ```
113
+
114
+ ### 2. Client: Add the Chat Widget
115
+
116
+ ```tsx
117
+ import { ChatBubble } from "simple-support-chat";
118
+
119
+ export default function App() {
120
+ return (
121
+ <>
122
+ {/* Your app content */}
123
+ <ChatBubble apiUrl="/api/support" />
124
+ </>
125
+ );
126
+ }
127
+ ```
128
+
129
+ That's it! Messages from your users will appear as threaded conversations in your Slack channel.
130
+
131
+ ## Configuration
132
+
133
+ ### ChatBubble Props
134
+
135
+ | Prop | Type | Default | Description |
136
+ |------|------|---------|-------------|
137
+ | `apiUrl` | `string` | (required) | URL of your support API endpoint |
138
+ | `position` | `'bottom-right' \| 'bottom-left' \| 'top-right' \| 'top-left'` | `'bottom-right'` | Bubble position |
139
+ | `color` | `string` | `'#2563eb'` | Primary color (bubble, header, sent messages) |
140
+ | `title` | `string` | `'Support'` | Chat panel header title |
141
+ | `placeholder` | `string` | `'Type a message...'` | Input placeholder text |
142
+ | `show` | `boolean` | `true` | Show/hide the floating bubble |
143
+ | `user` | `ChatUser` | `undefined` | Authenticated user info |
144
+
145
+ ### Server Options
146
+
147
+ | Option | Type | Description |
148
+ |--------|------|-------------|
149
+ | `slackBotToken` | `string` | Slack Bot OAuth Token (`xoxb-...`) |
150
+ | `slackChannel` | `string` | Slack channel ID |
151
+ | `botName` | `string` | Custom bot display name |
152
+ | `botIcon` | `string` | Bot icon emoji (e.g., `:speech_balloon:`) |
153
+ | `onMessage` | `(data) => void` | Callback on each message |
154
+
155
+ ## Identity Integration
156
+
157
+ Pass the logged-in user's identity to the widget so Slack threads show who they are:
158
+
159
+ ```tsx
160
+ import { ChatBubble } from "simple-support-chat";
161
+ import { useAuth } from "./your-auth"; // your auth provider
162
+
163
+ export function SupportWidget() {
164
+ const { user } = useAuth();
165
+
166
+ return (
167
+ <ChatBubble
168
+ apiUrl="/api/support"
169
+ user={user ? { id: user.id, name: user.name, email: user.email } : undefined}
170
+ />
171
+ );
172
+ }
173
+ ```
174
+
175
+ When a user is identified, Slack threads are titled: **Support: John Doe (john@example.com)**
176
+ When anonymous: **Support: Anonymous (session abc12345)**
177
+
178
+ ## Modal Mode
179
+
180
+ Hide the floating bubble and trigger the chat from a custom "Contact Us" element using the `useSupportChat` hook and `SupportChatModal` component:
181
+
182
+ ```tsx
183
+ import { SupportChatModal, useSupportChat } from "simple-support-chat";
184
+
185
+ function App() {
186
+ const { open, close, isOpen } = useSupportChat();
187
+
188
+ return (
189
+ <>
190
+ <nav>
191
+ <button onClick={open}>Contact Us</button>
192
+ </nav>
193
+
194
+ <SupportChatModal
195
+ apiUrl="/api/support"
196
+ isOpen={isOpen}
197
+ onClose={close}
198
+ title="Get Help"
199
+ color="#10b981"
200
+ user={currentUser}
201
+ />
202
+ </>
203
+ );
204
+ }
205
+ ```
206
+
207
+ The modal renders centered on desktop (max-width 500px) with a backdrop overlay, and full-screen on mobile. You can place the trigger button anywhere -- nav bar, footer, settings page, error boundaries, etc.
208
+
209
+ ### SupportChatModal Props
210
+
211
+ | Prop | Type | Default | Description |
212
+ |------|------|---------|-------------|
213
+ | `apiUrl` | `string` | (required) | URL of your support API endpoint |
214
+ | `isOpen` | `boolean` | (required) | Whether the modal is open |
215
+ | `onClose` | `() => void` | (required) | Callback to close the modal |
216
+ | `color` | `string` | `'#2563eb'` | Primary color |
217
+ | `title` | `string` | `'Contact Us'` | Modal header title |
218
+ | `placeholder` | `string` | `'Type a message...'` | Input placeholder text |
219
+ | `user` | `ChatUser` | `undefined` | Authenticated user info |
220
+
221
+ ### useSupportChat Hook
222
+
223
+ ```tsx
224
+ import { useSupportChat } from "simple-support-chat";
225
+
226
+ function ContactButton() {
227
+ const { open } = useSupportChat();
228
+ return <button onClick={open}>Contact Us</button>;
229
+ }
230
+ ```
231
+
232
+ ## Express / Connect Handler
233
+
234
+ For Express-style frameworks (Express, Fastify with express compat, etc.), use `createExpressHandler` instead:
235
+
236
+ ```ts
237
+ import express from "express";
238
+ import { createExpressHandler } from "simple-support-chat/server";
239
+
240
+ const app = express();
241
+ app.use(express.json());
242
+
243
+ app.post(
244
+ "/api/support",
245
+ createExpressHandler({
246
+ slackBotToken: process.env.SLACK_BOT_TOKEN!,
247
+ slackChannel: process.env.SLACK_CHANNEL_ID!,
248
+ botName: "Support Bot",
249
+ botIcon: ":speech_balloon:",
250
+ onMessage: (data) => {
251
+ console.log("New support message:", data.message);
252
+ },
253
+ }),
254
+ );
255
+
256
+ app.listen(3000);
257
+ ```
258
+
259
+ The Express handler uses the same threading logic as the Web API handler. The request body must be parsed before the handler runs (use `express.json()` middleware).
260
+
261
+ ## API Reference
262
+
263
+ ### Server Exports (`simple-support-chat/server`)
264
+
265
+ | Export | Description |
266
+ |--------|-------------|
267
+ | `createSupportHandler(options)` | Returns a Web API `(Request) => Promise<Response>` handler |
268
+ | `createExpressHandler(options)` | Returns an Express `(req, res) => Promise<void>` handler |
269
+ | `validateSlackToken(token)` | Validates a Slack token via `auth.test` |
270
+
271
+ ### Client Exports (`simple-support-chat`)
272
+
273
+ | Export | Description |
274
+ |--------|-------------|
275
+ | `ChatBubble` | Floating chat bubble React component |
276
+ | `SupportChatModal` | Modal-based chat React component |
277
+ | `useSupportChat()` | Hook returning `{ open, close, toggle, isOpen }` |
278
+ | `useChatEngine(options)` | Low-level hook for chat message state |
279
+ | `collectAnonymousContext()` | Collects browser context (page URL, user agent, etc.) |
280
+ | `getSessionId()` | Gets or creates a persistent anonymous session ID |
281
+
282
+ ### Types
283
+
284
+ All TypeScript types are exported from both entry points:
285
+
286
+ ```ts
287
+ // Client types
288
+ import type { ChatBubbleProps, ChatUser, SupportChatModalProps, ChatMessage } from "simple-support-chat";
289
+
290
+ // Server types
291
+ import type { SupportHandlerOptions, IncomingMessage, HandlerResponse } from "simple-support-chat/server";
292
+ ```
293
+
294
+ ## Development
295
+
296
+ ```bash
297
+ pnpm install
298
+ pnpm dev # Watch mode
299
+ pnpm test # Run tests
300
+ pnpm typecheck # TypeScript check
301
+ pnpm lint # ESLint
302
+ pnpm build # Production build
303
+ ```
304
+
305
+ ## License
306
+
307
+ MIT