@upgoose/browser 0.2.1 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +18 -6
- package/dist/index.cjs +1 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +14 -5
- package/dist/index.d.ts +14 -5
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/package.json +9 -3
package/README.md
CHANGED
|
@@ -14,15 +14,26 @@ npm install @upgoose/browser
|
|
|
14
14
|
import upgoose from '@upgoose/browser';
|
|
15
15
|
|
|
16
16
|
upgoose.init({
|
|
17
|
-
|
|
18
|
-
apiUrl: 'https://api.upgoose.app',
|
|
17
|
+
dsn: 'https://ug_...@api.upgoose.app/<PROJECT_ID>',
|
|
19
18
|
environment: 'production',
|
|
20
|
-
release: '1.2.3', // optional, enables release tracking
|
|
21
19
|
});
|
|
22
20
|
```
|
|
23
21
|
|
|
24
22
|
Errors are captured automatically. That's it.
|
|
25
23
|
|
|
24
|
+
`dsn` supplies `apiKey` and `apiUrl` in one value — or pass them separately if
|
|
25
|
+
you prefer. `release` is optional: if you use [`@upgoose/vite-plugin`](https://www.npmjs.com/package/@upgoose/vite-plugin),
|
|
26
|
+
it injects the build's release automatically so symbolicated stack traces line
|
|
27
|
+
up with your uploaded source maps. Otherwise set it yourself:
|
|
28
|
+
|
|
29
|
+
```ts
|
|
30
|
+
upgoose.init({
|
|
31
|
+
apiKey: 'ug_...',
|
|
32
|
+
apiUrl: 'https://api.upgoose.app',
|
|
33
|
+
release: '1.2.3', // match the release your source maps were uploaded under
|
|
34
|
+
});
|
|
35
|
+
```
|
|
36
|
+
|
|
26
37
|
## Manual capture
|
|
27
38
|
|
|
28
39
|
```ts
|
|
@@ -53,10 +64,11 @@ upgoose.setTag('team', 'checkout');
|
|
|
53
64
|
|
|
54
65
|
| Option | Type | Default | Description |
|
|
55
66
|
|--------|------|---------|-------------|
|
|
56
|
-
| `
|
|
57
|
-
| `
|
|
67
|
+
| `dsn` | `string` | — | `https://<API_KEY>@api.upgoose.app/<PROJECT_ID>` — supplies `apiKey` + `apiUrl`. Provide this **or** `apiKey` + `apiUrl`. |
|
|
68
|
+
| `apiKey` | `string` | required if no `dsn` | API key from your project DSN |
|
|
69
|
+
| `apiUrl` | `string` | required if no `dsn` | Upgoose API base URL |
|
|
58
70
|
| `environment` | `string` | — | e.g. `'production'`, `'staging'` |
|
|
59
|
-
| `release` | `string` |
|
|
71
|
+
| `release` | `string` | injected by the Vite plugin | App version or git SHA |
|
|
60
72
|
| `sampleRate` | `number` | `1` | 0–1, fraction of events to capture |
|
|
61
73
|
| `maxBreadcrumbs` | `number` | `50` | Max breadcrumbs retained |
|
|
62
74
|
| `beforeSend` | `function` | — | Transform or drop events before send |
|
package/dist/index.cjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
"use strict";var p=Object.defineProperty;var
|
|
1
|
+
"use strict";var p=Object.defineProperty;var k=Object.getOwnPropertyDescriptor;var E=Object.getOwnPropertyNames;var C=Object.prototype.hasOwnProperty;var U=(r,e)=>{for(var t in e)p(r,t,{get:e[t],enumerable:!0})},S=(r,e,t,n)=>{if(e&&typeof e=="object"||typeof e=="function")for(let o of E(e))!C.call(r,o)&&o!==t&&p(r,o,{get:()=>e[o],enumerable:!(n=k(e,o))||n.enumerable});return r};var R=r=>S(p({},"__esModule",{value:!0}),r);var O={};U(O,{Upgoose:()=>l,default:()=>A,upgoose:()=>b});module.exports=R(O);var i=[];function g(r,e){r.captureConsole!==!1&&P(e),r.captureClicks!==!1&&L(e),r.captureNavigation!==!1&&_(e),r.captureNetwork!==!1&&(D(e),x(e)),r.captureCSP&&$(e)}function f(){for(let r of i)try{r()}catch{}i.length=0}function P(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]=(...o)=>{r({timestamp:Date.now(),category:"console",message:o.map(String).join(" ").slice(0,500),level:t==="error"?"error":t==="warn"?"warning":"info"}),n.apply(console,o)}}i.push(()=>{console.log=e.log,console.warn=e.warn,console.error=e.error})}function L(r){let e=t=>{let n=t.target;if(!n)return;let o=n.tagName?.toLowerCase(),s=n.textContent?.trim().slice(0,100)||"",a=j(n);r({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 _(r){let e=history.pushState;history.pushState=function(...o){return r({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 r({timestamp:Date.now(),category:"navigation",message:String(o[2]||""),data:{from:location.href}}),t.apply(this,o)};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 D(r){let e=window.fetch;window.fetch=async function(t,n){let o=typeof t=="string"?t:t instanceof URL?t.href:t.url,s=n?.method||"GET",a=Date.now();if(o.includes("/public/errors"))return e.call(window,t,n);let u=h(o);try{let c=await e.call(window,t,n);return r({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 r({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 x(r){let e=XMLHttpRequest.prototype.open,t=XMLHttpRequest.prototype.send;XMLHttpRequest.prototype.open=function(n,o){return this.__ug_method=n,this.__ug_url=String(o),e.apply(this,arguments)},XMLHttpRequest.prototype.send=function(){let n=Date.now(),o=this.__ug_method||"GET",s=this.__ug_url||"";if(s.includes("/public/errors"))return t.apply(this,arguments);let a=h(s);return this.addEventListener("loadend",function(){r({timestamp:Date.now(),category:"xhr",message:`${o} ${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(o=>n.toLowerCase().includes(o))&&e.searchParams.set(n,"[REDACTED]");return e.hash="",e.toString()}catch{return r}}function $(r){let e=t=>{let n=t.effectiveDirective||t.violatedDirective||"unknown";r({timestamp:Date.now(),category:"csp",message:`${n} 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 j(r){let e=[],t=r;for(let n=0;n<3&&t&&t!==document.body;n++){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(r,e){let t=`${r.apiUrl}/public/errors`,n=JSON.stringify(e);try{d(t,{method:"POST",headers:{"Content-Type":"application/json","X-API-Key":r.apiKey},body:n,keepalive:!0}).catch(()=>{})}catch{}}function y(r){let e=`${r.apiUrl}/public/connect`;try{d(e,{method:"POST",headers:{"Content-Type":"application/json","X-API-Key":r.apiKey},body:"{}",keepalive:!0}).catch(()=>{})}catch{}}function v(r,e){let t=`${r.apiUrl}/public/deploys`,n=JSON.stringify({release:e});try{d(t,{method:"POST",headers:{"Content-Type":"application/json","X-API-Key":r.apiKey},body:n,keepalive:!0}).catch(()=>{})}catch{}}function H(r){let e;try{e=new URL(r)}catch{throw new Error("malformed DSN (not a URL)")}let t=e.username,n=e.pathname.replace(/^\/+|\/+$/g,"");if(!t)throw new Error("DSN is missing the API key");if(!n)throw new Error("DSN is missing the project id");return{apiKey:t,apiUrl:`${e.protocol}//${e.host}`,projectId:n}}var T={sampleRate:1,maxBreadcrumbs:50,captureConsole:!0,captureClicks:!0,captureNavigation:!0,captureNetwork:!0,captureUnhandled:!0};function w(r,e){let t=r.apiKey,n=r.apiUrl;if((!t||!n)&&r.dsn)try{let o=H(r.dsn);t=t??o.apiKey,n=n??o.apiUrl}catch(o){return{ok:!1,error:`Invalid dsn: ${o.message}`}}return!t||!n?{ok:!1,error:"Provide a `dsn`, or both `apiKey` and `apiUrl`."}:{ok:!0,config:{...T,...r,apiKey:t,apiUrl:n,release:r.release??e}}}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",n=I(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":n},fingerprint:`csp:${t}:${n}`})}}init(e){if(this.initialized){console.warn("[Upgoose] Already initialized. Call destroy() first to re-initialize.");return}let n=w(e,typeof __UPGOOSE_RELEASE__<"u"?__UPGOOSE_RELEASE__:void 0);if(!n.ok){console.error(`[Upgoose] ${n.error}`);return}this.config=n.config,this.breadcrumbs=[],this.initialized=!0,g(this.config,s=>this.addBreadcrumb(s)),this.config.captureUnhandled&&this.setupGlobalHandlers(),this.config.captureCSP&&this.setupCSPHandler();let o={apiKey:this.config.apiKey,apiUrl:this.config.apiUrl};y(o),this.config.release&&v(o,this.config.release)}captureException(e,t){if(!this.config)return;let{message:n,stack:o}=B(e);this.send({message:n,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(){f(),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 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:K(location.href),title:document.title},browser:N()},fingerprint:e.fingerprint},o=n;if(t.beforeSend)try{o=t.beforeSend(n)}catch{return}if(!o)return;m({apiKey:t.apiKey,apiUrl:t.apiUrl},o)}finally{this.sending=!1}}}};function B(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 K(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(o=>n.toLowerCase().includes(o))&&e.searchParams.set(n,"[REDACTED]");return e.hash="",e.toString()}catch{return r}}function I(r){if(!r)return"inline";if(r==="eval"||r==="inline"||r==="data"||r==="blob")return r;try{return new URL(r).origin}catch{return r.slice(0,100)}}function N(){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 b=new l,A=b;0&&(module.exports={Upgoose,upgoose});
|
|
2
2
|
//# sourceMappingURL=index.cjs.map
|
package/dist/index.cjs.map
CHANGED
|
@@ -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, 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":"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","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"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/breadcrumbs.ts","../src/transport.ts","../src/config.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\";\nimport { resolveInitConfig, type ResolvedConfig } from \"./config\";\n\n// Injected at build time by @upgoose/vite-plugin via Vite's `define`. Left\n// undefined in dev and in non-Vite builds — the typeof guard at the call site\n// keeps referencing it safe even when it's never defined.\ndeclare const __UPGOOSE_RELEASE__: string | undefined;\n\nexport type {\n UpgooseConfig,\n Breadcrumb,\n UserContext,\n ErrorPayload,\n};\n\nclass Upgoose {\n private config: ResolvedConfig | 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 // Release comes from the build-time inject when not passed explicitly.\n const injectedRelease =\n typeof __UPGOOSE_RELEASE__ !== \"undefined\" ? __UPGOOSE_RELEASE__ : undefined;\n const result = resolveInitConfig(config, injectedRelease);\n if (!result.ok) {\n console.error(`[Upgoose] ${result.error}`);\n return;\n }\n this.config = result.config;\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","import type { UpgooseConfig } from \"./types\";\n\n/** Config after DSN + release resolution — apiKey and apiUrl are guaranteed. */\nexport type ResolvedConfig = UpgooseConfig & { apiKey: string; apiUrl: string };\n\nexport interface ParsedDsn {\n apiKey: string;\n apiUrl: string;\n projectId: string;\n}\n\n/**\n * Parse an Upgoose DSN of the form\n * https://<API_KEY>@api.upgoose.app/<PROJECT_ID>\n * into its parts. Throws on a malformed DSN.\n */\nexport function parseDsn(dsn: string): ParsedDsn {\n let url: URL;\n try {\n url = new URL(dsn);\n } catch {\n throw new Error(\"malformed DSN (not a URL)\");\n }\n const apiKey = url.username;\n const projectId = url.pathname.replace(/^\\/+|\\/+$/g, \"\");\n if (!apiKey) throw new Error(\"DSN is missing the API key\");\n if (!projectId) throw new Error(\"DSN is missing the project id\");\n return { apiKey, apiUrl: `${url.protocol}//${url.host}`, projectId };\n}\n\nconst DEFAULTS = {\n sampleRate: 1,\n maxBreadcrumbs: 50,\n captureConsole: true,\n captureClicks: true,\n captureNavigation: true,\n captureNetwork: true,\n captureUnhandled: true,\n} as const;\n\n/**\n * Merge a user config with the build-injected release into a fully-resolved\n * config. Pure (no DOM / globals) so it's unit-testable — `init()` reads the\n * injected release from the `__UPGOOSE_RELEASE__` define and passes it in.\n *\n * Resolution:\n * - apiKey/apiUrl: explicit fields win, else parsed from `dsn`.\n * - release: explicit `release` wins, else the injected build value.\n */\nexport function resolveInitConfig(\n config: UpgooseConfig,\n injectedRelease: string | undefined\n): { ok: true; config: ResolvedConfig } | { ok: false; error: string } {\n let apiKey = config.apiKey;\n let apiUrl = config.apiUrl;\n\n if ((!apiKey || !apiUrl) && config.dsn) {\n try {\n const parsed = parseDsn(config.dsn);\n apiKey = apiKey ?? parsed.apiKey;\n apiUrl = apiUrl ?? parsed.apiUrl;\n } catch (e) {\n return { ok: false, error: `Invalid dsn: ${(e as Error).message}` };\n }\n }\n\n if (!apiKey || !apiUrl) {\n return { ok: false, error: \"Provide a `dsn`, or both `apiKey` and `apiUrl`.\" };\n }\n\n return {\n ok: true,\n config: {\n ...DEFAULTS,\n ...config,\n apiKey,\n apiUrl,\n release: config.release ?? injectedRelease,\n },\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","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","parseDsn","dsn","url","apiKey","projectId","DEFAULTS","resolveInitConfig","config","injectedRelease","apiUrl","parsed","e","Upgoose","event","error","directive","blocked","normalizeBlockedURI","config","result","resolveInitConfig","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
|
@@ -1,9 +1,18 @@
|
|
|
1
1
|
interface UpgooseConfig {
|
|
2
|
-
/**
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
2
|
+
/**
|
|
3
|
+
* Upgoose DSN — `https://<API_KEY>@api.upgoose.app/<PROJECT_ID>`. Supplies
|
|
4
|
+
* `apiKey` and `apiUrl` in one value. Provide this OR `apiKey` + `apiUrl`.
|
|
5
|
+
*/
|
|
6
|
+
dsn?: string;
|
|
7
|
+
/** API key from your Upgoose error project. Optional if `dsn` is set. */
|
|
8
|
+
apiKey?: string;
|
|
9
|
+
/** Upgoose API base URL. Optional if `dsn` is set. */
|
|
10
|
+
apiUrl?: string;
|
|
11
|
+
/**
|
|
12
|
+
* App release/version (e.g. "1.2.3" or a git SHA). If omitted, it's read
|
|
13
|
+
* from the value @upgoose/vite-plugin injects at build time. Must match the
|
|
14
|
+
* release the source maps were uploaded under for symbolication to work.
|
|
15
|
+
*/
|
|
7
16
|
release?: string;
|
|
8
17
|
/** Environment (e.g. "production", "staging") */
|
|
9
18
|
environment?: string;
|
package/dist/index.d.ts
CHANGED
|
@@ -1,9 +1,18 @@
|
|
|
1
1
|
interface UpgooseConfig {
|
|
2
|
-
/**
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
2
|
+
/**
|
|
3
|
+
* Upgoose DSN — `https://<API_KEY>@api.upgoose.app/<PROJECT_ID>`. Supplies
|
|
4
|
+
* `apiKey` and `apiUrl` in one value. Provide this OR `apiKey` + `apiUrl`.
|
|
5
|
+
*/
|
|
6
|
+
dsn?: string;
|
|
7
|
+
/** API key from your Upgoose error project. Optional if `dsn` is set. */
|
|
8
|
+
apiKey?: string;
|
|
9
|
+
/** Upgoose API base URL. Optional if `dsn` is set. */
|
|
10
|
+
apiUrl?: string;
|
|
11
|
+
/**
|
|
12
|
+
* App release/version (e.g. "1.2.3" or a git SHA). If omitted, it's read
|
|
13
|
+
* from the value @upgoose/vite-plugin injects at build time. Must match the
|
|
14
|
+
* release the source maps were uploaded under for symbolication to work.
|
|
15
|
+
*/
|
|
7
16
|
release?: string;
|
|
8
17
|
/** Environment (e.g. "production", "staging") */
|
|
9
18
|
environment?: string;
|
package/dist/index.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
var i=[];function u(
|
|
1
|
+
var i=[];function u(r,t){r.captureConsole!==!1&&w(t),r.captureClicks!==!1&&b(t),r.captureNavigation!==!1&&k(t),r.captureNetwork!==!1&&(E(t),C(t)),r.captureCSP&&U(t)}function g(){for(let r of i)try{r()}catch{}i.length=0}function w(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]=(...o)=>{r({timestamp:Date.now(),category:"console",message:o.map(String).join(" ").slice(0,500),level:e==="error"?"error":e==="warn"?"warning":"info"}),n.apply(console,o)}}i.push(()=>{console.log=t.log,console.warn=t.warn,console.error=t.error})}function b(r){let t=e=>{let n=e.target;if(!n)return;let o=n.tagName?.toLowerCase(),s=n.textContent?.trim().slice(0,100)||"",a=S(n);r({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 k(r){let t=history.pushState;history.pushState=function(...o){return r({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 r({timestamp:Date.now(),category:"navigation",message:String(o[2]||""),data:{from:location.href}}),e.apply(this,o)};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 E(r){let t=window.fetch;window.fetch=async function(e,n){let o=typeof e=="string"?e:e instanceof URL?e.href:e.url,s=n?.method||"GET",a=Date.now();if(o.includes("/public/errors"))return t.call(window,e,n);let d=f(o);try{let c=await t.call(window,e,n);return r({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 r({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(r){let t=XMLHttpRequest.prototype.open,e=XMLHttpRequest.prototype.send;XMLHttpRequest.prototype.open=function(n,o){return this.__ug_method=n,this.__ug_url=String(o),t.apply(this,arguments)},XMLHttpRequest.prototype.send=function(){let n=Date.now(),o=this.__ug_method||"GET",s=this.__ug_url||"";if(s.includes("/public/errors"))return e.apply(this,arguments);let a=f(s);return this.addEventListener("loadend",function(){r({timestamp:Date.now(),category:"xhr",message:`${o} ${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 f(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(o=>n.toLowerCase().includes(o))&&t.searchParams.set(n,"[REDACTED]");return t.hash="",t.toString()}catch{return r}}function U(r){let t=e=>{let n=e.effectiveDirective||e.violatedDirective||"unknown";r({timestamp:Date.now(),category:"csp",message:`${n} 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 S(r){let t=[],e=r;for(let n=0;n<3&&e&&e!==document.body;n++){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 h(r,t){let e=`${r.apiUrl}/public/errors`,n=JSON.stringify(t);try{l(e,{method:"POST",headers:{"Content-Type":"application/json","X-API-Key":r.apiKey},body:n,keepalive:!0}).catch(()=>{})}catch{}}function m(r){let t=`${r.apiUrl}/public/connect`;try{l(t,{method:"POST",headers:{"Content-Type":"application/json","X-API-Key":r.apiKey},body:"{}",keepalive:!0}).catch(()=>{})}catch{}}function y(r,t){let e=`${r.apiUrl}/public/deploys`,n=JSON.stringify({release:t});try{l(e,{method:"POST",headers:{"Content-Type":"application/json","X-API-Key":r.apiKey},body:n,keepalive:!0}).catch(()=>{})}catch{}}function R(r){let t;try{t=new URL(r)}catch{throw new Error("malformed DSN (not a URL)")}let e=t.username,n=t.pathname.replace(/^\/+|\/+$/g,"");if(!e)throw new Error("DSN is missing the API key");if(!n)throw new Error("DSN is missing the project id");return{apiKey:e,apiUrl:`${t.protocol}//${t.host}`,projectId:n}}var P={sampleRate:1,maxBreadcrumbs:50,captureConsole:!0,captureClicks:!0,captureNavigation:!0,captureNetwork:!0,captureUnhandled:!0};function v(r,t){let e=r.apiKey,n=r.apiUrl;if((!e||!n)&&r.dsn)try{let o=R(r.dsn);e=e??o.apiKey,n=n??o.apiUrl}catch(o){return{ok:!1,error:`Invalid dsn: ${o.message}`}}return!e||!n?{ok:!1,error:"Provide a `dsn`, or both `apiKey` and `apiUrl`."}:{ok:!0,config:{...P,...r,apiKey:e,apiUrl:n,release:r.release??t}}}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",n=D(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":n},fingerprint:`csp:${e}:${n}`})}}init(t){if(this.initialized){console.warn("[Upgoose] Already initialized. Call destroy() first to re-initialize.");return}let n=v(t,typeof __UPGOOSE_RELEASE__<"u"?__UPGOOSE_RELEASE__:void 0);if(!n.ok){console.error(`[Upgoose] ${n.error}`);return}this.config=n.config,this.breadcrumbs=[],this.initialized=!0,u(this.config,s=>this.addBreadcrumb(s)),this.config.captureUnhandled&&this.setupGlobalHandlers(),this.config.captureCSP&&this.setupCSPHandler();let o={apiKey:this.config.apiKey,apiUrl:this.config.apiUrl};m(o),this.config.release&&y(o,this.config.release)}captureException(t,e){if(!this.config)return;let{message:n,stack:o}=L(t);this.send({message:n,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 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:_(location.href),title:document.title},browser:x()},fingerprint:t.fingerprint},o=n;if(e.beforeSend)try{o=e.beforeSend(n)}catch{return}if(!o)return;h({apiKey:e.apiKey,apiUrl:e.apiUrl},o)}finally{this.sending=!1}}}};function L(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 _(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(o=>n.toLowerCase().includes(o))&&t.searchParams.set(n,"[REDACTED]");return t.hash="",t.toString()}catch{return r}}function D(r){if(!r)return"inline";if(r==="eval"||r==="inline"||r==="data"||r==="blob")return r;try{return new URL(r).origin}catch{return r.slice(0,100)}}function x(){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 $=new p,N=$;export{p as Upgoose,N as default,$ 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 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":"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,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,CClFA,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":["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"]}
|
|
1
|
+
{"version":3,"sources":["../src/breadcrumbs.ts","../src/transport.ts","../src/config.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 { UpgooseConfig } from \"./types\";\n\n/** Config after DSN + release resolution — apiKey and apiUrl are guaranteed. */\nexport type ResolvedConfig = UpgooseConfig & { apiKey: string; apiUrl: string };\n\nexport interface ParsedDsn {\n apiKey: string;\n apiUrl: string;\n projectId: string;\n}\n\n/**\n * Parse an Upgoose DSN of the form\n * https://<API_KEY>@api.upgoose.app/<PROJECT_ID>\n * into its parts. Throws on a malformed DSN.\n */\nexport function parseDsn(dsn: string): ParsedDsn {\n let url: URL;\n try {\n url = new URL(dsn);\n } catch {\n throw new Error(\"malformed DSN (not a URL)\");\n }\n const apiKey = url.username;\n const projectId = url.pathname.replace(/^\\/+|\\/+$/g, \"\");\n if (!apiKey) throw new Error(\"DSN is missing the API key\");\n if (!projectId) throw new Error(\"DSN is missing the project id\");\n return { apiKey, apiUrl: `${url.protocol}//${url.host}`, projectId };\n}\n\nconst DEFAULTS = {\n sampleRate: 1,\n maxBreadcrumbs: 50,\n captureConsole: true,\n captureClicks: true,\n captureNavigation: true,\n captureNetwork: true,\n captureUnhandled: true,\n} as const;\n\n/**\n * Merge a user config with the build-injected release into a fully-resolved\n * config. Pure (no DOM / globals) so it's unit-testable — `init()` reads the\n * injected release from the `__UPGOOSE_RELEASE__` define and passes it in.\n *\n * Resolution:\n * - apiKey/apiUrl: explicit fields win, else parsed from `dsn`.\n * - release: explicit `release` wins, else the injected build value.\n */\nexport function resolveInitConfig(\n config: UpgooseConfig,\n injectedRelease: string | undefined\n): { ok: true; config: ResolvedConfig } | { ok: false; error: string } {\n let apiKey = config.apiKey;\n let apiUrl = config.apiUrl;\n\n if ((!apiKey || !apiUrl) && config.dsn) {\n try {\n const parsed = parseDsn(config.dsn);\n apiKey = apiKey ?? parsed.apiKey;\n apiUrl = apiUrl ?? parsed.apiUrl;\n } catch (e) {\n return { ok: false, error: `Invalid dsn: ${(e as Error).message}` };\n }\n }\n\n if (!apiKey || !apiUrl) {\n return { ok: false, error: \"Provide a `dsn`, or both `apiKey` and `apiUrl`.\" };\n }\n\n return {\n ok: true,\n config: {\n ...DEFAULTS,\n ...config,\n apiKey,\n apiUrl,\n release: config.release ?? injectedRelease,\n },\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\";\nimport { resolveInitConfig, type ResolvedConfig } from \"./config\";\n\n// Injected at build time by @upgoose/vite-plugin via Vite's `define`. Left\n// undefined in dev and in non-Vite builds — the typeof guard at the call site\n// keeps referencing it safe even when it's never defined.\ndeclare const __UPGOOSE_RELEASE__: string | undefined;\n\nexport type {\n UpgooseConfig,\n Breadcrumb,\n UserContext,\n ErrorPayload,\n};\n\nclass Upgoose {\n private config: ResolvedConfig | 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 // Release comes from the build-time inject when not passed explicitly.\n const injectedRelease =\n typeof __UPGOOSE_RELEASE__ !== \"undefined\" ? __UPGOOSE_RELEASE__ : undefined;\n const result = resolveInitConfig(config, injectedRelease);\n if (!result.ok) {\n console.error(`[Upgoose] ${result.error}`);\n return;\n }\n this.config = result.config;\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":"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,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,CClFO,SAASK,EAASC,EAAwB,CAC/C,IAAIC,EACJ,GAAI,CACFA,EAAM,IAAI,IAAID,CAAG,CACnB,MAAQ,CACN,MAAM,IAAI,MAAM,2BAA2B,CAC7C,CACA,IAAME,EAASD,EAAI,SACbE,EAAYF,EAAI,SAAS,QAAQ,aAAc,EAAE,EACvD,GAAI,CAACC,EAAQ,MAAM,IAAI,MAAM,4BAA4B,EACzD,GAAI,CAACC,EAAW,MAAM,IAAI,MAAM,+BAA+B,EAC/D,MAAO,CAAE,OAAAD,EAAQ,OAAQ,GAAGD,EAAI,QAAQ,KAAKA,EAAI,IAAI,GAAI,UAAAE,CAAU,CACrE,CAEA,IAAMC,EAAW,CACf,WAAY,EACZ,eAAgB,GAChB,eAAgB,GAChB,cAAe,GACf,kBAAmB,GACnB,eAAgB,GAChB,iBAAkB,EACpB,EAWO,SAASC,EACdC,EACAC,EACqE,CACrE,IAAIL,EAASI,EAAO,OAChBE,EAASF,EAAO,OAEpB,IAAK,CAACJ,GAAU,CAACM,IAAWF,EAAO,IACjC,GAAI,CACF,IAAMG,EAASV,EAASO,EAAO,GAAG,EAClCJ,EAASA,GAAUO,EAAO,OAC1BD,EAASA,GAAUC,EAAO,MAC5B,OAASC,EAAG,CACV,MAAO,CAAE,GAAI,GAAO,MAAO,gBAAiBA,EAAY,OAAO,EAAG,CACpE,CAGF,MAAI,CAACR,GAAU,CAACM,EACP,CAAE,GAAI,GAAO,MAAO,iDAAkD,EAGxE,CACL,GAAI,GACJ,OAAQ,CACN,GAAGJ,EACH,GAAGE,EACH,OAAAJ,EACA,OAAAM,EACA,QAASF,EAAO,SAAWC,CAC7B,CACF,CACF,CC1DA,IAAMI,EAAN,KAAc,CAAd,cACE,KAAQ,OAAgC,KACxC,KAAQ,YAA4B,CAAC,EACrC,KAAQ,KAA2B,KACnC,KAAQ,KAA+B,CAAC,EACxC,KAAQ,YAAc,GACtB,KAAQ,QAAU,GAiKlB,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,EA/KA,KAAKE,EAA6B,CAChC,GAAI,KAAK,YAAa,CACpB,QAAQ,KAAK,uEAAuE,EACpF,MACF,CAKA,IAAMC,EAASC,EAAkBF,EAD/B,OAAO,oBAAwB,IAAc,oBAAsB,MACb,EACxD,GAAI,CAACC,EAAO,GAAI,CACd,QAAQ,MAAM,aAAaA,EAAO,KAAK,EAAE,EACzC,MACF,CACA,KAAK,OAASA,EAAO,OAErB,KAAK,YAAc,CAAC,EACpB,KAAK,YAAc,GAGnBE,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,iBACET,EACAY,EACM,CACN,GAAI,CAAC,KAAK,OAAQ,OAElB,GAAM,CAAE,QAAAC,EAAS,MAAAC,CAAM,EAAIC,EAAef,CAAK,EAC/C,KAAK,KAAK,CACR,QAAAa,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,IAAMlB,EAAS,KAAK,OACpB,GAAKA,GAID,MAAK,QACT,MAAK,QAAU,GAEf,GAAI,CAEF,GAAI,OAAOA,EAAO,YAAe,UAAY,KAAK,OAAO,EAAIA,EAAO,WAClE,OAGF,IAAMmB,EAAwB,CAC5B,QAASD,EAAQ,QAAQ,MAAM,EAAG,GAAI,EACtC,MAAOA,EAAQ,OAAO,MAAM,EAAG,GAAK,EACpC,MAAOA,EAAQ,MACf,YAAalB,EAAO,YACpB,QAASA,EAAO,QAChB,KAAM,CAAE,GAAG,KAAK,KAAM,GAAGkB,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,GAAInB,EAAO,WACT,GAAI,CACFsB,EAAYtB,EAAO,WAAWmB,CAAO,CACvC,MAAQ,CAEN,MACF,CAEF,GAAI,CAACG,EAAW,OAEhBC,EACE,CAAE,OAAQvB,EAAO,OAAQ,OAAQA,EAAO,MAAO,EAC/CsB,CACF,CACF,QAAE,CACA,KAAK,QAAU,EACjB,EACF,CACF,EAEA,SAASX,EAAef,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,SAASwB,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,SAASzB,EAAoB8B,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,IAAIrC,EACbsC,EAAQD","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","parseDsn","dsn","url","apiKey","projectId","DEFAULTS","resolveInitConfig","config","injectedRelease","apiUrl","parsed","e","Upgoose","event","error","directive","blocked","normalizeBlockedURI","config","result","resolveInitConfig","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.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Upgoose error tracking SDK for browsers",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.cjs",
|
|
@@ -19,11 +19,17 @@
|
|
|
19
19
|
"scripts": {
|
|
20
20
|
"build": "tsup",
|
|
21
21
|
"dev": "tsup --watch",
|
|
22
|
-
"typecheck": "tsc --noEmit"
|
|
22
|
+
"typecheck": "tsc --noEmit",
|
|
23
|
+
"test": "vitest run",
|
|
24
|
+
"prepublishOnly": "npm run build"
|
|
25
|
+
},
|
|
26
|
+
"publishConfig": {
|
|
27
|
+
"access": "public"
|
|
23
28
|
},
|
|
24
29
|
"devDependencies": {
|
|
25
30
|
"tsup": "^8.0.0",
|
|
26
|
-
"typescript": "^5.7.2"
|
|
31
|
+
"typescript": "^5.7.2",
|
|
32
|
+
"vitest": "^4.1.4"
|
|
27
33
|
},
|
|
28
34
|
"keywords": [
|
|
29
35
|
"error-tracking",
|