@aj-2000-test/goodlogs-sdk 0.1.15

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.cjs ADDED
@@ -0,0 +1,2 @@
1
+ 'use strict';var f={eu:"https://goodlogs-api-eu.yellowmeadow-422296f6.japaneast.azurecontainerapps.io",ap:"https://goodlogs-api-ap.delightfulsand-90b72c09.southeastasia.azurecontainerapps.io"};function h(r){let e=r.split("_");return e.length>=4&&e[0]==="gl"?e[2]:"eu"}function l(r,e){if(e)return e;let n=h(r);return f[n]||f.eu}var y=5e3,b=50,c="0.1.15",g="gl_anon_id",u="gl_session_id";function d(){return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g,r=>{let e=Math.random()*16|0;return (r==="x"?e:e&3|8).toString(16)})}function k(){let r={$goodlogs_sdk:"js",$goodlogs_sdk_version:c};return typeof navigator>"u"||(typeof screen<"u"&&(r.$screen=`${screen.width}x${screen.height}`),typeof navigator<"u"&&(r.$language=navigator.language??""),typeof location<"u"&&(r.$url=location.href,r.$path=location.pathname,r.$page=location.pathname.split("/").filter(Boolean).pop()||"/"),typeof document<"u"&&(document.referrer&&(r.$referrer=document.referrer),document.title&&(r.$title=document.title))),r}function S(r,e){let n={$session_id:r,$distinct_id:e,$goodlogs_sdk:"js",$goodlogs_sdk_version:c};return typeof navigator>"u"||typeof location<"u"&&(n.$url=location.href,n.$path=location.pathname),n}function w(){if(!(typeof document<"u"))try{let e=new Error().stack?.split(`
2
+ `);if(!e)return;for(let n=3;n<Math.min(e.length,8);n++){let t=e[n]?.trim();if(!t||t.includes("goodlogs")&&(t.includes("client.")||t.includes("index.")))continue;let i=t.match(/at\s+(?:(.+?)\s+\()?(.+?):(\d+):\d+\)?/);if(i){let s=i[1]||"<anonymous>",o=i[2]?.replace(/^.*[/\\]/,"")??"",a=i[3];return `${s}@${o}:${a}`}}}catch{}}function x(){if(typeof localStorage<"u"){let r=localStorage.getItem(g);if(r)return r;let e=d();return localStorage.setItem(g,e),e}return d()}function I(){if(typeof sessionStorage<"u"){let r=sessionStorage.getItem(u);if(r)return r;let e=d();return sessionStorage.setItem(u,e),e}return d()}var p=class{constructor(e){this.logBuffer=[];this.eventBuffer=[];this.timer=null;this.visibilityHandler=null;this.clickHandler=null;this.lastPath="";this.apiKey=e.apiKey,this.endpoint=l(e.apiKey,e.endpoint).replace(/\/+$/,""),this.flushInterval=e.flushInterval??y,this.batchSize=e.batchSize??b,this.defaultContext=e.defaultContext??{},this.onError=e.onError??(()=>{}),this.disabled=e.disabled??false,this.telemetry=e.telemetry??true,this.anonymousId=x(),this.sessionId=I(),this.disabled&&typeof console<"u"&&console.warn("[GoodLogs] SDK is disabled. No events or logs will be sent."),this.disabled||(this.startTimer(),this.attachPageLifecycle(),e.autocapture!==false&&(this.attachClickCapture(),this.captureWebVitals(),this.attachPageviewCapture()),this.sendTelemetry("init","info"));}log(e,n,t){if(this.disabled)return;let i={severity:e,message:n,metadata:t?.metadata,codeLocation:t?.codeLocation||w(),context:{...this.defaultContext,...S(this.sessionId,this.anonymousId),...t?.context},timestamp:new Date().toISOString()};this.logBuffer.push(i),this.logBuffer.length>=this.batchSize&&this.flush();}debug(e,n){this.log("debug",e,n);}info(e,n){this.log("info",e,n);}warn(e,n){this.log("warn",e,n);}error(e,n){this.log("error",e,n);}fatal(e,n){this.log("fatal",e,n);}identify(e){this.anonymousId=e,typeof localStorage<"u"&&localStorage.setItem(g,e);}getDistinctId(){return this.anonymousId}reset(){this.anonymousId=d(),this.sessionId=d(),typeof localStorage<"u"&&localStorage.setItem(g,this.anonymousId),typeof sessionStorage<"u"&&sessionStorage.setItem(u,this.sessionId);}getSessionId(){return this.sessionId}newSession(){this.sessionId=d(),typeof sessionStorage<"u"&&sessionStorage.setItem(u,this.sessionId);}setSessionId(e){this.sessionId=e,typeof sessionStorage<"u"&&sessionStorage.setItem(u,e);}track(e,n){if(this.disabled)return;let t={event:e,distinctId:n?.distinctId??this.anonymousId,properties:{...k(),...n?.properties,$session_id:this.sessionId},timestamp:new Date().toISOString()};this.eventBuffer.push(t),this.eventBuffer.length>=this.batchSize&&this.flush();}async flush(){let e=this.logBuffer.splice(0),n=this.eventBuffer.splice(0),t=[];e.length>0&&t.push(this.sendLogs(e)),n.length>0&&t.push(this.sendEvents(n)),await Promise.all(t);}async shutdown(){this.stopTimer(),this.detachPageLifecycle(),this.detachClickCapture(),await this.flush();}async sendLogs(e){let n=e.map(t=>({severity:t.severity,message:t.message,metadata:t.metadata,code_location:t.codeLocation,context:t.context,timestamp:t.timestamp}));await this.postWithRetry("/v1/logs",n,t=>{for(let i of t)this.logBuffer.push({severity:i.severity,message:i.message,metadata:i.metadata,codeLocation:i.code_location,context:i.context,timestamp:i.timestamp});});}async sendEvents(e){let n=e.map(t=>({event:t.event,distinctId:t.distinctId,properties:t.properties,timestamp:t.timestamp}));await this.postWithRetry("/v1/events",n,t=>{for(let i of t)this.eventBuffer.push({event:i.event,distinctId:i.distinctId,properties:i.properties,timestamp:i.timestamp});});}async postWithRetry(e,n,t,i=0){try{let s=await fetch(`${this.endpoint}${e}`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${this.apiKey}`,"X-GoodLogs-SDK":`js/${c}`},body:JSON.stringify({batch:n})});if(s.status===429||s.status===503){if(i<2)return await new Promise(a=>setTimeout(a,1e3*(i+1))),this.postWithRetry(e,n,t,i+1);t(n);return}if(!s.ok){let a=await s.text().catch(()=>"");throw new Error(`GoodLogs API error ${s.status}: ${a}`)}let o=await s.json().catch(()=>null);if(o&&o.rejected&&o.rejected>0){let a=n.slice(o.accepted??0);a.length>0&&t(a);}}catch(s){let o=s instanceof Error?s:new Error(String(s));i===0&&t(n),this.onError(o);}}startTimer(){this.timer=setInterval(()=>{this.flush();},this.flushInterval);let e=this.timer;typeof e?.unref=="function"&&e.unref();}stopTimer(){this.timer!==null&&(clearInterval(this.timer),this.timer=null);}attachPageLifecycle(){typeof document>"u"||(this.visibilityHandler=()=>{document.visibilityState==="hidden"&&this.keepaliveFlush();},document.addEventListener("visibilitychange",this.visibilityHandler));}captureWebVitals(){if(!(typeof PerformanceObserver>"u")){try{new PerformanceObserver(e=>{let n=e.getEntries()[0];n&&this.track("$web_vital",{properties:{$metric:"FCP",$value_ms:Math.round(n.startTime)}});}).observe({type:"paint",buffered:!0});}catch{}try{new PerformanceObserver(e=>{let n=e.getEntries(),t=n[n.length-1];t&&this.track("$web_vital",{properties:{$metric:"LCP",$value_ms:Math.round(t.startTime)}});}).observe({type:"largest-contentful-paint",buffered:!0});}catch{}try{let e=0;if(new PerformanceObserver(n=>{for(let t of n.getEntries())t.hadRecentInput||(e+=t.value);}).observe({type:"layout-shift",buffered:!0}),typeof document<"u"){let n=()=>{e>0&&this.track("$web_vital",{properties:{$metric:"CLS",$value:Math.round(e*1e3)/1e3}});};document.addEventListener("visibilitychange",()=>{document.visibilityState==="hidden"&&n();});}}catch{}try{if(typeof performance<"u"){let e=performance.getEntriesByType?.("navigation")?.[0];e?.responseStart&&this.track("$web_vital",{properties:{$metric:"TTFB",$value_ms:Math.round(e.responseStart)}});}}catch{}try{new PerformanceObserver(e=>{for(let n of e.getEntries()){let t=n.duration;t>0&&this.track("$web_vital",{properties:{$metric:"INP",$value_ms:Math.round(t)}});}}).observe({type:"event",buffered:!0});}catch{}}}attachPageviewCapture(){if(typeof document>"u"||typeof location>"u")return;this.lastPath=location.pathname,this.track("$pageview");let e=()=>{typeof location<"u"&&location.pathname!==this.lastPath&&(this.lastPath=location.pathname,this.track("$pageview"));};if(typeof history<"u"){let n=history.pushState,t=history.replaceState;history.pushState=function(...i){n.apply(this,i),e();},history.replaceState=function(...i){t.apply(this,i),e();};}typeof addEventListener<"u"&&addEventListener("popstate",e);}attachClickCapture(){typeof document>"u"||(this.clickHandler=e=>{let t=e.target?.closest?.("a, button, [data-gl-event], [role='button']");if(!t)return;let i=t.getAttribute?.("data-gl-event"),s=t.tagName?.toLowerCase()??"",o=(t.textContent??"").trim().slice(0,50),a=t.getAttribute?.("href")??void 0,v=(t.className??"").toString().slice(0,80);this.track(i||"$click",{properties:{$element_tag:s,$element_text:o||void 0,$element_href:a,$element_classes:v||void 0}});},document.addEventListener("click",this.clickHandler));}detachClickCapture(){this.clickHandler&&typeof document<"u"&&(document.removeEventListener("click",this.clickHandler),this.clickHandler=null);}detachPageLifecycle(){this.visibilityHandler&&typeof document<"u"&&(document.removeEventListener("visibilitychange",this.visibilityHandler),this.visibilityHandler=null);}keepaliveFlush(){let e=this.logBuffer.splice(0),n=this.eventBuffer.splice(0),t={"Content-Type":"application/json",Authorization:`Bearer ${this.apiKey}`,"X-GoodLogs-SDK":`js/${c}`};e.length>0&&fetch(`${this.endpoint}/v1/logs`,{method:"POST",headers:t,body:JSON.stringify({batch:e.map(i=>({severity:i.severity,message:i.message,metadata:i.metadata,code_location:i.codeLocation,context:i.context,timestamp:i.timestamp}))}),keepalive:true}).catch(()=>{}),n.length>0&&fetch(`${this.endpoint}/v1/events`,{method:"POST",headers:t,body:JSON.stringify({batch:n.map(i=>({event:i.event,distinctId:i.distinctId,properties:i.properties,timestamp:i.timestamp}))}),keepalive:true}).catch(()=>{});}sendTelemetry(e,n,t){!this.telemetry||this.disabled||fetch(`${this.endpoint}/v1/telemetry`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${this.apiKey}`,"X-GoodLogs-SDK":`js/${c}`},body:JSON.stringify({source:"sdk",event:e,level:n,metadata:{...t,sdk_version:c}}),keepalive:true}).catch(()=>{});}};var m="0.1.0";function E(r,e){let n=l(e?.apiKey??"",e?.endpoint).replace(/\/+$/,""),t={"Content-Type":"application/json","X-GoodLogs-SDK":m};e?.token&&(t.Authorization=`Bearer ${e.token}`),fetch(`${n}/v1/telemetry`,{method:"POST",headers:t,body:JSON.stringify({source:r.source,event:r.event,level:r.level??"info",metadata:{...r.metadata,sdk_version:m}}),keepalive:true}).catch(()=>{});}exports.GoodLogs=p;exports.REGION_URLS=f;exports.resolveEndpoint=l;exports.resolveRegion=h;exports.sendTelemetry=E;
@@ -0,0 +1,167 @@
1
+ type Severity = "debug" | "info" | "warn" | "error" | "fatal";
2
+ interface GoodLogsOptions {
3
+ /** API key (gl_sk_... for logs, gl_pk_... for events only) */
4
+ apiKey: string;
5
+ /** API endpoint (default: auto-resolved from API key region) */
6
+ endpoint?: string;
7
+ /** Flush interval in ms (default: 5000) */
8
+ flushInterval?: number;
9
+ /** Max batch size before auto-flush (default: 50) */
10
+ batchSize?: number;
11
+ /** Default context merged into every log/event */
12
+ defaultContext?: Record<string, unknown>;
13
+ /** Called on flush errors */
14
+ onError?: (error: Error) => void;
15
+ /** Disable SDK (useful for tests) */
16
+ disabled?: boolean;
17
+ /** Send SDK diagnostics to platform logs (default: true) */
18
+ telemetry?: boolean;
19
+ /** Auto-capture click events on elements with data-gl-event attribute (default: true in browser) */
20
+ autocapture?: boolean;
21
+ }
22
+ interface LogEntry {
23
+ severity: Severity;
24
+ message: string;
25
+ metadata?: Record<string, unknown>;
26
+ codeLocation?: string;
27
+ context?: Record<string, unknown>;
28
+ timestamp?: string;
29
+ }
30
+ interface EventEntry {
31
+ event: string;
32
+ distinctId?: string;
33
+ properties?: Record<string, unknown>;
34
+ timestamp?: string;
35
+ }
36
+
37
+ declare class GoodLogs {
38
+ private apiKey;
39
+ private endpoint;
40
+ private flushInterval;
41
+ private batchSize;
42
+ private defaultContext;
43
+ private onError;
44
+ private disabled;
45
+ private telemetry;
46
+ private logBuffer;
47
+ private eventBuffer;
48
+ private timer;
49
+ private visibilityHandler;
50
+ private clickHandler;
51
+ private lastPath;
52
+ private anonymousId;
53
+ private sessionId;
54
+ constructor(options: GoodLogsOptions);
55
+ log(severity: Severity, message: string, opts?: {
56
+ metadata?: Record<string, unknown>;
57
+ codeLocation?: string;
58
+ context?: Record<string, unknown>;
59
+ }): void;
60
+ debug(message: string, opts?: {
61
+ metadata?: Record<string, unknown>;
62
+ codeLocation?: string;
63
+ context?: Record<string, unknown>;
64
+ }): void;
65
+ info(message: string, opts?: {
66
+ metadata?: Record<string, unknown>;
67
+ codeLocation?: string;
68
+ context?: Record<string, unknown>;
69
+ }): void;
70
+ warn(message: string, opts?: {
71
+ metadata?: Record<string, unknown>;
72
+ codeLocation?: string;
73
+ context?: Record<string, unknown>;
74
+ }): void;
75
+ error(message: string, opts?: {
76
+ metadata?: Record<string, unknown>;
77
+ codeLocation?: string;
78
+ context?: Record<string, unknown>;
79
+ }): void;
80
+ fatal(message: string, opts?: {
81
+ metadata?: Record<string, unknown>;
82
+ codeLocation?: string;
83
+ context?: Record<string, unknown>;
84
+ }): void;
85
+ /** Set the user ID for all subsequent events. Replaces the anonymous ID. */
86
+ identify(userId: string): void;
87
+ /** Get the current anonymous/user ID. */
88
+ getDistinctId(): string;
89
+ /** Reset identity — generates a new anonymous ID (e.g., on logout). */
90
+ reset(): void;
91
+ /** Get the current session ID. */
92
+ getSessionId(): string;
93
+ /** Start a new session (e.g., on navigation reset). */
94
+ newSession(): void;
95
+ /** Set a specific session ID. */
96
+ setSessionId(id: string): void;
97
+ track(event: string, opts?: {
98
+ distinctId?: string;
99
+ properties?: Record<string, unknown>;
100
+ }): void;
101
+ flush(): Promise<void>;
102
+ /** Flush remaining data and stop the timer */
103
+ shutdown(): Promise<void>;
104
+ private sendLogs;
105
+ private sendEvents;
106
+ private postWithRetry;
107
+ private startTimer;
108
+ private stopTimer;
109
+ /**
110
+ * In browsers: flush via fetch+keepalive when page goes hidden or closes.
111
+ * keepalive lets the request complete even after the page unloads,
112
+ * while still supporting custom headers (unlike sendBeacon).
113
+ */
114
+ private attachPageLifecycle;
115
+ /** Auto-capture Core Web Vitals using PerformanceObserver */
116
+ private captureWebVitals;
117
+ /** Auto-capture page views on route changes (SPA-aware via History API) */
118
+ private attachPageviewCapture;
119
+ /** Auto-capture clicks on elements with text content or data-gl-event */
120
+ private attachClickCapture;
121
+ private detachClickCapture;
122
+ private detachPageLifecycle;
123
+ /** Fire-and-forget flush using fetch with keepalive — survives page unload */
124
+ private keepaliveFlush;
125
+ /** Fire-and-forget telemetry to platform logs. Never throws, never recurses. */
126
+ private sendTelemetry;
127
+ }
128
+
129
+ interface TelemetryEvent {
130
+ /** "sdk" | "dashboard" | "api" | custom */
131
+ source: string;
132
+ /** Event name e.g. "init", "flush_error", "auth_error", "render_error" */
133
+ event: string;
134
+ /** "debug" | "info" | "warn" | "error" */
135
+ level?: string;
136
+ /** Arbitrary metadata */
137
+ metadata?: Record<string, unknown>;
138
+ }
139
+ interface TelemetryOptions {
140
+ /** GoodLogs API endpoint (overrides auto-resolution) */
141
+ endpoint?: string;
142
+ /** API key — used to auto-resolve regional endpoint */
143
+ apiKey?: string;
144
+ /** Auth token (API key or JWT) */
145
+ token?: string;
146
+ }
147
+ /**
148
+ * Standalone telemetry sender for frontend/backend.
149
+ * Fire-and-forget — never throws.
150
+ * Auto-resolves the regional endpoint from apiKey if no explicit endpoint given.
151
+ *
152
+ * Usage (Next.js frontend):
153
+ * sendTelemetry({ source: "dashboard", event: "page_error", level: "error", metadata: { page: "/logs", error: "..." } }, { token: jwtToken })
154
+ *
155
+ * Usage (Next.js API route):
156
+ * sendTelemetry({ source: "api", event: "openai_timeout", level: "warn" }, { apiKey: "gl_sk_us_xxx" })
157
+ */
158
+ declare function sendTelemetry(event: TelemetryEvent, options?: TelemetryOptions): void;
159
+
160
+ /** Region-to-API URL mapping. SDK auto-resolves from API key prefix. */
161
+ declare const REGION_URLS: Record<string, string>;
162
+ /** Extract region from API key prefix: gl_sk_us_xxx → "us", gl_pk_eu_xxx → "eu" */
163
+ declare function resolveRegion(apiKey: string): string;
164
+ /** Resolve API endpoint from key. User-provided endpoint always wins. */
165
+ declare function resolveEndpoint(apiKey: string, userEndpoint?: string): string;
166
+
167
+ export { type EventEntry, GoodLogs, type GoodLogsOptions, type LogEntry, REGION_URLS, type Severity, type TelemetryEvent, type TelemetryOptions, resolveEndpoint, resolveRegion, sendTelemetry };
@@ -0,0 +1,167 @@
1
+ type Severity = "debug" | "info" | "warn" | "error" | "fatal";
2
+ interface GoodLogsOptions {
3
+ /** API key (gl_sk_... for logs, gl_pk_... for events only) */
4
+ apiKey: string;
5
+ /** API endpoint (default: auto-resolved from API key region) */
6
+ endpoint?: string;
7
+ /** Flush interval in ms (default: 5000) */
8
+ flushInterval?: number;
9
+ /** Max batch size before auto-flush (default: 50) */
10
+ batchSize?: number;
11
+ /** Default context merged into every log/event */
12
+ defaultContext?: Record<string, unknown>;
13
+ /** Called on flush errors */
14
+ onError?: (error: Error) => void;
15
+ /** Disable SDK (useful for tests) */
16
+ disabled?: boolean;
17
+ /** Send SDK diagnostics to platform logs (default: true) */
18
+ telemetry?: boolean;
19
+ /** Auto-capture click events on elements with data-gl-event attribute (default: true in browser) */
20
+ autocapture?: boolean;
21
+ }
22
+ interface LogEntry {
23
+ severity: Severity;
24
+ message: string;
25
+ metadata?: Record<string, unknown>;
26
+ codeLocation?: string;
27
+ context?: Record<string, unknown>;
28
+ timestamp?: string;
29
+ }
30
+ interface EventEntry {
31
+ event: string;
32
+ distinctId?: string;
33
+ properties?: Record<string, unknown>;
34
+ timestamp?: string;
35
+ }
36
+
37
+ declare class GoodLogs {
38
+ private apiKey;
39
+ private endpoint;
40
+ private flushInterval;
41
+ private batchSize;
42
+ private defaultContext;
43
+ private onError;
44
+ private disabled;
45
+ private telemetry;
46
+ private logBuffer;
47
+ private eventBuffer;
48
+ private timer;
49
+ private visibilityHandler;
50
+ private clickHandler;
51
+ private lastPath;
52
+ private anonymousId;
53
+ private sessionId;
54
+ constructor(options: GoodLogsOptions);
55
+ log(severity: Severity, message: string, opts?: {
56
+ metadata?: Record<string, unknown>;
57
+ codeLocation?: string;
58
+ context?: Record<string, unknown>;
59
+ }): void;
60
+ debug(message: string, opts?: {
61
+ metadata?: Record<string, unknown>;
62
+ codeLocation?: string;
63
+ context?: Record<string, unknown>;
64
+ }): void;
65
+ info(message: string, opts?: {
66
+ metadata?: Record<string, unknown>;
67
+ codeLocation?: string;
68
+ context?: Record<string, unknown>;
69
+ }): void;
70
+ warn(message: string, opts?: {
71
+ metadata?: Record<string, unknown>;
72
+ codeLocation?: string;
73
+ context?: Record<string, unknown>;
74
+ }): void;
75
+ error(message: string, opts?: {
76
+ metadata?: Record<string, unknown>;
77
+ codeLocation?: string;
78
+ context?: Record<string, unknown>;
79
+ }): void;
80
+ fatal(message: string, opts?: {
81
+ metadata?: Record<string, unknown>;
82
+ codeLocation?: string;
83
+ context?: Record<string, unknown>;
84
+ }): void;
85
+ /** Set the user ID for all subsequent events. Replaces the anonymous ID. */
86
+ identify(userId: string): void;
87
+ /** Get the current anonymous/user ID. */
88
+ getDistinctId(): string;
89
+ /** Reset identity — generates a new anonymous ID (e.g., on logout). */
90
+ reset(): void;
91
+ /** Get the current session ID. */
92
+ getSessionId(): string;
93
+ /** Start a new session (e.g., on navigation reset). */
94
+ newSession(): void;
95
+ /** Set a specific session ID. */
96
+ setSessionId(id: string): void;
97
+ track(event: string, opts?: {
98
+ distinctId?: string;
99
+ properties?: Record<string, unknown>;
100
+ }): void;
101
+ flush(): Promise<void>;
102
+ /** Flush remaining data and stop the timer */
103
+ shutdown(): Promise<void>;
104
+ private sendLogs;
105
+ private sendEvents;
106
+ private postWithRetry;
107
+ private startTimer;
108
+ private stopTimer;
109
+ /**
110
+ * In browsers: flush via fetch+keepalive when page goes hidden or closes.
111
+ * keepalive lets the request complete even after the page unloads,
112
+ * while still supporting custom headers (unlike sendBeacon).
113
+ */
114
+ private attachPageLifecycle;
115
+ /** Auto-capture Core Web Vitals using PerformanceObserver */
116
+ private captureWebVitals;
117
+ /** Auto-capture page views on route changes (SPA-aware via History API) */
118
+ private attachPageviewCapture;
119
+ /** Auto-capture clicks on elements with text content or data-gl-event */
120
+ private attachClickCapture;
121
+ private detachClickCapture;
122
+ private detachPageLifecycle;
123
+ /** Fire-and-forget flush using fetch with keepalive — survives page unload */
124
+ private keepaliveFlush;
125
+ /** Fire-and-forget telemetry to platform logs. Never throws, never recurses. */
126
+ private sendTelemetry;
127
+ }
128
+
129
+ interface TelemetryEvent {
130
+ /** "sdk" | "dashboard" | "api" | custom */
131
+ source: string;
132
+ /** Event name e.g. "init", "flush_error", "auth_error", "render_error" */
133
+ event: string;
134
+ /** "debug" | "info" | "warn" | "error" */
135
+ level?: string;
136
+ /** Arbitrary metadata */
137
+ metadata?: Record<string, unknown>;
138
+ }
139
+ interface TelemetryOptions {
140
+ /** GoodLogs API endpoint (overrides auto-resolution) */
141
+ endpoint?: string;
142
+ /** API key — used to auto-resolve regional endpoint */
143
+ apiKey?: string;
144
+ /** Auth token (API key or JWT) */
145
+ token?: string;
146
+ }
147
+ /**
148
+ * Standalone telemetry sender for frontend/backend.
149
+ * Fire-and-forget — never throws.
150
+ * Auto-resolves the regional endpoint from apiKey if no explicit endpoint given.
151
+ *
152
+ * Usage (Next.js frontend):
153
+ * sendTelemetry({ source: "dashboard", event: "page_error", level: "error", metadata: { page: "/logs", error: "..." } }, { token: jwtToken })
154
+ *
155
+ * Usage (Next.js API route):
156
+ * sendTelemetry({ source: "api", event: "openai_timeout", level: "warn" }, { apiKey: "gl_sk_us_xxx" })
157
+ */
158
+ declare function sendTelemetry(event: TelemetryEvent, options?: TelemetryOptions): void;
159
+
160
+ /** Region-to-API URL mapping. SDK auto-resolves from API key prefix. */
161
+ declare const REGION_URLS: Record<string, string>;
162
+ /** Extract region from API key prefix: gl_sk_us_xxx → "us", gl_pk_eu_xxx → "eu" */
163
+ declare function resolveRegion(apiKey: string): string;
164
+ /** Resolve API endpoint from key. User-provided endpoint always wins. */
165
+ declare function resolveEndpoint(apiKey: string, userEndpoint?: string): string;
166
+
167
+ export { type EventEntry, GoodLogs, type GoodLogsOptions, type LogEntry, REGION_URLS, type Severity, type TelemetryEvent, type TelemetryOptions, resolveEndpoint, resolveRegion, sendTelemetry };
package/dist/index.js ADDED
@@ -0,0 +1,2 @@
1
+ var f={eu:"https://goodlogs-api-eu.yellowmeadow-422296f6.japaneast.azurecontainerapps.io",ap:"https://goodlogs-api-ap.delightfulsand-90b72c09.southeastasia.azurecontainerapps.io"};function h(r){let e=r.split("_");return e.length>=4&&e[0]==="gl"?e[2]:"eu"}function l(r,e){if(e)return e;let n=h(r);return f[n]||f.eu}var y=5e3,b=50,c="0.1.15",g="gl_anon_id",u="gl_session_id";function d(){return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g,r=>{let e=Math.random()*16|0;return (r==="x"?e:e&3|8).toString(16)})}function k(){let r={$goodlogs_sdk:"js",$goodlogs_sdk_version:c};return typeof navigator>"u"||(typeof screen<"u"&&(r.$screen=`${screen.width}x${screen.height}`),typeof navigator<"u"&&(r.$language=navigator.language??""),typeof location<"u"&&(r.$url=location.href,r.$path=location.pathname,r.$page=location.pathname.split("/").filter(Boolean).pop()||"/"),typeof document<"u"&&(document.referrer&&(r.$referrer=document.referrer),document.title&&(r.$title=document.title))),r}function S(r,e){let n={$session_id:r,$distinct_id:e,$goodlogs_sdk:"js",$goodlogs_sdk_version:c};return typeof navigator>"u"||typeof location<"u"&&(n.$url=location.href,n.$path=location.pathname),n}function w(){if(!(typeof document<"u"))try{let e=new Error().stack?.split(`
2
+ `);if(!e)return;for(let n=3;n<Math.min(e.length,8);n++){let t=e[n]?.trim();if(!t||t.includes("goodlogs")&&(t.includes("client.")||t.includes("index.")))continue;let i=t.match(/at\s+(?:(.+?)\s+\()?(.+?):(\d+):\d+\)?/);if(i){let s=i[1]||"<anonymous>",o=i[2]?.replace(/^.*[/\\]/,"")??"",a=i[3];return `${s}@${o}:${a}`}}}catch{}}function x(){if(typeof localStorage<"u"){let r=localStorage.getItem(g);if(r)return r;let e=d();return localStorage.setItem(g,e),e}return d()}function I(){if(typeof sessionStorage<"u"){let r=sessionStorage.getItem(u);if(r)return r;let e=d();return sessionStorage.setItem(u,e),e}return d()}var p=class{constructor(e){this.logBuffer=[];this.eventBuffer=[];this.timer=null;this.visibilityHandler=null;this.clickHandler=null;this.lastPath="";this.apiKey=e.apiKey,this.endpoint=l(e.apiKey,e.endpoint).replace(/\/+$/,""),this.flushInterval=e.flushInterval??y,this.batchSize=e.batchSize??b,this.defaultContext=e.defaultContext??{},this.onError=e.onError??(()=>{}),this.disabled=e.disabled??false,this.telemetry=e.telemetry??true,this.anonymousId=x(),this.sessionId=I(),this.disabled&&typeof console<"u"&&console.warn("[GoodLogs] SDK is disabled. No events or logs will be sent."),this.disabled||(this.startTimer(),this.attachPageLifecycle(),e.autocapture!==false&&(this.attachClickCapture(),this.captureWebVitals(),this.attachPageviewCapture()),this.sendTelemetry("init","info"));}log(e,n,t){if(this.disabled)return;let i={severity:e,message:n,metadata:t?.metadata,codeLocation:t?.codeLocation||w(),context:{...this.defaultContext,...S(this.sessionId,this.anonymousId),...t?.context},timestamp:new Date().toISOString()};this.logBuffer.push(i),this.logBuffer.length>=this.batchSize&&this.flush();}debug(e,n){this.log("debug",e,n);}info(e,n){this.log("info",e,n);}warn(e,n){this.log("warn",e,n);}error(e,n){this.log("error",e,n);}fatal(e,n){this.log("fatal",e,n);}identify(e){this.anonymousId=e,typeof localStorage<"u"&&localStorage.setItem(g,e);}getDistinctId(){return this.anonymousId}reset(){this.anonymousId=d(),this.sessionId=d(),typeof localStorage<"u"&&localStorage.setItem(g,this.anonymousId),typeof sessionStorage<"u"&&sessionStorage.setItem(u,this.sessionId);}getSessionId(){return this.sessionId}newSession(){this.sessionId=d(),typeof sessionStorage<"u"&&sessionStorage.setItem(u,this.sessionId);}setSessionId(e){this.sessionId=e,typeof sessionStorage<"u"&&sessionStorage.setItem(u,e);}track(e,n){if(this.disabled)return;let t={event:e,distinctId:n?.distinctId??this.anonymousId,properties:{...k(),...n?.properties,$session_id:this.sessionId},timestamp:new Date().toISOString()};this.eventBuffer.push(t),this.eventBuffer.length>=this.batchSize&&this.flush();}async flush(){let e=this.logBuffer.splice(0),n=this.eventBuffer.splice(0),t=[];e.length>0&&t.push(this.sendLogs(e)),n.length>0&&t.push(this.sendEvents(n)),await Promise.all(t);}async shutdown(){this.stopTimer(),this.detachPageLifecycle(),this.detachClickCapture(),await this.flush();}async sendLogs(e){let n=e.map(t=>({severity:t.severity,message:t.message,metadata:t.metadata,code_location:t.codeLocation,context:t.context,timestamp:t.timestamp}));await this.postWithRetry("/v1/logs",n,t=>{for(let i of t)this.logBuffer.push({severity:i.severity,message:i.message,metadata:i.metadata,codeLocation:i.code_location,context:i.context,timestamp:i.timestamp});});}async sendEvents(e){let n=e.map(t=>({event:t.event,distinctId:t.distinctId,properties:t.properties,timestamp:t.timestamp}));await this.postWithRetry("/v1/events",n,t=>{for(let i of t)this.eventBuffer.push({event:i.event,distinctId:i.distinctId,properties:i.properties,timestamp:i.timestamp});});}async postWithRetry(e,n,t,i=0){try{let s=await fetch(`${this.endpoint}${e}`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${this.apiKey}`,"X-GoodLogs-SDK":`js/${c}`},body:JSON.stringify({batch:n})});if(s.status===429||s.status===503){if(i<2)return await new Promise(a=>setTimeout(a,1e3*(i+1))),this.postWithRetry(e,n,t,i+1);t(n);return}if(!s.ok){let a=await s.text().catch(()=>"");throw new Error(`GoodLogs API error ${s.status}: ${a}`)}let o=await s.json().catch(()=>null);if(o&&o.rejected&&o.rejected>0){let a=n.slice(o.accepted??0);a.length>0&&t(a);}}catch(s){let o=s instanceof Error?s:new Error(String(s));i===0&&t(n),this.onError(o);}}startTimer(){this.timer=setInterval(()=>{this.flush();},this.flushInterval);let e=this.timer;typeof e?.unref=="function"&&e.unref();}stopTimer(){this.timer!==null&&(clearInterval(this.timer),this.timer=null);}attachPageLifecycle(){typeof document>"u"||(this.visibilityHandler=()=>{document.visibilityState==="hidden"&&this.keepaliveFlush();},document.addEventListener("visibilitychange",this.visibilityHandler));}captureWebVitals(){if(!(typeof PerformanceObserver>"u")){try{new PerformanceObserver(e=>{let n=e.getEntries()[0];n&&this.track("$web_vital",{properties:{$metric:"FCP",$value_ms:Math.round(n.startTime)}});}).observe({type:"paint",buffered:!0});}catch{}try{new PerformanceObserver(e=>{let n=e.getEntries(),t=n[n.length-1];t&&this.track("$web_vital",{properties:{$metric:"LCP",$value_ms:Math.round(t.startTime)}});}).observe({type:"largest-contentful-paint",buffered:!0});}catch{}try{let e=0;if(new PerformanceObserver(n=>{for(let t of n.getEntries())t.hadRecentInput||(e+=t.value);}).observe({type:"layout-shift",buffered:!0}),typeof document<"u"){let n=()=>{e>0&&this.track("$web_vital",{properties:{$metric:"CLS",$value:Math.round(e*1e3)/1e3}});};document.addEventListener("visibilitychange",()=>{document.visibilityState==="hidden"&&n();});}}catch{}try{if(typeof performance<"u"){let e=performance.getEntriesByType?.("navigation")?.[0];e?.responseStart&&this.track("$web_vital",{properties:{$metric:"TTFB",$value_ms:Math.round(e.responseStart)}});}}catch{}try{new PerformanceObserver(e=>{for(let n of e.getEntries()){let t=n.duration;t>0&&this.track("$web_vital",{properties:{$metric:"INP",$value_ms:Math.round(t)}});}}).observe({type:"event",buffered:!0});}catch{}}}attachPageviewCapture(){if(typeof document>"u"||typeof location>"u")return;this.lastPath=location.pathname,this.track("$pageview");let e=()=>{typeof location<"u"&&location.pathname!==this.lastPath&&(this.lastPath=location.pathname,this.track("$pageview"));};if(typeof history<"u"){let n=history.pushState,t=history.replaceState;history.pushState=function(...i){n.apply(this,i),e();},history.replaceState=function(...i){t.apply(this,i),e();};}typeof addEventListener<"u"&&addEventListener("popstate",e);}attachClickCapture(){typeof document>"u"||(this.clickHandler=e=>{let t=e.target?.closest?.("a, button, [data-gl-event], [role='button']");if(!t)return;let i=t.getAttribute?.("data-gl-event"),s=t.tagName?.toLowerCase()??"",o=(t.textContent??"").trim().slice(0,50),a=t.getAttribute?.("href")??void 0,v=(t.className??"").toString().slice(0,80);this.track(i||"$click",{properties:{$element_tag:s,$element_text:o||void 0,$element_href:a,$element_classes:v||void 0}});},document.addEventListener("click",this.clickHandler));}detachClickCapture(){this.clickHandler&&typeof document<"u"&&(document.removeEventListener("click",this.clickHandler),this.clickHandler=null);}detachPageLifecycle(){this.visibilityHandler&&typeof document<"u"&&(document.removeEventListener("visibilitychange",this.visibilityHandler),this.visibilityHandler=null);}keepaliveFlush(){let e=this.logBuffer.splice(0),n=this.eventBuffer.splice(0),t={"Content-Type":"application/json",Authorization:`Bearer ${this.apiKey}`,"X-GoodLogs-SDK":`js/${c}`};e.length>0&&fetch(`${this.endpoint}/v1/logs`,{method:"POST",headers:t,body:JSON.stringify({batch:e.map(i=>({severity:i.severity,message:i.message,metadata:i.metadata,code_location:i.codeLocation,context:i.context,timestamp:i.timestamp}))}),keepalive:true}).catch(()=>{}),n.length>0&&fetch(`${this.endpoint}/v1/events`,{method:"POST",headers:t,body:JSON.stringify({batch:n.map(i=>({event:i.event,distinctId:i.distinctId,properties:i.properties,timestamp:i.timestamp}))}),keepalive:true}).catch(()=>{});}sendTelemetry(e,n,t){!this.telemetry||this.disabled||fetch(`${this.endpoint}/v1/telemetry`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${this.apiKey}`,"X-GoodLogs-SDK":`js/${c}`},body:JSON.stringify({source:"sdk",event:e,level:n,metadata:{...t,sdk_version:c}}),keepalive:true}).catch(()=>{});}};var m="0.1.0";function E(r,e){let n=l(e?.apiKey??"",e?.endpoint).replace(/\/+$/,""),t={"Content-Type":"application/json","X-GoodLogs-SDK":m};e?.token&&(t.Authorization=`Bearer ${e.token}`),fetch(`${n}/v1/telemetry`,{method:"POST",headers:t,body:JSON.stringify({source:r.source,event:r.event,level:r.level??"info",metadata:{...r.metadata,sdk_version:m}}),keepalive:true}).catch(()=>{});}export{p as GoodLogs,f as REGION_URLS,l as resolveEndpoint,h as resolveRegion,E as sendTelemetry};
package/package.json ADDED
@@ -0,0 +1,39 @@
1
+ {
2
+ "name": "@aj-2000-test/goodlogs-sdk",
3
+ "version": "0.1.15",
4
+ "description": "GoodLogs SDK — send logs and events to GoodLogs from Node.js and browsers",
5
+ "type": "module",
6
+ "main": "dist/index.cjs",
7
+ "module": "dist/index.js",
8
+ "types": "dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "import": "./dist/index.js",
12
+ "require": "./dist/index.cjs",
13
+ "types": "./dist/index.d.ts"
14
+ }
15
+ },
16
+ "files": [
17
+ "dist"
18
+ ],
19
+ "scripts": {
20
+ "build": "tsup",
21
+ "dev": "tsup --watch",
22
+ "test": "vitest run",
23
+ "test:watch": "vitest",
24
+ "typecheck": "tsc --noEmit",
25
+ "prepublishOnly": "npm run build"
26
+ },
27
+ "keywords": [
28
+ "logging",
29
+ "analytics",
30
+ "observability",
31
+ "goodlogs"
32
+ ],
33
+ "license": "MIT",
34
+ "devDependencies": {
35
+ "tsup": "^8.0.0",
36
+ "typescript": "^5.5.0",
37
+ "vitest": "^2.0.0"
38
+ }
39
+ }