@synod-ai/extension-feedback
v0.1.6
Published
Synod feedback extension core SDK — HTTP client, lifecycle management, and React wrapper for submitting external findings.
Downloads
291
Readme
@synod-ai/extension-feedback
Synod Feedback Extension SDK — submit in-app feedback as structured findings to a Synod gateway instance. Captures element context (CSS selector, viewport, route), optional screenshots, and queues findings for batch submission with retry.
Installation
npm install @synod-ai/extension-feedbackPeer Dependencies
- React ≥18 and react-dom ≥18 (only if using the React wrapper)
- The core SDK is framework-agnostic — zero React imports
Quick Start
Vanilla JavaScript
import { init, enable, queue, sendBatch } from '@synod-ai/extension-feedback';
// 1. Configure with your gateway URL, project ID, and extension token
init({
gatewayUrl: 'http://localhost:8787',
projectId: 'your-project-id',
token: 'your-extension-token',
});
// 2. Enable the feedback overlay (key-combo listener activates)
enable();
// 3. Queue a finding manually (or use the built-in element picker via Ctrl+Shift+E)
queue({
title: 'Button contrast too low',
description: 'The checkout button fails WCAG AA contrast at 2.4:1',
severity: 'high',
context_json: {
url: 'https://shop.example.com/checkout',
css_selector: '#checkout-btn',
element_text: 'Pay Now',
viewport: { width: 1440, height: 900 },
route: '/checkout',
},
});
// 4. Submit batch to the gateway
const result = await sendBatch();
console.log(`Created: ${result.created.length}, Errors: ${result.errors.length}`);React
import { FeedbackProvider, useFeedback } from '@synod-ai/extension-feedback/react';
// Wrap your app
function App() {
return (
<FeedbackProvider
config={{
gatewayUrl: 'http://localhost:8787',
projectId: 'your-project-id',
token: 'your-extension-token',
keyCombo: 'Ctrl+Shift+E',
}}
autoEnable
>
<YourApp />
</FeedbackProvider>
);
}
// Use the hook in any child component
function FeedbackButton() {
const { isEnabled, enable, disable, sendBatch } = useFeedback();
return (
<button onClick={() => (isEnabled ? disable() : enable())}>
{isEnabled ? 'Disable Feedback' : 'Enable Feedback'}
</button>
);
}Configuration
FeedbackConfig
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
| gatewayUrl | string | Yes (defaults if omitted) | http://localhost:8787 | Synod gateway base URL |
| projectId | string | Yes | — | Target project ID |
| token | string | Yes | — | Extension auth token (generated in Desktop → Extensions settings tab) |
| keyCombo | string | No | Ctrl+Shift+E | Key combo to activate the feedback overlay (hold ~500ms) |
| timeout | number | No | 15000 | Request timeout in ms |
| maxRetries | number | No | 3 | Max retry attempts on transient failures |
Key Combo Format
The keyCombo string uses +-separated modifiers + key:
Ctrl+Shift+E (default — avoids Ctrl+Shift+F browser fullscreen conflict)
Ctrl+Alt+F
Shift+QSupported modifiers: Ctrl (or Control), Shift, Alt.
Hold detection: the combo must be held for ~500ms to activate, preventing accidental triggers.
API Reference
Core Functions (@synod-ai/extension-feedback)
| Function | Description |
|---|---|
| init(config) | Initialize the SDK with gateway config |
| enable() | Enable the feedback overlay + key-combo listener |
| disable() | Disable the feedback overlay |
| isEnabled() | Returns true if the overlay is active |
| queue(finding) | Add a finding to the submission queue |
| sendBatch() | Submit all queued findings to the gateway (returns Promise<BatchResult>) |
| clearQueue() | Remove all findings from the queue |
| getQueueLength() | Current queue size |
React (@synod-ai/extension-feedback/react)
| Export | Description |
|---|---|
| <FeedbackProvider config={...} autoEnable?> | Context provider that manages the FeedbackManager lifecycle |
| useFeedback() | Hook returning { init, enable, disable, sendBatch, queue, isEnabled, enabled } |
Types
type FindingSeverity = 'low' | 'medium' | 'high';
interface FindingContextJson {
url?: string;
css_selector?: string;
element_text?: string;
viewport?: { width: number; height: number };
route?: string;
screenshot_data_url?: string; // data:image/* URL, ≤10KB total context budget
}
interface FindingPayload {
title: string;
description: string;
severity: FindingSeverity;
suggested_action?: 'create_story';
context_json: FindingContextJson;
}Gateway Setup
1. Token Generation
Generate an extension token from the Synod Desktop app:
- Open Settings → Extensions tab
- Click Generate Token
- Copy the token and pass it to
init({ token })
2. Project ID
Find your project ID in the Synod Desktop sidebar or via GET /v2/projects/active.
Network Topology
The extension SDK communicates with the Synod gateway via HTTP POST /v2/findings/external. The gateway URL determines how the extension connects.
Localhost (Default)
Browser (your app) → http://localhost:8787 → Synod Gateway- Default mode — works for same-machine testing
- Chrome and Firefox have a localhost mixed-content exemption:
fetch()from an HTTPS page tohttp://localhost:*is allowed - No additional setup needed
- Gateway URL:
http://localhost:8787
LAN (Same Network)
Browser (other device) → http://192.168.x.x:8787 → Synod Gateway (0.0.0.0 binding)- For testing from other devices on the same local network
- Bind the gateway to
0.0.0.0to accept LAN connections - Gateway URL:
http://<your-lan-ip>:8787 - Note: Only works if your app is served over HTTP. If your app is HTTPS, see Tunnel mode below.
Tunnel (HTTPS Apps / Remote Access)
Browser (HTTPS app) → https://your-tunnel.ngrok.io → Synod GatewayWhen to use: Your web app is served over HTTPS and needs to call the gateway. Browsers block fetch() from HTTPS pages to HTTP endpoints (mixed content blocking). The localhost exemption does not apply to LAN IPs — only to localhost / 127.0.0.1.
Setup with ngrok:
# Expose the Synod gateway (default port 8787) via HTTPS
ngrok http 8787
# Copy the HTTPS forwarding URL (e.g., https://abc123.ngrok.io)
# Use it as the gatewayUrl:init({
gatewayUrl: 'https://abc123.ngrok.io',
projectId: 'your-project-id',
token: 'your-extension-token',
});Setup with Cloudflare Tunnel:
cloudflared tunnel --url http://localhost:8787
# Copy the generated HTTPS URLMixed Content Guidance
| App URL | Gateway URL | Works? | Notes |
|---|---|---|---|
| http://localhost:3000 | http://localhost:8787 | ✅ | Same-origin HTTP, no issue |
| https://localhost:3000 | http://localhost:8787 | ✅ | Localhost mixed-content exemption |
| https://app.example.com | http://localhost:8787 | ❌ | Mixed content blocked — use tunnel |
| https://app.example.com | http://192.168.x.x:8787 | ❌ | Mixed content blocked — use tunnel |
| https://app.example.com | https://tunnel.ngrok.io | ✅ | HTTPS-to-HTTPS via tunnel |
Recommendation: For production HTTPS environments, always use a tunnel (ngrok or Cloudflare Tunnel) to provide an HTTPS endpoint for the gateway.
Context JSON
The context_json object captures browser context for each finding. All fields are optional; the SDK enforces a 64KB total budget (MAX_CONTEXT_JSON_BYTES = 65_536), of which the screenshot may consume up to 32KB (MAX_SCREENSHOT_BYTES).
| Field | Type | Description |
|---|---|---|
| url | string | Full page URL |
| css_selector | string | CSS selector of the target element |
| element_text | string | Text content of the target element |
| viewport | { width, height } | Browser viewport dimensions |
| route | string | Current route/pathname |
| screenshot_data_url | string | Base64 data:image/* screenshot (pre-compressed to fit budget) |
| user_agent | string | Browser user agent |
| batch_id | string | Batch correlation ID |
| extension_version | string | SDK version |
Screenshot Size Limit
The screenshot_data_url must fit within the 10KB total context_json budget. The SDK pre-compresses screenshots before queuing. If the serialized context exceeds 10KB, the gateway rejects the batch with a CONTEXT_TOO_LARGE error.
Error Handling
import { sendBatch } from '@synod-ai/extension-feedback';
try {
const result = await sendBatch();
// result.created: array of { id, title, severity }
// result.errors: array of { index, error }
} catch (err) {
// err.code: 'AUTH_ERROR' | 'RATE_LIMITED' | 'NETWORK_ERROR' | 'TIMEOUT' | 'SERVER_ERROR' | 'CONTEXT_TOO_LARGE'
console.error(`[${err.code}] ${err.message}`);
if (err.retryable) {
// SDK already retries automatically (maxRetries), but you can re-queue
}
}| Error Code | Retryable | Description |
|---|---|---|
| AUTH_ERROR | No | Invalid or expired token |
| RATE_LIMITED | Yes | Gateway rate limit hit (429) |
| NETWORK_ERROR | Yes | Connection failed |
| TIMEOUT | Yes | Request exceeded timeout |
| SERVER_ERROR | Yes | Gateway 5xx response |
| CONTEXT_TOO_LARGE | No | context_json exceeds 10KB budget |
License
MIT
