npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@fdback.io/sdk

v0.5.0

Published

TypeScript SDK for Fdback user feedback platform

Readme

@fdback.io/sdk

Official TypeScript SDK for integrating Fdback user feedback into your application.

Features

  • Type-safe - Full TypeScript support with detailed type definitions
  • Isomorphic - Works in Node.js and browsers
  • Secure - HMAC-SHA256 signed authentication
  • Framework support - First-class React and Next.js integrations
  • Lightweight - ~3.7KB core, ~5KB for framework bindings

Installation

npm install @fdback.io/sdk
# or
pnpm add @fdback.io/sdk
# or
yarn add @fdback.io/sdk

Quick Start

1. Get your credentials

Get your Workspace ID and Workspace Secret from your Fdback dashboard under Settings > SDK Credentials.

2. Server-side: Sign user data

import { FdbackServer } from "@fdback.io/sdk";

const fdback = new FdbackServer({
  workspaceId: process.env.FDBACK_WORKSPACE_ID!,
  workspaceSecret: process.env.FDBACK_WORKSPACE_SECRET!,
});

// Create signed login data for a user
const signedData = await fdback.createSignedLoginData({
  email: "[email protected]",
  name: "John Doe",
  avatar: "https://example.com/avatar.jpg", // optional
});

3. Client-side: Open feedback board

import { FdbackClient } from "@fdback.io/sdk";

const fdback = new FdbackClient({
  workspaceId: "your-workspace-id",
});

// Open with signed data from your server
fdback.open(signedData, { mode: "popup" });

Note: FdbackServer and FdbackClient are aliases for the same Fdback class. The naming makes it clearer which environment you're in.


Security Model

The workspace secret is used to sign requests with HMAC-SHA256. Never expose the secret in client-side code.

| Environment | Approach | |-------------|----------| | Server-side | Use workspaceSecret directly | | Client-side | Fetch signed data from your backend API |


Core SDK

Fdback Class

import { Fdback } from "@fdback.io/sdk";

const fdback = new Fdback({
  workspaceId: string;       // Required
  workspaceSecret?: string;  // Server-side only
  baseUrl?: string;          // Default: "https://app.fdback.io"
});

Methods

| Method | Environment | Description | |--------|-------------|-------------| | createSignedLoginData(user) | Server | Create signed auth data for a user | | getLoginUrl(user) | Server | Get a signed login URL for redirects | | open(signedData, options?) | Browser | Open feedback board in popup/tab | | openDirect(user, options?) | Browser | Sign and open (requires secret - not recommended) | | getBoardUrl() | Both | Get the public board URL | | isServerMode() | Both | Check if secret is configured |

Types

interface FdbackUser {
  email: string;      // Required
  name?: string;      // Display name
  avatar?: string;    // Avatar URL
}

interface SignedLoginData {
  url: string;
  headers: {
    "x-workspace-id": string;
    "x-timestamp": string;
    "x-signature": string;
  };
  body: string;
  expiresAt: number;  // Expires in 5 minutes
}

interface OpenOptions {
  mode?: "popup" | "tab" | "redirect";  // Default: "popup"
  width?: number;   // Default: 800
  height?: number;  // Default: 700
}

React Integration

npm install @fdback.io/sdk react

Setup Provider

// app/providers.tsx or app/layout.tsx
import { FdbackProvider } from "@fdback.io/sdk/react";

export function Providers({ children }: { children: React.ReactNode }) {
  return (
    <FdbackProvider
      workspaceId="your-workspace-id"
      signEndpoint="/api/fdback/sign"
    >
      {children}
    </FdbackProvider>
  );
}

FeedbackButton Component

The easiest way to add feedback - a ready-to-use button:

"use client";
import { FeedbackButton } from "@fdback.io/sdk/react";

export function Header() {
  const user = useCurrentUser(); // Your auth hook

  return (
    <FeedbackButton
      user={{ email: user.email, name: user.name }}
      className="btn btn-primary"
      onOpen={(win) => console.log("Opened!", win)}
      onError={(err) => console.error(err)}
    >
      Give Feedback
    </FeedbackButton>
  );
}

