@upgoose/browser 0.1.3 → 0.2.1

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 CHANGED
@@ -65,12 +65,45 @@ upgoose.setTag('team', 'checkout');
65
65
  | `captureNavigation` | `boolean` | `true` | Log route changes as breadcrumbs |
66
66
  | `captureNetwork` | `boolean` | `true` | Log fetch/XHR as breadcrumbs |
67
67
  | `captureUnhandled` | `boolean` | `true` | Global error + rejection handlers |
68
+ | `captureCSP` | `boolean` | `false` | Capture CSP violations as warning events |
69
+
70
+ ## CSP violation reporting
71
+
72
+ Enable `captureCSP` to capture Content-Security-Policy violations as warning-level events:
73
+
74
+ ```ts
75
+ upgoose.init({
76
+ apiKey: '...',
77
+ apiUrl: '...',
78
+ captureCSP: true,
79
+ });
80
+ ```
81
+
82
+ Each violation is fingerprinted by directive + blocked origin (e.g. `script-src` from `https://evil.com`), so repeated violations from the same source are grouped together instead of flooding your error list.
83
+
84
+ Events include tags for filtering: `csp.directive`, `csp.disposition` (enforce vs report-only), and `csp.blocked_origin`. Violations also appear as breadcrumbs in the trail for subsequent errors.
85
+
86
+ This is opt-in because browser extensions can generate noise. Use `beforeSend` to filter out known false positives:
87
+
88
+ ```ts
89
+ upgoose.init({
90
+ apiKey: '...',
91
+ apiUrl: '...',
92
+ captureCSP: true,
93
+ beforeSend(event) {
94
+ // Ignore violations from browser extensions
95
+ if (event.tags?.['csp.blocked_origin']?.includes('extension://')) return null;
96
+ return event;
97
+ },
98
+ });
99
+ ```
68
100
 
69
101
  ## What gets captured
70
102
 
71
103
  - Unhandled exceptions (`window.onerror`)
72
104
  - Unhandled promise rejections
73
- - Breadcrumbs: clicks, navigation, console, fetch/XHR (with timing)
105
+ - CSP violations (opt-in via `captureCSP`)
106
+ - Breadcrumbs: clicks, navigation, console, fetch/XHR, CSP (with timing)
74
107
  - Browser context: user agent, viewport, language
75
108
  - Page URL (sensitive query params automatically redacted)
76
109
 
