@bliplogs/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.mjs ADDED
@@ -0,0 +1,344 @@
1
+ // src/index.ts
2
+ var _BlipLogs = class _BlipLogs {
3
+ constructor(config) {
4
+ if (!config.apiKey) {
5
+ throw new Error("BlipLogs: apiKey is required");
6
+ }
7
+ if (!config.projectId) {
8
+ throw new Error("BlipLogs: projectId is required");
9
+ }
10
+ this.apiKey = config.apiKey;
11
+ this.projectId = config.projectId;
12
+ this.debug = config.debug ?? false;
13
+ this.onError = config.onError;
14
+ this.privacy = {
15
+ anonymizeIp: config.privacy?.anonymizeIp ?? false,
16
+ collectUrl: config.privacy?.collectUrl ?? true,
17
+ collectReferrer: config.privacy?.collectReferrer ?? true,
18
+ collectUserAgent: config.privacy?.collectUserAgent ?? true,
19
+ collectSessionId: config.privacy?.collectSessionId ?? true
20
+ };
21
+ }
22
+ /**
23
+ * Configure the global BlipLogs instance
24
+ * Call this once at the start of your application (e.g., in your Astro layout)
25
+ *
26
+ * @example
27
+ * ```ts
28
+ * // In Astro layout or initialization script
29
+ * BlipLogs.configure({
30
+ * apiKey: import.meta.env.PUBLIC_BLIPLOGS_API_KEY,
31
+ * projectId: import.meta.env.PUBLIC_BLIPLOGS_PROJECT_ID
32
+ * });
33
+ * ```
34
+ */
35
+ static configure(config) {
36
+ _BlipLogs.globalInstance = new _BlipLogs(config);
37
+ }
38
+ /**
39
+ * Get the global BlipLogs instance
40
+ * Throws an error if not configured
41
+ */
42
+ static getInstance() {
43
+ if (!_BlipLogs.globalInstance) {
44
+ throw new Error("BlipLogs: Global instance not configured. Call BlipLogs.configure() first.");
45
+ }
46
+ return _BlipLogs.globalInstance;
47
+ }
48
+ /**
49
+ * Track an event using the global instance
50
+ *
51
+ * @example
52
+ * ```ts
53
+ * BlipLogs.track('button_clicked', { buttonId: 'signup' });
54
+ * ```
55
+ */
56
+ static track(event, metadata, level = "info") {
57
+ return _BlipLogs.getInstance().track(event, metadata, level);
58
+ }
59
+ /**
60
+ * Track an info-level event using the global instance
61
+ *
62
+ * @example
63
+ * ```ts
64
+ * BlipLogs.info('page_viewed', { page: '/dashboard' });
65
+ * ```
66
+ */
67
+ static info(event, metadata) {
68
+ return _BlipLogs.getInstance().info(event, metadata);
69
+ }
70
+ /**
71
+ * Track a warn-level event using the global instance
72
+ *
73
+ * @example
74
+ * ```ts
75
+ * BlipLogs.warn('slow_request', { duration: 5000 });
76
+ * ```
77
+ */
78
+ static warn(event, metadata) {
79
+ return _BlipLogs.getInstance().warn(event, metadata);
80
+ }
81
+ /**
82
+ * Track an error-level event using the global instance
83
+ *
84
+ * @example
85
+ * ```ts
86
+ * BlipLogs.error('api_error', { message: 'Failed to fetch' });
87
+ * ```
88
+ */
89
+ static error(event, metadata) {
90
+ return _BlipLogs.getInstance().error(event, metadata);
91
+ }
92
+ /**
93
+ * Gets or creates a session ID from sessionStorage
94
+ * Sessions expire after 30 minutes of inactivity
95
+ */
96
+ getSessionId() {
97
+ if (typeof window === "undefined" || typeof sessionStorage === "undefined") {
98
+ return void 0;
99
+ }
100
+ const SESSION_TIMEOUT = 30 * 60 * 1e3;
101
+ const STORAGE_KEY_ID = "blip_session_id";
102
+ const STORAGE_KEY_TS = "blip_session_ts";
103
+ try {
104
+ let sessionId = sessionStorage.getItem(STORAGE_KEY_ID);
105
+ const lastActivityStr = sessionStorage.getItem(STORAGE_KEY_TS);
106
+ const lastActivity = lastActivityStr ? parseInt(lastActivityStr, 10) : null;
107
+ const now = Date.now();
108
+ if (!sessionId || !lastActivity || now - lastActivity > SESSION_TIMEOUT) {
109
+ sessionId = crypto.randomUUID();
110
+ sessionStorage.setItem(STORAGE_KEY_ID, sessionId);
111
+ }
112
+ sessionStorage.setItem(STORAGE_KEY_TS, now.toString());
113
+ return sessionId;
114
+ } catch (error) {
115
+ return void 0;
116
+ }
117
+ }
118
+ /**
119
+ * Sanitize a URL by removing sensitive query parameters
120
+ */
121
+ sanitizeUrl(url) {
122
+ if (!url) return void 0;
123
+ try {
124
+ const urlObj = new URL(url);
125
+ const params = urlObj.searchParams;
126
+ const sensitiveSet = new Set(_BlipLogs.SENSITIVE_PARAMS.map((p) => p.toLowerCase()));
127
+ const paramsToRedact = [];
128
+ params.forEach((_, key) => {
129
+ if (sensitiveSet.has(key.toLowerCase())) {
130
+ paramsToRedact.push(key);
131
+ }
132
+ });
133
+ for (const key of paramsToRedact) {
134
+ params.set(key, "[REDACTED]");
135
+ }
136
+ return urlObj.toString();
137
+ } catch {
138
+ const queryIndex = url.indexOf("?");
139
+ if (queryIndex !== -1) {
140
+ return url.substring(0, queryIndex) + "?[QUERY_REDACTED]";
141
+ }
142
+ return url;
143
+ }
144
+ }
145
+ /**
146
+ * Auto-captures browser context information with sensitive data sanitization
147
+ * Respects privacy configuration settings
148
+ */
149
+ getContext() {
150
+ const baseContext = {
151
+ projectId: this.projectId
152
+ };
153
+ if (typeof window === "undefined") {
154
+ return baseContext;
155
+ }
156
+ return {
157
+ ...baseContext,
158
+ url: this.privacy.collectUrl ? this.sanitizeUrl(window.location?.href) : void 0,
159
+ referrer: this.privacy.collectReferrer ? this.sanitizeUrl(document.referrer) || void 0 : void 0,
160
+ userAgent: this.privacy.collectUserAgent ? navigator.userAgent : void 0
161
+ };
162
+ }
163
+ /**
164
+ * Track an event with optional metadata and level
165
+ *
166
+ * @param event - The event name (e.g., 'signup_modal_opened')
167
+ * @param metadata - Optional custom data to attach to the event
168
+ * @param level - Event level: 'info' (default), 'warn', or 'error'
169
+ * @returns boolean - Whether the event was queued for delivery
170
+ */
171
+ track(event, metadata, level = "info") {
172
+ if (!event) {
173
+ console.warn("BlipLogs: event name is required");
174
+ return false;
175
+ }
176
+ const now = Date.now();
177
+ const sessionId = this.privacy.collectSessionId ? this.getSessionId() : void 0;
178
+ const payload = {
179
+ event,
180
+ level,
181
+ metadata,
182
+ context: this.getContext(),
183
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
184
+ // Keep for backward compatibility
185
+ session_id: sessionId,
186
+ timestamp_ms: now,
187
+ // Numeric timestamp in milliseconds
188
+ anonymize_ip: this.privacy.anonymizeIp || void 0
189
+ // Only include if true
190
+ };
191
+ return this.send(payload);
192
+ }
193
+ /**
194
+ * Convenience method for info-level events
195
+ */
196
+ info(event, metadata) {
197
+ return this.track(event, metadata, "info");
198
+ }
199
+ /**
200
+ * Convenience method for warn-level events
201
+ */
202
+ warn(event, metadata) {
203
+ return this.track(event, metadata, "warn");
204
+ }
205
+ /**
206
+ * Convenience method for error-level events
207
+ */
208
+ error(event, metadata) {
209
+ return this.track(event, metadata, "error");
210
+ }
211
+ /**
212
+ * Sends the payload using sendBeacon for speed and reliability
213
+ * Falls back to fetch if sendBeacon is unavailable or blocked
214
+ */
215
+ send(payload) {
216
+ const payloadWithKey = {
217
+ ...payload,
218
+ api_key: this.apiKey
219
+ };
220
+ const body = JSON.stringify(payloadWithKey);
221
+ if (typeof navigator !== "undefined" && navigator.sendBeacon) {
222
+ try {
223
+ const blob = new Blob([body], { type: "application/json" });
224
+ const sent = navigator.sendBeacon(_BlipLogs.API_ENDPOINT, blob);
225
+ if (!sent) {
226
+ return this.sendWithFetch(body);
227
+ }
228
+ return true;
229
+ } catch (error) {
230
+ return this.sendWithFetch(body);
231
+ }
232
+ }
233
+ return this.sendWithFetch(body);
234
+ }
235
+ /**
236
+ * Handle and report errors
237
+ */
238
+ handleError(error) {
239
+ if (this.debug) {
240
+ console.error(`BlipLogs Error [${error.type}]:`, error.message, error);
241
+ }
242
+ if (this.onError) {
243
+ try {
244
+ this.onError(error);
245
+ } catch (callbackError) {
246
+ if (this.debug) {
247
+ console.error("BlipLogs: Error in onError callback:", callbackError);
248
+ }
249
+ }
250
+ }
251
+ }
252
+ /**
253
+ * Fallback method using fetch API
254
+ */
255
+ sendWithFetch(body) {
256
+ if (typeof fetch !== "undefined") {
257
+ fetch(_BlipLogs.API_ENDPOINT, {
258
+ method: "POST",
259
+ headers: {
260
+ "Content-Type": "application/json",
261
+ "x-api-key": this.apiKey
262
+ },
263
+ body,
264
+ keepalive: true,
265
+ credentials: "omit"
266
+ }).then((response) => {
267
+ if (!response.ok) {
268
+ let errorType = "unknown";
269
+ let message = `HTTP ${response.status}`;
270
+ if (response.status === 429) {
271
+ errorType = "rate_limit";
272
+ message = "Monthly event limit exceeded. Upgrade your plan to continue.";
273
+ } else if (response.status === 401) {
274
+ errorType = "auth";
275
+ message = "Invalid API key";
276
+ } else if (response.status === 400) {
277
+ errorType = "validation";
278
+ message = "Invalid request payload";
279
+ }
280
+ this.handleError({
281
+ type: errorType,
282
+ message,
283
+ statusCode: response.status
284
+ });
285
+ }
286
+ }).catch((err) => {
287
+ this.handleError({
288
+ type: "network",
289
+ message: err?.message || "Network request failed",
290
+ originalError: err instanceof Error ? err : void 0
291
+ });
292
+ });
293
+ return true;
294
+ }
295
+ this.handleError({
296
+ type: "unknown",
297
+ message: "No suitable transport available (fetch not defined)"
298
+ });
299
+ return false;
300
+ }
301
+ };
302
+ _BlipLogs.globalInstance = null;
303
+ _BlipLogs.API_ENDPOINT = "https://api.bliplogs.co.uk";
304
+ /**
305
+ * Sensitive URL parameters that should be redacted
306
+ */
307
+ _BlipLogs.SENSITIVE_PARAMS = [
308
+ "token",
309
+ "access_token",
310
+ "refresh_token",
311
+ "id_token",
312
+ "apikey",
313
+ "api_key",
314
+ "api-key",
315
+ "key",
316
+ "password",
317
+ "pwd",
318
+ "pass",
319
+ "secret",
320
+ "auth",
321
+ "authorization",
322
+ "bearer",
323
+ "session",
324
+ "sessionid",
325
+ "session_id",
326
+ "code",
327
+ "state",
328
+ "nonce",
329
+ // OAuth params
330
+ "email",
331
+ "phone",
332
+ "ssn",
333
+ // PII
334
+ "credit_card",
335
+ "cc",
336
+ "cvv",
337
+ "card"
338
+ ];
339
+ var BlipLogs = _BlipLogs;
340
+ var index_default = BlipLogs;
341
+ export {
342
+ BlipLogs,
343
+ index_default as default
344
+ };
package/package.json ADDED
@@ -0,0 +1,66 @@
1
+ {
2
+ "name": "@bliplogs/sdk",
3
+ "version": "0.1.0",
4
+ "description": "Zero-dependency event logging SDK for BlipLogs. Track events from browser and server with minimal overhead.",
5
+ "author": "BlipLogs <hello@bliplogs.co.uk>",
6
+ "license": "MIT",
7
+ "homepage": "https://bliplogs.co.uk",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://github.com/NoSweatWebsites/bliplogs-sdk.git"
11
+ },
12
+ "bugs": {
13
+ "url": "https://github.com/NoSweatWebsites/bliplogs-sdk/issues"
14
+ },
15
+ "main": "./dist/index.js",
16
+ "module": "./dist/index.mjs",
17
+ "types": "./dist/index.d.ts",
18
+ "exports": {
19
+ ".": {
20
+ "import": {
21
+ "types": "./dist/index.d.mts",
22
+ "default": "./dist/index.mjs"
23
+ },
24
+ "require": {
25
+ "types": "./dist/index.d.ts",
26
+ "default": "./dist/index.js"
27
+ }
28
+ }
29
+ },
30
+ "files": [
31
+ "dist",
32
+ "README.md",
33
+ "LICENSE",
34
+ "CHANGELOG.md"
35
+ ],
36
+ "sideEffects": false,
37
+ "engines": {
38
+ "node": ">=18.0.0"
39
+ },
40
+ "scripts": {
41
+ "build": "tsup src/index.ts --format cjs,esm --dts",
42
+ "dev": "tsup src/index.ts --format cjs,esm --dts --watch",
43
+ "prepublishOnly": "npm run build"
44
+ },
45
+ "devDependencies": {
46
+ "tsup": "^8.3.0",
47
+ "typescript": "^5.7.0"
48
+ },
49
+ "keywords": [
50
+ "logging",
51
+ "events",
52
+ "analytics",
53
+ "bliplogs",
54
+ "event-tracking",
55
+ "telemetry",
56
+ "observability",
57
+ "monitoring",
58
+ "sendbeacon",
59
+ "fire-and-forget",
60
+ "zero-dependencies",
61
+ "debugging"
62
+ ],
63
+ "publishConfig": {
64
+ "access": "public"
65
+ }
66
+ }