@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/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
@@ -0,0 +1,10 @@
1
+ import {
2
+ SimpleProduct,
3
+ createSimpleProduct,
4
+ index_default
5
+ } from "./chunk-7F2GKQLH.mjs";
6
+ export {
7
+ SimpleProduct,
8
+ createSimpleProduct,
9
+ index_default as default
10
+ };
@@ -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 };
@@ -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 };