@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 +175 -0
- package/dist/chunk-7F2GKQLH.mjs +216 -0
- package/dist/index.d.mts +85 -0
- package/dist/index.d.ts +85 -0
- package/dist/index.js +241 -0
- package/dist/index.mjs +10 -0
- package/dist/react.d.mts +82 -0
- package/dist/react.d.ts +82 -0
- package/dist/react.js +313 -0
- package/dist/react.mjs +80 -0
- package/package.json +49 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
SimpleProduct: () => SimpleProduct,
|
|
24
|
+
createSimpleProduct: () => createSimpleProduct,
|
|
25
|
+
default: () => index_default
|
|
26
|
+
});
|
|
27
|
+
module.exports = __toCommonJS(index_exports);
|
|
28
|
+
var STORAGE_KEY_PREFIX = "sp_sdk_";
|
|
29
|
+
var HEARTBEAT_STORAGE_KEY = `${STORAGE_KEY_PREFIX}last_heartbeat`;
|
|
30
|
+
var USER_ID_STORAGE_KEY = `${STORAGE_KEY_PREFIX}user_id`;
|
|
31
|
+
var ANONYMOUS_ID_STORAGE_KEY = `${STORAGE_KEY_PREFIX}anonymous_id`;
|
|
32
|
+
var SimpleProduct = class {
|
|
33
|
+
constructor(config) {
|
|
34
|
+
this.userId = null;
|
|
35
|
+
this.heartbeatInterval = null;
|
|
36
|
+
this.visibilityHandler = null;
|
|
37
|
+
this.apiKey = config.apiKey;
|
|
38
|
+
this.endpoint = config.endpoint || "https://simpleproduct.io/api/v1";
|
|
39
|
+
this.debug = config.debug || false;
|
|
40
|
+
this.disableAutoHeartbeat = config.disableAutoHeartbeat || false;
|
|
41
|
+
this.anonymousId = this.getOrCreateAnonymousId();
|
|
42
|
+
this.userId = this.getStoredUserId();
|
|
43
|
+
if (!this.disableAutoHeartbeat && typeof window !== "undefined") {
|
|
44
|
+
this.startHeartbeat();
|
|
45
|
+
}
|
|
46
|
+
this.log("Initialized", { userId: this.userId, anonymousId: this.anonymousId });
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Identify a user - call this when a user signs in
|
|
50
|
+
*/
|
|
51
|
+
identify(params) {
|
|
52
|
+
this.userId = params.userId;
|
|
53
|
+
this.storeUserId(params.userId);
|
|
54
|
+
this.log("Identify", params);
|
|
55
|
+
this.sendRequest("/people", {
|
|
56
|
+
method: "POST",
|
|
57
|
+
body: {
|
|
58
|
+
externalId: params.userId,
|
|
59
|
+
email: params.email,
|
|
60
|
+
name: params.name,
|
|
61
|
+
company: params.company,
|
|
62
|
+
...params.traits
|
|
63
|
+
}
|
|
64
|
+
}).catch((err) => this.log("Identify error", err));
|
|
65
|
+
this.sendHeartbeat();
|
|
66
|
+
}
|
|
67
|
+
track(eventOrParams, properties) {
|
|
68
|
+
const event = typeof eventOrParams === "string" ? eventOrParams : eventOrParams.event;
|
|
69
|
+
const props = typeof eventOrParams === "string" ? properties : eventOrParams.properties;
|
|
70
|
+
this.log("Track", { event, properties: props });
|
|
71
|
+
this.sendRequest("/events", {
|
|
72
|
+
method: "POST",
|
|
73
|
+
body: {
|
|
74
|
+
event,
|
|
75
|
+
userId: this.userId,
|
|
76
|
+
anonymousId: this.anonymousId,
|
|
77
|
+
properties: props,
|
|
78
|
+
context: this.getContext()
|
|
79
|
+
}
|
|
80
|
+
}).catch((err) => this.log("Track error", err));
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Reset the user - call this when a user signs out
|
|
84
|
+
*/
|
|
85
|
+
reset() {
|
|
86
|
+
this.userId = null;
|
|
87
|
+
this.clearStoredUserId();
|
|
88
|
+
this.anonymousId = this.generateAnonymousId();
|
|
89
|
+
this.storeAnonymousId(this.anonymousId);
|
|
90
|
+
this.log("Reset");
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Manually send a heartbeat (usually not needed - happens automatically)
|
|
94
|
+
*/
|
|
95
|
+
heartbeat() {
|
|
96
|
+
this.sendHeartbeat();
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Clean up SDK resources
|
|
100
|
+
*/
|
|
101
|
+
destroy() {
|
|
102
|
+
if (this.heartbeatInterval) {
|
|
103
|
+
clearInterval(this.heartbeatInterval);
|
|
104
|
+
this.heartbeatInterval = null;
|
|
105
|
+
}
|
|
106
|
+
if (this.visibilityHandler && typeof document !== "undefined") {
|
|
107
|
+
document.removeEventListener("visibilitychange", this.visibilityHandler);
|
|
108
|
+
this.visibilityHandler = null;
|
|
109
|
+
}
|
|
110
|
+
this.log("Destroyed");
|
|
111
|
+
}
|
|
112
|
+
// --- Private methods ---
|
|
113
|
+
startHeartbeat() {
|
|
114
|
+
this.sendHeartbeatIfNeeded();
|
|
115
|
+
this.heartbeatInterval = setInterval(() => {
|
|
116
|
+
this.sendHeartbeatIfNeeded();
|
|
117
|
+
}, 5 * 60 * 1e3);
|
|
118
|
+
this.visibilityHandler = () => {
|
|
119
|
+
if (document.visibilityState === "visible") {
|
|
120
|
+
this.sendHeartbeatIfNeeded();
|
|
121
|
+
}
|
|
122
|
+
};
|
|
123
|
+
document.addEventListener("visibilitychange", this.visibilityHandler);
|
|
124
|
+
}
|
|
125
|
+
sendHeartbeatIfNeeded() {
|
|
126
|
+
if (!this.userId) {
|
|
127
|
+
this.log("Skipping heartbeat - no user identified");
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
const today = this.getTodayString();
|
|
131
|
+
const lastHeartbeat = this.getLastHeartbeatDate();
|
|
132
|
+
if (lastHeartbeat === today) {
|
|
133
|
+
this.log("Skipping heartbeat - already sent today");
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
this.sendHeartbeat();
|
|
137
|
+
}
|
|
138
|
+
sendHeartbeat() {
|
|
139
|
+
if (!this.userId) {
|
|
140
|
+
this.log("Cannot send heartbeat - no user identified");
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
const today = this.getTodayString();
|
|
144
|
+
this.log("Sending heartbeat");
|
|
145
|
+
this.sendRequest("/events", {
|
|
146
|
+
method: "POST",
|
|
147
|
+
body: {
|
|
148
|
+
event: "sp_heartbeat",
|
|
149
|
+
userId: this.userId,
|
|
150
|
+
anonymousId: this.anonymousId,
|
|
151
|
+
context: this.getContext()
|
|
152
|
+
}
|
|
153
|
+
}).then(() => {
|
|
154
|
+
this.storeLastHeartbeatDate(today);
|
|
155
|
+
this.log("Heartbeat sent successfully");
|
|
156
|
+
}).catch((err) => this.log("Heartbeat error", err));
|
|
157
|
+
}
|
|
158
|
+
async sendRequest(path, options) {
|
|
159
|
+
const url = `${this.endpoint}${path}`;
|
|
160
|
+
const response = await fetch(url, {
|
|
161
|
+
method: options.method,
|
|
162
|
+
headers: {
|
|
163
|
+
"Content-Type": "application/json",
|
|
164
|
+
Authorization: `Bearer ${this.apiKey}`
|
|
165
|
+
},
|
|
166
|
+
body: options.body ? JSON.stringify(options.body) : void 0
|
|
167
|
+
});
|
|
168
|
+
if (!response.ok) {
|
|
169
|
+
const error = await response.text();
|
|
170
|
+
throw new Error(`API error: ${response.status} ${error}`);
|
|
171
|
+
}
|
|
172
|
+
return response.json();
|
|
173
|
+
}
|
|
174
|
+
getContext() {
|
|
175
|
+
if (typeof window === "undefined") {
|
|
176
|
+
return {};
|
|
177
|
+
}
|
|
178
|
+
return {
|
|
179
|
+
userAgent: navigator.userAgent,
|
|
180
|
+
page: {
|
|
181
|
+
url: window.location.href,
|
|
182
|
+
title: document.title,
|
|
183
|
+
referrer: document.referrer
|
|
184
|
+
}
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
getTodayString() {
|
|
188
|
+
return (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
189
|
+
}
|
|
190
|
+
getLastHeartbeatDate() {
|
|
191
|
+
if (typeof localStorage === "undefined") return null;
|
|
192
|
+
return localStorage.getItem(HEARTBEAT_STORAGE_KEY);
|
|
193
|
+
}
|
|
194
|
+
storeLastHeartbeatDate(date) {
|
|
195
|
+
if (typeof localStorage === "undefined") return;
|
|
196
|
+
localStorage.setItem(HEARTBEAT_STORAGE_KEY, date);
|
|
197
|
+
}
|
|
198
|
+
getStoredUserId() {
|
|
199
|
+
if (typeof localStorage === "undefined") return null;
|
|
200
|
+
return localStorage.getItem(USER_ID_STORAGE_KEY);
|
|
201
|
+
}
|
|
202
|
+
storeUserId(userId) {
|
|
203
|
+
if (typeof localStorage === "undefined") return;
|
|
204
|
+
localStorage.setItem(USER_ID_STORAGE_KEY, userId);
|
|
205
|
+
}
|
|
206
|
+
clearStoredUserId() {
|
|
207
|
+
if (typeof localStorage === "undefined") return;
|
|
208
|
+
localStorage.removeItem(USER_ID_STORAGE_KEY);
|
|
209
|
+
localStorage.removeItem(HEARTBEAT_STORAGE_KEY);
|
|
210
|
+
}
|
|
211
|
+
getOrCreateAnonymousId() {
|
|
212
|
+
if (typeof localStorage === "undefined") {
|
|
213
|
+
return this.generateAnonymousId();
|
|
214
|
+
}
|
|
215
|
+
const stored = localStorage.getItem(ANONYMOUS_ID_STORAGE_KEY);
|
|
216
|
+
if (stored) return stored;
|
|
217
|
+
const id = this.generateAnonymousId();
|
|
218
|
+
this.storeAnonymousId(id);
|
|
219
|
+
return id;
|
|
220
|
+
}
|
|
221
|
+
storeAnonymousId(id) {
|
|
222
|
+
if (typeof localStorage === "undefined") return;
|
|
223
|
+
localStorage.setItem(ANONYMOUS_ID_STORAGE_KEY, id);
|
|
224
|
+
}
|
|
225
|
+
generateAnonymousId() {
|
|
226
|
+
return "anon_" + Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15);
|
|
227
|
+
}
|
|
228
|
+
log(message, data) {
|
|
229
|
+
if (!this.debug) return;
|
|
230
|
+
console.log(`[SimpleProduct] ${message}`, data !== void 0 ? data : "");
|
|
231
|
+
}
|
|
232
|
+
};
|
|
233
|
+
function createSimpleProduct(config) {
|
|
234
|
+
return new SimpleProduct(config);
|
|
235
|
+
}
|
|
236
|
+
var index_default = SimpleProduct;
|
|
237
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
238
|
+
0 && (module.exports = {
|
|
239
|
+
SimpleProduct,
|
|
240
|
+
createSimpleProduct
|
|
241
|
+
});
|
package/dist/index.mjs
ADDED
package/dist/react.d.mts
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
+
import { ReactNode } from 'react';
|
|
3
|
+
import { IdentifyParams } from './index.mjs';
|
|
4
|
+
|
|
5
|
+
interface SimpleProductContextValue {
|
|
6
|
+
/** Track a custom event */
|
|
7
|
+
track: (event: string, properties?: Record<string, unknown>) => void;
|
|
8
|
+
/** Manually trigger identify (usually not needed if user prop is provided) */
|
|
9
|
+
identify: (params: IdentifyParams) => void;
|
|
10
|
+
/** Reset user on logout */
|
|
11
|
+
reset: () => void;
|
|
12
|
+
}
|
|
13
|
+
interface SimpleProductProviderProps {
|
|
14
|
+
children: ReactNode;
|
|
15
|
+
/** Your Simple Product API key */
|
|
16
|
+
apiKey: string;
|
|
17
|
+
/** Optional API endpoint override */
|
|
18
|
+
endpoint?: string;
|
|
19
|
+
/**
|
|
20
|
+
* The current user. When provided, identify() is called automatically.
|
|
21
|
+
* At minimum, provide userId. Email and name are recommended.
|
|
22
|
+
*/
|
|
23
|
+
user?: {
|
|
24
|
+
id: string;
|
|
25
|
+
email?: string;
|
|
26
|
+
name?: string;
|
|
27
|
+
company?: string;
|
|
28
|
+
[key: string]: unknown;
|
|
29
|
+
} | null;
|
|
30
|
+
/** Disable automatic heartbeat (not recommended) */
|
|
31
|
+
disableAutoHeartbeat?: boolean;
|
|
32
|
+
/** Enable debug logging */
|
|
33
|
+
debug?: boolean;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Simple Product Provider
|
|
37
|
+
*
|
|
38
|
+
* Wrap your app with this provider to enable analytics tracking.
|
|
39
|
+
*
|
|
40
|
+
* @example
|
|
41
|
+
* ```tsx
|
|
42
|
+
* // app/providers.tsx
|
|
43
|
+
* import { SimpleProductProvider } from '@simple-product/sdk/react';
|
|
44
|
+
*
|
|
45
|
+
* export function Providers({ children }) {
|
|
46
|
+
* const user = useCurrentUser(); // from your auth
|
|
47
|
+
*
|
|
48
|
+
* return (
|
|
49
|
+
* <SimpleProductProvider
|
|
50
|
+
* apiKey={process.env.NEXT_PUBLIC_SP_API_KEY!}
|
|
51
|
+
* user={user ? { id: user.id, email: user.email, name: user.name } : null}
|
|
52
|
+
* >
|
|
53
|
+
* {children}
|
|
54
|
+
* </SimpleProductProvider>
|
|
55
|
+
* );
|
|
56
|
+
* }
|
|
57
|
+
* ```
|
|
58
|
+
*/
|
|
59
|
+
declare function SimpleProductProvider({ children, apiKey, endpoint, user, disableAutoHeartbeat, debug, }: SimpleProductProviderProps): react_jsx_runtime.JSX.Element;
|
|
60
|
+
/**
|
|
61
|
+
* Hook to access Simple Product tracking functions
|
|
62
|
+
*
|
|
63
|
+
* @example
|
|
64
|
+
* ```tsx
|
|
65
|
+
* function MyComponent() {
|
|
66
|
+
* const { track } = useSimpleProduct();
|
|
67
|
+
*
|
|
68
|
+
* return (
|
|
69
|
+
* <button onClick={() => track('button_clicked', { button: 'signup' })}>
|
|
70
|
+
* Sign Up
|
|
71
|
+
* </button>
|
|
72
|
+
* );
|
|
73
|
+
* }
|
|
74
|
+
* ```
|
|
75
|
+
*/
|
|
76
|
+
declare function useSimpleProduct(): SimpleProductContextValue;
|
|
77
|
+
/**
|
|
78
|
+
* Hook that returns track function only (convenience for common use case)
|
|
79
|
+
*/
|
|
80
|
+
declare function useTrack(): (event: string, properties?: Record<string, unknown>) => void;
|
|
81
|
+
|
|
82
|
+
export { SimpleProductProvider, type SimpleProductProviderProps, useSimpleProduct, useTrack };
|
package/dist/react.d.ts
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
+
import { ReactNode } from 'react';
|
|
3
|
+
import { IdentifyParams } from './index.js';
|
|
4
|
+
|
|
5
|
+
interface SimpleProductContextValue {
|
|
6
|
+
/** Track a custom event */
|
|
7
|
+
track: (event: string, properties?: Record<string, unknown>) => void;
|
|
8
|
+
/** Manually trigger identify (usually not needed if user prop is provided) */
|
|
9
|
+
identify: (params: IdentifyParams) => void;
|
|
10
|
+
/** Reset user on logout */
|
|
11
|
+
reset: () => void;
|
|
12
|
+
}
|
|
13
|
+
interface SimpleProductProviderProps {
|
|
14
|
+
children: ReactNode;
|
|
15
|
+
/** Your Simple Product API key */
|
|
16
|
+
apiKey: string;
|
|
17
|
+
/** Optional API endpoint override */
|
|
18
|
+
endpoint?: string;
|
|
19
|
+
/**
|
|
20
|
+
* The current user. When provided, identify() is called automatically.
|
|
21
|
+
* At minimum, provide userId. Email and name are recommended.
|
|
22
|
+
*/
|
|
23
|
+
user?: {
|
|
24
|
+
id: string;
|
|
25
|
+
email?: string;
|
|
26
|
+
name?: string;
|
|
27
|
+
company?: string;
|
|
28
|
+
[key: string]: unknown;
|
|
29
|
+
} | null;
|
|
30
|
+
/** Disable automatic heartbeat (not recommended) */
|
|
31
|
+
disableAutoHeartbeat?: boolean;
|
|
32
|
+
/** Enable debug logging */
|
|
33
|
+
debug?: boolean;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Simple Product Provider
|
|
37
|
+
*
|
|
38
|
+
* Wrap your app with this provider to enable analytics tracking.
|
|
39
|
+
*
|
|
40
|
+
* @example
|
|
41
|
+
* ```tsx
|
|
42
|
+
* // app/providers.tsx
|
|
43
|
+
* import { SimpleProductProvider } from '@simple-product/sdk/react';
|
|
44
|
+
*
|
|
45
|
+
* export function Providers({ children }) {
|
|
46
|
+
* const user = useCurrentUser(); // from your auth
|
|
47
|
+
*
|
|
48
|
+
* return (
|
|
49
|
+
* <SimpleProductProvider
|
|
50
|
+
* apiKey={process.env.NEXT_PUBLIC_SP_API_KEY!}
|
|
51
|
+
* user={user ? { id: user.id, email: user.email, name: user.name } : null}
|
|
52
|
+
* >
|
|
53
|
+
* {children}
|
|
54
|
+
* </SimpleProductProvider>
|
|
55
|
+
* );
|
|
56
|
+
* }
|
|
57
|
+
* ```
|
|
58
|
+
*/
|
|
59
|
+
declare function SimpleProductProvider({ children, apiKey, endpoint, user, disableAutoHeartbeat, debug, }: SimpleProductProviderProps): react_jsx_runtime.JSX.Element;
|
|
60
|
+
/**
|
|
61
|
+
* Hook to access Simple Product tracking functions
|
|
62
|
+
*
|
|
63
|
+
* @example
|
|
64
|
+
* ```tsx
|
|
65
|
+
* function MyComponent() {
|
|
66
|
+
* const { track } = useSimpleProduct();
|
|
67
|
+
*
|
|
68
|
+
* return (
|
|
69
|
+
* <button onClick={() => track('button_clicked', { button: 'signup' })}>
|
|
70
|
+
* Sign Up
|
|
71
|
+
* </button>
|
|
72
|
+
* );
|
|
73
|
+
* }
|
|
74
|
+
* ```
|
|
75
|
+
*/
|
|
76
|
+
declare function useSimpleProduct(): SimpleProductContextValue;
|
|
77
|
+
/**
|
|
78
|
+
* Hook that returns track function only (convenience for common use case)
|
|
79
|
+
*/
|
|
80
|
+
declare function useTrack(): (event: string, properties?: Record<string, unknown>) => void;
|
|
81
|
+
|
|
82
|
+
export { SimpleProductProvider, type SimpleProductProviderProps, useSimpleProduct, useTrack };
|