@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/react.js ADDED
@@ -0,0 +1,313 @@
1
+ "use strict";
2
+ "use client";
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
7
+ var __export = (target, all) => {
8
+ for (var name in all)
9
+ __defProp(target, name, { get: all[name], enumerable: true });
10
+ };
11
+ var __copyProps = (to, from, except, desc) => {
12
+ if (from && typeof from === "object" || typeof from === "function") {
13
+ for (let key of __getOwnPropNames(from))
14
+ if (!__hasOwnProp.call(to, key) && key !== except)
15
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
16
+ }
17
+ return to;
18
+ };
19
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
20
+
21
+ // src/react.tsx
22
+ var react_exports = {};
23
+ __export(react_exports, {
24
+ SimpleProductProvider: () => SimpleProductProvider,
25
+ useSimpleProduct: () => useSimpleProduct,
26
+ useTrack: () => useTrack
27
+ });
28
+ module.exports = __toCommonJS(react_exports);
29
+ var import_react = require("react");
30
+
31
+ // src/index.ts
32
+ var STORAGE_KEY_PREFIX = "sp_sdk_";
33
+ var HEARTBEAT_STORAGE_KEY = `${STORAGE_KEY_PREFIX}last_heartbeat`;
34
+ var USER_ID_STORAGE_KEY = `${STORAGE_KEY_PREFIX}user_id`;
35
+ var ANONYMOUS_ID_STORAGE_KEY = `${STORAGE_KEY_PREFIX}anonymous_id`;
36
+ var SimpleProduct = class {
37
+ constructor(config) {
38
+ this.userId = null;
39
+ this.heartbeatInterval = null;
40
+ this.visibilityHandler = null;
41
+ this.apiKey = config.apiKey;
42
+ this.endpoint = config.endpoint || "https://simpleproduct.io/api/v1";
43
+ this.debug = config.debug || false;
44
+ this.disableAutoHeartbeat = config.disableAutoHeartbeat || false;
45
+ this.anonymousId = this.getOrCreateAnonymousId();
46
+ this.userId = this.getStoredUserId();
47
+ if (!this.disableAutoHeartbeat && typeof window !== "undefined") {
48
+ this.startHeartbeat();
49
+ }
50
+ this.log("Initialized", { userId: this.userId, anonymousId: this.anonymousId });
51
+ }
52
+ /**
53
+ * Identify a user - call this when a user signs in
54
+ */
55
+ identify(params) {
56
+ this.userId = params.userId;
57
+ this.storeUserId(params.userId);
58
+ this.log("Identify", params);
59
+ this.sendRequest("/people", {
60
+ method: "POST",
61
+ body: {
62
+ externalId: params.userId,
63
+ email: params.email,
64
+ name: params.name,
65
+ company: params.company,
66
+ ...params.traits
67
+ }
68
+ }).catch((err) => this.log("Identify error", err));
69
+ this.sendHeartbeat();
70
+ }
71
+ track(eventOrParams, properties) {
72
+ const event = typeof eventOrParams === "string" ? eventOrParams : eventOrParams.event;
73
+ const props = typeof eventOrParams === "string" ? properties : eventOrParams.properties;
74
+ this.log("Track", { event, properties: props });
75
+ this.sendRequest("/events", {
76
+ method: "POST",
77
+ body: {
78
+ event,
79
+ userId: this.userId,
80
+ anonymousId: this.anonymousId,
81
+ properties: props,
82
+ context: this.getContext()
83
+ }
84
+ }).catch((err) => this.log("Track error", err));
85
+ }
86
+ /**
87
+ * Reset the user - call this when a user signs out
88
+ */
89
+ reset() {
90
+ this.userId = null;
91
+ this.clearStoredUserId();
92
+ this.anonymousId = this.generateAnonymousId();
93
+ this.storeAnonymousId(this.anonymousId);
94
+ this.log("Reset");
95
+ }
96
+ /**
97
+ * Manually send a heartbeat (usually not needed - happens automatically)
98
+ */
99
+ heartbeat() {
100
+ this.sendHeartbeat();
101
+ }
102
+ /**
103
+ * Clean up SDK resources
104
+ */
105
+ destroy() {
106
+ if (this.heartbeatInterval) {
107
+ clearInterval(this.heartbeatInterval);
108
+ this.heartbeatInterval = null;
109
+ }
110
+ if (this.visibilityHandler && typeof document !== "undefined") {
111
+ document.removeEventListener("visibilitychange", this.visibilityHandler);
112
+ this.visibilityHandler = null;
113
+ }
114
+ this.log("Destroyed");
115
+ }
116
+ // --- Private methods ---
117
+ startHeartbeat() {
118
+ this.sendHeartbeatIfNeeded();
119
+ this.heartbeatInterval = setInterval(() => {
120
+ this.sendHeartbeatIfNeeded();
121
+ }, 5 * 60 * 1e3);
122
+ this.visibilityHandler = () => {
123
+ if (document.visibilityState === "visible") {
124
+ this.sendHeartbeatIfNeeded();
125
+ }
126
+ };
127
+ document.addEventListener("visibilitychange", this.visibilityHandler);
128
+ }
129
+ sendHeartbeatIfNeeded() {
130
+ if (!this.userId) {
131
+ this.log("Skipping heartbeat - no user identified");
132
+ return;
133
+ }
134
+ const today = this.getTodayString();
135
+ const lastHeartbeat = this.getLastHeartbeatDate();
136
+ if (lastHeartbeat === today) {
137
+ this.log("Skipping heartbeat - already sent today");
138
+ return;
139
+ }
140
+ this.sendHeartbeat();
141
+ }
142
+ sendHeartbeat() {
143
+ if (!this.userId) {
144
+ this.log("Cannot send heartbeat - no user identified");
145
+ return;
146
+ }
147
+ const today = this.getTodayString();
148
+ this.log("Sending heartbeat");
149
+ this.sendRequest("/events", {
150
+ method: "POST",
151
+ body: {
152
+ event: "sp_heartbeat",
153
+ userId: this.userId,
154
+ anonymousId: this.anonymousId,
155
+ context: this.getContext()
156
+ }
157
+ }).then(() => {
158
+ this.storeLastHeartbeatDate(today);
159
+ this.log("Heartbeat sent successfully");
160
+ }).catch((err) => this.log("Heartbeat error", err));
161
+ }
162
+ async sendRequest(path, options) {
163
+ const url = `${this.endpoint}${path}`;
164
+ const response = await fetch(url, {
165
+ method: options.method,
166
+ headers: {
167
+ "Content-Type": "application/json",
168
+ Authorization: `Bearer ${this.apiKey}`
169
+ },
170
+ body: options.body ? JSON.stringify(options.body) : void 0
171
+ });
172
+ if (!response.ok) {
173
+ const error = await response.text();
174
+ throw new Error(`API error: ${response.status} ${error}`);
175
+ }
176
+ return response.json();
177
+ }
178
+ getContext() {
179
+ if (typeof window === "undefined") {
180
+ return {};
181
+ }
182
+ return {
183
+ userAgent: navigator.userAgent,
184
+ page: {
185
+ url: window.location.href,
186
+ title: document.title,
187
+ referrer: document.referrer
188
+ }
189
+ };
190
+ }
191
+ getTodayString() {
192
+ return (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
193
+ }
194
+ getLastHeartbeatDate() {
195
+ if (typeof localStorage === "undefined") return null;
196
+ return localStorage.getItem(HEARTBEAT_STORAGE_KEY);
197
+ }
198
+ storeLastHeartbeatDate(date) {
199
+ if (typeof localStorage === "undefined") return;
200
+ localStorage.setItem(HEARTBEAT_STORAGE_KEY, date);
201
+ }
202
+ getStoredUserId() {
203
+ if (typeof localStorage === "undefined") return null;
204
+ return localStorage.getItem(USER_ID_STORAGE_KEY);
205
+ }
206
+ storeUserId(userId) {
207
+ if (typeof localStorage === "undefined") return;
208
+ localStorage.setItem(USER_ID_STORAGE_KEY, userId);
209
+ }
210
+ clearStoredUserId() {
211
+ if (typeof localStorage === "undefined") return;
212
+ localStorage.removeItem(USER_ID_STORAGE_KEY);
213
+ localStorage.removeItem(HEARTBEAT_STORAGE_KEY);
214
+ }
215
+ getOrCreateAnonymousId() {
216
+ if (typeof localStorage === "undefined") {
217
+ return this.generateAnonymousId();
218
+ }
219
+ const stored = localStorage.getItem(ANONYMOUS_ID_STORAGE_KEY);
220
+ if (stored) return stored;
221
+ const id = this.generateAnonymousId();
222
+ this.storeAnonymousId(id);
223
+ return id;
224
+ }
225
+ storeAnonymousId(id) {
226
+ if (typeof localStorage === "undefined") return;
227
+ localStorage.setItem(ANONYMOUS_ID_STORAGE_KEY, id);
228
+ }
229
+ generateAnonymousId() {
230
+ return "anon_" + Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15);
231
+ }
232
+ log(message, data) {
233
+ if (!this.debug) return;
234
+ console.log(`[SimpleProduct] ${message}`, data !== void 0 ? data : "");
235
+ }
236
+ };
237
+ var index_default = SimpleProduct;
238
+
239
+ // src/react.tsx
240
+ var import_jsx_runtime = require("react/jsx-runtime");
241
+ var SimpleProductContext = (0, import_react.createContext)(null);
242
+ function SimpleProductProvider({
243
+ children,
244
+ apiKey,
245
+ endpoint,
246
+ user,
247
+ disableAutoHeartbeat,
248
+ debug
249
+ }) {
250
+ const spRef = (0, import_react.useRef)(null);
251
+ const identifiedUserIdRef = (0, import_react.useRef)(null);
252
+ (0, import_react.useEffect)(() => {
253
+ if (typeof window === "undefined") return;
254
+ spRef.current = new index_default({
255
+ apiKey,
256
+ endpoint,
257
+ disableAutoHeartbeat,
258
+ debug
259
+ });
260
+ return () => {
261
+ spRef.current?.destroy();
262
+ spRef.current = null;
263
+ };
264
+ }, [apiKey, endpoint, disableAutoHeartbeat, debug]);
265
+ (0, import_react.useEffect)(() => {
266
+ if (!spRef.current) return;
267
+ if (user?.id && user.id !== identifiedUserIdRef.current) {
268
+ const { id, email, name, company, ...traits } = user;
269
+ spRef.current.identify({
270
+ userId: id,
271
+ email,
272
+ name,
273
+ company,
274
+ traits: Object.keys(traits).length > 0 ? traits : void 0
275
+ });
276
+ identifiedUserIdRef.current = id;
277
+ } else if (!user && identifiedUserIdRef.current) {
278
+ spRef.current.reset();
279
+ identifiedUserIdRef.current = null;
280
+ }
281
+ }, [user]);
282
+ const contextValue = {
283
+ track: (event, properties) => {
284
+ spRef.current?.track(event, properties);
285
+ },
286
+ identify: (params) => {
287
+ spRef.current?.identify(params);
288
+ identifiedUserIdRef.current = params.userId;
289
+ },
290
+ reset: () => {
291
+ spRef.current?.reset();
292
+ identifiedUserIdRef.current = null;
293
+ }
294
+ };
295
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SimpleProductContext.Provider, { value: contextValue, children });
296
+ }
297
+ function useSimpleProduct() {
298
+ const context = (0, import_react.useContext)(SimpleProductContext);
299
+ if (!context) {
300
+ throw new Error("useSimpleProduct must be used within a SimpleProductProvider");
301
+ }
302
+ return context;
303
+ }
304
+ function useTrack() {
305
+ const { track } = useSimpleProduct();
306
+ return track;
307
+ }
308
+ // Annotate the CommonJS export names for ESM import in node:
309
+ 0 && (module.exports = {
310
+ SimpleProductProvider,
311
+ useSimpleProduct,
312
+ useTrack
313
+ });
package/dist/react.mjs ADDED
@@ -0,0 +1,80 @@
1
+ "use client";
2
+ import {
3
+ index_default
4
+ } from "./chunk-7F2GKQLH.mjs";
5
+
6
+ // src/react.tsx
7
+ import { createContext, useContext, useEffect, useRef } from "react";
8
+ import { jsx } from "react/jsx-runtime";
9
+ var SimpleProductContext = createContext(null);
10
+ function SimpleProductProvider({
11
+ children,
12
+ apiKey,
13
+ endpoint,
14
+ user,
15
+ disableAutoHeartbeat,
16
+ debug
17
+ }) {
18
+ const spRef = useRef(null);
19
+ const identifiedUserIdRef = useRef(null);
20
+ useEffect(() => {
21
+ if (typeof window === "undefined") return;
22
+ spRef.current = new index_default({
23
+ apiKey,
24
+ endpoint,
25
+ disableAutoHeartbeat,
26
+ debug
27
+ });
28
+ return () => {
29
+ spRef.current?.destroy();
30
+ spRef.current = null;
31
+ };
32
+ }, [apiKey, endpoint, disableAutoHeartbeat, debug]);
33
+ useEffect(() => {
34
+ if (!spRef.current) return;
35
+ if (user?.id && user.id !== identifiedUserIdRef.current) {
36
+ const { id, email, name, company, ...traits } = user;
37
+ spRef.current.identify({
38
+ userId: id,
39
+ email,
40
+ name,
41
+ company,
42
+ traits: Object.keys(traits).length > 0 ? traits : void 0
43
+ });
44
+ identifiedUserIdRef.current = id;
45
+ } else if (!user && identifiedUserIdRef.current) {
46
+ spRef.current.reset();
47
+ identifiedUserIdRef.current = null;
48
+ }
49
+ }, [user]);
50
+ const contextValue = {
51
+ track: (event, properties) => {
52
+ spRef.current?.track(event, properties);
53
+ },
54
+ identify: (params) => {
55
+ spRef.current?.identify(params);
56
+ identifiedUserIdRef.current = params.userId;
57
+ },
58
+ reset: () => {
59
+ spRef.current?.reset();
60
+ identifiedUserIdRef.current = null;
61
+ }
62
+ };
63
+ return /* @__PURE__ */ jsx(SimpleProductContext.Provider, { value: contextValue, children });
64
+ }
65
+ function useSimpleProduct() {
66
+ const context = useContext(SimpleProductContext);
67
+ if (!context) {
68
+ throw new Error("useSimpleProduct must be used within a SimpleProductProvider");
69
+ }
70
+ return context;
71
+ }
72
+ function useTrack() {
73
+ const { track } = useSimpleProduct();
74
+ return track;
75
+ }
76
+ export {
77
+ SimpleProductProvider,
78
+ useSimpleProduct,
79
+ useTrack
80
+ };
package/package.json ADDED
@@ -0,0 +1,49 @@
1
+ {
2
+ "name": "@simple-product/sdk",
3
+ "version": "0.1.0",
4
+ "description": "Simple Product SDK - Track users and product usage",
5
+ "main": "dist/index.js",
6
+ "module": "dist/index.mjs",
7
+ "types": "dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "import": "./dist/index.mjs",
12
+ "require": "./dist/index.js"
13
+ },
14
+ "./react": {
15
+ "types": "./dist/react.d.ts",
16
+ "import": "./dist/react.mjs",
17
+ "require": "./dist/react.js"
18
+ }
19
+ },
20
+ "files": [
21
+ "dist"
22
+ ],
23
+ "scripts": {
24
+ "build": "tsup src/index.ts src/react.tsx --format cjs,esm --dts --external react",
25
+ "dev": "tsup src/index.ts src/react.tsx --format cjs,esm --dts --external react --watch",
26
+ "prepublishOnly": "npm run build"
27
+ },
28
+ "keywords": [
29
+ "analytics",
30
+ "tracking",
31
+ "product",
32
+ "sdk",
33
+ "react"
34
+ ],
35
+ "author": "Simple Product",
36
+ "license": "MIT",
37
+ "peerDependencies": {
38
+ "react": ">=17.0.0"
39
+ },
40
+ "peerDependenciesMeta": {
41
+ "react": {
42
+ "optional": true
43
+ }
44
+ },
45
+ "devDependencies": {
46
+ "tsup": "^8.0.0",
47
+ "typescript": "^5.0.0"
48
+ }
49
+ }