@nexussdk/tracker 0.0.1 → 0.0.3

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 ADDED
@@ -0,0 +1,31 @@
1
+ # @nexussdk/tracker
2
+
3
+ Ultra-resilient client crash ingestion and telemetry SDK with automated PII sanitization (<8KB gzipped).
4
+
5
+ [![npm version](https://img.shields.io/npm/v/@nexussdk/tracker.svg)](https://www.npmjs.com/package/@nexussdk/tracker)
6
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)
7
+
8
+ ---
9
+
10
+ ## Installation
11
+
12
+ ```bash
13
+ npm install @nexussdk/tracker
14
+ # or
15
+ pnpm add @nexussdk/tracker
16
+ ```
17
+
18
+ ---
19
+
20
+ ## Features
21
+
22
+ - **Automated PII Scrubbing**: Regex filters automatically sanitize emails, credit cards, IPv4/IPv6, and API keys before transmission.
23
+ - **Breadcrumbs Ring Buffer**: Records preceding user actions (clicks, navigation, console) without memory leaks.
24
+ - **Deduplication**: 5000ms sliding deduplication window prevents event storms.
25
+ - **Resilient Transport**: Background flush with retry logic and offline queueing.
26
+
27
+ ---
28
+
29
+ ## License
30
+
31
+ MIT © [Nexus Platform](https://github.com/Huynhdung295/NexusSDK)
package/dist/index.cjs CHANGED
@@ -1,3 +1,2 @@
1
1
  'use strict';var core=require('@nexussdk/core');var T=/^(password|passwd|token|secret|authorization|bearer|auth|credit_?card|cvv|cvc|ssn|api_?key|access_?token|refresh_?token)$/i,C=/\b(?:\d[ -]*?){13,16}\b/g,R=/[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/g,y=/([?&](token|auth|key|secret|password|api_key|access_token|refresh_token)=)[^&]*/gi;function A(n){return n.replace(C,"[CARD_REDACTED]").replace(R,"[EMAIL_REDACTED]").replace(y,"$1[REDACTED]")}function s(n,e=0){if(e>5)return "[MaxDepthExceeded]";if(n==null)return n;if(typeof n=="string")return A(n);if(typeof n!="object")return n;if(Array.isArray(n))return n.map(r=>s(r,e+1));let t={};for(let[r,o]of Object.entries(n))T.test(r)?t[r]="[REDACTED]":t[r]=s(o,e+1);return t}function l(n){return n.replace(y,"$1[REDACTED]")}function f(n){if(!n)return [];let e=[],t=n.split(`
2
- `);for(let r of t){let o=r.trim(),i=o.match(/^at\s+(.+?)\s+\((.+?):(\d+):(\d+)\)$/)||o.match(/^at\s+(.+?):(\d+):(\d+)$/)||o.match(/^at\s+\((.+?):(\d+):(\d+)\)$/);if(i){i.length===5?e.push({functionName:i[1]??"<anonymous>",fileName:i[2]??"<unknown>",lineNumber:parseInt(i[3]??"0",10),columnNumber:parseInt(i[4]??"0",10)}):i.length===4&&e.push({functionName:"<anonymous>",fileName:i[1]??"<unknown>",lineNumber:parseInt(i[2]??"0",10),columnNumber:parseInt(i[3]??"0",10)});continue}let a=o.match(/^(.+?)@(.+?):(\d+):(\d+)$/);a&&e.push({functionName:a[1]??"<anonymous>",fileName:a[2]??"<unknown>",lineNumber:parseInt(a[3]??"0",10),columnNumber:parseInt(a[4]??"0",10)});}return e}function g(n,e,t){let r=t?.fileName??"unknown",o=t?.lineNumber??0,i=`${n}:${e}:${r}:${o}`,a=5381;for(let d=0;d<i.length;d++)a=(a<<5)+a+i.charCodeAt(d)>>>0;return `fp_${a.toString(16).padStart(8,"0")}`}var c=class{buffer;listenersAttached=false;clickHandler;popStateHandler;constructor(e){this.buffer=new core.RingBuffer(e),this.clickHandler=this.handleClick.bind(this),this.popStateHandler=this.handleNavigation.bind(this);}push(e){let t=e.data?s(e.data):void 0;this.buffer.push({...e,data:t,timestamp:Date.now()});}getAll(){return this.buffer.toArray()}clear(){this.buffer.clear();}attachListeners(){this.listenersAttached||typeof window>"u"||(this.listenersAttached=true,document.addEventListener("click",this.clickHandler,{capture:true,passive:true}),window.addEventListener("popstate",this.popStateHandler,{passive:true}),this.interceptConsoleError());}detachListeners(){!this.listenersAttached||typeof window>"u"||(document.removeEventListener("click",this.clickHandler,{capture:true}),window.removeEventListener("popstate",this.popStateHandler),this.listenersAttached=false);}handleClick(e){let t=e.target;if(!t||t instanceof HTMLInputElement&&(t.type==="password"||t.hasAttribute("data-nexus-mask")))return;let r=this.describeElement(t);this.push({category:"ui.click",message:`Clicked ${r}`,level:"info",data:{elementTag:t.tagName.toLowerCase(),elementId:t.id||void 0,elementClass:t.className||void 0}});}handleNavigation(){this.push({category:"navigation",message:`Navigated to ${l(window.location.href)}`,level:"info",data:{url:l(window.location.href)}});}describeElement(e){let t=[e.tagName.toLowerCase()];return e.id&&t.push(`#${e.id}`),e.getAttribute("aria-label")&&t.push(`[aria-label="${e.getAttribute("aria-label")}"]`),t.join("")}interceptConsoleError(){let e=console.error.bind(console);console.error=(...t)=>{this.push({category:"console",message:t.map(String).join(" ").substring(0,500),level:"error"}),e(...t);};}};var u=class{options;maxRetries;isPageHiding=false;constructor(e){this.options=e,this.maxRetries=e.maxRetries??2,typeof document<"u"&&document.addEventListener("visibilitychange",()=>{document.visibilityState==="hidden"&&(this.isPageHiding=true);}),typeof window<"u"&&window.addEventListener("pagehide",()=>{this.isPageHiding=true;});}send(e){let t=core.safeStringify(e);if(this.isPageHiding&&typeof navigator<"u"&&typeof navigator.sendBeacon=="function"){let r=new Blob([t],{type:"application/json"});navigator.sendBeacon(this.options.endpoint,r);return}this.sendWithRetry(t,0);}async flush(e){let t=core.safeStringify(e);if(typeof navigator<"u"&&typeof navigator.sendBeacon=="function"){let r=new Blob([t],{type:"application/json"});navigator.sendBeacon(this.options.endpoint,r);return}await this.sendWithRetry(t,0);}async sendWithRetry(e,t){try{let r=await fetch(this.options.endpoint,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${this.options.apiKey}`},body:e,keepalive:!0});if(r.ok)return;if(r.status>=500&&t<this.maxRetries){let o=core.computeBackoffMs(t,1e3,3e4);await new Promise(i=>setTimeout(i,o)),await this.sendWithRetry(e,t+1);}}catch{if(t<this.maxRetries){let r=core.computeBackoffMs(t,1e3,3e4);await new Promise(o=>setTimeout(o,r)),await this.sendWithRetry(e,t+1);}}}};function w(n){if(typeof window>"u")return ()=>{};let e=r=>{let o=r.error instanceof Error?r.error:new Error(r.message);n.captureError(o);},t=r=>{n.captureError(r.reason);};return window.addEventListener("error",e),window.addEventListener("unhandledrejection",t),()=>{window.removeEventListener("error",e),window.removeEventListener("unhandledrejection",t);}}var h=class{apiKey;environment;tags;beforeSend;breadcrumbManager;transport;userContext;dedupeMap=new Map;cleanupListeners;constructor(e={}){this.apiKey=core.resolveApiKey(e.apiKey);let t=core.resolveBaseUrl(e.baseUrl);this.environment=e.environment??"production",this.tags={...e.tags},this.beforeSend=e.beforeSend,this.breadcrumbManager=new c(Math.min(e.maxBreadcrumbs??20,50)),this.transport=new u({endpoint:`${t}/api/v1/telemetry/errors`,apiKey:this.apiKey}),e.autoCapture!==false&&typeof window<"u"&&(this.breadcrumbManager.attachListeners(),this.cleanupListeners=w(this));}captureError(e,t){let r=this.normalizeError(e),o=f(r.stack),i=g(r.type,r.message,o[0]),a=this.dedupeMap.get(i);if(a){a.count+=1;return}let d={fingerprint:i,errorType:r.type,errorMessage:r.message,stackTrace:o,breadcrumbs:this.breadcrumbManager.getAll(),userContext:this.userContext,deviceContext:this.getDeviceContext(),tags:{environment:this.environment,...this.tags,...t},occurrenceCount:1,clientTimestamp:Date.now()},v=s(d),m=this.beforeSend?this.beforeSend(v):v;if(!m)return;this.transport.send(m);let k=setTimeout(()=>{let p=this.dedupeMap.get(i);if(p&&p.count>1){let x={...p.lastPayload,occurrenceCount:p.count,clientTimestamp:Date.now()};this.transport.send(x);}this.dedupeMap.delete(i);},1e4);this.dedupeMap.set(i,{timer:k,count:1,lastPayload:m});}addBreadcrumb(e){this.breadcrumbManager.push(e);}setUser(e){this.userContext=e??void 0;}setTag(e,t){this.tags[e]=t;}async flush(){for(let[e,t]of this.dedupeMap.entries()){if(clearTimeout(t.timer),t.count>0){let r={...t.lastPayload,occurrenceCount:t.count,clientTimestamp:Date.now()};await this.transport.flush(r);}this.dedupeMap.delete(e);}}destroy(){this.cleanupListeners?.(),this.breadcrumbManager.detachListeners(),this.breadcrumbManager.clear(),this.dedupeMap.forEach(e=>clearTimeout(e.timer)),this.dedupeMap.clear();}normalizeError(e){return e instanceof Error?{type:e.name||"Error",message:e.message,stack:e.stack}:typeof e=="string"?{type:"UnhandledException",message:e}:{type:"NonErrorRejection",message:String(e)}}getDeviceContext(){let e=typeof window<"u"&&typeof navigator<"u";return {userAgent:e?navigator.userAgent:"Node/SSR",currentUrl:e?window.location.href:"",viewport:e?`${window.innerWidth}x${window.innerHeight}`:void 0,timezone:Intl?.DateTimeFormat()?.resolvedOptions()?.timeZone,networkStatus:e&&"connection"in navigator?navigator.connection?.effectiveType??"unknown":void 0}}};exports.BreadcrumbManager=c;exports.NexusTrackerClient=h;exports.Transport=u;exports.computeFingerprint=g;exports.parseStackTrace=f;exports.sanitizeObject=s;exports.sanitizeUrl=l;//# sourceMappingURL=index.cjs.map
3
- //# sourceMappingURL=index.cjs.map
2
+ `);for(let r of t){let o=r.trim(),i=o.match(/^at\s+(.+?)\s+\((.+?):(\d+):(\d+)\)$/)||o.match(/^at\s+(.+?):(\d+):(\d+)$/)||o.match(/^at\s+\((.+?):(\d+):(\d+)\)$/);if(i){i.length===5?e.push({functionName:i[1]??"<anonymous>",fileName:i[2]??"<unknown>",lineNumber:parseInt(i[3]??"0",10),columnNumber:parseInt(i[4]??"0",10)}):i.length===4&&e.push({functionName:"<anonymous>",fileName:i[1]??"<unknown>",lineNumber:parseInt(i[2]??"0",10),columnNumber:parseInt(i[3]??"0",10)});continue}let a=o.match(/^(.+?)@(.+?):(\d+):(\d+)$/);a&&e.push({functionName:a[1]??"<anonymous>",fileName:a[2]??"<unknown>",lineNumber:parseInt(a[3]??"0",10),columnNumber:parseInt(a[4]??"0",10)});}return e}function g(n,e,t){let r=t?.fileName??"unknown",o=t?.lineNumber??0,i=`${n}:${e}:${r}:${o}`,a=5381;for(let d=0;d<i.length;d++)a=(a<<5)+a+i.charCodeAt(d)>>>0;return `fp_${a.toString(16).padStart(8,"0")}`}var c=class{buffer;listenersAttached=false;clickHandler;popStateHandler;constructor(e){this.buffer=new core.RingBuffer(e),this.clickHandler=this.handleClick.bind(this),this.popStateHandler=this.handleNavigation.bind(this);}push(e){let t=e.data?s(e.data):void 0;this.buffer.push({...e,data:t,timestamp:Date.now()});}getAll(){return this.buffer.toArray()}clear(){this.buffer.clear();}attachListeners(){this.listenersAttached||typeof window>"u"||(this.listenersAttached=true,document.addEventListener("click",this.clickHandler,{capture:true,passive:true}),window.addEventListener("popstate",this.popStateHandler,{passive:true}),this.interceptConsoleError());}detachListeners(){!this.listenersAttached||typeof window>"u"||(document.removeEventListener("click",this.clickHandler,{capture:true}),window.removeEventListener("popstate",this.popStateHandler),this.listenersAttached=false);}handleClick(e){let t=e.target;if(!t||t instanceof HTMLInputElement&&(t.type==="password"||t.hasAttribute("data-nexus-mask")))return;let r=this.describeElement(t);this.push({category:"ui.click",message:`Clicked ${r}`,level:"info",data:{elementTag:t.tagName.toLowerCase(),elementId:t.id||void 0,elementClass:t.className||void 0}});}handleNavigation(){this.push({category:"navigation",message:`Navigated to ${l(window.location.href)}`,level:"info",data:{url:l(window.location.href)}});}describeElement(e){let t=[e.tagName.toLowerCase()];return e.id&&t.push(`#${e.id}`),e.getAttribute("aria-label")&&t.push(`[aria-label="${e.getAttribute("aria-label")}"]`),t.join("")}interceptConsoleError(){let e=console.error.bind(console);console.error=(...t)=>{this.push({category:"console",message:t.map(String).join(" ").substring(0,500),level:"error"}),e(...t);};}};var u=class{options;maxRetries;isPageHiding=false;constructor(e){this.options=e,this.maxRetries=e.maxRetries??2,typeof document<"u"&&document.addEventListener("visibilitychange",()=>{document.visibilityState==="hidden"&&(this.isPageHiding=true);}),typeof window<"u"&&window.addEventListener("pagehide",()=>{this.isPageHiding=true;});}send(e){let t=core.safeStringify(e);if(this.isPageHiding&&typeof navigator<"u"&&typeof navigator.sendBeacon=="function"){let r=new Blob([t],{type:"application/json"});navigator.sendBeacon(this.options.endpoint,r);return}this.sendWithRetry(t,0);}async flush(e){let t=core.safeStringify(e);if(typeof navigator<"u"&&typeof navigator.sendBeacon=="function"){let r=new Blob([t],{type:"application/json"});navigator.sendBeacon(this.options.endpoint,r);return}await this.sendWithRetry(t,0);}async sendWithRetry(e,t){try{let r=await fetch(this.options.endpoint,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${this.options.apiKey}`},body:e,keepalive:!0});if(r.ok)return;if(r.status>=500&&t<this.maxRetries){let o=core.computeBackoffMs(t,1e3,3e4);await new Promise(i=>setTimeout(i,o)),await this.sendWithRetry(e,t+1);}}catch{if(t<this.maxRetries){let r=core.computeBackoffMs(t,1e3,3e4);await new Promise(o=>setTimeout(o,r)),await this.sendWithRetry(e,t+1);}}}};function w(n){if(typeof window>"u")return ()=>{};let e=r=>{let o=r.error instanceof Error?r.error:new Error(r.message);n.captureError(o);},t=r=>{n.captureError(r.reason);};return window.addEventListener("error",e),window.addEventListener("unhandledrejection",t),()=>{window.removeEventListener("error",e),window.removeEventListener("unhandledrejection",t);}}var h=class{apiKey;environment;tags;beforeSend;breadcrumbManager;transport;userContext;dedupeMap=new Map;cleanupListeners;constructor(e={}){this.apiKey=core.resolveApiKey(e.apiKey);let t=core.resolveBaseUrl(e.baseUrl);this.environment=e.environment??"production",this.tags={...e.tags},this.beforeSend=e.beforeSend,this.breadcrumbManager=new c(Math.min(e.maxBreadcrumbs??20,50)),this.transport=new u({endpoint:`${t}/api/v1/telemetry/errors`,apiKey:this.apiKey}),e.autoCapture!==false&&typeof window<"u"&&(this.breadcrumbManager.attachListeners(),this.cleanupListeners=w(this));}captureError(e,t){let r=this.normalizeError(e),o=f(r.stack),i=g(r.type,r.message,o[0]),a=this.dedupeMap.get(i);if(a){a.count+=1;return}let d={fingerprint:i,errorType:r.type,errorMessage:r.message,stackTrace:o,breadcrumbs:this.breadcrumbManager.getAll(),userContext:this.userContext,deviceContext:this.getDeviceContext(),tags:{environment:this.environment,...this.tags,...t},occurrenceCount:1,clientTimestamp:Date.now()},v=s(d),m=this.beforeSend?this.beforeSend(v):v;if(!m)return;this.transport.send(m);let k=setTimeout(()=>{let p=this.dedupeMap.get(i);if(p&&p.count>1){let x={...p.lastPayload,occurrenceCount:p.count,clientTimestamp:Date.now()};this.transport.send(x);}this.dedupeMap.delete(i);},1e4);this.dedupeMap.set(i,{timer:k,count:1,lastPayload:m});}addBreadcrumb(e){this.breadcrumbManager.push(e);}setUser(e){this.userContext=e??void 0;}setTag(e,t){this.tags[e]=t;}async flush(){for(let[e,t]of this.dedupeMap.entries()){if(clearTimeout(t.timer),t.count>0){let r={...t.lastPayload,occurrenceCount:t.count,clientTimestamp:Date.now()};await this.transport.flush(r);}this.dedupeMap.delete(e);}}destroy(){this.cleanupListeners?.(),this.breadcrumbManager.detachListeners(),this.breadcrumbManager.clear(),this.dedupeMap.forEach(e=>clearTimeout(e.timer)),this.dedupeMap.clear();}normalizeError(e){return e instanceof Error?{type:e.name||"Error",message:e.message,stack:e.stack}:typeof e=="string"?{type:"UnhandledException",message:e}:{type:"NonErrorRejection",message:String(e)}}getDeviceContext(){let e=typeof window<"u"&&typeof navigator<"u";return {userAgent:e?navigator.userAgent:"Node/SSR",currentUrl:e?window.location.href:"",viewport:e?`${window.innerWidth}x${window.innerHeight}`:void 0,timezone:Intl?.DateTimeFormat()?.resolvedOptions()?.timeZone,networkStatus:e&&"connection"in navigator?navigator.connection?.effectiveType??"unknown":void 0}}};exports.BreadcrumbManager=c;exports.NexusTrackerClient=h;exports.Transport=u;exports.computeFingerprint=g;exports.parseStackTrace=f;exports.sanitizeObject=s;exports.sanitizeUrl=l;
@@ -5,5 +5,4 @@ var NexusTracker=(function(exports){'use strict';function g(t,e=1e3,r=3e4){let n
5
5
  4. NEXT_PUBLIC_NEXUS_API_KEY (Next.js)
6
6
  5. VITE_NEXUS_API_KEY (Vite)
7
7
  6. NUXT_PUBLIC_NEXUS_API_KEY (Nuxt)`);return e.trim()}function x(t,e="https://api.nexus.dev"){return (t||l("NEXT_PUBLIC_NEXUS_URL")||T("VITE_NEXUS_URL")||e).replace(/\/$/,"")}function h(t,e,r,n){if(r>n)return "[MaxDepthExceeded]";if(t==null)return t;if(typeof t!="object"&&typeof t!="function")return typeof t=="bigint"||typeof t=="symbol"?t.toString():typeof t=="function"?"[Function]":t;if(t instanceof Error)return {name:t.name,message:t.message,stack:t.stack};if(e.has(t))return "[Circular]";if(e.add(t),Array.isArray(t)){let i=t.map(o=>h(o,e,r+1,n));return e.delete(t),i}let a={};for(let i of Object.keys(t)){let o=t[i];a[i]=h(o,e,r+1,n);}return e.delete(t),a}function y(t,e=8){let r=h(t,new WeakSet,0,e);try{return JSON.stringify(r)}catch{return JSON.stringify({error:"[SerializationFailed]"})}}var M=/^(password|passwd|token|secret|authorization|bearer|auth|credit_?card|cvv|cvc|ssn|api_?key|access_?token|refresh_?token)$/i,R=/\b(?:\d[ -]*?){13,16}\b/g,B=/[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/g,N=/([?&](token|auth|key|secret|password|api_key|access_token|refresh_token)=)[^&]*/gi;function I(t){return t.replace(R,"[CARD_REDACTED]").replace(B,"[EMAIL_REDACTED]").replace(N,"$1[REDACTED]")}function s(t,e=0){if(e>5)return "[MaxDepthExceeded]";if(t==null)return t;if(typeof t=="string")return I(t);if(typeof t!="object")return t;if(Array.isArray(t))return t.map(n=>s(n,e+1));let r={};for(let[n,a]of Object.entries(t))M.test(n)?r[n]="[REDACTED]":r[n]=s(a,e+1);return r}function f(t){return t.replace(N,"$1[REDACTED]")}function v(t){if(!t)return [];let e=[],r=t.split(`
8
- `);for(let n of r){let a=n.trim(),i=a.match(/^at\s+(.+?)\s+\((.+?):(\d+):(\d+)\)$/)||a.match(/^at\s+(.+?):(\d+):(\d+)$/)||a.match(/^at\s+\((.+?):(\d+):(\d+)\)$/);if(i){i.length===5?e.push({functionName:i[1]??"<anonymous>",fileName:i[2]??"<unknown>",lineNumber:parseInt(i[3]??"0",10),columnNumber:parseInt(i[4]??"0",10)}):i.length===4&&e.push({functionName:"<anonymous>",fileName:i[1]??"<unknown>",lineNumber:parseInt(i[2]??"0",10),columnNumber:parseInt(i[3]??"0",10)});continue}let o=a.match(/^(.+?)@(.+?):(\d+):(\d+)$/);o&&e.push({functionName:o[1]??"<anonymous>",fileName:o[2]??"<unknown>",lineNumber:parseInt(o[3]??"0",10),columnNumber:parseInt(o[4]??"0",10)});}return e}function E(t,e,r){let n=r?.fileName??"unknown",a=r?.lineNumber??0,i=`${t}:${e}:${n}:${a}`,o=5381;for(let c=0;c<i.length;c++)o=(o<<5)+o+i.charCodeAt(c)>>>0;return `fp_${o.toString(16).padStart(8,"0")}`}var d=class{buffer;listenersAttached=false;clickHandler;popStateHandler;constructor(e){this.buffer=new _(e),this.clickHandler=this.handleClick.bind(this),this.popStateHandler=this.handleNavigation.bind(this);}push(e){let r=e.data?s(e.data):void 0;this.buffer.push({...e,data:r,timestamp:Date.now()});}getAll(){return this.buffer.toArray()}clear(){this.buffer.clear();}attachListeners(){this.listenersAttached||typeof window>"u"||(this.listenersAttached=true,document.addEventListener("click",this.clickHandler,{capture:true,passive:true}),window.addEventListener("popstate",this.popStateHandler,{passive:true}),this.interceptConsoleError());}detachListeners(){!this.listenersAttached||typeof window>"u"||(document.removeEventListener("click",this.clickHandler,{capture:true}),window.removeEventListener("popstate",this.popStateHandler),this.listenersAttached=false);}handleClick(e){let r=e.target;if(!r||r instanceof HTMLInputElement&&(r.type==="password"||r.hasAttribute("data-nexus-mask")))return;let n=this.describeElement(r);this.push({category:"ui.click",message:`Clicked ${n}`,level:"info",data:{elementTag:r.tagName.toLowerCase(),elementId:r.id||void 0,elementClass:r.className||void 0}});}handleNavigation(){this.push({category:"navigation",message:`Navigated to ${f(window.location.href)}`,level:"info",data:{url:f(window.location.href)}});}describeElement(e){let r=[e.tagName.toLowerCase()];return e.id&&r.push(`#${e.id}`),e.getAttribute("aria-label")&&r.push(`[aria-label="${e.getAttribute("aria-label")}"]`),r.join("")}interceptConsoleError(){let e=console.error.bind(console);console.error=(...r)=>{this.push({category:"console",message:r.map(String).join(" ").substring(0,500),level:"error"}),e(...r);};}};var u=class{options;maxRetries;isPageHiding=false;constructor(e){this.options=e,this.maxRetries=e.maxRetries??2,typeof document<"u"&&document.addEventListener("visibilitychange",()=>{document.visibilityState==="hidden"&&(this.isPageHiding=true);}),typeof window<"u"&&window.addEventListener("pagehide",()=>{this.isPageHiding=true;});}send(e){let r=y(e);if(this.isPageHiding&&typeof navigator<"u"&&typeof navigator.sendBeacon=="function"){let n=new Blob([r],{type:"application/json"});navigator.sendBeacon(this.options.endpoint,n);return}this.sendWithRetry(r,0);}async flush(e){let r=y(e);if(typeof navigator<"u"&&typeof navigator.sendBeacon=="function"){let n=new Blob([r],{type:"application/json"});navigator.sendBeacon(this.options.endpoint,n);return}await this.sendWithRetry(r,0);}async sendWithRetry(e,r){try{let n=await fetch(this.options.endpoint,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${this.options.apiKey}`},body:e,keepalive:!0});if(n.ok)return;if(n.status>=500&&r<this.maxRetries){let a=g(r,1e3,3e4);await new Promise(i=>setTimeout(i,a)),await this.sendWithRetry(e,r+1);}}catch{if(r<this.maxRetries){let n=g(r,1e3,3e4);await new Promise(a=>setTimeout(a,n)),await this.sendWithRetry(e,r+1);}}}};function A(t){if(typeof window>"u")return ()=>{};let e=n=>{let a=n.error instanceof Error?n.error:new Error(n.message);t.captureError(a);},r=n=>{t.captureError(n.reason);};return window.addEventListener("error",e),window.addEventListener("unhandledrejection",r),()=>{window.removeEventListener("error",e),window.removeEventListener("unhandledrejection",r);}}var b=class{apiKey;environment;tags;beforeSend;breadcrumbManager;transport;userContext;dedupeMap=new Map;cleanupListeners;constructor(e={}){this.apiKey=k(e.apiKey);let r=x(e.baseUrl);this.environment=e.environment??"production",this.tags={...e.tags},this.beforeSend=e.beforeSend,this.breadcrumbManager=new d(Math.min(e.maxBreadcrumbs??20,50)),this.transport=new u({endpoint:`${r}/api/v1/telemetry/errors`,apiKey:this.apiKey}),e.autoCapture!==false&&typeof window<"u"&&(this.breadcrumbManager.attachListeners(),this.cleanupListeners=A(this));}captureError(e,r){let n=this.normalizeError(e),a=v(n.stack),i=E(n.type,n.message,a[0]),o=this.dedupeMap.get(i);if(o){o.count+=1;return}let c={fingerprint:i,errorType:n.type,errorMessage:n.message,stackTrace:a,breadcrumbs:this.breadcrumbManager.getAll(),userContext:this.userContext,deviceContext:this.getDeviceContext(),tags:{environment:this.environment,...this.tags,...r},occurrenceCount:1,clientTimestamp:Date.now()},w=s(c),m=this.beforeSend?this.beforeSend(w):w;if(!m)return;this.transport.send(m);let P=setTimeout(()=>{let p=this.dedupeMap.get(i);if(p&&p.count>1){let S={...p.lastPayload,occurrenceCount:p.count,clientTimestamp:Date.now()};this.transport.send(S);}this.dedupeMap.delete(i);},1e4);this.dedupeMap.set(i,{timer:P,count:1,lastPayload:m});}addBreadcrumb(e){this.breadcrumbManager.push(e);}setUser(e){this.userContext=e??void 0;}setTag(e,r){this.tags[e]=r;}async flush(){for(let[e,r]of this.dedupeMap.entries()){if(clearTimeout(r.timer),r.count>0){let n={...r.lastPayload,occurrenceCount:r.count,clientTimestamp:Date.now()};await this.transport.flush(n);}this.dedupeMap.delete(e);}}destroy(){this.cleanupListeners?.(),this.breadcrumbManager.detachListeners(),this.breadcrumbManager.clear(),this.dedupeMap.forEach(e=>clearTimeout(e.timer)),this.dedupeMap.clear();}normalizeError(e){return e instanceof Error?{type:e.name||"Error",message:e.message,stack:e.stack}:typeof e=="string"?{type:"UnhandledException",message:e}:{type:"NonErrorRejection",message:String(e)}}getDeviceContext(){let e=typeof window<"u"&&typeof navigator<"u";return {userAgent:e?navigator.userAgent:"Node/SSR",currentUrl:e?window.location.href:"",viewport:e?`${window.innerWidth}x${window.innerHeight}`:void 0,timezone:Intl?.DateTimeFormat()?.resolvedOptions()?.timeZone,networkStatus:e&&"connection"in navigator?navigator.connection?.effectiveType??"unknown":void 0}}};exports.BreadcrumbManager=d;exports.NexusTrackerClient=b;exports.Transport=u;exports.computeFingerprint=E;exports.parseStackTrace=v;exports.sanitizeObject=s;exports.sanitizeUrl=f;return exports;})({});//# sourceMappingURL=index.global.js.map
9
- //# sourceMappingURL=index.global.js.map
8
+ `);for(let n of r){let a=n.trim(),i=a.match(/^at\s+(.+?)\s+\((.+?):(\d+):(\d+)\)$/)||a.match(/^at\s+(.+?):(\d+):(\d+)$/)||a.match(/^at\s+\((.+?):(\d+):(\d+)\)$/);if(i){i.length===5?e.push({functionName:i[1]??"<anonymous>",fileName:i[2]??"<unknown>",lineNumber:parseInt(i[3]??"0",10),columnNumber:parseInt(i[4]??"0",10)}):i.length===4&&e.push({functionName:"<anonymous>",fileName:i[1]??"<unknown>",lineNumber:parseInt(i[2]??"0",10),columnNumber:parseInt(i[3]??"0",10)});continue}let o=a.match(/^(.+?)@(.+?):(\d+):(\d+)$/);o&&e.push({functionName:o[1]??"<anonymous>",fileName:o[2]??"<unknown>",lineNumber:parseInt(o[3]??"0",10),columnNumber:parseInt(o[4]??"0",10)});}return e}function E(t,e,r){let n=r?.fileName??"unknown",a=r?.lineNumber??0,i=`${t}:${e}:${n}:${a}`,o=5381;for(let c=0;c<i.length;c++)o=(o<<5)+o+i.charCodeAt(c)>>>0;return `fp_${o.toString(16).padStart(8,"0")}`}var d=class{buffer;listenersAttached=false;clickHandler;popStateHandler;constructor(e){this.buffer=new _(e),this.clickHandler=this.handleClick.bind(this),this.popStateHandler=this.handleNavigation.bind(this);}push(e){let r=e.data?s(e.data):void 0;this.buffer.push({...e,data:r,timestamp:Date.now()});}getAll(){return this.buffer.toArray()}clear(){this.buffer.clear();}attachListeners(){this.listenersAttached||typeof window>"u"||(this.listenersAttached=true,document.addEventListener("click",this.clickHandler,{capture:true,passive:true}),window.addEventListener("popstate",this.popStateHandler,{passive:true}),this.interceptConsoleError());}detachListeners(){!this.listenersAttached||typeof window>"u"||(document.removeEventListener("click",this.clickHandler,{capture:true}),window.removeEventListener("popstate",this.popStateHandler),this.listenersAttached=false);}handleClick(e){let r=e.target;if(!r||r instanceof HTMLInputElement&&(r.type==="password"||r.hasAttribute("data-nexus-mask")))return;let n=this.describeElement(r);this.push({category:"ui.click",message:`Clicked ${n}`,level:"info",data:{elementTag:r.tagName.toLowerCase(),elementId:r.id||void 0,elementClass:r.className||void 0}});}handleNavigation(){this.push({category:"navigation",message:`Navigated to ${f(window.location.href)}`,level:"info",data:{url:f(window.location.href)}});}describeElement(e){let r=[e.tagName.toLowerCase()];return e.id&&r.push(`#${e.id}`),e.getAttribute("aria-label")&&r.push(`[aria-label="${e.getAttribute("aria-label")}"]`),r.join("")}interceptConsoleError(){let e=console.error.bind(console);console.error=(...r)=>{this.push({category:"console",message:r.map(String).join(" ").substring(0,500),level:"error"}),e(...r);};}};var u=class{options;maxRetries;isPageHiding=false;constructor(e){this.options=e,this.maxRetries=e.maxRetries??2,typeof document<"u"&&document.addEventListener("visibilitychange",()=>{document.visibilityState==="hidden"&&(this.isPageHiding=true);}),typeof window<"u"&&window.addEventListener("pagehide",()=>{this.isPageHiding=true;});}send(e){let r=y(e);if(this.isPageHiding&&typeof navigator<"u"&&typeof navigator.sendBeacon=="function"){let n=new Blob([r],{type:"application/json"});navigator.sendBeacon(this.options.endpoint,n);return}this.sendWithRetry(r,0);}async flush(e){let r=y(e);if(typeof navigator<"u"&&typeof navigator.sendBeacon=="function"){let n=new Blob([r],{type:"application/json"});navigator.sendBeacon(this.options.endpoint,n);return}await this.sendWithRetry(r,0);}async sendWithRetry(e,r){try{let n=await fetch(this.options.endpoint,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${this.options.apiKey}`},body:e,keepalive:!0});if(n.ok)return;if(n.status>=500&&r<this.maxRetries){let a=g(r,1e3,3e4);await new Promise(i=>setTimeout(i,a)),await this.sendWithRetry(e,r+1);}}catch{if(r<this.maxRetries){let n=g(r,1e3,3e4);await new Promise(a=>setTimeout(a,n)),await this.sendWithRetry(e,r+1);}}}};function A(t){if(typeof window>"u")return ()=>{};let e=n=>{let a=n.error instanceof Error?n.error:new Error(n.message);t.captureError(a);},r=n=>{t.captureError(n.reason);};return window.addEventListener("error",e),window.addEventListener("unhandledrejection",r),()=>{window.removeEventListener("error",e),window.removeEventListener("unhandledrejection",r);}}var b=class{apiKey;environment;tags;beforeSend;breadcrumbManager;transport;userContext;dedupeMap=new Map;cleanupListeners;constructor(e={}){this.apiKey=k(e.apiKey);let r=x(e.baseUrl);this.environment=e.environment??"production",this.tags={...e.tags},this.beforeSend=e.beforeSend,this.breadcrumbManager=new d(Math.min(e.maxBreadcrumbs??20,50)),this.transport=new u({endpoint:`${r}/api/v1/telemetry/errors`,apiKey:this.apiKey}),e.autoCapture!==false&&typeof window<"u"&&(this.breadcrumbManager.attachListeners(),this.cleanupListeners=A(this));}captureError(e,r){let n=this.normalizeError(e),a=v(n.stack),i=E(n.type,n.message,a[0]),o=this.dedupeMap.get(i);if(o){o.count+=1;return}let c={fingerprint:i,errorType:n.type,errorMessage:n.message,stackTrace:a,breadcrumbs:this.breadcrumbManager.getAll(),userContext:this.userContext,deviceContext:this.getDeviceContext(),tags:{environment:this.environment,...this.tags,...r},occurrenceCount:1,clientTimestamp:Date.now()},w=s(c),m=this.beforeSend?this.beforeSend(w):w;if(!m)return;this.transport.send(m);let P=setTimeout(()=>{let p=this.dedupeMap.get(i);if(p&&p.count>1){let S={...p.lastPayload,occurrenceCount:p.count,clientTimestamp:Date.now()};this.transport.send(S);}this.dedupeMap.delete(i);},1e4);this.dedupeMap.set(i,{timer:P,count:1,lastPayload:m});}addBreadcrumb(e){this.breadcrumbManager.push(e);}setUser(e){this.userContext=e??void 0;}setTag(e,r){this.tags[e]=r;}async flush(){for(let[e,r]of this.dedupeMap.entries()){if(clearTimeout(r.timer),r.count>0){let n={...r.lastPayload,occurrenceCount:r.count,clientTimestamp:Date.now()};await this.transport.flush(n);}this.dedupeMap.delete(e);}}destroy(){this.cleanupListeners?.(),this.breadcrumbManager.detachListeners(),this.breadcrumbManager.clear(),this.dedupeMap.forEach(e=>clearTimeout(e.timer)),this.dedupeMap.clear();}normalizeError(e){return e instanceof Error?{type:e.name||"Error",message:e.message,stack:e.stack}:typeof e=="string"?{type:"UnhandledException",message:e}:{type:"NonErrorRejection",message:String(e)}}getDeviceContext(){let e=typeof window<"u"&&typeof navigator<"u";return {userAgent:e?navigator.userAgent:"Node/SSR",currentUrl:e?window.location.href:"",viewport:e?`${window.innerWidth}x${window.innerHeight}`:void 0,timezone:Intl?.DateTimeFormat()?.resolvedOptions()?.timeZone,networkStatus:e&&"connection"in navigator?navigator.connection?.effectiveType??"unknown":void 0}}};exports.BreadcrumbManager=d;exports.NexusTrackerClient=b;exports.Transport=u;exports.computeFingerprint=E;exports.parseStackTrace=v;exports.sanitizeObject=s;exports.sanitizeUrl=f;return exports;})({});
package/dist/index.mjs CHANGED
@@ -1,3 +1,2 @@
1
1
  import {RingBuffer,safeStringify,computeBackoffMs,resolveApiKey,resolveBaseUrl}from'@nexussdk/core';var T=/^(password|passwd|token|secret|authorization|bearer|auth|credit_?card|cvv|cvc|ssn|api_?key|access_?token|refresh_?token)$/i,C=/\b(?:\d[ -]*?){13,16}\b/g,R=/[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/g,y=/([?&](token|auth|key|secret|password|api_key|access_token|refresh_token)=)[^&]*/gi;function A(n){return n.replace(C,"[CARD_REDACTED]").replace(R,"[EMAIL_REDACTED]").replace(y,"$1[REDACTED]")}function s(n,e=0){if(e>5)return "[MaxDepthExceeded]";if(n==null)return n;if(typeof n=="string")return A(n);if(typeof n!="object")return n;if(Array.isArray(n))return n.map(r=>s(r,e+1));let t={};for(let[r,o]of Object.entries(n))T.test(r)?t[r]="[REDACTED]":t[r]=s(o,e+1);return t}function l(n){return n.replace(y,"$1[REDACTED]")}function f(n){if(!n)return [];let e=[],t=n.split(`
2
- `);for(let r of t){let o=r.trim(),i=o.match(/^at\s+(.+?)\s+\((.+?):(\d+):(\d+)\)$/)||o.match(/^at\s+(.+?):(\d+):(\d+)$/)||o.match(/^at\s+\((.+?):(\d+):(\d+)\)$/);if(i){i.length===5?e.push({functionName:i[1]??"<anonymous>",fileName:i[2]??"<unknown>",lineNumber:parseInt(i[3]??"0",10),columnNumber:parseInt(i[4]??"0",10)}):i.length===4&&e.push({functionName:"<anonymous>",fileName:i[1]??"<unknown>",lineNumber:parseInt(i[2]??"0",10),columnNumber:parseInt(i[3]??"0",10)});continue}let a=o.match(/^(.+?)@(.+?):(\d+):(\d+)$/);a&&e.push({functionName:a[1]??"<anonymous>",fileName:a[2]??"<unknown>",lineNumber:parseInt(a[3]??"0",10),columnNumber:parseInt(a[4]??"0",10)});}return e}function g(n,e,t){let r=t?.fileName??"unknown",o=t?.lineNumber??0,i=`${n}:${e}:${r}:${o}`,a=5381;for(let d=0;d<i.length;d++)a=(a<<5)+a+i.charCodeAt(d)>>>0;return `fp_${a.toString(16).padStart(8,"0")}`}var c=class{buffer;listenersAttached=false;clickHandler;popStateHandler;constructor(e){this.buffer=new RingBuffer(e),this.clickHandler=this.handleClick.bind(this),this.popStateHandler=this.handleNavigation.bind(this);}push(e){let t=e.data?s(e.data):void 0;this.buffer.push({...e,data:t,timestamp:Date.now()});}getAll(){return this.buffer.toArray()}clear(){this.buffer.clear();}attachListeners(){this.listenersAttached||typeof window>"u"||(this.listenersAttached=true,document.addEventListener("click",this.clickHandler,{capture:true,passive:true}),window.addEventListener("popstate",this.popStateHandler,{passive:true}),this.interceptConsoleError());}detachListeners(){!this.listenersAttached||typeof window>"u"||(document.removeEventListener("click",this.clickHandler,{capture:true}),window.removeEventListener("popstate",this.popStateHandler),this.listenersAttached=false);}handleClick(e){let t=e.target;if(!t||t instanceof HTMLInputElement&&(t.type==="password"||t.hasAttribute("data-nexus-mask")))return;let r=this.describeElement(t);this.push({category:"ui.click",message:`Clicked ${r}`,level:"info",data:{elementTag:t.tagName.toLowerCase(),elementId:t.id||void 0,elementClass:t.className||void 0}});}handleNavigation(){this.push({category:"navigation",message:`Navigated to ${l(window.location.href)}`,level:"info",data:{url:l(window.location.href)}});}describeElement(e){let t=[e.tagName.toLowerCase()];return e.id&&t.push(`#${e.id}`),e.getAttribute("aria-label")&&t.push(`[aria-label="${e.getAttribute("aria-label")}"]`),t.join("")}interceptConsoleError(){let e=console.error.bind(console);console.error=(...t)=>{this.push({category:"console",message:t.map(String).join(" ").substring(0,500),level:"error"}),e(...t);};}};var u=class{options;maxRetries;isPageHiding=false;constructor(e){this.options=e,this.maxRetries=e.maxRetries??2,typeof document<"u"&&document.addEventListener("visibilitychange",()=>{document.visibilityState==="hidden"&&(this.isPageHiding=true);}),typeof window<"u"&&window.addEventListener("pagehide",()=>{this.isPageHiding=true;});}send(e){let t=safeStringify(e);if(this.isPageHiding&&typeof navigator<"u"&&typeof navigator.sendBeacon=="function"){let r=new Blob([t],{type:"application/json"});navigator.sendBeacon(this.options.endpoint,r);return}this.sendWithRetry(t,0);}async flush(e){let t=safeStringify(e);if(typeof navigator<"u"&&typeof navigator.sendBeacon=="function"){let r=new Blob([t],{type:"application/json"});navigator.sendBeacon(this.options.endpoint,r);return}await this.sendWithRetry(t,0);}async sendWithRetry(e,t){try{let r=await fetch(this.options.endpoint,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${this.options.apiKey}`},body:e,keepalive:!0});if(r.ok)return;if(r.status>=500&&t<this.maxRetries){let o=computeBackoffMs(t,1e3,3e4);await new Promise(i=>setTimeout(i,o)),await this.sendWithRetry(e,t+1);}}catch{if(t<this.maxRetries){let r=computeBackoffMs(t,1e3,3e4);await new Promise(o=>setTimeout(o,r)),await this.sendWithRetry(e,t+1);}}}};function w(n){if(typeof window>"u")return ()=>{};let e=r=>{let o=r.error instanceof Error?r.error:new Error(r.message);n.captureError(o);},t=r=>{n.captureError(r.reason);};return window.addEventListener("error",e),window.addEventListener("unhandledrejection",t),()=>{window.removeEventListener("error",e),window.removeEventListener("unhandledrejection",t);}}var h=class{apiKey;environment;tags;beforeSend;breadcrumbManager;transport;userContext;dedupeMap=new Map;cleanupListeners;constructor(e={}){this.apiKey=resolveApiKey(e.apiKey);let t=resolveBaseUrl(e.baseUrl);this.environment=e.environment??"production",this.tags={...e.tags},this.beforeSend=e.beforeSend,this.breadcrumbManager=new c(Math.min(e.maxBreadcrumbs??20,50)),this.transport=new u({endpoint:`${t}/api/v1/telemetry/errors`,apiKey:this.apiKey}),e.autoCapture!==false&&typeof window<"u"&&(this.breadcrumbManager.attachListeners(),this.cleanupListeners=w(this));}captureError(e,t){let r=this.normalizeError(e),o=f(r.stack),i=g(r.type,r.message,o[0]),a=this.dedupeMap.get(i);if(a){a.count+=1;return}let d={fingerprint:i,errorType:r.type,errorMessage:r.message,stackTrace:o,breadcrumbs:this.breadcrumbManager.getAll(),userContext:this.userContext,deviceContext:this.getDeviceContext(),tags:{environment:this.environment,...this.tags,...t},occurrenceCount:1,clientTimestamp:Date.now()},v=s(d),m=this.beforeSend?this.beforeSend(v):v;if(!m)return;this.transport.send(m);let k=setTimeout(()=>{let p=this.dedupeMap.get(i);if(p&&p.count>1){let x={...p.lastPayload,occurrenceCount:p.count,clientTimestamp:Date.now()};this.transport.send(x);}this.dedupeMap.delete(i);},1e4);this.dedupeMap.set(i,{timer:k,count:1,lastPayload:m});}addBreadcrumb(e){this.breadcrumbManager.push(e);}setUser(e){this.userContext=e??void 0;}setTag(e,t){this.tags[e]=t;}async flush(){for(let[e,t]of this.dedupeMap.entries()){if(clearTimeout(t.timer),t.count>0){let r={...t.lastPayload,occurrenceCount:t.count,clientTimestamp:Date.now()};await this.transport.flush(r);}this.dedupeMap.delete(e);}}destroy(){this.cleanupListeners?.(),this.breadcrumbManager.detachListeners(),this.breadcrumbManager.clear(),this.dedupeMap.forEach(e=>clearTimeout(e.timer)),this.dedupeMap.clear();}normalizeError(e){return e instanceof Error?{type:e.name||"Error",message:e.message,stack:e.stack}:typeof e=="string"?{type:"UnhandledException",message:e}:{type:"NonErrorRejection",message:String(e)}}getDeviceContext(){let e=typeof window<"u"&&typeof navigator<"u";return {userAgent:e?navigator.userAgent:"Node/SSR",currentUrl:e?window.location.href:"",viewport:e?`${window.innerWidth}x${window.innerHeight}`:void 0,timezone:Intl?.DateTimeFormat()?.resolvedOptions()?.timeZone,networkStatus:e&&"connection"in navigator?navigator.connection?.effectiveType??"unknown":void 0}}};export{c as BreadcrumbManager,h as NexusTrackerClient,u as Transport,g as computeFingerprint,f as parseStackTrace,s as sanitizeObject,l as sanitizeUrl};//# sourceMappingURL=index.mjs.map
3
- //# sourceMappingURL=index.mjs.map
2
+ `);for(let r of t){let o=r.trim(),i=o.match(/^at\s+(.+?)\s+\((.+?):(\d+):(\d+)\)$/)||o.match(/^at\s+(.+?):(\d+):(\d+)$/)||o.match(/^at\s+\((.+?):(\d+):(\d+)\)$/);if(i){i.length===5?e.push({functionName:i[1]??"<anonymous>",fileName:i[2]??"<unknown>",lineNumber:parseInt(i[3]??"0",10),columnNumber:parseInt(i[4]??"0",10)}):i.length===4&&e.push({functionName:"<anonymous>",fileName:i[1]??"<unknown>",lineNumber:parseInt(i[2]??"0",10),columnNumber:parseInt(i[3]??"0",10)});continue}let a=o.match(/^(.+?)@(.+?):(\d+):(\d+)$/);a&&e.push({functionName:a[1]??"<anonymous>",fileName:a[2]??"<unknown>",lineNumber:parseInt(a[3]??"0",10),columnNumber:parseInt(a[4]??"0",10)});}return e}function g(n,e,t){let r=t?.fileName??"unknown",o=t?.lineNumber??0,i=`${n}:${e}:${r}:${o}`,a=5381;for(let d=0;d<i.length;d++)a=(a<<5)+a+i.charCodeAt(d)>>>0;return `fp_${a.toString(16).padStart(8,"0")}`}var c=class{buffer;listenersAttached=false;clickHandler;popStateHandler;constructor(e){this.buffer=new RingBuffer(e),this.clickHandler=this.handleClick.bind(this),this.popStateHandler=this.handleNavigation.bind(this);}push(e){let t=e.data?s(e.data):void 0;this.buffer.push({...e,data:t,timestamp:Date.now()});}getAll(){return this.buffer.toArray()}clear(){this.buffer.clear();}attachListeners(){this.listenersAttached||typeof window>"u"||(this.listenersAttached=true,document.addEventListener("click",this.clickHandler,{capture:true,passive:true}),window.addEventListener("popstate",this.popStateHandler,{passive:true}),this.interceptConsoleError());}detachListeners(){!this.listenersAttached||typeof window>"u"||(document.removeEventListener("click",this.clickHandler,{capture:true}),window.removeEventListener("popstate",this.popStateHandler),this.listenersAttached=false);}handleClick(e){let t=e.target;if(!t||t instanceof HTMLInputElement&&(t.type==="password"||t.hasAttribute("data-nexus-mask")))return;let r=this.describeElement(t);this.push({category:"ui.click",message:`Clicked ${r}`,level:"info",data:{elementTag:t.tagName.toLowerCase(),elementId:t.id||void 0,elementClass:t.className||void 0}});}handleNavigation(){this.push({category:"navigation",message:`Navigated to ${l(window.location.href)}`,level:"info",data:{url:l(window.location.href)}});}describeElement(e){let t=[e.tagName.toLowerCase()];return e.id&&t.push(`#${e.id}`),e.getAttribute("aria-label")&&t.push(`[aria-label="${e.getAttribute("aria-label")}"]`),t.join("")}interceptConsoleError(){let e=console.error.bind(console);console.error=(...t)=>{this.push({category:"console",message:t.map(String).join(" ").substring(0,500),level:"error"}),e(...t);};}};var u=class{options;maxRetries;isPageHiding=false;constructor(e){this.options=e,this.maxRetries=e.maxRetries??2,typeof document<"u"&&document.addEventListener("visibilitychange",()=>{document.visibilityState==="hidden"&&(this.isPageHiding=true);}),typeof window<"u"&&window.addEventListener("pagehide",()=>{this.isPageHiding=true;});}send(e){let t=safeStringify(e);if(this.isPageHiding&&typeof navigator<"u"&&typeof navigator.sendBeacon=="function"){let r=new Blob([t],{type:"application/json"});navigator.sendBeacon(this.options.endpoint,r);return}this.sendWithRetry(t,0);}async flush(e){let t=safeStringify(e);if(typeof navigator<"u"&&typeof navigator.sendBeacon=="function"){let r=new Blob([t],{type:"application/json"});navigator.sendBeacon(this.options.endpoint,r);return}await this.sendWithRetry(t,0);}async sendWithRetry(e,t){try{let r=await fetch(this.options.endpoint,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${this.options.apiKey}`},body:e,keepalive:!0});if(r.ok)return;if(r.status>=500&&t<this.maxRetries){let o=computeBackoffMs(t,1e3,3e4);await new Promise(i=>setTimeout(i,o)),await this.sendWithRetry(e,t+1);}}catch{if(t<this.maxRetries){let r=computeBackoffMs(t,1e3,3e4);await new Promise(o=>setTimeout(o,r)),await this.sendWithRetry(e,t+1);}}}};function w(n){if(typeof window>"u")return ()=>{};let e=r=>{let o=r.error instanceof Error?r.error:new Error(r.message);n.captureError(o);},t=r=>{n.captureError(r.reason);};return window.addEventListener("error",e),window.addEventListener("unhandledrejection",t),()=>{window.removeEventListener("error",e),window.removeEventListener("unhandledrejection",t);}}var h=class{apiKey;environment;tags;beforeSend;breadcrumbManager;transport;userContext;dedupeMap=new Map;cleanupListeners;constructor(e={}){this.apiKey=resolveApiKey(e.apiKey);let t=resolveBaseUrl(e.baseUrl);this.environment=e.environment??"production",this.tags={...e.tags},this.beforeSend=e.beforeSend,this.breadcrumbManager=new c(Math.min(e.maxBreadcrumbs??20,50)),this.transport=new u({endpoint:`${t}/api/v1/telemetry/errors`,apiKey:this.apiKey}),e.autoCapture!==false&&typeof window<"u"&&(this.breadcrumbManager.attachListeners(),this.cleanupListeners=w(this));}captureError(e,t){let r=this.normalizeError(e),o=f(r.stack),i=g(r.type,r.message,o[0]),a=this.dedupeMap.get(i);if(a){a.count+=1;return}let d={fingerprint:i,errorType:r.type,errorMessage:r.message,stackTrace:o,breadcrumbs:this.breadcrumbManager.getAll(),userContext:this.userContext,deviceContext:this.getDeviceContext(),tags:{environment:this.environment,...this.tags,...t},occurrenceCount:1,clientTimestamp:Date.now()},v=s(d),m=this.beforeSend?this.beforeSend(v):v;if(!m)return;this.transport.send(m);let k=setTimeout(()=>{let p=this.dedupeMap.get(i);if(p&&p.count>1){let x={...p.lastPayload,occurrenceCount:p.count,clientTimestamp:Date.now()};this.transport.send(x);}this.dedupeMap.delete(i);},1e4);this.dedupeMap.set(i,{timer:k,count:1,lastPayload:m});}addBreadcrumb(e){this.breadcrumbManager.push(e);}setUser(e){this.userContext=e??void 0;}setTag(e,t){this.tags[e]=t;}async flush(){for(let[e,t]of this.dedupeMap.entries()){if(clearTimeout(t.timer),t.count>0){let r={...t.lastPayload,occurrenceCount:t.count,clientTimestamp:Date.now()};await this.transport.flush(r);}this.dedupeMap.delete(e);}}destroy(){this.cleanupListeners?.(),this.breadcrumbManager.detachListeners(),this.breadcrumbManager.clear(),this.dedupeMap.forEach(e=>clearTimeout(e.timer)),this.dedupeMap.clear();}normalizeError(e){return e instanceof Error?{type:e.name||"Error",message:e.message,stack:e.stack}:typeof e=="string"?{type:"UnhandledException",message:e}:{type:"NonErrorRejection",message:String(e)}}getDeviceContext(){let e=typeof window<"u"&&typeof navigator<"u";return {userAgent:e?navigator.userAgent:"Node/SSR",currentUrl:e?window.location.href:"",viewport:e?`${window.innerWidth}x${window.innerHeight}`:void 0,timezone:Intl?.DateTimeFormat()?.resolvedOptions()?.timeZone,networkStatus:e&&"connection"in navigator?navigator.connection?.effectiveType??"unknown":void 0}}};export{c as BreadcrumbManager,h as NexusTrackerClient,u as Transport,g as computeFingerprint,f as parseStackTrace,s as sanitizeObject,l as sanitizeUrl};
package/package.json CHANGED
@@ -1,11 +1,39 @@
1
1
  {
2
2
  "name": "@nexussdk/tracker",
3
- "version": "0.0.1",
3
+ "version": "0.0.3",
4
4
  "private": false,
5
5
  "publishConfig": {
6
6
  "access": "public"
7
7
  },
8
8
  "description": "Ultra-resilient client crash ingestion and telemetry SDK with automated PII sanitization",
9
+ "license": "MIT",
10
+ "author": "Nexus",
11
+ "contributors": [
12
+ {
13
+ "name": "Hồ Huỳnh Dũng",
14
+ "email": "hohuynhdung@gmail.com",
15
+ "url": "https://github.com/Huynhdung295"
16
+ }
17
+ ],
18
+ "repository": {
19
+ "type": "git",
20
+ "url": "https://github.com/Huynhdung295/NexusSDK.git",
21
+ "directory": "packages/sdk-tracker"
22
+ },
23
+ "homepage": "https://github.com/Huynhdung295/NexusSDK#readme",
24
+ "keywords": [
25
+ "nexus",
26
+ "nexussdk",
27
+ "error-tracking",
28
+ "telemetry",
29
+ "crash-reporting",
30
+ "breadcrumbs",
31
+ "pii-sanitizer"
32
+ ],
33
+ "files": [
34
+ "dist",
35
+ "README.md"
36
+ ],
9
37
  "main": "./dist/index.cjs",
10
38
  "module": "./dist/index.mjs",
11
39
  "types": "./dist/index.d.ts",
@@ -17,8 +45,8 @@
17
45
  }
18
46
  },
19
47
  "dependencies": {
20
- "@nexussdk/contracts": "0.0.1",
21
- "@nexussdk/core": "0.0.1"
48
+ "@nexussdk/contracts": "0.0.3",
49
+ "@nexussdk/core": "0.0.3"
22
50
  },
23
51
  "devDependencies": {
24
52
  "tsup": "^8.0.2",
@@ -1,26 +0,0 @@
1
-
2
- > @nexussdk/tracker@0.0.1 build /home/runner/work/NexusSDK/NexusSDK/packages/sdk-tracker
3
- > tsup
4
-
5
- CLI Building entry: src/index.ts
6
- CLI Using tsconfig: tsconfig.json
7
- CLI tsup v8.5.1
8
- CLI Using tsup config: /home/runner/work/NexusSDK/NexusSDK/packages/sdk-tracker/tsup.config.ts
9
- CLI Target: es2022
10
- CLI Cleaning output folder
11
- ESM Build start
12
- CJS Build start
13
- IIFE Build start
14
- IIFE dist/index.global.js 10.01 KB
15
- IIFE dist/index.global.js.map 64.52 KB
16
- IIFE ⚡️ Build success in 666ms
17
- ESM dist/index.mjs 7.60 KB
18
- ESM dist/index.mjs.map 40.58 KB
19
- ESM ⚡️ Build success in 666ms
20
- CJS dist/index.cjs 7.61 KB
21
- CJS dist/index.cjs.map 40.59 KB
22
- CJS ⚡️ Build success in 666ms
23
- DTS Build start
24
- DTS ⚡️ Build success in 1595ms
25
- DTS dist/index.d.mts 11.78 KB
26
- DTS dist/index.d.ts 11.78 KB
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/sanitizer.ts","../src/fingerprint.ts","../src/breadcrumbs.ts","../src/transport.ts","../src/listeners.ts","../src/client.ts"],"names":["SENSITIVE_KEY_PATTERN","CREDIT_CARD_PATTERN","EMAIL_PATTERN","SENSITIVE_QUERY_PARAM_PATTERN","sanitizeString","value","sanitizeObject","depth","item","sanitized","key","val","sanitizeUrl","url","parseStackTrace","stack","frames","lines","line","trimmed","v8Match","geckoMatch","computeFingerprint","errorType","errorMessage","topFrame","fileName","lineNumber","signature","hash","i","BreadcrumbManager","maxBreadcrumbs","RingBuffer","breadcrumb","sanitizedData","evt","target","description","el","parts","original","args","Transport","options","payload","body","safeStringify","blob","attempt","response","delay","computeBackoffMs","r","attachGlobalListeners","client","errorHandler","event","error","rejectionHandler","NexusTrackerClient","resolveApiKey","baseUrl","resolveBaseUrl","extra","normalized","stackFrames","fingerprint","existing","finalPayload","timer","entry","aggregated","user","err","isBrowser"],"mappings":"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","file":"index.cjs","sourcesContent":["/**\n * @fileoverview PII sanitization engine for safe error payload transmission.\n * Recursively redacts sensitive fields before any data leaves the client device.\n * @module @nexus/sdk-tracker/sanitizer\n */\n\n/** Maximum object recursion depth to prevent stack overflows on deep structures. */\nconst MAX_DEPTH = 5;\n\n/**\n * Regex matching sensitive key names that should be redacted.\n * Matches exact key names (case-insensitive) for password, token, secret, etc.\n */\nconst SENSITIVE_KEY_PATTERN =\n /^(password|passwd|token|secret|authorization|bearer|auth|credit_?card|cvv|cvc|ssn|api_?key|access_?token|refresh_?token)$/i;\n\n/**\n * Regex matching credit card number patterns (13-16 digit sequences).\n * Covers common formats with spaces or dashes between groups.\n */\nconst CREDIT_CARD_PATTERN = /\\b(?:\\d[ -]*?){13,16}\\b/g;\n\n/**\n * Regex matching email addresses in string values.\n * Only the domain portion is retained for limited diagnostic context.\n */\nconst EMAIL_PATTERN = /[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}/g;\n\n/**\n * Regex matching URL query parameters containing sensitive tokens.\n * Strips values for: token, auth, key, secret, password, api_key.\n */\nconst SENSITIVE_QUERY_PARAM_PATTERN =\n /([?&](token|auth|key|secret|password|api_key|access_token|refresh_token)=)[^&]*/gi;\n\n/**\n * Sanitizes a string value by removing credit card numbers and email addresses.\n *\n * @param value - The string to sanitize.\n * @returns Sanitized string with sensitive patterns replaced.\n */\nfunction sanitizeString(value: string): string {\n return value\n .replace(CREDIT_CARD_PATTERN, '[CARD_REDACTED]')\n .replace(EMAIL_PATTERN, '[EMAIL_REDACTED]')\n .replace(SENSITIVE_QUERY_PARAM_PATTERN, '$1[REDACTED]');\n}\n\n/**\n * Recursively sanitizes an object, redacting sensitive key-value pairs.\n * Handles nested objects, arrays, and string values.\n *\n * @param value - The value to sanitize (any type).\n * @param depth - Current recursion depth (internal, starts at 0).\n * @returns A sanitized deep copy of the input.\n *\n * @example\n * const payload = {\n * user: { email: 'john@example.com', password: 'secret123' },\n * token: 'Bearer abc123',\n * creditCard: '4111 1111 1111 1111',\n * };\n * const safe = sanitizeObject(payload);\n * // { user: { email: '[EMAIL_REDACTED]', password: '[REDACTED]' },\n * // token: '[REDACTED]', creditCard: '[REDACTED]' }\n */\nexport function sanitizeObject(value: unknown, depth = 0): unknown {\n if (depth > MAX_DEPTH) return '[MaxDepthExceeded]';\n\n if (value === null || value === undefined) return value;\n\n if (typeof value === 'string') {\n return sanitizeString(value);\n }\n\n if (typeof value !== 'object') {\n return value; // number, boolean, etc.\n }\n\n if (Array.isArray(value)) {\n return value.map((item) => sanitizeObject(item, depth + 1));\n }\n\n const sanitized: Record<string, unknown> = {};\n for (const [key, val] of Object.entries(value as Record<string, unknown>)) {\n if (SENSITIVE_KEY_PATTERN.test(key)) {\n sanitized[key] = '[REDACTED]';\n } else {\n sanitized[key] = sanitizeObject(val, depth + 1);\n }\n }\n return sanitized;\n}\n\n/**\n * Sanitizes a URL by stripping sensitive query parameters.\n *\n * @param url - URL string to sanitize.\n * @returns URL with sensitive query values replaced with [REDACTED].\n *\n * @example\n * sanitizeUrl('https://app.com/auth?token=abc123&redirect=/home');\n * // 'https://app.com/auth?token=[REDACTED]&redirect=/home'\n */\nexport function sanitizeUrl(url: string): string {\n return url.replace(SENSITIVE_QUERY_PARAM_PATTERN, '$1[REDACTED]');\n}\n","/**\n * @fileoverview Error fingerprinting and stack trace parsing utilities.\n * Generates deterministic SHA-256-based fingerprints for error deduplication.\n * @module @nexus/sdk-tracker/fingerprint\n */\n\nimport type { StackFrame } from '@nexussdk/contracts';\n\n/**\n * Parses a JavaScript error stack string into structured StackFrame objects.\n * Supports V8 (Chrome/Node), SpiderMonkey (Firefox), and JavaScriptCore (Safari) formats.\n *\n * @param stack - Raw stack trace string from an Error object.\n * @returns Array of parsed {@link StackFrame} objects (innermost first).\n *\n * @example\n * const frames = parseStackTrace(new TypeError('test').stack);\n * // [{ functionName: 'processPayment', fileName: 'chunk.js', lineNumber: 1, columnNumber: 400 }]\n */\nexport function parseStackTrace(stack?: string): StackFrame[] {\n if (!stack) return [];\n\n const frames: StackFrame[] = [];\n const lines = stack.split('\\n');\n\n for (const line of lines) {\n const trimmed = line.trim();\n\n // V8 format: \" at FunctionName (file.js:line:col)\"\n // V8 anonymous: \" at file.js:line:col\"\n const v8Match =\n trimmed.match(/^at\\s+(.+?)\\s+\\((.+?):(\\d+):(\\d+)\\)$/) ||\n trimmed.match(/^at\\s+(.+?):(\\d+):(\\d+)$/) ||\n trimmed.match(/^at\\s+\\((.+?):(\\d+):(\\d+)\\)$/);\n\n if (v8Match) {\n if (v8Match.length === 5) {\n // Named function\n frames.push({\n functionName: v8Match[1] ?? '<anonymous>',\n fileName: v8Match[2] ?? '<unknown>',\n lineNumber: parseInt(v8Match[3] ?? '0', 10),\n columnNumber: parseInt(v8Match[4] ?? '0', 10),\n });\n } else if (v8Match.length === 4) {\n // Anonymous or \"at (file:line:col)\"\n frames.push({\n functionName: '<anonymous>',\n fileName: v8Match[1] ?? '<unknown>',\n lineNumber: parseInt(v8Match[2] ?? '0', 10),\n columnNumber: parseInt(v8Match[3] ?? '0', 10),\n });\n }\n continue;\n }\n\n // Firefox/Safari format: \"functionName@file.js:line:col\"\n const geckoMatch = trimmed.match(/^(.+?)@(.+?):(\\d+):(\\d+)$/);\n if (geckoMatch) {\n frames.push({\n functionName: geckoMatch[1] ?? '<anonymous>',\n fileName: geckoMatch[2] ?? '<unknown>',\n lineNumber: parseInt(geckoMatch[3] ?? '0', 10),\n columnNumber: parseInt(geckoMatch[4] ?? '0', 10),\n });\n }\n }\n\n return frames;\n}\n\n/**\n * Computes a fast non-cryptographic fingerprint string for error deduplication.\n * Uses a djb2-style hash over the signature components to avoid SubtleCrypto async API.\n *\n * Format: hash(errorType + \":\" + errorMessage + \":\" + topFileName + \":\" + topLineNumber)\n *\n * @param errorType - JavaScript error type (e.g. \"TypeError\").\n * @param errorMessage - Primary error message.\n * @param topFrame - Innermost (first) stack frame, or undefined if stack is empty.\n * @returns Hex-like fingerprint string for deduplication grouping.\n *\n * @example\n * const fp = computeFingerprint(\n * 'TypeError',\n * \"Cannot read properties of undefined (reading 'map')\",\n * { functionName: 'render', fileName: 'chunk.js', lineNumber: 1, columnNumber: 400 }\n * );\n * // 'fp_a3f9b2c1d4...'\n */\nexport function computeFingerprint(\n errorType: string,\n errorMessage: string,\n topFrame?: StackFrame,\n): string {\n const fileName = topFrame?.fileName ?? 'unknown';\n const lineNumber = topFrame?.lineNumber ?? 0;\n const signature = `${errorType}:${errorMessage}:${fileName}:${lineNumber}`;\n\n // djb2 hash algorithm — fast, no async, no external deps\n let hash = 5381;\n for (let i = 0; i < signature.length; i++) {\n hash = ((hash << 5) + hash + signature.charCodeAt(i)) >>> 0;\n }\n\n return `fp_${hash.toString(16).padStart(8, '0')}`;\n}\n","/**\n * @fileoverview Breadcrumb ring buffer manager for recording user activity trails.\n * Automatically captures DOM clicks, navigation events, and console calls.\n * @module @nexus/sdk-tracker/breadcrumbs\n */\n\nimport type { Breadcrumb, BreadcrumbCategory } from '@nexussdk/contracts';\nimport { RingBuffer } from '@nexussdk/core';\nimport { sanitizeObject, sanitizeUrl } from './sanitizer.js';\n\n/**\n * Manages the breadcrumb ring buffer and attaches automatic DOM/navigation listeners.\n *\n * @example\n * const manager = new BreadcrumbManager(20);\n * manager.attachListeners();\n * manager.push({ category: 'custom', message: 'User entered checkout flow', level: 'info' });\n * const trail = manager.getAll(); // Array of last 20 breadcrumbs\n */\nexport class BreadcrumbManager {\n private readonly buffer: RingBuffer<Breadcrumb>;\n private listenersAttached = false;\n private readonly clickHandler: (evt: MouseEvent) => void;\n private readonly popStateHandler: () => void;\n\n constructor(maxBreadcrumbs: number) {\n this.buffer = new RingBuffer<Breadcrumb>(maxBreadcrumbs);\n\n // Bind handlers once to enable proper removeEventListener\n this.clickHandler = this.handleClick.bind(this);\n this.popStateHandler = this.handleNavigation.bind(this);\n }\n\n /**\n * Adds a breadcrumb to the ring buffer, sanitizing any PII in the data.\n *\n * @param breadcrumb - Breadcrumb data (timestamp will be auto-injected).\n *\n * @example\n * manager.push({ category: 'http', message: 'POST /api/checkout', level: 'info', data: { status: 200 } });\n */\n push(breadcrumb: Omit<Breadcrumb, 'timestamp'>): void {\n const sanitizedData = breadcrumb.data\n ? (sanitizeObject(breadcrumb.data) as Record<string, unknown>)\n : undefined;\n\n this.buffer.push({\n ...breadcrumb,\n data: sanitizedData,\n timestamp: Date.now(),\n });\n }\n\n /**\n * Returns all breadcrumbs in chronological order (oldest first).\n *\n * @returns Ordered array of breadcrumbs for inclusion in error payloads.\n */\n getAll(): Breadcrumb[] {\n return this.buffer.toArray();\n }\n\n /**\n * Clears all stored breadcrumbs.\n */\n clear(): void {\n this.buffer.clear();\n }\n\n /**\n * Attaches automatic event listeners for DOM clicks and navigation changes.\n * Safe to call multiple times — idempotent.\n *\n * @example\n * manager.attachListeners(); // called during SDK init\n */\n attachListeners(): void {\n if (this.listenersAttached || typeof window === 'undefined') return;\n this.listenersAttached = true;\n\n // DOM click breadcrumbs\n document.addEventListener('click', this.clickHandler, { capture: true, passive: true });\n\n // Browser navigation breadcrumbs (popstate = back/forward)\n window.addEventListener('popstate', this.popStateHandler, { passive: true });\n\n // Intercept console.error for breadcrumb recording\n this.interceptConsoleError();\n }\n\n /**\n * Removes all attached event listeners.\n *\n * @example\n * manager.detachListeners(); // called during SDK destroy\n */\n detachListeners(): void {\n if (!this.listenersAttached || typeof window === 'undefined') return;\n document.removeEventListener('click', this.clickHandler, { capture: true });\n window.removeEventListener('popstate', this.popStateHandler);\n this.listenersAttached = false;\n }\n\n private handleClick(evt: MouseEvent): void {\n const target = evt.target as HTMLElement | null;\n if (!target) return;\n\n // Mask password fields and data-nexus-mask elements\n if (\n target instanceof HTMLInputElement &&\n (target.type === 'password' || target.hasAttribute('data-nexus-mask'))\n ) {\n return; // Skip masked fields entirely\n }\n\n const description = this.describeElement(target);\n this.push({\n category: 'ui.click' as BreadcrumbCategory,\n message: `Clicked ${description}`,\n level: 'info',\n data: {\n elementTag: target.tagName.toLowerCase(),\n elementId: target.id || undefined,\n elementClass: target.className || undefined,\n },\n });\n }\n\n private handleNavigation(): void {\n this.push({\n category: 'navigation' as BreadcrumbCategory,\n message: `Navigated to ${sanitizeUrl(window.location.href)}`,\n level: 'info',\n data: { url: sanitizeUrl(window.location.href) },\n });\n }\n\n private describeElement(el: HTMLElement): string {\n const parts: string[] = [el.tagName.toLowerCase()];\n if (el.id) parts.push(`#${el.id}`);\n if (el.getAttribute('aria-label')) parts.push(`[aria-label=\"${el.getAttribute('aria-label')}\"]`);\n return parts.join('');\n }\n\n private interceptConsoleError(): void {\n const original = console.error.bind(console);\n console.error = (...args: unknown[]) => {\n this.push({\n category: 'console' as BreadcrumbCategory,\n message: args.map(String).join(' ').substring(0, 500),\n level: 'error',\n });\n original(...args);\n };\n }\n}\n","/**\n * @fileoverview Hybrid transport dispatcher for error payload delivery.\n * Prefers fetch with keepalive; falls back to navigator.sendBeacon during page unload.\n * @module @nexus/sdk-tracker/transport\n */\n\nimport type { ErrorEventPayload } from '@nexussdk/contracts';\nimport { safeStringify, computeBackoffMs } from '@nexussdk/core';\n\n/**\n * Options for configuring the transport dispatcher.\n */\nexport interface TransportOptions {\n /** Go-Gin ingestion endpoint URL. */\n endpoint: string;\n /** Public API key for Authorization header. */\n apiKey: string;\n /** Maximum retry attempts on network failures. Defaults to 2. */\n maxRetries?: number;\n}\n\n/**\n * Hybrid transport dispatcher that intelligently selects the delivery mechanism:\n * - **Normal execution**: `fetch(url, { keepalive: true })` with retry\n * - **Page teardown** (`visibilityState === 'hidden'` or `pagehide`): `navigator.sendBeacon`\n *\n * @example\n * const transport = new Transport({\n * endpoint: 'http://localhost:8080/api/v1/telemetry/errors',\n * apiKey: 'pk_live_...',\n * });\n * transport.send(errorPayload);\n */\nexport class Transport {\n private readonly options: TransportOptions;\n private readonly maxRetries: number;\n private isPageHiding = false;\n\n constructor(options: TransportOptions) {\n this.options = options;\n this.maxRetries = options.maxRetries ?? 2;\n\n // Detect page teardown events to switch to sendBeacon\n if (typeof document !== 'undefined') {\n document.addEventListener('visibilitychange', () => {\n if (document.visibilityState === 'hidden') {\n this.isPageHiding = true;\n }\n });\n }\n if (typeof window !== 'undefined') {\n window.addEventListener('pagehide', () => {\n this.isPageHiding = true;\n });\n }\n }\n\n /**\n * Dispatches an error payload to the Go-Gin ingestion endpoint.\n * Automatically selects fetch or sendBeacon based on page lifecycle state.\n *\n * @param payload - The sanitized {@link ErrorEventPayload} to transmit.\n *\n * @example\n * transport.send({\n * fingerprint: 'fp_abc123',\n * errorType: 'TypeError',\n * errorMessage: \"Cannot read property 'map' of undefined\",\n * // ...\n * });\n */\n send(payload: ErrorEventPayload): void {\n const body = safeStringify(payload);\n\n // Use sendBeacon during page teardown — prevents cancelled fetch requests\n if (\n this.isPageHiding &&\n typeof navigator !== 'undefined' &&\n typeof navigator.sendBeacon === 'function'\n ) {\n const blob = new Blob([body], { type: 'application/json' });\n navigator.sendBeacon(this.options.endpoint, blob);\n return;\n }\n\n // Normal execution: fetch with keepalive and exponential backoff retry\n void this.sendWithRetry(body, 0);\n }\n\n /**\n * Forces immediate flush of all pending events using sendBeacon.\n * Called during manual flush or SDK destroy lifecycle.\n *\n * @param payload - The error payload to flush.\n * @returns Promise that resolves when the beacon is dispatched.\n */\n async flush(payload: ErrorEventPayload): Promise<void> {\n const body = safeStringify(payload);\n if (typeof navigator !== 'undefined' && typeof navigator.sendBeacon === 'function') {\n const blob = new Blob([body], { type: 'application/json' });\n navigator.sendBeacon(this.options.endpoint, blob);\n return;\n }\n await this.sendWithRetry(body, 0);\n }\n\n private async sendWithRetry(body: string, attempt: number): Promise<void> {\n try {\n const response = await fetch(this.options.endpoint, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n Authorization: `Bearer ${this.options.apiKey}`,\n },\n body,\n keepalive: true,\n });\n\n if (response.ok) return;\n\n // Retry on 5xx\n if (response.status >= 500 && attempt < this.maxRetries) {\n const delay = computeBackoffMs(attempt, 1000, 30_000);\n await new Promise((r) => setTimeout(r, delay));\n await this.sendWithRetry(body, attempt + 1);\n }\n } catch {\n // Network failure — retry with backoff\n if (attempt < this.maxRetries) {\n const delay = computeBackoffMs(attempt, 1000, 30_000);\n await new Promise((r) => setTimeout(r, delay));\n await this.sendWithRetry(body, attempt + 1);\n }\n // All retries exhausted — silently drop to prevent host app instability\n }\n }\n}\n","/**\n * @fileoverview Global error and unhandled rejection listeners.\n * Attaches window-level hooks without interfering with native browser behavior.\n * @module @nexus/sdk-tracker/listeners\n */\n\nimport type { NexusTrackerClient } from './client.js';\n\n/**\n * Attaches global error event listeners to the browser window.\n * Captures:\n * - `window.onerror` — synchronous uncaught exceptions\n * - `window.onunhandledrejection` — unhandled Promise rejections\n *\n * CRITICAL: Neither handler calls `event.preventDefault()`.\n * Native browser behavior (console.error, DevTools display) is preserved.\n *\n * @param client - The NexusTrackerClient instance to forward errors to.\n * @returns Cleanup function that removes all attached listeners.\n *\n * @example\n * const cleanup = attachGlobalListeners(trackerClient);\n * // On SDK destroy:\n * cleanup();\n */\nexport function attachGlobalListeners(client: NexusTrackerClient): () => void {\n if (typeof window === 'undefined') {\n return () => void 0; // No-op in SSR contexts\n }\n\n const errorHandler = (event: ErrorEvent): void => {\n // Do NOT call event.preventDefault() — preserve native browser behavior\n const error = event.error instanceof Error ? event.error : new Error(event.message);\n client.captureError(error);\n };\n\n const rejectionHandler = (event: PromiseRejectionEvent): void => {\n // Do NOT call event.preventDefault()\n client.captureError(event.reason);\n };\n\n window.addEventListener('error', errorHandler);\n window.addEventListener('unhandledrejection', rejectionHandler);\n\n return () => {\n window.removeEventListener('error', errorHandler);\n window.removeEventListener('unhandledrejection', rejectionHandler);\n };\n}\n","/**\n * @fileoverview NexusTrackerClient — Full-featured crash ingestion and telemetry SDK.\n * Automated global error capture, PII sanitization, deduplication, and hybrid transport.\n * @module @nexus/sdk-tracker/client\n */\n\nimport type { Breadcrumb, DeviceContext, ErrorEventPayload, UserContext } from '@nexussdk/contracts';\nimport { resolveApiKey, resolveBaseUrl } from '@nexussdk/core';\nimport { sanitizeObject } from './sanitizer.js';\nimport { computeFingerprint, parseStackTrace } from './fingerprint.js';\nimport { BreadcrumbManager } from './breadcrumbs.js';\nimport { Transport } from './transport.js';\nimport { attachGlobalListeners } from './listeners.js';\n\n/**\n * Options for initializing the NexusTrackerClient.\n *\n * @example\n * const tracker = new NexusTrackerClient({\n * apiKey: 'pk_live_...',\n * environment: 'production',\n * autoCapture: true,\n * maxBreadcrumbs: 20,\n * });\n */\nexport interface NexusTrackerOptions {\n /**\n * Public API Key ('pk_live_...' or 'pk_test_...').\n * If omitted, resolved automatically via env variables.\n */\n apiKey?: string;\n /**\n * Centralized Go-Gin Ingestion URL. Defaults to 'https://api.nexus.dev'.\n */\n baseUrl?: string;\n /**\n * Target deployment environment ('production' | 'staging' | 'development').\n */\n environment?: string;\n /**\n * Max breadcrumbs retained in ring buffer. Defaults to 20 (max 50).\n */\n maxBreadcrumbs?: number;\n /**\n * Global tags attached to every captured telemetry event.\n */\n tags?: Record<string, string>;\n /**\n * Toggle automated capturing of uncaught exceptions. Defaults to true.\n */\n autoCapture?: boolean;\n /**\n * Callback hook to inspect, mutate, or drop an event before dispatch.\n * Return null to drop the event completely.\n */\n beforeSend?: (event: ErrorEventPayload) => ErrorEventPayload | null;\n}\n\n/** Internal deduplication entry. */\ninterface DedupeEntry {\n timer: ReturnType<typeof setTimeout>;\n count: number;\n lastPayload: ErrorEventPayload;\n}\n\n/**\n * Public interface contract for NexusTrackerClient.\n */\nexport interface INexusTrackerClient {\n /**\n * Manually captures an exception or custom error instance.\n *\n * @param error - Error object, string message, or unknown rejection value.\n * @param extra - Optional custom metadata tags.\n *\n * @example\n * tracker.captureError(new TypeError('Payment failed'), { checkoutStep: 'payment' });\n */\n captureError(error: unknown, extra?: Record<string, unknown>): void;\n\n /**\n * Records a user activity step into the chronological breadcrumb ring buffer.\n *\n * @param breadcrumb - Breadcrumb data without timestamp (auto-injected).\n *\n * @example\n * tracker.addBreadcrumb({ category: 'navigation', message: 'Navigated to /checkout', level: 'info' });\n */\n addBreadcrumb(breadcrumb: Omit<Breadcrumb, 'timestamp'>): void;\n\n /**\n * Attaches end-user context to subsequent error payloads.\n *\n * @param user - User context or null to clear.\n *\n * @example\n * tracker.setUser({ id: 'usr_12345', email: 'john@example.com' });\n */\n setUser(user: UserContext | null): void;\n\n /**\n * Dynamically sets or updates a persistent search tag.\n *\n * @param key - Tag key name.\n * @param value - Tag value string.\n *\n * @example\n * tracker.setTag('app_version', '2.4.1');\n */\n setTag(key: string, value: string): void;\n\n /**\n * Flushes any buffered events immediately via navigator.sendBeacon or fetch.\n *\n * @returns Promise that resolves when all events are dispatched.\n *\n * @example\n * await tracker.flush();\n */\n flush(): Promise<void>;\n\n /**\n * Detaches global window listeners and clears in-memory ring buffers.\n *\n * @example\n * tracker.destroy();\n */\n destroy(): void;\n}\n\n/**\n * NexusTrackerClient — resilient browser crash ingestion agent.\n *\n * @implements {INexusTrackerClient}\n *\n * @example\n * const tracker = new NexusTrackerClient({ apiKey: 'pk_live_...' });\n * tracker.captureError(new Error('Checkout failed'));\n */\nexport class NexusTrackerClient implements INexusTrackerClient {\n private readonly apiKey: string;\n private readonly environment: string;\n private readonly tags: Record<string, string>;\n private readonly beforeSend?: (event: ErrorEventPayload) => ErrorEventPayload | null;\n private readonly breadcrumbManager: BreadcrumbManager;\n private readonly transport: Transport;\n private userContext?: UserContext;\n private readonly dedupeMap = new Map<string, DedupeEntry>();\n private cleanupListeners?: () => void;\n\n constructor(options: NexusTrackerOptions = {}) {\n this.apiKey = resolveApiKey(options.apiKey);\n const baseUrl = resolveBaseUrl(options.baseUrl);\n this.environment = options.environment ?? 'production';\n this.tags = { ...options.tags };\n this.beforeSend = options.beforeSend;\n\n this.breadcrumbManager = new BreadcrumbManager(Math.min(options.maxBreadcrumbs ?? 20, 50));\n this.transport = new Transport({\n endpoint: `${baseUrl}/api/v1/telemetry/errors`,\n apiKey: this.apiKey,\n });\n\n if (options.autoCapture !== false && typeof window !== 'undefined') {\n this.breadcrumbManager.attachListeners();\n this.cleanupListeners = attachGlobalListeners(this);\n }\n }\n\n /**\n * Captures an error exception, generates fingerprint, scrubs PII, and enqueues transmission.\n */\n public captureError(error: unknown, extra?: Record<string, unknown>): void {\n const normalized = this.normalizeError(error);\n const stackFrames = parseStackTrace(normalized.stack);\n const fingerprint = computeFingerprint(normalized.type, normalized.message, stackFrames[0]);\n\n // Client-side 10-second sliding window deduplication\n const existing = this.dedupeMap.get(fingerprint);\n if (existing) {\n existing.count += 1;\n return;\n }\n\n const payload: ErrorEventPayload = {\n fingerprint,\n errorType: normalized.type,\n errorMessage: normalized.message,\n stackTrace: stackFrames,\n breadcrumbs: this.breadcrumbManager.getAll(),\n userContext: this.userContext,\n deviceContext: this.getDeviceContext(),\n tags: { environment: this.environment, ...this.tags, ...(extra as Record<string, string> | undefined) },\n occurrenceCount: 1,\n clientTimestamp: Date.now(),\n };\n\n // Apply PII sanitization before transmission\n const sanitized = sanitizeObject(payload) as ErrorEventPayload;\n const finalPayload = this.beforeSend ? this.beforeSend(sanitized) : sanitized;\n if (!finalPayload) return;\n\n this.transport.send(finalPayload);\n\n // Track duplicate window — send aggregated event after 10 seconds\n const timer = setTimeout(() => {\n const entry = this.dedupeMap.get(fingerprint);\n if (entry && entry.count > 1) {\n const aggregated = { ...entry.lastPayload, occurrenceCount: entry.count, clientTimestamp: Date.now() };\n this.transport.send(aggregated);\n }\n this.dedupeMap.delete(fingerprint);\n }, 10_000);\n\n this.dedupeMap.set(fingerprint, { timer, count: 1, lastPayload: finalPayload });\n }\n\n /**\n * Records a contextual breadcrumb in the ring buffer.\n */\n public addBreadcrumb(breadcrumb: Omit<Breadcrumb, 'timestamp'>): void {\n this.breadcrumbManager.push(breadcrumb);\n }\n\n /**\n * Sets or clears the active user context for error telemetry.\n */\n public setUser(user: UserContext | null): void {\n this.userContext = user ?? undefined;\n }\n\n /**\n * Sets a custom tag associated with error events.\n */\n public setTag(key: string, value: string): void {\n this.tags[key] = value;\n }\n\n /**\n * Flushes all pending deduplication queues and dispatches queued payloads immediately.\n */\n public async flush(): Promise<void> {\n // Flush all pending deduplication timers immediately\n for (const [fingerprint, entry] of this.dedupeMap.entries()) {\n clearTimeout(entry.timer);\n if (entry.count > 0) {\n const aggregated = { ...entry.lastPayload, occurrenceCount: entry.count, clientTimestamp: Date.now() };\n await this.transport.flush(aggregated);\n }\n this.dedupeMap.delete(fingerprint);\n }\n }\n\n /**\n * Tears down global event listeners, flushes queues, and releases resources.\n */\n public destroy(): void {\n this.cleanupListeners?.();\n this.breadcrumbManager.detachListeners();\n this.breadcrumbManager.clear();\n this.dedupeMap.forEach((entry) => clearTimeout(entry.timer));\n this.dedupeMap.clear();\n }\n\n private normalizeError(err: unknown): { type: string; message: string; stack?: string } {\n if (err instanceof Error) {\n return { type: err.name || 'Error', message: err.message, stack: err.stack };\n }\n if (typeof err === 'string') {\n return { type: 'UnhandledException', message: err };\n }\n // Unknown rejection value (non-Error thrown)\n return { type: 'NonErrorRejection', message: String(err) };\n }\n\n private getDeviceContext(): DeviceContext {\n const isBrowser = typeof window !== 'undefined' && typeof navigator !== 'undefined';\n return {\n userAgent: isBrowser ? navigator.userAgent : 'Node/SSR',\n currentUrl: isBrowser ? window.location.href : '',\n viewport: isBrowser ? `${window.innerWidth}x${window.innerHeight}` : undefined,\n timezone: Intl?.DateTimeFormat()?.resolvedOptions()?.timeZone,\n networkStatus:\n isBrowser && 'connection' in navigator\n ? ((navigator as { connection?: { effectiveType?: string } }).connection?.effectiveType ?? 'unknown')\n : undefined,\n };\n }\n}\n"]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../../core/src/http-client.ts","../../core/src/ring-buffer.ts","../../core/src/env-resolver.ts","../../core/src/safe-json.ts","../src/sanitizer.ts","../src/fingerprint.ts","../src/breadcrumbs.ts","../src/transport.ts","../src/listeners.ts","../src/client.ts"],"names":["computeBackoffMs","attempt","baseMs","maxMs","exponential","capped","RingBuffer","capacity","item","result","i","lastIndex","readProcessEnv","key","proc","readViteEnv","meta","dynamicMeta","readWindowGlobal","resolveApiKey","directValue","resolved","resolveBaseUrl","defaultUrl","sanitizeForSerialization","value","seen","depth","maxDepth","propValue","safeStringify","sanitized","SENSITIVE_KEY_PATTERN","CREDIT_CARD_PATTERN","EMAIL_PATTERN","SENSITIVE_QUERY_PARAM_PATTERN","sanitizeString","sanitizeObject","val","sanitizeUrl","url","parseStackTrace","stack","frames","lines","line","trimmed","v8Match","geckoMatch","computeFingerprint","errorType","errorMessage","topFrame","fileName","lineNumber","signature","hash","BreadcrumbManager","maxBreadcrumbs","breadcrumb","sanitizedData","evt","target","description","el","parts","original","args","Transport","options","payload","body","I","blob","response","delay","h","r","attachGlobalListeners","client","errorHandler","event","error","rejectionHandler","NexusTrackerClient","N","baseUrl","U","extra","normalized","stackFrames","fingerprint","existing","finalPayload","timer","entry","aggregated","user","err","isBrowser"],"mappings":"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","file":"index.global.js","sourcesContent":["/**\n * @fileoverview Resilient HTTP client with exponential backoff and jitter.\n * Zero external dependencies — uses only native browser fetch and AbortController.\n * @module @nexus/core/http-client\n */\n\n/**\n * Options for configuring a single HTTP request with retry behaviour.\n *\n * @example\n * const opts: HttpClientOptions = {\n * url: 'https://api.nexus.dev/api/v1/flags/eval',\n * method: 'GET',\n * headers: { Authorization: 'Bearer pk_live_...' },\n * timeoutMs: 3000,\n * maxRetries: 3,\n * };\n */\nexport interface HttpClientOptions {\n /** Request URL. */\n url: string;\n /** HTTP method. Defaults to 'GET'. */\n method?: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';\n /** Request headers. */\n headers?: Record<string, string>;\n /** Request body (will be JSON-serialized if object). */\n body?: unknown;\n /** Request timeout in milliseconds. Defaults to 5000ms. */\n timeoutMs?: number;\n /** Maximum retry attempts on 5xx / network errors. Defaults to 3. */\n maxRetries?: number;\n /** Base delay in milliseconds for exponential backoff. Defaults to 1000ms. */\n retryBaseMs?: number;\n /** Maximum backoff delay in milliseconds. Defaults to 30000ms. */\n retryMaxMs?: number;\n /** Optional AbortSignal for external cancellation. */\n signal?: AbortSignal;\n}\n\n/**\n * Result of a successful HTTP fetch.\n *\n * @template T The expected response body type.\n */\nexport interface HttpClientResult<T> {\n /** Parsed response body. */\n data: T;\n /** HTTP status code. */\n status: number;\n /** Response headers. */\n headers: Headers;\n}\n\n/**\n * Computes the exponential backoff sleep duration with full random jitter.\n * Formula: sleep = random(0, min(maxMs, baseMs * 2^attempt))\n *\n * @param attempt - Zero-based retry attempt index.\n * @param baseMs - Base delay in milliseconds.\n * @param maxMs - Maximum delay cap in milliseconds.\n * @returns Sleep duration in milliseconds.\n *\n * @example\n * const delay = computeBackoffMs(2, 1000, 30000); // ~0-4000ms\n */\nexport function computeBackoffMs(attempt: number, baseMs = 1000, maxMs = 30_000): number {\n const exponential = baseMs * Math.pow(2, attempt);\n const capped = Math.min(maxMs, exponential);\n // Full jitter: random value in [0, capped]\n return Math.random() * capped;\n}\n\n/**\n * Sleeps for the given number of milliseconds.\n *\n * @param ms - Delay in milliseconds.\n * @param signal - Optional AbortSignal to cancel the sleep.\n * @returns Promise that resolves after delay, or rejects if aborted.\n */\nfunction sleep(ms: number, signal?: AbortSignal): Promise<void> {\n return new Promise((resolve, reject) => {\n if (signal?.aborted) {\n reject(new DOMException('Aborted', 'AbortError'));\n return;\n }\n const timer = setTimeout(resolve, ms);\n signal?.addEventListener('abort', () => {\n clearTimeout(timer);\n reject(new DOMException('Aborted', 'AbortError'));\n });\n });\n}\n\n/**\n * Determines if an HTTP response status warrants a retry attempt.\n *\n * @param status - HTTP status code.\n * @returns `true` if the request should be retried.\n */\nfunction isRetryableStatus(status: number): boolean {\n return status >= 500 || status === 429;\n}\n\n/**\n * Performs a resilient HTTP fetch with exponential backoff and jitter.\n * Automatically retries on network failures and 5xx/429 responses.\n *\n * @template T The expected response body type.\n * @param options - Request configuration options.\n * @returns Promise resolving to {@link HttpClientResult}.\n * @throws {Error} When all retry attempts are exhausted or request is aborted.\n *\n * @example\n * const result = await fetchWithRetry<BatchFlagEvaluation>({\n * url: 'https://api.nexus.dev/api/v1/flags/eval',\n * method: 'GET',\n * headers: { Authorization: 'Bearer pk_live_...' },\n * timeoutMs: 3000,\n * maxRetries: 3,\n * });\n * console.log(result.data); // { checkout_v2: { enabled: true, ... } }\n */\nexport async function fetchWithRetry<T = unknown>(\n options: HttpClientOptions,\n): Promise<HttpClientResult<T>> {\n const {\n url,\n method = 'GET',\n headers = {},\n body,\n timeoutMs = 5_000,\n maxRetries = 3,\n retryBaseMs = 1_000,\n retryMaxMs = 30_000,\n signal: externalSignal,\n } = options;\n\n let lastError: Error = new Error('Request failed');\n\n for (let attempt = 0; attempt <= maxRetries; attempt++) {\n // Abort if external signal is already triggered\n if (externalSignal?.aborted) {\n throw new DOMException('Request aborted by caller.', 'AbortError');\n }\n\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), timeoutMs);\n\n // Merge external abort signal\n externalSignal?.addEventListener('abort', () => controller.abort());\n\n try {\n const requestInit: RequestInit = {\n method,\n headers: {\n 'Content-Type': 'application/json',\n ...headers,\n },\n signal: controller.signal,\n };\n\n if (body !== undefined) {\n requestInit.body = typeof body === 'string' ? body : JSON.stringify(body);\n }\n\n const response = await fetch(url, requestInit);\n clearTimeout(timeoutId);\n\n if (response.ok) {\n const data = (await response.json()) as T;\n return { data, status: response.status, headers: response.headers };\n }\n\n // Non-ok response: check if retryable\n if (isRetryableStatus(response.status) && attempt < maxRetries) {\n lastError = new Error(`HTTP ${response.status}: ${response.statusText}`);\n const delay = computeBackoffMs(attempt, retryBaseMs, retryMaxMs);\n await sleep(delay, externalSignal);\n continue;\n }\n\n // Non-retryable error (400, 401, 403, 404, etc.)\n const errorBody = await response.text().catch(() => '');\n throw new Error(`HTTP ${response.status}: ${errorBody}`);\n } catch (err) {\n clearTimeout(timeoutId);\n\n if (err instanceof DOMException && err.name === 'AbortError') {\n throw err; // Propagate abort without retry\n }\n\n lastError = err instanceof Error ? err : new Error(String(err));\n\n if (attempt < maxRetries) {\n const delay = computeBackoffMs(attempt, retryBaseMs, retryMaxMs);\n await sleep(delay, externalSignal);\n }\n }\n }\n\n throw lastError;\n}\n","/**\n * @fileoverview Fixed-capacity ring buffer with FIFO eviction under pressure.\n * Used by SDK Tracker for bounded breadcrumb and telemetry event storage.\n * @module @nexus/core/ring-buffer\n */\n\n/**\n * A fixed-capacity circular buffer that drops the oldest entry when full.\n * Never causes memory leaks or starvation of host applications.\n *\n * @template T The type of items stored in the buffer.\n *\n * @example\n * const buffer = new RingBuffer<string>(3);\n * buffer.push('a'); // [a]\n * buffer.push('b'); // [a, b]\n * buffer.push('c'); // [a, b, c]\n * buffer.push('d'); // [b, c, d] — 'a' evicted (FIFO)\n * buffer.toArray(); // ['b', 'c', 'd']\n */\nexport class RingBuffer<T> {\n private readonly capacity: number;\n private readonly buffer: Array<T | undefined>;\n private head = 0; // Points to the next write position\n private count = 0; // Current number of items\n\n /**\n * Creates a new RingBuffer with the given capacity.\n *\n * @param capacity - Maximum number of items to retain. Must be >= 1.\n * @throws {RangeError} If capacity is less than 1.\n *\n * @example\n * const breadcrumbBuffer = new RingBuffer<Breadcrumb>(20);\n */\n constructor(capacity: number) {\n if (capacity < 1) {\n throw new RangeError(`RingBuffer capacity must be >= 1, got ${capacity}`);\n }\n this.capacity = capacity;\n this.buffer = new Array<T | undefined>(capacity).fill(undefined);\n }\n\n /**\n * Appends an item to the buffer.\n * If the buffer is at capacity, the oldest item is silently dropped.\n *\n * @param item - The item to insert.\n *\n * @example\n * buffer.push({ timestamp: Date.now(), category: 'ui.click', message: 'Clicked #btn' });\n */\n push(item: T): void {\n this.buffer[this.head] = item;\n this.head = (this.head + 1) % this.capacity;\n if (this.count < this.capacity) {\n this.count++;\n }\n }\n\n /**\n * Returns all stored items in chronological order (oldest first).\n *\n * @returns Ordered array of stored items.\n *\n * @example\n * const breadcrumbs = buffer.toArray(); // [{...}, {...}]\n */\n toArray(): T[] {\n if (this.count === 0) return [];\n\n const result: T[] = [];\n if (this.count < this.capacity) {\n // Buffer not yet full — read from index 0 to head-1\n for (let i = 0; i < this.count; i++) {\n result.push(this.buffer[i] as T);\n }\n } else {\n // Buffer full — oldest item is at `head`\n for (let i = 0; i < this.capacity; i++) {\n result.push(this.buffer[(this.head + i) % this.capacity] as T);\n }\n }\n return result;\n }\n\n /**\n * Returns the current number of items in the buffer.\n *\n * @returns Item count (0 to capacity).\n *\n * @example\n * console.log(buffer.size); // 3\n */\n get size(): number {\n return this.count;\n }\n\n /**\n * Returns the maximum capacity of the buffer.\n *\n * @returns Buffer capacity.\n *\n * @example\n * console.log(buffer.maxCapacity); // 20\n */\n get maxCapacity(): number {\n return this.capacity;\n }\n\n /**\n * Checks if the buffer is currently at full capacity.\n *\n * @returns `true` if the buffer is full.\n *\n * @example\n * if (buffer.isFull) console.log('Oldest breadcrumb will be evicted on next push.');\n */\n get isFull(): boolean {\n return this.count === this.capacity;\n }\n\n /**\n * Removes all items from the buffer and resets internal state.\n *\n * @example\n * buffer.clear(); // buffer is now empty\n */\n clear(): void {\n this.buffer.fill(undefined);\n this.head = 0;\n this.count = 0;\n }\n\n /**\n * Peeks at the most recently added item without removing it.\n *\n * @returns The last item pushed, or `undefined` if empty.\n *\n * @example\n * const last = buffer.peek(); // most recent item\n */\n peek(): T | undefined {\n if (this.count === 0) return undefined;\n const lastIndex = (this.head - 1 + this.capacity) % this.capacity;\n return this.buffer[lastIndex];\n }\n}\n","/**\n * @fileoverview Universal API key resolver across all JavaScript runtimes.\n * Supports: Browser globals, Node.js process.env, Vite, Next.js, and Nuxt.\n * @module @nexus/core/env-resolver\n */\n\n/**\n * Resolution priority for API key lookup.\n * 1. Direct parameter passed into init options\n * 2. Browser global: window.__NEXUS_API_KEY__\n * 3. Node/Next.js: process.env.NEXUS_API_KEY\n * 4. Next.js client bundler: process.env.NEXT_PUBLIC_NEXUS_API_KEY\n * 5. Vite bundler: import.meta.env.VITE_NEXUS_API_KEY\n * 6. Nuxt: process.env.NUXT_PUBLIC_NEXUS_API_KEY\n */\n\n// Augment global types for browser injection\ndeclare global {\n interface Window {\n /** Browser-injected API key for Nexus SDK. */\n __NEXUS_API_KEY__?: string;\n }\n}\n\n/**\n * Safely reads an environment variable from process.env without throwing.\n *\n * @param key - The environment variable name.\n * @returns The value or `undefined`.\n */\nfunction readProcessEnv(key: string): string | undefined {\n try {\n const proc = (globalThis as unknown as { process?: { env?: Record<string, string | undefined> } }).process;\n if (typeof proc !== 'undefined' && proc && proc.env) {\n return proc.env[key] ?? undefined;\n }\n } catch {\n // process is not defined in pure browser environments\n }\n return undefined;\n}\n\n/**\n * Safely reads a Vite environment variable from import.meta.env.\n *\n * @param key - The Vite env variable name (VITE_* prefix required by Vite).\n * @returns The value or `undefined`.\n */\nfunction readViteEnv(key: string): string | undefined {\n try {\n const meta = typeof globalThis !== 'undefined' && (globalThis as unknown as { __import_meta__?: { env?: Record<string, string | undefined> } }).__import_meta__;\n if (meta && meta.env) {\n return meta.env[key] ?? undefined;\n }\n // Safe dynamic evaluation avoiding CJS compile-time import.meta error\n const dynamicMeta = new Function('try { return import.meta; } catch(e) { return undefined; }')() as { env?: Record<string, string | undefined> } | undefined;\n if (dynamicMeta && dynamicMeta.env) {\n return dynamicMeta.env[key] ?? undefined;\n }\n } catch {\n // Not a Vite runtime\n }\n return undefined;\n}\n\n/**\n * Safely reads the browser window global injection.\n *\n * @returns The window-injected API key or `undefined`.\n */\nfunction readWindowGlobal(): string | undefined {\n try {\n if (typeof window !== 'undefined' && window.__NEXUS_API_KEY__) {\n return window.__NEXUS_API_KEY__;\n }\n } catch {\n // window is not accessible (SSR/Worker context)\n }\n return undefined;\n}\n\n/**\n * Resolves the Nexus API key from the environment using a prioritized lookup chain.\n *\n * Resolution order:\n * 1. `directValue` — Parameter passed directly into SDK init options\n * 2. `window.__NEXUS_API_KEY__` — Browser global injection (CDN/script embed use cases)\n * 3. `process.env.NEXUS_API_KEY` — Node.js / Docker / CI environments\n * 4. `process.env.NEXT_PUBLIC_NEXUS_API_KEY` — Next.js client-side bundling\n * 5. `import.meta.env.VITE_NEXUS_API_KEY` — Vite / Vitest bundling\n * 6. `process.env.NUXT_PUBLIC_NEXUS_API_KEY` — Nuxt 3 public runtime config\n *\n * @param directValue - Explicitly provided API key (highest priority).\n * @returns The resolved API key string.\n * @throws {Error} If no API key can be resolved from any source.\n *\n * @example\n * // In a Next.js application:\n * // process.env.NEXT_PUBLIC_NEXUS_API_KEY = 'pk_live_abc123'\n * const key = resolveApiKey(); // 'pk_live_abc123'\n *\n * @example\n * // Passing directly (overrides all env vars):\n * const key = resolveApiKey('pk_live_directkey'); // 'pk_live_directkey'\n */\nexport function resolveApiKey(directValue?: string): string {\n const resolved =\n directValue ||\n readWindowGlobal() ||\n readProcessEnv('NEXUS_API_KEY') ||\n readProcessEnv('NEXT_PUBLIC_NEXUS_API_KEY') ||\n readViteEnv('VITE_NEXUS_API_KEY') ||\n readProcessEnv('NUXT_PUBLIC_NEXUS_API_KEY');\n\n if (!resolved || resolved.trim() === '') {\n throw new Error(\n '[Nexus SDK] No API key found. ' +\n 'Please provide one via:\\n' +\n ' 1. Nexus.init({ apiKey: \"pk_live_...\" })\\n' +\n ' 2. window.__NEXUS_API_KEY__ = \"pk_live_...\"\\n' +\n ' 3. NEXUS_API_KEY env var\\n' +\n ' 4. NEXT_PUBLIC_NEXUS_API_KEY (Next.js)\\n' +\n ' 5. VITE_NEXUS_API_KEY (Vite)\\n' +\n ' 6. NUXT_PUBLIC_NEXUS_API_KEY (Nuxt)',\n );\n }\n\n return resolved.trim();\n}\n\n/**\n * Resolves the Nexus base URL from configuration or environment variables.\n *\n * @param directValue - Explicitly provided base URL.\n * @param defaultUrl - Default URL if no env var is found.\n * @returns The resolved base URL string (trailing slash stripped).\n *\n * @example\n * // Resolves to http://localhost:8080 in development\n * const url = resolveBaseUrl(undefined, 'https://api.nexus.dev');\n */\nexport function resolveBaseUrl(\n directValue?: string,\n defaultUrl = 'https://api.nexus.dev',\n): string {\n const resolved =\n directValue ||\n readProcessEnv('NEXT_PUBLIC_NEXUS_URL') ||\n readViteEnv('VITE_NEXUS_URL') ||\n defaultUrl;\n\n return resolved.replace(/\\/$/, '');\n}\n","/**\n * @fileoverview Circular-reference-safe JSON serializer using WeakSet tracking.\n * Prevents TypeError crashes when serializing objects with circular references.\n * @module @nexus/core/safe-json\n */\n\n/**\n * Recursively sanitizes an object for JSON serialization by replacing\n * circular references with the string \"[Circular]\".\n *\n * @param value - The value to sanitize.\n * @param seen - WeakSet tracking visited objects (used internally for recursion).\n * @param depth - Current recursion depth.\n * @param maxDepth - Maximum allowed recursion depth.\n * @returns A serialization-safe copy of the value.\n */\nfunction sanitizeForSerialization(\n value: unknown,\n seen: WeakSet<object>,\n depth: number,\n maxDepth: number,\n): unknown {\n if (depth > maxDepth) {\n return '[MaxDepthExceeded]';\n }\n\n if (value === null || value === undefined) {\n return value;\n }\n\n if (typeof value !== 'object' && typeof value !== 'function') {\n // Primitive value: string, number, boolean, bigint, symbol\n if (typeof value === 'bigint') {\n return value.toString(); // JSON cannot handle BigInt natively\n }\n if (typeof value === 'symbol') {\n return value.toString();\n }\n if (typeof value === 'function') {\n return '[Function]';\n }\n return value;\n }\n\n // Handle Error objects specially — preserve message and stack\n if (value instanceof Error) {\n return {\n name: value.name,\n message: value.message,\n stack: value.stack,\n };\n }\n\n // Circular reference detection\n if (seen.has(value as object)) {\n return '[Circular]';\n }\n seen.add(value as object);\n\n // Handle Arrays\n if (Array.isArray(value)) {\n const result = value.map((item) =>\n sanitizeForSerialization(item, seen, depth + 1, maxDepth),\n );\n seen.delete(value as object);\n return result;\n }\n\n // Handle plain Objects\n const result: Record<string, unknown> = {};\n for (const key of Object.keys(value as object)) {\n const propValue = (value as Record<string, unknown>)[key];\n result[key] = sanitizeForSerialization(propValue, seen, depth + 1, maxDepth);\n }\n seen.delete(value as object);\n return result;\n}\n\n/**\n * Serializes a value to a JSON string, gracefully handling:\n * - Circular references (replaced with \"[Circular]\")\n * - BigInt values (converted to string)\n * - Symbol values (converted to string)\n * - Function values (replaced with \"[Function]\")\n * - Deep nesting (capped at maxDepth, replaced with \"[MaxDepthExceeded]\")\n *\n * @param value - Any value to serialize.\n * @param maxDepth - Maximum recursion depth. Defaults to 8.\n * @returns JSON string representation of the value.\n *\n * @example\n * const obj: Record<string, unknown> = { name: 'test' };\n * obj['self'] = obj; // circular reference\n * const json = safeStringify(obj);\n * // '{\"name\":\"test\",\"self\":\"[Circular]\"}'\n *\n * @example\n * const err = new Error('Something failed');\n * const json = safeStringify({ error: err, code: 500 });\n * // '{\"error\":{\"name\":\"Error\",\"message\":\"Something failed\",\"stack\":\"...\"},\"code\":500}'\n */\nexport function safeStringify(value: unknown, maxDepth = 8): string {\n const seen = new WeakSet<object>();\n const sanitized = sanitizeForSerialization(value, seen, 0, maxDepth);\n try {\n return JSON.stringify(sanitized);\n } catch {\n // Absolute last resort fallback\n return JSON.stringify({ error: '[SerializationFailed]' });\n }\n}\n\n/**\n * Safely parses a JSON string without throwing on invalid input.\n *\n * @param input - The JSON string to parse.\n * @param fallback - Value to return if parsing fails. Defaults to `null`.\n * @returns Parsed value or fallback.\n *\n * @example\n * const data = safeParse<{ id: string }>('{\"id\":\"123\"}');\n * // { id: '123' }\n *\n * @example\n * const data = safeParse<unknown>('{{invalid json}}', null);\n * // null\n */\nexport function safeParse<T = unknown>(input: string, fallback: T | null = null): T | null {\n try {\n return JSON.parse(input) as T;\n } catch {\n return fallback;\n }\n}\n","/**\n * @fileoverview PII sanitization engine for safe error payload transmission.\n * Recursively redacts sensitive fields before any data leaves the client device.\n * @module @nexus/sdk-tracker/sanitizer\n */\n\n/** Maximum object recursion depth to prevent stack overflows on deep structures. */\nconst MAX_DEPTH = 5;\n\n/**\n * Regex matching sensitive key names that should be redacted.\n * Matches exact key names (case-insensitive) for password, token, secret, etc.\n */\nconst SENSITIVE_KEY_PATTERN =\n /^(password|passwd|token|secret|authorization|bearer|auth|credit_?card|cvv|cvc|ssn|api_?key|access_?token|refresh_?token)$/i;\n\n/**\n * Regex matching credit card number patterns (13-16 digit sequences).\n * Covers common formats with spaces or dashes between groups.\n */\nconst CREDIT_CARD_PATTERN = /\\b(?:\\d[ -]*?){13,16}\\b/g;\n\n/**\n * Regex matching email addresses in string values.\n * Only the domain portion is retained for limited diagnostic context.\n */\nconst EMAIL_PATTERN = /[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}/g;\n\n/**\n * Regex matching URL query parameters containing sensitive tokens.\n * Strips values for: token, auth, key, secret, password, api_key.\n */\nconst SENSITIVE_QUERY_PARAM_PATTERN =\n /([?&](token|auth|key|secret|password|api_key|access_token|refresh_token)=)[^&]*/gi;\n\n/**\n * Sanitizes a string value by removing credit card numbers and email addresses.\n *\n * @param value - The string to sanitize.\n * @returns Sanitized string with sensitive patterns replaced.\n */\nfunction sanitizeString(value: string): string {\n return value\n .replace(CREDIT_CARD_PATTERN, '[CARD_REDACTED]')\n .replace(EMAIL_PATTERN, '[EMAIL_REDACTED]')\n .replace(SENSITIVE_QUERY_PARAM_PATTERN, '$1[REDACTED]');\n}\n\n/**\n * Recursively sanitizes an object, redacting sensitive key-value pairs.\n * Handles nested objects, arrays, and string values.\n *\n * @param value - The value to sanitize (any type).\n * @param depth - Current recursion depth (internal, starts at 0).\n * @returns A sanitized deep copy of the input.\n *\n * @example\n * const payload = {\n * user: { email: 'john@example.com', password: 'secret123' },\n * token: 'Bearer abc123',\n * creditCard: '4111 1111 1111 1111',\n * };\n * const safe = sanitizeObject(payload);\n * // { user: { email: '[EMAIL_REDACTED]', password: '[REDACTED]' },\n * // token: '[REDACTED]', creditCard: '[REDACTED]' }\n */\nexport function sanitizeObject(value: unknown, depth = 0): unknown {\n if (depth > MAX_DEPTH) return '[MaxDepthExceeded]';\n\n if (value === null || value === undefined) return value;\n\n if (typeof value === 'string') {\n return sanitizeString(value);\n }\n\n if (typeof value !== 'object') {\n return value; // number, boolean, etc.\n }\n\n if (Array.isArray(value)) {\n return value.map((item) => sanitizeObject(item, depth + 1));\n }\n\n const sanitized: Record<string, unknown> = {};\n for (const [key, val] of Object.entries(value as Record<string, unknown>)) {\n if (SENSITIVE_KEY_PATTERN.test(key)) {\n sanitized[key] = '[REDACTED]';\n } else {\n sanitized[key] = sanitizeObject(val, depth + 1);\n }\n }\n return sanitized;\n}\n\n/**\n * Sanitizes a URL by stripping sensitive query parameters.\n *\n * @param url - URL string to sanitize.\n * @returns URL with sensitive query values replaced with [REDACTED].\n *\n * @example\n * sanitizeUrl('https://app.com/auth?token=abc123&redirect=/home');\n * // 'https://app.com/auth?token=[REDACTED]&redirect=/home'\n */\nexport function sanitizeUrl(url: string): string {\n return url.replace(SENSITIVE_QUERY_PARAM_PATTERN, '$1[REDACTED]');\n}\n","/**\n * @fileoverview Error fingerprinting and stack trace parsing utilities.\n * Generates deterministic SHA-256-based fingerprints for error deduplication.\n * @module @nexus/sdk-tracker/fingerprint\n */\n\nimport type { StackFrame } from '@nexussdk/contracts';\n\n/**\n * Parses a JavaScript error stack string into structured StackFrame objects.\n * Supports V8 (Chrome/Node), SpiderMonkey (Firefox), and JavaScriptCore (Safari) formats.\n *\n * @param stack - Raw stack trace string from an Error object.\n * @returns Array of parsed {@link StackFrame} objects (innermost first).\n *\n * @example\n * const frames = parseStackTrace(new TypeError('test').stack);\n * // [{ functionName: 'processPayment', fileName: 'chunk.js', lineNumber: 1, columnNumber: 400 }]\n */\nexport function parseStackTrace(stack?: string): StackFrame[] {\n if (!stack) return [];\n\n const frames: StackFrame[] = [];\n const lines = stack.split('\\n');\n\n for (const line of lines) {\n const trimmed = line.trim();\n\n // V8 format: \" at FunctionName (file.js:line:col)\"\n // V8 anonymous: \" at file.js:line:col\"\n const v8Match =\n trimmed.match(/^at\\s+(.+?)\\s+\\((.+?):(\\d+):(\\d+)\\)$/) ||\n trimmed.match(/^at\\s+(.+?):(\\d+):(\\d+)$/) ||\n trimmed.match(/^at\\s+\\((.+?):(\\d+):(\\d+)\\)$/);\n\n if (v8Match) {\n if (v8Match.length === 5) {\n // Named function\n frames.push({\n functionName: v8Match[1] ?? '<anonymous>',\n fileName: v8Match[2] ?? '<unknown>',\n lineNumber: parseInt(v8Match[3] ?? '0', 10),\n columnNumber: parseInt(v8Match[4] ?? '0', 10),\n });\n } else if (v8Match.length === 4) {\n // Anonymous or \"at (file:line:col)\"\n frames.push({\n functionName: '<anonymous>',\n fileName: v8Match[1] ?? '<unknown>',\n lineNumber: parseInt(v8Match[2] ?? '0', 10),\n columnNumber: parseInt(v8Match[3] ?? '0', 10),\n });\n }\n continue;\n }\n\n // Firefox/Safari format: \"functionName@file.js:line:col\"\n const geckoMatch = trimmed.match(/^(.+?)@(.+?):(\\d+):(\\d+)$/);\n if (geckoMatch) {\n frames.push({\n functionName: geckoMatch[1] ?? '<anonymous>',\n fileName: geckoMatch[2] ?? '<unknown>',\n lineNumber: parseInt(geckoMatch[3] ?? '0', 10),\n columnNumber: parseInt(geckoMatch[4] ?? '0', 10),\n });\n }\n }\n\n return frames;\n}\n\n/**\n * Computes a fast non-cryptographic fingerprint string for error deduplication.\n * Uses a djb2-style hash over the signature components to avoid SubtleCrypto async API.\n *\n * Format: hash(errorType + \":\" + errorMessage + \":\" + topFileName + \":\" + topLineNumber)\n *\n * @param errorType - JavaScript error type (e.g. \"TypeError\").\n * @param errorMessage - Primary error message.\n * @param topFrame - Innermost (first) stack frame, or undefined if stack is empty.\n * @returns Hex-like fingerprint string for deduplication grouping.\n *\n * @example\n * const fp = computeFingerprint(\n * 'TypeError',\n * \"Cannot read properties of undefined (reading 'map')\",\n * { functionName: 'render', fileName: 'chunk.js', lineNumber: 1, columnNumber: 400 }\n * );\n * // 'fp_a3f9b2c1d4...'\n */\nexport function computeFingerprint(\n errorType: string,\n errorMessage: string,\n topFrame?: StackFrame,\n): string {\n const fileName = topFrame?.fileName ?? 'unknown';\n const lineNumber = topFrame?.lineNumber ?? 0;\n const signature = `${errorType}:${errorMessage}:${fileName}:${lineNumber}`;\n\n // djb2 hash algorithm — fast, no async, no external deps\n let hash = 5381;\n for (let i = 0; i < signature.length; i++) {\n hash = ((hash << 5) + hash + signature.charCodeAt(i)) >>> 0;\n }\n\n return `fp_${hash.toString(16).padStart(8, '0')}`;\n}\n","/**\n * @fileoverview Breadcrumb ring buffer manager for recording user activity trails.\n * Automatically captures DOM clicks, navigation events, and console calls.\n * @module @nexus/sdk-tracker/breadcrumbs\n */\n\nimport type { Breadcrumb, BreadcrumbCategory } from '@nexussdk/contracts';\nimport { RingBuffer } from '@nexussdk/core';\nimport { sanitizeObject, sanitizeUrl } from './sanitizer.js';\n\n/**\n * Manages the breadcrumb ring buffer and attaches automatic DOM/navigation listeners.\n *\n * @example\n * const manager = new BreadcrumbManager(20);\n * manager.attachListeners();\n * manager.push({ category: 'custom', message: 'User entered checkout flow', level: 'info' });\n * const trail = manager.getAll(); // Array of last 20 breadcrumbs\n */\nexport class BreadcrumbManager {\n private readonly buffer: RingBuffer<Breadcrumb>;\n private listenersAttached = false;\n private readonly clickHandler: (evt: MouseEvent) => void;\n private readonly popStateHandler: () => void;\n\n constructor(maxBreadcrumbs: number) {\n this.buffer = new RingBuffer<Breadcrumb>(maxBreadcrumbs);\n\n // Bind handlers once to enable proper removeEventListener\n this.clickHandler = this.handleClick.bind(this);\n this.popStateHandler = this.handleNavigation.bind(this);\n }\n\n /**\n * Adds a breadcrumb to the ring buffer, sanitizing any PII in the data.\n *\n * @param breadcrumb - Breadcrumb data (timestamp will be auto-injected).\n *\n * @example\n * manager.push({ category: 'http', message: 'POST /api/checkout', level: 'info', data: { status: 200 } });\n */\n push(breadcrumb: Omit<Breadcrumb, 'timestamp'>): void {\n const sanitizedData = breadcrumb.data\n ? (sanitizeObject(breadcrumb.data) as Record<string, unknown>)\n : undefined;\n\n this.buffer.push({\n ...breadcrumb,\n data: sanitizedData,\n timestamp: Date.now(),\n });\n }\n\n /**\n * Returns all breadcrumbs in chronological order (oldest first).\n *\n * @returns Ordered array of breadcrumbs for inclusion in error payloads.\n */\n getAll(): Breadcrumb[] {\n return this.buffer.toArray();\n }\n\n /**\n * Clears all stored breadcrumbs.\n */\n clear(): void {\n this.buffer.clear();\n }\n\n /**\n * Attaches automatic event listeners for DOM clicks and navigation changes.\n * Safe to call multiple times — idempotent.\n *\n * @example\n * manager.attachListeners(); // called during SDK init\n */\n attachListeners(): void {\n if (this.listenersAttached || typeof window === 'undefined') return;\n this.listenersAttached = true;\n\n // DOM click breadcrumbs\n document.addEventListener('click', this.clickHandler, { capture: true, passive: true });\n\n // Browser navigation breadcrumbs (popstate = back/forward)\n window.addEventListener('popstate', this.popStateHandler, { passive: true });\n\n // Intercept console.error for breadcrumb recording\n this.interceptConsoleError();\n }\n\n /**\n * Removes all attached event listeners.\n *\n * @example\n * manager.detachListeners(); // called during SDK destroy\n */\n detachListeners(): void {\n if (!this.listenersAttached || typeof window === 'undefined') return;\n document.removeEventListener('click', this.clickHandler, { capture: true });\n window.removeEventListener('popstate', this.popStateHandler);\n this.listenersAttached = false;\n }\n\n private handleClick(evt: MouseEvent): void {\n const target = evt.target as HTMLElement | null;\n if (!target) return;\n\n // Mask password fields and data-nexus-mask elements\n if (\n target instanceof HTMLInputElement &&\n (target.type === 'password' || target.hasAttribute('data-nexus-mask'))\n ) {\n return; // Skip masked fields entirely\n }\n\n const description = this.describeElement(target);\n this.push({\n category: 'ui.click' as BreadcrumbCategory,\n message: `Clicked ${description}`,\n level: 'info',\n data: {\n elementTag: target.tagName.toLowerCase(),\n elementId: target.id || undefined,\n elementClass: target.className || undefined,\n },\n });\n }\n\n private handleNavigation(): void {\n this.push({\n category: 'navigation' as BreadcrumbCategory,\n message: `Navigated to ${sanitizeUrl(window.location.href)}`,\n level: 'info',\n data: { url: sanitizeUrl(window.location.href) },\n });\n }\n\n private describeElement(el: HTMLElement): string {\n const parts: string[] = [el.tagName.toLowerCase()];\n if (el.id) parts.push(`#${el.id}`);\n if (el.getAttribute('aria-label')) parts.push(`[aria-label=\"${el.getAttribute('aria-label')}\"]`);\n return parts.join('');\n }\n\n private interceptConsoleError(): void {\n const original = console.error.bind(console);\n console.error = (...args: unknown[]) => {\n this.push({\n category: 'console' as BreadcrumbCategory,\n message: args.map(String).join(' ').substring(0, 500),\n level: 'error',\n });\n original(...args);\n };\n }\n}\n","/**\n * @fileoverview Hybrid transport dispatcher for error payload delivery.\n * Prefers fetch with keepalive; falls back to navigator.sendBeacon during page unload.\n * @module @nexus/sdk-tracker/transport\n */\n\nimport type { ErrorEventPayload } from '@nexussdk/contracts';\nimport { safeStringify, computeBackoffMs } from '@nexussdk/core';\n\n/**\n * Options for configuring the transport dispatcher.\n */\nexport interface TransportOptions {\n /** Go-Gin ingestion endpoint URL. */\n endpoint: string;\n /** Public API key for Authorization header. */\n apiKey: string;\n /** Maximum retry attempts on network failures. Defaults to 2. */\n maxRetries?: number;\n}\n\n/**\n * Hybrid transport dispatcher that intelligently selects the delivery mechanism:\n * - **Normal execution**: `fetch(url, { keepalive: true })` with retry\n * - **Page teardown** (`visibilityState === 'hidden'` or `pagehide`): `navigator.sendBeacon`\n *\n * @example\n * const transport = new Transport({\n * endpoint: 'http://localhost:8080/api/v1/telemetry/errors',\n * apiKey: 'pk_live_...',\n * });\n * transport.send(errorPayload);\n */\nexport class Transport {\n private readonly options: TransportOptions;\n private readonly maxRetries: number;\n private isPageHiding = false;\n\n constructor(options: TransportOptions) {\n this.options = options;\n this.maxRetries = options.maxRetries ?? 2;\n\n // Detect page teardown events to switch to sendBeacon\n if (typeof document !== 'undefined') {\n document.addEventListener('visibilitychange', () => {\n if (document.visibilityState === 'hidden') {\n this.isPageHiding = true;\n }\n });\n }\n if (typeof window !== 'undefined') {\n window.addEventListener('pagehide', () => {\n this.isPageHiding = true;\n });\n }\n }\n\n /**\n * Dispatches an error payload to the Go-Gin ingestion endpoint.\n * Automatically selects fetch or sendBeacon based on page lifecycle state.\n *\n * @param payload - The sanitized {@link ErrorEventPayload} to transmit.\n *\n * @example\n * transport.send({\n * fingerprint: 'fp_abc123',\n * errorType: 'TypeError',\n * errorMessage: \"Cannot read property 'map' of undefined\",\n * // ...\n * });\n */\n send(payload: ErrorEventPayload): void {\n const body = safeStringify(payload);\n\n // Use sendBeacon during page teardown — prevents cancelled fetch requests\n if (\n this.isPageHiding &&\n typeof navigator !== 'undefined' &&\n typeof navigator.sendBeacon === 'function'\n ) {\n const blob = new Blob([body], { type: 'application/json' });\n navigator.sendBeacon(this.options.endpoint, blob);\n return;\n }\n\n // Normal execution: fetch with keepalive and exponential backoff retry\n void this.sendWithRetry(body, 0);\n }\n\n /**\n * Forces immediate flush of all pending events using sendBeacon.\n * Called during manual flush or SDK destroy lifecycle.\n *\n * @param payload - The error payload to flush.\n * @returns Promise that resolves when the beacon is dispatched.\n */\n async flush(payload: ErrorEventPayload): Promise<void> {\n const body = safeStringify(payload);\n if (typeof navigator !== 'undefined' && typeof navigator.sendBeacon === 'function') {\n const blob = new Blob([body], { type: 'application/json' });\n navigator.sendBeacon(this.options.endpoint, blob);\n return;\n }\n await this.sendWithRetry(body, 0);\n }\n\n private async sendWithRetry(body: string, attempt: number): Promise<void> {\n try {\n const response = await fetch(this.options.endpoint, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n Authorization: `Bearer ${this.options.apiKey}`,\n },\n body,\n keepalive: true,\n });\n\n if (response.ok) return;\n\n // Retry on 5xx\n if (response.status >= 500 && attempt < this.maxRetries) {\n const delay = computeBackoffMs(attempt, 1000, 30_000);\n await new Promise((r) => setTimeout(r, delay));\n await this.sendWithRetry(body, attempt + 1);\n }\n } catch {\n // Network failure — retry with backoff\n if (attempt < this.maxRetries) {\n const delay = computeBackoffMs(attempt, 1000, 30_000);\n await new Promise((r) => setTimeout(r, delay));\n await this.sendWithRetry(body, attempt + 1);\n }\n // All retries exhausted — silently drop to prevent host app instability\n }\n }\n}\n","/**\n * @fileoverview Global error and unhandled rejection listeners.\n * Attaches window-level hooks without interfering with native browser behavior.\n * @module @nexus/sdk-tracker/listeners\n */\n\nimport type { NexusTrackerClient } from './client.js';\n\n/**\n * Attaches global error event listeners to the browser window.\n * Captures:\n * - `window.onerror` — synchronous uncaught exceptions\n * - `window.onunhandledrejection` — unhandled Promise rejections\n *\n * CRITICAL: Neither handler calls `event.preventDefault()`.\n * Native browser behavior (console.error, DevTools display) is preserved.\n *\n * @param client - The NexusTrackerClient instance to forward errors to.\n * @returns Cleanup function that removes all attached listeners.\n *\n * @example\n * const cleanup = attachGlobalListeners(trackerClient);\n * // On SDK destroy:\n * cleanup();\n */\nexport function attachGlobalListeners(client: NexusTrackerClient): () => void {\n if (typeof window === 'undefined') {\n return () => void 0; // No-op in SSR contexts\n }\n\n const errorHandler = (event: ErrorEvent): void => {\n // Do NOT call event.preventDefault() — preserve native browser behavior\n const error = event.error instanceof Error ? event.error : new Error(event.message);\n client.captureError(error);\n };\n\n const rejectionHandler = (event: PromiseRejectionEvent): void => {\n // Do NOT call event.preventDefault()\n client.captureError(event.reason);\n };\n\n window.addEventListener('error', errorHandler);\n window.addEventListener('unhandledrejection', rejectionHandler);\n\n return () => {\n window.removeEventListener('error', errorHandler);\n window.removeEventListener('unhandledrejection', rejectionHandler);\n };\n}\n","/**\n * @fileoverview NexusTrackerClient — Full-featured crash ingestion and telemetry SDK.\n * Automated global error capture, PII sanitization, deduplication, and hybrid transport.\n * @module @nexus/sdk-tracker/client\n */\n\nimport type { Breadcrumb, DeviceContext, ErrorEventPayload, UserContext } from '@nexussdk/contracts';\nimport { resolveApiKey, resolveBaseUrl } from '@nexussdk/core';\nimport { sanitizeObject } from './sanitizer.js';\nimport { computeFingerprint, parseStackTrace } from './fingerprint.js';\nimport { BreadcrumbManager } from './breadcrumbs.js';\nimport { Transport } from './transport.js';\nimport { attachGlobalListeners } from './listeners.js';\n\n/**\n * Options for initializing the NexusTrackerClient.\n *\n * @example\n * const tracker = new NexusTrackerClient({\n * apiKey: 'pk_live_...',\n * environment: 'production',\n * autoCapture: true,\n * maxBreadcrumbs: 20,\n * });\n */\nexport interface NexusTrackerOptions {\n /**\n * Public API Key ('pk_live_...' or 'pk_test_...').\n * If omitted, resolved automatically via env variables.\n */\n apiKey?: string;\n /**\n * Centralized Go-Gin Ingestion URL. Defaults to 'https://api.nexus.dev'.\n */\n baseUrl?: string;\n /**\n * Target deployment environment ('production' | 'staging' | 'development').\n */\n environment?: string;\n /**\n * Max breadcrumbs retained in ring buffer. Defaults to 20 (max 50).\n */\n maxBreadcrumbs?: number;\n /**\n * Global tags attached to every captured telemetry event.\n */\n tags?: Record<string, string>;\n /**\n * Toggle automated capturing of uncaught exceptions. Defaults to true.\n */\n autoCapture?: boolean;\n /**\n * Callback hook to inspect, mutate, or drop an event before dispatch.\n * Return null to drop the event completely.\n */\n beforeSend?: (event: ErrorEventPayload) => ErrorEventPayload | null;\n}\n\n/** Internal deduplication entry. */\ninterface DedupeEntry {\n timer: ReturnType<typeof setTimeout>;\n count: number;\n lastPayload: ErrorEventPayload;\n}\n\n/**\n * Public interface contract for NexusTrackerClient.\n */\nexport interface INexusTrackerClient {\n /**\n * Manually captures an exception or custom error instance.\n *\n * @param error - Error object, string message, or unknown rejection value.\n * @param extra - Optional custom metadata tags.\n *\n * @example\n * tracker.captureError(new TypeError('Payment failed'), { checkoutStep: 'payment' });\n */\n captureError(error: unknown, extra?: Record<string, unknown>): void;\n\n /**\n * Records a user activity step into the chronological breadcrumb ring buffer.\n *\n * @param breadcrumb - Breadcrumb data without timestamp (auto-injected).\n *\n * @example\n * tracker.addBreadcrumb({ category: 'navigation', message: 'Navigated to /checkout', level: 'info' });\n */\n addBreadcrumb(breadcrumb: Omit<Breadcrumb, 'timestamp'>): void;\n\n /**\n * Attaches end-user context to subsequent error payloads.\n *\n * @param user - User context or null to clear.\n *\n * @example\n * tracker.setUser({ id: 'usr_12345', email: 'john@example.com' });\n */\n setUser(user: UserContext | null): void;\n\n /**\n * Dynamically sets or updates a persistent search tag.\n *\n * @param key - Tag key name.\n * @param value - Tag value string.\n *\n * @example\n * tracker.setTag('app_version', '2.4.1');\n */\n setTag(key: string, value: string): void;\n\n /**\n * Flushes any buffered events immediately via navigator.sendBeacon or fetch.\n *\n * @returns Promise that resolves when all events are dispatched.\n *\n * @example\n * await tracker.flush();\n */\n flush(): Promise<void>;\n\n /**\n * Detaches global window listeners and clears in-memory ring buffers.\n *\n * @example\n * tracker.destroy();\n */\n destroy(): void;\n}\n\n/**\n * NexusTrackerClient — resilient browser crash ingestion agent.\n *\n * @implements {INexusTrackerClient}\n *\n * @example\n * const tracker = new NexusTrackerClient({ apiKey: 'pk_live_...' });\n * tracker.captureError(new Error('Checkout failed'));\n */\nexport class NexusTrackerClient implements INexusTrackerClient {\n private readonly apiKey: string;\n private readonly environment: string;\n private readonly tags: Record<string, string>;\n private readonly beforeSend?: (event: ErrorEventPayload) => ErrorEventPayload | null;\n private readonly breadcrumbManager: BreadcrumbManager;\n private readonly transport: Transport;\n private userContext?: UserContext;\n private readonly dedupeMap = new Map<string, DedupeEntry>();\n private cleanupListeners?: () => void;\n\n constructor(options: NexusTrackerOptions = {}) {\n this.apiKey = resolveApiKey(options.apiKey);\n const baseUrl = resolveBaseUrl(options.baseUrl);\n this.environment = options.environment ?? 'production';\n this.tags = { ...options.tags };\n this.beforeSend = options.beforeSend;\n\n this.breadcrumbManager = new BreadcrumbManager(Math.min(options.maxBreadcrumbs ?? 20, 50));\n this.transport = new Transport({\n endpoint: `${baseUrl}/api/v1/telemetry/errors`,\n apiKey: this.apiKey,\n });\n\n if (options.autoCapture !== false && typeof window !== 'undefined') {\n this.breadcrumbManager.attachListeners();\n this.cleanupListeners = attachGlobalListeners(this);\n }\n }\n\n /**\n * Captures an error exception, generates fingerprint, scrubs PII, and enqueues transmission.\n */\n public captureError(error: unknown, extra?: Record<string, unknown>): void {\n const normalized = this.normalizeError(error);\n const stackFrames = parseStackTrace(normalized.stack);\n const fingerprint = computeFingerprint(normalized.type, normalized.message, stackFrames[0]);\n\n // Client-side 10-second sliding window deduplication\n const existing = this.dedupeMap.get(fingerprint);\n if (existing) {\n existing.count += 1;\n return;\n }\n\n const payload: ErrorEventPayload = {\n fingerprint,\n errorType: normalized.type,\n errorMessage: normalized.message,\n stackTrace: stackFrames,\n breadcrumbs: this.breadcrumbManager.getAll(),\n userContext: this.userContext,\n deviceContext: this.getDeviceContext(),\n tags: { environment: this.environment, ...this.tags, ...(extra as Record<string, string> | undefined) },\n occurrenceCount: 1,\n clientTimestamp: Date.now(),\n };\n\n // Apply PII sanitization before transmission\n const sanitized = sanitizeObject(payload) as ErrorEventPayload;\n const finalPayload = this.beforeSend ? this.beforeSend(sanitized) : sanitized;\n if (!finalPayload) return;\n\n this.transport.send(finalPayload);\n\n // Track duplicate window — send aggregated event after 10 seconds\n const timer = setTimeout(() => {\n const entry = this.dedupeMap.get(fingerprint);\n if (entry && entry.count > 1) {\n const aggregated = { ...entry.lastPayload, occurrenceCount: entry.count, clientTimestamp: Date.now() };\n this.transport.send(aggregated);\n }\n this.dedupeMap.delete(fingerprint);\n }, 10_000);\n\n this.dedupeMap.set(fingerprint, { timer, count: 1, lastPayload: finalPayload });\n }\n\n /**\n * Records a contextual breadcrumb in the ring buffer.\n */\n public addBreadcrumb(breadcrumb: Omit<Breadcrumb, 'timestamp'>): void {\n this.breadcrumbManager.push(breadcrumb);\n }\n\n /**\n * Sets or clears the active user context for error telemetry.\n */\n public setUser(user: UserContext | null): void {\n this.userContext = user ?? undefined;\n }\n\n /**\n * Sets a custom tag associated with error events.\n */\n public setTag(key: string, value: string): void {\n this.tags[key] = value;\n }\n\n /**\n * Flushes all pending deduplication queues and dispatches queued payloads immediately.\n */\n public async flush(): Promise<void> {\n // Flush all pending deduplication timers immediately\n for (const [fingerprint, entry] of this.dedupeMap.entries()) {\n clearTimeout(entry.timer);\n if (entry.count > 0) {\n const aggregated = { ...entry.lastPayload, occurrenceCount: entry.count, clientTimestamp: Date.now() };\n await this.transport.flush(aggregated);\n }\n this.dedupeMap.delete(fingerprint);\n }\n }\n\n /**\n * Tears down global event listeners, flushes queues, and releases resources.\n */\n public destroy(): void {\n this.cleanupListeners?.();\n this.breadcrumbManager.detachListeners();\n this.breadcrumbManager.clear();\n this.dedupeMap.forEach((entry) => clearTimeout(entry.timer));\n this.dedupeMap.clear();\n }\n\n private normalizeError(err: unknown): { type: string; message: string; stack?: string } {\n if (err instanceof Error) {\n return { type: err.name || 'Error', message: err.message, stack: err.stack };\n }\n if (typeof err === 'string') {\n return { type: 'UnhandledException', message: err };\n }\n // Unknown rejection value (non-Error thrown)\n return { type: 'NonErrorRejection', message: String(err) };\n }\n\n private getDeviceContext(): DeviceContext {\n const isBrowser = typeof window !== 'undefined' && typeof navigator !== 'undefined';\n return {\n userAgent: isBrowser ? navigator.userAgent : 'Node/SSR',\n currentUrl: isBrowser ? window.location.href : '',\n viewport: isBrowser ? `${window.innerWidth}x${window.innerHeight}` : undefined,\n timezone: Intl?.DateTimeFormat()?.resolvedOptions()?.timeZone,\n networkStatus:\n isBrowser && 'connection' in navigator\n ? ((navigator as { connection?: { effectiveType?: string } }).connection?.effectiveType ?? 'unknown')\n : undefined,\n };\n }\n}\n"]}