@upgoose/browser 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,28 @@
1
+ BSD 3-Clause License
2
+
3
+ Copyright (c) 2026, Leyline Digital
4
+
5
+ Redistribution and use in source and binary forms, with or without
6
+ modification, are permitted provided that the following conditions are met:
7
+
8
+ 1. Redistributions of source code must retain the above copyright notice, this
9
+ list of conditions and the following disclaimer.
10
+
11
+ 2. Redistributions in binary form must reproduce the above copyright notice,
12
+ this list of conditions and the following disclaimer in the documentation
13
+ and/or other materials provided with the distribution.
14
+
15
+ 3. Neither the name of the copyright holder nor the names of its
16
+ contributors may be used to endorse or promote products derived from
17
+ this software without specific prior written permission.
18
+
19
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
20
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
22
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
23
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
25
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
26
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
27
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
package/README.md ADDED
@@ -0,0 +1,112 @@
1
+ # @upgoose/browser
2
+
3
+ Lightweight error tracking SDK for browser applications. Captures unhandled exceptions, promise rejections, and manual error reports — with breadcrumbs, PII scrubbing, and zero dependencies.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ npm install @upgoose/browser
9
+ ```
10
+
11
+ ## Quick start
12
+
13
+ ```ts
14
+ import upgoose from '@upgoose/browser';
15
+
16
+ upgoose.init({
17
+ apiKey: 'ug_...', // from your project's DSN
18
+ apiUrl: 'https://api.upgoose.app',
19
+ environment: 'production',
20
+ release: '1.2.3', // optional, enables release tracking
21
+ });
22
+ ```
23
+
24
+ Errors are captured automatically. That's it.
25
+
26
+ ## Manual capture
27
+
28
+ ```ts
29
+ // Capture an exception
30
+ try {
31
+ riskyOperation();
32
+ } catch (err) {
33
+ upgoose.captureException(err);
34
+ }
35
+
36
+ // Capture a message
37
+ upgoose.captureMessage("Payment flow failed", "warning");
38
+ ```
39
+
40
+ ## User context
41
+
42
+ ```ts
43
+ upgoose.setUser({ id: '123', email: 'user@example.com' });
44
+ ```
45
+
46
+ ## Tags
47
+
48
+ ```ts
49
+ upgoose.setTag('team', 'checkout');
50
+ ```
51
+
52
+ ## Configuration
53
+
54
+ | Option | Type | Default | Description |
55
+ |--------|------|---------|-------------|
56
+ | `apiKey` | `string` | required | API key from your project DSN |
57
+ | `apiUrl` | `string` | required | Upgoose API base URL |
58
+ | `environment` | `string` | — | e.g. `'production'`, `'staging'` |
59
+ | `release` | `string` | — | App version or git SHA |
60
+ | `sampleRate` | `number` | `1` | 0–1, fraction of events to capture |
61
+ | `maxBreadcrumbs` | `number` | `50` | Max breadcrumbs retained |
62
+ | `beforeSend` | `function` | — | Transform or drop events before send |
63
+ | `captureConsole` | `boolean` | `true` | Log console.error/warn as breadcrumbs |
64
+ | `captureClicks` | `boolean` | `true` | Log click events as breadcrumbs |
65
+ | `captureNavigation` | `boolean` | `true` | Log route changes as breadcrumbs |
66
+ | `captureNetwork` | `boolean` | `true` | Log fetch/XHR as breadcrumbs |
67
+ | `captureUnhandled` | `boolean` | `true` | Global error + rejection handlers |
68
+
69
+ ## What gets captured
70
+
71
+ - Unhandled exceptions (`window.onerror`)
72
+ - Unhandled promise rejections
73
+ - Breadcrumbs: clicks, navigation, console, fetch/XHR (with timing)
74
+ - Browser context: user agent, viewport, language
75
+ - Page URL (sensitive query params automatically redacted)
76
+
77
+ ## What doesn't get sent
78
+
79
+ - No cookies, localStorage, or session data
80
+ - No DOM snapshots or session replay
81
+ - No performance traces
82
+ - Sensitive URL params (`token`, `key`, `secret`, `password`, `auth`, `session`, `jwt`) are redacted before leaving the browser
83
+
84
+ ## Filtering events
85
+
86
+ ```ts
87
+ upgoose.init({
88
+ apiKey: '...',
89
+ apiUrl: '...',
90
+ beforeSend(event) {
91
+ // Drop events from browser extensions
92
+ if (event.stack?.includes('chrome-extension://')) return null;
93
+ // Redact custom fields
94
+ delete event.context?.sensitiveField;
95
+ return event;
96
+ },
97
+ });
98
+ ```
99
+
100
+ ## Bundle size
101
+
102
+ ~3 KB gzipped. Zero dependencies.
103
+
104
+ ## Documentation
105
+
106
+ Full integration guide: [upgoose.app/docs](https://upgoose.app/docs#error-tracking)
107
+
108
+ Already using a Sentry SDK? You can also point it at Upgoose with a one-line DSN swap: [Migration guide](https://upgoose.app/docs#migrate-from-sentry)
109
+
110
+ ## License
111
+
112
+ BSD-3-Clause
package/dist/index.cjs ADDED
@@ -0,0 +1,2 @@
1
+ "use strict";var u=Object.defineProperty;var y=Object.getOwnPropertyDescriptor;var w=Object.getOwnPropertyNames;var v=Object.prototype.hasOwnProperty;var b=(r,e)=>{for(var t in e)u(r,t,{get:e[t],enumerable:!0})},k=(r,e,t,s)=>{if(e&&typeof e=="object"||typeof e=="function")for(let n of w(e))!v.call(r,n)&&n!==t&&u(r,n,{get:()=>e[n],enumerable:!(s=y(e,n))||s.enumerable});return r};var E=r=>k(u({},"__esModule",{value:!0}),r);var $={};b($,{Upgoose:()=>l,default:()=>H,upgoose:()=>f});module.exports=E($);var a=[];function d(r,e){r.captureConsole!==!1&&C(e),r.captureClicks!==!1&&U(e),r.captureNavigation!==!1&&L(e),r.captureNetwork!==!1&&(R(e),x(e))}function g(){for(let r of a)try{r()}catch{}a.length=0}function C(r){let e={log:console.log,warn:console.warn,error:console.error};for(let t of["log","warn","error"]){let s=e[t];console[t]=(...n)=>{r({timestamp:Date.now(),category:"console",message:n.map(String).join(" ").slice(0,500),level:t==="error"?"error":t==="warn"?"warning":"info"}),s.apply(console,n)}}a.push(()=>{console.log=e.log,console.warn=e.warn,console.error=e.error})}function U(r){let e=t=>{let s=t.target;if(!s)return;let n=s.tagName?.toLowerCase(),o=s.textContent?.trim().slice(0,100)||"",i=S(s);r({timestamp:Date.now(),category:"ui",message:`click on ${n}${o?`: "${o}"`:""}`,data:{selector:i}})};document.addEventListener("click",e,{capture:!0,passive:!0}),a.push(()=>document.removeEventListener("click",e,{capture:!0}))}function L(r){let e=history.pushState;history.pushState=function(...n){return r({timestamp:Date.now(),category:"navigation",message:String(n[2]||""),data:{from:location.href}}),e.apply(this,n)};let t=history.replaceState;history.replaceState=function(...n){return r({timestamp:Date.now(),category:"navigation",message:String(n[2]||""),data:{from:location.href}}),t.apply(this,n)};let s=()=>{r({timestamp:Date.now(),category:"navigation",message:location.href})};window.addEventListener("popstate",s),a.push(()=>{history.pushState=e,history.replaceState=t,window.removeEventListener("popstate",s)})}function R(r){let e=window.fetch;window.fetch=async function(t,s){let n=typeof t=="string"?t:t instanceof URL?t.href:t.url,o=s?.method||"GET",i=Date.now();if(n.includes("/public/errors"))return e.call(window,t,s);let p=h(n);try{let c=await e.call(window,t,s);return r({timestamp:Date.now(),category:"fetch",message:`${o} ${p}`,level:c.ok?"info":"error",data:{status:c.status,duration:Date.now()-i}}),c}catch(c){throw r({timestamp:Date.now(),category:"fetch",message:`${o} ${p}`,level:"error",data:{error:String(c),duration:Date.now()-i}}),c}},a.push(()=>{window.fetch=e})}function x(r){let e=XMLHttpRequest.prototype.open,t=XMLHttpRequest.prototype.send;XMLHttpRequest.prototype.open=function(s,n){return this.__ug_method=s,this.__ug_url=String(n),e.apply(this,arguments)},XMLHttpRequest.prototype.send=function(){let s=Date.now(),n=this.__ug_method||"GET",o=this.__ug_url||"";if(o.includes("/public/errors"))return t.apply(this,arguments);let i=h(o);return this.addEventListener("loadend",function(){r({timestamp:Date.now(),category:"xhr",message:`${n} ${i}`,level:this.status>=400?"error":"info",data:{status:this.status,duration:Date.now()-s}})}),t.apply(this,arguments)},a.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 s of[...e.searchParams.keys()])t.some(n=>s.toLowerCase().includes(n))&&e.searchParams.set(s,"[REDACTED]");return e.hash="",e.toString()}catch{return r}}function S(r){let e=[],t=r;for(let s=0;s<3&&t&&t!==document.body;s++){let n=t.tagName.toLowerCase();if(t.id)n+=`#${t.id}`;else if(t.className&&typeof t.className=="string"){let o=t.className.trim().split(/\s+/).slice(0,2).join(".");o&&(n+=`.${o}`)}e.unshift(n),t=t.parentElement}return e.join(" > ")}var B=typeof window<"u"?window.fetch.bind(window):(()=>{});function m(r,e){let t=`${r.apiUrl}/public/errors`,s=JSON.stringify(e);try{B(t,{method:"POST",headers:{"Content-Type":"application/json","X-API-Key":r.apiKey},body:s,keepalive:!0}).catch(()=>{})}catch{}}var l=class{constructor(){this.config=null;this.breadcrumbs=[];this.user=null;this.tags={};this.initialized=!1;this.sending=!1;this.onError=e=>{let t=e.error||new Error(e.message||"Unknown error");this.captureException(t)};this.onUnhandledRejection=e=>{let t=e.reason;this.captureException(t)}}init(e){if(this.initialized){console.warn("[Upgoose] Already initialized. Call destroy() first to re-initialize.");return}if(!e.apiKey||!e.apiUrl){console.error("[Upgoose] apiKey and apiUrl are required.");return}this.config={sampleRate:1,maxBreadcrumbs:50,captureConsole:!0,captureClicks:!0,captureNavigation:!0,captureNetwork:!0,captureUnhandled:!0,...e},this.breadcrumbs=[],this.initialized=!0,d(this.config,t=>this.addBreadcrumb(t)),this.config.captureUnhandled&&this.setupGlobalHandlers()}captureException(e,t){if(!this.config)return;let{message:s,stack:n}=P(e);this.send({message:s,stack:n,level:t?.level||"error",tags:t?.tags,fingerprint:t?.fingerprint})}captureMessage(e,t="error"){this.config&&this.send({message:e,level:t})}setUser(e){this.user=e}setTag(e,t){this.tags[e]=t}addBreadcrumb(e){let t=this.config?.maxBreadcrumbs??50;this.breadcrumbs.push(e),this.breadcrumbs.length>t&&(this.breadcrumbs=this.breadcrumbs.slice(-t))}destroy(){g(),this.removeGlobalHandlers(),this.config=null,this.breadcrumbs=[],this.user=null,this.tags={},this.initialized=!1}setupGlobalHandlers(){window.addEventListener("error",this.onError),window.addEventListener("unhandledrejection",this.onUnhandledRejection)}removeGlobalHandlers(){window.removeEventListener("error",this.onError),window.removeEventListener("unhandledrejection",this.onUnhandledRejection)}send(e){let t=this.config;if(t&&!this.sending){this.sending=!0;try{if(typeof t.sampleRate=="number"&&Math.random()>t.sampleRate)return;let s={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:_(location.href),title:document.title},browser:D()},fingerprint:e.fingerprint},n=s;if(t.beforeSend)try{n=t.beforeSend(s)}catch{return}if(!n)return;m({apiKey:t.apiKey,apiUrl:t.apiUrl},n)}finally{this.sending=!1}}}};function P(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 e=new URL(r),t=["token","key","secret","password","auth","code","session","jwt","access_token","refresh_token","api_key","apikey"];for(let s of[...e.searchParams.keys()])t.some(n=>s.toLowerCase().includes(n))&&e.searchParams.set(s,"[REDACTED]");return e.hash="",e.toString()}catch{return r}}function D(){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 f=new l,H=f;0&&(module.exports={Upgoose,upgoose});
2
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +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 } 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\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 * Capture a message as an error event.\n *\n * ```ts\n * upgoose.captureMessage(\"User hit rate limit\", \"warning\");\n * ```\n */\n captureMessage(\n message: string,\n level: ErrorPayload[\"level\"] = \"error\"\n ): void {\n if (!this.config) return;\n this.send({ message, level });\n }\n\n /**\n * Set user context. Attached to all subsequent events.\n *\n * ```ts\n * upgoose.setUser({ id: '123', email: 'user@example.com' });\n * ```\n */\n setUser(user: UserContext | null): void {\n this.user = user;\n }\n\n /**\n * Set a global tag. Attached to all subsequent events.\n */\n setTag(key: string, value: string): void {\n this.tags[key] = value;\n }\n\n /**\n * Add a breadcrumb manually.\n */\n addBreadcrumb(crumb: Breadcrumb): void {\n const max = this.config?.maxBreadcrumbs ?? 50;\n this.breadcrumbs.push(crumb);\n if (this.breadcrumbs.length > max) {\n this.breadcrumbs = this.breadcrumbs.slice(-max);\n }\n }\n\n /**\n * Tear down the SDK — removes global handlers and breadcrumb instrumentation.\n */\n destroy(): void {\n teardownBreadcrumbs();\n this.removeGlobalHandlers();\n this.config = null;\n this.breadcrumbs = [];\n this.user = null;\n this.tags = {};\n this.initialized = false;\n }\n\n // -- Private --\n\n private onError = (event: ErrorEvent): void => {\n const error = event.error || new Error(event.message || \"Unknown error\");\n this.captureException(error);\n };\n\n private onUnhandledRejection = (event: PromiseRejectionEvent): void => {\n const error = event.reason;\n this.captureException(error);\n };\n\n private setupGlobalHandlers(): void {\n window.addEventListener(\"error\", this.onError);\n window.addEventListener(\"unhandledrejection\", this.onUnhandledRejection);\n }\n\n private removeGlobalHandlers(): void {\n window.removeEventListener(\"error\", this.onError);\n window.removeEventListener(\"unhandledrejection\", this.onUnhandledRejection);\n }\n\n private send(partial: {\n message: string;\n stack?: string;\n level: ErrorPayload[\"level\"];\n tags?: Record<string, string>;\n fingerprint?: string;\n }): void {\n const config = this.config;\n if (!config) return;\n\n // Re-entrancy guard: prevent infinite loops if send() itself throws\n // or if beforeSend throws and triggers the global error handler\n if (this.sending) return;\n this.sending = true;\n\n try {\n // Sample rate check\n if (typeof config.sampleRate === \"number\" && Math.random() > config.sampleRate) {\n return;\n }\n\n const payload: ErrorPayload = {\n message: partial.message.slice(0, 2000),\n stack: partial.stack?.slice(0, 10000),\n level: partial.level,\n environment: config.environment,\n release: config.release,\n tags: { ...this.tags, ...partial.tags },\n context: {\n breadcrumbs: this.breadcrumbs.slice(-30),\n ...(this.user ? { user: this.user } : {}),\n page: {\n url: stripUrlSensitiveParams(location.href),\n title: document.title,\n },\n browser: getBrowserContext(),\n },\n fingerprint: partial.fingerprint,\n };\n\n // beforeSend hook (wrapped to prevent throws from looping)\n let processed: ErrorPayload | null = payload;\n if (config.beforeSend) {\n try {\n processed = config.beforeSend(payload);\n } catch {\n // Drop event if beforeSend throws\n return;\n }\n }\n if (!processed) return;\n\n sendEvent(\n { apiKey: config.apiKey, apiUrl: config.apiUrl },\n processed\n );\n } finally {\n this.sending = false;\n }\n }\n}\n\nfunction normalizeError(error: unknown): { message: string; stack?: string } {\n if (error instanceof Error) {\n return { message: error.message, stack: error.stack };\n }\n if (typeof error === \"string\") {\n return { message: error };\n }\n if (error && typeof error === \"object\" && \"message\" in error) {\n return {\n message: String((error as { message: unknown }).message),\n stack: \"stack\" in error ? String((error as { stack: unknown }).stack) : undefined,\n };\n }\n return { message: String(error) };\n}\n\n/** Strip common sensitive query/hash params from URLs */\nfunction stripUrlSensitiveParams(url: string): string {\n try {\n const u = new URL(url);\n const sensitive = [\"token\", \"key\", \"secret\", \"password\", \"auth\", \"code\", \"session\", \"jwt\", \"access_token\", \"refresh_token\", \"api_key\", \"apikey\"];\n for (const param of [...u.searchParams.keys()]) {\n if (sensitive.some((s) => param.toLowerCase().includes(s))) {\n u.searchParams.set(param, \"[REDACTED]\");\n }\n }\n // Strip hash (often contains tokens in SPAs)\n u.hash = \"\";\n return u.toString();\n } catch {\n return url;\n }\n}\n\nfunction getBrowserContext(): Record<string, string> {\n const ctx: Record<string, string> = {};\n try {\n ctx.userAgent = navigator.userAgent;\n ctx.language = navigator.language;\n ctx.viewport = `${window.innerWidth}x${window.innerHeight}`;\n ctx.screen = `${screen.width}x${screen.height}`;\n } catch {\n // Ignore\n }\n return ctx;\n}\n\n// Singleton instance\nconst upgoose = new Upgoose();\nexport default upgoose;\nexport { upgoose, Upgoose };\n","import type { Breadcrumb, UpgooseConfig } from \"./types\";\n\ntype BreadcrumbCallback = (crumb: Breadcrumb) => void;\n\nconst teardowns: Array<() => void> = [];\n\nexport function setupBreadcrumbs(\n config: UpgooseConfig,\n addBreadcrumb: BreadcrumbCallback\n): void {\n if (config.captureConsole !== false) {\n setupConsole(addBreadcrumb);\n }\n if (config.captureClicks !== false) {\n setupClicks(addBreadcrumb);\n }\n if (config.captureNavigation !== false) {\n setupNavigation(addBreadcrumb);\n }\n if (config.captureNetwork !== false) {\n setupFetch(addBreadcrumb);\n setupXHR(addBreadcrumb);\n }\n}\n\nexport function teardownBreadcrumbs(): void {\n for (const fn of teardowns) {\n try {\n fn();\n } catch {\n // Best effort cleanup\n }\n }\n teardowns.length = 0;\n}\n\nfunction setupConsole(add: BreadcrumbCallback): void {\n const original = {\n log: console.log,\n warn: console.warn,\n error: console.error,\n };\n\n for (const level of [\"log\", \"warn\", \"error\"] as const) {\n const orig = original[level];\n console[level] = (...args: unknown[]) => {\n add({\n timestamp: Date.now(),\n category: \"console\",\n message: args.map(String).join(\" \").slice(0, 500),\n level: level === \"error\" ? \"error\" : level === \"warn\" ? \"warning\" : \"info\",\n });\n orig.apply(console, args);\n };\n }\n\n teardowns.push(() => {\n console.log = original.log;\n console.warn = original.warn;\n console.error = original.error;\n });\n}\n\nfunction setupClicks(add: BreadcrumbCallback): void {\n const handler = (event: MouseEvent) => {\n const target = event.target as HTMLElement | null;\n if (!target) return;\n\n const tag = target.tagName?.toLowerCase();\n const text = target.textContent?.trim().slice(0, 100) || \"\";\n const selector = getSelector(target);\n\n add({\n timestamp: Date.now(),\n category: \"ui\",\n message: `click on ${tag}${text ? `: \"${text}\"` : \"\"}`,\n data: { selector },\n });\n };\n\n document.addEventListener(\"click\", handler, { capture: true, passive: true });\n teardowns.push(() => document.removeEventListener(\"click\", handler, { capture: true }));\n}\n\nfunction setupNavigation(add: BreadcrumbCallback): void {\n // pushState\n const origPush = history.pushState;\n history.pushState = function (...args) {\n add({\n timestamp: Date.now(),\n category: \"navigation\",\n message: String(args[2] || \"\"),\n data: { from: location.href },\n });\n return origPush.apply(this, args);\n };\n\n // replaceState\n const origReplace = history.replaceState;\n history.replaceState = function (...args) {\n add({\n timestamp: Date.now(),\n category: \"navigation\",\n message: String(args[2] || \"\"),\n data: { from: location.href },\n });\n return origReplace.apply(this, args);\n };\n\n // popstate (back/forward)\n const popHandler = () => {\n add({\n timestamp: Date.now(),\n category: \"navigation\",\n message: location.href,\n });\n };\n window.addEventListener(\"popstate\", popHandler);\n\n teardowns.push(() => {\n history.pushState = origPush;\n history.replaceState = origReplace;\n window.removeEventListener(\"popstate\", popHandler);\n });\n}\n\nfunction setupFetch(add: BreadcrumbCallback): void {\n const origFetch = window.fetch;\n\n window.fetch = async function (input, init?) {\n const rawUrl = typeof input === \"string\" ? input : input instanceof URL ? input.href : input.url;\n const method = init?.method || \"GET\";\n const start = Date.now();\n\n // Skip breadcrumbs for SDK's own requests\n if (rawUrl.includes(\"/public/errors\")) {\n return origFetch.call(window, input, init);\n }\n\n const url = scrubUrl(rawUrl);\n\n try {\n const response = await origFetch.call(window, input, init);\n add({\n timestamp: Date.now(),\n category: \"fetch\",\n message: `${method} ${url}`,\n level: response.ok ? \"info\" : \"error\",\n data: {\n status: response.status,\n duration: Date.now() - start,\n },\n });\n return response;\n } catch (err) {\n add({\n timestamp: Date.now(),\n category: \"fetch\",\n message: `${method} ${url}`,\n level: \"error\",\n data: {\n error: String(err),\n duration: Date.now() - start,\n },\n });\n throw err;\n }\n };\n\n teardowns.push(() => {\n window.fetch = origFetch;\n });\n}\n\nfunction setupXHR(add: BreadcrumbCallback): void {\n const origOpen = XMLHttpRequest.prototype.open;\n const origSend = XMLHttpRequest.prototype.send;\n\n XMLHttpRequest.prototype.open = function (method: string, url: string | URL) {\n (this as any).__ug_method = method;\n (this as any).__ug_url = String(url);\n // eslint-disable-next-line prefer-rest-params\n return origOpen.apply(this, arguments as any);\n };\n\n XMLHttpRequest.prototype.send = function () {\n const start = Date.now();\n const method = (this as any).__ug_method || \"GET\";\n const rawUrl: string = (this as any).__ug_url || \"\";\n\n // Skip breadcrumbs for SDK's own requests\n if (rawUrl.includes(\"/public/errors\")) {\n return origSend.apply(this, arguments as any);\n }\n\n const url = scrubUrl(rawUrl);\n\n this.addEventListener(\"loadend\", function () {\n add({\n timestamp: Date.now(),\n category: \"xhr\",\n message: `${method} ${url}`,\n level: this.status >= 400 ? \"error\" : \"info\",\n data: {\n status: this.status,\n duration: Date.now() - start,\n },\n });\n });\n\n // eslint-disable-next-line prefer-rest-params\n return origSend.apply(this, arguments as any);\n };\n\n teardowns.push(() => {\n XMLHttpRequest.prototype.open = origOpen;\n XMLHttpRequest.prototype.send = origSend;\n });\n}\n\n/** Strip sensitive query params from URLs in breadcrumbs */\nfunction scrubUrl(url: string): string {\n try {\n const u = new URL(url, location.origin);\n const sensitive = [\"token\", \"key\", \"secret\", \"password\", \"auth\", \"code\", \"session\", \"jwt\", \"access_token\", \"refresh_token\", \"api_key\", \"apikey\"];\n for (const param of [...u.searchParams.keys()]) {\n if (sensitive.some((s) => param.toLowerCase().includes(s))) {\n u.searchParams.set(param, \"[REDACTED]\");\n }\n }\n u.hash = \"\";\n return u.toString();\n } catch {\n return url;\n }\n}\n\nfunction getSelector(el: HTMLElement): string {\n const parts: string[] = [];\n let current: HTMLElement | null = el;\n\n for (let i = 0; i < 3 && current && current !== document.body; i++) {\n let part = current.tagName.toLowerCase();\n if (current.id) {\n part += `#${current.id}`;\n } else if (current.className && typeof current.className === \"string\") {\n const cls = current.className.trim().split(/\\s+/).slice(0, 2).join(\".\");\n if (cls) part += `.${cls}`;\n }\n parts.unshift(part);\n current = current.parentElement;\n }\n\n return parts.join(\" > \");\n}\n","import type { ErrorPayload } from \"./types\";\n\n// Capture the original fetch before any instrumentation wraps it.\n// This prevents SDK requests from appearing in breadcrumbs and\n// avoids recursive loops if the fetch wrapper itself errors.\nconst originalFetch: typeof fetch =\n typeof window !== \"undefined\" ? window.fetch.bind(window) : ((() => {}) as any);\n\nexport interface TransportConfig {\n apiKey: string;\n apiUrl: string;\n}\n\n/**\n * Must be called BEFORE setupBreadcrumbs() to capture the\n * un-instrumented fetch reference.\n */\nexport function captureOriginalFetch(): void {\n // Already captured at module load time above.\n // This function exists as a documentation marker.\n}\n\nexport function sendEvent(\n config: TransportConfig,\n payload: ErrorPayload\n): void {\n const url = `${config.apiUrl}/public/errors`;\n const body = JSON.stringify(payload);\n\n try {\n originalFetch(url, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"X-API-Key\": config.apiKey,\n },\n body,\n keepalive: true, // Survives page unload\n }).catch(() => {\n // Silently fail — never impact user's app\n });\n } catch {\n // Silently fail\n }\n}\n"],"mappings":"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","names":["index_exports","__export","Upgoose","index_default","upgoose","__toCommonJS","teardowns","setupBreadcrumbs","config","addBreadcrumb","setupConsole","setupClicks","setupNavigation","setupFetch","setupXHR","teardownBreadcrumbs","fn","add","original","level","orig","args","handler","event","target","tag","text","selector","getSelector","origPush","origReplace","popHandler","origFetch","input","init","rawUrl","method","start","url","scrubUrl","response","err","origOpen","origSend","u","sensitive","param","s","el","parts","current","i","part","cls","originalFetch","sendEvent","config","payload","url","body","originalFetch","Upgoose","event","error","config","setupBreadcrumbs","crumb","extra","message","stack","normalizeError","level","user","key","value","max","teardownBreadcrumbs","partial","payload","stripUrlSensitiveParams","getBrowserContext","processed","sendEvent","url","u","sensitive","param","s","ctx","upgoose","index_default"]}
@@ -0,0 +1,118 @@
1
+ interface UpgooseConfig {
2
+ /** API key from your Upgoose error project */
3
+ apiKey: string;
4
+ /** Upgoose API base URL */
5
+ apiUrl: string;
6
+ /** App release/version (e.g. "1.2.3") */
7
+ release?: string;
8
+ /** Environment (e.g. "production", "staging") */
9
+ environment?: string;
10
+ /** Sample rate 0-1 (1 = capture everything, 0.5 = 50%) */
11
+ sampleRate?: number;
12
+ /** Max breadcrumbs to keep (default: 50) */
13
+ maxBreadcrumbs?: number;
14
+ /** Transform or filter events before sending. Return null to drop. */
15
+ beforeSend?: (event: ErrorPayload) => ErrorPayload | null;
16
+ /** Enable console breadcrumbs (default: true) */
17
+ captureConsole?: boolean;
18
+ /** Enable click breadcrumbs (default: true) */
19
+ captureClicks?: boolean;
20
+ /** Enable navigation breadcrumbs (default: true) */
21
+ captureNavigation?: boolean;
22
+ /** Enable fetch/XHR breadcrumbs (default: true) */
23
+ captureNetwork?: boolean;
24
+ /** Enable global error handlers (default: true) */
25
+ captureUnhandled?: boolean;
26
+ }
27
+ interface Breadcrumb {
28
+ timestamp: number;
29
+ category: "ui" | "navigation" | "console" | "fetch" | "xhr";
30
+ message: string;
31
+ level?: "info" | "warning" | "error";
32
+ data?: Record<string, unknown>;
33
+ }
34
+ interface UserContext {
35
+ id?: string;
36
+ email?: string;
37
+ username?: string;
38
+ }
39
+ interface ErrorPayload {
40
+ message: string;
41
+ stack?: string;
42
+ level: "error" | "warning" | "fatal";
43
+ environment?: string;
44
+ release?: string;
45
+ tags?: Record<string, string>;
46
+ context?: Record<string, unknown>;
47
+ fingerprint?: string;
48
+ }
49
+
50
+ declare class Upgoose {
51
+ private config;
52
+ private breadcrumbs;
53
+ private user;
54
+ private tags;
55
+ private initialized;
56
+ private sending;
57
+ /**
58
+ * Initialize the Upgoose SDK. Call this once, as early as possible.
59
+ *
60
+ * ```ts
61
+ * import upgoose from '@upgoose/browser';
62
+ * upgoose.init({ apiKey: 'ug_...', apiUrl: 'https://api.upgoose.app' });
63
+ * ```
64
+ */
65
+ init(config: UpgooseConfig): void;
66
+ /**
67
+ * Capture an exception manually.
68
+ *
69
+ * ```ts
70
+ * try {
71
+ * riskyOperation();
72
+ * } catch (err) {
73
+ * upgoose.captureException(err);
74
+ * }
75
+ * ```
76
+ */
77
+ captureException(error: unknown, extra?: {
78
+ tags?: Record<string, string>;
79
+ fingerprint?: string;
80
+ level?: ErrorPayload["level"];
81
+ }): void;
82
+ /**
83
+ * Capture a message as an error event.
84
+ *
85
+ * ```ts
86
+ * upgoose.captureMessage("User hit rate limit", "warning");
87
+ * ```
88
+ */
89
+ captureMessage(message: string, level?: ErrorPayload["level"]): void;
90
+ /**
91
+ * Set user context. Attached to all subsequent events.
92
+ *
93
+ * ```ts
94
+ * upgoose.setUser({ id: '123', email: 'user@example.com' });
95
+ * ```
96
+ */
97
+ setUser(user: UserContext | null): void;
98
+ /**
99
+ * Set a global tag. Attached to all subsequent events.
100
+ */
101
+ setTag(key: string, value: string): void;
102
+ /**
103
+ * Add a breadcrumb manually.
104
+ */
105
+ addBreadcrumb(crumb: Breadcrumb): void;
106
+ /**
107
+ * Tear down the SDK — removes global handlers and breadcrumb instrumentation.
108
+ */
109
+ destroy(): void;
110
+ private onError;
111
+ private onUnhandledRejection;
112
+ private setupGlobalHandlers;
113
+ private removeGlobalHandlers;
114
+ private send;
115
+ }
116
+ declare const upgoose: Upgoose;
117
+
118
+ export { type Breadcrumb, type ErrorPayload, Upgoose, type UpgooseConfig, type UserContext, upgoose as default, upgoose };
@@ -0,0 +1,118 @@
1
+ interface UpgooseConfig {
2
+ /** API key from your Upgoose error project */
3
+ apiKey: string;
4
+ /** Upgoose API base URL */
5
+ apiUrl: string;
6
+ /** App release/version (e.g. "1.2.3") */
7
+ release?: string;
8
+ /** Environment (e.g. "production", "staging") */
9
+ environment?: string;
10
+ /** Sample rate 0-1 (1 = capture everything, 0.5 = 50%) */
11
+ sampleRate?: number;
12
+ /** Max breadcrumbs to keep (default: 50) */
13
+ maxBreadcrumbs?: number;
14
+ /** Transform or filter events before sending. Return null to drop. */
15
+ beforeSend?: (event: ErrorPayload) => ErrorPayload | null;
16
+ /** Enable console breadcrumbs (default: true) */
17
+ captureConsole?: boolean;
18
+ /** Enable click breadcrumbs (default: true) */
19
+ captureClicks?: boolean;
20
+ /** Enable navigation breadcrumbs (default: true) */
21
+ captureNavigation?: boolean;
22
+ /** Enable fetch/XHR breadcrumbs (default: true) */
23
+ captureNetwork?: boolean;
24
+ /** Enable global error handlers (default: true) */
25
+ captureUnhandled?: boolean;
26
+ }
27
+ interface Breadcrumb {
28
+ timestamp: number;
29
+ category: "ui" | "navigation" | "console" | "fetch" | "xhr";
30
+ message: string;
31
+ level?: "info" | "warning" | "error";
32
+ data?: Record<string, unknown>;
33
+ }
34
+ interface UserContext {
35
+ id?: string;
36
+ email?: string;
37
+ username?: string;
38
+ }
39
+ interface ErrorPayload {
40
+ message: string;
41
+ stack?: string;
42
+ level: "error" | "warning" | "fatal";
43
+ environment?: string;
44
+ release?: string;
45
+ tags?: Record<string, string>;
46
+ context?: Record<string, unknown>;
47
+ fingerprint?: string;
48
+ }
49
+
50
+ declare class Upgoose {
51
+ private config;
52
+ private breadcrumbs;
53
+ private user;
54
+ private tags;
55
+ private initialized;
56
+ private sending;
57
+ /**
58
+ * Initialize the Upgoose SDK. Call this once, as early as possible.
59
+ *
60
+ * ```ts
61
+ * import upgoose from '@upgoose/browser';
62
+ * upgoose.init({ apiKey: 'ug_...', apiUrl: 'https://api.upgoose.app' });
63
+ * ```
64
+ */
65
+ init(config: UpgooseConfig): void;
66
+ /**
67
+ * Capture an exception manually.
68
+ *
69
+ * ```ts
70
+ * try {
71
+ * riskyOperation();
72
+ * } catch (err) {
73
+ * upgoose.captureException(err);
74
+ * }
75
+ * ```
76
+ */
77
+ captureException(error: unknown, extra?: {
78
+ tags?: Record<string, string>;
79
+ fingerprint?: string;
80
+ level?: ErrorPayload["level"];
81
+ }): void;
82
+ /**
83
+ * Capture a message as an error event.
84
+ *
85
+ * ```ts
86
+ * upgoose.captureMessage("User hit rate limit", "warning");
87
+ * ```
88
+ */
89
+ captureMessage(message: string, level?: ErrorPayload["level"]): void;
90
+ /**
91
+ * Set user context. Attached to all subsequent events.
92
+ *
93
+ * ```ts
94
+ * upgoose.setUser({ id: '123', email: 'user@example.com' });
95
+ * ```
96
+ */
97
+ setUser(user: UserContext | null): void;
98
+ /**
99
+ * Set a global tag. Attached to all subsequent events.
100
+ */
101
+ setTag(key: string, value: string): void;
102
+ /**
103
+ * Add a breadcrumb manually.
104
+ */
105
+ addBreadcrumb(crumb: Breadcrumb): void;
106
+ /**
107
+ * Tear down the SDK — removes global handlers and breadcrumb instrumentation.
108
+ */
109
+ destroy(): void;
110
+ private onError;
111
+ private onUnhandledRejection;
112
+ private setupGlobalHandlers;
113
+ private removeGlobalHandlers;
114
+ private send;
115
+ }
116
+ declare const upgoose: Upgoose;
117
+
118
+ export { type Breadcrumb, type ErrorPayload, Upgoose, type UpgooseConfig, type UserContext, upgoose as default, upgoose };
package/dist/index.js ADDED
@@ -0,0 +1,2 @@
1
+ var a=[];function p(r,t){r.captureConsole!==!1&&m(t),r.captureClicks!==!1&&f(t),r.captureNavigation!==!1&&y(t),r.captureNetwork!==!1&&(w(t),v(t))}function d(){for(let r of a)try{r()}catch{}a.length=0}function m(r){let t={log:console.log,warn:console.warn,error:console.error};for(let e of["log","warn","error"]){let n=t[e];console[e]=(...s)=>{r({timestamp:Date.now(),category:"console",message:s.map(String).join(" ").slice(0,500),level:e==="error"?"error":e==="warn"?"warning":"info"}),n.apply(console,s)}}a.push(()=>{console.log=t.log,console.warn=t.warn,console.error=t.error})}function f(r){let t=e=>{let n=e.target;if(!n)return;let s=n.tagName?.toLowerCase(),o=n.textContent?.trim().slice(0,100)||"",i=b(n);r({timestamp:Date.now(),category:"ui",message:`click on ${s}${o?`: "${o}"`:""}`,data:{selector:i}})};document.addEventListener("click",t,{capture:!0,passive:!0}),a.push(()=>document.removeEventListener("click",t,{capture:!0}))}function y(r){let t=history.pushState;history.pushState=function(...s){return r({timestamp:Date.now(),category:"navigation",message:String(s[2]||""),data:{from:location.href}}),t.apply(this,s)};let e=history.replaceState;history.replaceState=function(...s){return r({timestamp:Date.now(),category:"navigation",message:String(s[2]||""),data:{from:location.href}}),e.apply(this,s)};let n=()=>{r({timestamp:Date.now(),category:"navigation",message:location.href})};window.addEventListener("popstate",n),a.push(()=>{history.pushState=t,history.replaceState=e,window.removeEventListener("popstate",n)})}function w(r){let t=window.fetch;window.fetch=async function(e,n){let s=typeof e=="string"?e:e instanceof URL?e.href:e.url,o=n?.method||"GET",i=Date.now();if(s.includes("/public/errors"))return t.call(window,e,n);let u=g(s);try{let c=await t.call(window,e,n);return r({timestamp:Date.now(),category:"fetch",message:`${o} ${u}`,level:c.ok?"info":"error",data:{status:c.status,duration:Date.now()-i}}),c}catch(c){throw r({timestamp:Date.now(),category:"fetch",message:`${o} ${u}`,level:"error",data:{error:String(c),duration:Date.now()-i}}),c}},a.push(()=>{window.fetch=t})}function v(r){let t=XMLHttpRequest.prototype.open,e=XMLHttpRequest.prototype.send;XMLHttpRequest.prototype.open=function(n,s){return this.__ug_method=n,this.__ug_url=String(s),t.apply(this,arguments)},XMLHttpRequest.prototype.send=function(){let n=Date.now(),s=this.__ug_method||"GET",o=this.__ug_url||"";if(o.includes("/public/errors"))return e.apply(this,arguments);let i=g(o);return this.addEventListener("loadend",function(){r({timestamp:Date.now(),category:"xhr",message:`${s} ${i}`,level:this.status>=400?"error":"info",data:{status:this.status,duration:Date.now()-n}})}),e.apply(this,arguments)},a.push(()=>{XMLHttpRequest.prototype.open=t,XMLHttpRequest.prototype.send=e})}function g(r){try{let t=new URL(r,location.origin),e=["token","key","secret","password","auth","code","session","jwt","access_token","refresh_token","api_key","apikey"];for(let n of[...t.searchParams.keys()])e.some(s=>n.toLowerCase().includes(s))&&t.searchParams.set(n,"[REDACTED]");return t.hash="",t.toString()}catch{return r}}function b(r){let t=[],e=r;for(let n=0;n<3&&e&&e!==document.body;n++){let s=e.tagName.toLowerCase();if(e.id)s+=`#${e.id}`;else if(e.className&&typeof e.className=="string"){let o=e.className.trim().split(/\s+/).slice(0,2).join(".");o&&(s+=`.${o}`)}t.unshift(s),e=e.parentElement}return t.join(" > ")}var k=typeof window<"u"?window.fetch.bind(window):(()=>{});function h(r,t){let e=`${r.apiUrl}/public/errors`,n=JSON.stringify(t);try{k(e,{method:"POST",headers:{"Content-Type":"application/json","X-API-Key":r.apiKey},body:n,keepalive:!0}).catch(()=>{})}catch{}}var l=class{constructor(){this.config=null;this.breadcrumbs=[];this.user=null;this.tags={};this.initialized=!1;this.sending=!1;this.onError=t=>{let e=t.error||new Error(t.message||"Unknown error");this.captureException(e)};this.onUnhandledRejection=t=>{let e=t.reason;this.captureException(e)}}init(t){if(this.initialized){console.warn("[Upgoose] Already initialized. Call destroy() first to re-initialize.");return}if(!t.apiKey||!t.apiUrl){console.error("[Upgoose] apiKey and apiUrl are required.");return}this.config={sampleRate:1,maxBreadcrumbs:50,captureConsole:!0,captureClicks:!0,captureNavigation:!0,captureNetwork:!0,captureUnhandled:!0,...t},this.breadcrumbs=[],this.initialized=!0,p(this.config,e=>this.addBreadcrumb(e)),this.config.captureUnhandled&&this.setupGlobalHandlers()}captureException(t,e){if(!this.config)return;let{message:n,stack:s}=E(t);this.send({message:n,stack:s,level:e?.level||"error",tags:e?.tags,fingerprint:e?.fingerprint})}captureMessage(t,e="error"){this.config&&this.send({message:t,level:e})}setUser(t){this.user=t}setTag(t,e){this.tags[t]=e}addBreadcrumb(t){let e=this.config?.maxBreadcrumbs??50;this.breadcrumbs.push(t),this.breadcrumbs.length>e&&(this.breadcrumbs=this.breadcrumbs.slice(-e))}destroy(){d(),this.removeGlobalHandlers(),this.config=null,this.breadcrumbs=[],this.user=null,this.tags={},this.initialized=!1}setupGlobalHandlers(){window.addEventListener("error",this.onError),window.addEventListener("unhandledrejection",this.onUnhandledRejection)}removeGlobalHandlers(){window.removeEventListener("error",this.onError),window.removeEventListener("unhandledrejection",this.onUnhandledRejection)}send(t){let e=this.config;if(e&&!this.sending){this.sending=!0;try{if(typeof e.sampleRate=="number"&&Math.random()>e.sampleRate)return;let n={message:t.message.slice(0,2e3),stack:t.stack?.slice(0,1e4),level:t.level,environment:e.environment,release:e.release,tags:{...this.tags,...t.tags},context:{breadcrumbs:this.breadcrumbs.slice(-30),...this.user?{user:this.user}:{},page:{url:C(location.href),title:document.title},browser:U()},fingerprint:t.fingerprint},s=n;if(e.beforeSend)try{s=e.beforeSend(n)}catch{return}if(!s)return;h({apiKey:e.apiKey,apiUrl:e.apiUrl},s)}finally{this.sending=!1}}}};function E(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 C(r){try{let t=new URL(r),e=["token","key","secret","password","auth","code","session","jwt","access_token","refresh_token","api_key","apikey"];for(let n of[...t.searchParams.keys()])e.some(s=>n.toLowerCase().includes(s))&&t.searchParams.set(n,"[REDACTED]");return t.hash="",t.toString()}catch{return r}}function U(){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 L=new l,P=L;export{l as Upgoose,P as default,L as upgoose};
2
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/breadcrumbs.ts","../src/transport.ts","../src/index.ts"],"sourcesContent":["import type { Breadcrumb, UpgooseConfig } from \"./types\";\n\ntype BreadcrumbCallback = (crumb: Breadcrumb) => void;\n\nconst teardowns: Array<() => void> = [];\n\nexport function setupBreadcrumbs(\n config: UpgooseConfig,\n addBreadcrumb: BreadcrumbCallback\n): void {\n if (config.captureConsole !== false) {\n setupConsole(addBreadcrumb);\n }\n if (config.captureClicks !== false) {\n setupClicks(addBreadcrumb);\n }\n if (config.captureNavigation !== false) {\n setupNavigation(addBreadcrumb);\n }\n if (config.captureNetwork !== false) {\n setupFetch(addBreadcrumb);\n setupXHR(addBreadcrumb);\n }\n}\n\nexport function teardownBreadcrumbs(): void {\n for (const fn of teardowns) {\n try {\n fn();\n } catch {\n // Best effort cleanup\n }\n }\n teardowns.length = 0;\n}\n\nfunction setupConsole(add: BreadcrumbCallback): void {\n const original = {\n log: console.log,\n warn: console.warn,\n error: console.error,\n };\n\n for (const level of [\"log\", \"warn\", \"error\"] as const) {\n const orig = original[level];\n console[level] = (...args: unknown[]) => {\n add({\n timestamp: Date.now(),\n category: \"console\",\n message: args.map(String).join(\" \").slice(0, 500),\n level: level === \"error\" ? \"error\" : level === \"warn\" ? \"warning\" : \"info\",\n });\n orig.apply(console, args);\n };\n }\n\n teardowns.push(() => {\n console.log = original.log;\n console.warn = original.warn;\n console.error = original.error;\n });\n}\n\nfunction setupClicks(add: BreadcrumbCallback): void {\n const handler = (event: MouseEvent) => {\n const target = event.target as HTMLElement | null;\n if (!target) return;\n\n const tag = target.tagName?.toLowerCase();\n const text = target.textContent?.trim().slice(0, 100) || \"\";\n const selector = getSelector(target);\n\n add({\n timestamp: Date.now(),\n category: \"ui\",\n message: `click on ${tag}${text ? `: \"${text}\"` : \"\"}`,\n data: { selector },\n });\n };\n\n document.addEventListener(\"click\", handler, { capture: true, passive: true });\n teardowns.push(() => document.removeEventListener(\"click\", handler, { capture: true }));\n}\n\nfunction setupNavigation(add: BreadcrumbCallback): void {\n // pushState\n const origPush = history.pushState;\n history.pushState = function (...args) {\n add({\n timestamp: Date.now(),\n category: \"navigation\",\n message: String(args[2] || \"\"),\n data: { from: location.href },\n });\n return origPush.apply(this, args);\n };\n\n // replaceState\n const origReplace = history.replaceState;\n history.replaceState = function (...args) {\n add({\n timestamp: Date.now(),\n category: \"navigation\",\n message: String(args[2] || \"\"),\n data: { from: location.href },\n });\n return origReplace.apply(this, args);\n };\n\n // popstate (back/forward)\n const popHandler = () => {\n add({\n timestamp: Date.now(),\n category: \"navigation\",\n message: location.href,\n });\n };\n window.addEventListener(\"popstate\", popHandler);\n\n teardowns.push(() => {\n history.pushState = origPush;\n history.replaceState = origReplace;\n window.removeEventListener(\"popstate\", popHandler);\n });\n}\n\nfunction setupFetch(add: BreadcrumbCallback): void {\n const origFetch = window.fetch;\n\n window.fetch = async function (input, init?) {\n const rawUrl = typeof input === \"string\" ? input : input instanceof URL ? input.href : input.url;\n const method = init?.method || \"GET\";\n const start = Date.now();\n\n // Skip breadcrumbs for SDK's own requests\n if (rawUrl.includes(\"/public/errors\")) {\n return origFetch.call(window, input, init);\n }\n\n const url = scrubUrl(rawUrl);\n\n try {\n const response = await origFetch.call(window, input, init);\n add({\n timestamp: Date.now(),\n category: \"fetch\",\n message: `${method} ${url}`,\n level: response.ok ? \"info\" : \"error\",\n data: {\n status: response.status,\n duration: Date.now() - start,\n },\n });\n return response;\n } catch (err) {\n add({\n timestamp: Date.now(),\n category: \"fetch\",\n message: `${method} ${url}`,\n level: \"error\",\n data: {\n error: String(err),\n duration: Date.now() - start,\n },\n });\n throw err;\n }\n };\n\n teardowns.push(() => {\n window.fetch = origFetch;\n });\n}\n\nfunction setupXHR(add: BreadcrumbCallback): void {\n const origOpen = XMLHttpRequest.prototype.open;\n const origSend = XMLHttpRequest.prototype.send;\n\n XMLHttpRequest.prototype.open = function (method: string, url: string | URL) {\n (this as any).__ug_method = method;\n (this as any).__ug_url = String(url);\n // eslint-disable-next-line prefer-rest-params\n return origOpen.apply(this, arguments as any);\n };\n\n XMLHttpRequest.prototype.send = function () {\n const start = Date.now();\n const method = (this as any).__ug_method || \"GET\";\n const rawUrl: string = (this as any).__ug_url || \"\";\n\n // Skip breadcrumbs for SDK's own requests\n if (rawUrl.includes(\"/public/errors\")) {\n return origSend.apply(this, arguments as any);\n }\n\n const url = scrubUrl(rawUrl);\n\n this.addEventListener(\"loadend\", function () {\n add({\n timestamp: Date.now(),\n category: \"xhr\",\n message: `${method} ${url}`,\n level: this.status >= 400 ? \"error\" : \"info\",\n data: {\n status: this.status,\n duration: Date.now() - start,\n },\n });\n });\n\n // eslint-disable-next-line prefer-rest-params\n return origSend.apply(this, arguments as any);\n };\n\n teardowns.push(() => {\n XMLHttpRequest.prototype.open = origOpen;\n XMLHttpRequest.prototype.send = origSend;\n });\n}\n\n/** Strip sensitive query params from URLs in breadcrumbs */\nfunction scrubUrl(url: string): string {\n try {\n const u = new URL(url, location.origin);\n const sensitive = [\"token\", \"key\", \"secret\", \"password\", \"auth\", \"code\", \"session\", \"jwt\", \"access_token\", \"refresh_token\", \"api_key\", \"apikey\"];\n for (const param of [...u.searchParams.keys()]) {\n if (sensitive.some((s) => param.toLowerCase().includes(s))) {\n u.searchParams.set(param, \"[REDACTED]\");\n }\n }\n u.hash = \"\";\n return u.toString();\n } catch {\n return url;\n }\n}\n\nfunction getSelector(el: HTMLElement): string {\n const parts: string[] = [];\n let current: HTMLElement | null = el;\n\n for (let i = 0; i < 3 && current && current !== document.body; i++) {\n let part = current.tagName.toLowerCase();\n if (current.id) {\n part += `#${current.id}`;\n } else if (current.className && typeof current.className === \"string\") {\n const cls = current.className.trim().split(/\\s+/).slice(0, 2).join(\".\");\n if (cls) part += `.${cls}`;\n }\n parts.unshift(part);\n current = current.parentElement;\n }\n\n return parts.join(\" > \");\n}\n","import type { ErrorPayload } from \"./types\";\n\n// Capture the original fetch before any instrumentation wraps it.\n// This prevents SDK requests from appearing in breadcrumbs and\n// avoids recursive loops if the fetch wrapper itself errors.\nconst originalFetch: typeof fetch =\n typeof window !== \"undefined\" ? window.fetch.bind(window) : ((() => {}) as any);\n\nexport interface TransportConfig {\n apiKey: string;\n apiUrl: string;\n}\n\n/**\n * Must be called BEFORE setupBreadcrumbs() to capture the\n * un-instrumented fetch reference.\n */\nexport function captureOriginalFetch(): void {\n // Already captured at module load time above.\n // This function exists as a documentation marker.\n}\n\nexport function sendEvent(\n config: TransportConfig,\n payload: ErrorPayload\n): void {\n const url = `${config.apiUrl}/public/errors`;\n const body = JSON.stringify(payload);\n\n try {\n originalFetch(url, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"X-API-Key\": config.apiKey,\n },\n body,\n keepalive: true, // Survives page unload\n }).catch(() => {\n // Silently fail — never impact user's app\n });\n } catch {\n // Silently fail\n }\n}\n","import type {\n UpgooseConfig,\n Breadcrumb,\n UserContext,\n ErrorPayload,\n} from \"./types\";\nimport { setupBreadcrumbs, teardownBreadcrumbs } from \"./breadcrumbs\";\nimport { sendEvent } 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\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 * Capture a message as an error event.\n *\n * ```ts\n * upgoose.captureMessage(\"User hit rate limit\", \"warning\");\n * ```\n */\n captureMessage(\n message: string,\n level: ErrorPayload[\"level\"] = \"error\"\n ): void {\n if (!this.config) return;\n this.send({ message, level });\n }\n\n /**\n * Set user context. Attached to all subsequent events.\n *\n * ```ts\n * upgoose.setUser({ id: '123', email: 'user@example.com' });\n * ```\n */\n setUser(user: UserContext | null): void {\n this.user = user;\n }\n\n /**\n * Set a global tag. Attached to all subsequent events.\n */\n setTag(key: string, value: string): void {\n this.tags[key] = value;\n }\n\n /**\n * Add a breadcrumb manually.\n */\n addBreadcrumb(crumb: Breadcrumb): void {\n const max = this.config?.maxBreadcrumbs ?? 50;\n this.breadcrumbs.push(crumb);\n if (this.breadcrumbs.length > max) {\n this.breadcrumbs = this.breadcrumbs.slice(-max);\n }\n }\n\n /**\n * Tear down the SDK — removes global handlers and breadcrumb instrumentation.\n */\n destroy(): void {\n teardownBreadcrumbs();\n this.removeGlobalHandlers();\n this.config = null;\n this.breadcrumbs = [];\n this.user = null;\n this.tags = {};\n this.initialized = false;\n }\n\n // -- Private --\n\n private onError = (event: ErrorEvent): void => {\n const error = event.error || new Error(event.message || \"Unknown error\");\n this.captureException(error);\n };\n\n private onUnhandledRejection = (event: PromiseRejectionEvent): void => {\n const error = event.reason;\n this.captureException(error);\n };\n\n private setupGlobalHandlers(): void {\n window.addEventListener(\"error\", this.onError);\n window.addEventListener(\"unhandledrejection\", this.onUnhandledRejection);\n }\n\n private removeGlobalHandlers(): void {\n window.removeEventListener(\"error\", this.onError);\n window.removeEventListener(\"unhandledrejection\", this.onUnhandledRejection);\n }\n\n private send(partial: {\n message: string;\n stack?: string;\n level: ErrorPayload[\"level\"];\n tags?: Record<string, string>;\n fingerprint?: string;\n }): void {\n const config = this.config;\n if (!config) return;\n\n // Re-entrancy guard: prevent infinite loops if send() itself throws\n // or if beforeSend throws and triggers the global error handler\n if (this.sending) return;\n this.sending = true;\n\n try {\n // Sample rate check\n if (typeof config.sampleRate === \"number\" && Math.random() > config.sampleRate) {\n return;\n }\n\n const payload: ErrorPayload = {\n message: partial.message.slice(0, 2000),\n stack: partial.stack?.slice(0, 10000),\n level: partial.level,\n environment: config.environment,\n release: config.release,\n tags: { ...this.tags, ...partial.tags },\n context: {\n breadcrumbs: this.breadcrumbs.slice(-30),\n ...(this.user ? { user: this.user } : {}),\n page: {\n url: stripUrlSensitiveParams(location.href),\n title: document.title,\n },\n browser: getBrowserContext(),\n },\n fingerprint: partial.fingerprint,\n };\n\n // beforeSend hook (wrapped to prevent throws from looping)\n let processed: ErrorPayload | null = payload;\n if (config.beforeSend) {\n try {\n processed = config.beforeSend(payload);\n } catch {\n // Drop event if beforeSend throws\n return;\n }\n }\n if (!processed) return;\n\n sendEvent(\n { apiKey: config.apiKey, apiUrl: config.apiUrl },\n processed\n );\n } finally {\n this.sending = false;\n }\n }\n}\n\nfunction normalizeError(error: unknown): { message: string; stack?: string } {\n if (error instanceof Error) {\n return { message: error.message, stack: error.stack };\n }\n if (typeof error === \"string\") {\n return { message: error };\n }\n if (error && typeof error === \"object\" && \"message\" in error) {\n return {\n message: String((error as { message: unknown }).message),\n stack: \"stack\" in error ? String((error as { stack: unknown }).stack) : undefined,\n };\n }\n return { message: String(error) };\n}\n\n/** Strip common sensitive query/hash params from URLs */\nfunction stripUrlSensitiveParams(url: string): string {\n try {\n const u = new URL(url);\n const sensitive = [\"token\", \"key\", \"secret\", \"password\", \"auth\", \"code\", \"session\", \"jwt\", \"access_token\", \"refresh_token\", \"api_key\", \"apikey\"];\n for (const param of [...u.searchParams.keys()]) {\n if (sensitive.some((s) => param.toLowerCase().includes(s))) {\n u.searchParams.set(param, \"[REDACTED]\");\n }\n }\n // Strip hash (often contains tokens in SPAs)\n u.hash = \"\";\n return u.toString();\n } catch {\n return url;\n }\n}\n\nfunction getBrowserContext(): Record<string, string> {\n const ctx: Record<string, string> = {};\n try {\n ctx.userAgent = navigator.userAgent;\n ctx.language = navigator.language;\n ctx.viewport = `${window.innerWidth}x${window.innerHeight}`;\n ctx.screen = `${screen.width}x${screen.height}`;\n } catch {\n // Ignore\n }\n return ctx;\n}\n\n// Singleton instance\nconst upgoose = new Upgoose();\nexport default upgoose;\nexport { upgoose, Upgoose };\n"],"mappings":"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","names":["teardowns","setupBreadcrumbs","config","addBreadcrumb","setupConsole","setupClicks","setupNavigation","setupFetch","setupXHR","teardownBreadcrumbs","fn","add","original","level","orig","args","handler","event","target","tag","text","selector","getSelector","origPush","origReplace","popHandler","origFetch","input","init","rawUrl","method","start","url","scrubUrl","response","err","origOpen","origSend","u","sensitive","param","el","parts","current","i","part","cls","originalFetch","sendEvent","config","payload","url","body","originalFetch","Upgoose","event","error","config","setupBreadcrumbs","crumb","extra","message","stack","normalizeError","level","user","key","value","max","teardownBreadcrumbs","partial","payload","stripUrlSensitiveParams","getBrowserContext","processed","sendEvent","url","u","sensitive","param","ctx","upgoose","index_default"]}
package/package.json ADDED
@@ -0,0 +1,44 @@
1
+ {
2
+ "name": "@upgoose/browser",
3
+ "version": "0.1.0",
4
+ "description": "Upgoose error tracking SDK for browsers",
5
+ "type": "module",
6
+ "main": "./dist/index.cjs",
7
+ "module": "./dist/index.js",
8
+ "types": "./dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/index.d.ts",
12
+ "import": "./dist/index.js",
13
+ "require": "./dist/index.cjs"
14
+ }
15
+ },
16
+ "files": [
17
+ "dist"
18
+ ],
19
+ "scripts": {
20
+ "build": "tsup",
21
+ "dev": "tsup --watch",
22
+ "typecheck": "tsc --noEmit"
23
+ },
24
+ "devDependencies": {
25
+ "tsup": "^8.0.0",
26
+ "typescript": "^5.7.2"
27
+ },
28
+ "keywords": [
29
+ "error-tracking",
30
+ "monitoring",
31
+ "upgoose",
32
+ "browser",
33
+ "sdk"
34
+ ],
35
+ "license": "BSD-3-Clause",
36
+ "repository": {
37
+ "type": "git",
38
+ "url": "https://github.com/randell/upgoose",
39
+ "directory": "packages/sdk-browser"
40
+ },
41
+ "homepage": "https://upgoose.app/docs#error-tracking",
42
+ "bugs": "https://github.com/randell/upgoose/issues",
43
+ "sideEffects": false
44
+ }