Skip to content

About

Drop-in feedback widget that ends in a pull request. Preact, Shadow DOM, CSP-compliant.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

Β 

History

1 Commit

Folders and files

NameName
Last commit message
Last commit date
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

Feedby Widget

A drop-in feedback widget for capturing user feedback and turning it into actionable pull requests.

Features:

  • 🎯 Shadow DOM isolation β€” no style conflicts with host page
  • πŸ”’ Privacy-first β€” PII redaction (emails, phone numbers, SSNs, credit cards)
  • πŸš€ Lightweight β€” ~38KB gzipped (includes loader, main chunk, and bootstrap capture)
  • πŸ“Έ Rich context capture β€” console errors, network requests, DOM interactions, viewport state
  • πŸ” Content Security Policy compliant β€” works in strict CSP environments (one known exception, see docs/CSP.md)
  • 🌐 Multi-locale β€” English, Polish, and more
  • πŸ”‘ SSO-ready β€” optional JWT-verified submitter identity for B2B apps

Works everywhere:

  • Script tag embed (any website)
  • React/Vue/Svelte (same script tag, loaded from a lifecycle hook)
  • Vanilla JavaScript

Quick Start

Script Tag (Simplest)

Add this to your HTML <head>, before the opening <body>:

<meta name="feedby-build-sha" content="your-app-version">
<script>
  window.FeedbyConfig = {
    widgetKey: "wk_live_YOUR_WIDGET_KEY",
    locale: "en",
    position: "bottom-right"
  };
</script>
<script async src="https://feedby.borg.tools/v1/feedby.global.js"></script>

The widget loads asynchronously and won't block your page.

React/Vue/Svelte

This is a source repo (build it yourself, or use the hosted feedby.global.js above) β€” there is no importable npm package. In a React/Vue/Svelte app, load the same script tag from a top-level lifecycle hook instead of hardcoding it in HTML:

import { useEffect } from 'react';

export default function RootLayout({ children }) {
  useEffect(() => {
    // Set config
    window.FeedbyConfig = {
      widgetKey: "wk_live_YOUR_WIDGET_KEY",
      locale: navigator.language.split('-')[0],
      buildSha: process.env.REACT_APP_VERSION
    };

    // Load widget
    const script = document.createElement('script');
    script.async = true;
    script.src = 'https://feedby.borg.tools/v1/feedby.global.js';
    document.head.appendChild(script);
  }, []);

  return (
    <>
      {children}
    </>
  );
}

Configuration

Set window.FeedbyConfig before the widget script loads:

window.FeedbyConfig = {
  // Required
  widgetKey: "wk_live_xxx",

  // Recommended
  locale: "pl",                     // 'en', 'pl', 'de', 'fr', etc.
  position: "bottom-right",         // 'bottom-right', 'bottom-left', 'top-right', 'top-left'
  theme: "auto",                    // 'light', 'dark', 'auto'
  buildSha: "v1.2.3+abc123",        // Your app version for debugging

  // Optional: multi-tenant + SSO
  user: {
    id: "u-123",                    // User's internal ID
    email: "user@example.com",      // (requires ssoJwt)
    name: "Jane Doe",               // (requires ssoJwt)
    companyId: "org-456",           // (requires ssoJwt)
    role: "admin"                   // (requires ssoJwt)
  },
  ssoJwt: "eyJ..."                  // HS256 JWT signed by your backend
};

Authenticated Users (with JWT)

For apps with authentication, mint a server-side JWT and pass it to the widget:

Backend (Python Flask/FastAPI example):

import jwt, time, os

def mint_feedby_jwt(user_id, email, name, company_id):
    secret = os.environ["FEEDBY_SSO_SECRET"]
    project_id = os.environ["FEEDBY_PROJECT_ID"]
    
    payload = {
        "sub": str(user_id),
        "aud": project_id,
        "iat": int(time.time()),
        "exp": int(time.time()) + 3600,
        "email": email,
        "name": name,
        "company_id": company_id
    }
    return jwt.encode(payload, secret, algorithm="HS256")

