@simple-product/sdk 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,175 @@
1
+ # @simple-product/sdk
2
+
3
+ Track users and product usage with Simple Product.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install @simple-product/sdk
9
+ ```
10
+
11
+ ## Quick Start
12
+
13
+ ```typescript
14
+ import { SimpleProduct } from '@simple-product/sdk';
15
+
16
+ // Initialize once in your app
17
+ const sp = new SimpleProduct({
18
+ apiKey: 'sk_your_api_key',
19
+ });
20
+
21
+ // Identify users when they sign in
22
+ sp.identify({
23
+ userId: user.id,
24
+ email: user.email,
25
+ name: user.name,
26
+ company: user.company,
27
+ });
28
+
29
+ // That's it! Analytics are now tracked automatically.
30
+ ```
31
+
32
+ ## How It Works
33
+
34
+ The SDK automatically sends a **heartbeat event once per day** per user. This single event powers:
35
+
36
+ - **DAU/WAU/MAU** - Daily, weekly, monthly active users
37
+ - **Retention** - Cohort retention analysis
38
+ - **Stickiness** - DAU/MAU engagement ratio
39
+
40
+ No configuration needed. Just identify your users and analytics happen automatically.
41
+
42
+ ## API Reference
43
+
44
+ ### `new SimpleProduct(config)`
45
+
46
+ Create a new SDK instance.
47
+
48
+ ```typescript
49
+ const sp = new SimpleProduct({
50
+ apiKey: 'sk_your_api_key', // Required
51
+ endpoint: 'https://...', // Optional, defaults to production
52
+ disableAutoHeartbeat: false, // Optional, disable auto-tracking
53
+ debug: false, // Optional, enable console logging
54
+ });
55
+ ```
56
+
57
+ ### `sp.identify(params)`
58
+
59
+ Identify a user. Call this when a user signs in.
60
+
61
+ ```typescript
62
+ sp.identify({
63
+ userId: 'user_123', // Required - your internal user ID
64
+ email: 'user@example.com', // Optional
65
+ name: 'Jane Doe', // Optional
66
+ company: 'Acme Inc', // Optional
67
+ traits: { // Optional - any additional data
68
+ plan: 'pro',
69
+ role: 'admin',
70
+ },
71
+ });
72
+ ```
73
+
74
+ ### `sp.track(event, properties?)`
75
+
76
+ Track a custom event.
77
+
78
+ ```typescript
79
+ sp.track('feature_used', {
80
+ feature: 'export_report',
81
+ format: 'csv',
82
+ });
83
+ ```
84
+
85
+ ### `sp.reset()`
86
+
87
+ Reset the user. Call this when a user signs out.
88
+
89
+ ```typescript
90
+ sp.reset();
91
+ ```
92
+
93
+ ### `sp.destroy()`
94
+
95
+ Clean up SDK resources. Call this if you need to remove the SDK.
96
+
97
+ ```typescript
98
+ sp.destroy();
99
+ ```
100
+
101
+ ## React Integration (Recommended)
102
+
103
+ The easiest way to integrate is with our React provider:
104
+
105
+ ```tsx
106
+ // app/providers.tsx
107
+ 'use client';
108
+
109
+ import { SimpleProductProvider } from '@simple-product/sdk/react';
110
+
111
+ export function Providers({ children, user }) {
112
+ return (
113
+ <SimpleProductProvider
114
+ apiKey={process.env.NEXT_PUBLIC_SP_API_KEY!}
115
+ user={user ? {
116
+ id: user.id,
117
+ email: user.email,
118
+ name: user.name,
119
+ } : null}
120
+ >
121
+ {children}
122
+ </SimpleProductProvider>
123
+ );
124
+ }
125
+ ```
126
+
127
+ The provider automatically:
128
+ - Calls `identify()` when user changes
129
+ - Sends heartbeats once per day
130
+ - Resets on logout
131
+
132
+ ### Track Custom Events
133
+
134
+ ```tsx
135
+ import { useSimpleProduct } from '@simple-product/sdk/react';
136
+
137
+ function MyComponent() {
138
+ const { track } = useSimpleProduct();
139
+
140
+ return (
141
+ <button onClick={() => track('button_clicked', { button: 'signup' })}>
142
+ Sign Up
143
+ </button>
144
+ );
145
+ }
146
+ ```
147
+
148
+ ## Manual Setup
149
+
150
+ If you're not using React, you can use the SDK directly:
151
+
152
+ ```tsx
153
+ import { SimpleProduct } from '@simple-product/sdk';
154
+
155
+ const sp = new SimpleProduct({
156
+ apiKey: process.env.NEXT_PUBLIC_SP_API_KEY!,
157
+ });
158
+
159
+ // When user logs in
160
+ sp.identify({
161
+ userId: user.id,
162
+ email: user.email,
163
+ name: user.name,
164
+ });
165
+
166
+ // Track custom events
167
+ sp.track('feature_used', { feature: 'export' });
168
+
169
+ // On logout
170
+ sp.reset();
171
+ ```
172
+
173
+ ## License
174
+
175
+ MIT
@@ -0,0 +1,216 @@
1
+ // src/index.ts
2
+ var STORAGE_KEY_PREFIX = "sp_sdk_";
3
+ var HEARTBEAT_STORAGE_KEY = `${STORAGE_KEY_PREFIX}last_heartbeat`;
4
+ var USER_ID_STORAGE_KEY = `${STORAGE_KEY_PREFIX}user_id`;
5
+ var ANONYMOUS_ID_STORAGE_KEY = `${STORAGE_KEY_PREFIX}anonymous_id`;
6
+ var SimpleProduct = class {
7
+ constructor(config) {
8
+ this.userId = null;
9
+ this.heartbeatInterval = null;
10
+ this.visibilityHandler = null;
11
+ this.apiKey = config.apiKey;
12
+ this.endpoint = config.endpoint || "https://simpleproduct.io/api/v1";
13
+ this.debug = config.debug || false;
14
+ this.disableAutoHeartbeat = config.disableAutoHeartbeat || false;
15
+ this.anonymousId = this.getOrCreateAnonymousId();
16
+ this.userId = this.getStoredUserId();
17
+ if (!this.disableAutoHeartbeat && typeof window !== "undefined") {
18
+ this.startHeartbeat();
19
+ }
20
+ this.log("Initialized", { userId: this.userId, anonymousId: this.anonymousId });
21
+ }
22
+ /**
23
+ * Identify a user - call this when a user signs in
24
+ */
25
+ identify(params) {
26
+ this.userId = params.userId;
27
+ this.storeUserId(params.userId);
28
+ this.log("Identify", params);
29
+ this.sendRequest("/people", {
30
+ method: "POST",
31
+ body: {
32
+ externalId: params.userId,
33
+ email: params.email,
34
+ name: params.name,
35
+ company: params.company,
36
+ ...params.traits
37
+ }
38
+ }).catch((err) => this.log("Identify error", err));
39
+ this.sendHeartbeat();
40
+ }
41
+ track(eventOrParams, properties) {
42
+ const event = typeof eventOrParams === "string" ? eventOrParams : eventOrParams.event;
43
+ const props = typeof eventOrParams === "string" ? properties : eventOrParams.properties;
44
+ this.log("Track", { event, properties: props });
45
+ this.sendRequest("/events", {
46
+ method: "POST",
47
+ body: {
48
+ event,
49
+ userId: this.userId,
50
+ anonymousId: this.anonymousId,
51
+ properties: props,
52
+ context: this.getContext()
53
+ }
54
+ }).catch((err) => this.log("Track error", err));
55
+ }
56
+ /**
57
+ * Reset the user - call this when a user signs out
58
+ */
59
+ reset() {
60
+ this.userId = null;
61
+ this.clearStoredUserId();
62
+ this.anonymousId = this.generateAnonymousId();
63
+ this.storeAnonymousId(this.anonymousId);
64
+ this.log("Reset");
65
+ }
66
+ /**
67
+ * Manually send a heartbeat (usually not needed - happens automatically)
68
+ */
69
+ heartbeat() {
70
+ this.sendHeartbeat();
71
+ }
72
+ /**
73
+ * Clean up SDK resources
74
+ */
75
+ destroy() {
76
+ if (this.heartbeatInterval) {
77
+ clearInterval(this.heartbeatInterval);
78
+ this.heartbeatInterval = null;
79
+ }
80
+ if (this.visibilityHandler && typeof document !== "undefined") {
81
+ document.removeEventListener("visibilitychange", this.visibilityHandler);
82
+ this.visibilityHandler = null;
83
+ }
84
+ this.log("Destroyed");
85
+ }
86
+ // --- Private methods ---
87
+ startHeartbeat() {
88
+ this.sendHeartbeatIfNeeded();
89
+ this.heartbeatInterval = setInterval(() => {
90
+ this.sendHeartbeatIfNeeded();
91
+ }, 5 * 60 * 1e3);
92
+ this.visibilityHandler = () => {
93
+ if (document.visibilityState === "visible") {
94
+ this.sendHeartbeatIfNeeded();
95
+ }
96
+ };
97
+ document.addEventListener("visibilitychange", this.visibilityHandler);
98
+ }
99
+ sendHeartbeatIfNeeded() {
100
+ if (!this.userId) {
101
+ this.log("Skipping heartbeat - no user identified");
102
+ return;
103
+ }
104
+ const today = this.getTodayString();
105
+ const lastHeartbeat = this.getLastHeartbeatDate();
106
+ if (lastHeartbeat === today) {
107
+ this.log("Skipping heartbeat - already sent today");
108
+ return;
109
+ }
110
+ this.sendHeartbeat();
111
+ }
112
+ sendHeartbeat() {
113
+ if (!this.userId) {
114
+ this.log("Cannot send heartbeat - no user identified");
115
+ return;
116
+ }
117
+ const today = this.getTodayString();
118
+ this.log("Sending heartbeat");
119
+ this.sendRequest("/events", {
120
+ method: "POST",
121
+ body: {
122
+ event: "sp_heartbeat",
123
+ userId: this.userId,
124
+ anonymousId: this.anonymousId,
125
+ context: this.getContext()
126
+ }
127
+ }).then(() => {
128
+ this.storeLastHeartbeatDate(today);
129
+ this.log("Heartbeat sent successfully");
130
+ }).catch((err) => this.log("Heartbeat error", err));
131
+ }
132
+ async sendRequest(path, options) {
133
+ const url = `${this.endpoint}${path}`;
134
+ const response = await fetch(url, {
135
+ method: options.method,
136
+ headers: {
137
+ "Content-Type": "application/json",
138
+ Authorization: `Bearer ${this.apiKey}`
139
+ },
140
+ body: options.body ? JSON.stringify(options.body) : void 0
141
+ });
142
+ if (!response.ok) {
143
+ const error = await response.text();
144
+ throw new Error(`API error: ${response.status} ${error}`);
145
+ }
146
+ return response.json();
147
+ }
148
+ getContext() {
149
+ if (typeof window === "undefined") {
150
+ return {};
151
+ }
152
+ return {
153
+ userAgent: navigator.userAgent,
154
+ page: {
155
+ url: window.location.href,
156
+ title: document.title,
157
+ referrer: document.referrer
158
+ }
159
+ };
160
+ }
161
+ getTodayString() {
162
+ return (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
163
+ }
164
+ getLastHeartbeatDate() {
165
+ if (typeof localStorage === "undefined") return null;
166
+ return localStorage.getItem(HEARTBEAT_STORAGE_KEY);
167
+ }
168
+ storeLastHeartbeatDate(date) {
169
+ if (typeof localStorage === "undefined") return;
170
+ localStorage.setItem(HEARTBEAT_STORAGE_KEY, date);
171
+ }
172
+ getStoredUserId() {
173
+ if (typeof localStorage === "undefined") return null;
174
+ return localStorage.getItem(USER_ID_STORAGE_KEY);
175
+ }
176
+ storeUserId(userId) {
177
+ if (typeof localStorage === "undefined") return;
178
+ localStorage.setItem(USER_ID_STORAGE_KEY, userId);
179
+ }
180
+ clearStoredUserId() {
181
+ if (typeof localStorage === "undefined") return;
182
+ localStorage.removeItem(USER_ID_STORAGE_KEY);
183
+ localStorage.removeItem(HEARTBEAT_STORAGE_KEY);
184
+ }
185
+ getOrCreateAnonymousId() {
186
+ if (typeof localStorage === "undefined") {
187
+ return this.generateAnonymousId();
188
+ }
189
+ const stored = localStorage.getItem(ANONYMOUS_ID_STORAGE_KEY);
190
+ if (stored) return stored;
191
+ const id = this.generateAnonymousId();
192
+ this.storeAnonymousId(id);
193
+ return id;
194
+ }
195
+ storeAnonymousId(id) {
196
+ if (typeof localStorage === "undefined") return;
197
+ localStorage.setItem(ANONYMOUS_ID_STORAGE_KEY, id);
198
+ }
199
+ generateAnonymousId() {
200
+ return "anon_" + Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15);
201
+ }
202
+ log(message, data) {
203
+ if (!this.debug) return;
204
+ console.log(`[SimpleProduct] ${message}`, data !== void 0 ? data : "");
205
+ }
206
+ };
207
+ function createSimpleProduct(config) {
208
+ return new SimpleProduct(config);
209
+ }
210
+ var index_default = SimpleProduct;
211
+
212
+ export {
213
+ SimpleProduct,
214
+ createSimpleProduct,
215
+ index_default
216
+ };
@@ -0,0 +1,85 @@
1
+ /**
2
+ * Simple Product SDK
3
+ *
4
+ * Track users and product usage with minimal setup.
5
+ * Automatically sends heartbeat events (once per day) to power analytics.
6
+ */
7
+ interface SimpleProductConfig {
8
+ /** Your API key (starts with sk_) */
9
+ apiKey: string;
10
+ /** API endpoint (defaults to https://simpleproduct.io/api/v1) */
11
+ endpoint?: string;
12
+ /** Disable automatic heartbeat tracking (default: false) */
13
+ disableAutoHeartbeat?: boolean;
14
+ /** Enable debug logging (default: false) */
15
+ debug?: boolean;
16
+ }
17
+ interface IdentifyParams {
18
+ /** Unique user ID from your system */
19
+ userId: string;
20
+ /** User's email address */
21
+ email?: string;
22
+ /** User's display name */
23
+ name?: string;
24
+ /** Company/organization name */
25
+ company?: string;
26
+ /** Any additional traits */
27
+ traits?: Record<string, unknown>;
28
+ }
29
+ interface TrackParams {
30
+ /** Event name (e.g., "feature_used", "button_clicked") */
31
+ event: string;
32
+ /** Event properties */
33
+ properties?: Record<string, unknown>;
34
+ }
35
+ declare class SimpleProduct {
36
+ private apiKey;
37
+ private endpoint;
38
+ private debug;
39
+ private userId;
40
+ private anonymousId;
41
+ private disableAutoHeartbeat;
42
+ private heartbeatInterval;
43
+ private visibilityHandler;
44
+ constructor(config: SimpleProductConfig);
45
+ /**
46
+ * Identify a user - call this when a user signs in
47
+ */
48
+ identify(params: IdentifyParams): void;
49
+ /**
50
+ * Track a custom event
51
+ */
52
+ track(event: string, properties?: Record<string, unknown>): void;
53
+ track(params: TrackParams): void;
54
+ /**
55
+ * Reset the user - call this when a user signs out
56
+ */
57
+ reset(): void;
58
+ /**
59
+ * Manually send a heartbeat (usually not needed - happens automatically)
60
+ */
61
+ heartbeat(): void;
62
+ /**
63
+ * Clean up SDK resources
64
+ */
65
+ destroy(): void;
66
+ private startHeartbeat;
67
+ private sendHeartbeatIfNeeded;
68
+ private sendHeartbeat;
69
+ private sendRequest;
70
+ private getContext;
71
+ private getTodayString;
72
+ private getLastHeartbeatDate;
73
+ private storeLastHeartbeatDate;
74
+ private getStoredUserId;
75
+ private storeUserId;
76
+ private clearStoredUserId;
77
+ private getOrCreateAnonymousId;
78
+ private storeAnonymousId;
79
+ private generateAnonymousId;
80
+ private log;
81
+ }
82
+
83
+ declare function createSimpleProduct(config: SimpleProductConfig): SimpleProduct;
84
+
85
+ export { type IdentifyParams, SimpleProduct, type SimpleProductConfig, type TrackParams, createSimpleProduct, SimpleProduct as default };
@@ -0,0 +1,85 @@
1
+ /**
2
+ * Simple Product SDK
3
+ *
4
+ * Track users and product usage with minimal setup.
5
+ * Automatically sends heartbeat events (once per day) to power analytics.
6
+ */
7
+ interface SimpleProductConfig {
8
+ /** Your API key (starts with sk_) */
9
+ apiKey: string;
10
+ /** API endpoint (defaults to https://simpleproduct.io/api/v1) */
11
+ endpoint?: string;
12
+ /** Disable automatic heartbeat tracking (default: false) */
13
+ disableAutoHeartbeat?: boolean;
14
+ /** Enable debug logging (default: false) */
15
+ debug?: boolean;
16
+ }
17
+ interface IdentifyParams {
18
+ /** Unique user ID from your system */
19
+ userId: string;
20
+ /** User's email address */
21
+ email?: string;
22
+ /** User's display name */
23
+ name?: string;
24
+ /** Company/organization name */
25
+ company?: string;
26
+ /** Any additional traits */
27
+ traits?: Record<string, unknown>;
28
+ }
29
+ interface TrackParams {
30
+ /** Event name (e.g., "feature_used", "button_clicked") */
31
+ event: string;
32
+ /** Event properties */
33
+ properties?: Record<string, unknown>;
34
+ }
35
+ declare class SimpleProduct {
36
+ private apiKey;
37
+ private endpoint;
38
+ private debug;
39
+ private userId;
40
+ private anonymousId;
41
+ private disableAutoHeartbeat;
42
+ private heartbeatInterval;
43
+ private visibilityHandler;
44
+ constructor(config: SimpleProductConfig);
45
+ /**
46
+ * Identify a user - call this when a user signs in
47
+ */
48
+ identify(params: IdentifyParams): void;
49
+ /**
50
+ * Track a custom event
51
+ */
52
+ track(event: string, properties?: Record<string, unknown>): void;
53
+ track(params: TrackParams): void;
54
+ /**
55
+ * Reset the user - call this when a user signs out
56
+ */
57
+ reset(): void;
58
+ /**
59
+ * Manually send a heartbeat (usually not needed - happens automatically)
60
+ */
61
+ heartbeat(): void;
62
+ /**
63
+ * Clean up SDK resources
64
+ */
65
+ destroy(): void;
66
+ private startHeartbeat;
67
+ private sendHeartbeatIfNeeded;
68
+ private sendHeartbeat;
69
+ private sendRequest;
70
+ private getContext;
71
+ private getTodayString;
72
+ private getLastHeartbeatDate;
73
+ private storeLastHeartbeatDate;
74
+ private getStoredUserId;
75
+ private storeUserId;
76
+ private clearStoredUserId;
77
+ private getOrCreateAnonymousId;
78
+ private storeAnonymousId;
79
+ private generateAnonymousId;
80
+ private log;
81
+ }
82
+
83
+ declare function createSimpleProduct(config: SimpleProductConfig): SimpleProduct;
84
+
85
+ export { type IdentifyParams, SimpleProduct, type SimpleProductConfig, type TrackParams, createSimpleProduct, SimpleProduct as default };