package/dist/index.cjs CHANGED
@@ -1,2 +1,2 @@
1
- "use strict";var u=Object.defineProperty;var w=Object.getOwnPropertyDescriptor;var b=Object.getOwnPropertyNames;var k=Object.prototype.hasOwnProperty;var E=(r,e)=>{for(var t in e)u(r,t,{get:e[t],enumerable:!0})},C=(r,e,t,n)=>{if(e&&typeof e=="object"||typeof e=="function")for(let s of b(e))!k.call(r,s)&&s!==t&&u(r,s,{get:()=>e[s],enumerable:!(n=w(e,s))||n.enumerable});return r};var U=r=>C(u({},"__esModule",{value:!0}),r);var T={};E(T,{Upgoose:()=>l,default:()=>H,upgoose:()=>v});module.exports=U(T);var i=[];function d(r,e){r.captureConsole!==!1&&L(e),r.captureClicks!==!1&&R(e),r.captureNavigation!==!1&&x(e),r.captureNetwork!==!1&&(S(e),P(e))}function g(){for(let r of i)try{r()}catch{}i.length=0}function L(r){let e={log:console.log,warn:console.warn,error:console.error};for(let t of["log","warn","error"]){let n=e[t];console[t]=(...s)=>{r({timestamp:Date.now(),category:"console",message:s.map(String).join(" ").slice(0,500),level:t==="error"?"error":t==="warn"?"warning":"info"}),n.apply(console,s)}}i.push(()=>{console.log=e.log,console.warn=e.warn,console.error=e.error})}function R(r){let e=t=>{let n=t.target;if(!n)return;let s=n.tagName?.toLowerCase(),o=n.textContent?.trim().slice(0,100)||"",a=D(n);r({timestamp:Date.now(),category:"ui",message:`click on ${s}${o?`: "${o}"`:""}`,data:{selector:a}})};document.addEventListener("click",e,{capture:!0,passive:!0}),i.push(()=>document.removeEventListener("click",e,{capture:!0}))}function x(r){let e=history.pushState;history.pushState=function(...s){return r({timestamp:Date.now(),category:"navigation",message:String(s[2]||""),data:{from:location.href}}),e.apply(this,s)};let t=history.replaceState;history.replaceState=function(...s){return r({timestamp:Date.now(),category:"navigation",message:String(s[2]||""),data:{from:location.href}}),t.apply(this,s)};let n=()=>{r({timestamp:Date.now(),category:"navigation",message:location.href})};window.addEventListener("popstate",n),i.push(()=>{history.pushState=e,history.replaceState=t,window.removeEventListener("popstate",n)})}function S(r){let e=window.fetch;window.fetch=async function(t,n){let s=typeof t=="string"?t:t instanceof URL?t.href:t.url,o=n?.method||"GET",a=Date.now();if(s.includes("/public/errors"))return e.call(window,t,n);let p=h(s);try{let c=await e.call(window,t,n);return r({timestamp:Date.now(),category:"fetch",message:`${o} ${p}`,level:c.ok?"info":"error",data:{status:c.status,duration:Date.now()-a}}),c}catch(c){throw r({timestamp:Date.now(),category:"fetch",message:`${o} ${p}`,level:"error",data:{error:String(c),duration:Date.now()-a}}),c}},i.push(()=>{window.fetch=e})}function P(r){let e=XMLHttpRequest.prototype.open,t=XMLHttpRequest.prototype.send;XMLHttpRequest.prototype.open=function(n,s){return this.__ug_method=n,this.__ug_url=String(s),e.apply(this,arguments)},XMLHttpRequest.prototype.send=function(){let n=Date.now(),s=this.__ug_method||"GET",o=this.__ug_url||"";if(o.includes("/public/errors"))return t.apply(this,arguments);let a=h(o);return this.addEventListener("loadend",function(){r({timestamp:Date.now(),category:"xhr",message:`${s} ${a}`,level:this.status>=400?"error":"info",data:{status:this.status,duration:Date.now()-n}})}),t.apply(this,arguments)},i.push(()=>{XMLHttpRequest.prototype.open=e,XMLHttpRequest.prototype.send=t})}function h(r){try{let e=new URL(r,location.origin),t=["token","key","secret","password","auth","code","session","jwt","access_token","refresh_token","api_key","apikey"];for(let n of[...e.searchParams.keys()])t.some(s=>n.toLowerCase().includes(s))&&e.searchParams.set(n,"[REDACTED]");return e.hash="",e.toString()}catch{return r}}function D(r){let e=[],t=r;for(let n=0;n<3&&t&&t!==document.body;n++){let s=t.tagName.toLowerCase();if(t.id)s+=`#${t.id}`;else if(t.className&&typeof t.className=="string"){let o=t.className.trim().split(/\s+/).slice(0,2).join(".");o&&(s+=`.${o}`)}e.unshift(s),t=t.parentElement}return e.join(" > ")}var f=typeof window<"u"?window.fetch.bind(window):(()=>{});function m(r,e){let t=`${r.apiUrl}/public/errors`,n=JSON.stringify(e);try{f(t,{method:"POST",headers:{"Content-Type":"application/json","X-API-Key":r.apiKey},body:n,keepalive:!0}).catch(()=>{})}catch{}}function y(r,e){let t=`${r.apiUrl}/public/deploys`,n=JSON.stringify({release:e});try{f(t,{method:"POST",headers:{"Content-Type":"application/json","X-API-Key":r.apiKey},body:n,keepalive:!0}).catch(()=>{})}catch{}}var l=class{constructor(){this.config=null;this.breadcrumbs=[];this.user=null;this.tags={};this.initialized=!1;this.sending=!1;this.onError=e=>{let t=e.error||new Error(e.message||"Unknown error");this.captureException(t)};this.onUnhandledRejection=e=>{let t=e.reason;this.captureException(t)}}init(e){if(this.initialized){console.warn("[Upgoose] Already initialized. Call destroy() first to re-initialize.");return}if(!e.apiKey||!e.apiUrl){console.error("[Upgoose] apiKey and apiUrl are required.");return}this.config={sampleRate:1,maxBreadcrumbs:50,captureConsole:!0,captureClicks:!0,captureNavigation:!0,captureNetwork:!0,captureUnhandled:!0,...e},this.breadcrumbs=[],this.initialized=!0,d(this.config,t=>this.addBreadcrumb(t)),this.config.captureUnhandled&&this.setupGlobalHandlers(),this.config.release&&y({apiKey:this.config.apiKey,apiUrl:this.config.apiUrl},this.config.release)}captureException(e,t){if(!this.config)return;let{message:n,stack:s}=_(e);this.send({message:n,stack:s,level:t?.level||"error",tags:t?.tags,fingerprint:t?.fingerprint})}test(){if(!this.config){console.warn("[Upgoose] Not initialized. Call init() first.");return}this.send({message:"Upgoose test event \u2014 integration verified",level:"warning",tags:{upgoose_test:"true"},fingerprint:"upgoose-test-event"}),console.info("[Upgoose] Test event sent. Check your project page.")}captureMessage(e,t="error"){this.config&&this.send({message:e,level:t})}setUser(e){this.user=e}setTag(e,t){this.tags[e]=t}addBreadcrumb(e){let t=this.config?.maxBreadcrumbs??50;this.breadcrumbs.push(e),this.breadcrumbs.length>t&&(this.breadcrumbs=this.breadcrumbs.slice(-t))}destroy(){g(),this.removeGlobalHandlers(),this.config=null,this.breadcrumbs=[],this.user=null,this.tags={},this.initialized=!1}setupGlobalHandlers(){window.addEventListener("error",this.onError),window.addEventListener("unhandledrejection",this.onUnhandledRejection)}removeGlobalHandlers(){window.removeEventListener("error",this.onError),window.removeEventListener("unhandledrejection",this.onUnhandledRejection)}send(e){let t=this.config;if(t&&!this.sending){this.sending=!0;try{if(typeof t.sampleRate=="number"&&Math.random()>t.sampleRate)return;let n={message:e.message.slice(0,2e3),stack:e.stack?.slice(0,1e4),level:e.level,environment:t.environment,release:t.release,tags:{...this.tags,...e.tags},context:{breadcrumbs:this.breadcrumbs.slice(-30),...this.user?{user:this.user}:{},page:{url:B(location.href),title:document.title},browser:$()},fingerprint:e.fingerprint},s=n;if(t.beforeSend)try{s=t.beforeSend(n)}catch{return}if(!s)return;m({apiKey:t.apiKey,apiUrl:t.apiUrl},s)}finally{this.sending=!1}}}};function _(r){return r instanceof Error?{message:r.message,stack:r.stack}:typeof r=="string"?{message:r}:r&&typeof r=="object"&&"message"in r?{message:String(r.message),stack:"stack"in r?String(r.stack):void 0}:{message:String(r)}}function B(r){try{let e=new URL(r),t=["token","key","secret","password","auth","code","session","jwt","access_token","refresh_token","api_key","apikey"];for(let n of[...e.searchParams.keys()])t.some(s=>n.toLowerCase().includes(s))&&e.searchParams.set(n,"[REDACTED]");return e.hash="",e.toString()}catch{return r}}function $(){let r={};try{r.userAgent=navigator.userAgent,r.language=navigator.language,r.viewport=`${window.innerWidth}x${window.innerHeight}`,r.screen=`${screen.width}x${screen.height}`}catch{}return r}var v=new l,H=v;0&&(module.exports={Upgoose,upgoose});
1
+ "use strict";var p=Object.defineProperty;var b=Object.getOwnPropertyDescriptor;var k=Object.getOwnPropertyNames;var C=Object.prototype.hasOwnProperty;var E=(n,e)=>{for(var t in e)p(n,t,{get:e[t],enumerable:!0})},U=(n,e,t,r)=>{if(e&&typeof e=="object"||typeof e=="function")for(let o of k(e))!C.call(n,o)&&o!==t&&p(n,o,{get:()=>e[o],enumerable:!(r=b(e,o))||r.enumerable});return n};var S=n=>U(p({},"__esModule",{value:!0}),n);var N={};E(N,{Upgoose:()=>l,default:()=>K,upgoose:()=>w});module.exports=S(N);var i=[];function g(n,e){n.captureConsole!==!1&&P(e),n.captureClicks!==!1&&R(e),n.captureNavigation!==!1&&L(e),n.captureNetwork!==!1&&(x(e),D(e)),n.captureCSP&&$(e)}function h(){for(let n of i)try{n()}catch{}i.length=0}function P(n){let e={log:console.log,warn:console.warn,error:console.error};for(let t of["log","warn","error"]){let r=e[t];console[t]=(...o)=>{n({timestamp:Date.now(),category:"console",message:o.map(String).join(" ").slice(0,500),level:t==="error"?"error":t==="warn"?"warning":"info"}),r.apply(console,o)}}i.push(()=>{console.log=e.log,console.warn=e.warn,console.error=e.error})}function R(n){let e=t=>{let r=t.target;if(!r)return;let o=r.tagName?.toLowerCase(),s=r.textContent?.trim().slice(0,100)||"",a=H(r);n({timestamp:Date.now(),category:"ui",message:`click on ${o}${s?`: "${s}"`:""}`,data:{selector:a}})};document.addEventListener("click",e,{capture:!0,passive:!0}),i.push(()=>document.removeEventListener("click",e,{capture:!0}))}function L(n){let e=history.pushState;history.pushState=function(...o){return n({timestamp:Date.now(),category:"navigation",message:String(o[2]||""),data:{from:location.href}}),e.apply(this,o)};let t=history.replaceState;history.replaceState=function(...o){return n({timestamp:Date.now(),category:"navigation",message:String(o[2]||""),data:{from:location.href}}),t.apply(this,o)};let r=()=>{n({timestamp:Date.now(),category:"navigation",message:location.href})};window.addEventListener("popstate",r),i.push(()=>{history.pushState=e,history.replaceState=t,window.removeEventListener("popstate",r)})}function x(n){let e=window.fetch;window.fetch=async function(t,r){let o=typeof t=="string"?t:t instanceof URL?t.href:t.url,s=r?.method||"GET",a=Date.now();if(o.includes("/public/errors"))return e.call(window,t,r);let u=f(o);try{let c=await e.call(window,t,r);return n({timestamp:Date.now(),category:"fetch",message:`${s} ${u}`,level:c.ok?"info":"error",data:{status:c.status,duration:Date.now()-a}}),c}catch(c){throw n({timestamp:Date.now(),category:"fetch",message:`${s} ${u}`,level:"error",data:{error:String(c),duration:Date.now()-a}}),c}},i.push(()=>{window.fetch=e})}function D(n){let e=XMLHttpRequest.prototype.open,t=XMLHttpRequest.prototype.send;XMLHttpRequest.prototype.open=function(r,o){return this.__ug_method=r,this.__ug_url=String(o),e.apply(this,arguments)},XMLHttpRequest.prototype.send=function(){let r=Date.now(),o=this.__ug_method||"GET",s=this.__ug_url||"";if(s.includes("/public/errors"))return t.apply(this,arguments);let a=f(s);return this.addEventListener("loadend",function(){n({timestamp:Date.now(),category:"xhr",message:`${o} ${a}`,level:this.status>=400?"error":"info",data:{status:this.status,duration:Date.now()-r}})}),t.apply(this,arguments)},i.push(()=>{XMLHttpRequest.prototype.open=e,XMLHttpRequest.prototype.send=t})}function f(n){try{let e=new URL(n,location.origin),t=["token","key","secret","password","auth","code","session","jwt","access_token","refresh_token","api_key","apikey"];for(let r of[...e.searchParams.keys()])t.some(o=>r.toLowerCase().includes(o))&&e.searchParams.set(r,"[REDACTED]");return e.hash="",e.toString()}catch{return n}}function $(n){let e=t=>{let r=t.effectiveDirective||t.violatedDirective||"unknown";n({timestamp:Date.now(),category:"csp",message:`${r} blocked ${t.blockedURI||"inline content"}`,level:"warning",data:{blockedURI:t.blockedURI,documentURI:t.documentURI,disposition:t.disposition}})};document.addEventListener("securitypolicyviolation",e),i.push(()=>document.removeEventListener("securitypolicyviolation",e))}function H(n){let e=[],t=n;for(let r=0;r<3&&t&&t!==document.body;r++){let o=t.tagName.toLowerCase();if(t.id)o+=`#${t.id}`;else if(t.className&&typeof t.className=="string"){let s=t.className.trim().split(/\s+/).slice(0,2).join(".");s&&(o+=`.${s}`)}e.unshift(o),t=t.parentElement}return e.join(" > ")}var d=typeof window<"u"?window.fetch.bind(window):(()=>{});function m(n,e){let t=`${n.apiUrl}/public/errors`,r=JSON.stringify(e);try{d(t,{method:"POST",headers:{"Content-Type":"application/json","X-API-Key":n.apiKey},body:r,keepalive:!0}).catch(()=>{})}catch{}}function y(n){let e=`${n.apiUrl}/public/connect`;try{d(e,{method:"POST",headers:{"Content-Type":"application/json","X-API-Key":n.apiKey},body:"{}",keepalive:!0}).catch(()=>{})}catch{}}function v(n,e){let t=`${n.apiUrl}/public/deploys`,r=JSON.stringify({release:e});try{d(t,{method:"POST",headers:{"Content-Type":"application/json","X-API-Key":n.apiKey},body:r,keepalive:!0}).catch(()=>{})}catch{}}var l=class{constructor(){this.config=null;this.breadcrumbs=[];this.user=null;this.tags={};this.initialized=!1;this.sending=!1;this.onError=e=>{let t=e.error||new Error(e.message||"Unknown error");this.captureException(t)};this.onUnhandledRejection=e=>{let t=e.reason;this.captureException(t)};this.onCSPViolation=e=>{let t=e.effectiveDirective||e.violatedDirective||"unknown",r=T(e.blockedURI);this.send({message:`CSP violation: ${t} blocked ${e.blockedURI||"inline content"}`,level:"warning",tags:{"csp.directive":t,"csp.disposition":e.disposition||"enforce","csp.blocked_origin":r},fingerprint:`csp:${t}:${r}`})}}init(e){if(this.initialized){console.warn("[Upgoose] Already initialized. Call destroy() first to re-initialize.");return}if(!e.apiKey||!e.apiUrl){console.error("[Upgoose] apiKey and apiUrl are required.");return}this.config={sampleRate:1,maxBreadcrumbs:50,captureConsole:!0,captureClicks:!0,captureNavigation:!0,captureNetwork:!0,captureUnhandled:!0,...e},this.breadcrumbs=[],this.initialized=!0,g(this.config,r=>this.addBreadcrumb(r)),this.config.captureUnhandled&&this.setupGlobalHandlers(),this.config.captureCSP&&this.setupCSPHandler();let t={apiKey:this.config.apiKey,apiUrl:this.config.apiUrl};y(t),this.config.release&&v(t,this.config.release)}captureException(e,t){if(!this.config)return;let{message:r,stack:o}=_(e);this.send({message:r,stack:o,level:t?.level||"error",tags:t?.tags,fingerprint:t?.fingerprint})}test(){if(!this.config){console.warn("[Upgoose] Not initialized. Call init() first.");return}this.send({message:"Upgoose test event \u2014 integration verified",level:"warning",tags:{upgoose_test:"true"},fingerprint:"upgoose-test-event"}),console.info("[Upgoose] Test event sent. Check your project page.")}captureMessage(e,t="error"){this.config&&this.send({message:e,level:t})}setUser(e){this.user=e}setTag(e,t){this.tags[e]=t}addBreadcrumb(e){let t=this.config?.maxBreadcrumbs??50;this.breadcrumbs.push(e),this.breadcrumbs.length>t&&(this.breadcrumbs=this.breadcrumbs.slice(-t))}destroy(){h(),this.removeGlobalHandlers(),this.removeCSPHandler(),this.config=null,this.breadcrumbs=[],this.user=null,this.tags={},this.initialized=!1}setupGlobalHandlers(){window.addEventListener("error",this.onError),window.addEventListener("unhandledrejection",this.onUnhandledRejection)}removeGlobalHandlers(){window.removeEventListener("error",this.onError),window.removeEventListener("unhandledrejection",this.onUnhandledRejection)}setupCSPHandler(){document.addEventListener("securitypolicyviolation",this.onCSPViolation)}removeCSPHandler(){document.removeEventListener("securitypolicyviolation",this.onCSPViolation)}send(e){let t=this.config;if(t&&!this.sending){this.sending=!0;try{if(typeof t.sampleRate=="number"&&Math.random()>t.sampleRate)return;let r={message:e.message.slice(0,2e3),stack:e.stack?.slice(0,1e4),level:e.level,environment:t.environment,release:t.release,tags:{...this.tags,...e.tags},context:{breadcrumbs:this.breadcrumbs.slice(-30),...this.user?{user:this.user}:{},page:{url:B(location.href),title:document.title},browser:j()},fingerprint:e.fingerprint},o=r;if(t.beforeSend)try{o=t.beforeSend(r)}catch{return}if(!o)return;m({apiKey:t.apiKey,apiUrl:t.apiUrl},o)}finally{this.sending=!1}}}};function _(n){return n instanceof Error?{message:n.message,stack:n.stack}:typeof n=="string"?{message:n}:n&&typeof n=="object"&&"message"in n?{message:String(n.message),stack:"stack"in n?String(n.stack):void 0}:{message:String(n)}}function B(n){try{let e=new URL(n),t=["token","key","secret","password","auth","code","session","jwt","access_token","refresh_token","api_key","apikey"];for(let r of[...e.searchParams.keys()])t.some(o=>r.toLowerCase().includes(o))&&e.searchParams.set(r,"[REDACTED]");return e.hash="",e.toString()}catch{return n}}function T(n){if(!n)return"inline";if(n==="eval"||n==="inline"||n==="data"||n==="blob")return n;try{return new URL(n).origin}catch{return n.slice(0,100)}}function j(){let n={};try{n.userAgent=navigator.userAgent,n.language=navigator.language,n.viewport=`${window.innerWidth}x${window.innerHeight}`,n.screen=`${screen.width}x${screen.height}`}catch{}return n}var w=new l,K=w;0&&(module.exports={Upgoose,upgoose});
2
2
  //# sourceMappingURL=index.cjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/breadcrumbs.ts","../src/transport.ts"],"sourcesContent":["import type {\n UpgooseConfig,\n Breadcrumb,\n UserContext,\n ErrorPayload,\n} from \"./types\";\nimport { setupBreadcrumbs, teardownBreadcrumbs } from \"./breadcrumbs\";\nimport { sendEvent, sendDeploy } from \"./transport\";\n\nexport type {\n UpgooseConfig,\n Breadcrumb,\n UserContext,\n ErrorPayload,\n};\n\nclass Upgoose {\n private config: UpgooseConfig | null = null;\n private breadcrumbs: Breadcrumb[] = [];\n private user: UserContext | null = null;\n private tags: Record<string, string> = {};\n private initialized = false;\n private sending = false; // re-entrancy guard\n\n /**\n * Initialize the Upgoose SDK. Call this once, as early as possible.\n *\n * ```ts\n * import upgoose from '@upgoose/browser';\n * upgoose.init({ apiKey: 'ug_...', apiUrl: 'https://api.upgoose.app' });\n * ```\n */\n init(config: UpgooseConfig): void {\n if (this.initialized) {\n console.warn(\"[Upgoose] Already initialized. Call destroy() first to re-initialize.\");\n return;\n }\n\n if (!config.apiKey || !config.apiUrl) {\n console.error(\"[Upgoose] apiKey and apiUrl are required.\");\n return;\n }\n\n this.config = {\n sampleRate: 1,\n maxBreadcrumbs: 50,\n captureConsole: true,\n captureClicks: true,\n captureNavigation: true,\n captureNetwork: true,\n captureUnhandled: true,\n ...config,\n };\n\n this.breadcrumbs = [];\n this.initialized = true;\n\n // Set up breadcrumb collection\n setupBreadcrumbs(this.config, (crumb) => this.addBreadcrumb(crumb));\n\n // Set up global error handlers\n if (this.config.captureUnhandled) {\n this.setupGlobalHandlers();\n }\n\n // Notify server of this release so it can auto-resolve older error groups\n if (this.config.release) {\n sendDeploy(\n { apiKey: this.config.apiKey, apiUrl: this.config.apiUrl },\n this.config.release\n );\n }\n }\n\n /**\n * Capture an exception manually.\n *\n * ```ts\n * try {\n * riskyOperation();\n * } catch (err) {\n * upgoose.captureException(err);\n * }\n * ```\n */\n captureException(\n error: unknown,\n extra?: { tags?: Record<string, string>; fingerprint?: string; level?: ErrorPayload[\"level\"] }\n ): void {\n if (!this.config) return;\n\n const { message, stack } = normalizeError(error);\n this.send({\n message,\n stack,\n level: extra?.level || \"error\",\n tags: extra?.tags,\n fingerprint: extra?.fingerprint,\n });\n }\n\n /**\n * Send a test event to verify your integration is working.\n * Check your project page — the status should flip to \"CONNECTED\" within seconds.\n *\n * ```ts\n * upgoose.test();\n * ```\n */\n test(): void {\n if (!this.config) {\n console.warn(\"[Upgoose] Not initialized. Call init() first.\");\n return;\n }\n this.send({\n message: \"Upgoose test event — integration verified\",\n level: \"warning\",\n tags: { upgoose_test: \"true\" },\n fingerprint: \"upgoose-test-event\",\n });\n console.info(\"[Upgoose] Test event sent. Check your project page.\");\n }\n\n /**\n * Capture a message as an error event.\n *\n * ```ts\n * upgoose.captureMessage(\"User hit rate limit\", \"warning\");\n * ```\n */\n captureMessage(\n message: string,\n level: ErrorPayload[\"level\"] = \"error\"\n ): void {\n if (!this.config) return;\n this.send({ message, level });\n }\n\n /**\n * Set user context. Attached to all subsequent events.\n *\n * ```ts\n * upgoose.setUser({ id: '123', email: 'user@example.com' });\n * ```\n */\n setUser(user: UserContext | null): void {\n this.user = user;\n }\n\n /**\n * Set a global tag. Attached to all subsequent events.\n */\n setTag(key: string, value: string): void {\n this.tags[key] = value;\n }\n\n /**\n * Add a breadcrumb manually.\n */\n addBreadcrumb(crumb: Breadcrumb): void {\n const max = this.config?.maxBreadcrumbs ?? 50;\n this.breadcrumbs.push(crumb);\n if (this.breadcrumbs.length > max) {\n this.breadcrumbs = this.breadcrumbs.slice(-max);\n }\n }\n\n /**\n * Tear down the SDK — removes global handlers and breadcrumb instrumentation.\n */\n destroy(): void {\n teardownBreadcrumbs();\n this.removeGlobalHandlers();\n this.config = null;\n this.breadcrumbs = [];\n this.user = null;\n this.tags = {};\n this.initialized = false;\n }\n\n // -- Private --\n\n private onError = (event: ErrorEvent): void => {\n const error = event.error || new Error(event.message || \"Unknown error\");\n this.captureException(error);\n };\n\n private onUnhandledRejection = (event: PromiseRejectionEvent): void => {\n const error = event.reason;\n this.captureException(error);\n };\n\n private setupGlobalHandlers(): void {\n window.addEventListener(\"error\", this.onError);\n window.addEventListener(\"unhandledrejection\", this.onUnhandledRejection);\n }\n\n private removeGlobalHandlers(): void {\n window.removeEventListener(\"error\", this.onError);\n window.removeEventListener(\"unhandledrejection\", this.onUnhandledRejection);\n }\n\n private send(partial: {\n message: string;\n stack?: string;\n level: ErrorPayload[\"level\"];\n tags?: Record<string, string>;\n fingerprint?: string;\n }): void {\n const config = this.config;\n if (!config) return;\n\n // Re-entrancy guard: prevent infinite loops if send() itself throws\n // or if beforeSend throws and triggers the global error handler\n if (this.sending) return;\n this.sending = true;\n\n try {\n // Sample rate check\n if (typeof config.sampleRate === \"number\" && Math.random() > config.sampleRate) {\n return;\n }\n\n const payload: ErrorPayload = {\n message: partial.message.slice(0, 2000),\n stack: partial.stack?.slice(0, 10000),\n level: partial.level,\n environment: config.environment,\n release: config.release,\n tags: { ...this.tags, ...partial.tags },\n context: {\n breadcrumbs: this.breadcrumbs.slice(-30),\n ...(this.user ? { user: this.user } : {}),\n page: {\n url: stripUrlSensitiveParams(location.href),\n title: document.title,\n },\n browser: getBrowserContext(),\n },\n fingerprint: partial.fingerprint,\n };\n\n // beforeSend hook (wrapped to prevent throws from looping)\n let processed: ErrorPayload | null = payload;\n if (config.beforeSend) {\n try {\n processed = config.beforeSend(payload);\n } catch {\n // Drop event if beforeSend throws\n return;\n }\n }\n if (!processed) return;\n\n sendEvent(\n { apiKey: config.apiKey, apiUrl: config.apiUrl },\n processed\n );\n } finally {\n this.sending = false;\n }\n }\n}\n\nfunction normalizeError(error: unknown): { message: string; stack?: string } {\n if (error instanceof Error) {\n return { message: error.message, stack: error.stack };\n }\n if (typeof error === \"string\") {\n return { message: error };\n }\n if (error && typeof error === \"object\" && \"message\" in error) {\n return {\n message: String((error as { message: unknown }).message),\n stack: \"stack\" in error ? String((error as { stack: unknown }).stack) : undefined,\n };\n }\n return { message: String(error) };\n}\n\n/** Strip common sensitive query/hash params from URLs */\nfunction stripUrlSensitiveParams(url: string): string {\n try {\n const u = new URL(url);\n const sensitive = [\"token\", \"key\", \"secret\", \"password\", \"auth\", \"code\", \"session\", \"jwt\", \"access_token\", \"refresh_token\", \"api_key\", \"apikey\"];\n for (const param of [...u.searchParams.keys()]) {\n if (sensitive.some((s) => param.toLowerCase().includes(s))) {\n u.searchParams.set(param, \"[REDACTED]\");\n }\n }\n // Strip hash (often contains tokens in SPAs)\n u.hash = \"\";\n return u.toString();\n } catch {\n return url;\n }\n}\n\nfunction getBrowserContext(): Record<string, string> {\n const ctx: Record<string, string> = {};\n try {\n ctx.userAgent = navigator.userAgent;\n ctx.language = navigator.language;\n ctx.viewport = `${window.innerWidth}x${window.innerHeight}`;\n ctx.screen = `${screen.width}x${screen.height}`;\n } catch {\n // Ignore\n }\n return ctx;\n}\n\n// Singleton instance\nconst upgoose = new Upgoose();\nexport default upgoose;\nexport { upgoose, Upgoose };\n","import type { Breadcrumb, UpgooseConfig } from \"./types\";\n\ntype BreadcrumbCallback = (crumb: Breadcrumb) => void;\n\nconst teardowns: Array<() => void> = [];\n\nexport function setupBreadcrumbs(\n config: UpgooseConfig,\n addBreadcrumb: BreadcrumbCallback\n): void {\n if (config.captureConsole !== false) {\n setupConsole(addBreadcrumb);\n }\n if (config.captureClicks !== false) {\n setupClicks(addBreadcrumb);\n }\n if (config.captureNavigation !== false) {\n setupNavigation(addBreadcrumb);\n }\n if (config.captureNetwork !== false) {\n setupFetch(addBreadcrumb);\n setupXHR(addBreadcrumb);\n }\n}\n\nexport function teardownBreadcrumbs(): void {\n for (const fn of teardowns) {\n try {\n fn();\n } catch {\n // Best effort cleanup\n }\n }\n teardowns.length = 0;\n}\n\nfunction setupConsole(add: BreadcrumbCallback): void {\n const original = {\n log: console.log,\n warn: console.warn,\n error: console.error,\n };\n\n for (const level of [\"log\", \"warn\", \"error\"] as const) {\n const orig = original[level];\n console[level] = (...args: unknown[]) => {\n add({\n timestamp: Date.now(),\n category: \"console\",\n message: args.map(String).join(\" \").slice(0, 500),\n level: level === \"error\" ? \"error\" : level === \"warn\" ? \"warning\" : \"info\",\n });\n orig.apply(console, args);\n };\n }\n\n teardowns.push(() => {\n console.log = original.log;\n console.warn = original.warn;\n console.error = original.error;\n });\n}\n\nfunction setupClicks(add: BreadcrumbCallback): void {\n const handler = (event: MouseEvent) => {\n const target = event.target as HTMLElement | null;\n if (!target) return;\n\n const tag = target.tagName?.toLowerCase();\n const text = target.textContent?.trim().slice(0, 100) || \"\";\n const selector = getSelector(target);\n\n add({\n timestamp: Date.now(),\n category: \"ui\",\n message: `click on ${tag}${text ? `: \"${text}\"` : \"\"}`,\n data: { selector },\n });\n };\n\n document.addEventListener(\"click\", handler, { capture: true, passive: true });\n teardowns.push(() => document.removeEventListener(\"click\", handler, { capture: true }));\n}\n\nfunction setupNavigation(add: BreadcrumbCallback): void {\n // pushState\n const origPush = history.pushState;\n history.pushState = function (...args) {\n add({\n timestamp: Date.now(),\n category: \"navigation\",\n message: String(args[2] || \"\"),\n data: { from: location.href },\n });\n return origPush.apply(this, args);\n };\n\n // replaceState\n const origReplace = history.replaceState;\n history.replaceState = function (...args) {\n add({\n timestamp: Date.now(),\n category: \"navigation\",\n message: String(args[2] || \"\"),\n data: { from: location.href },\n });\n return origReplace.apply(this, args);\n };\n\n // popstate (back/forward)\n const popHandler = () => {\n add({\n timestamp: Date.now(),\n category: \"navigation\",\n message: location.href,\n });\n };\n window.addEventListener(\"popstate\", popHandler);\n\n teardowns.push(() => {\n history.pushState = origPush;\n history.replaceState = origReplace;\n window.removeEventListener(\"popstate\", popHandler);\n });\n}\n\nfunction setupFetch(add: BreadcrumbCallback): void {\n const origFetch = window.fetch;\n\n window.fetch = async function (input, init?) {\n const rawUrl = typeof input === \"string\" ? input : input instanceof URL ? input.href : input.url;\n const method = init?.method || \"GET\";\n const start = Date.now();\n\n // Skip breadcrumbs for SDK's own requests\n if (rawUrl.includes(\"/public/errors\")) {\n return origFetch.call(window, input, init);\n }\n\n const url = scrubUrl(rawUrl);\n\n try {\n const response = await origFetch.call(window, input, init);\n add({\n timestamp: Date.now(),\n category: \"fetch\",\n message: `${method} ${url}`,\n level: response.ok ? \"info\" : \"error\",\n data: {\n status: response.status,\n duration: Date.now() - start,\n },\n });\n return response;\n } catch (err) {\n add({\n timestamp: Date.now(),\n category: \"fetch\",\n message: `${method} ${url}`,\n level: \"error\",\n data: {\n error: String(err),\n duration: Date.now() - start,\n },\n });\n throw err;\n }\n };\n\n teardowns.push(() => {\n window.fetch = origFetch;\n });\n}\n\nfunction setupXHR(add: BreadcrumbCallback): void {\n const origOpen = XMLHttpRequest.prototype.open;\n const origSend = XMLHttpRequest.prototype.send;\n\n XMLHttpRequest.prototype.open = function (method: string, url: string | URL) {\n (this as any).__ug_method = method;\n (this as any).__ug_url = String(url);\n // eslint-disable-next-line prefer-rest-params\n return origOpen.apply(this, arguments as any);\n };\n\n XMLHttpRequest.prototype.send = function () {\n const start = Date.now();\n const method = (this as any).__ug_method || \"GET\";\n const rawUrl: string = (this as any).__ug_url || \"\";\n\n // Skip breadcrumbs for SDK's own requests\n if (rawUrl.includes(\"/public/errors\")) {\n return origSend.apply(this, arguments as any);\n }\n\n const url = scrubUrl(rawUrl);\n\n this.addEventListener(\"loadend\", function () {\n add({\n timestamp: Date.now(),\n category: \"xhr\",\n message: `${method} ${url}`,\n level: this.status >= 400 ? \"error\" : \"info\",\n data: {\n status: this.status,\n duration: Date.now() - start,\n },\n });\n });\n\n // eslint-disable-next-line prefer-rest-params\n return origSend.apply(this, arguments as any);\n };\n\n teardowns.push(() => {\n XMLHttpRequest.prototype.open = origOpen;\n XMLHttpRequest.prototype.send = origSend;\n });\n}\n\n/** Strip sensitive query params from URLs in breadcrumbs */\nfunction scrubUrl(url: string): string {\n try {\n const u = new URL(url, location.origin);\n const sensitive = [\"token\", \"key\", \"secret\", \"password\", \"auth\", \"code\", \"session\", \"jwt\", \"access_token\", \"refresh_token\", \"api_key\", \"apikey\"];\n for (const param of [...u.searchParams.keys()]) {\n if (sensitive.some((s) => param.toLowerCase().includes(s))) {\n u.searchParams.set(param, \"[REDACTED]\");\n }\n }\n u.hash = \"\";\n return u.toString();\n } catch {\n return url;\n }\n}\n\nfunction getSelector(el: HTMLElement): string {\n const parts: string[] = [];\n let current: HTMLElement | null = el;\n\n for (let i = 0; i < 3 && current && current !== document.body; i++) {\n let part = current.tagName.toLowerCase();\n if (current.id) {\n part += `#${current.id}`;\n } else if (current.className && typeof current.className === \"string\") {\n const cls = current.className.trim().split(/\\s+/).slice(0, 2).join(\".\");\n if (cls) part += `.${cls}`;\n }\n parts.unshift(part);\n current = current.parentElement;\n }\n\n return parts.join(\" > \");\n}\n","import type { ErrorPayload } from \"./types\";\n\n// Capture the original fetch before any instrumentation wraps it.\n// This prevents SDK requests from appearing in breadcrumbs and\n// avoids recursive loops if the fetch wrapper itself errors.\nconst originalFetch: typeof fetch =\n typeof window !== \"undefined\" ? window.fetch.bind(window) : ((() => {}) as any);\n\nexport interface TransportConfig {\n apiKey: string;\n apiUrl: string;\n}\n\n/**\n * Must be called BEFORE setupBreadcrumbs() to capture the\n * un-instrumented fetch reference.\n */\nexport function captureOriginalFetch(): void {\n // Already captured at module load time above.\n // This function exists as a documentation marker.\n}\n\nexport function sendEvent(\n config: TransportConfig,\n payload: ErrorPayload\n): void {\n const url = `${config.apiUrl}/public/errors`;\n const body = JSON.stringify(payload);\n\n try {\n originalFetch(url, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"X-API-Key\": config.apiKey,\n },\n body,\n keepalive: true, // Survives page unload\n }).catch(() => {\n // Silently fail — never impact user's app\n });\n } catch {\n // Silently fail\n }\n}\n\n/**\n * Notify the server that this release is running. Triggers auto-resolve\n * of error groups from older releases. Fire-and-forget; the server\n * deduplicates via IS DISTINCT FROM so calling on every init() is safe.\n */\nexport function sendDeploy(\n config: TransportConfig,\n release: string\n): void {\n const url = `${config.apiUrl}/public/deploys`;\n const body = JSON.stringify({ release });\n\n try {\n originalFetch(url, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"X-API-Key\": config.apiKey,\n },\n body,\n keepalive: true,\n }).catch(() => {\n // Silently fail\n });\n } catch {\n // Silently fail\n }\n}\n"],"mappings":"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","names":["index_exports","__export","Upgoose","index_default","upgoose","__toCommonJS","teardowns","setupBreadcrumbs","config","addBreadcrumb","setupConsole","setupClicks","setupNavigation","setupFetch","setupXHR","teardownBreadcrumbs","fn","add","original","level","orig","args","handler","event","target","tag","text","selector","getSelector","origPush","origReplace","popHandler","origFetch","input","init","rawUrl","method","start","url","scrubUrl","response","err","origOpen","origSend","u","sensitive","param","el","parts","current","i","part","cls","originalFetch","sendEvent","config","payload","url","body","originalFetch","sendDeploy","release","Upgoose","event","error","config","setupBreadcrumbs","crumb","sendDeploy","extra","message","stack","normalizeError","level","user","key","value","max","teardownBreadcrumbs","partial","payload","stripUrlSensitiveParams","getBrowserContext","processed","sendEvent","url","u","sensitive","param","ctx","upgoose","index_default"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/breadcrumbs.ts","../src/transport.ts"],"sourcesContent":["import type {\n UpgooseConfig,\n Breadcrumb,\n UserContext,\n ErrorPayload,\n} from \"./types\";\nimport { setupBreadcrumbs, teardownBreadcrumbs } from \"./breadcrumbs\";\nimport { sendEvent, sendDeploy, sendConnect } from \"./transport\";\n\nexport type {\n UpgooseConfig,\n Breadcrumb,\n UserContext,\n ErrorPayload,\n};\n\nclass Upgoose {\n private config: UpgooseConfig | null = null;\n private breadcrumbs: Breadcrumb[] = [];\n private user: UserContext | null = null;\n private tags: Record<string, string> = {};\n private initialized = false;\n private sending = false; // re-entrancy guard\n\n /**\n * Initialize the Upgoose SDK. Call this once, as early as possible.\n *\n * ```ts\n * import upgoose from '@upgoose/browser';\n * upgoose.init({ apiKey: 'ug_...', apiUrl: 'https://api.upgoose.app' });\n * ```\n */\n init(config: UpgooseConfig): void {\n if (this.initialized) {\n console.warn(\"[Upgoose] Already initialized. Call destroy() first to re-initialize.\");\n return;\n }\n\n if (!config.apiKey || !config.apiUrl) {\n console.error(\"[Upgoose] apiKey and apiUrl are required.\");\n return;\n }\n\n this.config = {\n sampleRate: 1,\n maxBreadcrumbs: 50,\n captureConsole: true,\n captureClicks: true,\n captureNavigation: true,\n captureNetwork: true,\n captureUnhandled: true,\n ...config,\n };\n\n this.breadcrumbs = [];\n this.initialized = true;\n\n // Set up breadcrumb collection\n setupBreadcrumbs(this.config, (crumb) => this.addBreadcrumb(crumb));\n\n // Set up global error handlers\n if (this.config.captureUnhandled) {\n this.setupGlobalHandlers();\n }\n\n // Set up CSP violation capture\n if (this.config.captureCSP) {\n this.setupCSPHandler();\n }\n\n // Signal to the server that the SDK is installed\n const transport = { apiKey: this.config.apiKey, apiUrl: this.config.apiUrl };\n sendConnect(transport);\n\n // Notify server of this release so it can auto-resolve older error groups\n if (this.config.release) {\n sendDeploy(transport, this.config.release);\n }\n }\n\n /**\n * Capture an exception manually.\n *\n * ```ts\n * try {\n * riskyOperation();\n * } catch (err) {\n * upgoose.captureException(err);\n * }\n * ```\n */\n captureException(\n error: unknown,\n extra?: { tags?: Record<string, string>; fingerprint?: string; level?: ErrorPayload[\"level\"] }\n ): void {\n if (!this.config) return;\n\n const { message, stack } = normalizeError(error);\n this.send({\n message,\n stack,\n level: extra?.level || \"error\",\n tags: extra?.tags,\n fingerprint: extra?.fingerprint,\n });\n }\n\n /**\n * Send a test event to verify your integration is working.\n * Check your project page — the status should flip to \"CONNECTED\" within seconds.\n *\n * ```ts\n * upgoose.test();\n * ```\n */\n test(): void {\n if (!this.config) {\n console.warn(\"[Upgoose] Not initialized. Call init() first.\");\n return;\n }\n this.send({\n message: \"Upgoose test event — integration verified\",\n level: \"warning\",\n tags: { upgoose_test: \"true\" },\n fingerprint: \"upgoose-test-event\",\n });\n console.info(\"[Upgoose] Test event sent. Check your project page.\");\n }\n\n /**\n * Capture a message as an error event.\n *\n * ```ts\n * upgoose.captureMessage(\"User hit rate limit\", \"warning\");\n * ```\n */\n captureMessage(\n message: string,\n level: ErrorPayload[\"level\"] = \"error\"\n ): void {\n if (!this.config) return;\n this.send({ message, level });\n }\n\n /**\n * Set user context. Attached to all subsequent events.\n *\n * ```ts\n * upgoose.setUser({ id: '123', email: 'user@example.com' });\n * ```\n */\n setUser(user: UserContext | null): void {\n this.user = user;\n }\n\n /**\n * Set a global tag. Attached to all subsequent events.\n */\n setTag(key: string, value: string): void {\n this.tags[key] = value;\n }\n\n /**\n * Add a breadcrumb manually.\n */\n addBreadcrumb(crumb: Breadcrumb): void {\n const max = this.config?.maxBreadcrumbs ?? 50;\n this.breadcrumbs.push(crumb);\n if (this.breadcrumbs.length > max) {\n this.breadcrumbs = this.breadcrumbs.slice(-max);\n }\n }\n\n /**\n * Tear down the SDK — removes global handlers and breadcrumb instrumentation.\n */\n destroy(): void {\n teardownBreadcrumbs();\n this.removeGlobalHandlers();\n this.removeCSPHandler();\n this.config = null;\n this.breadcrumbs = [];\n this.user = null;\n this.tags = {};\n this.initialized = false;\n }\n\n // -- Private --\n\n private onError = (event: ErrorEvent): void => {\n const error = event.error || new Error(event.message || \"Unknown error\");\n this.captureException(error);\n };\n\n private onUnhandledRejection = (event: PromiseRejectionEvent): void => {\n const error = event.reason;\n this.captureException(error);\n };\n\n private onCSPViolation = (event: SecurityPolicyViolationEvent): void => {\n const directive = event.effectiveDirective || event.violatedDirective || \"unknown\";\n const blocked = normalizeBlockedURI(event.blockedURI);\n\n this.send({\n message: `CSP violation: ${directive} blocked ${event.blockedURI || \"inline content\"}`,\n level: \"warning\",\n tags: {\n \"csp.directive\": directive,\n \"csp.disposition\": event.disposition || \"enforce\",\n \"csp.blocked_origin\": blocked,\n },\n fingerprint: `csp:${directive}:${blocked}`,\n });\n };\n\n private setupGlobalHandlers(): void {\n window.addEventListener(\"error\", this.onError);\n window.addEventListener(\"unhandledrejection\", this.onUnhandledRejection);\n }\n\n private removeGlobalHandlers(): void {\n window.removeEventListener(\"error\", this.onError);\n window.removeEventListener(\"unhandledrejection\", this.onUnhandledRejection);\n }\n\n private setupCSPHandler(): void {\n document.addEventListener(\"securitypolicyviolation\", this.onCSPViolation);\n }\n\n private removeCSPHandler(): void {\n document.removeEventListener(\"securitypolicyviolation\", this.onCSPViolation);\n }\n\n private send(partial: {\n message: string;\n stack?: string;\n level: ErrorPayload[\"level\"];\n tags?: Record<string, string>;\n fingerprint?: string;\n }): void {\n const config = this.config;\n if (!config) return;\n\n // Re-entrancy guard: prevent infinite loops if send() itself throws\n // or if beforeSend throws and triggers the global error handler\n if (this.sending) return;\n this.sending = true;\n\n try {\n // Sample rate check\n if (typeof config.sampleRate === \"number\" && Math.random() > config.sampleRate) {\n return;\n }\n\n const payload: ErrorPayload = {\n message: partial.message.slice(0, 2000),\n stack: partial.stack?.slice(0, 10000),\n level: partial.level,\n environment: config.environment,\n release: config.release,\n tags: { ...this.tags, ...partial.tags },\n context: {\n breadcrumbs: this.breadcrumbs.slice(-30),\n ...(this.user ? { user: this.user } : {}),\n page: {\n url: stripUrlSensitiveParams(location.href),\n title: document.title,\n },\n browser: getBrowserContext(),\n },\n fingerprint: partial.fingerprint,\n };\n\n // beforeSend hook (wrapped to prevent throws from looping)\n let processed: ErrorPayload | null = payload;\n if (config.beforeSend) {\n try {\n processed = config.beforeSend(payload);\n } catch {\n // Drop event if beforeSend throws\n return;\n }\n }\n if (!processed) return;\n\n sendEvent(\n { apiKey: config.apiKey, apiUrl: config.apiUrl },\n processed\n );\n } finally {\n this.sending = false;\n }\n }\n}\n\nfunction normalizeError(error: unknown): { message: string; stack?: string } {\n if (error instanceof Error) {\n return { message: error.message, stack: error.stack };\n }\n if (typeof error === \"string\") {\n return { message: error };\n }\n if (error && typeof error === \"object\" && \"message\" in error) {\n return {\n message: String((error as { message: unknown }).message),\n stack: \"stack\" in error ? String((error as { stack: unknown }).stack) : undefined,\n };\n }\n return { message: String(error) };\n}\n\n/** Strip common sensitive query/hash params from URLs */\nfunction stripUrlSensitiveParams(url: string): string {\n try {\n const u = new URL(url);\n const sensitive = [\"token\", \"key\", \"secret\", \"password\", \"auth\", \"code\", \"session\", \"jwt\", \"access_token\", \"refresh_token\", \"api_key\", \"apikey\"];\n for (const param of [...u.searchParams.keys()]) {\n if (sensitive.some((s) => param.toLowerCase().includes(s))) {\n u.searchParams.set(param, \"[REDACTED]\");\n }\n }\n // Strip hash (often contains tokens in SPAs)\n u.hash = \"\";\n return u.toString();\n } catch {\n return url;\n }\n}\n\n/** Normalize a CSP blockedURI to its origin for fingerprinting */\nfunction normalizeBlockedURI(uri: string): string {\n if (!uri) return \"inline\";\n if (uri === \"eval\" || uri === \"inline\" || uri === \"data\" || uri === \"blob\") return uri;\n try {\n return new URL(uri).origin;\n } catch {\n return uri.slice(0, 100);\n }\n}\n\nfunction getBrowserContext(): Record<string, string> {\n const ctx: Record<string, string> = {};\n try {\n ctx.userAgent = navigator.userAgent;\n ctx.language = navigator.language;\n ctx.viewport = `${window.innerWidth}x${window.innerHeight}`;\n ctx.screen = `${screen.width}x${screen.height}`;\n } catch {\n // Ignore\n }\n return ctx;\n}\n\n// Singleton instance\nconst upgoose = new Upgoose();\nexport default upgoose;\nexport { upgoose, Upgoose };\n","import type { Breadcrumb, UpgooseConfig } from \"./types\";\n\ntype BreadcrumbCallback = (crumb: Breadcrumb) => void;\n\nconst teardowns: Array<() => void> = [];\n\nexport function setupBreadcrumbs(\n config: UpgooseConfig,\n addBreadcrumb: BreadcrumbCallback\n): void {\n if (config.captureConsole !== false) {\n setupConsole(addBreadcrumb);\n }\n if (config.captureClicks !== false) {\n setupClicks(addBreadcrumb);\n }\n if (config.captureNavigation !== false) {\n setupNavigation(addBreadcrumb);\n }\n if (config.captureNetwork !== false) {\n setupFetch(addBreadcrumb);\n setupXHR(addBreadcrumb);\n }\n if (config.captureCSP) {\n setupCSP(addBreadcrumb);\n }\n}\n\nexport function teardownBreadcrumbs(): void {\n for (const fn of teardowns) {\n try {\n fn();\n } catch {\n // Best effort cleanup\n }\n }\n teardowns.length = 0;\n}\n\nfunction setupConsole(add: BreadcrumbCallback): void {\n const original = {\n log: console.log,\n warn: console.warn,\n error: console.error,\n };\n\n for (const level of [\"log\", \"warn\", \"error\"] as const) {\n const orig = original[level];\n console[level] = (...args: unknown[]) => {\n add({\n timestamp: Date.now(),\n category: \"console\",\n message: args.map(String).join(\" \").slice(0, 500),\n level: level === \"error\" ? \"error\" : level === \"warn\" ? \"warning\" : \"info\",\n });\n orig.apply(console, args);\n };\n }\n\n teardowns.push(() => {\n console.log = original.log;\n console.warn = original.warn;\n console.error = original.error;\n });\n}\n\nfunction setupClicks(add: BreadcrumbCallback): void {\n const handler = (event: MouseEvent) => {\n const target = event.target as HTMLElement | null;\n if (!target) return;\n\n const tag = target.tagName?.toLowerCase();\n const text = target.textContent?.trim().slice(0, 100) || \"\";\n const selector = getSelector(target);\n\n add({\n timestamp: Date.now(),\n category: \"ui\",\n message: `click on ${tag}${text ? `: \"${text}\"` : \"\"}`,\n data: { selector },\n });\n };\n\n document.addEventListener(\"click\", handler, { capture: true, passive: true });\n teardowns.push(() => document.removeEventListener(\"click\", handler, { capture: true }));\n}\n\nfunction setupNavigation(add: BreadcrumbCallback): void {\n // pushState\n const origPush = history.pushState;\n history.pushState = function (...args) {\n add({\n timestamp: Date.now(),\n category: \"navigation\",\n message: String(args[2] || \"\"),\n data: { from: location.href },\n });\n return origPush.apply(this, args);\n };\n\n // replaceState\n const origReplace = history.replaceState;\n history.replaceState = function (...args) {\n add({\n timestamp: Date.now(),\n category: \"navigation\",\n message: String(args[2] || \"\"),\n data: { from: location.href },\n });\n return origReplace.apply(this, args);\n };\n\n // popstate (back/forward)\n const popHandler = () => {\n add({\n timestamp: Date.now(),\n category: \"navigation\",\n message: location.href,\n });\n };\n window.addEventListener(\"popstate\", popHandler);\n\n teardowns.push(() => {\n history.pushState = origPush;\n history.replaceState = origReplace;\n window.removeEventListener(\"popstate\", popHandler);\n });\n}\n\nfunction setupFetch(add: BreadcrumbCallback): void {\n const origFetch = window.fetch;\n\n window.fetch = async function (input, init?) {\n const rawUrl = typeof input === \"string\" ? input : input instanceof URL ? input.href : input.url;\n const method = init?.method || \"GET\";\n const start = Date.now();\n\n // Skip breadcrumbs for SDK's own requests\n if (rawUrl.includes(\"/public/errors\")) {\n return origFetch.call(window, input, init);\n }\n\n const url = scrubUrl(rawUrl);\n\n try {\n const response = await origFetch.call(window, input, init);\n add({\n timestamp: Date.now(),\n category: \"fetch\",\n message: `${method} ${url}`,\n level: response.ok ? \"info\" : \"error\",\n data: {\n status: response.status,\n duration: Date.now() - start,\n },\n });\n return response;\n } catch (err) {\n add({\n timestamp: Date.now(),\n category: \"fetch\",\n message: `${method} ${url}`,\n level: \"error\",\n data: {\n error: String(err),\n duration: Date.now() - start,\n },\n });\n throw err;\n }\n };\n\n teardowns.push(() => {\n window.fetch = origFetch;\n });\n}\n\nfunction setupXHR(add: BreadcrumbCallback): void {\n const origOpen = XMLHttpRequest.prototype.open;\n const origSend = XMLHttpRequest.prototype.send;\n\n XMLHttpRequest.prototype.open = function (method: string, url: string | URL) {\n (this as any).__ug_method = method;\n (this as any).__ug_url = String(url);\n // eslint-disable-next-line prefer-rest-params\n return origOpen.apply(this, arguments as any);\n };\n\n XMLHttpRequest.prototype.send = function () {\n const start = Date.now();\n const method = (this as any).__ug_method || \"GET\";\n const rawUrl: string = (this as any).__ug_url || \"\";\n\n // Skip breadcrumbs for SDK's own requests\n if (rawUrl.includes(\"/public/errors\")) {\n return origSend.apply(this, arguments as any);\n }\n\n const url = scrubUrl(rawUrl);\n\n this.addEventListener(\"loadend\", function () {\n add({\n timestamp: Date.now(),\n category: \"xhr\",\n message: `${method} ${url}`,\n level: this.status >= 400 ? \"error\" : \"info\",\n data: {\n status: this.status,\n duration: Date.now() - start,\n },\n });\n });\n\n // eslint-disable-next-line prefer-rest-params\n return origSend.apply(this, arguments as any);\n };\n\n teardowns.push(() => {\n XMLHttpRequest.prototype.open = origOpen;\n XMLHttpRequest.prototype.send = origSend;\n });\n}\n\n/** Strip sensitive query params from URLs in breadcrumbs */\nfunction scrubUrl(url: string): string {\n try {\n const u = new URL(url, location.origin);\n const sensitive = [\"token\", \"key\", \"secret\", \"password\", \"auth\", \"code\", \"session\", \"jwt\", \"access_token\", \"refresh_token\", \"api_key\", \"apikey\"];\n for (const param of [...u.searchParams.keys()]) {\n if (sensitive.some((s) => param.toLowerCase().includes(s))) {\n u.searchParams.set(param, \"[REDACTED]\");\n }\n }\n u.hash = \"\";\n return u.toString();\n } catch {\n return url;\n }\n}\n\nfunction setupCSP(add: BreadcrumbCallback): void {\n const handler = (event: SecurityPolicyViolationEvent) => {\n const directive = event.effectiveDirective || event.violatedDirective || \"unknown\";\n add({\n timestamp: Date.now(),\n category: \"csp\",\n message: `${directive} blocked ${event.blockedURI || \"inline content\"}`,\n level: \"warning\",\n data: {\n blockedURI: event.blockedURI,\n documentURI: event.documentURI,\n disposition: event.disposition,\n },\n });\n };\n\n document.addEventListener(\"securitypolicyviolation\", handler);\n teardowns.push(() => document.removeEventListener(\"securitypolicyviolation\", handler));\n}\n\nfunction getSelector(el: HTMLElement): string {\n const parts: string[] = [];\n let current: HTMLElement | null = el;\n\n for (let i = 0; i < 3 && current && current !== document.body; i++) {\n let part = current.tagName.toLowerCase();\n if (current.id) {\n part += `#${current.id}`;\n } else if (current.className && typeof current.className === \"string\") {\n const cls = current.className.trim().split(/\\s+/).slice(0, 2).join(\".\");\n if (cls) part += `.${cls}`;\n }\n parts.unshift(part);\n current = current.parentElement;\n }\n\n return parts.join(\" > \");\n}\n","import type { ErrorPayload } from \"./types\";\n\n// Capture the original fetch before any instrumentation wraps it.\n// This prevents SDK requests from appearing in breadcrumbs and\n// avoids recursive loops if the fetch wrapper itself errors.\nconst originalFetch: typeof fetch =\n typeof window !== \"undefined\" ? window.fetch.bind(window) : ((() => {}) as any);\n\nexport interface TransportConfig {\n apiKey: string;\n apiUrl: string;\n}\n\n/**\n * Must be called BEFORE setupBreadcrumbs() to capture the\n * un-instrumented fetch reference.\n */\nexport function captureOriginalFetch(): void {\n // Already captured at module load time above.\n // This function exists as a documentation marker.\n}\n\nexport function sendEvent(\n config: TransportConfig,\n payload: ErrorPayload\n): void {\n const url = `${config.apiUrl}/public/errors`;\n const body = JSON.stringify(payload);\n\n try {\n originalFetch(url, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"X-API-Key\": config.apiKey,\n },\n body,\n keepalive: true, // Survives page unload\n }).catch(() => {\n // Silently fail — never impact user's app\n });\n } catch {\n // Silently fail\n }\n}\n\n/**\n * Signal that the SDK is installed and initialized. Sets sdkConnectedAt\n * on the project without requiring an error event. Idempotent — the\n * server no-ops if already connected.\n */\nexport function sendConnect(config: TransportConfig): void {\n const url = `${config.apiUrl}/public/connect`;\n\n try {\n originalFetch(url, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"X-API-Key\": config.apiKey,\n },\n body: \"{}\",\n keepalive: true,\n }).catch(() => {\n // Silently fail\n });\n } catch {\n // Silently fail\n }\n}\n\n/**\n * Notify the server that this release is running. Triggers auto-resolve\n * of error groups from older releases. Fire-and-forget; the server\n * deduplicates via IS DISTINCT FROM so calling on every init() is safe.\n */\nexport function sendDeploy(\n config: TransportConfig,\n release: string\n): void {\n const url = `${config.apiUrl}/public/deploys`;\n const body = JSON.stringify({ release });\n\n try {\n originalFetch(url, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"X-API-Key\": config.apiKey,\n },\n body,\n keepalive: true,\n }).catch(() => {\n // Silently fail\n });\n } catch {\n // Silently fail\n }\n}\n"],"mappings":"yaAAA,IAAAA,EAAA,GAAAC,EAAAD,EAAA,aAAAE,EAAA,YAAAC,EAAA,YAAAC,IAAA,eAAAC,EAAAL,GCIA,IAAMM,EAA+B,CAAC,EAE/B,SAASC,EACdC,EACAC,EACM,CACFD,EAAO,iBAAmB,IAC5BE,EAAaD,CAAa,EAExBD,EAAO,gBAAkB,IAC3BG,EAAYF,CAAa,EAEvBD,EAAO,oBAAsB,IAC/BI,EAAgBH,CAAa,EAE3BD,EAAO,iBAAmB,KAC5BK,EAAWJ,CAAa,EACxBK,EAASL,CAAa,GAEpBD,EAAO,YACTO,EAASN,CAAa,CAE1B,CAEO,SAASO,GAA4B,CAC1C,QAAWC,KAAMX,EACf,GAAI,CACFW,EAAG,CACL,MAAQ,CAER,CAEFX,EAAU,OAAS,CACrB,CAEA,SAASI,EAAaQ,EAA+B,CACnD,IAAMC,EAAW,CACf,IAAK,QAAQ,IACb,KAAM,QAAQ,KACd,MAAO,QAAQ,KACjB,EAEA,QAAWC,IAAS,CAAC,MAAO,OAAQ,OAAO,EAAY,CACrD,IAAMC,EAAOF,EAASC,CAAK,EAC3B,QAAQA,CAAK,EAAI,IAAIE,IAAoB,CACvCJ,EAAI,CACF,UAAW,KAAK,IAAI,EACpB,SAAU,UACV,QAASI,EAAK,IAAI,MAAM,EAAE,KAAK,GAAG,EAAE,MAAM,EAAG,GAAG,EAChD,MAAOF,IAAU,QAAU,QAAUA,IAAU,OAAS,UAAY,MACtE,CAAC,EACDC,EAAK,MAAM,QAASC,CAAI,CAC1B,CACF,CAEAhB,EAAU,KAAK,IAAM,CACnB,QAAQ,IAAMa,EAAS,IACvB,QAAQ,KAAOA,EAAS,KACxB,QAAQ,MAAQA,EAAS,KAC3B,CAAC,CACH,CAEA,SAASR,EAAYO,EAA+B,CAClD,IAAMK,EAAWC,GAAsB,CACrC,IAAMC,EAASD,EAAM,OACrB,GAAI,CAACC,EAAQ,OAEb,IAAMC,EAAMD,EAAO,SAAS,YAAY,EAClCE,EAAOF,EAAO,aAAa,KAAK,EAAE,MAAM,EAAG,GAAG,GAAK,GACnDG,EAAWC,EAAYJ,CAAM,EAEnCP,EAAI,CACF,UAAW,KAAK,IAAI,EACpB,SAAU,KACV,QAAS,YAAYQ,CAAG,GAAGC,EAAO,MAAMA,CAAI,IAAM,EAAE,GACpD,KAAM,CAAE,SAAAC,CAAS,CACnB,CAAC,CACH,EAEA,SAAS,iBAAiB,QAASL,EAAS,CAAE,QAAS,GAAM,QAAS,EAAK,CAAC,EAC5EjB,EAAU,KAAK,IAAM,SAAS,oBAAoB,QAASiB,EAAS,CAAE,QAAS,EAAK,CAAC,CAAC,CACxF,CAEA,SAASX,EAAgBM,EAA+B,CAEtD,IAAMY,EAAW,QAAQ,UACzB,QAAQ,UAAY,YAAaR,EAAM,CACrC,OAAAJ,EAAI,CACF,UAAW,KAAK,IAAI,EACpB,SAAU,aACV,QAAS,OAAOI,EAAK,CAAC,GAAK,EAAE,EAC7B,KAAM,CAAE,KAAM,SAAS,IAAK,CAC9B,CAAC,EACMQ,EAAS,MAAM,KAAMR,CAAI,CAClC,EAGA,IAAMS,EAAc,QAAQ,aAC5B,QAAQ,aAAe,YAAaT,EAAM,CACxC,OAAAJ,EAAI,CACF,UAAW,KAAK,IAAI,EACpB,SAAU,aACV,QAAS,OAAOI,EAAK,CAAC,GAAK,EAAE,EAC7B,KAAM,CAAE,KAAM,SAAS,IAAK,CAC9B,CAAC,EACMS,EAAY,MAAM,KAAMT,CAAI,CACrC,EAGA,IAAMU,EAAa,IAAM,CACvBd,EAAI,CACF,UAAW,KAAK,IAAI,EACpB,SAAU,aACV,QAAS,SAAS,IACpB,CAAC,CACH,EACA,OAAO,iBAAiB,WAAYc,CAAU,EAE9C1B,EAAU,KAAK,IAAM,CACnB,QAAQ,UAAYwB,EACpB,QAAQ,aAAeC,EACvB,OAAO,oBAAoB,WAAYC,CAAU,CACnD,CAAC,CACH,CAEA,SAASnB,EAAWK,EAA+B,CACjD,IAAMe,EAAY,OAAO,MAEzB,OAAO,MAAQ,eAAgBC,EAAOC,EAAO,CAC3C,IAAMC,EAAS,OAAOF,GAAU,SAAWA,EAAQA,aAAiB,IAAMA,EAAM,KAAOA,EAAM,IACvFG,EAASF,GAAM,QAAU,MACzBG,EAAQ,KAAK,IAAI,EAGvB,GAAIF,EAAO,SAAS,gBAAgB,EAClC,OAAOH,EAAU,KAAK,OAAQC,EAAOC,CAAI,EAG3C,IAAMI,EAAMC,EAASJ,CAAM,EAE3B,GAAI,CACF,IAAMK,EAAW,MAAMR,EAAU,KAAK,OAAQC,EAAOC,CAAI,EACzD,OAAAjB,EAAI,CACF,UAAW,KAAK,IAAI,EACpB,SAAU,QACV,QAAS,GAAGmB,CAAM,IAAIE,CAAG,GACzB,MAAOE,EAAS,GAAK,OAAS,QAC9B,KAAM,CACJ,OAAQA,EAAS,OACjB,SAAU,KAAK,IAAI,EAAIH,CACzB,CACF,CAAC,EACMG,CACT,OAASC,EAAK,CACZ,MAAAxB,EAAI,CACF,UAAW,KAAK,IAAI,EACpB,SAAU,QACV,QAAS,GAAGmB,CAAM,IAAIE,CAAG,GACzB,MAAO,QACP,KAAM,CACJ,MAAO,OAAOG,CAAG,EACjB,SAAU,KAAK,IAAI,EAAIJ,CACzB,CACF,CAAC,EACKI,CACR,CACF,EAEApC,EAAU,KAAK,IAAM,CACnB,OAAO,MAAQ2B,CACjB,CAAC,CACH,CAEA,SAASnB,EAASI,EAA+B,CAC/C,IAAMyB,EAAW,eAAe,UAAU,KACpCC,EAAW,eAAe,UAAU,KAE1C,eAAe,UAAU,KAAO,SAAUP,EAAgBE,EAAmB,CAC3E,OAAC,KAAa,YAAcF,EAC3B,KAAa,SAAW,OAAOE,CAAG,EAE5BI,EAAS,MAAM,KAAM,SAAgB,CAC9C,EAEA,eAAe,UAAU,KAAO,UAAY,CAC1C,IAAML,EAAQ,KAAK,IAAI,EACjBD,EAAU,KAAa,aAAe,MACtCD,EAAkB,KAAa,UAAY,GAGjD,GAAIA,EAAO,SAAS,gBAAgB,EAClC,OAAOQ,EAAS,MAAM,KAAM,SAAgB,EAG9C,IAAML,EAAMC,EAASJ,CAAM,EAE3B,YAAK,iBAAiB,UAAW,UAAY,CAC3ClB,EAAI,CACF,UAAW,KAAK,IAAI,EACpB,SAAU,MACV,QAAS,GAAGmB,CAAM,IAAIE,CAAG,GACzB,MAAO,KAAK,QAAU,IAAM,QAAU,OACtC,KAAM,CACJ,OAAQ,KAAK,OACb,SAAU,KAAK,IAAI,EAAID,CACzB,CACF,CAAC,CACH,CAAC,EAGMM,EAAS,MAAM,KAAM,SAAgB,CAC9C,EAEAtC,EAAU,KAAK,IAAM,CACnB,eAAe,UAAU,KAAOqC,EAChC,eAAe,UAAU,KAAOC,CAClC,CAAC,CACH,CAGA,SAASJ,EAASD,EAAqB,CACrC,GAAI,CACF,IAAMM,EAAI,IAAI,IAAIN,EAAK,SAAS,MAAM,EAChCO,EAAY,CAAC,QAAS,MAAO,SAAU,WAAY,OAAQ,OAAQ,UAAW,MAAO,eAAgB,gBAAiB,UAAW,QAAQ,EAC/I,QAAWC,IAAS,CAAC,GAAGF,EAAE,aAAa,KAAK,CAAC,EACvCC,EAAU,KAAME,GAAMD,EAAM,YAAY,EAAE,SAASC,CAAC,CAAC,GACvDH,EAAE,aAAa,IAAIE,EAAO,YAAY,EAG1C,OAAAF,EAAE,KAAO,GACFA,EAAE,SAAS,CACpB,MAAQ,CACN,OAAON,CACT,CACF,CAEA,SAASxB,EAASG,EAA+B,CAC/C,IAAMK,EAAWC,GAAwC,CACvD,IAAMyB,EAAYzB,EAAM,oBAAsBA,EAAM,mBAAqB,UACzEN,EAAI,CACF,UAAW,KAAK,IAAI,EACpB,SAAU,MACV,QAAS,GAAG+B,CAAS,YAAYzB,EAAM,YAAc,gBAAgB,GACrE,MAAO,UACP,KAAM,CACJ,WAAYA,EAAM,WAClB,YAAaA,EAAM,YACnB,YAAaA,EAAM,WACrB,CACF,CAAC,CACH,EAEA,SAAS,iBAAiB,0BAA2BD,CAAO,EAC5DjB,EAAU,KAAK,IAAM,SAAS,oBAAoB,0BAA2BiB,CAAO,CAAC,CACvF,CAEA,SAASM,EAAYqB,EAAyB,CAC5C,IAAMC,EAAkB,CAAC,EACrBC,EAA8BF,EAElC,QAASG,EAAI,EAAGA,EAAI,GAAKD,GAAWA,IAAY,SAAS,KAAMC,IAAK,CAClE,IAAIC,EAAOF,EAAQ,QAAQ,YAAY,EACvC,GAAIA,EAAQ,GACVE,GAAQ,IAAIF,EAAQ,EAAE,WACbA,EAAQ,WAAa,OAAOA,EAAQ,WAAc,SAAU,CACrE,IAAMG,EAAMH,EAAQ,UAAU,KAAK,EAAE,MAAM,KAAK,EAAE,MAAM,EAAG,CAAC,EAAE,KAAK,GAAG,EAClEG,IAAKD,GAAQ,IAAIC,CAAG,GAC1B,CACAJ,EAAM,QAAQG,CAAI,EAClBF,EAAUA,EAAQ,aACpB,CAEA,OAAOD,EAAM,KAAK,KAAK,CACzB,CChRA,IAAMK,EACJ,OAAO,OAAW,IAAc,OAAO,MAAM,KAAK,MAAM,GAAM,IAAM,CAAC,GAgBhE,SAASC,EACdC,EACAC,EACM,CACN,IAAMC,EAAM,GAAGF,EAAO,MAAM,iBACtBG,EAAO,KAAK,UAAUF,CAAO,EAEnC,GAAI,CACFG,EAAcF,EAAK,CACjB,OAAQ,OACR,QAAS,CACP,eAAgB,mBAChB,YAAaF,EAAO,MACtB,EACA,KAAAG,EACA,UAAW,EACb,CAAC,EAAE,MAAM,IAAM,CAEf,CAAC,CACH,MAAQ,CAER,CACF,CAOO,SAASE,EAAYL,EAA+B,CACzD,IAAME,EAAM,GAAGF,EAAO,MAAM,kBAE5B,GAAI,CACFI,EAAcF,EAAK,CACjB,OAAQ,OACR,QAAS,CACP,eAAgB,mBAChB,YAAaF,EAAO,MACtB,EACA,KAAM,KACN,UAAW,EACb,CAAC,EAAE,MAAM,IAAM,CAEf,CAAC,CACH,MAAQ,CAER,CACF,CAOO,SAASM,EACdN,EACAO,EACM,CACN,IAAML,EAAM,GAAGF,EAAO,MAAM,kBACtBG,EAAO,KAAK,UAAU,CAAE,QAAAI,CAAQ,CAAC,EAEvC,GAAI,CACFH,EAAcF,EAAK,CACjB,OAAQ,OACR,QAAS,CACP,eAAgB,mBAChB,YAAaF,EAAO,MACtB,EACA,KAAAG,EACA,UAAW,EACb,CAAC,EAAE,MAAM,IAAM,CAEf,CAAC,CACH,MAAQ,CAER,CACF,CFlFA,IAAMK,EAAN,KAAc,CAAd,cACE,KAAQ,OAA+B,KACvC,KAAQ,YAA4B,CAAC,EACrC,KAAQ,KAA2B,KACnC,KAAQ,KAA+B,CAAC,EACxC,KAAQ,YAAc,GACtB,KAAQ,QAAU,GAuKlB,KAAQ,QAAWC,GAA4B,CAC7C,IAAMC,EAAQD,EAAM,OAAS,IAAI,MAAMA,EAAM,SAAW,eAAe,EACvE,KAAK,iBAAiBC,CAAK,CAC7B,EAEA,KAAQ,qBAAwBD,GAAuC,CACrE,IAAMC,EAAQD,EAAM,OACpB,KAAK,iBAAiBC,CAAK,CAC7B,EAEA,KAAQ,eAAkBD,GAA8C,CACtE,IAAME,EAAYF,EAAM,oBAAsBA,EAAM,mBAAqB,UACnEG,EAAUC,EAAoBJ,EAAM,UAAU,EAEpD,KAAK,KAAK,CACR,QAAS,kBAAkBE,CAAS,YAAYF,EAAM,YAAc,gBAAgB,GACpF,MAAO,UACP,KAAM,CACJ,gBAAiBE,EACjB,kBAAmBF,EAAM,aAAe,UACxC,qBAAsBG,CACxB,EACA,YAAa,OAAOD,CAAS,IAAIC,CAAO,EAC1C,CAAC,CACH,EArLA,KAAKE,EAA6B,CAChC,GAAI,KAAK,YAAa,CACpB,QAAQ,KAAK,uEAAuE,EACpF,MACF,CAEA,GAAI,CAACA,EAAO,QAAU,CAACA,EAAO,OAAQ,CACpC,QAAQ,MAAM,2CAA2C,EACzD,MACF,CAEA,KAAK,OAAS,CACZ,WAAY,EACZ,eAAgB,GAChB,eAAgB,GAChB,cAAe,GACf,kBAAmB,GACnB,eAAgB,GAChB,iBAAkB,GAClB,GAAGA,CACL,EAEA,KAAK,YAAc,CAAC,EACpB,KAAK,YAAc,GAGnBC,EAAiB,KAAK,OAASC,GAAU,KAAK,cAAcA,CAAK,CAAC,EAG9D,KAAK,OAAO,kBACd,KAAK,oBAAoB,EAIvB,KAAK,OAAO,YACd,KAAK,gBAAgB,EAIvB,IAAMC,EAAY,CAAE,OAAQ,KAAK,OAAO,OAAQ,OAAQ,KAAK,OAAO,MAAO,EAC3EC,EAAYD,CAAS,EAGjB,KAAK,OAAO,SACdE,EAAWF,EAAW,KAAK,OAAO,OAAO,CAE7C,CAaA,iBACEP,EACAU,EACM,CACN,GAAI,CAAC,KAAK,OAAQ,OAElB,GAAM,CAAE,QAAAC,EAAS,MAAAC,CAAM,EAAIC,EAAeb,CAAK,EAC/C,KAAK,KAAK,CACR,QAAAW,EACA,MAAAC,EACA,MAAOF,GAAO,OAAS,QACvB,KAAMA,GAAO,KACb,YAAaA,GAAO,WACtB,CAAC,CACH,CAUA,MAAa,CACX,GAAI,CAAC,KAAK,OAAQ,CAChB,QAAQ,KAAK,+CAA+C,EAC5D,MACF,CACA,KAAK,KAAK,CACR,QAAS,iDACT,MAAO,UACP,KAAM,CAAE,aAAc,MAAO,EAC7B,YAAa,oBACf,CAAC,EACD,QAAQ,KAAK,qDAAqD,CACpE,CASA,eACEC,EACAG,EAA+B,QACzB,CACD,KAAK,QACV,KAAK,KAAK,CAAE,QAAAH,EAAS,MAAAG,CAAM,CAAC,CAC9B,CASA,QAAQC,EAAgC,CACtC,KAAK,KAAOA,CACd,CAKA,OAAOC,EAAaC,EAAqB,CACvC,KAAK,KAAKD,CAAG,EAAIC,CACnB,CAKA,cAAcX,EAAyB,CACrC,IAAMY,EAAM,KAAK,QAAQ,gBAAkB,GAC3C,KAAK,YAAY,KAAKZ,CAAK,EACvB,KAAK,YAAY,OAASY,IAC5B,KAAK,YAAc,KAAK,YAAY,MAAM,CAACA,CAAG,EAElD,CAKA,SAAgB,CACdC,EAAoB,EACpB,KAAK,qBAAqB,EAC1B,KAAK,iBAAiB,EACtB,KAAK,OAAS,KACd,KAAK,YAAc,CAAC,EACpB,KAAK,KAAO,KACZ,KAAK,KAAO,CAAC,EACb,KAAK,YAAc,EACrB,CA8BQ,qBAA4B,CAClC,OAAO,iBAAiB,QAAS,KAAK,OAAO,EAC7C,OAAO,iBAAiB,qBAAsB,KAAK,oBAAoB,CACzE,CAEQ,sBAA6B,CACnC,OAAO,oBAAoB,QAAS,KAAK,OAAO,EAChD,OAAO,oBAAoB,qBAAsB,KAAK,oBAAoB,CAC5E,CAEQ,iBAAwB,CAC9B,SAAS,iBAAiB,0BAA2B,KAAK,cAAc,CAC1E,CAEQ,kBAAyB,CAC/B,SAAS,oBAAoB,0BAA2B,KAAK,cAAc,CAC7E,CAEQ,KAAKC,EAMJ,CACP,IAAMhB,EAAS,KAAK,OACpB,GAAKA,GAID,MAAK,QACT,MAAK,QAAU,GAEf,GAAI,CAEF,GAAI,OAAOA,EAAO,YAAe,UAAY,KAAK,OAAO,EAAIA,EAAO,WAClE,OAGF,IAAMiB,EAAwB,CAC5B,QAASD,EAAQ,QAAQ,MAAM,EAAG,GAAI,EACtC,MAAOA,EAAQ,OAAO,MAAM,EAAG,GAAK,EACpC,MAAOA,EAAQ,MACf,YAAahB,EAAO,YACpB,QAASA,EAAO,QAChB,KAAM,CAAE,GAAG,KAAK,KAAM,GAAGgB,EAAQ,IAAK,EACtC,QAAS,CACP,YAAa,KAAK,YAAY,MAAM,GAAG,EACvC,GAAI,KAAK,KAAO,CAAE,KAAM,KAAK,IAAK,EAAI,CAAC,EACvC,KAAM,CACJ,IAAKE,EAAwB,SAAS,IAAI,EAC1C,MAAO,SAAS,KAClB,EACA,QAASC,EAAkB,CAC7B,EACA,YAAaH,EAAQ,WACvB,EAGII,EAAiCH,EACrC,GAAIjB,EAAO,WACT,GAAI,CACFoB,EAAYpB,EAAO,WAAWiB,CAAO,CACvC,MAAQ,CAEN,MACF,CAEF,GAAI,CAACG,EAAW,OAEhBC,EACE,CAAE,OAAQrB,EAAO,OAAQ,OAAQA,EAAO,MAAO,EAC/CoB,CACF,CACF,QAAE,CACA,KAAK,QAAU,EACjB,EACF,CACF,EAEA,SAASX,EAAeb,EAAqD,CAC3E,OAAIA,aAAiB,MACZ,CAAE,QAASA,EAAM,QAAS,MAAOA,EAAM,KAAM,EAElD,OAAOA,GAAU,SACZ,CAAE,QAASA,CAAM,EAEtBA,GAAS,OAAOA,GAAU,UAAY,YAAaA,EAC9C,CACL,QAAS,OAAQA,EAA+B,OAAO,EACvD,MAAO,UAAWA,EAAQ,OAAQA,EAA6B,KAAK,EAAI,MAC1E,EAEK,CAAE,QAAS,OAAOA,CAAK,CAAE,CAClC,CAGA,SAASsB,EAAwBI,EAAqB,CACpD,GAAI,CACF,IAAMC,EAAI,IAAI,IAAID,CAAG,EACfE,EAAY,CAAC,QAAS,MAAO,SAAU,WAAY,OAAQ,OAAQ,UAAW,MAAO,eAAgB,gBAAiB,UAAW,QAAQ,EAC/I,QAAWC,IAAS,CAAC,GAAGF,EAAE,aAAa,KAAK,CAAC,EACvCC,EAAU,KAAME,GAAMD,EAAM,YAAY,EAAE,SAASC,CAAC,CAAC,GACvDH,EAAE,aAAa,IAAIE,EAAO,YAAY,EAI1C,OAAAF,EAAE,KAAO,GACFA,EAAE,SAAS,CACpB,MAAQ,CACN,OAAOD,CACT,CACF,CAGA,SAASvB,EAAoB4B,EAAqB,CAChD,GAAI,CAACA,EAAK,MAAO,SACjB,GAAIA,IAAQ,QAAUA,IAAQ,UAAYA,IAAQ,QAAUA,IAAQ,OAAQ,OAAOA,EACnF,GAAI,CACF,OAAO,IAAI,IAAIA,CAAG,EAAE,MACtB,MAAQ,CACN,OAAOA,EAAI,MAAM,EAAG,GAAG,CACzB,CACF,CAEA,SAASR,GAA4C,CACnD,IAAMS,EAA8B,CAAC,EACrC,GAAI,CACFA,EAAI,UAAY,UAAU,UAC1BA,EAAI,SAAW,UAAU,SACzBA,EAAI,SAAW,GAAG,OAAO,UAAU,IAAI,OAAO,WAAW,GACzDA,EAAI,OAAS,GAAG,OAAO,KAAK,IAAI,OAAO,MAAM,EAC/C,MAAQ,CAER,CACA,OAAOA,CACT,CAGA,IAAMC,EAAU,IAAInC,EACboC,EAAQD","names":["index_exports","__export","Upgoose","index_default","upgoose","__toCommonJS","teardowns","setupBreadcrumbs","config","addBreadcrumb","setupConsole","setupClicks","setupNavigation","setupFetch","setupXHR","setupCSP","teardownBreadcrumbs","fn","add","original","level","orig","args","handler","event","target","tag","text","selector","getSelector","origPush","origReplace","popHandler","origFetch","input","init","rawUrl","method","start","url","scrubUrl","response","err","origOpen","origSend","u","sensitive","param","s","directive","el","parts","current","i","part","cls","originalFetch","sendEvent","config","payload","url","body","originalFetch","sendConnect","sendDeploy","release","Upgoose","event","error","directive","blocked","normalizeBlockedURI","config","setupBreadcrumbs","crumb","transport","sendConnect","sendDeploy","extra","message","stack","normalizeError","level","user","key","value","max","teardownBreadcrumbs","partial","payload","stripUrlSensitiveParams","getBrowserContext","processed","sendEvent","url","u","sensitive","param","s","uri","ctx","upgoose","index_default"]}
