@heedb/web-sdk 0.1.0 → 0.1.1

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 (2) hide show
  1. package/README.md +451 -16
  2. package/package.json +1 -1
package/README.md CHANGED
@@ -1,8 +1,16 @@
1
1
  # @heedb/web-sdk
2
2
 
3
- Drop-in feedback widget with email conversations for any website.
3
+ A lightweight (~12 KB) feedback widget that drops into any website. Customers can send messages, make privacy requests, and view their conversation history — all through email-based threads. No login required.
4
4
 
5
- ## CDN (quickest)
5
+ [Dashboard](https://heedb.com) · [GitHub](https://github.com/TheAleSch/heedb)
6
+
7
+ ---
8
+
9
+ ## Quick start
10
+
11
+ ### CDN (recommended)
12
+
13
+ The fastest way — no build step, no dependencies. Add one line before `</body>`:
6
14
 
7
15
  ```html
8
16
  <script
@@ -12,7 +20,11 @@ Drop-in feedback widget with email conversations for any website.
12
20
  ></script>
13
21
  ```
14
22
 
15
- ## npm
23
+ > **Important:** `data-host` is required when loading from a CDN. It tells the widget where to send API requests. Omit it only when the script is served from the same domain as your Heedb instance.
24
+
25
+ A floating chat button appears in the bottom-right corner. That's it.
26
+
27
+ ### npm
16
28
 
17
29
  ```bash
18
30
  npm install @heedb/web-sdk
@@ -21,29 +33,452 @@ npm install @heedb/web-sdk
21
33
  ```ts
22
34
  import { Heedb } from "@heedb/web-sdk";
23
35
 
36
+ Heedb.init({ apiKey: "YOUR_API_KEY" });
37
+ ```
38
+
39
+ This dynamically loads the widget and attaches it to the page. Works with React, Next.js, Vue, Svelte, or any framework.
40
+
41
+ ### Self-hosted
42
+
43
+ If you run your own Heedb instance, serve `widget.js` from your domain:
44
+
45
+ ```html
46
+ <script src="https://your-instance.com/widget.js" data-api-key="YOUR_API_KEY"></script>
47
+ ```
48
+
49
+ The widget auto-detects the API host from the script's origin — no `data-host` needed.
50
+
51
+ ---
52
+
53
+ ## Environment variables
54
+
55
+ Add these to your `.env` (or equivalent):
56
+
57
+ ```env
58
+ # Public — safe for client-side code, used in data-api-key or Heedb.init()
59
+ NEXT_PUBLIC_HEEDB_API_KEY=your_api_key_here
60
+
61
+ # Private — server-side only, used to generate userHash
62
+ # NEVER expose this in client-side code, bundle, or git
63
+ HEEDB_WIDGET_SECRET=your_widget_secret_here
64
+ ```
65
+
66
+ Both values are in your [dashboard settings](https://heedb.com/app/settings).
67
+
68
+ ---
69
+
70
+ ## Identify users
71
+
72
+ By default, the widget shows a form asking for name, email, and message. If the user is already logged in to your app, you can skip that step.
73
+
74
+ There are three levels:
75
+
76
+ ### 1. Anonymous (default)
77
+
78
+ No `init()` call needed. The widget shows the full contact form. Good for marketing sites, landing pages, or anywhere users aren't logged in.
79
+
80
+ ### 2. Identified (name + email)
81
+
82
+ Pre-fills the form and hides the name/email fields. The user only sees the message box. **No server-side code required.**
83
+
84
+ ```ts
24
85
  Heedb.init({
25
86
  apiKey: "YOUR_API_KEY",
26
- email: user.email, // optional
27
- name: user.name, // optional
28
- userHash: serverHash, // optional — HMAC-SHA256 for verified identity
87
+ email: "jane@example.com",
88
+ name: "Jane",
29
89
  });
30
90
  ```
31
91
 
32
- ## Identify users
92
+ Use this when you know the user's identity but don't need to show them their conversation history.
33
93
 
34
- Generate a `userHash` on your server to verify user identity:
94
+ ### 3. Verified (name + email + userHash)
95
+
96
+ Same as identified, plus unlocks the **Messages** tab where the user can view their previous conversation threads.
97
+
98
+ **This requires a server-generated HMAC.** The `userHash` must be computed on your backend and passed to the frontend — never generate it client-side, as that would expose your Widget Secret.
99
+
100
+ ```ts
101
+ // Client-side — after receiving userHash from your server
102
+ Heedb.init({
103
+ apiKey: "YOUR_API_KEY",
104
+ email: "jane@example.com",
105
+ name: "Jane",
106
+ userHash: serverGeneratedHash, // from your backend
107
+ });
108
+ ```
109
+
110
+ **Only pass `userHash` when a user is authenticated.** For logged-out users, either call `init()` without it (identified) or don't call `init()` at all (anonymous).
111
+
112
+ ---
113
+
114
+ ## Generate the userHash (server-side)
115
+
116
+ The `userHash` is an HMAC-SHA256 of the user's email, signed with your **Widget Secret**. It proves your server vouches for this user's identity.
117
+
118
+ **The flow:**
119
+ 1. User logs in to your app
120
+ 2. Your server computes `HMAC-SHA256(email, WIDGET_SECRET)` → `userHash`
121
+ 3. Your server passes `userHash` to the frontend (via props, API response, or server-rendered HTML)
122
+ 4. The frontend calls `Heedb.init({ email, userHash })`
123
+
124
+ **Never compute the hash client-side — the Widget Secret must stay on your server.**
125
+
126
+ ### Node.js
35
127
 
36
128
  ```js
37
129
  const crypto = require("crypto");
38
- const userHash = crypto
39
- .createHmac("sha256", WIDGET_SECRET)
40
- .update(userEmail)
41
- .digest("hex");
130
+
131
+ function heedbUserHash(email) {
132
+ return crypto
133
+ .createHmac("sha256", process.env.HEEDB_WIDGET_SECRET)
134
+ .update(email)
135
+ .digest("hex");
136
+ }
137
+ ```
138
+
139
+ ### Python
140
+
141
+ ```python
142
+ import hmac, hashlib, os
143
+
144
+ def heedb_user_hash(email: str) -> str:
145
+ secret = os.environ["HEEDB_WIDGET_SECRET"].encode()
146
+ return hmac.new(secret, email.encode(), hashlib.sha256).hexdigest()
147
+ ```
148
+
149
+ ### PHP
150
+
151
+ ```php
152
+ function heedbUserHash(string $email): string {
153
+ return hash_hmac('sha256', $email, getenv('HEEDB_WIDGET_SECRET'));
154
+ }
155
+ ```
156
+
157
+ ### Ruby
158
+
159
+ ```ruby
160
+ require "openssl"
161
+
162
+ def heedb_user_hash(email)
163
+ OpenSSL::HMAC.hexdigest("sha256", ENV["HEEDB_WIDGET_SECRET"], email)
164
+ end
165
+ ```
166
+
167
+ ### Go
168
+
169
+ ```go
170
+ import (
171
+ "crypto/hmac"
172
+ "crypto/sha256"
173
+ "encoding/hex"
174
+ "os"
175
+ )
176
+
177
+ func heedbUserHash(email string) string {
178
+ h := hmac.New(sha256.New, []byte(os.Getenv("HEEDB_WIDGET_SECRET")))
179
+ h.Write([]byte(email))
180
+ return hex.EncodeToString(h.Sum(nil))
181
+ }
182
+ ```
183
+
184
+ ---
185
+
186
+ ## Framework examples
187
+
188
+ ### Next.js (App Router) — full example with verified identity
189
+
190
+ This is the recommended pattern. The hash is computed in a server component and passed to a client component.
191
+
192
+ ```tsx
193
+ // app/components/HeedbWidget.server.tsx — Server Component
194
+ import { auth } from "@/lib/auth"; // your auth library
195
+ import { headers } from "next/headers";
196
+ import { createHmac } from "crypto";
197
+ import HeedbWidgetClient from "./HeedbWidget.client";
198
+
199
+ export default async function HeedbWidget() {
200
+ let email: string | undefined;
201
+ let name: string | undefined;
202
+ let userHash: string | undefined;
203
+
204
+ try {
205
+ const session = await auth.api.getSession({ headers: await headers() });
206
+ if (session?.user?.email) {
207
+ email = session.user.email;
208
+ name = session.user.name ?? undefined;
209
+ // Compute HMAC on the server — secret never reaches the client
210
+ userHash = createHmac("sha256", process.env.HEEDB_WIDGET_SECRET!)
211
+ .update(email)
212
+ .digest("hex");
213
+ }
214
+ } catch {
215
+ // No session — widget will work anonymously
216
+ }
217
+
218
+ return <HeedbWidgetClient email={email} name={name} userHash={userHash} />;
219
+ }
42
220
  ```
43
221
 
44
- Then pass it to `Heedb.init({ email, userHash })`.
222
+ ```tsx
223
+ // app/components/HeedbWidget.client.tsx — Client Component
224
+ "use client";
225
+
226
+ import { useEffect } from "react";
227
+ import { Heedb } from "@heedb/web-sdk";
228
+
229
+ export default function HeedbWidgetClient({ email, name, userHash }: {
230
+ email?: string;
231
+ name?: string;
232
+ userHash?: string;
233
+ }) {
234
+ useEffect(() => {
235
+ Heedb.init({
236
+ apiKey: process.env.NEXT_PUBLIC_HEEDB_API_KEY!,
237
+ email,
238
+ name,
239
+ userHash,
240
+ });
241
+ }, [email, name, userHash]);
242
+
243
+ return null;
244
+ }
245
+ ```
246
+
247
+ ```tsx
248
+ // app/layout.tsx
249
+ import HeedbWidget from "./components/HeedbWidget.server";
250
+
251
+ export default function RootLayout({ children }: { children: React.ReactNode }) {
252
+ return (
253
+ <html>
254
+ <body>
255
+ {children}
256
+ <HeedbWidget />
257
+ </body>
258
+ </html>
259
+ );
260
+ }
261
+ ```
262
+
263
+ ### Next.js (Pages Router)
264
+
265
+ Compute the hash in `getServerSideProps` and pass it as a prop:
266
+
267
+ ```tsx
268
+ // pages/_app.tsx
269
+ import { useEffect } from "react";
270
+ import { Heedb } from "@heedb/web-sdk";
271
+
272
+ export default function App({ Component, pageProps }: AppProps) {
273
+ useEffect(() => {
274
+ if (pageProps.heedbEmail) {
275
+ Heedb.init({
276
+ apiKey: process.env.NEXT_PUBLIC_HEEDB_API_KEY!,
277
+ email: pageProps.heedbEmail,
278
+ name: pageProps.heedbName,
279
+ userHash: pageProps.heedbUserHash,
280
+ });
281
+ } else {
282
+ Heedb.init({ apiKey: process.env.NEXT_PUBLIC_HEEDB_API_KEY! });
283
+ }
284
+ }, [pageProps.heedbEmail]);
285
+
286
+ return <Component {...pageProps} />;
287
+ }
288
+ ```
289
+
290
+ ### React (Vite / CRA) — with API route for hash
291
+
292
+ When you don't have server components, fetch the hash from an API endpoint:
293
+
294
+ ```ts
295
+ // Server: POST /api/heedb-hash
296
+ import crypto from "crypto";
297
+ export function handler(req, res) {
298
+ const { email } = req.body;
299
+ const hash = crypto
300
+ .createHmac("sha256", process.env.HEEDB_WIDGET_SECRET!)
301
+ .update(email)
302
+ .digest("hex");
303
+ res.json({ userHash: hash });
304
+ }
305
+ ```
306
+
307
+ ```tsx
308
+ // Client: App.tsx
309
+ import { useEffect } from "react";
310
+ import { Heedb } from "@heedb/web-sdk";
311
+
312
+ function App() {
313
+ const user = useAuth(); // your auth hook
314
+
315
+ useEffect(() => {
316
+ if (!user) {
317
+ Heedb.init({ apiKey: import.meta.env.VITE_HEEDB_API_KEY });
318
+ return;
319
+ }
320
+
321
+ // Fetch the hash from your server
322
+ fetch("/api/heedb-hash", {
323
+ method: "POST",
324
+ headers: { "Content-Type": "application/json" },
325
+ body: JSON.stringify({ email: user.email }),
326
+ })
327
+ .then((r) => r.json())
328
+ .then(({ userHash }) => {
329
+ Heedb.init({
330
+ apiKey: import.meta.env.VITE_HEEDB_API_KEY,
331
+ email: user.email,
332
+ name: user.name,
333
+ userHash,
334
+ });
335
+ });
336
+ }, [user]);
337
+
338
+ return <div>Your app</div>;
339
+ }
340
+ ```
341
+
342
+ ### Vue
343
+
344
+ ```vue
345
+ <script setup>
346
+ import { onMounted } from "vue";
347
+ import { Heedb } from "@heedb/web-sdk";
348
+
349
+ const props = defineProps<{
350
+ email?: string;
351
+ name?: string;
352
+ userHash?: string;
353
+ }>();
354
+
355
+ onMounted(() => {
356
+ Heedb.init({
357
+ apiKey: import.meta.env.VITE_HEEDB_API_KEY,
358
+ email: props.email,
359
+ name: props.name,
360
+ userHash: props.userHash,
361
+ });
362
+ });
363
+ </script>
364
+ ```
365
+
366
+ ### Svelte
367
+
368
+ ```svelte
369
+ <script>
370
+ import { onMount } from "svelte";
371
+ import { Heedb } from "@heedb/web-sdk";
372
+
373
+ export let email = undefined;
374
+ export let name = undefined;
375
+ export let userHash = undefined;
376
+
377
+ onMount(() => {
378
+ Heedb.init({
379
+ apiKey: import.meta.env.VITE_HEEDB_API_KEY,
380
+ email,
381
+ name,
382
+ userHash,
383
+ });
384
+ });
385
+ </script>
386
+ ```
387
+
388
+ ### Static HTML / WordPress / Webflow
389
+
390
+ Use the CDN script tag — no build tools needed:
391
+
392
+ ```html
393
+ <script
394
+ src="https://cdn.jsdelivr.net/npm/@heedb/web-sdk/dist/widget.js"
395
+ data-api-key="YOUR_API_KEY"
396
+ data-host="https://heedb.com"
397
+ ></script>
398
+ ```
399
+
400
+ For verified identity on static sites, you'll need a small server endpoint that returns the `userHash` — see the React + API route example above.
401
+
402
+ ---
403
+
404
+ ## What the widget does
405
+
406
+ The widget adds a floating button to your page with three tabs:
407
+
408
+ | Tab | Description | Requires |
409
+ |-----|-------------|----------|
410
+ | **Message** | Contact form — name, email, and a message. Creates a new support thread. Fields are hidden when the user is identified. | Nothing |
411
+ | **Privacy** | GDPR/privacy request form — data export, deletion, or opt-out. | Nothing |
412
+ | **Messages** | Conversation history — shows open threads. | Verified identity (`userHash`) |
413
+
414
+ **The full conversation loop:**
415
+
416
+ 1. Customer submits a message via the widget
417
+ 2. A thread appears in your [Heedb dashboard](https://heedb.com/app/threads)
418
+ 3. You get an email notification
419
+ 4. You reply from the dashboard — the customer receives an email
420
+ 5. The customer replies to that email — it shows up in the dashboard
421
+ 6. If the customer is verified, they can also see the full conversation in the widget's Messages tab
422
+
423
+ ---
424
+
425
+ ## Configuration reference
426
+
427
+ ### Script tag attributes
428
+
429
+ | Attribute | Required | Description |
430
+ |-----------|----------|-------------|
431
+ | `data-api-key` | Yes | Your project's public API key |
432
+ | `data-host` | CDN: Yes. Self-hosted: No | API host URL. **Required when loading from CDN** (e.g. jsDelivr). When self-hosted, defaults to the script's origin. |
433
+
434
+ ### `Heedb.init()` options
435
+
436
+ | Option | Type | Required | Description |
437
+ |--------|------|----------|-------------|
438
+ | `apiKey` | `string` | npm: Yes. Script tag: No | Your project's public API key. Script tag reads it from `data-api-key`. |
439
+ | `host` | `string` | No | API host URL. Defaults to `https://heedb.com`. |
440
+ | `email` | `string` | No | User's email — pre-fills the form and hides the email field |
441
+ | `name` | `string` | No | User's name — pre-fills the form and hides the name field |
442
+ | `userHash` | `string` | No | Server-generated HMAC-SHA256 hash — unlocks conversation history. **Must come from your server.** |
443
+
444
+ ### Behavior by identity level
445
+
446
+ | What happens | Anonymous | Identified | Verified |
447
+ |---|---|---|---|
448
+ | `init()` called | No | Yes (email + name) | Yes (email + name + userHash) |
449
+ | Contact form visible | Yes (full form) | Yes (message only) | Yes (message only) |
450
+ | Privacy tab visible | Yes | Yes | Yes |
451
+ | Messages tab visible | No | No | Yes |
452
+ | Name/email fields | Shown | Hidden | Hidden |
453
+
454
+ ---
455
+
456
+ ## Security
457
+
458
+ | Credential | Where it lives | Purpose |
459
+ |------------|---------------|---------|
460
+ | **API Key** (`NEXT_PUBLIC_HEEDB_API_KEY`) | Client-side (public) | Identifies your project. Safe to embed in frontend code. |
461
+ | **Widget Secret** (`HEEDB_WIDGET_SECRET`) | Server-side only (private) | Signs the `userHash`. **Never expose in client code, bundles, or git.** |
462
+
463
+ - The API key controls which project receives the submission — it doesn't grant read access to any data
464
+ - The `userHash` is a cryptographic proof that your server vouches for the user's email — without it, users can submit messages but can't read thread history
465
+ - Restrict which domains can use your API key under **Settings > Allowed Domains** in the dashboard
466
+ - If no allowed domains are configured, the widget works from any origin (useful for development)
467
+
468
+ ---
469
+
470
+ ## Troubleshooting
471
+
472
+ | Problem | Cause | Fix |
473
+ |---------|-------|-----|
474
+ | Widget doesn't appear | Missing `data-api-key` or invalid API key | Check the key in your [dashboard settings](https://heedb.com/app/settings) |
475
+ | "Origin not allowed" (403) | Your domain isn't in the project's allowed domains list | Add your domain in Settings > Allowed Domains, or leave the list empty to allow all |
476
+ | "Failed to load messages" | `userHash` is missing, invalid, or computed with the wrong secret | Verify you're using the Widget Secret (not the API key) and computing HMAC-SHA256 server-side |
477
+ | Widget loads but `init()` has no effect | `init()` called before script loads | With npm: `Heedb.init()` handles this automatically. With CDN: call `init()` in the script's `onload` or after `DOMContentLoaded` |
478
+ | Messages tab not visible | User is identified but not verified | Pass `userHash` from your server to enable the Messages tab |
479
+
480
+ ---
45
481
 
46
- ## Links
482
+ ## License
47
483
 
48
- - [Dashboard](https://heedb.com)
49
- - [GitHub](https://github.com/TheAleSch/heedb)
484
+ MIT
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@heedb/web-sdk",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "Drop-in feedback widget with email conversations for any website",
5
5
  "license": "MIT",
6
6
  "repository": {