@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/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
|
+
}
|