package/dist/index.d.cts CHANGED
@@ -23,10 +23,12 @@ interface UpgooseConfig {
23
23
  captureNetwork?: boolean;
24
24
  /** Enable global error handlers (default: true) */
25
25
  captureUnhandled?: boolean;
26
+ /** Capture Content-Security-Policy violations as events (default: false) */
27
+ captureCSP?: boolean;
26
28
  }
27
29
  interface Breadcrumb {
28
30
  timestamp: number;
29
- category: "ui" | "navigation" | "console" | "fetch" | "xhr";
31
+ category: "ui" | "navigation" | "console" | "fetch" | "xhr" | "csp";
30
32
  message: string;
31
33
  level?: "info" | "warning" | "error";
32
34
  data?: Record<string, unknown>;
@@ -118,8 +120,11 @@ declare class Upgoose {
118
120
  destroy(): void;
119
121
  private onError;
120
122
  private onUnhandledRejection;
123
+ private onCSPViolation;
121
124
  private setupGlobalHandlers;
122
125
  private removeGlobalHandlers;
126
+ private setupCSPHandler;
127
+ private removeCSPHandler;
123
128
  private send;
124
129
  }
125
130
  declare const upgoose: Upgoose;
package/dist/index.d.ts CHANGED
@@ -23,10 +23,12 @@ interface UpgooseConfig {
23
23
  captureNetwork?: boolean;
24
24
  /** Enable global error handlers (default: true) */
25
25
  captureUnhandled?: boolean;
26
+ /** Capture Content-Security-Policy violations as events (default: false) */
27
+ captureCSP?: boolean;
26
28
  }
27
29
  interface Breadcrumb {
28
30
  timestamp: number;
29
- category: "ui" | "navigation" | "console" | "fetch" | "xhr";
31
+ category: "ui" | "navigation" | "console" | "fetch" | "xhr" | "csp";
30
32
  message: string;
31
33
  level?: "info" | "warning" | "error";
32
34
  data?: Record<string, unknown>;
@@ -118,8 +120,11 @@ declare class Upgoose {
118
120
  destroy(): void;
119
121
  private onError;
120
122
  private onUnhandledRejection;
123
+ private onCSPViolation;
121
124
  private setupGlobalHandlers;
122
125
  private removeGlobalHandlers;
126
+ private setupCSPHandler;
127
+ private removeCSPHandler;
123
128
  private send;
124
129
  }
125
130
  declare const upgoose: Upgoose;
package/dist/index.js CHANGED
@@ -1,2 +1,2 @@
1
- var i=[];function p(r,t){r.captureConsole!==!1&&y(t),r.captureClicks!==!1&&v(t),r.captureNavigation!==!1&&w(t),r.captureNetwork!==!1&&(b(t),k(t))}function d(){for(let r of i)try{r()}catch{}i.length=0}function y(r){let t={log:console.log,warn:console.warn,error:console.error};for(let e of["log","warn","error"]){let n=t[e];console[e]=(...s)=>{r({timestamp:Date.now(),category:"console",message:s.map(String).join(" ").slice(0,500),level:e==="error"?"error":e==="warn"?"warning":"info"}),n.apply(console,s)}}i.push(()=>{console.log=t.log,console.warn=t.warn,console.error=t.error})}function v(r){let t=e=>{let n=e.target;if(!n)return;let s=n.tagName?.toLowerCase(),o=n.textContent?.trim().slice(0,100)||"",a=E(n);r({timestamp:Date.now(),category:"ui",message:`click on ${s}${o?`: "${o}"`:""}`,data:{selector:a}})};document.addEventListener("click",t,{capture:!0,passive:!0}),i.push(()=>document.removeEventListener("click",t,{capture:!0}))}function w(r){let t=history.pushState;history.pushState=function(...s){return r({timestamp:Date.now(),category:"navigation",message:String(s[2]||""),data:{from:location.href}}),t.apply(this,s)};let e=history.replaceState;history.replaceState=function(...s){return r({timestamp:Date.now(),category:"navigation",message:String(s[2]||""),data:{from:location.href}}),e.apply(this,s)};let n=()=>{r({timestamp:Date.now(),category:"navigation",message:location.href})};window.addEventListener("popstate",n),i.push(()=>{history.pushState=t,history.replaceState=e,window.removeEventListener("popstate",n)})}function b(r){let t=window.fetch;window.fetch=async function(e,n){let s=typeof e=="string"?e:e instanceof URL?e.href:e.url,o=n?.method||"GET",a=Date.now();if(s.includes("/public/errors"))return t.call(window,e,n);let u=g(s);try{let c=await t.call(window,e,n);return r({timestamp:Date.now(),category:"fetch",message:`${o} ${u}`,level:c.ok?"info":"error",data:{status:c.status,duration:Date.now()-a}}),c}catch(c){throw r({timestamp:Date.now(),category:"fetch",message:`${o} ${u}`,level:"error",data:{error:String(c),duration:Date.now()-a}}),c}},i.push(()=>{window.fetch=t})}function k(r){let t=XMLHttpRequest.prototype.open,e=XMLHttpRequest.prototype.send;XMLHttpRequest.prototype.open=function(n,s){return this.__ug_method=n,this.__ug_url=String(s),t.apply(this,arguments)},XMLHttpRequest.prototype.send=function(){let n=Date.now(),s=this.__ug_method||"GET",o=this.__ug_url||"";if(o.includes("/public/errors"))return e.apply(this,arguments);let a=g(o);return this.addEventListener("loadend",function(){r({timestamp:Date.now(),category:"xhr",message:`${s} ${a}`,level:this.status>=400?"error":"info",data:{status:this.status,duration:Date.now()-n}})}),e.apply(this,arguments)},i.push(()=>{XMLHttpRequest.prototype.open=t,XMLHttpRequest.prototype.send=e})}function g(r){try{let t=new URL(r,location.origin),e=["token","key","secret","password","auth","code","session","jwt","access_token","refresh_token","api_key","apikey"];for(let n of[...t.searchParams.keys()])e.some(s=>n.toLowerCase().includes(s))&&t.searchParams.set(n,"[REDACTED]");return t.hash="",t.toString()}catch{return r}}function E(r){let t=[],e=r;for(let n=0;n<3&&e&&e!==document.body;n++){let s=e.tagName.toLowerCase();if(e.id)s+=`#${e.id}`;else if(e.className&&typeof e.className=="string"){let o=e.className.trim().split(/\s+/).slice(0,2).join(".");o&&(s+=`.${o}`)}t.unshift(s),e=e.parentElement}return t.join(" > ")}var h=typeof window<"u"?window.fetch.bind(window):(()=>{});function f(r,t){let e=`${r.apiUrl}/public/errors`,n=JSON.stringify(t);try{h(e,{method:"POST",headers:{"Content-Type":"application/json","X-API-Key":r.apiKey},body:n,keepalive:!0}).catch(()=>{})}catch{}}function m(r,t){let e=`${r.apiUrl}/public/deploys`,n=JSON.stringify({release:t});try{h(e,{method:"POST",headers:{"Content-Type":"application/json","X-API-Key":r.apiKey},body:n,keepalive:!0}).catch(()=>{})}catch{}}var l=class{constructor(){this.config=null;this.breadcrumbs=[];this.user=null;this.tags={};this.initialized=!1;this.sending=!1;this.onError=t=>{let e=t.error||new Error(t.message||"Unknown error");this.captureException(e)};this.onUnhandledRejection=t=>{let e=t.reason;this.captureException(e)}}init(t){if(this.initialized){console.warn("[Upgoose] Already initialized. Call destroy() first to re-initialize.");return}if(!t.apiKey||!t.apiUrl){console.error("[Upgoose] apiKey and apiUrl are required.");return}this.config={sampleRate:1,maxBreadcrumbs:50,captureConsole:!0,captureClicks:!0,captureNavigation:!0,captureNetwork:!0,captureUnhandled:!0,...t},this.breadcrumbs=[],this.initialized=!0,p(this.config,e=>this.addBreadcrumb(e)),this.config.captureUnhandled&&this.setupGlobalHandlers(),this.config.release&&m({apiKey:this.config.apiKey,apiUrl:this.config.apiUrl},this.config.release)}captureException(t,e){if(!this.config)return;let{message:n,stack:s}=C(t);this.send({message:n,stack:s,level:e?.level||"error",tags:e?.tags,fingerprint:e?.fingerprint})}test(){if(!this.config){console.warn("[Upgoose] Not initialized. Call init() first.");return}this.send({message:"Upgoose test event \u2014 integration verified",level:"warning",tags:{upgoose_test:"true"},fingerprint:"upgoose-test-event"}),console.info("[Upgoose] Test event sent. Check your project page.")}captureMessage(t,e="error"){this.config&&this.send({message:t,level:e})}setUser(t){this.user=t}setTag(t,e){this.tags[t]=e}addBreadcrumb(t){let e=this.config?.maxBreadcrumbs??50;this.breadcrumbs.push(t),this.breadcrumbs.length>e&&(this.breadcrumbs=this.breadcrumbs.slice(-e))}destroy(){d(),this.removeGlobalHandlers(),this.config=null,this.breadcrumbs=[],this.user=null,this.tags={},this.initialized=!1}setupGlobalHandlers(){window.addEventListener("error",this.onError),window.addEventListener("unhandledrejection",this.onUnhandledRejection)}removeGlobalHandlers(){window.removeEventListener("error",this.onError),window.removeEventListener("unhandledrejection",this.onUnhandledRejection)}send(t){let e=this.config;if(e&&!this.sending){this.sending=!0;try{if(typeof e.sampleRate=="number"&&Math.random()>e.sampleRate)return;let n={message:t.message.slice(0,2e3),stack:t.stack?.slice(0,1e4),level:t.level,environment:e.environment,release:e.release,tags:{...this.tags,...t.tags},context:{breadcrumbs:this.breadcrumbs.slice(-30),...this.user?{user:this.user}:{},page:{url:U(location.href),title:document.title},browser:L()},fingerprint:t.fingerprint},s=n;if(e.beforeSend)try{s=e.beforeSend(n)}catch{return}if(!s)return;f({apiKey:e.apiKey,apiUrl:e.apiUrl},s)}finally{this.sending=!1}}}};function C(r){return r instanceof Error?{message:r.message,stack:r.stack}:typeof r=="string"?{message:r}:r&&typeof r=="object"&&"message"in r?{message:String(r.message),stack:"stack"in r?String(r.stack):void 0}:{message:String(r)}}function U(r){try{let t=new URL(r),e=["token","key","secret","password","auth","code","session","jwt","access_token","refresh_token","api_key","apikey"];for(let n of[...t.searchParams.keys()])e.some(s=>n.toLowerCase().includes(s))&&t.searchParams.set(n,"[REDACTED]");return t.hash="",t.toString()}catch{return r}}function L(){let r={};try{r.userAgent=navigator.userAgent,r.language=navigator.language,r.viewport=`${window.innerWidth}x${window.innerHeight}`,r.screen=`${screen.width}x${screen.height}`}catch{}return r}var R=new l,_=R;export{l as Upgoose,_ as default,R as upgoose};
1
+ var i=[];function u(n,t){n.captureConsole!==!1&&v(t),n.captureClicks!==!1&&w(t),n.captureNavigation!==!1&&b(t),n.captureNetwork!==!1&&(k(t),C(t)),n.captureCSP&&E(t)}function g(){for(let n of i)try{n()}catch{}i.length=0}function v(n){let t={log:console.log,warn:console.warn,error:console.error};for(let e of["log","warn","error"]){let r=t[e];console[e]=(...o)=>{n({timestamp:Date.now(),category:"console",message:o.map(String).join(" ").slice(0,500),level:e==="error"?"error":e==="warn"?"warning":"info"}),r.apply(console,o)}}i.push(()=>{console.log=t.log,console.warn=t.warn,console.error=t.error})}function w(n){let t=e=>{let r=e.target;if(!r)return;let o=r.tagName?.toLowerCase(),s=r.textContent?.trim().slice(0,100)||"",a=U(r);n({timestamp:Date.now(),category:"ui",message:`click on ${o}${s?`: "${s}"`:""}`,data:{selector:a}})};document.addEventListener("click",t,{capture:!0,passive:!0}),i.push(()=>document.removeEventListener("click",t,{capture:!0}))}function b(n){let t=history.pushState;history.pushState=function(...o){return n({timestamp:Date.now(),category:"navigation",message:String(o[2]||""),data:{from:location.href}}),t.apply(this,o)};let e=history.replaceState;history.replaceState=function(...o){return n({timestamp:Date.now(),category:"navigation",message:String(o[2]||""),data:{from:location.href}}),e.apply(this,o)};let r=()=>{n({timestamp:Date.now(),category:"navigation",message:location.href})};window.addEventListener("popstate",r),i.push(()=>{history.pushState=t,history.replaceState=e,window.removeEventListener("popstate",r)})}function k(n){let t=window.fetch;window.fetch=async function(e,r){let o=typeof e=="string"?e:e instanceof URL?e.href:e.url,s=r?.method||"GET",a=Date.now();if(o.includes("/public/errors"))return t.call(window,e,r);let d=h(o);try{let c=await t.call(window,e,r);return n({timestamp:Date.now(),category:"fetch",message:`${s} ${d}`,level:c.ok?"info":"error",data:{status:c.status,duration:Date.now()-a}}),c}catch(c){throw n({timestamp:Date.now(),category:"fetch",message:`${s} ${d}`,level:"error",data:{error:String(c),duration:Date.now()-a}}),c}},i.push(()=>{window.fetch=t})}function C(n){let t=XMLHttpRequest.prototype.open,e=XMLHttpRequest.prototype.send;XMLHttpRequest.prototype.open=function(r,o){return this.__ug_method=r,this.__ug_url=String(o),t.apply(this,arguments)},XMLHttpRequest.prototype.send=function(){let r=Date.now(),o=this.__ug_method||"GET",s=this.__ug_url||"";if(s.includes("/public/errors"))return e.apply(this,arguments);let a=h(s);return this.addEventListener("loadend",function(){n({timestamp:Date.now(),category:"xhr",message:`${o} ${a}`,level:this.status>=400?"error":"info",data:{status:this.status,duration:Date.now()-r}})}),e.apply(this,arguments)},i.push(()=>{XMLHttpRequest.prototype.open=t,XMLHttpRequest.prototype.send=e})}function h(n){try{let t=new URL(n,location.origin),e=["token","key","secret","password","auth","code","session","jwt","access_token","refresh_token","api_key","apikey"];for(let r of[...t.searchParams.keys()])e.some(o=>r.toLowerCase().includes(o))&&t.searchParams.set(r,"[REDACTED]");return t.hash="",t.toString()}catch{return n}}function E(n){let t=e=>{let r=e.effectiveDirective||e.violatedDirective||"unknown";n({timestamp:Date.now(),category:"csp",message:`${r} blocked ${e.blockedURI||"inline content"}`,level:"warning",data:{blockedURI:e.blockedURI,documentURI:e.documentURI,disposition:e.disposition}})};document.addEventListener("securitypolicyviolation",t),i.push(()=>document.removeEventListener("securitypolicyviolation",t))}function U(n){let t=[],e=n;for(let r=0;r<3&&e&&e!==document.body;r++){let o=e.tagName.toLowerCase();if(e.id)o+=`#${e.id}`;else if(e.className&&typeof e.className=="string"){let s=e.className.trim().split(/\s+/).slice(0,2).join(".");s&&(o+=`.${s}`)}t.unshift(o),e=e.parentElement}return t.join(" > ")}var l=typeof window<"u"?window.fetch.bind(window):(()=>{});function f(n,t){let e=`${n.apiUrl}/public/errors`,r=JSON.stringify(t);try{l(e,{method:"POST",headers:{"Content-Type":"application/json","X-API-Key":n.apiKey},body:r,keepalive:!0}).catch(()=>{})}catch{}}function m(n){let t=`${n.apiUrl}/public/connect`;try{l(t,{method:"POST",headers:{"Content-Type":"application/json","X-API-Key":n.apiKey},body:"{}",keepalive:!0}).catch(()=>{})}catch{}}function y(n,t){let e=`${n.apiUrl}/public/deploys`,r=JSON.stringify({release:t});try{l(e,{method:"POST",headers:{"Content-Type":"application/json","X-API-Key":n.apiKey},body:r,keepalive:!0}).catch(()=>{})}catch{}}var p=class{constructor(){this.config=null;this.breadcrumbs=[];this.user=null;this.tags={};this.initialized=!1;this.sending=!1;this.onError=t=>{let e=t.error||new Error(t.message||"Unknown error");this.captureException(e)};this.onUnhandledRejection=t=>{let e=t.reason;this.captureException(e)};this.onCSPViolation=t=>{let e=t.effectiveDirective||t.violatedDirective||"unknown",r=R(t.blockedURI);this.send({message:`CSP violation: ${e} blocked ${t.blockedURI||"inline content"}`,level:"warning",tags:{"csp.directive":e,"csp.disposition":t.disposition||"enforce","csp.blocked_origin":r},fingerprint:`csp:${e}:${r}`})}}init(t){if(this.initialized){console.warn("[Upgoose] Already initialized. Call destroy() first to re-initialize.");return}if(!t.apiKey||!t.apiUrl){console.error("[Upgoose] apiKey and apiUrl are required.");return}this.config={sampleRate:1,maxBreadcrumbs:50,captureConsole:!0,captureClicks:!0,captureNavigation:!0,captureNetwork:!0,captureUnhandled:!0,...t},this.breadcrumbs=[],this.initialized=!0,u(this.config,r=>this.addBreadcrumb(r)),this.config.captureUnhandled&&this.setupGlobalHandlers(),this.config.captureCSP&&this.setupCSPHandler();let e={apiKey:this.config.apiKey,apiUrl:this.config.apiUrl};m(e),this.config.release&&y(e,this.config.release)}captureException(t,e){if(!this.config)return;let{message:r,stack:o}=S(t);this.send({message:r,stack:o,level:e?.level||"error",tags:e?.tags,fingerprint:e?.fingerprint})}test(){if(!this.config){console.warn("[Upgoose] Not initialized. Call init() first.");return}this.send({message:"Upgoose test event \u2014 integration verified",level:"warning",tags:{upgoose_test:"true"},fingerprint:"upgoose-test-event"}),console.info("[Upgoose] Test event sent. Check your project page.")}captureMessage(t,e="error"){this.config&&this.send({message:t,level:e})}setUser(t){this.user=t}setTag(t,e){this.tags[t]=e}addBreadcrumb(t){let e=this.config?.maxBreadcrumbs??50;this.breadcrumbs.push(t),this.breadcrumbs.length>e&&(this.breadcrumbs=this.breadcrumbs.slice(-e))}destroy(){g(),this.removeGlobalHandlers(),this.removeCSPHandler(),this.config=null,this.breadcrumbs=[],this.user=null,this.tags={},this.initialized=!1}setupGlobalHandlers(){window.addEventListener("error",this.onError),window.addEventListener("unhandledrejection",this.onUnhandledRejection)}removeGlobalHandlers(){window.removeEventListener("error",this.onError),window.removeEventListener("unhandledrejection",this.onUnhandledRejection)}setupCSPHandler(){document.addEventListener("securitypolicyviolation",this.onCSPViolation)}removeCSPHandler(){document.removeEventListener("securitypolicyviolation",this.onCSPViolation)}send(t){let e=this.config;if(e&&!this.sending){this.sending=!0;try{if(typeof e.sampleRate=="number"&&Math.random()>e.sampleRate)return;let r={message:t.message.slice(0,2e3),stack:t.stack?.slice(0,1e4),level:t.level,environment:e.environment,release:e.release,tags:{...this.tags,...t.tags},context:{breadcrumbs:this.breadcrumbs.slice(-30),...this.user?{user:this.user}:{},page:{url:P(location.href),title:document.title},browser:L()},fingerprint:t.fingerprint},o=r;if(e.beforeSend)try{o=e.beforeSend(r)}catch{return}if(!o)return;f({apiKey:e.apiKey,apiUrl:e.apiUrl},o)}finally{this.sending=!1}}}};function S(n){return n instanceof Error?{message:n.message,stack:n.stack}:typeof n=="string"?{message:n}:n&&typeof n=="object"&&"message"in n?{message:String(n.message),stack:"stack"in n?String(n.stack):void 0}:{message:String(n)}}function P(n){try{let t=new URL(n),e=["token","key","secret","password","auth","code","session","jwt","access_token","refresh_token","api_key","apikey"];for(let r of[...t.searchParams.keys()])e.some(o=>r.toLowerCase().includes(o))&&t.searchParams.set(r,"[REDACTED]");return t.hash="",t.toString()}catch{return n}}function R(n){if(!n)return"inline";if(n==="eval"||n==="inline"||n==="data"||n==="blob")return n;try{return new URL(n).origin}catch{return n.slice(0,100)}}function L(){let n={};try{n.userAgent=navigator.userAgent,n.language=navigator.language,n.viewport=`${window.innerWidth}x${window.innerHeight}`,n.screen=`${screen.width}x${screen.height}`}catch{}return n}var x=new p,B=x;export{p as Upgoose,B as default,x as upgoose};
2
2
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/breadcrumbs.ts","../src/transport.ts","../src/index.ts"],"sourcesContent":["import type { Breadcrumb, UpgooseConfig } from \"./types\";\n\ntype BreadcrumbCallback = (crumb: Breadcrumb) => void;\n\nconst teardowns: Array<() => void> = [];\n\nexport function setupBreadcrumbs(\n config: UpgooseConfig,\n addBreadcrumb: BreadcrumbCallback\n): void {\n if (config.captureConsole !== false) {\n setupConsole(addBreadcrumb);\n }\n if (config.captureClicks !== false) {\n setupClicks(addBreadcrumb);\n }\n if (config.captureNavigation !== false) {\n setupNavigation(addBreadcrumb);\n }\n if (config.captureNetwork !== false) {\n setupFetch(addBreadcrumb);\n setupXHR(addBreadcrumb);\n }\n}\n\nexport function teardownBreadcrumbs(): void {\n for (const fn of teardowns) {\n try {\n fn();\n } catch {\n // Best effort cleanup\n }\n }\n teardowns.length = 0;\n}\n\nfunction setupConsole(add: BreadcrumbCallback): void {\n const original = {\n log: console.log,\n warn: console.warn,\n error: console.error,\n };\n\n for (const level of [\"log\", \"warn\", \"error\"] as const) {\n const orig = original[level];\n console[level] = (...args: unknown[]) => {\n add({\n timestamp: Date.now(),\n category: \"console\",\n message: args.map(String).join(\" \").slice(0, 500),\n level: level === \"error\" ? \"error\" : level === \"warn\" ? \"warning\" : \"info\",\n });\n orig.apply(console, args);\n };\n }\n\n teardowns.push(() => {\n console.log = original.log;\n console.warn = original.warn;\n console.error = original.error;\n });\n}\n\nfunction setupClicks(add: BreadcrumbCallback): void {\n const handler = (event: MouseEvent) => {\n const target = event.target as HTMLElement | null;\n if (!target) return;\n\n const tag = target.tagName?.toLowerCase();\n const text = target.textContent?.trim().slice(0, 100) || \"\";\n const selector = getSelector(target);\n\n add({\n timestamp: Date.now(),\n category: \"ui\",\n message: `click on ${tag}${text ? `: \"${text}\"` : \"\"}`,\n data: { selector },\n });\n };\n\n document.addEventListener(\"click\", handler, { capture: true, passive: true });\n teardowns.push(() => document.removeEventListener(\"click\", handler, { capture: true }));\n}\n\nfunction setupNavigation(add: BreadcrumbCallback): void {\n // pushState\n const origPush = history.pushState;\n history.pushState = function (...args) {\n add({\n timestamp: Date.now(),\n category: \"navigation\",\n message: String(args[2] || \"\"),\n data: { from: location.href },\n });\n return origPush.apply(this, args);\n };\n\n // replaceState\n const origReplace = history.replaceState;\n history.replaceState = function (...args) {\n add({\n timestamp: Date.now(),\n category: \"navigation\",\n message: String(args[2] || \"\"),\n data: { from: location.href },\n });\n return origReplace.apply(this, args);\n };\n\n // popstate (back/forward)\n const popHandler = () => {\n add({\n timestamp: Date.now(),\n category: \"navigation\",\n message: location.href,\n });\n };\n window.addEventListener(\"popstate\", popHandler);\n\n teardowns.push(() => {\n history.pushState = origPush;\n history.replaceState = origReplace;\n window.removeEventListener(\"popstate\", popHandler);\n });\n}\n\nfunction setupFetch(add: BreadcrumbCallback): void {\n const origFetch = window.fetch;\n\n window.fetch = async function (input, init?) {\n const rawUrl = typeof input === \"string\" ? input : input instanceof URL ? input.href : input.url;\n const method = init?.method || \"GET\";\n const start = Date.now();\n\n // Skip breadcrumbs for SDK's own requests\n if (rawUrl.includes(\"/public/errors\")) {\n return origFetch.call(window, input, init);\n }\n\n const url = scrubUrl(rawUrl);\n\n try {\n const response = await origFetch.call(window, input, init);\n add({\n timestamp: Date.now(),\n category: \"fetch\",\n message: `${method} ${url}`,\n level: response.ok ? \"info\" : \"error\",\n data: {\n status: response.status,\n duration: Date.now() - start,\n },\n });\n return response;\n } catch (err) {\n add({\n timestamp: Date.now(),\n category: \"fetch\",\n message: `${method} ${url}`,\n level: \"error\",\n data: {\n error: String(err),\n duration: Date.now() - start,\n },\n });\n throw err;\n }\n };\n\n teardowns.push(() => {\n window.fetch = origFetch;\n });\n}\n\nfunction setupXHR(add: BreadcrumbCallback): void {\n const origOpen = XMLHttpRequest.prototype.open;\n const origSend = XMLHttpRequest.prototype.send;\n\n XMLHttpRequest.prototype.open = function (method: string, url: string | URL) {\n (this as any).__ug_method = method;\n (this as any).__ug_url = String(url);\n // eslint-disable-next-line prefer-rest-params\n return origOpen.apply(this, arguments as any);\n };\n\n XMLHttpRequest.prototype.send = function () {\n const start = Date.now();\n const method = (this as any).__ug_method || \"GET\";\n const rawUrl: string = (this as any).__ug_url || \"\";\n\n // Skip breadcrumbs for SDK's own requests\n if (rawUrl.includes(\"/public/errors\")) {\n return origSend.apply(this, arguments as any);\n }\n\n const url = scrubUrl(rawUrl);\n\n this.addEventListener(\"loadend\", function () {\n add({\n timestamp: Date.now(),\n category: \"xhr\",\n message: `${method} ${url}`,\n level: this.status >= 400 ? \"error\" : \"info\",\n data: {\n status: this.status,\n duration: Date.now() - start,\n },\n });\n });\n\n // eslint-disable-next-line prefer-rest-params\n return origSend.apply(this, arguments as any);\n };\n\n teardowns.push(() => {\n XMLHttpRequest.prototype.open = origOpen;\n XMLHttpRequest.prototype.send = origSend;\n });\n}\n\n/** Strip sensitive query params from URLs in breadcrumbs */\nfunction scrubUrl(url: string): string {\n try {\n const u = new URL(url, location.origin);\n const sensitive = [\"token\", \"key\", \"secret\", \"password\", \"auth\", \"code\", \"session\", \"jwt\", \"access_token\", \"refresh_token\", \"api_key\", \"apikey\"];\n for (const param of [...u.searchParams.keys()]) {\n if (sensitive.some((s) => param.toLowerCase().includes(s))) {\n u.searchParams.set(param, \"[REDACTED]\");\n }\n }\n u.hash = \"\";\n return u.toString();\n } catch {\n return url;\n }\n}\n\nfunction getSelector(el: HTMLElement): string {\n const parts: string[] = [];\n let current: HTMLElement | null = el;\n\n for (let i = 0; i < 3 && current && current !== document.body; i++) {\n let part = current.tagName.toLowerCase();\n if (current.id) {\n part += `#${current.id}`;\n } else if (current.className && typeof current.className === \"string\") {\n const cls = current.className.trim().split(/\\s+/).slice(0, 2).join(\".\");\n if (cls) part += `.${cls}`;\n }\n parts.unshift(part);\n current = current.parentElement;\n }\n\n return parts.join(\" > \");\n}\n","import type { ErrorPayload } from \"./types\";\n\n// Capture the original fetch before any instrumentation wraps it.\n// This prevents SDK requests from appearing in breadcrumbs and\n// avoids recursive loops if the fetch wrapper itself errors.\nconst originalFetch: typeof fetch =\n typeof window !== \"undefined\" ? window.fetch.bind(window) : ((() => {}) as any);\n\nexport interface TransportConfig {\n apiKey: string;\n apiUrl: string;\n}\n\n/**\n * Must be called BEFORE setupBreadcrumbs() to capture the\n * un-instrumented fetch reference.\n */\nexport function captureOriginalFetch(): void {\n // Already captured at module load time above.\n // This function exists as a documentation marker.\n}\n\nexport function sendEvent(\n config: TransportConfig,\n payload: ErrorPayload\n): void {\n const url = `${config.apiUrl}/public/errors`;\n const body = JSON.stringify(payload);\n\n try {\n originalFetch(url, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"X-API-Key\": config.apiKey,\n },\n body,\n keepalive: true, // Survives page unload\n }).catch(() => {\n // Silently fail — never impact user's app\n });\n } catch {\n // Silently fail\n }\n}\n\n/**\n * Notify the server that this release is running. Triggers auto-resolve\n * of error groups from older releases. Fire-and-forget; the server\n * deduplicates via IS DISTINCT FROM so calling on every init() is safe.\n */\nexport function sendDeploy(\n config: TransportConfig,\n release: string\n): void {\n const url = `${config.apiUrl}/public/deploys`;\n const body = JSON.stringify({ release });\n\n try {\n originalFetch(url, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"X-API-Key\": config.apiKey,\n },\n body,\n keepalive: true,\n }).catch(() => {\n // Silently fail\n });\n } catch {\n // Silently fail\n }\n}\n","import type {\n UpgooseConfig,\n Breadcrumb,\n UserContext,\n ErrorPayload,\n} from \"./types\";\nimport { setupBreadcrumbs, teardownBreadcrumbs } from \"./breadcrumbs\";\nimport { sendEvent, sendDeploy } from \"./transport\";\n\nexport type {\n UpgooseConfig,\n Breadcrumb,\n UserContext,\n ErrorPayload,\n};\n\nclass Upgoose {\n private config: UpgooseConfig | null = null;\n private breadcrumbs: Breadcrumb[] = [];\n private user: UserContext | null = null;\n private tags: Record<string, string> = {};\n private initialized = false;\n private sending = false; // re-entrancy guard\n\n /**\n * Initialize the Upgoose SDK. Call this once, as early as possible.\n *\n * ```ts\n * import upgoose from '@upgoose/browser';\n * upgoose.init({ apiKey: 'ug_...', apiUrl: 'https://api.upgoose.app' });\n * ```\n */\n init(config: UpgooseConfig): void {\n if (this.initialized) {\n console.warn(\"[Upgoose] Already initialized. Call destroy() first to re-initialize.\");\n return;\n }\n\n if (!config.apiKey || !config.apiUrl) {\n console.error(\"[Upgoose] apiKey and apiUrl are required.\");\n return;\n }\n\n this.config = {\n sampleRate: 1,\n maxBreadcrumbs: 50,\n captureConsole: true,\n captureClicks: true,\n captureNavigation: true,\n captureNetwork: true,\n captureUnhandled: true,\n ...config,\n };\n\n this.breadcrumbs = [];\n this.initialized = true;\n\n // Set up breadcrumb collection\n setupBreadcrumbs(this.config, (crumb) => this.addBreadcrumb(crumb));\n\n // Set up global error handlers\n if (this.config.captureUnhandled) {\n this.setupGlobalHandlers();\n }\n\n // Notify server of this release so it can auto-resolve older error groups\n if (this.config.release) {\n sendDeploy(\n { apiKey: this.config.apiKey, apiUrl: this.config.apiUrl },\n this.config.release\n );\n }\n }\n\n /**\n * Capture an exception manually.\n *\n * ```ts\n * try {\n * riskyOperation();\n * } catch (err) {\n * upgoose.captureException(err);\n * }\n * ```\n */\n captureException(\n error: unknown,\n extra?: { tags?: Record<string, string>; fingerprint?: string; level?: ErrorPayload[\"level\"] }\n ): void {\n if (!this.config) return;\n\n const { message, stack } = normalizeError(error);\n this.send({\n message,\n stack,\n level: extra?.level || \"error\",\n tags: extra?.tags,\n fingerprint: extra?.fingerprint,\n });\n }\n\n /**\n * Send a test event to verify your integration is working.\n * Check your project page — the status should flip to \"CONNECTED\" within seconds.\n *\n * ```ts\n * upgoose.test();\n * ```\n */\n test(): void {\n if (!this.config) {\n console.warn(\"[Upgoose] Not initialized. Call init() first.\");\n return;\n }\n this.send({\n message: \"Upgoose test event — integration verified\",\n level: \"warning\",\n tags: { upgoose_test: \"true\" },\n fingerprint: \"upgoose-test-event\",\n });\n console.info(\"[Upgoose] Test event sent. Check your project page.\");\n }\n\n /**\n * Capture a message as an error event.\n *\n * ```ts\n * upgoose.captureMessage(\"User hit rate limit\", \"warning\");\n * ```\n */\n captureMessage(\n message: string,\n level: ErrorPayload[\"level\"] = \"error\"\n ): void {\n if (!this.config) return;\n this.send({ message, level });\n }\n\n /**\n * Set user context. Attached to all subsequent events.\n *\n * ```ts\n * upgoose.setUser({ id: '123', email: 'user@example.com' });\n * ```\n */\n setUser(user: UserContext | null): void {\n this.user = user;\n }\n\n /**\n * Set a global tag. Attached to all subsequent events.\n */\n setTag(key: string, value: string): void {\n this.tags[key] = value;\n }\n\n /**\n * Add a breadcrumb manually.\n */\n addBreadcrumb(crumb: Breadcrumb): void {\n const max = this.config?.maxBreadcrumbs ?? 50;\n this.breadcrumbs.push(crumb);\n if (this.breadcrumbs.length > max) {\n this.breadcrumbs = this.breadcrumbs.slice(-max);\n }\n }\n\n /**\n * Tear down the SDK — removes global handlers and breadcrumb instrumentation.\n */\n destroy(): void {\n teardownBreadcrumbs();\n this.removeGlobalHandlers();\n this.config = null;\n this.breadcrumbs = [];\n this.user = null;\n this.tags = {};\n this.initialized = false;\n }\n\n // -- Private --\n\n private onError = (event: ErrorEvent): void => {\n const error = event.error || new Error(event.message || \"Unknown error\");\n this.captureException(error);\n };\n\n private onUnhandledRejection = (event: PromiseRejectionEvent): void => {\n const error = event.reason;\n this.captureException(error);\n };\n\n private setupGlobalHandlers(): void {\n window.addEventListener(\"error\", this.onError);\n window.addEventListener(\"unhandledrejection\", this.onUnhandledRejection);\n }\n\n private removeGlobalHandlers(): void {\n window.removeEventListener(\"error\", this.onError);\n window.removeEventListener(\"unhandledrejection\", this.onUnhandledRejection);\n }\n\n private send(partial: {\n message: string;\n stack?: string;\n level: ErrorPayload[\"level\"];\n tags?: Record<string, string>;\n fingerprint?: string;\n }): void {\n const config = this.config;\n if (!config) return;\n\n // Re-entrancy guard: prevent infinite loops if send() itself throws\n // or if beforeSend throws and triggers the global error handler\n if (this.sending) return;\n this.sending = true;\n\n try {\n // Sample rate check\n if (typeof config.sampleRate === \"number\" && Math.random() > config.sampleRate) {\n return;\n }\n\n const payload: ErrorPayload = {\n message: partial.message.slice(0, 2000),\n stack: partial.stack?.slice(0, 10000),\n level: partial.level,\n environment: config.environment,\n release: config.release,\n tags: { ...this.tags, ...partial.tags },\n context: {\n breadcrumbs: this.breadcrumbs.slice(-30),\n ...(this.user ? { user: this.user } : {}),\n page: {\n url: stripUrlSensitiveParams(location.href),\n title: document.title,\n },\n browser: getBrowserContext(),\n },\n fingerprint: partial.fingerprint,\n };\n\n // beforeSend hook (wrapped to prevent throws from looping)\n let processed: ErrorPayload | null = payload;\n if (config.beforeSend) {\n try {\n processed = config.beforeSend(payload);\n } catch {\n // Drop event if beforeSend throws\n return;\n }\n }\n if (!processed) return;\n\n sendEvent(\n { apiKey: config.apiKey, apiUrl: config.apiUrl },\n processed\n );\n } finally {\n this.sending = false;\n }\n }\n}\n\nfunction normalizeError(error: unknown): { message: string; stack?: string } {\n if (error instanceof Error) {\n return { message: error.message, stack: error.stack };\n }\n if (typeof error === \"string\") {\n return { message: error };\n }\n if (error && typeof error === \"object\" && \"message\" in error) {\n return {\n message: String((error as { message: unknown }).message),\n stack: \"stack\" in error ? String((error as { stack: unknown }).stack) : undefined,\n };\n }\n return { message: String(error) };\n}\n\n/** Strip common sensitive query/hash params from URLs */\nfunction stripUrlSensitiveParams(url: string): string {\n try {\n const u = new URL(url);\n const sensitive = [\"token\", \"key\", \"secret\", \"password\", \"auth\", \"code\", \"session\", \"jwt\", \"access_token\", \"refresh_token\", \"api_key\", \"apikey\"];\n for (const param of [...u.searchParams.keys()]) {\n if (sensitive.some((s) => param.toLowerCase().includes(s))) {\n u.searchParams.set(param, \"[REDACTED]\");\n }\n }\n // Strip hash (often contains tokens in SPAs)\n u.hash = \"\";\n return u.toString();\n } catch {\n return url;\n }\n}\n\nfunction getBrowserContext(): Record<string, string> {\n const ctx: Record<string, string> = {};\n try {\n ctx.userAgent = navigator.userAgent;\n ctx.language = navigator.language;\n ctx.viewport = `${window.innerWidth}x${window.innerHeight}`;\n ctx.screen = `${screen.width}x${screen.height}`;\n } catch {\n // Ignore\n }\n return ctx;\n}\n\n// Singleton instance\nconst upgoose = new Upgoose();\nexport default upgoose;\nexport { upgoose, Upgoose };\n"],"mappings":"AAIA,IAAMA,EAA+B,CAAC,EAE/B,SAASC,EACdC,EACAC,EACM,CACFD,EAAO,iBAAmB,IAC5BE,EAAaD,CAAa,EAExBD,EAAO,gBAAkB,IAC3BG,EAAYF,CAAa,EAEvBD,EAAO,oBAAsB,IAC/BI,EAAgBH,CAAa,EAE3BD,EAAO,iBAAmB,KAC5BK,EAAWJ,CAAa,EACxBK,EAASL,CAAa,EAE1B,CAEO,SAASM,GAA4B,CAC1C,QAAWC,KAAMV,EACf,GAAI,CACFU,EAAG,CACL,MAAQ,CAER,CAEFV,EAAU,OAAS,CACrB,CAEA,SAASI,EAAaO,EAA+B,CACnD,IAAMC,EAAW,CACf,IAAK,QAAQ,IACb,KAAM,QAAQ,KACd,MAAO,QAAQ,KACjB,EAEA,QAAWC,IAAS,CAAC,MAAO,OAAQ,OAAO,EAAY,CACrD,IAAMC,EAAOF,EAASC,CAAK,EAC3B,QAAQA,CAAK,EAAI,IAAIE,IAAoB,CACvCJ,EAAI,CACF,UAAW,KAAK,IAAI,EACpB,SAAU,UACV,QAASI,EAAK,IAAI,MAAM,EAAE,KAAK,GAAG,EAAE,MAAM,EAAG,GAAG,EAChD,MAAOF,IAAU,QAAU,QAAUA,IAAU,OAAS,UAAY,MACtE,CAAC,EACDC,EAAK,MAAM,QAASC,CAAI,CAC1B,CACF,CAEAf,EAAU,KAAK,IAAM,CACnB,QAAQ,IAAMY,EAAS,IACvB,QAAQ,KAAOA,EAAS,KACxB,QAAQ,MAAQA,EAAS,KAC3B,CAAC,CACH,CAEA,SAASP,EAAYM,EAA+B,CAClD,IAAMK,EAAWC,GAAsB,CACrC,IAAMC,EAASD,EAAM,OACrB,GAAI,CAACC,EAAQ,OAEb,IAAMC,EAAMD,EAAO,SAAS,YAAY,EAClCE,EAAOF,EAAO,aAAa,KAAK,EAAE,MAAM,EAAG,GAAG,GAAK,GACnDG,EAAWC,EAAYJ,CAAM,EAEnCP,EAAI,CACF,UAAW,KAAK,IAAI,EACpB,SAAU,KACV,QAAS,YAAYQ,CAAG,GAAGC,EAAO,MAAMA,CAAI,IAAM,EAAE,GACpD,KAAM,CAAE,SAAAC,CAAS,CACnB,CAAC,CACH,EAEA,SAAS,iBAAiB,QAASL,EAAS,CAAE,QAAS,GAAM,QAAS,EAAK,CAAC,EAC5EhB,EAAU,KAAK,IAAM,SAAS,oBAAoB,QAASgB,EAAS,CAAE,QAAS,EAAK,CAAC,CAAC,CACxF,CAEA,SAASV,EAAgBK,EAA+B,CAEtD,IAAMY,EAAW,QAAQ,UACzB,QAAQ,UAAY,YAAaR,EAAM,CACrC,OAAAJ,EAAI,CACF,UAAW,KAAK,IAAI,EACpB,SAAU,aACV,QAAS,OAAOI,EAAK,CAAC,GAAK,EAAE,EAC7B,KAAM,CAAE,KAAM,SAAS,IAAK,CAC9B,CAAC,EACMQ,EAAS,MAAM,KAAMR,CAAI,CAClC,EAGA,IAAMS,EAAc,QAAQ,aAC5B,QAAQ,aAAe,YAAaT,EAAM,CACxC,OAAAJ,EAAI,CACF,UAAW,KAAK,IAAI,EACpB,SAAU,aACV,QAAS,OAAOI,EAAK,CAAC,GAAK,EAAE,EAC7B,KAAM,CAAE,KAAM,SAAS,IAAK,CAC9B,CAAC,EACMS,EAAY,MAAM,KAAMT,CAAI,CACrC,EAGA,IAAMU,EAAa,IAAM,CACvBd,EAAI,CACF,UAAW,KAAK,IAAI,EACpB,SAAU,aACV,QAAS,SAAS,IACpB,CAAC,CACH,EACA,OAAO,iBAAiB,WAAYc,CAAU,EAE9CzB,EAAU,KAAK,IAAM,CACnB,QAAQ,UAAYuB,EACpB,QAAQ,aAAeC,EACvB,OAAO,oBAAoB,WAAYC,CAAU,CACnD,CAAC,CACH,CAEA,SAASlB,EAAWI,EAA+B,CACjD,IAAMe,EAAY,OAAO,MAEzB,OAAO,MAAQ,eAAgBC,EAAOC,EAAO,CAC3C,IAAMC,EAAS,OAAOF,GAAU,SAAWA,EAAQA,aAAiB,IAAMA,EAAM,KAAOA,EAAM,IACvFG,EAASF,GAAM,QAAU,MACzBG,EAAQ,KAAK,IAAI,EAGvB,GAAIF,EAAO,SAAS,gBAAgB,EAClC,OAAOH,EAAU,KAAK,OAAQC,EAAOC,CAAI,EAG3C,IAAMI,EAAMC,EAASJ,CAAM,EAE3B,GAAI,CACF,IAAMK,EAAW,MAAMR,EAAU,KAAK,OAAQC,EAAOC,CAAI,EACzD,OAAAjB,EAAI,CACF,UAAW,KAAK,IAAI,EACpB,SAAU,QACV,QAAS,GAAGmB,CAAM,IAAIE,CAAG,GACzB,MAAOE,EAAS,GAAK,OAAS,QAC9B,KAAM,CACJ,OAAQA,EAAS,OACjB,SAAU,KAAK,IAAI,EAAIH,CACzB,CACF,CAAC,EACMG,CACT,OAASC,EAAK,CACZ,MAAAxB,EAAI,CACF,UAAW,KAAK,IAAI,EACpB,SAAU,QACV,QAAS,GAAGmB,CAAM,IAAIE,CAAG,GACzB,MAAO,QACP,KAAM,CACJ,MAAO,OAAOG,CAAG,EACjB,SAAU,KAAK,IAAI,EAAIJ,CACzB,CACF,CAAC,EACKI,CACR,CACF,EAEAnC,EAAU,KAAK,IAAM,CACnB,OAAO,MAAQ0B,CACjB,CAAC,CACH,CAEA,SAASlB,EAASG,EAA+B,CAC/C,IAAMyB,EAAW,eAAe,UAAU,KACpCC,EAAW,eAAe,UAAU,KAE1C,eAAe,UAAU,KAAO,SAAUP,EAAgBE,EAAmB,CAC3E,OAAC,KAAa,YAAcF,EAC3B,KAAa,SAAW,OAAOE,CAAG,EAE5BI,EAAS,MAAM,KAAM,SAAgB,CAC9C,EAEA,eAAe,UAAU,KAAO,UAAY,CAC1C,IAAML,EAAQ,KAAK,IAAI,EACjBD,EAAU,KAAa,aAAe,MACtCD,EAAkB,KAAa,UAAY,GAGjD,GAAIA,EAAO,SAAS,gBAAgB,EAClC,OAAOQ,EAAS,MAAM,KAAM,SAAgB,EAG9C,IAAML,EAAMC,EAASJ,CAAM,EAE3B,YAAK,iBAAiB,UAAW,UAAY,CAC3ClB,EAAI,CACF,UAAW,KAAK,IAAI,EACpB,SAAU,MACV,QAAS,GAAGmB,CAAM,IAAIE,CAAG,GACzB,MAAO,KAAK,QAAU,IAAM,QAAU,OACtC,KAAM,CACJ,OAAQ,KAAK,OACb,SAAU,KAAK,IAAI,EAAID,CACzB,CACF,CAAC,CACH,CAAC,EAGMM,EAAS,MAAM,KAAM,SAAgB,CAC9C,EAEArC,EAAU,KAAK,IAAM,CACnB,eAAe,UAAU,KAAOoC,EAChC,eAAe,UAAU,KAAOC,CAClC,CAAC,CACH,CAGA,SAASJ,EAASD,EAAqB,CACrC,GAAI,CACF,IAAMM,EAAI,IAAI,IAAIN,EAAK,SAAS,MAAM,EAChCO,EAAY,CAAC,QAAS,MAAO,SAAU,WAAY,OAAQ,OAAQ,UAAW,MAAO,eAAgB,gBAAiB,UAAW,QAAQ,EAC/I,QAAWC,IAAS,CAAC,GAAGF,EAAE,aAAa,KAAK,CAAC,EACvCC,EAAU,KAAM,GAAMC,EAAM,YAAY,EAAE,SAAS,CAAC,CAAC,GACvDF,EAAE,aAAa,IAAIE,EAAO,YAAY,EAG1C,OAAAF,EAAE,KAAO,GACFA,EAAE,SAAS,CACpB,MAAQ,CACN,OAAON,CACT,CACF,CAEA,SAASV,EAAYmB,EAAyB,CAC5C,IAAMC,EAAkB,CAAC,EACrBC,EAA8BF,EAElC,QAASG,EAAI,EAAGA,EAAI,GAAKD,GAAWA,IAAY,SAAS,KAAMC,IAAK,CAClE,IAAIC,EAAOF,EAAQ,QAAQ,YAAY,EACvC,GAAIA,EAAQ,GACVE,GAAQ,IAAIF,EAAQ,EAAE,WACbA,EAAQ,WAAa,OAAOA,EAAQ,WAAc,SAAU,CACrE,IAAMG,EAAMH,EAAQ,UAAU,KAAK,EAAE,MAAM,KAAK,EAAE,MAAM,EAAG,CAAC,EAAE,KAAK,GAAG,EAClEG,IAAKD,GAAQ,IAAIC,CAAG,GAC1B,CACAJ,EAAM,QAAQG,CAAI,EAClBF,EAAUA,EAAQ,aACpB,CAEA,OAAOD,EAAM,KAAK,KAAK,CACzB,CCzPA,IAAMK,EACJ,OAAO,OAAW,IAAc,OAAO,MAAM,KAAK,MAAM,GAAM,IAAM,CAAC,GAgBhE,SAASC,EACdC,EACAC,EACM,CACN,IAAMC,EAAM,GAAGF,EAAO,MAAM,iBACtBG,EAAO,KAAK,UAAUF,CAAO,EAEnC,GAAI,CACFG,EAAcF,EAAK,CACjB,OAAQ,OACR,QAAS,CACP,eAAgB,mBAChB,YAAaF,EAAO,MACtB,EACA,KAAAG,EACA,UAAW,EACb,CAAC,EAAE,MAAM,IAAM,CAEf,CAAC,CACH,MAAQ,CAER,CACF,CAOO,SAASE,EACdL,EACAM,EACM,CACN,IAAMJ,EAAM,GAAGF,EAAO,MAAM,kBACtBG,EAAO,KAAK,UAAU,CAAE,QAAAG,CAAQ,CAAC,EAEvC,GAAI,CACFF,EAAcF,EAAK,CACjB,OAAQ,OACR,QAAS,CACP,eAAgB,mBAChB,YAAaF,EAAO,MACtB,EACA,KAAAG,EACA,UAAW,EACb,CAAC,EAAE,MAAM,IAAM,CAEf,CAAC,CACH,MAAQ,CAER,CACF,CCzDA,IAAMI,EAAN,KAAc,CAAd,cACE,KAAQ,OAA+B,KACvC,KAAQ,YAA4B,CAAC,EACrC,KAAQ,KAA2B,KACnC,KAAQ,KAA+B,CAAC,EACxC,KAAQ,YAAc,GACtB,KAAQ,QAAU,GAgKlB,KAAQ,QAAWC,GAA4B,CAC7C,IAAMC,EAAQD,EAAM,OAAS,IAAI,MAAMA,EAAM,SAAW,eAAe,EACvE,KAAK,iBAAiBC,CAAK,CAC7B,EAEA,KAAQ,qBAAwBD,GAAuC,CACrE,IAAMC,EAAQD,EAAM,OACpB,KAAK,iBAAiBC,CAAK,CAC7B,EA9JA,KAAKC,EAA6B,CAChC,GAAI,KAAK,YAAa,CACpB,QAAQ,KAAK,uEAAuE,EACpF,MACF,CAEA,GAAI,CAACA,EAAO,QAAU,CAACA,EAAO,OAAQ,CACpC,QAAQ,MAAM,2CAA2C,EACzD,MACF,CAEA,KAAK,OAAS,CACZ,WAAY,EACZ,eAAgB,GAChB,eAAgB,GAChB,cAAe,GACf,kBAAmB,GACnB,eAAgB,GAChB,iBAAkB,GAClB,GAAGA,CACL,EAEA,KAAK,YAAc,CAAC,EACpB,KAAK,YAAc,GAGnBC,EAAiB,KAAK,OAASC,GAAU,KAAK,cAAcA,CAAK,CAAC,EAG9D,KAAK,OAAO,kBACd,KAAK,oBAAoB,EAIvB,KAAK,OAAO,SACdC,EACE,CAAE,OAAQ,KAAK,OAAO,OAAQ,OAAQ,KAAK,OAAO,MAAO,EACzD,KAAK,OAAO,OACd,CAEJ,CAaA,iBACEJ,EACAK,EACM,CACN,GAAI,CAAC,KAAK,OAAQ,OAElB,GAAM,CAAE,QAAAC,EAAS,MAAAC,CAAM,EAAIC,EAAeR,CAAK,EAC/C,KAAK,KAAK,CACR,QAAAM,EACA,MAAAC,EACA,MAAOF,GAAO,OAAS,QACvB,KAAMA,GAAO,KACb,YAAaA,GAAO,WACtB,CAAC,CACH,CAUA,MAAa,CACX,GAAI,CAAC,KAAK,OAAQ,CAChB,QAAQ,KAAK,+CAA+C,EAC5D,MACF,CACA,KAAK,KAAK,CACR,QAAS,iDACT,MAAO,UACP,KAAM,CAAE,aAAc,MAAO,EAC7B,YAAa,oBACf,CAAC,EACD,QAAQ,KAAK,qDAAqD,CACpE,CASA,eACEC,EACAG,EAA+B,QACzB,CACD,KAAK,QACV,KAAK,KAAK,CAAE,QAAAH,EAAS,MAAAG,CAAM,CAAC,CAC9B,CASA,QAAQC,EAAgC,CACtC,KAAK,KAAOA,CACd,CAKA,OAAOC,EAAaC,EAAqB,CACvC,KAAK,KAAKD,CAAG,EAAIC,CACnB,CAKA,cAAcT,EAAyB,CACrC,IAAMU,EAAM,KAAK,QAAQ,gBAAkB,GAC3C,KAAK,YAAY,KAAKV,CAAK,EACvB,KAAK,YAAY,OAASU,IAC5B,KAAK,YAAc,KAAK,YAAY,MAAM,CAACA,CAAG,EAElD,CAKA,SAAgB,CACdC,EAAoB,EACpB,KAAK,qBAAqB,EAC1B,KAAK,OAAS,KACd,KAAK,YAAc,CAAC,EACpB,KAAK,KAAO,KACZ,KAAK,KAAO,CAAC,EACb,KAAK,YAAc,EACrB,CAcQ,qBAA4B,CAClC,OAAO,iBAAiB,QAAS,KAAK,OAAO,EAC7C,OAAO,iBAAiB,qBAAsB,KAAK,oBAAoB,CACzE,CAEQ,sBAA6B,CACnC,OAAO,oBAAoB,QAAS,KAAK,OAAO,EAChD,OAAO,oBAAoB,qBAAsB,KAAK,oBAAoB,CAC5E,CAEQ,KAAKC,EAMJ,CACP,IAAMd,EAAS,KAAK,OACpB,GAAKA,GAID,MAAK,QACT,MAAK,QAAU,GAEf,GAAI,CAEF,GAAI,OAAOA,EAAO,YAAe,UAAY,KAAK,OAAO,EAAIA,EAAO,WAClE,OAGF,IAAMe,EAAwB,CAC5B,QAASD,EAAQ,QAAQ,MAAM,EAAG,GAAI,EACtC,MAAOA,EAAQ,OAAO,MAAM,EAAG,GAAK,EACpC,MAAOA,EAAQ,MACf,YAAad,EAAO,YACpB,QAASA,EAAO,QAChB,KAAM,CAAE,GAAG,KAAK,KAAM,GAAGc,EAAQ,IAAK,EACtC,QAAS,CACP,YAAa,KAAK,YAAY,MAAM,GAAG,EACvC,GAAI,KAAK,KAAO,CAAE,KAAM,KAAK,IAAK,EAAI,CAAC,EACvC,KAAM,CACJ,IAAKE,EAAwB,SAAS,IAAI,EAC1C,MAAO,SAAS,KAClB,EACA,QAASC,EAAkB,CAC7B,EACA,YAAaH,EAAQ,WACvB,EAGII,EAAiCH,EACrC,GAAIf,EAAO,WACT,GAAI,CACFkB,EAAYlB,EAAO,WAAWe,CAAO,CACvC,MAAQ,CAEN,MACF,CAEF,GAAI,CAACG,EAAW,OAEhBC,EACE,CAAE,OAAQnB,EAAO,OAAQ,OAAQA,EAAO,MAAO,EAC/CkB,CACF,CACF,QAAE,CACA,KAAK,QAAU,EACjB,EACF,CACF,EAEA,SAASX,EAAeR,EAAqD,CAC3E,OAAIA,aAAiB,MACZ,CAAE,QAASA,EAAM,QAAS,MAAOA,EAAM,KAAM,EAElD,OAAOA,GAAU,SACZ,CAAE,QAASA,CAAM,EAEtBA,GAAS,OAAOA,GAAU,UAAY,YAAaA,EAC9C,CACL,QAAS,OAAQA,EAA+B,OAAO,EACvD,MAAO,UAAWA,EAAQ,OAAQA,EAA6B,KAAK,EAAI,MAC1E,EAEK,CAAE,QAAS,OAAOA,CAAK,CAAE,CAClC,CAGA,SAASiB,EAAwBI,EAAqB,CACpD,GAAI,CACF,IAAMC,EAAI,IAAI,IAAID,CAAG,EACfE,EAAY,CAAC,QAAS,MAAO,SAAU,WAAY,OAAQ,OAAQ,UAAW,MAAO,eAAgB,gBAAiB,UAAW,QAAQ,EAC/I,QAAWC,IAAS,CAAC,GAAGF,EAAE,aAAa,KAAK,CAAC,EACvCC,EAAU,KAAM,GAAMC,EAAM,YAAY,EAAE,SAAS,CAAC,CAAC,GACvDF,EAAE,aAAa,IAAIE,EAAO,YAAY,EAI1C,OAAAF,EAAE,KAAO,GACFA,EAAE,SAAS,CACpB,MAAQ,CACN,OAAOD,CACT,CACF,CAEA,SAASH,GAA4C,CACnD,IAAMO,EAA8B,CAAC,EACrC,GAAI,CACFA,EAAI,UAAY,UAAU,UAC1BA,EAAI,SAAW,UAAU,SACzBA,EAAI,SAAW,GAAG,OAAO,UAAU,IAAI,OAAO,WAAW,GACzDA,EAAI,OAAS,GAAG,OAAO,KAAK,IAAI,OAAO,MAAM,EAC/C,MAAQ,CAER,CACA,OAAOA,CACT,CAGA,IAAMC,EAAU,IAAI5B,EACb6B,EAAQD","names":["teardowns","setupBreadcrumbs","config","addBreadcrumb","setupConsole","setupClicks","setupNavigation","setupFetch","setupXHR","teardownBreadcrumbs","fn","add","original","level","orig","args","handler","event","target","tag","text","selector","getSelector","origPush","origReplace","popHandler","origFetch","input","init","rawUrl","method","start","url","scrubUrl","response","err","origOpen","origSend","u","sensitive","param","el","parts","current","i","part","cls","originalFetch","sendEvent","config","payload","url","body","originalFetch","sendDeploy","release","Upgoose","event","error","config","setupBreadcrumbs","crumb","sendDeploy","extra","message","stack","normalizeError","level","user","key","value","max","teardownBreadcrumbs","partial","payload","stripUrlSensitiveParams","getBrowserContext","processed","sendEvent","url","u","sensitive","param","ctx","upgoose","index_default"]}
1
+ {"version":3,"sources":["../src/breadcrumbs.ts","../src/transport.ts","../src/index.ts"],"sourcesContent":["import type { Breadcrumb, UpgooseConfig } from \"./types\";\n\ntype BreadcrumbCallback = (crumb: Breadcrumb) => void;\n\nconst teardowns: Array<() => void> = [];\n\nexport function setupBreadcrumbs(\n config: UpgooseConfig,\n addBreadcrumb: BreadcrumbCallback\n): void {\n if (config.captureConsole !== false) {\n setupConsole(addBreadcrumb);\n }\n if (config.captureClicks !== false) {\n setupClicks(addBreadcrumb);\n }\n if (config.captureNavigation !== false) {\n setupNavigation(addBreadcrumb);\n }\n if (config.captureNetwork !== false) {\n setupFetch(addBreadcrumb);\n setupXHR(addBreadcrumb);\n }\n if (config.captureCSP) {\n setupCSP(addBreadcrumb);\n }\n}\n\nexport function teardownBreadcrumbs(): void {\n for (const fn of teardowns) {\n try {\n fn();\n } catch {\n // Best effort cleanup\n }\n }\n teardowns.length = 0;\n}\n\nfunction setupConsole(add: BreadcrumbCallback): void {\n const original = {\n log: console.log,\n warn: console.warn,\n error: console.error,\n };\n\n for (const level of [\"log\", \"warn\", \"error\"] as const) {\n const orig = original[level];\n console[level] = (...args: unknown[]) => {\n add({\n timestamp: Date.now(),\n category: \"console\",\n message: args.map(String).join(\" \").slice(0, 500),\n level: level === \"error\" ? \"error\" : level === \"warn\" ? \"warning\" : \"info\",\n });\n orig.apply(console, args);\n };\n }\n\n teardowns.push(() => {\n console.log = original.log;\n console.warn = original.warn;\n console.error = original.error;\n });\n}\n\nfunction setupClicks(add: BreadcrumbCallback): void {\n const handler = (event: MouseEvent) => {\n const target = event.target as HTMLElement | null;\n if (!target) return;\n\n const tag = target.tagName?.toLowerCase();\n const text = target.textContent?.trim().slice(0, 100) || \"\";\n const selector = getSelector(target);\n\n add({\n timestamp: Date.now(),\n category: \"ui\",\n message: `click on ${tag}${text ? `: \"${text}\"` : \"\"}`,\n data: { selector },\n });\n };\n\n document.addEventListener(\"click\", handler, { capture: true, passive: true });\n teardowns.push(() => document.removeEventListener(\"click\", handler, { capture: true }));\n}\n\nfunction setupNavigation(add: BreadcrumbCallback): void {\n // pushState\n const origPush = history.pushState;\n history.pushState = function (...args) {\n add({\n timestamp: Date.now(),\n category: \"navigation\",\n message: String(args[2] || \"\"),\n data: { from: location.href },\n });\n return origPush.apply(this, args);\n };\n\n // replaceState\n const origReplace = history.replaceState;\n history.replaceState = function (...args) {\n add({\n timestamp: Date.now(),\n category: \"navigation\",\n message: String(args[2] || \"\"),\n data: { from: location.href },\n });\n return origReplace.apply(this, args);\n };\n\n // popstate (back/forward)\n const popHandler = () => {\n add({\n timestamp: Date.now(),\n category: \"navigation\",\n message: location.href,\n });\n };\n window.addEventListener(\"popstate\", popHandler);\n\n teardowns.push(() => {\n history.pushState = origPush;\n history.replaceState = origReplace;\n window.removeEventListener(\"popstate\", popHandler);\n });\n}\n\nfunction setupFetch(add: BreadcrumbCallback): void {\n const origFetch = window.fetch;\n\n window.fetch = async function (input, init?) {\n const rawUrl = typeof input === \"string\" ? input : input instanceof URL ? input.href : input.url;\n const method = init?.method || \"GET\";\n const start = Date.now();\n\n // Skip breadcrumbs for SDK's own requests\n if (rawUrl.includes(\"/public/errors\")) {\n return origFetch.call(window, input, init);\n }\n\n const url = scrubUrl(rawUrl);\n\n try {\n const response = await origFetch.call(window, input, init);\n add({\n timestamp: Date.now(),\n category: \"fetch\",\n message: `${method} ${url}`,\n level: response.ok ? \"info\" : \"error\",\n data: {\n status: response.status,\n duration: Date.now() - start,\n },\n });\n return response;\n } catch (err) {\n add({\n timestamp: Date.now(),\n category: \"fetch\",\n message: `${method} ${url}`,\n level: \"error\",\n data: {\n error: String(err),\n duration: Date.now() - start,\n },\n });\n throw err;\n }\n };\n\n teardowns.push(() => {\n window.fetch = origFetch;\n });\n}\n\nfunction setupXHR(add: BreadcrumbCallback): void {\n const origOpen = XMLHttpRequest.prototype.open;\n const origSend = XMLHttpRequest.prototype.send;\n\n XMLHttpRequest.prototype.open = function (method: string, url: string | URL) {\n (this as any).__ug_method = method;\n (this as any).__ug_url = String(url);\n // eslint-disable-next-line prefer-rest-params\n return origOpen.apply(this, arguments as any);\n };\n\n XMLHttpRequest.prototype.send = function () {\n const start = Date.now();\n const method = (this as any).__ug_method || \"GET\";\n const rawUrl: string = (this as any).__ug_url || \"\";\n\n // Skip breadcrumbs for SDK's own requests\n if (rawUrl.includes(\"/public/errors\")) {\n return origSend.apply(this, arguments as any);\n }\n\n const url = scrubUrl(rawUrl);\n\n this.addEventListener(\"loadend\", function () {\n add({\n timestamp: Date.now(),\n category: \"xhr\",\n message: `${method} ${url}`,\n level: this.status >= 400 ? \"error\" : \"info\",\n data: {\n status: this.status,\n duration: Date.now() - start,\n },\n });\n });\n\n // eslint-disable-next-line prefer-rest-params\n return origSend.apply(this, arguments as any);\n };\n\n teardowns.push(() => {\n XMLHttpRequest.prototype.open = origOpen;\n XMLHttpRequest.prototype.send = origSend;\n });\n}\n\n/** Strip sensitive query params from URLs in breadcrumbs */\nfunction scrubUrl(url: string): string {\n try {\n const u = new URL(url, location.origin);\n const sensitive = [\"token\", \"key\", \"secret\", \"password\", \"auth\", \"code\", \"session\", \"jwt\", \"access_token\", \"refresh_token\", \"api_key\", \"apikey\"];\n for (const param of [...u.searchParams.keys()]) {\n if (sensitive.some((s) => param.toLowerCase().includes(s))) {\n u.searchParams.set(param, \"[REDACTED]\");\n }\n }\n u.hash = \"\";\n return u.toString();\n } catch {\n return url;\n }\n}\n\nfunction setupCSP(add: BreadcrumbCallback): void {\n const handler = (event: SecurityPolicyViolationEvent) => {\n const directive = event.effectiveDirective || event.violatedDirective || \"unknown\";\n add({\n timestamp: Date.now(),\n category: \"csp\",\n message: `${directive} blocked ${event.blockedURI || \"inline content\"}`,\n level: \"warning\",\n data: {\n blockedURI: event.blockedURI,\n documentURI: event.documentURI,\n disposition: event.disposition,\n },\n });\n };\n\n document.addEventListener(\"securitypolicyviolation\", handler);\n teardowns.push(() => document.removeEventListener(\"securitypolicyviolation\", handler));\n}\n\nfunction getSelector(el: HTMLElement): string {\n const parts: string[] = [];\n let current: HTMLElement | null = el;\n\n for (let i = 0; i < 3 && current && current !== document.body; i++) {\n let part = current.tagName.toLowerCase();\n if (current.id) {\n part += `#${current.id}`;\n } else if (current.className && typeof current.className === \"string\") {\n const cls = current.className.trim().split(/\\s+/).slice(0, 2).join(\".\");\n if (cls) part += `.${cls}`;\n }\n parts.unshift(part);\n current = current.parentElement;\n }\n\n return parts.join(\" > \");\n}\n","import type { ErrorPayload } from \"./types\";\n\n// Capture the original fetch before any instrumentation wraps it.\n// This prevents SDK requests from appearing in breadcrumbs and\n// avoids recursive loops if the fetch wrapper itself errors.\nconst originalFetch: typeof fetch =\n typeof window !== \"undefined\" ? window.fetch.bind(window) : ((() => {}) as any);\n\nexport interface TransportConfig {\n apiKey: string;\n apiUrl: string;\n}\n\n/**\n * Must be called BEFORE setupBreadcrumbs() to capture the\n * un-instrumented fetch reference.\n */\nexport function captureOriginalFetch(): void {\n // Already captured at module load time above.\n // This function exists as a documentation marker.\n}\n\nexport function sendEvent(\n config: TransportConfig,\n payload: ErrorPayload\n): void {\n const url = `${config.apiUrl}/public/errors`;\n const body = JSON.stringify(payload);\n\n try {\n originalFetch(url, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"X-API-Key\": config.apiKey,\n },\n body,\n keepalive: true, // Survives page unload\n }).catch(() => {\n // Silently fail — never impact user's app\n });\n } catch {\n // Silently fail\n }\n}\n\n/**\n * Signal that the SDK is installed and initialized. Sets sdkConnectedAt\n * on the project without requiring an error event. Idempotent — the\n * server no-ops if already connected.\n */\nexport function sendConnect(config: TransportConfig): void {\n const url = `${config.apiUrl}/public/connect`;\n\n try {\n originalFetch(url, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"X-API-Key\": config.apiKey,\n },\n body: \"{}\",\n keepalive: true,\n }).catch(() => {\n // Silently fail\n });\n } catch {\n // Silently fail\n }\n}\n\n/**\n * Notify the server that this release is running. Triggers auto-resolve\n * of error groups from older releases. Fire-and-forget; the server\n * deduplicates via IS DISTINCT FROM so calling on every init() is safe.\n */\nexport function sendDeploy(\n config: TransportConfig,\n release: string\n): void {\n const url = `${config.apiUrl}/public/deploys`;\n const body = JSON.stringify({ release });\n\n try {\n originalFetch(url, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"X-API-Key\": config.apiKey,\n },\n body,\n keepalive: true,\n }).catch(() => {\n // Silently fail\n });\n } catch {\n // Silently fail\n }\n}\n","import type {\n UpgooseConfig,\n Breadcrumb,\n UserContext,\n ErrorPayload,\n} from \"./types\";\nimport { setupBreadcrumbs, teardownBreadcrumbs } from \"./breadcrumbs\";\nimport { sendEvent, sendDeploy, sendConnect } from \"./transport\";\n\nexport type {\n UpgooseConfig,\n Breadcrumb,\n UserContext,\n ErrorPayload,\n};\n\nclass Upgoose {\n private config: UpgooseConfig | null = null;\n private breadcrumbs: Breadcrumb[] = [];\n private user: UserContext | null = null;\n private tags: Record<string, string> = {};\n private initialized = false;\n private sending = false; // re-entrancy guard\n\n /**\n * Initialize the Upgoose SDK. Call this once, as early as possible.\n *\n * ```ts\n * import upgoose from '@upgoose/browser';\n * upgoose.init({ apiKey: 'ug_...', apiUrl: 'https://api.upgoose.app' });\n * ```\n */\n init(config: UpgooseConfig): void {\n if (this.initialized) {\n console.warn(\"[Upgoose] Already initialized. Call destroy() first to re-initialize.\");\n return;\n }\n\n if (!config.apiKey || !config.apiUrl) {\n console.error(\"[Upgoose] apiKey and apiUrl are required.\");\n return;\n }\n\n this.config = {\n sampleRate: 1,\n maxBreadcrumbs: 50,\n captureConsole: true,\n captureClicks: true,\n captureNavigation: true,\n captureNetwork: true,\n captureUnhandled: true,\n ...config,\n };\n\n this.breadcrumbs = [];\n this.initialized = true;\n\n // Set up breadcrumb collection\n setupBreadcrumbs(this.config, (crumb) => this.addBreadcrumb(crumb));\n\n // Set up global error handlers\n if (this.config.captureUnhandled) {\n this.setupGlobalHandlers();\n }\n\n // Set up CSP violation capture\n if (this.config.captureCSP) {\n this.setupCSPHandler();\n }\n\n // Signal to the server that the SDK is installed\n const transport = { apiKey: this.config.apiKey, apiUrl: this.config.apiUrl };\n sendConnect(transport);\n\n // Notify server of this release so it can auto-resolve older error groups\n if (this.config.release) {\n sendDeploy(transport, this.config.release);\n }\n }\n\n /**\n * Capture an exception manually.\n *\n * ```ts\n * try {\n * riskyOperation();\n * } catch (err) {\n * upgoose.captureException(err);\n * }\n * ```\n */\n captureException(\n error: unknown,\n extra?: { tags?: Record<string, string>; fingerprint?: string; level?: ErrorPayload[\"level\"] }\n ): void {\n if (!this.config) return;\n\n const { message, stack } = normalizeError(error);\n this.send({\n message,\n stack,\n level: extra?.level || \"error\",\n tags: extra?.tags,\n fingerprint: extra?.fingerprint,\n });\n }\n\n /**\n * Send a test event to verify your integration is working.\n * Check your project page — the status should flip to \"CONNECTED\" within seconds.\n *\n * ```ts\n * upgoose.test();\n * ```\n */\n test(): void {\n if (!this.config) {\n console.warn(\"[Upgoose] Not initialized. Call init() first.\");\n return;\n }\n this.send({\n message: \"Upgoose test event — integration verified\",\n level: \"warning\",\n tags: { upgoose_test: \"true\" },\n fingerprint: \"upgoose-test-event\",\n });\n console.info(\"[Upgoose] Test event sent. Check your project page.\");\n }\n\n /**\n * Capture a message as an error event.\n *\n * ```ts\n * upgoose.captureMessage(\"User hit rate limit\", \"warning\");\n * ```\n */\n captureMessage(\n message: string,\n level: ErrorPayload[\"level\"] = \"error\"\n ): void {\n if (!this.config) return;\n this.send({ message, level });\n }\n\n /**\n * Set user context. Attached to all subsequent events.\n *\n * ```ts\n * upgoose.setUser({ id: '123', email: 'user@example.com' });\n * ```\n */\n setUser(user: UserContext | null): void {\n this.user = user;\n }\n\n /**\n * Set a global tag. Attached to all subsequent events.\n */\n setTag(key: string, value: string): void {\n this.tags[key] = value;\n }\n\n /**\n * Add a breadcrumb manually.\n */\n addBreadcrumb(crumb: Breadcrumb): void {\n const max = this.config?.maxBreadcrumbs ?? 50;\n this.breadcrumbs.push(crumb);\n if (this.breadcrumbs.length > max) {\n this.breadcrumbs = this.breadcrumbs.slice(-max);\n }\n }\n\n /**\n * Tear down the SDK — removes global handlers and breadcrumb instrumentation.\n */\n destroy(): void {\n teardownBreadcrumbs();\n this.removeGlobalHandlers();\n this.removeCSPHandler();\n this.config = null;\n this.breadcrumbs = [];\n this.user = null;\n this.tags = {};\n this.initialized = false;\n }\n\n // -- Private --\n\n private onError = (event: ErrorEvent): void => {\n const error = event.error || new Error(event.message || \"Unknown error\");\n this.captureException(error);\n };\n\n private onUnhandledRejection = (event: PromiseRejectionEvent): void => {\n const error = event.reason;\n this.captureException(error);\n };\n\n private onCSPViolation = (event: SecurityPolicyViolationEvent): void => {\n const directive = event.effectiveDirective || event.violatedDirective || \"unknown\";\n const blocked = normalizeBlockedURI(event.blockedURI);\n\n this.send({\n message: `CSP violation: ${directive} blocked ${event.blockedURI || \"inline content\"}`,\n level: \"warning\",\n tags: {\n \"csp.directive\": directive,\n \"csp.disposition\": event.disposition || \"enforce\",\n \"csp.blocked_origin\": blocked,\n },\n fingerprint: `csp:${directive}:${blocked}`,\n });\n };\n\n private setupGlobalHandlers(): void {\n window.addEventListener(\"error\", this.onError);\n window.addEventListener(\"unhandledrejection\", this.onUnhandledRejection);\n }\n\n private removeGlobalHandlers(): void {\n window.removeEventListener(\"error\", this.onError);\n window.removeEventListener(\"unhandledrejection\", this.onUnhandledRejection);\n }\n\n private setupCSPHandler(): void {\n document.addEventListener(\"securitypolicyviolation\", this.onCSPViolation);\n }\n\n private removeCSPHandler(): void {\n document.removeEventListener(\"securitypolicyviolation\", this.onCSPViolation);\n }\n\n private send(partial: {\n message: string;\n stack?: string;\n level: ErrorPayload[\"level\"];\n tags?: Record<string, string>;\n fingerprint?: string;\n }): void {\n const config = this.config;\n if (!config) return;\n\n // Re-entrancy guard: prevent infinite loops if send() itself throws\n // or if beforeSend throws and triggers the global error handler\n if (this.sending) return;\n this.sending = true;\n\n try {\n // Sample rate check\n if (typeof config.sampleRate === \"number\" && Math.random() > config.sampleRate) {\n return;\n }\n\n const payload: ErrorPayload = {\n message: partial.message.slice(0, 2000),\n stack: partial.stack?.slice(0, 10000),\n level: partial.level,\n environment: config.environment,\n release: config.release,\n tags: { ...this.tags, ...partial.tags },\n context: {\n breadcrumbs: this.breadcrumbs.slice(-30),\n ...(this.user ? { user: this.user } : {}),\n page: {\n url: stripUrlSensitiveParams(location.href),\n title: document.title,\n },\n browser: getBrowserContext(),\n },\n fingerprint: partial.fingerprint,\n };\n\n // beforeSend hook (wrapped to prevent throws from looping)\n let processed: ErrorPayload | null = payload;\n if (config.beforeSend) {\n try {\n processed = config.beforeSend(payload);\n } catch {\n // Drop event if beforeSend throws\n return;\n }\n }\n if (!processed) return;\n\n sendEvent(\n { apiKey: config.apiKey, apiUrl: config.apiUrl },\n processed\n );\n } finally {\n this.sending = false;\n }\n }\n}\n\nfunction normalizeError(error: unknown): { message: string; stack?: string } {\n if (error instanceof Error) {\n return { message: error.message, stack: error.stack };\n }\n if (typeof error === \"string\") {\n return { message: error };\n }\n if (error && typeof error === \"object\" && \"message\" in error) {\n return {\n message: String((error as { message: unknown }).message),\n stack: \"stack\" in error ? String((error as { stack: unknown }).stack) : undefined,\n };\n }\n return { message: String(error) };\n}\n\n/** Strip common sensitive query/hash params from URLs */\nfunction stripUrlSensitiveParams(url: string): string {\n try {\n const u = new URL(url);\n const sensitive = [\"token\", \"key\", \"secret\", \"password\", \"auth\", \"code\", \"session\", \"jwt\", \"access_token\", \"refresh_token\", \"api_key\", \"apikey\"];\n for (const param of [...u.searchParams.keys()]) {\n if (sensitive.some((s) => param.toLowerCase().includes(s))) {\n u.searchParams.set(param, \"[REDACTED]\");\n }\n }\n // Strip hash (often contains tokens in SPAs)\n u.hash = \"\";\n return u.toString();\n } catch {\n return url;\n }\n}\n\n/** Normalize a CSP blockedURI to its origin for fingerprinting */\nfunction normalizeBlockedURI(uri: string): string {\n if (!uri) return \"inline\";\n if (uri === \"eval\" || uri === \"inline\" || uri === \"data\" || uri === \"blob\") return uri;\n try {\n return new URL(uri).origin;\n } catch {\n return uri.slice(0, 100);\n }\n}\n\nfunction getBrowserContext(): Record<string, string> {\n const ctx: Record<string, string> = {};\n try {\n ctx.userAgent = navigator.userAgent;\n ctx.language = navigator.language;\n ctx.viewport = `${window.innerWidth}x${window.innerHeight}`;\n ctx.screen = `${screen.width}x${screen.height}`;\n } catch {\n // Ignore\n }\n return ctx;\n}\n\n// Singleton instance\nconst upgoose = new Upgoose();\nexport default upgoose;\nexport { upgoose, Upgoose };\n"],"mappings":"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","names":["teardowns","setupBreadcrumbs","config","addBreadcrumb","setupConsole","setupClicks","setupNavigation","setupFetch","setupXHR","setupCSP","teardownBreadcrumbs","fn","add","original","level","orig","args","handler","event","target","tag","text","selector","getSelector","origPush","origReplace","popHandler","origFetch","input","init","rawUrl","method","start","url","scrubUrl","response","err","origOpen","origSend","u","sensitive","param","s","directive","el","parts","current","i","part","cls","originalFetch","sendEvent","config","payload","url","body","originalFetch","sendConnect","sendDeploy","release","Upgoose","event","error","directive","blocked","normalizeBlockedURI","config","setupBreadcrumbs","crumb","transport","sendConnect","sendDeploy","extra","message","stack","normalizeError","level","user","key","value","max","teardownBreadcrumbs","partial","payload","stripUrlSensitiveParams","getBrowserContext","processed","sendEvent","url","u","sensitive","param","s","uri","ctx","upgoose","index_default"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@upgoose/browser",
3
- "version": "0.1.3",
3
+ "version": "0.2.1",
4
4
  "description": "Upgoose error tracking SDK for browsers",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",