useFdbackLogin Hook

For custom implementations:

"use client";
import { useFdbackLogin } from "@fdback.io/sdk/react";

export function CustomFeedback() {
  const { login, isLoading, error } = useFdbackLogin();

  const handleClick = async () => {
    await login(
      { email: "[email protected]", name: "John" },
      { mode: "popup" }
    );
  };

  return (
    <button onClick={handleClick} disabled={isLoading}>
      {isLoading ? "Opening..." : "Feedback"}
    </button>
  );
}

useFdback Hook

Access the Fdback context directly:

"use client";
import { useFdback } from "@fdback.io/sdk/react";

export function FeedbackStatus() {
  const { isOpen, getBoardUrl, workspaceId } = useFdback();

  return (
    <div>
      <p>Workspace: {workspaceId}</p>
      <p>Board URL: {getBoardUrl()}</p>
      <p>Popup open: {isOpen ? "Yes" : "No"}</p>
    </div>
  );
}

FdbackWidget Component

Embed the full widget (feedback, roadmap, changelog tabs) directly in your React app:

"use client";
import { FdbackWidget } from "@fdback.io/sdk/react";

// Uncontrolled - uses default floating launcher button
export function App() {
  return (
    <FdbackWidget
      workspaceId="your-workspace-id"
      mode="full"
      tabs={["feedback", "roadmap", "changelog"]}
      theme="dark"  // or "light" or "auto"
      primaryColor="#6366f1"
    />
  );
}

// Controlled - use your own trigger button
export function CustomTrigger() {
  const [open, setOpen] = useState(false);

  return (
    <>
      <button onClick={() => setOpen(true)}>Open Feedback</button>
      <FdbackWidget
        workspaceId="your-workspace-id"
        open={open}
        onOpenChange={setOpen}
        showLauncher={false}
      />
    </>
  );
}

FdbackWidget Props

| Prop | Type | Default | Description | |------|------|---------|-------------| | workspaceId | string | required | Your workspace ID | | mode | "simple" \| "full" | "simple" | Simple = feedback only, Full = tabs | | tabs | ("feedback" \| "roadmap" \| "changelog")[] | ["feedback"] | Tabs to show in full mode | | theme | "auto" \| "dark" \| "light" | "auto" | Theme mode | | primaryColor | string | - | Hex color for accent | | position | "bottom-right" \| "bottom-left" \| "top-right" \| "top-left" | "bottom-right" | Launcher position | | open | boolean | - | Controlled open state | | onOpenChange | (open: boolean) => void | - | Open state change callback | | showLauncher | boolean | true | Show the floating launcher button | | signEndpoint | string | - | Auth endpoint URL |

React API Reference

| Export | Type | Description | |--------|------|-------------| | FdbackProvider | Component | Context provider | | FdbackWidget | Component | Embeddable widget with tabs | | FeedbackButton | Component | Ready-to-use button | | useFdback | Hook | Access context (client, isOpen, etc.) | | useFdbackLogin | Hook | Login + open with loading/error state | | useWidget | Hook | Programmatic widget control |


Next.js Integration

npm install @fdback.io/sdk server-only

The /next subpath includes server-only protection to prevent accidental client-side imports.

Option 1: API Route Handler

Create a route that handles signing:

// app/api/fdback/sign/route.ts
import { createSignHandler } from "@fdback.io/sdk/next";

export const POST = createSignHandler({
  workspaceId: process.env.FDBACK_WORKSPACE_ID!,
  workspaceSecret: process.env.FDBACK_WORKSPACE_SECRET!,
});

With authentication:

// app/api/fdback/sign/route.ts
import { createSignHandler } from "@fdback.io/sdk/next";
import { auth } from "@/server/auth";

