@simple-product/sdk
v0.4.0
Published
Simple Product SDK - Track users and product usage
Maintainers
Readme
@simple-product/sdk
Track users and product usage with Simple Product.
Installation
npm install @simple-product/sdkQuick Start
import { SimpleProduct } from '@simple-product/sdk';
// Initialize once in your app
const sp = new SimpleProduct({
apiKey: 'sk_your_api_key',
});
// Identify users when they sign in
sp.identify({
userId: user.id,
email: user.email,
name: user.name,
company: user.company,
});
// That's it! Analytics are now tracked automatically.How It Works
The SDK automatically sends a heartbeat event once per day per user. This single event powers:
- DAU/WAU/MAU - Daily, weekly, monthly active users
- Retention - Cohort retention analysis
- Stickiness - DAU/MAU engagement ratio
No configuration needed. Just identify your users and analytics happen automatically.
API Reference
new SimpleProduct(config)
Create a new SDK instance.
const sp = new SimpleProduct({
apiKey: 'sk_your_api_key', // Required
endpoint: 'https://...', // Optional, defaults to production
disableAutoHeartbeat: false, // Optional, disable auto-tracking
debug: false, // Optional, enable console logging
});sp.identify(params)
Identify a user. Call this when a user signs in.
sp.identify({
userId: 'user_123', // Required - your internal user ID
email: '[email protected]', // Optional
name: 'Jane Doe', // Optional
company: 'Acme Inc', // Optional
traits: { // Optional - any additional data
plan: 'pro',
role: 'admin',
},
});sp.track(event, properties?)
Track a custom event.
sp.track('feature_used', {
feature: 'export_report',
format: 'csv',
});sp.reset()
Reset the user. Call this when a user signs out.
sp.reset();sp.destroy()
Clean up SDK resources. Call this if you need to remove the SDK.
sp.destroy();React Integration (Recommended)
The easiest way to integrate is with our React provider:
// app/providers.tsx
'use client';
import { SimpleProductProvider } from '@simple-product/sdk/react';
export function Providers({ children, user }) {
return (
<SimpleProductProvider
apiKey={process.env.NEXT_PUBLIC_SP_API_KEY!}
user={user ? {
id: user.id,
email: user.email,
name: user.name,
} : null}
>
{children}
</SimpleProductProvider>
);
}The provider automatically:
- Calls
identify()when user changes - Sends heartbeats once per day
- Resets on logout
Track Custom Events
import { useSimpleProduct } from '@simple-product/sdk/react';
function MyComponent() {
const { track } = useSimpleProduct();
return (
<button onClick={() => track('button_clicked', { button: 'signup' })}>
Sign Up
</button>
);
}Narrated Feedback
Let people record themselves using your app and talking through what they see. We record the mic only, never the screen. Alongside it we keep a timeline of what the cursor pointed at (the element's text, where it sits, a few styles). We transcribe the audio, delete it, and file the recording as one feedback doc in your workspace, linked to the identified user. Each issue they raised gets a section with their words and the elements it's about, followed by the full narration.
Full guide: https://simpleproduct.dev/docs/narrated-feedback
- Privacy: the audio stays in the browser until they send, and is deleted as soon as it's transcribed. Form field values are never captured. Mark anything sensitive with
data-sp-maskand its text is left out. - Limits: recordings stop at 10 minutes (
maxSecondscan make that shorter); 20 recordings per workspace per hour. Usage counts toward your workspace credits.
There are two ways to use it.
1. Our UI
const { sent } = await sp.feedback.record();Shows a small "Recording" bar with Pause, Stop and Cancel. Pausing stops both the mic and the cursor timeline, and the pause isn't part of the recording. After Stop, a review card offers an optional note, then Send or Discard.
People can drag the bar out of the way (by its grip or any empty space); it snaps to the nearest corner or the middle of the top or bottom edge, and stays there next time. To choose where it starts, or keep it fixed:
sp.feedback.record({ position: "bottom-right" }); // top-/bottom- + left | center | right
sp.feedback.record({ position: "top-center", movable: false });Restyle it with CSS variables on the widget:
[data-sp-feedback] {
--sp-bg: #0b0b0c; /* surface */
--sp-fg: #fafafa; /* text */
--sp-muted: #a1a1aa; /* secondary text */
--sp-border: #27272a;
--sp-subtle: #18181b; /* hover backgrounds */
--sp-accent: #4f46e5; /* Stop / Send */
--sp-accent-fg: #ffffff;
--sp-live: #ef4444; /* recording dot, errors */
--sp-radius: 4px; /* the bar and card */
--sp-radius-inner: 2px; /* buttons and note box */
}Or reshape any piece with ::part(): panel, grip, dot, time, hint, pause, resume, stop, cancel, note, send, discard.
[data-sp-feedback]::part(panel) { left: auto; right: 24px; transform: none; font-family: Inter, sans-serif; }For quick brand colors there's also sp.feedback.record({ theme: { accent: "#4f46e5", accentText: "#fff" } }).
2. Headless: your own UI
const rec = sp.feedback.recorder();
rec.subscribe(({ status, elapsedMs, error }) => render(status, elapsedMs, error));
await rec.start(); // asks for the mic, starts recording
rec.pause(); rec.resume(); // optional; paused time isn't recorded
rec.stop(); // stops; nothing is sent yet
await rec.send({ note: "…" }); // or rec.discard()status moves idle → requesting-mic → recording (⇄ paused) → stopped → sending → sent, or to error with error set to unsupported, mic-blocked, busy (another recording is running), or send-failed (the recording is kept; call send() again).
Put data-sp-ignore on your own controls so clicking your Record and Stop buttons doesn't end up in the timeline.
In React:
import { useFeedbackRecorder } from '@simple-product/sdk/react';
function RecordButton() {
const rec = useFeedbackRecorder();
if (!rec.available) return null;
if (rec.status === 'recording') return <button data-sp-ignore onClick={rec.stop}>Stop ({Math.round(rec.elapsedMs / 1000)}s)</button>;
if (rec.status === 'stopped') return <button data-sp-ignore onClick={() => rec.send()}>Send feedback</button>;
return <button data-sp-ignore onClick={rec.start}>Record feedback</button>;
}useRecordFeedback() returns a function that opens our UI instead.
Desktop apps (Tauri, Electron)
The recorder runs in any webview; the OS controls the mic:
- macOS: add
NSMicrophoneUsageDescriptiontoInfo.plist, plus thecom.apple.security.device.audio-inputentitlement if the app is sandboxed or uses the hardened runtime. - Tauri: the webview must pass mic requests through to the page (recent versions do). Test with a bare
navigator.mediaDevices.getUserMedia({ audio: true })first. - Electron: call
systemPreferences.askForMediaAccess('microphone')on macOS and allowmediain your session's permission handler.
If start() goes straight to mic-blocked, it's one of these.
Manual Setup
If you're not using React, you can use the SDK directly:
import { SimpleProduct } from '@simple-product/sdk';
const sp = new SimpleProduct({
apiKey: process.env.NEXT_PUBLIC_SP_API_KEY!,
});
// When user logs in
sp.identify({
userId: user.id,
email: user.email,
name: user.name,
});
// Track custom events
sp.track('feature_used', { feature: 'export' });
// On logout
sp.reset();License
MIT
