@fdback.io/sdk
v0.5.0
Published
TypeScript SDK for Fdback user feedback platform
Maintainers
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/sdkQuick 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:
FdbackServerandFdbackClientare aliases for the sameFdbackclass. 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 reactSetup 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-onlyThe /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-secret2. 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:
- Widget initializes and calls your
signEndpointwith cookies - Your server reads the user session and returns HMAC-signed data
- Widget sends signed data to fdback.io for verification
- 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