export const POST = createSignHandler({
  workspaceId: process.env.FDBACK_WORKSPACE_ID!,
  workspaceSecret: process.env.FDBACK_WORKSPACE_SECRET!,
  authorize: async () => {
    const session = await auth();
    return !!session?.user;
  },
  getUser: async () => {
    const session = await auth();
    if (!session?.user?.email) return null;
    return {
      email: session.user.email,
      name: session.user.name ?? undefined,
      avatar: session.user.image ?? undefined,
    };
  },
});

Option 2: Server Action

Create a server action for use with React Server Components:

// lib/fdback.ts
"use server";
import { createSignAction } from "@fdback.io/sdk/next";
import { auth } from "@/server/auth";

const _signAction = createSignAction({
  workspaceId: process.env.FDBACK_WORKSPACE_ID!,
  workspaceSecret: process.env.FDBACK_WORKSPACE_SECRET!,
});

// Wrap with auth check
export async function signFdbackLogin() {
  const session = await auth();
  if (!session?.user?.email) {
    return { success: false as const, error: "Unauthorized" };
  }
  return _signAction({
    email: session.user.email,
    name: session.user.name ?? undefined,
    avatar: session.user.image ?? undefined,
  });
}

Use in client component:

"use client";
import { signFdbackLogin } from "@/lib/fdback";
import { useFdback } from "@fdback.io/sdk/react";

export function FeedbackButton() {
  const { open } = useFdback();

  const handleClick = async () => {
    const result = await signFdbackLogin();
    if (result.success) {
      open(result.data);
    } else {
      console.error(result.error);
    }
  };

  return <button onClick={handleClick}>Feedback</button>;
}

Next.js API Reference

| Export | Description | |--------|-------------| | createSignHandler(options) | Creates a POST route handler | | createSignAction(options) | Creates a server action factory |

SignHandlerOptions

interface SignHandlerOptions {
  workspaceId: string;
  workspaceSecret: string;
  baseUrl?: string;
  authorize?: (request: Request) => Promise<boolean> | boolean;
  getUser?: (request: Request) => Promise<FdbackUser | null> | FdbackUser | null;
}

SignActionOptions

interface SignActionOptions {
  workspaceId: string;
  workspaceSecret: string;
  baseUrl?: string;
}

CDN Usage

For non-bundled usage, include via CDN:

<script src="https://unpkg.com/@fdback.io/sdk"></script>
<script>
  const fdback = new FdbackSDK.Fdback({
    workspaceId: "your-workspace-id",
  });

  // Use with pre-signed data from your server
  fetch("/api/fdback/sign", { method: "POST" })
    .then((r) => r.json())
    .then((signedData) => {
      fdback.open(signedData, { mode: "popup" });
    });
</script>

Complete Next.js Example

1. Environment variables

FDBACK_WORKSPACE_ID=your-workspace-id
FDBACK_WORKSPACE_SECRET=your-workspace-secret

2. API route

// app/api/fdback/sign/route.ts
import { createSignHandler } from "@fdback.io/sdk/next";
import { auth } from "@/server/auth";

export const POST = createSignHandler({
  workspaceId: process.env.FDBACK_WORKSPACE_ID!,
  workspaceSecret: process.env.FDBACK_WORKSPACE_SECRET!,
  authorize: async () => !!(await auth())?.user,
  getUser: async () => {
    const session = await auth();
    if (!session?.user?.email) return null;
    return {
      email: session.user.email,
      name: session.user.name ?? undefined,
    };
  },
});

3. Provider setup

// app/layout.tsx
import { FdbackProvider } from "@fdback.io/sdk/react";

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html>
      <body>
        <FdbackProvider
          workspaceId={process.env.NEXT_PUBLIC_FDBACK_WORKSPACE_ID!}
          signEndpoint="/api/fdback/sign"
        >
          {children}
        </FdbackProvider>
      </body>
    </html>
  );
}

4. Feedback button

// components/feedback-button.tsx
"use client";
import { FeedbackButton } from "@fdback.io/sdk/react";
import { useSession } from "next-auth/react";

export function AppFeedbackButton() {
  const { data: session } = useSession();

  if (!session?.user?.email) return null;

  return (
    <FeedbackButton
      user={{
        email: session.user.email,
        name: session.user.name ?? undefined,
        avatar: session.user.image ?? undefined,
      }}
      className="fixed bottom-4 right-4 rounded-full bg-primary px-4 py-2 text-white"
    >
      Feedback
    </FeedbackButton>
  );
}

Embedded Widget

The SDK includes an embeddable widget that can be integrated directly into your application. The widget provides a floating button and panel for collecting feedback.

Basic Usage

<script src="https://unpkg.com/@fdback.io/sdk"></script>
<script>
  const widget = FdbackSDK.widget.init({
    workspaceId: "your-workspace-id",
  });
</script>

With User Authentication

For identified users, provide a signEndpoint that authenticates users via HMAC-signed data:

<script src="https://unpkg.com/@fdback.io/sdk"></script>
<script>
  const widget = FdbackSDK.widget.init({
    workspaceId: "your-workspace-id",
    signEndpoint: "/api/fdback/sign",
    onAuthenticated: (user) => {
      console.log("User authenticated:", user);
    },
  });
</script>

The signEndpoint should return signed user data from your server. See Next.js Integration for creating sign endpoints.

Configuration Options

interface WidgetConfig {
  workspaceId: string;                    // Required
  mode?: "simple" | "full";               // "simple" = feedback only, "full" = tabs
  position?: "bottom-right" | "bottom-left" | "top-right" | "top-left";
  tabs?: ("feedback" | "roadmap" | "changelog")[];
  primaryColor?: string;                  // Hex color for theming
  theme?: "auto" | "dark" | "light";      // Theme mode (default: "auto")
  width?: number;                         // Widget width in pixels (default: 440, min: 320, max: 600)
  height?: number;                        // Widget height in pixels (default: 680, min: 400, max: 900)
  signEndpoint?: string;                  // URL for HMAC-signed authentication
  onOpen?: () => void;
  onClose?: () => void;
  onFeedbackSubmitted?: (feedback: { id: string }) => void;
  onTabChange?: (tab: string) => void;
  onAuthenticated?: (user: { id: string; email: string; name?: string }) => void;
}

Widget Methods

const widget = FdbackSDK.widget.init(config);

widget.open();                            // Open the widget
widget.close();                           // Close the widget
widget.toggle();                          // Toggle open/closed
widget.setTab("roadmap");                 // Switch tab (full mode)
widget.setUser({ email: "...", name: "..." });  // Update user data
widget.destroy();                         // Remove widget from page
widget.isOpen;                            // Current state (boolean)

Authentication Flow

When signEndpoint is configured:

  1. Widget initializes and calls your signEndpoint with cookies
  2. Your server reads the user session and returns HMAC-signed data
  3. Widget sends signed data to fdback.io for verification
  4. User is authenticated and feedback is attributed to them
┌─────────────────────────────────────────────────────────────┐
│  YOUR APP (acme.com)                                        │
│  1. widget.init({ signEndpoint: '/api/fdback/sign' })       │
│  2. SDK calls signEndpoint (with cookies for session)       │
│  3. Server returns HMAC-signed user data                    │
└────────────────────────┬────────────────────────────────────┘
                         │
                         ▼
┌─────────────────────────────────────────────────────────────┐
│  FDBACK WIDGET (fdback.io)                                  │
│  4. Validates signature with workspace secret               │
│  5. Creates/updates user, sets session cookie               │
│  6. User is now authenticated                               │
└─────────────────────────────────────────────────────────────┘

Troubleshooting

"Signed login data has expired"

The signed data expires after 5 minutes. Generate fresh data before opening.

"workspaceSecret should not be used in browser code"

You're initializing Fdback with a secret in client-side code. Remove the secret and use a backend API to sign requests.

Build error: "This module cannot be imported from a Client Component"

You're importing @fdback.io/sdk/next in client code. These helpers are server-only. Create a server action or API route instead.


License

MIT