@duvandroid/react-blind-agents 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 (3) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +624 -45
  3. package/package.json +9 -2
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 blind-creator-inc
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 CHANGED
@@ -1,26 +1,81 @@
1
- # react-blind-agents
1
+ # @duvandroid/react-blind-agents
2
2
 
3
3
  React component for the [Blind Agents](https://blindagents.com) pixel widget — AI bug reporter, webchat, and product guides.
4
4
 
5
+ [![npm](https://img.shields.io/npm/v/@duvandroid/react-blind-agents)](https://www.npmjs.com/package/@duvandroid/react-blind-agents)
6
+ [![license](https://img.shields.io/npm/l/@duvandroid/react-blind-agents)](LICENSE)
7
+
8
+ ---
9
+
10
+ ## Table of Contents
11
+
12
+ - [Installation](#installation)
13
+ - [Quick Start](#quick-start)
14
+ - [React (Vite / CRA)](#react-vite--cra)
15
+ - [Next.js — App Router](#nextjs--app-router)
16
+ - [Next.js — Pages Router](#nextjs--pages-router)
17
+ - [Authenticated users](#authenticated-users)
18
+ - [Props reference](#props-reference)
19
+ - [HTML / Any website (script tag)](#html--any-website-script-tag)
20
+ - [Shopify](#shopify)
21
+ - [Lovable](#lovable)
22
+ - [Wix](#wix)
23
+ - [WordPress](#wordpress)
24
+ - [Webflow](#webflow)
25
+ - [Squarespace](#squarespace)
26
+ - [Ghost](#ghost)
27
+ - [Bubble](#bubble)
28
+ - [Framer](#framer)
29
+ - [Google Tag Manager](#google-tag-manager)
30
+ - [Webhooks — Slack integration](#webhooks--slack-integration)
31
+ - [Webhooks — n8n integration](#webhooks--n8n-integration)
32
+ - [Webhook payload reference](#webhook-payload-reference)
33
+ - [Signature verification](#signature-verification)
34
+
35
+ ---
36
+
5
37
  ## Installation
6
38
 
7
39
  ```bash
8
- npm install react-blind-agents
40
+ npm install @duvandroid/react-blind-agents
9
41
  ```
10
42
 
11
- ## Usage
43
+ ---
44
+
45
+ ## Quick Start
46
+
47
+ Paste this before `</body>` in any HTML file — no npm needed:
48
+
49
+ ```html
50
+ <script
51
+ src="https://cdn.blindagents.com/report.js"
52
+ data-api-key="YOUR_API_KEY"
53
+ data-primary-color="#e11d48"
54
+ data-title="Help Center"
55
+ data-report-btn-text="Report an issue"
56
+ data-btn-emoji="🔴"
57
+ data-btn-tooltip="Report an issue"
58
+ data-empty-text="No issues reported yet."
59
+ data-user-whatsapp="">
60
+ </script>
61
+ ```
62
+
63
+ > Get your API key from **Blind Agents → Settings → API Keys**.
64
+
65
+ ---
12
66
 
13
- ### Plain React (Vite, CRA)
67
+ ## React (Vite / CRA)
14
68
 
15
69
  ```tsx
16
- import { BlindAgentsWidget } from 'react-blind-agents';
70
+ // src/App.tsx
71
+ import { BlindAgentsWidget } from '@duvandroid/react-blind-agents';
17
72
 
18
73
  export default function App() {
19
74
  return (
20
75
  <>
21
76
  <MyRoutes />
22
77
  <BlindAgentsWidget
23
- apiKey="ba_YOUR_API_KEY"
78
+ apiKey="YOUR_API_KEY"
24
79
  primaryColor="#e11d48"
25
80
  title="Help Center"
26
81
  reportBtnText="Report an issue"
@@ -33,11 +88,17 @@ export default function App() {
33
88
  }
34
89
  ```
35
90
 
36
- ### Next.js App Router
91
+ Place `<BlindAgentsWidget>` once at the App root — it renders nothing in the DOM, only injects the script.
92
+
93
+ ---
94
+
95
+ ## Next.js — App Router
96
+
97
+ Import from the `/next` subpath — it uses `next/script` internally for correct hydration and strategy support.
37
98
 
38
99
  ```tsx
39
100
  // app/layout.tsx
40
- import { BlindAgentsWidget } from 'react-blind-agents/next';
101
+ import { BlindAgentsWidget } from '@duvandroid/react-blind-agents/next';
41
102
 
42
103
  export default function RootLayout({ children }: { children: React.ReactNode }) {
43
104
  return (
@@ -45,7 +106,7 @@ export default function RootLayout({ children }: { children: React.ReactNode })
45
106
  <body>
46
107
  {children}
47
108
  <BlindAgentsWidget
48
- apiKey="ba_YOUR_API_KEY"
109
+ apiKey="YOUR_API_KEY"
49
110
  primaryColor="#e11d48"
50
111
  title="Help Center"
51
112
  reportBtnText="Report an issue"
@@ -58,67 +119,585 @@ export default function RootLayout({ children }: { children: React.ReactNode })
58
119
  }
59
120
  ```
60
121
 
61
- ### Next.js with authenticated user (skip identity verification)
122
+ ---
123
+
124
+ ## Next.js — Pages Router
125
+
126
+ ```tsx
127
+ // pages/_app.tsx
128
+ import type { AppProps } from 'next/app';
129
+ import { BlindAgentsWidget } from '@duvandroid/react-blind-agents/next';
130
+
131
+ export default function App({ Component, pageProps }: AppProps) {
132
+ return (
133
+ <>
134
+ <Component {...pageProps} />
135
+ <BlindAgentsWidget apiKey="YOUR_API_KEY" primaryColor="#e11d48" />
136
+ </>
137
+ );
138
+ }
139
+ ```
140
+
141
+ ---
142
+
143
+ ## Authenticated users
144
+
145
+ Pass the logged-in user's phone or email via `userWhatsapp` to skip the identity verification step inside the widget:
62
146
 
63
147
  ```tsx
64
148
  // components/ReportWidget.tsx
65
149
  'use client';
66
- import { BlindAgentsWidget } from 'react-blind-agents/next';
150
+ import { BlindAgentsWidget } from '@duvandroid/react-blind-agents/next';
67
151
  import { useAuth } from './AuthProvider';
68
152
 
69
153
  export function ReportWidget() {
70
154
  const { user } = useAuth();
71
155
  return (
72
156
  <BlindAgentsWidget
73
- apiKey="ba_YOUR_API_KEY"
157
+ apiKey="YOUR_API_KEY"
74
158
  primaryColor="#e11d48"
75
- userWhatsapp={user?.whatsapp_number ?? user?.email ?? ''}
159
+ userWhatsapp={user?.phone ?? user?.email ?? ''}
76
160
  />
77
161
  );
78
162
  }
79
163
  ```
80
164
 
81
- ### Next.js Pages Router
165
+ Place `<ReportWidget />` **inside** your `AuthProvider` tree so `useAuth()` has access to the context.
82
166
 
83
- ```tsx
84
- // pages/_app.tsx
85
- import type { AppProps } from 'next/app';
86
- import { BlindAgentsWidget } from 'react-blind-agents/next';
167
+ ---
87
168
 
88
- export default function App({ Component, pageProps }: AppProps) {
89
- return (
90
- <>
91
- <Component {...pageProps} />
92
- <BlindAgentsWidget apiKey="ba_YOUR_API_KEY" primaryColor="#e11d48" />
93
- </>
94
- );
169
+ ## Props reference
170
+
171
+ | Prop | Type | Required | Default | Description |
172
+ |---|---|---|---|---|
173
+ | `apiKey` | `string` | ✅ | — | Your Blind Agents public API key (`ba_...`) |
174
+ | `primaryColor` | `string` | — | — | Accent color (any valid CSS color) |
175
+ | `title` | `string` | — | `"Help Center"` | Widget panel header title |
176
+ | `reportBtnText` | `string` | — | `"Report an issue"` | Report button label |
177
+ | `btnEmoji` | `string` | — | — | Emoji on the floating launcher button |
178
+ | `btnTooltip` | `string` | — | — | Tooltip on the launcher button |
179
+ | `emptyText` | `string` | — | `"No issues reported yet."` | Text shown when there are no reports |
180
+ | `userWhatsapp` | `string` | — | — | Pre-fill user phone/email — skips identity verification |
181
+ | `strategy` | `"afterInteractive" \| "lazyOnload" \| "beforeInteractive"` | — | `"afterInteractive"` | Script loading strategy |
182
+ | `src` | `string` | — | `"https://cdn.blindagents.com/report.js"` | Override CDN URL (self-hosting) |
183
+ | `onLoad` | `() => void` | — | — | Called when the script loads |
184
+ | `onError` | `(error: Error) => void` | — | — | Called if the script fails to load |
185
+
186
+ ### Why two import paths?
187
+
188
+ | Import | Use case |
189
+ |---|---|
190
+ | `@duvandroid/react-blind-agents` | Plain React — Vite, CRA, Remix, any non-Next.js |
191
+ | `@duvandroid/react-blind-agents/next` | Next.js — App Router & Pages Router |
192
+
193
+ Importing `/next` in a non-Next.js project will throw because `next/script` won't be available.
194
+
195
+ ---
196
+
197
+ ## HTML / Any website (script tag)
198
+
199
+ No npm required. Paste before `</body>`:
200
+
201
+ ```html
202
+ <script
203
+ src="https://cdn.blindagents.com/report.js"
204
+ data-api-key="YOUR_API_KEY"
205
+ data-primary-color="#e11d48"
206
+ data-title="Help Center"
207
+ data-report-btn-text="Report an issue"
208
+ data-btn-emoji="🔴"
209
+ data-btn-tooltip="Report an issue"
210
+ data-empty-text="No issues reported yet."
211
+ data-user-whatsapp="">
212
+ </script>
213
+ ```
214
+
215
+ The `data-*` attribute names map 1:1 to the React props (kebab-case → camelCase).
216
+
217
+ ---
218
+
219
+ ## Shopify
220
+
221
+ 1. **Online Store → Themes → Edit code → Layout → theme.liquid**
222
+ 2. Paste before `</body>`:
223
+
224
+ ```liquid
225
+ {%- comment -%} Blind Agents widget {%- endcomment -%}
226
+ <script
227
+ src="https://cdn.blindagents.com/report.js"
228
+ data-api-key="YOUR_API_KEY"
229
+ data-primary-color="#e11d48"
230
+ data-title="Help Center"
231
+ data-report-btn-text="Report an issue"
232
+ data-btn-emoji="🔴"
233
+ data-btn-tooltip="Report an issue"
234
+ data-user-whatsapp="{{ customer.phone | default: '' }}">
235
+ </script>
236
+ ```
237
+
238
+ The `{{ customer.phone }}` Liquid variable auto-fills logged-in customer's phone — skipping identity verification for authenticated shoppers.
239
+
240
+ For Shopify Plus headless stores (Hydrogen / Remix), use the React npm package instead.
241
+
242
+ ---
243
+
244
+ ## Lovable
245
+
246
+ **Option A — Prompt Lovable:**
247
+ > "Install @duvandroid/react-blind-agents and add a BlindAgentsWidget to App.tsx with apiKey='YOUR_API_KEY' and primaryColor='#e11d48'"
248
+
249
+ **Option B — Manual (index.html):**
250
+ ```html
251
+ <!-- Paste in index.html before </body> -->
252
+ <script
253
+ src="https://cdn.blindagents.com/report.js"
254
+ data-api-key="YOUR_API_KEY"
255
+ data-primary-color="#e11d48"
256
+ data-title="Help Center"
257
+ data-report-btn-text="Report an issue"
258
+ data-btn-emoji="🔴">
259
+ </script>
260
+ ```
261
+
262
+ ---
263
+
264
+ ## Wix
265
+
266
+ **Option A — Custom Code (no-code, recommended):**
267
+ 1. **Settings → Custom Code → + Add Custom Code**
268
+ 2. Paste the script tag, set placement to **Body – end**, apply to **All Pages**, load **Once**
269
+
270
+ **Option B — Velo:**
271
+ ```js
272
+ $w.onReady(() => {
273
+ const script = document.createElement('script');
274
+ script.src = 'https://cdn.blindagents.com/report.js';
275
+ script.setAttribute('data-api-key', 'YOUR_API_KEY');
276
+ script.setAttribute('data-primary-color', '#e11d48');
277
+ script.setAttribute('data-title', 'Help Center');
278
+ script.defer = true;
279
+ document.body.appendChild(script);
280
+ });
281
+ ```
282
+
283
+ ---
284
+
285
+ ## WordPress
286
+
287
+ **Via functions.php (child theme):**
288
+ ```php
289
+ function blindagents_widget() {
290
+ echo '<script
291
+ src="https://cdn.blindagents.com/report.js"
292
+ data-api-key="YOUR_API_KEY"
293
+ data-primary-color="#e11d48"
294
+ data-title="Help Center"
295
+ data-report-btn-text="Report an issue"
296
+ data-btn-emoji="🔴"
297
+ defer>
298
+ </script>';
299
+ }
300
+ add_action('wp_footer', 'blindagents_widget');
301
+ ```
302
+
303
+ **Via plugin (no-code):** Install **WPCode** or **Insert Headers and Footers**, paste the script tag in the Footer section.
304
+
305
+ **Via Elementor:** Custom Code → Body End.
306
+
307
+ ---
308
+
309
+ ## Webflow
310
+
311
+ **Site Settings → Custom Code → Footer Code:**
312
+ ```html
313
+ <script
314
+ src="https://cdn.blindagents.com/report.js"
315
+ data-api-key="YOUR_API_KEY"
316
+ data-primary-color="#e11d48"
317
+ data-title="Help Center"
318
+ data-report-btn-text="Report an issue"
319
+ data-btn-emoji="🔴"
320
+ defer>
321
+ </script>
322
+ ```
323
+
324
+ ---
325
+
326
+ ## Squarespace
327
+
328
+ > Requires Business plan or above.
329
+
330
+ **Settings → Advanced → Code Injection → Footer:**
331
+ ```html
332
+ <script
333
+ src="https://cdn.blindagents.com/report.js"
334
+ data-api-key="YOUR_API_KEY"
335
+ data-primary-color="#e11d48"
336
+ data-title="Help Center"
337
+ data-report-btn-text="Report an issue"
338
+ data-btn-emoji="🔴"
339
+ defer>
340
+ </script>
341
+ ```
342
+
343
+ ---
344
+
345
+ ## Ghost
346
+
347
+ **Admin → Settings → Code Injection → Site Footer:**
348
+ ```html
349
+ <script
350
+ src="https://cdn.blindagents.com/report.js"
351
+ data-api-key="YOUR_API_KEY"
352
+ data-primary-color="#e11d48"
353
+ data-title="Help Center"
354
+ data-report-btn-text="Report an issue"
355
+ data-btn-emoji="🔴"
356
+ defer>
357
+ </script>
358
+ ```
359
+
360
+ ---
361
+
362
+ ## Bubble
363
+
364
+ **Settings → SEO / metatags → Script/meta tags in header:**
365
+ ```html
366
+ <script
367
+ src="https://cdn.blindagents.com/report.js"
368
+ data-api-key="YOUR_API_KEY"
369
+ data-primary-color="#e11d48"
370
+ data-title="Help Center"
371
+ data-report-btn-text="Report an issue"
372
+ data-btn-emoji="🔴"
373
+ defer>
374
+ </script>
375
+ ```
376
+
377
+ Or add an **HTML element** on any page and paste the script tag there.
378
+
379
+ ---
380
+
381
+ ## Framer
382
+
383
+ **Site Settings → General → Custom Code → End of body tag:**
384
+ ```html
385
+ <script
386
+ src="https://cdn.blindagents.com/report.js"
387
+ data-api-key="YOUR_API_KEY"
388
+ data-primary-color="#e11d48"
389
+ data-title="Help Center"
390
+ data-report-btn-text="Report an issue"
391
+ data-btn-emoji="🔴"
392
+ defer>
393
+ </script>
394
+ ```
395
+
396
+ > Requires a Framer paid plan.
397
+
398
+ ---
399
+
400
+ ## Google Tag Manager
401
+
402
+ 1. **Tags → New → Custom HTML**
403
+ 2. Paste:
404
+
405
+ ```html
406
+ <script>
407
+ (function() {
408
+ var el = document.createElement('script');
409
+ el.src = 'https://cdn.blindagents.com/report.js';
410
+ el.setAttribute('data-api-key', 'YOUR_API_KEY');
411
+ el.setAttribute('data-primary-color', '#e11d48');
412
+ el.setAttribute('data-title', 'Help Center');
413
+ el.setAttribute('data-report-btn-text', 'Report an issue');
414
+ el.setAttribute('data-btn-emoji', '🔴');
415
+ el.defer = true;
416
+ document.head.appendChild(el);
417
+ })();
418
+ </script>
419
+ ```
420
+
421
+ 3. Trigger: **All Pages — Page View**
422
+ 4. Submit and publish
423
+
424
+ ---
425
+
426
+ ## Webhooks — Slack integration
427
+
428
+ Get real-time Slack notifications for every Blind Agents event.
429
+
430
+ ### 1. Create a Slack Incoming Webhook
431
+
432
+ 1. Go to [api.slack.com/apps](https://api.slack.com/apps) → **Create New App → From scratch**
433
+ 2. **Features → Incoming Webhooks** → enable → **Add New Webhook to Workspace**
434
+ 3. Select the channel (e.g. `#bugs`) and copy the webhook URL
435
+
436
+ Test it:
437
+ ```bash
438
+ curl -X POST https://hooks.slack.com/services/YOUR/SLACK/WEBHOOK \
439
+ -H "Content-Type: application/json" \
440
+ -d '{"text": "Blind Agents test message ✅"}'
441
+ ```
442
+
443
+ ### 2. Create a Blind Agents webhook
444
+
445
+ In **Blind Agents → Webhooks → Add webhook**, set the URL to your server endpoint and select events.
446
+
447
+ ### 3. Forward events to Slack (Node.js / Express)
448
+
449
+ ```ts
450
+ import express from 'express';
451
+ import crypto from 'crypto';
452
+
453
+ const app = express();
454
+ app.use(express.raw({ type: 'application/json' })); // raw body required for signature check
455
+
456
+ const BA_SECRET = process.env.BLIND_AGENTS_WEBHOOK_SECRET;
457
+ const SLACK_URL = process.env.SLACK_WEBHOOK_URL;
458
+
459
+ function verify(rawBody: string, signature: string) {
460
+ const parts = Object.fromEntries(signature.split(',').map(p => p.split('=')));
461
+ const expected = crypto.createHmac('sha256', BA_SECRET!)
462
+ .update(`${parts.t}.${rawBody}`).digest('hex');
463
+ const valid = crypto.timingSafeEqual(Buffer.from(parts.v1, 'hex'), Buffer.from(expected, 'hex'));
464
+ const recent = Date.now() / 1000 - Number(parts.t) < 300;
465
+ return valid && recent;
466
+ }
467
+
468
+ app.post('/webhooks/blind-agents', async (req, res) => {
469
+ const sig = req.headers['x-blindagents-signature'] as string;
470
+ if (!verify(req.body.toString(), sig)) return res.sendStatus(401);
471
+
472
+ const { event, data } = JSON.parse(req.body.toString());
473
+
474
+ const messages: Record<string, string> = {
475
+ 'ticket.created': `🎫 *New ticket:* <${data.page_url}|${data.title}> — ${data.priority} priority`,
476
+ 'ticket.status_changed': `🔄 *Ticket updated:* ${data.title} → ${data.status}`,
477
+ 'ticket.resolved': `✅ *Ticket resolved:* ${data.title}`,
478
+ 'contact.created': `👤 *New contact:* ${data.name} (${data.email})`,
479
+ };
480
+
481
+ await fetch(SLACK_URL!, {
482
+ method: 'POST',
483
+ headers: { 'Content-Type': 'application/json' },
484
+ body: JSON.stringify({ text: messages[event] ?? `📡 Blind Agents event: ${event}` }),
485
+ });
486
+
487
+ res.sendStatus(200);
488
+ });
489
+ ```
490
+
491
+ ### 4. Rich Slack messages with Block Kit (optional)
492
+
493
+ ```ts
494
+ const block = {
495
+ blocks: [
496
+ { type: 'header', text: { type: 'plain_text', text: '🎫 New Bug Report' } },
497
+ {
498
+ type: 'section',
499
+ fields: [
500
+ { type: 'mrkdwn', text: `*Title:*\n${data.title}` },
501
+ { type: 'mrkdwn', text: `*Priority:*\n${data.priority}` },
502
+ { type: 'mrkdwn', text: `*Reporter:*\n${data.contact?.name ?? 'Anonymous'}` },
503
+ { type: 'mrkdwn', text: `*Page:*\n${data.page_url ?? '—'}` },
504
+ ],
505
+ },
506
+ {
507
+ type: 'actions',
508
+ elements: [{
509
+ type: 'button',
510
+ text: { type: 'plain_text', text: 'View ticket' },
511
+ url: `https://app.blindagents.com/tickets/${data.id}`,
512
+ style: 'primary',
513
+ }],
514
+ },
515
+ ],
516
+ };
517
+ ```
518
+
519
+ ---
520
+
521
+ ## Webhooks — n8n integration
522
+
523
+ Connect Blind Agents to any service (Slack, Jira, Notion, Google Sheets, HubSpot…) without writing a server.
524
+
525
+ ### 1. Add a Webhook trigger node in n8n
526
+
527
+ 1. Create a new workflow in n8n
528
+ 2. Add a **Webhook** node → Method: **POST** → copy the URL
529
+ 3. In **Blind Agents → Webhooks → Add webhook**, paste that URL and select events
530
+ 4. Copy the **signing secret**
531
+
532
+ ### 2. Verify the signature (Code node)
533
+
534
+ Add a **Code** node after the Webhook trigger. Store the secret in **n8n Credentials** as `BLIND_AGENTS_SECRET`:
535
+
536
+ ```js
537
+ const crypto = require('crypto');
538
+
539
+ const secret = $env.BLIND_AGENTS_SECRET;
540
+ const signature = $input.first().headers['x-blindagents-signature'];
541
+ const rawBody = JSON.stringify($input.first().body);
542
+
543
+ const parts = Object.fromEntries(signature.split(',').map(p => p.split('=')));
544
+ const hmac = crypto.createHmac('sha256', secret).update(`${parts.t}.${rawBody}`).digest('hex');
545
+
546
+ const isValid = crypto.timingSafeEqual(Buffer.from(parts.v1, 'hex'), Buffer.from(hmac, 'hex'));
547
+ const isRecent = Math.abs(Date.now() / 1000 - Number(parts.t)) < 300;
548
+
549
+ if (!isValid || !isRecent) throw new Error('Invalid signature — aborting workflow');
550
+
551
+ return $input.all();
552
+ ```
553
+
554
+ ### 3. Send to Slack (Slack node)
555
+
556
+ Add a **Slack** node, connect it to your workspace, and set the message text using expressions:
557
+
558
+ ```
559
+ 🎫 New ticket: {{ $json.body.data.title }}
560
+ Priority: {{ $json.body.data.priority }}
561
+ Reporter: {{ $json.body.data.contact.name }}
562
+ Page: {{ $json.body.data.page_url }}
563
+ ```
564
+
565
+ ### 4. Always respond with 200
566
+
567
+ Add a **Respond to Webhook** node at the end — Response Code `200`. Without it, Blind Agents marks the delivery as failed and retries.
568
+
569
+ ### Other n8n workflow ideas
570
+
571
+ | Trigger | Action |
572
+ |---|---|
573
+ | `ticket.created` | Create Jira / Linear issue |
574
+ | `ticket.created` (high priority) | Send Gmail alert to on-call |
575
+ | `ticket.resolved` | Append row to Google Sheet |
576
+ | `contact.created` | Sync contact to HubSpot / Mailchimp |
577
+ | `ticket.status_changed` | Update Notion database row |
578
+ | Any event | POST to Zapier catch hook for further routing |
579
+
580
+ ### Self-hosting n8n with Docker
581
+
582
+ ```bash
583
+ docker run -it --rm \
584
+ --name n8n \
585
+ -p 5678:5678 \
586
+ -e N8N_BASIC_AUTH_ACTIVE=true \
587
+ -e N8N_BASIC_AUTH_USER=admin \
588
+ -e N8N_BASIC_AUTH_PASSWORD=yourpassword \
589
+ -v ~/.n8n:/home/node/.n8n \
590
+ docker.n8n.io/n8nio/n8n
591
+ ```
592
+
593
+ Expose port 5678 via your domain and use `https://your-domain.com/webhook/...` as the Blind Agents webhook URL.
594
+
595
+ ---
596
+
597
+ ## Webhook payload reference
598
+
599
+ Every event sends this JSON body:
600
+
601
+ ```json
602
+ {
603
+ "id": "evt_01HXYZ...",
604
+ "event": "ticket.created",
605
+ "created_at": "2026-04-09T03:00:00Z",
606
+ "data": {
607
+ "id": "tkt_01HXYZ...",
608
+ "title": "Button not working on checkout",
609
+ "status": "open",
610
+ "priority": "high",
611
+ "type": "bug",
612
+ "contact": { "name": "Jane Doe", "email": "jane@example.com" },
613
+ "page_url": "https://yoursite.com/checkout"
614
+ }
615
+ }
616
+ ```
617
+
618
+ ### Supported events
619
+
620
+ | Event | Fired when |
621
+ |---|---|
622
+ | `ticket.created` | A new ticket is submitted |
623
+ | `ticket.status_changed` | Ticket status is updated |
624
+ | `ticket.resolved` | Ticket is marked resolved |
625
+ | `ticket.closed` | Ticket is closed |
626
+ | `contact.created` | A new contact is registered |
627
+ | `conversation.created` | A new chat conversation starts |
628
+
629
+ ---
630
+
631
+ ## Signature verification
632
+
633
+ Every request includes an `X-BlindAgents-Signature` header:
634
+
635
+ ```
636
+ t={unix_timestamp},v1={hmac_sha256(secret, "{timestamp}.{raw_body}")}
637
+ ```
638
+
639
+ **Node.js:**
640
+ ```ts
641
+ import crypto from 'crypto';
642
+
643
+ function verifyWebhook(rawBody: string, signature: string, secret: string) {
644
+ const parts = Object.fromEntries(signature.split(',').map(p => p.split('=')));
645
+ const expected = crypto
646
+ .createHmac('sha256', secret)
647
+ .update(`${parts.t}.${rawBody}`)
648
+ .digest('hex');
649
+ const valid = crypto.timingSafeEqual(Buffer.from(parts.v1, 'hex'), Buffer.from(expected, 'hex'));
650
+ const recent = Date.now() / 1000 - Number(parts.t) < 300;
651
+ return valid && recent;
95
652
  }
96
653
  ```
97
654
 
98
- ## Props
655
+ **Python:**
656
+ ```python
657
+ import hmac, hashlib, time
658
+
659
+ def verify_webhook(raw_body: bytes, signature: str, secret: str) -> bool:
660
+ parts = dict(p.split("=", 1) for p in signature.split(","))
661
+ msg = f"{parts['t']}.".encode() + raw_body
662
+ expected = hmac.new(secret.encode(), msg, hashlib.sha256).hexdigest()
663
+ return (
664
+ hmac.compare_digest(parts["v1"], expected)
665
+ and abs(time.time() - int(parts["t"])) < 300
666
+ )
667
+ ```
99
668
 
100
- | Prop | Type | Default | Description |
101
- |-----------------|---------------------------------------------------|--------------------------------------------|-----------------------------------------------------|
102
- | `apiKey` | `string` | **required** | Your Blind Agents public API key (`ba_...`) |
103
- | `primaryColor` | `string` | — | Accent color (any valid CSS color) |
104
- | `title` | `string` | `"Help Center"` | Widget panel header title |
105
- | `reportBtnText` | `string` | `"Report an issue"` | Report button label |
106
- | `btnEmoji` | `string` | — | Emoji on the floating launcher button |
107
- | `btnTooltip` | `string` | — | Tooltip on the launcher button |
108
- | `emptyText` | `string` | `"No issues reported yet."` | Text shown when there are no reports |
109
- | `userWhatsapp` | `string` | — | Pre-fill user identity (WhatsApp number or email) |
110
- | `strategy` | `"afterInteractive" \| "lazyOnload" \| "beforeInteractive"` | `"afterInteractive"` | Script loading strategy |
111
- | `src` | `string` | `"https://cdn.blindagents.com/report.js"` | Override CDN URL (self-hosting) |
112
- | `onLoad` | `() => void` | — | Called when the script loads successfully |
113
- | `onError` | `(error: Error) => void` | — | Called if the script fails to load |
669
+ **Go:**
670
+ ```go
671
+ import (
672
+ "crypto/hmac"
673
+ "crypto/sha256"
674
+ "encoding/hex"
675
+ "math"
676
+ "strings"
677
+ "strconv"
678
+ "time"
679
+ )
114
680
 
115
- ## Why two import paths?
681
+ func VerifyWebhook(rawBody []byte, signature, secret string) bool {
682
+ parts := map[string]string{}
683
+ for _, p := range strings.Split(signature, ",") {
684
+ kv := strings.SplitN(p, "=", 2)
685
+ if len(kv) == 2 { parts[kv[0]] = kv[1] }
686
+ }
687
+ ts, _ := strconv.ParseInt(parts["t"], 10, 64)
688
+ msg := append([]byte(parts["t"]+"."), rawBody...)
689
+ mac := hmac.New(sha256.New, []byte(secret))
690
+ mac.Write(msg)
691
+ expected, _ := hex.DecodeString(hex.EncodeToString(mac.Sum(nil)))
692
+ got, _ := hex.DecodeString(parts["v1"])
693
+ return hmac.Equal(got, expected) && math.Abs(float64(time.Now().Unix()-ts)) < 300
694
+ }
695
+ ```
116
696
 
117
- - `react-blind-agents` plain React. Injects the script via `useEffect`. Works anywhere React runs.
118
- - `react-blind-agents/next` — Next.js only. Uses `next/script` for correct hydration, strategy support, and deduplication across navigations.
697
+ > Always use constant-time comparison (`timingSafeEqual` / `hmac.Equal` / `hmac.compare_digest`) and reject requests older than 5 minutes to prevent replay attacks.
119
698
 
120
- Importing `react-blind-agents/next` in a non-Next.js app will throw because `next/script` won't be available.
699
+ ---
121
700
 
122
701
  ## License
123
702
 
124
- MIT
703
+ MIT © [Blind Agents](https://blindagents.com)
package/package.json CHANGED
@@ -1,10 +1,17 @@
1
1
  {
2
2
  "name": "@duvandroid/react-blind-agents",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "React component for the Blind Agents pixel widget — bug reporter, webchat, and product guides.",
5
5
  "author": "Blind Agents <hola@blindagents.com>",
6
6
  "license": "MIT",
7
- "keywords": ["react", "blind-agents", "widget", "bug-reporter", "webchat", "next"],
7
+ "keywords": [
8
+ "react",
9
+ "blind-agents",
10
+ "widget",
11
+ "bug-reporter",
12
+ "webchat",
13
+ "next"
14
+ ],
8
15
  "main": "./dist/index.cjs",
9
16
  "module": "./dist/index.js",
10
17
  "types": "./dist/index.d.ts",