Frontend:

// After user logs in
async function initFeedbyWithAuth() {
  const token = localStorage.getItem("auth_token");
  const response = await fetch("/api/feedby-jwt", {
    headers: { "Authorization": `Bearer ${token}` }
  });
  const { ssoJwt, user } = await response.json();
  
  window.FeedbyConfig = {
    widgetKey: "wk_live_xxx",
    user,
    ssoJwt
  };
  
  // Now load widget
  const script = document.createElement("script");
  script.async = true;
  script.src = "https://feedby.borg.tools/v1/feedby.global.js";
  document.head.appendChild(script);
}

Security Model

Shadow DOM Isolation

The widget lives in a Shadow DOM tree isolated from your page's styles and scripts. CSS from your page won't leak into the widget, and widget styles won't affect your app.

PII Redaction

Before sending feedback, the widget automatically redacts:

  • Email addresses (user@example.com β†’ u***@example.com)
  • Phone numbers (555-1234 β†’ 5***1234)
  • Social security numbers (*--4321)
  • Credit card numbers (****4321)
  • API keys and tokens

Redaction happens client-side before transmission.

Submitter Verification

  • submitter_source: 'verified' β€” JWT signature valid, identity proven
  • submitter_source: 'jwt_claimed' β€” JWT provided but not verified server-side (public submission)
  • submitter_source: 'unverified' β€” No JWT; user context is a claim only

To enable server-side verification, the Feedby platform must:

  1. Store an sso_secret per project
  2. Enable the SSO_ENFORCE_INGEST flag on your Feedby admin instance
  3. Your host application must mint a valid HS256 JWT using that secret

See docs/SSO.md for detailed SSO implementation.

Content Security Policy

The widget is CSP-compliant and works in strict CSP environments:

Content-Security-Policy: 
  default-src 'self'; 
  script-src 'self' https://feedby.borg.tools;
  connect-src 'self' https://feedby-admin.borg.tools;

No inline styles, no eval, no unsafe-inline for the core widget β€” one lazy-loaded optional feature (the screenshot annotate overlay) is a known exception, see docs/CSP.md.

Documentation

  • docs/EMBED.md β€” Comprehensive embedding guide (script tag, React, Vue, Svelte, multi-tenant)
  • docs/CSP.md β€” Content Security Policy compliance
  • docs/SSO.md β€” SSO JWT authentication and server-side verification
  • docs/BUDGET.md β€” Bundle size audit and optimization notes

Development

# Install dependencies
npm install

# Build
npm run build

# Watch mode
npm run dev

# Run tests
npm run test
npm run test:watch

# Type check
npm run typecheck

# Check bundle size
npm run size

# CSP compliance audit (currently reports 1 known violation β€” the
# annotate overlay's <style> tag, see docs/CSP.md#known-limitation-annotate-overlay)
npm run csp-audit

Architecture

loader (feedby.global.js) β€” IIFE that:

  1. Seeds shared capture buffers
  2. Installs always-on hooks (console, fetch, XHR, errors, clicks)
  3. Runs PII classifier
  4. Lazy-loads the main widget when user clicks FAB

main chunk (feedby-main.mjs) β€” Opens panel, handles UI, processes screenshot + annotations

bootstrap (feedby-bootstrap.global.js) β€” Optional early-load script that captures events before main widget loads (closes a capture blackout window during script loading)

See tsup.config.ts for entry points and src/ for module organization.

Feedback Platform

This widget is open source (MIT). The Feedby platform (admin dashboard, backend, CRM integration, analytics) is a separate commercial product at https://feedby.borg.tools.

License

MIT Β© 2026 Wojciech Wiesner


Questions? Open an issue on GitHub or visit feedby.borg.tools.

About

Drop-in feedback widget that ends in a pull request. Preact, Shadow DOM, CSP-compliant.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages