@supastory/capture-sdk
v0.3.2
Published
SupaStory SDK — capture user sessions and let AI find bugs and UX issues for you
Maintainers
Readme
@supastory/capture-sdk
Session replay SDK for capturing user interactions. Records DOM changes, clicks, scrolls, network requests, and more for playback in the SupaStory dashboard.
Installation
npm install @supastory/capture-sdk
# or
yarn add @supastory/capture-sdk
# or
pnpm add @supastory/capture-sdkQuick Start
import SupaStory from '@supastory/capture-sdk';
SupaStory.init({
projectKey: 'pk_live_your_project_key',
});Usage
Basic Initialization
import SupaStory from '@supastory/capture-sdk';
SupaStory.init({
projectKey: 'pk_live_abc123',
});With Callbacks
SupaStory.init({
projectKey: 'pk_live_abc123',
onReady: (sessionId) => {
console.log('Recording started:', sessionId);
// Link session to your analytics
analytics.track('session_started', { sessionId });
},
onError: (error) => {
// Send to your error tracking service
errorTracker.capture(error);
},
});With Consent Flow
// Initialize without auto-starting
SupaStory.init({
projectKey: 'pk_live_abc123',
autoStart: false,
onReady: (sessionId) => {
console.log('Recording started:', sessionId);
},
});
// Later, after user consent
function handleConsentAccepted() {
SupaStory.start();
}
function handleConsentDeclined() {
// Session was initialized but never started recording
SupaStory.stop();
}Script Tag Usage
<script src="https://app.supastory.com/api/sdk.js"></script>
<script>
SupaStory.init({
projectKey: 'pk_live_abc123',
});
</script>Configuration Options
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| projectKey | string | required | Your project key from the SupaStory dashboard |
| endpoint | string | 'https://api.supastory.com/ingest' | API endpoint for sending events |
| sampleRate | number | 1.0 | Session sampling rate (0.0 - 1.0) |
| maskInputs | boolean | true | Mask password and sensitive inputs |
| maskAllInputs | boolean | false | Mask all input fields |
| excludeSelectors | string[] | [] | CSS selectors to exclude from capture |
| batchSize | number | 50 | Events per batch |
| batchIntervalMs | number | 5000 | Batch send interval |
| maxSessionDurationMs | number | 1800000 | Max session duration (30 min) |
| captureNetworkRequests | boolean | true | Capture XHR/Fetch requests |
| captureNetworkPayloads | boolean | true | Include request/response bodies |
| startDelayMs | number | 5000 | Delay before starting capture |
| autoStart | boolean | true | Auto-start recording on init |
| logLevel | LogLevel | 'none' | Logging level |
| onReady | function | - | Called when recording starts |
| onError | function | - | Called on SDK errors |
| onSampledOut | function | - | Called when session is not sampled |
Log Levels
type LogLevel = 'none' | 'error' | 'warn' | 'info' | 'verbose';'none'- No logging (production default)'error'- Only critical errors'warn'- Errors and warnings'info'- General information'verbose'- Detailed debugging
API Reference
init(config)
Initialize the SDK. Call once when your app loads.
SupaStory.init({
projectKey: 'pk_live_abc123',
sampleRate: 0.1, // Capture 10% of sessions
logLevel: 'info',
});start()
Manually start recording when autoStart: false. Use for consent flows.
// Initialize without auto-start for consent flow
SupaStory.init({
projectKey: 'pk_live_abc123',
autoStart: false,
});
// Later, after user consents
function handleConsentAccepted() {
SupaStory.start();
}stop()
Stop recording and clean up. Flushes remaining events.
// Stop when user logs out
function handleLogout() {
SupaStory.stop();
}pause() / resume()
Temporarily pause and resume recording.
// Pause during sensitive operations
SupaStory.pause();
await showSensitiveModal();
SupaStory.resume();getSessionId()
Get the current session ID for linking to support tools.
const sessionId = SupaStory.getSessionId();
if (sessionId) {
supportTicket.replayUrl = `https://app.supastory.com/replay/${sessionId}`;
}isRecording()
Check if currently recording.
if (SupaStory.isRecording()) {
console.log('Session replay is active');
}getStatus()
Get detailed SDK status for debugging.
const status = SupaStory.getStatus();
console.log({
initialized: status.initialized,
recording: status.recording,
eventCount: status.eventCount,
sampledOut: status.sampledOut,
});Returns:
interface SDKStatus {
initialized: boolean;
recording: boolean;
paused: boolean;
sessionId: string | null;
visitorId: string | null;
queueSize: number;
eventCount: number;
sampledOut: boolean;
}identify(userId, traits?)
Link sessions to your users.
// After user login
SupaStory.identify('user_123', {
email: '[email protected]',
plan: 'premium',
company: 'Acme Inc',
});setLogLevel(level)
Change log level at runtime.
// Enable verbose logging for debugging
SupaStory.setLogLevel('verbose');
// Disable logging
SupaStory.setLogLevel('none');Error Handling
The SDK never throws exceptions. Errors are passed to the onError callback.
SupaStory.init({
projectKey: 'pk_live_abc123',
onError: (error) => {
// Error structure
// {
// code: 'NETWORK_ERROR',
// message: 'Failed to send events',
// originalError?: Error,
// context?: { ... }
// }
switch (error.code) {
case 'NETWORK_ERROR':
// Network issues - events will be retried
break;
case 'STORAGE_ERROR':
// localStorage/sessionStorage unavailable
break;
case 'NOT_INITIALIZED':
// Called method before init()
break;
}
},
});Error Codes
| Code | Description |
|------|-------------|
| ALREADY_INITIALIZED | init() called twice |
| NOT_INITIALIZED | Method called before init() |
| MISSING_PROJECT_KEY | No projectKey provided |
| INVALID_CONFIG | Invalid configuration value |
| NETWORK_ERROR | Failed to send events |
| STORAGE_ERROR | Storage API unavailable |
| SESSION_NOT_SAMPLED | Session excluded by sample rate |
Privacy & Security
Input Masking
By default, password fields and inputs with type="password" are masked.
SupaStory.init({
projectKey: 'pk_live_abc123',
maskInputs: true, // Mask sensitive inputs (default)
maskAllInputs: false, // Don't mask all inputs
});Excluding Elements
Exclude sensitive elements from capture:
SupaStory.init({
projectKey: 'pk_live_abc123',
excludeSelectors: [
'.sensitive-data',
'[data-private]',
'#credit-card-form',
],
});Or use data attributes to exclude elements from capture:
<div data-no-record>
This content won't be captured
</div>
<!-- Alternative attribute -->
<div data-private>
This content won't be captured either
</div>You can also use CSS classes:
<div class="no-record">Excluded</div>
<div class="private">Also excluded</div>Competitor Attribute Compatibility
If you're migrating from another session replay tool, SupaStory automatically recognizes their attributes — no code changes needed:
| Provider | Exclude Element | Mask Input |
|----------|----------------|------------|
| Sentry | data-sentry-block | data-sentry-mask, data-sentry-block |
| FullStory | data-fs-hidden, data-fs-block | data-fs-mask, data-fs-hidden, data-fs-block |
| OpenReplay | data-openreplay-hidden | data-openreplay-mask, data-openreplay-hidden |
| LogRocket | — | data-logrocket-mask |
| PostHog | ph-no-capture | ph-no-capture |
Testing
The SDK exports test utilities for mocking in your tests:
import { createMockSDK, createMockConfig } from '@supastory/capture-sdk/testing';
describe('MyComponent', () => {
let mockSDK;
beforeEach(() => {
mockSDK = createMockSDK();
});
afterEach(() => {
mockSDK._reset();
});
it('initializes SupaStory on mount', () => {
mockSDK.init(createMockConfig());
expect(mockSDK.isRecording()).toBe(true);
expect(mockSDK._getCallHistory().init).toHaveLength(1);
});
it('identifies user after login', () => {
mockSDK.init(createMockConfig());
mockSDK.identify('user_123', { plan: 'premium' });
const history = mockSDK._getCallHistory();
expect(history.identify[0]).toEqual({
userId: 'user_123',
traits: { plan: 'premium' },
});
});
});Test Utilities
createMockSDK()- Creates a mock SDK instancecreateMockConfig(overrides?)- Creates valid test configcreateErrorCapture()- Captures errors for assertionscreateReadyCapture()- Captures onReady callbacks
Framework Integration
React
import { useEffect } from 'react';
import SupaStory from '@supastory/capture-sdk';
function App() {
useEffect(() => {
SupaStory.init({
projectKey: process.env.REACT_APP_SUPASTORY_KEY,
onReady: (sessionId) => {
console.log('Session:', sessionId);
},
});
return () => SupaStory.stop();
}, []);
return <YourApp />;
}Next.js
// app/providers.tsx
'use client';
import { useEffect } from 'react';
import SupaStory from '@supastory/capture-sdk';
export function Providers({ children }) {
useEffect(() => {
SupaStory.init({
projectKey: process.env.NEXT_PUBLIC_SUPASTORY_KEY,
});
return () => SupaStory.stop();
}, []);
return children;
}Vue
// main.js
import { createApp } from 'vue';
import SupaStory from '@supastory/capture-sdk';
import App from './App.vue';
SupaStory.init({
projectKey: import.meta.env.VITE_SUPASTORY_KEY,
});
createApp(App).mount('#app');Troubleshooting
SDK not capturing events
- Check if initialized:
SupaStory.getStatus().initialized - Check if recording:
SupaStory.isRecording() - Check if sampled out:
SupaStory.getStatus().sampledOut - Enable verbose logging:
SupaStory.setLogLevel('verbose')
Events not appearing in dashboard
- Check network tab for failed requests to
/ingest - Verify your
projectKeyis correct - Check
onErrorcallback for errors
Performance concerns
- Reduce
batchSizefor more frequent, smaller batches - Increase
batchIntervalMsfor less frequent sends - Use
sampleRateto capture fewer sessions - Use
excludeSelectorsfor high-frequency DOM updates
TypeScript
Full TypeScript support with exported types:
import SupaStory, {
type SDKConfig,
type SDKStatus,
type SDKError,
type LogLevel,
} from '@supastory/capture-sdk';
const config: SDKConfig = {
projectKey: 'pk_live_abc123',
onError: (error: SDKError) => {
console.error(error.code, error.message);
},
};
SupaStory.init(config);
const status: SDKStatus = SupaStory.getStatus();License
See LICENSE for details.
