@sjforge/feedback-widget
In-app feedback widget SDK with screenshots, annotations, session recording, and offline support.
Features
- Screenshot capture with annotation tools (draw, arrows, text, highlight, blur)
- Session recording using rrweb for pixel-perfect replay
- Automatic context capture (browser, errors, network failures)
- Offline support with IndexedDB queue and automatic sync
- Privacy controls for masking sensitive data
- Multiple platforms - Web, Electron, and React Native
Installation
npm install @sjforge/feedback-widget
Or via CDN:
<script src="https://cdn.sjforge.dev/feedback-widget.min.js"></script>
Quick Start
Web / Vanilla JS
import { FeedbackWidget } from '@sjforge/feedback-widget';
FeedbackWidget.init({
projectId: 'my-project',
apiKey: 'fpk_xxxxx',
});
await FeedbackWidget.submit({
type: 'bug',
priority: 'high',
title: 'Button not working',
description: 'The submit button does nothing when clicked',
});
CDN / Script Tag
<script src="https://cdn.sjforge.dev/feedback-widget.min.js"></script>
<script>
FeedbackWidget.init({
projectId: 'my-project',
apiKey: 'fpk_xxxxx',
});
</script>
Electron
import { FeedbackWidget, ElectronAdapter } from '@sjforge/feedback-widget';
FeedbackWidget.init({
projectId: 'my-app',
apiKey: 'fpk_xxxxx',
adapter: new ElectronAdapter(window.api),
});
See Electron Integration Guide for preload script setup.
React Native
Use the separate native package:
npm install @sjforge/feedback-widget-native
import { FeedbackWidget } from '@sjforge/feedback-widget-native';
FeedbackWidget.init({
projectId: 'my-app',
apiKey: 'fpk_xxxxx',
});
See @sjforge/feedback-widget-native for full documentation.
Configuration
FeedbackWidget.init({
projectId: 'my-project',
apiKey: 'fpk_xxxxx',
apiUrl: 'https://your-portal.com/api/widget',
user: {
name: 'John Doe',
email: 'john@example.com',
},
customContext: {
subscription: 'premium',
version: '2.1.0',
},
privacy: {
maskSelectors: ['.sensitive-data', '.credit-card'],
blockSelectors: ['.do-not-record'],
autoMaskPasswords: true,
},
ui: {
position: 'bottom-right',
primaryColor: '#007bff',
showButton: true,
buttonText: 'Feedback',
},
features: {
screenshots: true,
recording: false,
captureConsoleErrors: true,
captureNetworkErrors: true,
},
recording: {
maxDuration: 300000,
samplingRate: 'medium',
},
onSubmitStart: () => console.log('Submitting...'),
onSubmitSuccess: (feedbackId) => console.log('Submitted:', feedbackId),
onSubmitError: (error) => console.error('Error:', error),
});
API Reference
Static Methods
FeedbackWidget.init(config)
Initialize the widget. Must be called before using other methods.
FeedbackWidget.submit(feedback)
Submit feedback programmatically.
const result = await FeedbackWidget.submit({
type: 'bug',
priority: 'high',
title: 'Short title',
description: 'Detailed description',
});
if (result.success) {
console.log('Feedback ID:', result.feedback_id);
} else {
console.error('Error:', result.error);
}
FeedbackWidget.open() / FeedbackWidget.close()
Open or close the feedback form UI.
FeedbackWidget.setContext(context)
Update custom context that gets sent with submissions.
FeedbackWidget.setContext({
userId: 'user-123',
page: 'checkout',
});
FeedbackWidget.getContext()
Get the current context snapshot (for debugging).
FeedbackWidget.destroy()
Destroy the widget instance and clean up resources.
FeedbackWidget.isInitialized()
Returns true if the widget is initialized.
FeedbackWidget.getVersion()
Get the SDK version string.
Screenshot Methods
FeedbackWidget.captureScreenshot()
Capture a screenshot of the current page.
const screenshot = await FeedbackWidget.captureScreenshot();
FeedbackWidget.submitWithScreenshot(feedback)
Submit feedback with an automatically captured screenshot.
await FeedbackWidget.submitWithScreenshot({
type: 'bug',
priority: 'high',
title: 'UI Issue',
description: 'See attached screenshot',
});
Recording Methods
FeedbackWidget.startRecording()
Start a session recording.
FeedbackWidget.startRecording();
FeedbackWidget.stopRecording()
Stop the current recording and return the data.
const recording = await FeedbackWidget.stopRecording();
FeedbackWidget.isRecording()
Check if a recording is in progress.
FeedbackWidget.submitWithRecording(feedback)
Submit feedback with the current recording attached.
await FeedbackWidget.submitWithRecording({
type: 'bug',
priority: 'critical',
title: 'Workflow broken',
description: 'See attached recording',
});
Offline Methods
FeedbackWidget.isOnline()
Check if the widget has network connectivity.
FeedbackWidget.getPendingCount()
Get the number of submissions waiting to sync.
const pending = await FeedbackWidget.getPendingCount();
console.log(`${pending} submissions waiting to sync`);
FeedbackWidget.syncOffline()
Force sync offline submissions.
const { succeeded, failed } = await FeedbackWidget.syncOffline();
Screenshot & Annotation
Automatic Screenshot Capture
Screenshots are captured using html2canvas and can include annotations.
const screenshot = await FeedbackWidget.captureScreenshot();
await FeedbackWidget.submitWithScreenshot({
type: 'bug',
priority: 'high',
title: 'UI Issue',
description: 'See attached screenshot',
});
Annotation Editor
The SDK includes a standalone annotation editor:
import { AnnotationEditor } from '@sjforge/feedback-widget';
const editor = new AnnotationEditor({
container: document.getElementById('editor'),
imageSrc: screenshotDataUrl,
onSave: (annotatedImageDataUrl) => {
console.log('Annotated image:', annotatedImageDataUrl);
},
onCancel: () => {
console.log('Cancelled');
},
});
const annotatedImage = editor.getAnnotatedImage();
editor.destroy();
Available annotation tools:
- Rectangle - Draw boxes around areas
- Arrow - Point to specific elements
- Text - Add text labels
- Highlight - Semi-transparent highlight
- Blur - Obscure sensitive areas
Session Recording
Session recording captures DOM events using rrweb for pixel-perfect replay in the admin portal.
Enable Recording
FeedbackWidget.init({
projectId: 'my-project',
apiKey: 'fpk_xxxxx',
features: {
recording: true,
},
recording: {
maxDuration: 300000,
},
});
Manual Recording Control
FeedbackWidget.startRecording();
if (FeedbackWidget.isRecording()) {
console.log('Recording in progress...');
}
await FeedbackWidget.submitWithRecording({
type: 'bug',
priority: 'high',
title: 'See what happened',
description: 'Recording attached',
});
Recording Privacy
Recordings automatically respect privacy settings:
FeedbackWidget.init({
privacy: {
maskSelectors: ['.sensitive'],
blockSelectors: ['.private'],
autoMaskPasswords: true,
},
});
Privacy & Security
Automatic Protection
- Password fields are automatically masked
- Recordings exclude blocked elements entirely
- Screenshots mask sensitive areas
Data Attributes
<div data-feedback-mask>Sensitive content</div>
<div data-feedback-block>Private notes</div>
Configuration
FeedbackWidget.init({
privacy: {
maskSelectors: ['.credit-card', '.ssn-field'],
blockSelectors: ['.private-notes', '.admin-panel'],
autoMaskPasswords: true,
},
});
Offline Support
The widget automatically queues submissions when offline and syncs when connectivity returns.
How It Works
- When offline, submissions are stored in IndexedDB
- When connectivity returns, queued items sync automatically
- Failed syncs retry up to 3 times with exponential backoff
- Callbacks notify you of sync status
Manual Control
if (!FeedbackWidget.isOnline()) {
console.log('Currently offline - submissions will queue');
}
const pending = await FeedbackWidget.getPendingCount();
const { succeeded, failed } = await FeedbackWidget.syncOffline();
console.log(`Synced: ${succeeded}, Failed: ${failed}`);
Auto-Captured Context
The widget automatically captures:
| User Agent | Browser and OS information |
| Viewport | Current window dimensions |
| Screen | Device screen size and pixel ratio |
| URL | Current page URL |
| Referrer | How user arrived at the page |
| Console Errors | Recent console.error calls |
| Network Errors | Failed fetch/XHR requests |
| Custom Context | Data you provide via setContext() |
Electron Integration
1. Install the Package
npm install @sjforge/feedback-widget
2. Set Up Preload Script
import { contextBridge, ipcRenderer } from 'electron';
contextBridge.exposeInMainWorld('feedbackAPI', {
captureScreen: () => ipcRenderer.invoke('feedback:capture-screen'),
storeOffline: (key: string, data: unknown) =>
ipcRenderer.invoke('feedback:store-offline', key, data),
getOffline: (key: string) =>
ipcRenderer.invoke('feedback:get-offline', key),
removeOffline: (key: string) =>
ipcRenderer.invoke('feedback:remove-offline', key),
getAppInfo: () => ipcRenderer.invoke('feedback:get-app-info'),
});
3. Set Up Main Process Handlers
import { ipcMain, desktopCapturer, app } from 'electron';
import Store from 'electron-store';
const store = new Store({ name: 'feedback-widget' });
ipcMain.handle('feedback:capture-screen', async () => {
const sources = await desktopCapturer.getSources({
types: ['window'],
thumbnailSize: { width: 1920, height: 1080 },
});
const currentWindow = sources.find(s => s.name === 'Your App Name');
return currentWindow?.thumbnail.toDataURL();
});
ipcMain.handle('feedback:store-offline', (_, key, data) => {
store.set(key, data);
});
ipcMain.handle('feedback:get-offline', (_, key) => {
return store.get(key);
});
ipcMain.handle('feedback:remove-offline', (_, key) => {
store.delete(key);
});
ipcMain.handle('feedback:get-app-info', () => ({
name: app.getName(),
version: app.getVersion(),
}));
4. Initialize in Renderer
import { FeedbackWidget, ElectronAdapter } from '@sjforge/feedback-widget';
FeedbackWidget.init({
projectId: 'my-electron-app',
apiKey: 'fpk_xxxxx',
adapter: new ElectronAdapter(window.feedbackAPI),
});
CDN Usage
For quick integration without a build step:
<!DOCTYPE html>
<html>
<head>
<title>My App</title>
</head>
<body>
<script src="https://cdn.sjforge.dev/feedback-widget.min.js"></script>
<script>
FeedbackWidget.init({
projectId: 'my-project',
apiKey: 'fpk_xxxxx',
ui: {
position: 'bottom-right',
primaryColor: '#007bff',
},
});
</script>
</body>
</html>
TypeScript Support
Full TypeScript definitions are included:
import {
FeedbackWidget,
WidgetConfig,
FeedbackSubmission,
SubmissionResponse,
FeedbackType,
FeedbackPriority,
} from '@sjforge/feedback-widget';
const config: WidgetConfig = {
projectId: 'my-project',
apiKey: 'fpk_xxxxx',
};
FeedbackWidget.init(config);
Browser Support
- Chrome 80+
- Firefox 75+
- Safari 13+
- Edge 80+
Bundle Size
| Core only | ~15 KB |
| + Screenshots | ~45 KB |
| + Recording | ~85 KB |
Changelog
0.3.0
- Session recording with rrweb
- Chunked upload for large recordings
- Recording playback in admin portal
0.2.0
- Screenshot capture with html2canvas
- Annotation editor with drawing tools
- Privacy masking for screenshots
0.1.0
- Initial release
- Text-only feedback submission
- Auto-captured context
- Offline queue with sync
License
MIT