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