@cherrypeak-org/cherryboard-web 1.1.0 → 1.2.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/CHANGELOG.md +37 -0
- package/dist/index.cjs +5 -5
- package/dist/index.d.cts +14 -1
- package/dist/index.d.ts +14 -1
- package/dist/index.mjs +5 -5
- package/dist/react/index.cjs +5 -5
- package/dist/react/index.d.cts +14 -1
- package/dist/react/index.d.ts +14 -1
- package/dist/react/index.mjs +5 -5
- package/package.json +5 -3
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,43 @@
|
|
|
3
3
|
All notable changes to `@cherrypeak-org/cherryboard-web` are documented here.
|
|
4
4
|
This project adheres to [Semantic Versioning](https://semver.org/).
|
|
5
5
|
|
|
6
|
+
## [1.2.0]
|
|
7
|
+
|
|
8
|
+
### Added
|
|
9
|
+
- **Performance and visit tracking**, opt-in via `trackPerformance: true`.
|
|
10
|
+
Records how long API calls take and how many page views and visits the app
|
|
11
|
+
gets, and reports them on the environment page in the dashboard.
|
|
12
|
+
|
|
13
|
+
Timings come from `PerformanceObserver` rather than another `fetch` patch:
|
|
14
|
+
the browser already records them, so nothing is added to the request path and
|
|
15
|
+
nothing collides with other libraries that wrap `fetch` themselves.
|
|
16
|
+
|
|
17
|
+
Rollups are aggregated in the browser and posted once a minute, so a page
|
|
18
|
+
making 200 API calls sends one small summary rather than 200 events. Payload
|
|
19
|
+
size tracks the number of distinct routes, not traffic. The metrics endpoint
|
|
20
|
+
also has its own rate-limit budget, so performance data can never crowd out
|
|
21
|
+
error reporting.
|
|
22
|
+
|
|
23
|
+
Off by default — upgrading should not silently start sending new data about
|
|
24
|
+
someone's users.
|
|
25
|
+
|
|
26
|
+
- `metricsFlushIntervalMs` to control how often rollups are posted (default
|
|
27
|
+
60000).
|
|
28
|
+
|
|
29
|
+
### Notes on what is collected
|
|
30
|
+
- Paths are normalized before anything is recorded (`/api/orders/12345` becomes
|
|
31
|
+
`/api/orders/:id`), so no identifier reaches the server and the data stays
|
|
32
|
+
aggregatable.
|
|
33
|
+
- Query strings are dropped entirely rather than normalized — they routinely
|
|
34
|
+
carry tokens and email addresses, and say nothing about how long a request
|
|
35
|
+
took.
|
|
36
|
+
- Nothing is written to the visitor's device: no cookie, no storage, no
|
|
37
|
+
identifier. "Visits" counts page loads, not people. Recognising a returning
|
|
38
|
+
visitor would require storing something, and that trade was deliberately not
|
|
39
|
+
made.
|
|
40
|
+
|
|
41
|
+
---
|
|
42
|
+
|
|
6
43
|
## [1.1.0]
|
|
7
44
|
|
|
8
45
|
### Changed
|
package/dist/index.cjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
'use strict';var
|
|
2
|
-
`).slice(0,4).map(n=>n.replace(/:\d+:\d+/g,"").replace(/\?[^\s)]*/g,"").replace(/https?:\/\/[^/]+/g,"").trim()).join("|");return `${t}::${r}`.slice(0,500)}function
|
|
1
|
+
'use strict';var k="1.2.0";function u(){return typeof window<"u"&&typeof document<"u"}function h(){return new Date().toISOString()}function a(t,e){return e<=0?"":t.length<=e?t:`${t.slice(0,e-1)}\u2026`}function R(t,e=24e3){let r=new WeakSet;try{let n=JSON.stringify(t,(s,i)=>{if(i instanceof Error)return {name:i.name,message:i.message,stack:i.stack};if(typeof i=="object"&&i!==null){if(r.has(i))return "[Circular]";r.add(i);}if(typeof i=="bigint")return i.toString();if(typeof i!="function")return i});return n===void 0||n.length>e?void 0:n}catch{return}}function C(t,e){return !t||e.length===0?false:e.some(r=>typeof r=="string"?t.includes(r):r.test(t))}function _(t){return `${L(t)}/errors/batch`}function N(t){return `${L(t)}/metrics`}function L(t){let e=t.replace(/\/+$/,"");return /\/api\/v\d+$/.test(e)?e:`${e}/api/v1`}var m=class{constructor(e){this.max=e;this.items=[];this.teardown=[];}add(e){this.items.push({timestamp:e.timestamp??h(),category:e.category,message:a(e.message,500),level:e.level,data:e.data}),this.items.length>this.max&&this.items.shift();}snapshot(){return this.items.slice()}install(){u()&&(this.installHistory(),this.installClicks(),this.installFetch());}installHistory(){try{let e=window.history,r=s=>{let i=e[s];if(typeof i!="function")return ()=>{};let o=(...d)=>{try{let c=d[2];typeof c=="string"&&this.add({category:"navigation",message:`${s} \u2192 ${c}`});}catch{}return i.apply(window.history,d)};return e[s]=o,()=>{e[s]===o&&(e[s]=i);}};this.teardown.push(r("pushState"),r("replaceState"));let n=()=>this.add({category:"navigation",message:`popstate \u2192 ${location.pathname}`});window.addEventListener("popstate",n),this.teardown.push(()=>window.removeEventListener("popstate",n));}catch{}}installClicks(){try{let e=r=>{let n=r.target;if(!n||typeof n.tagName!="string")return;let s=n.id?`#${n.id}`:"",i=typeof n.className=="string"?n.className:"",o=i?`.${i.split(/\s+/).filter(Boolean).slice(0,2).join(".")}`:"";this.add({category:"click",message:`${n.tagName.toLowerCase()}${s}${o}`});};window.addEventListener("click",e,{capture:!0,passive:!0}),this.teardown.push(()=>window.removeEventListener("click",e,{capture:!0}));}catch{}}installFetch(){try{let e=window.fetch;if(typeof e!="function")return;let r=(...n)=>{let[s,i]=n,o=i?.method??(typeof s=="object"&&s&&"method"in s?s.method:"GET"),d=typeof s=="string"?s:s instanceof URL?s.href:s.url;return e.apply(window,n).then(c=>(this.add({category:"fetch",message:`${o} ${d} \u2192 ${c.status}`,level:c.ok?void 0:"Warning"}),c),c=>{throw this.add({category:"fetch",message:`${o} ${d} \u2192 failed`,level:"Warning"}),c})};window.fetch=r,this.teardown.push(()=>{window.fetch===r&&(window.fetch=e);});}catch{}}close(){for(let e of this.teardown.splice(0))try{e();}catch{}}};var g=class{constructor(e=4e3,r=100){this.windowMs=e;this.max=r;this.seen=new Map;}shouldSend(e){let r=Date.now(),n=this.seen.get(e);if(n!==void 0&&r-n<this.windowMs)return false;if(this.seen.set(e,r),this.seen.size>this.max){let s=this.seen.keys().next().value;s!==void 0&&this.seen.delete(s);}return true}};function $(t,e){let r=(e??"").split(`
|
|
2
|
+
`).slice(0,4).map(n=>n.replace(/:\d+:\d+/g,"").replace(/\?[^\s)]*/g,"").replace(/https?:\/\/[^/]+/g,"").trim()).join("|");return `${t}::${r}`.slice(0,500)}function O(t){if(!u())return ()=>{};let e=t.config,r=[];if(e.captureUnhandledErrors||e.captureResourceErrors){let n=s=>{try{let i=s.target;if(i instanceof HTMLElement){if(!e.captureResourceErrors)return;let d=i,c=d.src||d.href;if(!c)return;t.captureMessage(`Resource failed to load: ${c}`,"Warning",{context:{resource:i.tagName.toLowerCase(),url:c}});return}if(!e.captureUnhandledErrors)return;let o=s;if(!o.error&&(!o.message||o.message==="Script error."))return;t.captureException(o.error??o.message);}catch{}};window.addEventListener("error",n,true),r.push(()=>window.removeEventListener("error",n,true));}if(e.captureUnhandledRejections){let n=s=>{try{t.captureException(s.reason??"Unhandled promise rejection",{context:{unhandledRejection:!0}});}catch{}};window.addEventListener("unhandledrejection",n),r.push(()=>window.removeEventListener("unhandledrejection",n));}if(e.captureConsole){let n=console;for(let s of ["error","warn"]){let i=n[s];if(typeof i!="function")continue;let o=(...d)=>{try{t.addBreadcrumb({category:"console",level:s==="error"?"Error":"Warning",message:d.map(V).join(" ").slice(0,500)});}catch{}return i.apply(console,d)};n[s]=o,r.push(()=>{n[s]===o&&(n[s]=i);});}}return ()=>{for(let n of r.splice(0))try{n();}catch{}}}function V(t){if(typeof t=="string")return t;if(t instanceof Error)return `${t.name}: ${t.message}`;try{return JSON.stringify(t)??String(t)}catch{return String(t)}}var y=2e3,T=8e3,S=4e3;function j(t){if(t instanceof Error)return {message:a(t.message||t.name||"Error",y),exceptionType:t.name||"Error",stackTrace:t.stack?a(t.stack,T):void 0,innerException:M(t.cause)};if(typeof t=="string")return {message:a(t,y),exceptionType:"Error"};if(t&&typeof t=="object"){let e=t,r=typeof e.message=="string"?e.message:q(e)??Object.prototype.toString.call(t),n=typeof e.name=="string"?e.name:"Error",s=typeof e.stack=="string"?e.stack:void 0;return {message:a(r,y),exceptionType:n,stackTrace:s?a(s,T):void 0,innerException:M(e.cause)}}return {message:a(String(t),y),exceptionType:"Error"}}function D(t){if(t.stackTrace)return t;try{let e=new Error(t.message).stack;if(e){let r=e.split(`
|
|
3
3
|
`).slice(2).join(`
|
|
4
|
-
`);return {...t,stackTrace:a(r||e,
|
|
5
|
-
`),
|
|
6
|
-
${
|
|
4
|
+
`);return {...t,stackTrace:a(r||e,T)}}}catch{}return t}function M(t){if(t==null)return;if(t instanceof Error){let r=[`${t.name}: ${t.message}`];t.stack&&r.push(t.stack);let n=M(t.cause);return n&&r.push(`Caused by: ${n}`),a(r.join(`
|
|
5
|
+
`),S)}let e=q(t);return e?a(e,S):a(String(t),S)}function q(t){try{let e=JSON.stringify(t);return e==="{}"?void 0:e}catch{return}}var z=/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i,G=/^[0-9a-f]{16,}$/i,X=/^\d+$/,W=/^(?=.*\d)[A-Za-z0-9_-]{12,}$/;function J(t){return z.test(t)||X.test(t)||G.test(t)||W.test(t)}function B(t,e){let r;try{r=new URL(t,e??(typeof location<"u"?location.href:void 0)).pathname;}catch{return null}if(!r||r==="/")return "/";let n=r.split("/").filter(Boolean);if(n.length===0)return "/";let s=n.slice(0,12).map(o=>J(o)?":id":o);n.length>12&&s.push("\u2026");let i="/"+s.join("/");return i.length>300?i.slice(0,300):i}var f=[50,100,250,500,1e3,2500,5e3];function H(t){for(let e=0;e<f.length;e++)if(t<=f[e])return e;return f.length}var b=class{constructor(e){this.options=e;this.requests=new Map;this.pageViews=new Map;this.teardown=[];this.closed=false;this.endpoint=N(e.apiUrl);let r=typeof fetch=="function"?fetch.bind(globalThis):void 0;this.fetchImpl=r??(()=>Promise.reject(new Error("fetch unavailable")));}start(){!u()||this.closed||(this.recordPageView(location.pathname,true),this.observeResources(),this.observeNavigation(),this.observeVisibility(),this.timer=setInterval(()=>{this.flush();},this.options.flushIntervalMs));}observeResources(){if(!(typeof PerformanceObserver>"u"))try{this.observer=new PerformanceObserver(e=>{for(let r of e.getEntries())this.recordResource(r);}),this.observer.observe({type:"resource",buffered:!0});}catch{}}recordResource(e){if(e.initiatorType!=="fetch"&&e.initiatorType!=="xmlhttprequest"||e.name.startsWith(this.endpoint)||e.name.includes("/api/v1/errors/batch"))return;let r=B(e.name);if(!r)return;let n=Math.round(e.duration);n<=0||this.addRequest(r,"ALL",n,false);}addRequest(e,r,n,s){let i=`${r} ${e}`,o=this.requests.get(i);if(!o){if(this.requests.size>=this.options.maxRoutes)return;o={route:e,method:r,count:0,errorCount:0,sumMs:0,maxMs:0,buckets:new Array(f.length+1).fill(0)},this.requests.set(i,o);}o.count++,s&&o.errorCount++,o.sumMs+=n,n>o.maxMs&&(o.maxMs=n),o.buckets[H(n)]++;}observeNavigation(){let e=()=>this.recordPageView(location.pathname,false);try{let r=window.history,n=s=>{let i=r[s];if(typeof i!="function")return ()=>{};let o=(...d)=>{let c=i.apply(window.history,d);return e(),c};return r[s]=o,()=>{r[s]===o&&(r[s]=i);}};this.teardown.push(n("pushState"),n("replaceState")),window.addEventListener("popstate",e),this.teardown.push(()=>window.removeEventListener("popstate",e));}catch{}}recordPageView(e,r){let n=B(e);if(!n)return;let s=this.pageViews.get(n);if(!s){if(this.pageViews.size>=this.options.maxRoutes)return;s={route:n,views:0,visits:0},this.pageViews.set(n,s);}s.views++,r&&s.visits++;}observeVisibility(){let e=()=>{document.visibilityState==="hidden"&&this.flush(true);};document.addEventListener("visibilitychange",e),this.teardown.push(()=>document.removeEventListener("visibilitychange",e));}async flush(e=false){if(this.requests.size===0&&this.pageViews.size===0)return;let r={requests:Array.from(this.requests.values()).map(n=>({route:n.route,method:n.method,count:n.count,errorCount:n.errorCount,sumMs:n.sumMs,maxMs:n.maxMs,buckets:n.buckets})),pageViews:Array.from(this.pageViews.values()).map(n=>({route:n.route,views:n.views,visits:n.visits}))};this.requests.clear(),this.pageViews.clear();try{await this.fetchImpl(this.endpoint,{method:"POST",headers:{"Content-Type":"application/json","X-API-Key":this.options.apiKey},body:JSON.stringify(r),keepalive:e,mode:"cors",credentials:"omit"});}catch{this.options.debug&&console.warn("[cherryboard] metrics flush failed");}}close(){this.closed=true,this.timer&&clearInterval(this.timer),this.observer?.disconnect();for(let e of this.teardown)e();this.teardown=[];}};var P="cherryboard:queue:v1",v=class{constructor(e,r){this.enabled=e;this.maxItems=r;}get store(){if(!this.enabled||!u())return null;try{return window.localStorage}catch{return null}}push(e){let r=this.store;if(!(!r||e.length===0))try{let n=this.read().concat(e).slice(-this.maxItems);r.setItem(P,JSON.stringify(n));}catch{}}read(){let e=this.store;if(!e)return [];try{let r=e.getItem(P);if(!r)return [];let n=JSON.parse(r);return Array.isArray(n)?n:[]}catch{return []}}drain(){let e=this.read();return this.clear(),e}clear(){let e=this.store;if(e)try{e.removeItem(P);}catch{}}};var w=class{constructor(e=30,r=5){this.capacity=e;this.refillPerSec=r;this.tokens=e,this.last=Date.now();}allow(){let e=Date.now(),r=(e-this.last)/1e3;return this.tokens=Math.min(this.capacity,this.tokens+r*this.refillPerSec),this.last=e,this.tokens>=1?(this.tokens-=1,true):false}};var Q=["password","passwd","secret","token","apikey","api_key","authorization","auth","cookie","session","credit","card","cvv","ssn"],Y=["token","access_token","apikey","api_key","email","password","code","secret"],U="[Filtered]",Z=/[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/g;function A(t){let e=t;for(let r of Y)e=e.replace(new RegExp(`([?&]${r}=)[^&#\\s]*`,"gi"),`$1${U}`);return e}function x(t,e=0){if(e>6)return t;if(typeof t=="string")return A(t).replace(Z,U);if(Array.isArray(t))return t.map(r=>x(r,e+1));if(t&&typeof t=="object"){let r={};for(let[n,s]of Object.entries(t)){let i=n.toLowerCase();r[n]=Q.some(o=>i.includes(o))?U:x(s,e+1);}return r}return t}function K(t){return {...t,message:A(t.message),context:x(t.context),breadcrumbs:t.breadcrumbs.map(e=>({...e,message:A(e.message),data:e.data?x(e.data):void 0}))}}var E=class{constructor(e,r){this.apiKey=r;this.endpoint=_(e);let n=typeof fetch=="function"?fetch.bind(globalThis):void 0;this.fetchImpl=n??(()=>Promise.reject(new Error("fetch unavailable")));}async send(e,r=false){if(e.length===0)return {ok:true,retryable:false,status:204};try{let n=await this.fetchImpl(this.endpoint,{method:"POST",headers:{"Content-Type":"application/json",Accept:"application/json","X-API-Key":this.apiKey},body:JSON.stringify({errors:e}),keepalive:r,mode:"cors",credentials:"omit"});if(n.status===429)return {ok:!1,retryable:!1,status:429,retryAfterMs:ee(n.headers?.get?.("Retry-After"))};let s=n.status>=500;return {ok:n.ok,retryable:s,status:n.status}}catch{return {ok:false,retryable:true,status:0}}}};function ee(t){if(!t)return 6e4;let r=Number(t);if(Number.isFinite(r)&&r>=0)return Math.min(r*1e3,36e5);let n=Date.parse(t);return Number.isNaN(n)?6e4:Math.min(Math.max(n-Date.now(),0),36e5)}var te=200;function I(t,e,r){return Math.max(e,Math.min(r,t))}function re(t){return new Promise(e=>setTimeout(e,t))}var ne=3e3;function se(t){return {apiKey:t.apiKey,apiUrl:t.apiUrl,environment:t.environment??"production",release:t.release,enabled:t.enabled??true,sampleRate:I(t.sampleRate??1,0,1),maxBatchSize:I(t.maxBatchSize??20,1,100),flushIntervalMs:t.flushIntervalMs??4e3,maxQueueItems:t.maxQueueItems??100,maxRetries:t.maxRetries??3,maxBreadcrumbs:t.maxBreadcrumbs??30,offlineStorage:t.offlineStorage??true,captureUnhandledErrors:t.captureUnhandledErrors??true,captureUnhandledRejections:t.captureUnhandledRejections??true,captureResourceErrors:t.captureResourceErrors??true,captureConsole:t.captureConsole??true,autoBreadcrumbs:t.autoBreadcrumbs??true,denyUrls:t.denyUrls??[],allowUrls:t.allowUrls??[],beforeSend:t.beforeSend,trackPerformance:t.trackPerformance??false,metricsFlushIntervalMs:t.metricsFlushIntervalMs??6e4,debug:t.debug??false}}var p=class{constructor(e){this.buffer=[];this.flushTimer=null;this.processing=false;this.rateLimitedUntil=0;this.discarded={};this.closed=false;this.scope={tags:{}};this.teardown=[];this.config=se(e),this.transport=new E(this.config.apiUrl,this.config.apiKey),this.deduper=new g(ne),this.limiter=new w,this.queue=new v(this.config.offlineStorage,this.config.maxQueueItems),this.config.trackPerformance&&this.config.enabled&&(this.metrics=new b({apiKey:this.config.apiKey,apiUrl:this.config.apiUrl,flushIntervalMs:this.config.metricsFlushIntervalMs,maxRoutes:te,debug:this.config.debug}),this.metrics.start()),this.breadcrumbs=new m(this.config.maxBreadcrumbs),this.config.enabled&&(this.config.autoBreadcrumbs&&this.breadcrumbs.install(),this.teardown.push(O(this)),this.installLifecycle(),this.drainOffline());}captureException(e,r){try{this.process(D(j(e)),r?.severity??"Error",r);}catch(n){this.debug("captureException failed",n);}}captureMessage(e,r="Info",n){try{this.process({message:a(e,2e3),exceptionType:"Message"},n?.severity??r,n);}catch(s){this.debug("captureMessage failed",s);}}getDiscardedEvents(){return {...this.discarded}}discard(e,r=1){this.discarded[e]=(this.discarded[e]??0)+r;}addBreadcrumb(e){this.breadcrumbs.add(e);}setUser(e){this.scope.user=e??void 0;}setTag(e,r){this.scope.tags[e]=r;}setContext(e,r){this.scope.tags[e]=r;}close(){this.closed=true,this.flushTimer!==null&&(clearTimeout(this.flushTimer),this.flushTimer=null),this.flush(true),this.metrics?.close(),this.breadcrumbs.close();for(let e of this.teardown.splice(0))try{e();}catch{}}process(e,r,n){if(!this.config.enabled||this.closed)return;if(this.config.sampleRate<1&&Math.random()>this.config.sampleRate){this.discard("sampled");return}let s=`${e.stackTrace??""}
|
|
6
|
+
${u()?location.href:""}`;if(C(s,this.config.denyUrls)){this.discard("filtered");return}if(this.config.allowUrls.length>0&&!C(s,this.config.allowUrls)){this.discard("filtered");return}if(!this.deduper.shouldSend($(e.message,e.stackTrace))){this.discard("deduped");return}let i={message:e.message,severity:r,timestamp:h(),exceptionType:e.exceptionType,stackTrace:e.stackTrace,innerException:e.innerException,userId:this.scope.user?.id,requestPath:u()?location.pathname:void 0,userAgent:u()?navigator.userAgent:void 0,context:this.buildContext(n),breadcrumbs:this.breadcrumbs.snapshot()};if(i=K(i),this.config.beforeSend){let o=this.config.beforeSend(i);if(!o){this.discard("filtered");return}i=o;}this.enqueue(this.toPayload(i));}buildContext(e){let r={sdk:{name:"cherryboard-web",version:k},environment:this.config.environment};return this.config.release&&(r.release=this.config.release),u()&&(r.url=location.href,document.referrer&&(r.referrer=document.referrer),r.language=navigator.language,r.viewport={width:window.innerWidth,height:window.innerHeight}),this.scope.user&&(r.user=this.scope.user),Object.keys(this.scope.tags).length>0&&(r.tags={...this.scope.tags}),e?.componentStack&&(r.componentStack=e.componentStack),e?.digest&&(r.digest=e.digest),e?.context&&Object.assign(r,e.context),r}toPayload(e){let r=R({...e.context,breadcrumbs:e.breadcrumbs});return r===void 0&&(r=R(e.context)),{message:e.message,stackTrace:e.stackTrace,severity:e.severity,timestamp:e.timestamp,userId:e.userId,requestPath:e.requestPath,userAgent:e.userAgent,exceptionType:e.exceptionType,innerException:e.innerException,metadata:r}}enqueue(e){this.buffer.push(e),this.buffer.length>=this.config.maxBatchSize?this.flush():this.scheduleFlush();}scheduleFlush(){this.closed||this.flushTimer===null&&(this.flushTimer=setTimeout(()=>{this.flushTimer=null,this.flush();},this.config.flushIntervalMs));}async flush(e=false){if(this.flushTimer!==null&&(clearTimeout(this.flushTimer),this.flushTimer=null),this.buffer.length!==0){if(e){if(Date.now()<this.rateLimitedUntil)return;for(;this.buffer.length>0;){let r=this.buffer.splice(0,this.config.maxBatchSize);if(!this.limiter.allow())break;this.transport.send(r,true);}return}if(Date.now()<this.rateLimitedUntil){this.scheduleFlush();return}if(!this.processing){this.processing=true;try{for(;this.buffer.length>0;){let r=this.buffer.splice(0,this.config.maxBatchSize);if(!this.limiter.allow()){this.debug("rate limited; dropping",r.length,"events"),this.discard("rate_limited",r.length);break}if(!await this.deliver(r)){this.queue.push(r);break}}}finally{this.processing=false;}}}}async deliver(e){for(let r=0;r<=this.config.maxRetries;r++){let n=await this.transport.send(e,false);if(n.ok)return true;if(n.status===429)return this.rateLimitedUntil=Date.now()+(n.retryAfterMs??6e4),this.debug("rate limited by server; pausing for",n.retryAfterMs,"ms"),false;if(!n.retryable)return this.debug("non-retryable response",n.status,"\u2014 dropping batch"),this.discard("send_failed",e.length),true;if(r===this.config.maxRetries)return false;await re(I(2**r*1e3+Math.random()*250,0,15e3));}return false}drainOffline(){let e=this.queue.drain();e.length>0&&(this.buffer.push(...e),this.scheduleFlush());}installLifecycle(){if(!u())return;let e=()=>{document.visibilityState==="hidden"&&this.flush(true);},r=()=>{this.flush(true);},n=()=>this.drainOffline();document.addEventListener("visibilitychange",e),window.addEventListener("pagehide",r),window.addEventListener("online",n),this.teardown.push(()=>{document.removeEventListener("visibilitychange",e),window.removeEventListener("pagehide",r),window.removeEventListener("online",n);});}debug(...e){this.config.debug&&console.warn("[cherryboard]",...e);}};var F="__CHERRYBOARD__";function l(){let t=globalThis,e=t[F];return e||(e={},t[F]=e),e}function $e(t){let e=l();return e.client||(e.client=new p(t)),e.client}function Oe(){return l().client??null}function je(){return l().client!=null}function De(t,e){l().client?.captureException(t,e);}function qe(t,e,r){l().client?.captureMessage(t,e,r);}function He(t){l().client?.addBreadcrumb(t);}function Ke(t){l().client?.setUser(t);}function Fe(t,e){l().client?.setTag(t,e);}function Ve(t,e){l().client?.setContext(t,e);}function ze(){let t=l().client;return t?t.flush():Promise.resolve()}function Ge(t,e,r){l().client?.captureException(t,{severity:"Error",context:{source:"nextjs-server",...e?.path?{requestPath:e.path}:{},...e?.method?{requestMethod:e.method}:{},...r??{}}});}function Xe(){return l().client?.getDiscardedEvents()??{}}function We(){let t=l();t.client?.close(),t.client=void 0;}exports.CherryBoardClient=p;exports.SDK_VERSION=k;exports.addBreadcrumb=He;exports.captureException=De;exports.captureMessage=qe;exports.captureRequestError=Ge;exports.close=We;exports.flush=ze;exports.getClient=Oe;exports.getDiscardedEvents=Xe;exports.init=$e;exports.isInitialized=je;exports.setContext=Ve;exports.setTag=Fe;exports.setUser=Ke;
|
package/dist/index.d.cts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/** SDK version — kept in sync with package.json. */
|
|
2
|
-
declare const SDK_VERSION = "1.
|
|
2
|
+
declare const SDK_VERSION = "1.2.0";
|
|
3
3
|
/** Severity levels — these strings match the backend `ErrorSeverity` enum exactly. */
|
|
4
4
|
type Severity = 'Debug' | 'Info' | 'Warning' | 'Error' | 'Critical';
|
|
5
5
|
/** A trail entry giving context that led up to an error. */
|
|
@@ -88,6 +88,16 @@ interface CherryBoardConfig {
|
|
|
88
88
|
captureResourceErrors?: boolean;
|
|
89
89
|
/** Turn console.error/warn into breadcrumbs (never into events). Default true. */
|
|
90
90
|
captureConsole?: boolean;
|
|
91
|
+
/**
|
|
92
|
+
* Collect API request timings and page views alongside errors.
|
|
93
|
+
*
|
|
94
|
+
* Off by default: upgrading the package must not silently start sending new
|
|
95
|
+
* data about someone's users. Aggregated in the browser and stripped of
|
|
96
|
+
* identifiers before it leaves — see the performance docs.
|
|
97
|
+
*/
|
|
98
|
+
trackPerformance?: boolean;
|
|
99
|
+
/** How often aggregated metrics are posted. Default 60000 (once a minute). */
|
|
100
|
+
metricsFlushIntervalMs?: number;
|
|
91
101
|
/** Auto-record navigation / click / fetch breadcrumbs. Default true. */
|
|
92
102
|
autoBreadcrumbs?: boolean;
|
|
93
103
|
/** Drop events whose stack/URL matches any of these. */
|
|
@@ -110,6 +120,8 @@ interface ResolvedConfig {
|
|
|
110
120
|
environment: string;
|
|
111
121
|
release?: string;
|
|
112
122
|
enabled: boolean;
|
|
123
|
+
trackPerformance: boolean;
|
|
124
|
+
metricsFlushIntervalMs: number;
|
|
113
125
|
sampleRate: number;
|
|
114
126
|
maxBatchSize: number;
|
|
115
127
|
flushIntervalMs: number;
|
|
@@ -163,6 +175,7 @@ declare class CherryBoardClient {
|
|
|
163
175
|
private readonly deduper;
|
|
164
176
|
private readonly limiter;
|
|
165
177
|
private readonly queue;
|
|
178
|
+
private readonly metrics?;
|
|
166
179
|
private buffer;
|
|
167
180
|
private flushTimer;
|
|
168
181
|
private processing;
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/** SDK version — kept in sync with package.json. */
|
|
2
|
-
declare const SDK_VERSION = "1.
|
|
2
|
+
declare const SDK_VERSION = "1.2.0";
|
|
3
3
|
/** Severity levels — these strings match the backend `ErrorSeverity` enum exactly. */
|
|
4
4
|
type Severity = 'Debug' | 'Info' | 'Warning' | 'Error' | 'Critical';
|
|
5
5
|
/** A trail entry giving context that led up to an error. */
|
|
@@ -88,6 +88,16 @@ interface CherryBoardConfig {
|
|
|
88
88
|
captureResourceErrors?: boolean;
|
|
89
89
|
/** Turn console.error/warn into breadcrumbs (never into events). Default true. */
|
|
90
90
|
captureConsole?: boolean;
|
|
91
|
+
/**
|
|
92
|
+
* Collect API request timings and page views alongside errors.
|
|
93
|
+
*
|
|
94
|
+
* Off by default: upgrading the package must not silently start sending new
|
|
95
|
+
* data about someone's users. Aggregated in the browser and stripped of
|
|
96
|
+
* identifiers before it leaves — see the performance docs.
|
|
97
|
+
*/
|
|
98
|
+
trackPerformance?: boolean;
|
|
99
|
+
/** How often aggregated metrics are posted. Default 60000 (once a minute). */
|
|
100
|
+
metricsFlushIntervalMs?: number;
|
|
91
101
|
/** Auto-record navigation / click / fetch breadcrumbs. Default true. */
|
|
92
102
|
autoBreadcrumbs?: boolean;
|
|
93
103
|
/** Drop events whose stack/URL matches any of these. */
|
|
@@ -110,6 +120,8 @@ interface ResolvedConfig {
|
|
|
110
120
|
environment: string;
|
|
111
121
|
release?: string;
|
|
112
122
|
enabled: boolean;
|
|
123
|
+
trackPerformance: boolean;
|
|
124
|
+
metricsFlushIntervalMs: number;
|
|
113
125
|
sampleRate: number;
|
|
114
126
|
maxBatchSize: number;
|
|
115
127
|
flushIntervalMs: number;
|
|
@@ -163,6 +175,7 @@ declare class CherryBoardClient {
|
|
|
163
175
|
private readonly deduper;
|
|
164
176
|
private readonly limiter;
|
|
165
177
|
private readonly queue;
|
|
178
|
+
private readonly metrics?;
|
|
166
179
|
private buffer;
|
|
167
180
|
private flushTimer;
|
|
168
181
|
private processing;
|
package/dist/index.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
var
|
|
2
|
-
`).slice(0,4).map(n=>n.replace(/:\d+:\d+/g,"").replace(/\?[^\s)]*/g,"").replace(/https?:\/\/[^/]+/g,"").trim()).join("|");return `${t}::${r}`.slice(0,500)}function
|
|
1
|
+
var k="1.2.0";function u(){return typeof window<"u"&&typeof document<"u"}function h(){return new Date().toISOString()}function a(t,e){return e<=0?"":t.length<=e?t:`${t.slice(0,e-1)}\u2026`}function R(t,e=24e3){let r=new WeakSet;try{let n=JSON.stringify(t,(s,i)=>{if(i instanceof Error)return {name:i.name,message:i.message,stack:i.stack};if(typeof i=="object"&&i!==null){if(r.has(i))return "[Circular]";r.add(i);}if(typeof i=="bigint")return i.toString();if(typeof i!="function")return i});return n===void 0||n.length>e?void 0:n}catch{return}}function C(t,e){return !t||e.length===0?false:e.some(r=>typeof r=="string"?t.includes(r):r.test(t))}function _(t){return `${L(t)}/errors/batch`}function N(t){return `${L(t)}/metrics`}function L(t){let e=t.replace(/\/+$/,"");return /\/api\/v\d+$/.test(e)?e:`${e}/api/v1`}var m=class{constructor(e){this.max=e;this.items=[];this.teardown=[];}add(e){this.items.push({timestamp:e.timestamp??h(),category:e.category,message:a(e.message,500),level:e.level,data:e.data}),this.items.length>this.max&&this.items.shift();}snapshot(){return this.items.slice()}install(){u()&&(this.installHistory(),this.installClicks(),this.installFetch());}installHistory(){try{let e=window.history,r=s=>{let i=e[s];if(typeof i!="function")return ()=>{};let o=(...d)=>{try{let c=d[2];typeof c=="string"&&this.add({category:"navigation",message:`${s} \u2192 ${c}`});}catch{}return i.apply(window.history,d)};return e[s]=o,()=>{e[s]===o&&(e[s]=i);}};this.teardown.push(r("pushState"),r("replaceState"));let n=()=>this.add({category:"navigation",message:`popstate \u2192 ${location.pathname}`});window.addEventListener("popstate",n),this.teardown.push(()=>window.removeEventListener("popstate",n));}catch{}}installClicks(){try{let e=r=>{let n=r.target;if(!n||typeof n.tagName!="string")return;let s=n.id?`#${n.id}`:"",i=typeof n.className=="string"?n.className:"",o=i?`.${i.split(/\s+/).filter(Boolean).slice(0,2).join(".")}`:"";this.add({category:"click",message:`${n.tagName.toLowerCase()}${s}${o}`});};window.addEventListener("click",e,{capture:!0,passive:!0}),this.teardown.push(()=>window.removeEventListener("click",e,{capture:!0}));}catch{}}installFetch(){try{let e=window.fetch;if(typeof e!="function")return;let r=(...n)=>{let[s,i]=n,o=i?.method??(typeof s=="object"&&s&&"method"in s?s.method:"GET"),d=typeof s=="string"?s:s instanceof URL?s.href:s.url;return e.apply(window,n).then(c=>(this.add({category:"fetch",message:`${o} ${d} \u2192 ${c.status}`,level:c.ok?void 0:"Warning"}),c),c=>{throw this.add({category:"fetch",message:`${o} ${d} \u2192 failed`,level:"Warning"}),c})};window.fetch=r,this.teardown.push(()=>{window.fetch===r&&(window.fetch=e);});}catch{}}close(){for(let e of this.teardown.splice(0))try{e();}catch{}}};var g=class{constructor(e=4e3,r=100){this.windowMs=e;this.max=r;this.seen=new Map;}shouldSend(e){let r=Date.now(),n=this.seen.get(e);if(n!==void 0&&r-n<this.windowMs)return false;if(this.seen.set(e,r),this.seen.size>this.max){let s=this.seen.keys().next().value;s!==void 0&&this.seen.delete(s);}return true}};function $(t,e){let r=(e??"").split(`
|
|
2
|
+
`).slice(0,4).map(n=>n.replace(/:\d+:\d+/g,"").replace(/\?[^\s)]*/g,"").replace(/https?:\/\/[^/]+/g,"").trim()).join("|");return `${t}::${r}`.slice(0,500)}function O(t){if(!u())return ()=>{};let e=t.config,r=[];if(e.captureUnhandledErrors||e.captureResourceErrors){let n=s=>{try{let i=s.target;if(i instanceof HTMLElement){if(!e.captureResourceErrors)return;let d=i,c=d.src||d.href;if(!c)return;t.captureMessage(`Resource failed to load: ${c}`,"Warning",{context:{resource:i.tagName.toLowerCase(),url:c}});return}if(!e.captureUnhandledErrors)return;let o=s;if(!o.error&&(!o.message||o.message==="Script error."))return;t.captureException(o.error??o.message);}catch{}};window.addEventListener("error",n,true),r.push(()=>window.removeEventListener("error",n,true));}if(e.captureUnhandledRejections){let n=s=>{try{t.captureException(s.reason??"Unhandled promise rejection",{context:{unhandledRejection:!0}});}catch{}};window.addEventListener("unhandledrejection",n),r.push(()=>window.removeEventListener("unhandledrejection",n));}if(e.captureConsole){let n=console;for(let s of ["error","warn"]){let i=n[s];if(typeof i!="function")continue;let o=(...d)=>{try{t.addBreadcrumb({category:"console",level:s==="error"?"Error":"Warning",message:d.map(V).join(" ").slice(0,500)});}catch{}return i.apply(console,d)};n[s]=o,r.push(()=>{n[s]===o&&(n[s]=i);});}}return ()=>{for(let n of r.splice(0))try{n();}catch{}}}function V(t){if(typeof t=="string")return t;if(t instanceof Error)return `${t.name}: ${t.message}`;try{return JSON.stringify(t)??String(t)}catch{return String(t)}}var y=2e3,T=8e3,S=4e3;function j(t){if(t instanceof Error)return {message:a(t.message||t.name||"Error",y),exceptionType:t.name||"Error",stackTrace:t.stack?a(t.stack,T):void 0,innerException:M(t.cause)};if(typeof t=="string")return {message:a(t,y),exceptionType:"Error"};if(t&&typeof t=="object"){let e=t,r=typeof e.message=="string"?e.message:q(e)??Object.prototype.toString.call(t),n=typeof e.name=="string"?e.name:"Error",s=typeof e.stack=="string"?e.stack:void 0;return {message:a(r,y),exceptionType:n,stackTrace:s?a(s,T):void 0,innerException:M(e.cause)}}return {message:a(String(t),y),exceptionType:"Error"}}function D(t){if(t.stackTrace)return t;try{let e=new Error(t.message).stack;if(e){let r=e.split(`
|
|
3
3
|
`).slice(2).join(`
|
|
4
|
-
`);return {...t,stackTrace:a(r||e,
|
|
5
|
-
`),
|
|
6
|
-
${
|
|
4
|
+
`);return {...t,stackTrace:a(r||e,T)}}}catch{}return t}function M(t){if(t==null)return;if(t instanceof Error){let r=[`${t.name}: ${t.message}`];t.stack&&r.push(t.stack);let n=M(t.cause);return n&&r.push(`Caused by: ${n}`),a(r.join(`
|
|
5
|
+
`),S)}let e=q(t);return e?a(e,S):a(String(t),S)}function q(t){try{let e=JSON.stringify(t);return e==="{}"?void 0:e}catch{return}}var z=/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i,G=/^[0-9a-f]{16,}$/i,X=/^\d+$/,W=/^(?=.*\d)[A-Za-z0-9_-]{12,}$/;function J(t){return z.test(t)||X.test(t)||G.test(t)||W.test(t)}function B(t,e){let r;try{r=new URL(t,e??(typeof location<"u"?location.href:void 0)).pathname;}catch{return null}if(!r||r==="/")return "/";let n=r.split("/").filter(Boolean);if(n.length===0)return "/";let s=n.slice(0,12).map(o=>J(o)?":id":o);n.length>12&&s.push("\u2026");let i="/"+s.join("/");return i.length>300?i.slice(0,300):i}var f=[50,100,250,500,1e3,2500,5e3];function H(t){for(let e=0;e<f.length;e++)if(t<=f[e])return e;return f.length}var b=class{constructor(e){this.options=e;this.requests=new Map;this.pageViews=new Map;this.teardown=[];this.closed=false;this.endpoint=N(e.apiUrl);let r=typeof fetch=="function"?fetch.bind(globalThis):void 0;this.fetchImpl=r??(()=>Promise.reject(new Error("fetch unavailable")));}start(){!u()||this.closed||(this.recordPageView(location.pathname,true),this.observeResources(),this.observeNavigation(),this.observeVisibility(),this.timer=setInterval(()=>{this.flush();},this.options.flushIntervalMs));}observeResources(){if(!(typeof PerformanceObserver>"u"))try{this.observer=new PerformanceObserver(e=>{for(let r of e.getEntries())this.recordResource(r);}),this.observer.observe({type:"resource",buffered:!0});}catch{}}recordResource(e){if(e.initiatorType!=="fetch"&&e.initiatorType!=="xmlhttprequest"||e.name.startsWith(this.endpoint)||e.name.includes("/api/v1/errors/batch"))return;let r=B(e.name);if(!r)return;let n=Math.round(e.duration);n<=0||this.addRequest(r,"ALL",n,false);}addRequest(e,r,n,s){let i=`${r} ${e}`,o=this.requests.get(i);if(!o){if(this.requests.size>=this.options.maxRoutes)return;o={route:e,method:r,count:0,errorCount:0,sumMs:0,maxMs:0,buckets:new Array(f.length+1).fill(0)},this.requests.set(i,o);}o.count++,s&&o.errorCount++,o.sumMs+=n,n>o.maxMs&&(o.maxMs=n),o.buckets[H(n)]++;}observeNavigation(){let e=()=>this.recordPageView(location.pathname,false);try{let r=window.history,n=s=>{let i=r[s];if(typeof i!="function")return ()=>{};let o=(...d)=>{let c=i.apply(window.history,d);return e(),c};return r[s]=o,()=>{r[s]===o&&(r[s]=i);}};this.teardown.push(n("pushState"),n("replaceState")),window.addEventListener("popstate",e),this.teardown.push(()=>window.removeEventListener("popstate",e));}catch{}}recordPageView(e,r){let n=B(e);if(!n)return;let s=this.pageViews.get(n);if(!s){if(this.pageViews.size>=this.options.maxRoutes)return;s={route:n,views:0,visits:0},this.pageViews.set(n,s);}s.views++,r&&s.visits++;}observeVisibility(){let e=()=>{document.visibilityState==="hidden"&&this.flush(true);};document.addEventListener("visibilitychange",e),this.teardown.push(()=>document.removeEventListener("visibilitychange",e));}async flush(e=false){if(this.requests.size===0&&this.pageViews.size===0)return;let r={requests:Array.from(this.requests.values()).map(n=>({route:n.route,method:n.method,count:n.count,errorCount:n.errorCount,sumMs:n.sumMs,maxMs:n.maxMs,buckets:n.buckets})),pageViews:Array.from(this.pageViews.values()).map(n=>({route:n.route,views:n.views,visits:n.visits}))};this.requests.clear(),this.pageViews.clear();try{await this.fetchImpl(this.endpoint,{method:"POST",headers:{"Content-Type":"application/json","X-API-Key":this.options.apiKey},body:JSON.stringify(r),keepalive:e,mode:"cors",credentials:"omit"});}catch{this.options.debug&&console.warn("[cherryboard] metrics flush failed");}}close(){this.closed=true,this.timer&&clearInterval(this.timer),this.observer?.disconnect();for(let e of this.teardown)e();this.teardown=[];}};var P="cherryboard:queue:v1",v=class{constructor(e,r){this.enabled=e;this.maxItems=r;}get store(){if(!this.enabled||!u())return null;try{return window.localStorage}catch{return null}}push(e){let r=this.store;if(!(!r||e.length===0))try{let n=this.read().concat(e).slice(-this.maxItems);r.setItem(P,JSON.stringify(n));}catch{}}read(){let e=this.store;if(!e)return [];try{let r=e.getItem(P);if(!r)return [];let n=JSON.parse(r);return Array.isArray(n)?n:[]}catch{return []}}drain(){let e=this.read();return this.clear(),e}clear(){let e=this.store;if(e)try{e.removeItem(P);}catch{}}};var w=class{constructor(e=30,r=5){this.capacity=e;this.refillPerSec=r;this.tokens=e,this.last=Date.now();}allow(){let e=Date.now(),r=(e-this.last)/1e3;return this.tokens=Math.min(this.capacity,this.tokens+r*this.refillPerSec),this.last=e,this.tokens>=1?(this.tokens-=1,true):false}};var Q=["password","passwd","secret","token","apikey","api_key","authorization","auth","cookie","session","credit","card","cvv","ssn"],Y=["token","access_token","apikey","api_key","email","password","code","secret"],U="[Filtered]",Z=/[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/g;function A(t){let e=t;for(let r of Y)e=e.replace(new RegExp(`([?&]${r}=)[^&#\\s]*`,"gi"),`$1${U}`);return e}function x(t,e=0){if(e>6)return t;if(typeof t=="string")return A(t).replace(Z,U);if(Array.isArray(t))return t.map(r=>x(r,e+1));if(t&&typeof t=="object"){let r={};for(let[n,s]of Object.entries(t)){let i=n.toLowerCase();r[n]=Q.some(o=>i.includes(o))?U:x(s,e+1);}return r}return t}function K(t){return {...t,message:A(t.message),context:x(t.context),breadcrumbs:t.breadcrumbs.map(e=>({...e,message:A(e.message),data:e.data?x(e.data):void 0}))}}var E=class{constructor(e,r){this.apiKey=r;this.endpoint=_(e);let n=typeof fetch=="function"?fetch.bind(globalThis):void 0;this.fetchImpl=n??(()=>Promise.reject(new Error("fetch unavailable")));}async send(e,r=false){if(e.length===0)return {ok:true,retryable:false,status:204};try{let n=await this.fetchImpl(this.endpoint,{method:"POST",headers:{"Content-Type":"application/json",Accept:"application/json","X-API-Key":this.apiKey},body:JSON.stringify({errors:e}),keepalive:r,mode:"cors",credentials:"omit"});if(n.status===429)return {ok:!1,retryable:!1,status:429,retryAfterMs:ee(n.headers?.get?.("Retry-After"))};let s=n.status>=500;return {ok:n.ok,retryable:s,status:n.status}}catch{return {ok:false,retryable:true,status:0}}}};function ee(t){if(!t)return 6e4;let r=Number(t);if(Number.isFinite(r)&&r>=0)return Math.min(r*1e3,36e5);let n=Date.parse(t);return Number.isNaN(n)?6e4:Math.min(Math.max(n-Date.now(),0),36e5)}var te=200;function I(t,e,r){return Math.max(e,Math.min(r,t))}function re(t){return new Promise(e=>setTimeout(e,t))}var ne=3e3;function se(t){return {apiKey:t.apiKey,apiUrl:t.apiUrl,environment:t.environment??"production",release:t.release,enabled:t.enabled??true,sampleRate:I(t.sampleRate??1,0,1),maxBatchSize:I(t.maxBatchSize??20,1,100),flushIntervalMs:t.flushIntervalMs??4e3,maxQueueItems:t.maxQueueItems??100,maxRetries:t.maxRetries??3,maxBreadcrumbs:t.maxBreadcrumbs??30,offlineStorage:t.offlineStorage??true,captureUnhandledErrors:t.captureUnhandledErrors??true,captureUnhandledRejections:t.captureUnhandledRejections??true,captureResourceErrors:t.captureResourceErrors??true,captureConsole:t.captureConsole??true,autoBreadcrumbs:t.autoBreadcrumbs??true,denyUrls:t.denyUrls??[],allowUrls:t.allowUrls??[],beforeSend:t.beforeSend,trackPerformance:t.trackPerformance??false,metricsFlushIntervalMs:t.metricsFlushIntervalMs??6e4,debug:t.debug??false}}var p=class{constructor(e){this.buffer=[];this.flushTimer=null;this.processing=false;this.rateLimitedUntil=0;this.discarded={};this.closed=false;this.scope={tags:{}};this.teardown=[];this.config=se(e),this.transport=new E(this.config.apiUrl,this.config.apiKey),this.deduper=new g(ne),this.limiter=new w,this.queue=new v(this.config.offlineStorage,this.config.maxQueueItems),this.config.trackPerformance&&this.config.enabled&&(this.metrics=new b({apiKey:this.config.apiKey,apiUrl:this.config.apiUrl,flushIntervalMs:this.config.metricsFlushIntervalMs,maxRoutes:te,debug:this.config.debug}),this.metrics.start()),this.breadcrumbs=new m(this.config.maxBreadcrumbs),this.config.enabled&&(this.config.autoBreadcrumbs&&this.breadcrumbs.install(),this.teardown.push(O(this)),this.installLifecycle(),this.drainOffline());}captureException(e,r){try{this.process(D(j(e)),r?.severity??"Error",r);}catch(n){this.debug("captureException failed",n);}}captureMessage(e,r="Info",n){try{this.process({message:a(e,2e3),exceptionType:"Message"},n?.severity??r,n);}catch(s){this.debug("captureMessage failed",s);}}getDiscardedEvents(){return {...this.discarded}}discard(e,r=1){this.discarded[e]=(this.discarded[e]??0)+r;}addBreadcrumb(e){this.breadcrumbs.add(e);}setUser(e){this.scope.user=e??void 0;}setTag(e,r){this.scope.tags[e]=r;}setContext(e,r){this.scope.tags[e]=r;}close(){this.closed=true,this.flushTimer!==null&&(clearTimeout(this.flushTimer),this.flushTimer=null),this.flush(true),this.metrics?.close(),this.breadcrumbs.close();for(let e of this.teardown.splice(0))try{e();}catch{}}process(e,r,n){if(!this.config.enabled||this.closed)return;if(this.config.sampleRate<1&&Math.random()>this.config.sampleRate){this.discard("sampled");return}let s=`${e.stackTrace??""}
|
|
6
|
+
${u()?location.href:""}`;if(C(s,this.config.denyUrls)){this.discard("filtered");return}if(this.config.allowUrls.length>0&&!C(s,this.config.allowUrls)){this.discard("filtered");return}if(!this.deduper.shouldSend($(e.message,e.stackTrace))){this.discard("deduped");return}let i={message:e.message,severity:r,timestamp:h(),exceptionType:e.exceptionType,stackTrace:e.stackTrace,innerException:e.innerException,userId:this.scope.user?.id,requestPath:u()?location.pathname:void 0,userAgent:u()?navigator.userAgent:void 0,context:this.buildContext(n),breadcrumbs:this.breadcrumbs.snapshot()};if(i=K(i),this.config.beforeSend){let o=this.config.beforeSend(i);if(!o){this.discard("filtered");return}i=o;}this.enqueue(this.toPayload(i));}buildContext(e){let r={sdk:{name:"cherryboard-web",version:k},environment:this.config.environment};return this.config.release&&(r.release=this.config.release),u()&&(r.url=location.href,document.referrer&&(r.referrer=document.referrer),r.language=navigator.language,r.viewport={width:window.innerWidth,height:window.innerHeight}),this.scope.user&&(r.user=this.scope.user),Object.keys(this.scope.tags).length>0&&(r.tags={...this.scope.tags}),e?.componentStack&&(r.componentStack=e.componentStack),e?.digest&&(r.digest=e.digest),e?.context&&Object.assign(r,e.context),r}toPayload(e){let r=R({...e.context,breadcrumbs:e.breadcrumbs});return r===void 0&&(r=R(e.context)),{message:e.message,stackTrace:e.stackTrace,severity:e.severity,timestamp:e.timestamp,userId:e.userId,requestPath:e.requestPath,userAgent:e.userAgent,exceptionType:e.exceptionType,innerException:e.innerException,metadata:r}}enqueue(e){this.buffer.push(e),this.buffer.length>=this.config.maxBatchSize?this.flush():this.scheduleFlush();}scheduleFlush(){this.closed||this.flushTimer===null&&(this.flushTimer=setTimeout(()=>{this.flushTimer=null,this.flush();},this.config.flushIntervalMs));}async flush(e=false){if(this.flushTimer!==null&&(clearTimeout(this.flushTimer),this.flushTimer=null),this.buffer.length!==0){if(e){if(Date.now()<this.rateLimitedUntil)return;for(;this.buffer.length>0;){let r=this.buffer.splice(0,this.config.maxBatchSize);if(!this.limiter.allow())break;this.transport.send(r,true);}return}if(Date.now()<this.rateLimitedUntil){this.scheduleFlush();return}if(!this.processing){this.processing=true;try{for(;this.buffer.length>0;){let r=this.buffer.splice(0,this.config.maxBatchSize);if(!this.limiter.allow()){this.debug("rate limited; dropping",r.length,"events"),this.discard("rate_limited",r.length);break}if(!await this.deliver(r)){this.queue.push(r);break}}}finally{this.processing=false;}}}}async deliver(e){for(let r=0;r<=this.config.maxRetries;r++){let n=await this.transport.send(e,false);if(n.ok)return true;if(n.status===429)return this.rateLimitedUntil=Date.now()+(n.retryAfterMs??6e4),this.debug("rate limited by server; pausing for",n.retryAfterMs,"ms"),false;if(!n.retryable)return this.debug("non-retryable response",n.status,"\u2014 dropping batch"),this.discard("send_failed",e.length),true;if(r===this.config.maxRetries)return false;await re(I(2**r*1e3+Math.random()*250,0,15e3));}return false}drainOffline(){let e=this.queue.drain();e.length>0&&(this.buffer.push(...e),this.scheduleFlush());}installLifecycle(){if(!u())return;let e=()=>{document.visibilityState==="hidden"&&this.flush(true);},r=()=>{this.flush(true);},n=()=>this.drainOffline();document.addEventListener("visibilitychange",e),window.addEventListener("pagehide",r),window.addEventListener("online",n),this.teardown.push(()=>{document.removeEventListener("visibilitychange",e),window.removeEventListener("pagehide",r),window.removeEventListener("online",n);});}debug(...e){this.config.debug&&console.warn("[cherryboard]",...e);}};var F="__CHERRYBOARD__";function l(){let t=globalThis,e=t[F];return e||(e={},t[F]=e),e}function $e(t){let e=l();return e.client||(e.client=new p(t)),e.client}function Oe(){return l().client??null}function je(){return l().client!=null}function De(t,e){l().client?.captureException(t,e);}function qe(t,e,r){l().client?.captureMessage(t,e,r);}function He(t){l().client?.addBreadcrumb(t);}function Ke(t){l().client?.setUser(t);}function Fe(t,e){l().client?.setTag(t,e);}function Ve(t,e){l().client?.setContext(t,e);}function ze(){let t=l().client;return t?t.flush():Promise.resolve()}function Ge(t,e,r){l().client?.captureException(t,{severity:"Error",context:{source:"nextjs-server",...e?.path?{requestPath:e.path}:{},...e?.method?{requestMethod:e.method}:{},...r??{}}});}function Xe(){return l().client?.getDiscardedEvents()??{}}function We(){let t=l();t.client?.close(),t.client=void 0;}export{p as CherryBoardClient,k as SDK_VERSION,He as addBreadcrumb,De as captureException,qe as captureMessage,Ge as captureRequestError,We as close,ze as flush,Oe as getClient,Xe as getDiscardedEvents,$e as init,je as isInitialized,Ve as setContext,Fe as setTag,Ke as setUser};
|
package/dist/react/index.cjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
'use strict';var react=require('react'),jsxRuntime=require('react/jsx-runtime');var
|
|
3
|
-
`).slice(0,4).map(n=>n.replace(/:\d+:\d+/g,"").replace(/\?[^\s)]*/g,"").replace(/https?:\/\/[^/]+/g,"").trim()).join("|");return `${r}::${t}`.slice(0,500)}function
|
|
2
|
+
'use strict';var react=require('react'),jsxRuntime=require('react/jsx-runtime');var m="1.2.0";function u(){return typeof window<"u"&&typeof document<"u"}function g(){return new Date().toISOString()}function a(r,e){return e<=0?"":r.length<=e?r:`${r.slice(0,e-1)}\u2026`}function S(r,e=24e3){let t=new WeakSet;try{let n=JSON.stringify(r,(s,o)=>{if(o instanceof Error)return {name:o.name,message:o.message,stack:o.stack};if(typeof o=="object"&&o!==null){if(t.has(o))return "[Circular]";t.add(o);}if(typeof o=="bigint")return o.toString();if(typeof o!="function")return o});return n===void 0||n.length>e?void 0:n}catch{return}}function B(r,e){return !r||e.length===0?false:e.some(t=>typeof t=="string"?r.includes(t):t.test(r))}function D(r){return `${j(r)}/errors/batch`}function O(r){return `${j(r)}/metrics`}function j(r){let e=r.replace(/\/+$/,"");return /\/api\/v\d+$/.test(e)?e:`${e}/api/v1`}var y=class{constructor(e){this.max=e;this.items=[];this.teardown=[];}add(e){this.items.push({timestamp:e.timestamp??g(),category:e.category,message:a(e.message,500),level:e.level,data:e.data}),this.items.length>this.max&&this.items.shift();}snapshot(){return this.items.slice()}install(){u()&&(this.installHistory(),this.installClicks(),this.installFetch());}installHistory(){try{let e=window.history,t=s=>{let o=e[s];if(typeof o!="function")return ()=>{};let i=(...d)=>{try{let c=d[2];typeof c=="string"&&this.add({category:"navigation",message:`${s} \u2192 ${c}`});}catch{}return o.apply(window.history,d)};return e[s]=i,()=>{e[s]===i&&(e[s]=o);}};this.teardown.push(t("pushState"),t("replaceState"));let n=()=>this.add({category:"navigation",message:`popstate \u2192 ${location.pathname}`});window.addEventListener("popstate",n),this.teardown.push(()=>window.removeEventListener("popstate",n));}catch{}}installClicks(){try{let e=t=>{let n=t.target;if(!n||typeof n.tagName!="string")return;let s=n.id?`#${n.id}`:"",o=typeof n.className=="string"?n.className:"",i=o?`.${o.split(/\s+/).filter(Boolean).slice(0,2).join(".")}`:"";this.add({category:"click",message:`${n.tagName.toLowerCase()}${s}${i}`});};window.addEventListener("click",e,{capture:!0,passive:!0}),this.teardown.push(()=>window.removeEventListener("click",e,{capture:!0}));}catch{}}installFetch(){try{let e=window.fetch;if(typeof e!="function")return;let t=(...n)=>{let[s,o]=n,i=o?.method??(typeof s=="object"&&s&&"method"in s?s.method:"GET"),d=typeof s=="string"?s:s instanceof URL?s.href:s.url;return e.apply(window,n).then(c=>(this.add({category:"fetch",message:`${i} ${d} \u2192 ${c.status}`,level:c.ok?void 0:"Warning"}),c),c=>{throw this.add({category:"fetch",message:`${i} ${d} \u2192 failed`,level:"Warning"}),c})};window.fetch=t,this.teardown.push(()=>{window.fetch===t&&(window.fetch=e);});}catch{}}close(){for(let e of this.teardown.splice(0))try{e();}catch{}}};var v=class{constructor(e=4e3,t=100){this.windowMs=e;this.max=t;this.seen=new Map;}shouldSend(e){let t=Date.now(),n=this.seen.get(e);if(n!==void 0&&t-n<this.windowMs)return false;if(this.seen.set(e,t),this.seen.size>this.max){let s=this.seen.keys().next().value;s!==void 0&&this.seen.delete(s);}return true}};function q(r,e){let t=(e??"").split(`
|
|
3
|
+
`).slice(0,4).map(n=>n.replace(/:\d+:\d+/g,"").replace(/\?[^\s)]*/g,"").replace(/https?:\/\/[^/]+/g,"").trim()).join("|");return `${r}::${t}`.slice(0,500)}function H(r){if(!u())return ()=>{};let e=r.config,t=[];if(e.captureUnhandledErrors||e.captureResourceErrors){let n=s=>{try{let o=s.target;if(o instanceof HTMLElement){if(!e.captureResourceErrors)return;let d=o,c=d.src||d.href;if(!c)return;r.captureMessage(`Resource failed to load: ${c}`,"Warning",{context:{resource:o.tagName.toLowerCase(),url:c}});return}if(!e.captureUnhandledErrors)return;let i=s;if(!i.error&&(!i.message||i.message==="Script error."))return;r.captureException(i.error??i.message);}catch{}};window.addEventListener("error",n,true),t.push(()=>window.removeEventListener("error",n,true));}if(e.captureUnhandledRejections){let n=s=>{try{r.captureException(s.reason??"Unhandled promise rejection",{context:{unhandledRejection:!0}});}catch{}};window.addEventListener("unhandledrejection",n),t.push(()=>window.removeEventListener("unhandledrejection",n));}if(e.captureConsole){let n=console;for(let s of ["error","warn"]){let o=n[s];if(typeof o!="function")continue;let i=(...d)=>{try{r.addBreadcrumb({category:"console",level:s==="error"?"Error":"Warning",message:d.map(J).join(" ").slice(0,500)});}catch{}return o.apply(console,d)};n[s]=i,t.push(()=>{n[s]===i&&(n[s]=o);});}}return ()=>{for(let n of t.splice(0))try{n();}catch{}}}function J(r){if(typeof r=="string")return r;if(r instanceof Error)return `${r.name}: ${r.message}`;try{return JSON.stringify(r)??String(r)}catch{return String(r)}}var b=2e3,T=8e3,P=4e3;function K(r){if(r instanceof Error)return {message:a(r.message||r.name||"Error",b),exceptionType:r.name||"Error",stackTrace:r.stack?a(r.stack,T):void 0,innerException:M(r.cause)};if(typeof r=="string")return {message:a(r,b),exceptionType:"Error"};if(r&&typeof r=="object"){let e=r,t=typeof e.message=="string"?e.message:V(e)??Object.prototype.toString.call(r),n=typeof e.name=="string"?e.name:"Error",s=typeof e.stack=="string"?e.stack:void 0;return {message:a(t,b),exceptionType:n,stackTrace:s?a(s,T):void 0,innerException:M(e.cause)}}return {message:a(String(r),b),exceptionType:"Error"}}function F(r){if(r.stackTrace)return r;try{let e=new Error(r.message).stack;if(e){let t=e.split(`
|
|
4
4
|
`).slice(2).join(`
|
|
5
|
-
`);return {...r,stackTrace:a(t||e,
|
|
6
|
-
`),
|
|
7
|
-
${
|
|
5
|
+
`);return {...r,stackTrace:a(t||e,T)}}}catch{}return r}function M(r){if(r==null)return;if(r instanceof Error){let t=[`${r.name}: ${r.message}`];r.stack&&t.push(r.stack);let n=M(r.cause);return n&&t.push(`Caused by: ${n}`),a(t.join(`
|
|
6
|
+
`),P)}let e=V(r);return e?a(e,P):a(String(r),P)}function V(r){try{let e=JSON.stringify(r);return e==="{}"?void 0:e}catch{return}}var Q=/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i,Y=/^[0-9a-f]{16,}$/i,Z=/^\d+$/,ee=/^(?=.*\d)[A-Za-z0-9_-]{12,}$/;function re(r){return Q.test(r)||Z.test(r)||Y.test(r)||ee.test(r)}function U(r,e){let t;try{t=new URL(r,e??(typeof location<"u"?location.href:void 0)).pathname;}catch{return null}if(!t||t==="/")return "/";let n=t.split("/").filter(Boolean);if(n.length===0)return "/";let s=n.slice(0,12).map(i=>re(i)?":id":i);n.length>12&&s.push("\u2026");let o="/"+s.join("/");return o.length>300?o.slice(0,300):o}var f=[50,100,250,500,1e3,2500,5e3];function z(r){for(let e=0;e<f.length;e++)if(r<=f[e])return e;return f.length}var w=class{constructor(e){this.options=e;this.requests=new Map;this.pageViews=new Map;this.teardown=[];this.closed=false;this.endpoint=O(e.apiUrl);let t=typeof fetch=="function"?fetch.bind(globalThis):void 0;this.fetchImpl=t??(()=>Promise.reject(new Error("fetch unavailable")));}start(){!u()||this.closed||(this.recordPageView(location.pathname,true),this.observeResources(),this.observeNavigation(),this.observeVisibility(),this.timer=setInterval(()=>{this.flush();},this.options.flushIntervalMs));}observeResources(){if(!(typeof PerformanceObserver>"u"))try{this.observer=new PerformanceObserver(e=>{for(let t of e.getEntries())this.recordResource(t);}),this.observer.observe({type:"resource",buffered:!0});}catch{}}recordResource(e){if(e.initiatorType!=="fetch"&&e.initiatorType!=="xmlhttprequest"||e.name.startsWith(this.endpoint)||e.name.includes("/api/v1/errors/batch"))return;let t=U(e.name);if(!t)return;let n=Math.round(e.duration);n<=0||this.addRequest(t,"ALL",n,false);}addRequest(e,t,n,s){let o=`${t} ${e}`,i=this.requests.get(o);if(!i){if(this.requests.size>=this.options.maxRoutes)return;i={route:e,method:t,count:0,errorCount:0,sumMs:0,maxMs:0,buckets:new Array(f.length+1).fill(0)},this.requests.set(o,i);}i.count++,s&&i.errorCount++,i.sumMs+=n,n>i.maxMs&&(i.maxMs=n),i.buckets[z(n)]++;}observeNavigation(){let e=()=>this.recordPageView(location.pathname,false);try{let t=window.history,n=s=>{let o=t[s];if(typeof o!="function")return ()=>{};let i=(...d)=>{let c=o.apply(window.history,d);return e(),c};return t[s]=i,()=>{t[s]===i&&(t[s]=o);}};this.teardown.push(n("pushState"),n("replaceState")),window.addEventListener("popstate",e),this.teardown.push(()=>window.removeEventListener("popstate",e));}catch{}}recordPageView(e,t){let n=U(e);if(!n)return;let s=this.pageViews.get(n);if(!s){if(this.pageViews.size>=this.options.maxRoutes)return;s={route:n,views:0,visits:0},this.pageViews.set(n,s);}s.views++,t&&s.visits++;}observeVisibility(){let e=()=>{document.visibilityState==="hidden"&&this.flush(true);};document.addEventListener("visibilitychange",e),this.teardown.push(()=>document.removeEventListener("visibilitychange",e));}async flush(e=false){if(this.requests.size===0&&this.pageViews.size===0)return;let t={requests:Array.from(this.requests.values()).map(n=>({route:n.route,method:n.method,count:n.count,errorCount:n.errorCount,sumMs:n.sumMs,maxMs:n.maxMs,buckets:n.buckets})),pageViews:Array.from(this.pageViews.values()).map(n=>({route:n.route,views:n.views,visits:n.visits}))};this.requests.clear(),this.pageViews.clear();try{await this.fetchImpl(this.endpoint,{method:"POST",headers:{"Content-Type":"application/json","X-API-Key":this.options.apiKey},body:JSON.stringify(t),keepalive:e,mode:"cors",credentials:"omit"});}catch{this.options.debug&&console.warn("[cherryboard] metrics flush failed");}}close(){this.closed=true,this.timer&&clearInterval(this.timer),this.observer?.disconnect();for(let e of this.teardown)e();this.teardown=[];}};var I="cherryboard:queue:v1",x=class{constructor(e,t){this.enabled=e;this.maxItems=t;}get store(){if(!this.enabled||!u())return null;try{return window.localStorage}catch{return null}}push(e){let t=this.store;if(!(!t||e.length===0))try{let n=this.read().concat(e).slice(-this.maxItems);t.setItem(I,JSON.stringify(n));}catch{}}read(){let e=this.store;if(!e)return [];try{let t=e.getItem(I);if(!t)return [];let n=JSON.parse(t);return Array.isArray(n)?n:[]}catch{return []}}drain(){let e=this.read();return this.clear(),e}clear(){let e=this.store;if(e)try{e.removeItem(I);}catch{}}};var E=class{constructor(e=30,t=5){this.capacity=e;this.refillPerSec=t;this.tokens=e,this.last=Date.now();}allow(){let e=Date.now(),t=(e-this.last)/1e3;return this.tokens=Math.min(this.capacity,this.tokens+t*this.refillPerSec),this.last=e,this.tokens>=1?(this.tokens-=1,true):false}};var te=["password","passwd","secret","token","apikey","api_key","authorization","auth","cookie","session","credit","card","cvv","ssn"],ne=["token","access_token","apikey","api_key","email","password","code","secret"],A="[Filtered]",se=/[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/g;function N(r){let e=r;for(let t of ne)e=e.replace(new RegExp(`([?&]${t}=)[^&#\\s]*`,"gi"),`$1${A}`);return e}function k(r,e=0){if(e>6)return r;if(typeof r=="string")return N(r).replace(se,A);if(Array.isArray(r))return r.map(t=>k(t,e+1));if(r&&typeof r=="object"){let t={};for(let[n,s]of Object.entries(r)){let o=n.toLowerCase();t[n]=te.some(i=>o.includes(i))?A:k(s,e+1);}return t}return r}function G(r){return {...r,message:N(r.message),context:k(r.context),breadcrumbs:r.breadcrumbs.map(e=>({...e,message:N(e.message),data:e.data?k(e.data):void 0}))}}var C=class{constructor(e,t){this.apiKey=t;this.endpoint=D(e);let n=typeof fetch=="function"?fetch.bind(globalThis):void 0;this.fetchImpl=n??(()=>Promise.reject(new Error("fetch unavailable")));}async send(e,t=false){if(e.length===0)return {ok:true,retryable:false,status:204};try{let n=await this.fetchImpl(this.endpoint,{method:"POST",headers:{"Content-Type":"application/json",Accept:"application/json","X-API-Key":this.apiKey},body:JSON.stringify({errors:e}),keepalive:t,mode:"cors",credentials:"omit"});if(n.status===429)return {ok:!1,retryable:!1,status:429,retryAfterMs:oe(n.headers?.get?.("Retry-After"))};let s=n.status>=500;return {ok:n.ok,retryable:s,status:n.status}}catch{return {ok:false,retryable:true,status:0}}}};function oe(r){if(!r)return 6e4;let t=Number(r);if(Number.isFinite(t)&&t>=0)return Math.min(t*1e3,36e5);let n=Date.parse(r);return Number.isNaN(n)?6e4:Math.min(Math.max(n-Date.now(),0),36e5)}var ie=200;function _(r,e,t){return Math.max(e,Math.min(t,r))}function ae(r){return new Promise(e=>setTimeout(e,r))}var ce=3e3;function ue(r){return {apiKey:r.apiKey,apiUrl:r.apiUrl,environment:r.environment??"production",release:r.release,enabled:r.enabled??true,sampleRate:_(r.sampleRate??1,0,1),maxBatchSize:_(r.maxBatchSize??20,1,100),flushIntervalMs:r.flushIntervalMs??4e3,maxQueueItems:r.maxQueueItems??100,maxRetries:r.maxRetries??3,maxBreadcrumbs:r.maxBreadcrumbs??30,offlineStorage:r.offlineStorage??true,captureUnhandledErrors:r.captureUnhandledErrors??true,captureUnhandledRejections:r.captureUnhandledRejections??true,captureResourceErrors:r.captureResourceErrors??true,captureConsole:r.captureConsole??true,autoBreadcrumbs:r.autoBreadcrumbs??true,denyUrls:r.denyUrls??[],allowUrls:r.allowUrls??[],beforeSend:r.beforeSend,trackPerformance:r.trackPerformance??false,metricsFlushIntervalMs:r.metricsFlushIntervalMs??6e4,debug:r.debug??false}}var R=class{constructor(e){this.buffer=[];this.flushTimer=null;this.processing=false;this.rateLimitedUntil=0;this.discarded={};this.closed=false;this.scope={tags:{}};this.teardown=[];this.config=ue(e),this.transport=new C(this.config.apiUrl,this.config.apiKey),this.deduper=new v(ce),this.limiter=new E,this.queue=new x(this.config.offlineStorage,this.config.maxQueueItems),this.config.trackPerformance&&this.config.enabled&&(this.metrics=new w({apiKey:this.config.apiKey,apiUrl:this.config.apiUrl,flushIntervalMs:this.config.metricsFlushIntervalMs,maxRoutes:ie,debug:this.config.debug}),this.metrics.start()),this.breadcrumbs=new y(this.config.maxBreadcrumbs),this.config.enabled&&(this.config.autoBreadcrumbs&&this.breadcrumbs.install(),this.teardown.push(H(this)),this.installLifecycle(),this.drainOffline());}captureException(e,t){try{this.process(F(K(e)),t?.severity??"Error",t);}catch(n){this.debug("captureException failed",n);}}captureMessage(e,t="Info",n){try{this.process({message:a(e,2e3),exceptionType:"Message"},n?.severity??t,n);}catch(s){this.debug("captureMessage failed",s);}}getDiscardedEvents(){return {...this.discarded}}discard(e,t=1){this.discarded[e]=(this.discarded[e]??0)+t;}addBreadcrumb(e){this.breadcrumbs.add(e);}setUser(e){this.scope.user=e??void 0;}setTag(e,t){this.scope.tags[e]=t;}setContext(e,t){this.scope.tags[e]=t;}close(){this.closed=true,this.flushTimer!==null&&(clearTimeout(this.flushTimer),this.flushTimer=null),this.flush(true),this.metrics?.close(),this.breadcrumbs.close();for(let e of this.teardown.splice(0))try{e();}catch{}}process(e,t,n){if(!this.config.enabled||this.closed)return;if(this.config.sampleRate<1&&Math.random()>this.config.sampleRate){this.discard("sampled");return}let s=`${e.stackTrace??""}
|
|
7
|
+
${u()?location.href:""}`;if(B(s,this.config.denyUrls)){this.discard("filtered");return}if(this.config.allowUrls.length>0&&!B(s,this.config.allowUrls)){this.discard("filtered");return}if(!this.deduper.shouldSend(q(e.message,e.stackTrace))){this.discard("deduped");return}let o={message:e.message,severity:t,timestamp:g(),exceptionType:e.exceptionType,stackTrace:e.stackTrace,innerException:e.innerException,userId:this.scope.user?.id,requestPath:u()?location.pathname:void 0,userAgent:u()?navigator.userAgent:void 0,context:this.buildContext(n),breadcrumbs:this.breadcrumbs.snapshot()};if(o=G(o),this.config.beforeSend){let i=this.config.beforeSend(o);if(!i){this.discard("filtered");return}o=i;}this.enqueue(this.toPayload(o));}buildContext(e){let t={sdk:{name:"cherryboard-web",version:m},environment:this.config.environment};return this.config.release&&(t.release=this.config.release),u()&&(t.url=location.href,document.referrer&&(t.referrer=document.referrer),t.language=navigator.language,t.viewport={width:window.innerWidth,height:window.innerHeight}),this.scope.user&&(t.user=this.scope.user),Object.keys(this.scope.tags).length>0&&(t.tags={...this.scope.tags}),e?.componentStack&&(t.componentStack=e.componentStack),e?.digest&&(t.digest=e.digest),e?.context&&Object.assign(t,e.context),t}toPayload(e){let t=S({...e.context,breadcrumbs:e.breadcrumbs});return t===void 0&&(t=S(e.context)),{message:e.message,stackTrace:e.stackTrace,severity:e.severity,timestamp:e.timestamp,userId:e.userId,requestPath:e.requestPath,userAgent:e.userAgent,exceptionType:e.exceptionType,innerException:e.innerException,metadata:t}}enqueue(e){this.buffer.push(e),this.buffer.length>=this.config.maxBatchSize?this.flush():this.scheduleFlush();}scheduleFlush(){this.closed||this.flushTimer===null&&(this.flushTimer=setTimeout(()=>{this.flushTimer=null,this.flush();},this.config.flushIntervalMs));}async flush(e=false){if(this.flushTimer!==null&&(clearTimeout(this.flushTimer),this.flushTimer=null),this.buffer.length!==0){if(e){if(Date.now()<this.rateLimitedUntil)return;for(;this.buffer.length>0;){let t=this.buffer.splice(0,this.config.maxBatchSize);if(!this.limiter.allow())break;this.transport.send(t,true);}return}if(Date.now()<this.rateLimitedUntil){this.scheduleFlush();return}if(!this.processing){this.processing=true;try{for(;this.buffer.length>0;){let t=this.buffer.splice(0,this.config.maxBatchSize);if(!this.limiter.allow()){this.debug("rate limited; dropping",t.length,"events"),this.discard("rate_limited",t.length);break}if(!await this.deliver(t)){this.queue.push(t);break}}}finally{this.processing=false;}}}}async deliver(e){for(let t=0;t<=this.config.maxRetries;t++){let n=await this.transport.send(e,false);if(n.ok)return true;if(n.status===429)return this.rateLimitedUntil=Date.now()+(n.retryAfterMs??6e4),this.debug("rate limited by server; pausing for",n.retryAfterMs,"ms"),false;if(!n.retryable)return this.debug("non-retryable response",n.status,"\u2014 dropping batch"),this.discard("send_failed",e.length),true;if(t===this.config.maxRetries)return false;await ae(_(2**t*1e3+Math.random()*250,0,15e3));}return false}drainOffline(){let e=this.queue.drain();e.length>0&&(this.buffer.push(...e),this.scheduleFlush());}installLifecycle(){if(!u())return;let e=()=>{document.visibilityState==="hidden"&&this.flush(true);},t=()=>{this.flush(true);},n=()=>this.drainOffline();document.addEventListener("visibilitychange",e),window.addEventListener("pagehide",t),window.addEventListener("online",n),this.teardown.push(()=>{document.removeEventListener("visibilitychange",e),window.removeEventListener("pagehide",t),window.removeEventListener("online",n);});}debug(...e){this.config.debug&&console.warn("[cherryboard]",...e);}};var X="__CHERRYBOARD__";function l(){let r=globalThis,e=r[X];return e||(e={},r[X]=e),e}function L(r){let e=l();return e.client||(e.client=new R(r)),e.client}function $(){return l().client??null}function de(){return l().client!=null}function p(r,e){l().client?.captureException(r,e);}function le(r,e,t){l().client?.captureMessage(r,e,t);}function pe(r){l().client?.addBreadcrumb(r);}function fe(r){l().client?.setUser(r);}function he(r,e){l().client?.setTag(r,e);}function me(r,e){l().client?.setContext(r,e);}function ge(){let r=l().client;return r?r.flush():Promise.resolve()}function ye(r,e,t){l().client?.captureException(r,{severity:"Error",context:{source:"nextjs-server",...e?.path?{requestPath:e.path}:{},...e?.method?{requestMethod:e.method}:{},...t??{}}});}function ve(){return l().client?.getDiscardedEvents()??{}}function be(){let r=l();r.client?.close(),r.client=void 0;}var h=class extends react.Component{constructor(){super(...arguments);this.state={error:null};this.reset=()=>{this.setState({error:null});};}static getDerivedStateFromError(t){return {error:t}}componentDidCatch(t,n){p(t,{severity:"Error",componentStack:n.componentStack??void 0,context:{source:"react-error-boundary"}}),this.props.onError?.(t,n);}componentDidUpdate(t){this.state.error&&!xe(t.resetKeys,this.props.resetKeys)&&this.reset();}render(){let{error:t}=this.state;if(t){let{fallback:n}=this.props;return typeof n=="function"?n({error:t,reset:this.reset}):n??null}return this.props.children}};function xe(r,e){return r===e?true:!r||!e||r.length!==e.length?false:r.every((t,n)=>Object.is(t,e[n]))}function Ce({config:r,children:e,withBoundary:t=false,fallback:n}){let s=react.useRef(r);return react.useEffect(()=>{typeof window<"u"&&L(s.current);},[]),t?jsxRuntime.jsx(h,{fallback:n,children:e}):jsxRuntime.jsx(jsxRuntime.Fragment,{children:e})}function Be(){return $()}function Pe(){return react.useCallback((r,e)=>p(r,e),[])}function Te(r){p(r,{severity:"Error",digest:r.digest,context:{source:"next-error-boundary"}});}exports.CherryBoardProvider=Ce;exports.ErrorBoundary=h;exports.SDK_VERSION=m;exports.addBreadcrumb=pe;exports.captureException=p;exports.captureMessage=le;exports.captureRequestError=ye;exports.captureRouteError=Te;exports.close=be;exports.flush=ge;exports.getClient=$;exports.getDiscardedEvents=ve;exports.init=L;exports.isInitialized=de;exports.setContext=me;exports.setTag=he;exports.setUser=fe;exports.useCaptureError=Pe;exports.useCherryBoard=Be;
|
package/dist/react/index.d.cts
CHANGED
|
@@ -32,7 +32,7 @@ declare class ErrorBoundary extends Component<ErrorBoundaryProps, ErrorBoundaryS
|
|
|
32
32
|
}
|
|
33
33
|
|
|
34
34
|
/** SDK version — kept in sync with package.json. */
|
|
35
|
-
declare const SDK_VERSION = "1.
|
|
35
|
+
declare const SDK_VERSION = "1.2.0";
|
|
36
36
|
/** Severity levels — these strings match the backend `ErrorSeverity` enum exactly. */
|
|
37
37
|
type Severity = 'Debug' | 'Info' | 'Warning' | 'Error' | 'Critical';
|
|
38
38
|
/** A trail entry giving context that led up to an error. */
|
|
@@ -121,6 +121,16 @@ interface CherryBoardConfig {
|
|
|
121
121
|
captureResourceErrors?: boolean;
|
|
122
122
|
/** Turn console.error/warn into breadcrumbs (never into events). Default true. */
|
|
123
123
|
captureConsole?: boolean;
|
|
124
|
+
/**
|
|
125
|
+
* Collect API request timings and page views alongside errors.
|
|
126
|
+
*
|
|
127
|
+
* Off by default: upgrading the package must not silently start sending new
|
|
128
|
+
* data about someone's users. Aggregated in the browser and stripped of
|
|
129
|
+
* identifiers before it leaves — see the performance docs.
|
|
130
|
+
*/
|
|
131
|
+
trackPerformance?: boolean;
|
|
132
|
+
/** How often aggregated metrics are posted. Default 60000 (once a minute). */
|
|
133
|
+
metricsFlushIntervalMs?: number;
|
|
124
134
|
/** Auto-record navigation / click / fetch breadcrumbs. Default true. */
|
|
125
135
|
autoBreadcrumbs?: boolean;
|
|
126
136
|
/** Drop events whose stack/URL matches any of these. */
|
|
@@ -143,6 +153,8 @@ interface ResolvedConfig {
|
|
|
143
153
|
environment: string;
|
|
144
154
|
release?: string;
|
|
145
155
|
enabled: boolean;
|
|
156
|
+
trackPerformance: boolean;
|
|
157
|
+
metricsFlushIntervalMs: number;
|
|
146
158
|
sampleRate: number;
|
|
147
159
|
maxBatchSize: number;
|
|
148
160
|
flushIntervalMs: number;
|
|
@@ -212,6 +224,7 @@ declare class CherryBoardClient {
|
|
|
212
224
|
private readonly deduper;
|
|
213
225
|
private readonly limiter;
|
|
214
226
|
private readonly queue;
|
|
227
|
+
private readonly metrics?;
|
|
215
228
|
private buffer;
|
|
216
229
|
private flushTimer;
|
|
217
230
|
private processing;
|
package/dist/react/index.d.ts
CHANGED
|
@@ -32,7 +32,7 @@ declare class ErrorBoundary extends Component<ErrorBoundaryProps, ErrorBoundaryS
|
|
|
32
32
|
}
|
|
33
33
|
|
|
34
34
|
/** SDK version — kept in sync with package.json. */
|
|
35
|
-
declare const SDK_VERSION = "1.
|
|
35
|
+
declare const SDK_VERSION = "1.2.0";
|
|
36
36
|
/** Severity levels — these strings match the backend `ErrorSeverity` enum exactly. */
|
|
37
37
|
type Severity = 'Debug' | 'Info' | 'Warning' | 'Error' | 'Critical';
|
|
38
38
|
/** A trail entry giving context that led up to an error. */
|
|
@@ -121,6 +121,16 @@ interface CherryBoardConfig {
|
|
|
121
121
|
captureResourceErrors?: boolean;
|
|
122
122
|
/** Turn console.error/warn into breadcrumbs (never into events). Default true. */
|
|
123
123
|
captureConsole?: boolean;
|
|
124
|
+
/**
|
|
125
|
+
* Collect API request timings and page views alongside errors.
|
|
126
|
+
*
|
|
127
|
+
* Off by default: upgrading the package must not silently start sending new
|
|
128
|
+
* data about someone's users. Aggregated in the browser and stripped of
|
|
129
|
+
* identifiers before it leaves — see the performance docs.
|
|
130
|
+
*/
|
|
131
|
+
trackPerformance?: boolean;
|
|
132
|
+
/** How often aggregated metrics are posted. Default 60000 (once a minute). */
|
|
133
|
+
metricsFlushIntervalMs?: number;
|
|
124
134
|
/** Auto-record navigation / click / fetch breadcrumbs. Default true. */
|
|
125
135
|
autoBreadcrumbs?: boolean;
|
|
126
136
|
/** Drop events whose stack/URL matches any of these. */
|
|
@@ -143,6 +153,8 @@ interface ResolvedConfig {
|
|
|
143
153
|
environment: string;
|
|
144
154
|
release?: string;
|
|
145
155
|
enabled: boolean;
|
|
156
|
+
trackPerformance: boolean;
|
|
157
|
+
metricsFlushIntervalMs: number;
|
|
146
158
|
sampleRate: number;
|
|
147
159
|
maxBatchSize: number;
|
|
148
160
|
flushIntervalMs: number;
|
|
@@ -212,6 +224,7 @@ declare class CherryBoardClient {
|
|
|
212
224
|
private readonly deduper;
|
|
213
225
|
private readonly limiter;
|
|
214
226
|
private readonly queue;
|
|
227
|
+
private readonly metrics?;
|
|
215
228
|
private buffer;
|
|
216
229
|
private flushTimer;
|
|
217
230
|
private processing;
|
package/dist/react/index.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import {Component,useRef,useEffect,useCallback}from'react';import {jsx,Fragment}from'react/jsx-runtime';var
|
|
3
|
-
`).slice(0,4).map(n=>n.replace(/:\d+:\d+/g,"").replace(/\?[^\s)]*/g,"").replace(/https?:\/\/[^/]+/g,"").trim()).join("|");return `${r}::${t}`.slice(0,500)}function
|
|
2
|
+
import {Component,useRef,useEffect,useCallback}from'react';import {jsx,Fragment}from'react/jsx-runtime';var m="1.2.0";function u(){return typeof window<"u"&&typeof document<"u"}function g(){return new Date().toISOString()}function a(r,e){return e<=0?"":r.length<=e?r:`${r.slice(0,e-1)}\u2026`}function S(r,e=24e3){let t=new WeakSet;try{let n=JSON.stringify(r,(s,o)=>{if(o instanceof Error)return {name:o.name,message:o.message,stack:o.stack};if(typeof o=="object"&&o!==null){if(t.has(o))return "[Circular]";t.add(o);}if(typeof o=="bigint")return o.toString();if(typeof o!="function")return o});return n===void 0||n.length>e?void 0:n}catch{return}}function B(r,e){return !r||e.length===0?false:e.some(t=>typeof t=="string"?r.includes(t):t.test(r))}function D(r){return `${j(r)}/errors/batch`}function O(r){return `${j(r)}/metrics`}function j(r){let e=r.replace(/\/+$/,"");return /\/api\/v\d+$/.test(e)?e:`${e}/api/v1`}var y=class{constructor(e){this.max=e;this.items=[];this.teardown=[];}add(e){this.items.push({timestamp:e.timestamp??g(),category:e.category,message:a(e.message,500),level:e.level,data:e.data}),this.items.length>this.max&&this.items.shift();}snapshot(){return this.items.slice()}install(){u()&&(this.installHistory(),this.installClicks(),this.installFetch());}installHistory(){try{let e=window.history,t=s=>{let o=e[s];if(typeof o!="function")return ()=>{};let i=(...d)=>{try{let c=d[2];typeof c=="string"&&this.add({category:"navigation",message:`${s} \u2192 ${c}`});}catch{}return o.apply(window.history,d)};return e[s]=i,()=>{e[s]===i&&(e[s]=o);}};this.teardown.push(t("pushState"),t("replaceState"));let n=()=>this.add({category:"navigation",message:`popstate \u2192 ${location.pathname}`});window.addEventListener("popstate",n),this.teardown.push(()=>window.removeEventListener("popstate",n));}catch{}}installClicks(){try{let e=t=>{let n=t.target;if(!n||typeof n.tagName!="string")return;let s=n.id?`#${n.id}`:"",o=typeof n.className=="string"?n.className:"",i=o?`.${o.split(/\s+/).filter(Boolean).slice(0,2).join(".")}`:"";this.add({category:"click",message:`${n.tagName.toLowerCase()}${s}${i}`});};window.addEventListener("click",e,{capture:!0,passive:!0}),this.teardown.push(()=>window.removeEventListener("click",e,{capture:!0}));}catch{}}installFetch(){try{let e=window.fetch;if(typeof e!="function")return;let t=(...n)=>{let[s,o]=n,i=o?.method??(typeof s=="object"&&s&&"method"in s?s.method:"GET"),d=typeof s=="string"?s:s instanceof URL?s.href:s.url;return e.apply(window,n).then(c=>(this.add({category:"fetch",message:`${i} ${d} \u2192 ${c.status}`,level:c.ok?void 0:"Warning"}),c),c=>{throw this.add({category:"fetch",message:`${i} ${d} \u2192 failed`,level:"Warning"}),c})};window.fetch=t,this.teardown.push(()=>{window.fetch===t&&(window.fetch=e);});}catch{}}close(){for(let e of this.teardown.splice(0))try{e();}catch{}}};var v=class{constructor(e=4e3,t=100){this.windowMs=e;this.max=t;this.seen=new Map;}shouldSend(e){let t=Date.now(),n=this.seen.get(e);if(n!==void 0&&t-n<this.windowMs)return false;if(this.seen.set(e,t),this.seen.size>this.max){let s=this.seen.keys().next().value;s!==void 0&&this.seen.delete(s);}return true}};function q(r,e){let t=(e??"").split(`
|
|
3
|
+
`).slice(0,4).map(n=>n.replace(/:\d+:\d+/g,"").replace(/\?[^\s)]*/g,"").replace(/https?:\/\/[^/]+/g,"").trim()).join("|");return `${r}::${t}`.slice(0,500)}function H(r){if(!u())return ()=>{};let e=r.config,t=[];if(e.captureUnhandledErrors||e.captureResourceErrors){let n=s=>{try{let o=s.target;if(o instanceof HTMLElement){if(!e.captureResourceErrors)return;let d=o,c=d.src||d.href;if(!c)return;r.captureMessage(`Resource failed to load: ${c}`,"Warning",{context:{resource:o.tagName.toLowerCase(),url:c}});return}if(!e.captureUnhandledErrors)return;let i=s;if(!i.error&&(!i.message||i.message==="Script error."))return;r.captureException(i.error??i.message);}catch{}};window.addEventListener("error",n,true),t.push(()=>window.removeEventListener("error",n,true));}if(e.captureUnhandledRejections){let n=s=>{try{r.captureException(s.reason??"Unhandled promise rejection",{context:{unhandledRejection:!0}});}catch{}};window.addEventListener("unhandledrejection",n),t.push(()=>window.removeEventListener("unhandledrejection",n));}if(e.captureConsole){let n=console;for(let s of ["error","warn"]){let o=n[s];if(typeof o!="function")continue;let i=(...d)=>{try{r.addBreadcrumb({category:"console",level:s==="error"?"Error":"Warning",message:d.map(J).join(" ").slice(0,500)});}catch{}return o.apply(console,d)};n[s]=i,t.push(()=>{n[s]===i&&(n[s]=o);});}}return ()=>{for(let n of t.splice(0))try{n();}catch{}}}function J(r){if(typeof r=="string")return r;if(r instanceof Error)return `${r.name}: ${r.message}`;try{return JSON.stringify(r)??String(r)}catch{return String(r)}}var b=2e3,T=8e3,P=4e3;function K(r){if(r instanceof Error)return {message:a(r.message||r.name||"Error",b),exceptionType:r.name||"Error",stackTrace:r.stack?a(r.stack,T):void 0,innerException:M(r.cause)};if(typeof r=="string")return {message:a(r,b),exceptionType:"Error"};if(r&&typeof r=="object"){let e=r,t=typeof e.message=="string"?e.message:V(e)??Object.prototype.toString.call(r),n=typeof e.name=="string"?e.name:"Error",s=typeof e.stack=="string"?e.stack:void 0;return {message:a(t,b),exceptionType:n,stackTrace:s?a(s,T):void 0,innerException:M(e.cause)}}return {message:a(String(r),b),exceptionType:"Error"}}function F(r){if(r.stackTrace)return r;try{let e=new Error(r.message).stack;if(e){let t=e.split(`
|
|
4
4
|
`).slice(2).join(`
|
|
5
|
-
`);return {...r,stackTrace:a(t||e,
|
|
6
|
-
`),
|
|
7
|
-
${
|
|
5
|
+
`);return {...r,stackTrace:a(t||e,T)}}}catch{}return r}function M(r){if(r==null)return;if(r instanceof Error){let t=[`${r.name}: ${r.message}`];r.stack&&t.push(r.stack);let n=M(r.cause);return n&&t.push(`Caused by: ${n}`),a(t.join(`
|
|
6
|
+
`),P)}let e=V(r);return e?a(e,P):a(String(r),P)}function V(r){try{let e=JSON.stringify(r);return e==="{}"?void 0:e}catch{return}}var Q=/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i,Y=/^[0-9a-f]{16,}$/i,Z=/^\d+$/,ee=/^(?=.*\d)[A-Za-z0-9_-]{12,}$/;function re(r){return Q.test(r)||Z.test(r)||Y.test(r)||ee.test(r)}function U(r,e){let t;try{t=new URL(r,e??(typeof location<"u"?location.href:void 0)).pathname;}catch{return null}if(!t||t==="/")return "/";let n=t.split("/").filter(Boolean);if(n.length===0)return "/";let s=n.slice(0,12).map(i=>re(i)?":id":i);n.length>12&&s.push("\u2026");let o="/"+s.join("/");return o.length>300?o.slice(0,300):o}var f=[50,100,250,500,1e3,2500,5e3];function z(r){for(let e=0;e<f.length;e++)if(r<=f[e])return e;return f.length}var w=class{constructor(e){this.options=e;this.requests=new Map;this.pageViews=new Map;this.teardown=[];this.closed=false;this.endpoint=O(e.apiUrl);let t=typeof fetch=="function"?fetch.bind(globalThis):void 0;this.fetchImpl=t??(()=>Promise.reject(new Error("fetch unavailable")));}start(){!u()||this.closed||(this.recordPageView(location.pathname,true),this.observeResources(),this.observeNavigation(),this.observeVisibility(),this.timer=setInterval(()=>{this.flush();},this.options.flushIntervalMs));}observeResources(){if(!(typeof PerformanceObserver>"u"))try{this.observer=new PerformanceObserver(e=>{for(let t of e.getEntries())this.recordResource(t);}),this.observer.observe({type:"resource",buffered:!0});}catch{}}recordResource(e){if(e.initiatorType!=="fetch"&&e.initiatorType!=="xmlhttprequest"||e.name.startsWith(this.endpoint)||e.name.includes("/api/v1/errors/batch"))return;let t=U(e.name);if(!t)return;let n=Math.round(e.duration);n<=0||this.addRequest(t,"ALL",n,false);}addRequest(e,t,n,s){let o=`${t} ${e}`,i=this.requests.get(o);if(!i){if(this.requests.size>=this.options.maxRoutes)return;i={route:e,method:t,count:0,errorCount:0,sumMs:0,maxMs:0,buckets:new Array(f.length+1).fill(0)},this.requests.set(o,i);}i.count++,s&&i.errorCount++,i.sumMs+=n,n>i.maxMs&&(i.maxMs=n),i.buckets[z(n)]++;}observeNavigation(){let e=()=>this.recordPageView(location.pathname,false);try{let t=window.history,n=s=>{let o=t[s];if(typeof o!="function")return ()=>{};let i=(...d)=>{let c=o.apply(window.history,d);return e(),c};return t[s]=i,()=>{t[s]===i&&(t[s]=o);}};this.teardown.push(n("pushState"),n("replaceState")),window.addEventListener("popstate",e),this.teardown.push(()=>window.removeEventListener("popstate",e));}catch{}}recordPageView(e,t){let n=U(e);if(!n)return;let s=this.pageViews.get(n);if(!s){if(this.pageViews.size>=this.options.maxRoutes)return;s={route:n,views:0,visits:0},this.pageViews.set(n,s);}s.views++,t&&s.visits++;}observeVisibility(){let e=()=>{document.visibilityState==="hidden"&&this.flush(true);};document.addEventListener("visibilitychange",e),this.teardown.push(()=>document.removeEventListener("visibilitychange",e));}async flush(e=false){if(this.requests.size===0&&this.pageViews.size===0)return;let t={requests:Array.from(this.requests.values()).map(n=>({route:n.route,method:n.method,count:n.count,errorCount:n.errorCount,sumMs:n.sumMs,maxMs:n.maxMs,buckets:n.buckets})),pageViews:Array.from(this.pageViews.values()).map(n=>({route:n.route,views:n.views,visits:n.visits}))};this.requests.clear(),this.pageViews.clear();try{await this.fetchImpl(this.endpoint,{method:"POST",headers:{"Content-Type":"application/json","X-API-Key":this.options.apiKey},body:JSON.stringify(t),keepalive:e,mode:"cors",credentials:"omit"});}catch{this.options.debug&&console.warn("[cherryboard] metrics flush failed");}}close(){this.closed=true,this.timer&&clearInterval(this.timer),this.observer?.disconnect();for(let e of this.teardown)e();this.teardown=[];}};var I="cherryboard:queue:v1",x=class{constructor(e,t){this.enabled=e;this.maxItems=t;}get store(){if(!this.enabled||!u())return null;try{return window.localStorage}catch{return null}}push(e){let t=this.store;if(!(!t||e.length===0))try{let n=this.read().concat(e).slice(-this.maxItems);t.setItem(I,JSON.stringify(n));}catch{}}read(){let e=this.store;if(!e)return [];try{let t=e.getItem(I);if(!t)return [];let n=JSON.parse(t);return Array.isArray(n)?n:[]}catch{return []}}drain(){let e=this.read();return this.clear(),e}clear(){let e=this.store;if(e)try{e.removeItem(I);}catch{}}};var E=class{constructor(e=30,t=5){this.capacity=e;this.refillPerSec=t;this.tokens=e,this.last=Date.now();}allow(){let e=Date.now(),t=(e-this.last)/1e3;return this.tokens=Math.min(this.capacity,this.tokens+t*this.refillPerSec),this.last=e,this.tokens>=1?(this.tokens-=1,true):false}};var te=["password","passwd","secret","token","apikey","api_key","authorization","auth","cookie","session","credit","card","cvv","ssn"],ne=["token","access_token","apikey","api_key","email","password","code","secret"],A="[Filtered]",se=/[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/g;function N(r){let e=r;for(let t of ne)e=e.replace(new RegExp(`([?&]${t}=)[^&#\\s]*`,"gi"),`$1${A}`);return e}function k(r,e=0){if(e>6)return r;if(typeof r=="string")return N(r).replace(se,A);if(Array.isArray(r))return r.map(t=>k(t,e+1));if(r&&typeof r=="object"){let t={};for(let[n,s]of Object.entries(r)){let o=n.toLowerCase();t[n]=te.some(i=>o.includes(i))?A:k(s,e+1);}return t}return r}function G(r){return {...r,message:N(r.message),context:k(r.context),breadcrumbs:r.breadcrumbs.map(e=>({...e,message:N(e.message),data:e.data?k(e.data):void 0}))}}var C=class{constructor(e,t){this.apiKey=t;this.endpoint=D(e);let n=typeof fetch=="function"?fetch.bind(globalThis):void 0;this.fetchImpl=n??(()=>Promise.reject(new Error("fetch unavailable")));}async send(e,t=false){if(e.length===0)return {ok:true,retryable:false,status:204};try{let n=await this.fetchImpl(this.endpoint,{method:"POST",headers:{"Content-Type":"application/json",Accept:"application/json","X-API-Key":this.apiKey},body:JSON.stringify({errors:e}),keepalive:t,mode:"cors",credentials:"omit"});if(n.status===429)return {ok:!1,retryable:!1,status:429,retryAfterMs:oe(n.headers?.get?.("Retry-After"))};let s=n.status>=500;return {ok:n.ok,retryable:s,status:n.status}}catch{return {ok:false,retryable:true,status:0}}}};function oe(r){if(!r)return 6e4;let t=Number(r);if(Number.isFinite(t)&&t>=0)return Math.min(t*1e3,36e5);let n=Date.parse(r);return Number.isNaN(n)?6e4:Math.min(Math.max(n-Date.now(),0),36e5)}var ie=200;function _(r,e,t){return Math.max(e,Math.min(t,r))}function ae(r){return new Promise(e=>setTimeout(e,r))}var ce=3e3;function ue(r){return {apiKey:r.apiKey,apiUrl:r.apiUrl,environment:r.environment??"production",release:r.release,enabled:r.enabled??true,sampleRate:_(r.sampleRate??1,0,1),maxBatchSize:_(r.maxBatchSize??20,1,100),flushIntervalMs:r.flushIntervalMs??4e3,maxQueueItems:r.maxQueueItems??100,maxRetries:r.maxRetries??3,maxBreadcrumbs:r.maxBreadcrumbs??30,offlineStorage:r.offlineStorage??true,captureUnhandledErrors:r.captureUnhandledErrors??true,captureUnhandledRejections:r.captureUnhandledRejections??true,captureResourceErrors:r.captureResourceErrors??true,captureConsole:r.captureConsole??true,autoBreadcrumbs:r.autoBreadcrumbs??true,denyUrls:r.denyUrls??[],allowUrls:r.allowUrls??[],beforeSend:r.beforeSend,trackPerformance:r.trackPerformance??false,metricsFlushIntervalMs:r.metricsFlushIntervalMs??6e4,debug:r.debug??false}}var R=class{constructor(e){this.buffer=[];this.flushTimer=null;this.processing=false;this.rateLimitedUntil=0;this.discarded={};this.closed=false;this.scope={tags:{}};this.teardown=[];this.config=ue(e),this.transport=new C(this.config.apiUrl,this.config.apiKey),this.deduper=new v(ce),this.limiter=new E,this.queue=new x(this.config.offlineStorage,this.config.maxQueueItems),this.config.trackPerformance&&this.config.enabled&&(this.metrics=new w({apiKey:this.config.apiKey,apiUrl:this.config.apiUrl,flushIntervalMs:this.config.metricsFlushIntervalMs,maxRoutes:ie,debug:this.config.debug}),this.metrics.start()),this.breadcrumbs=new y(this.config.maxBreadcrumbs),this.config.enabled&&(this.config.autoBreadcrumbs&&this.breadcrumbs.install(),this.teardown.push(H(this)),this.installLifecycle(),this.drainOffline());}captureException(e,t){try{this.process(F(K(e)),t?.severity??"Error",t);}catch(n){this.debug("captureException failed",n);}}captureMessage(e,t="Info",n){try{this.process({message:a(e,2e3),exceptionType:"Message"},n?.severity??t,n);}catch(s){this.debug("captureMessage failed",s);}}getDiscardedEvents(){return {...this.discarded}}discard(e,t=1){this.discarded[e]=(this.discarded[e]??0)+t;}addBreadcrumb(e){this.breadcrumbs.add(e);}setUser(e){this.scope.user=e??void 0;}setTag(e,t){this.scope.tags[e]=t;}setContext(e,t){this.scope.tags[e]=t;}close(){this.closed=true,this.flushTimer!==null&&(clearTimeout(this.flushTimer),this.flushTimer=null),this.flush(true),this.metrics?.close(),this.breadcrumbs.close();for(let e of this.teardown.splice(0))try{e();}catch{}}process(e,t,n){if(!this.config.enabled||this.closed)return;if(this.config.sampleRate<1&&Math.random()>this.config.sampleRate){this.discard("sampled");return}let s=`${e.stackTrace??""}
|
|
7
|
+
${u()?location.href:""}`;if(B(s,this.config.denyUrls)){this.discard("filtered");return}if(this.config.allowUrls.length>0&&!B(s,this.config.allowUrls)){this.discard("filtered");return}if(!this.deduper.shouldSend(q(e.message,e.stackTrace))){this.discard("deduped");return}let o={message:e.message,severity:t,timestamp:g(),exceptionType:e.exceptionType,stackTrace:e.stackTrace,innerException:e.innerException,userId:this.scope.user?.id,requestPath:u()?location.pathname:void 0,userAgent:u()?navigator.userAgent:void 0,context:this.buildContext(n),breadcrumbs:this.breadcrumbs.snapshot()};if(o=G(o),this.config.beforeSend){let i=this.config.beforeSend(o);if(!i){this.discard("filtered");return}o=i;}this.enqueue(this.toPayload(o));}buildContext(e){let t={sdk:{name:"cherryboard-web",version:m},environment:this.config.environment};return this.config.release&&(t.release=this.config.release),u()&&(t.url=location.href,document.referrer&&(t.referrer=document.referrer),t.language=navigator.language,t.viewport={width:window.innerWidth,height:window.innerHeight}),this.scope.user&&(t.user=this.scope.user),Object.keys(this.scope.tags).length>0&&(t.tags={...this.scope.tags}),e?.componentStack&&(t.componentStack=e.componentStack),e?.digest&&(t.digest=e.digest),e?.context&&Object.assign(t,e.context),t}toPayload(e){let t=S({...e.context,breadcrumbs:e.breadcrumbs});return t===void 0&&(t=S(e.context)),{message:e.message,stackTrace:e.stackTrace,severity:e.severity,timestamp:e.timestamp,userId:e.userId,requestPath:e.requestPath,userAgent:e.userAgent,exceptionType:e.exceptionType,innerException:e.innerException,metadata:t}}enqueue(e){this.buffer.push(e),this.buffer.length>=this.config.maxBatchSize?this.flush():this.scheduleFlush();}scheduleFlush(){this.closed||this.flushTimer===null&&(this.flushTimer=setTimeout(()=>{this.flushTimer=null,this.flush();},this.config.flushIntervalMs));}async flush(e=false){if(this.flushTimer!==null&&(clearTimeout(this.flushTimer),this.flushTimer=null),this.buffer.length!==0){if(e){if(Date.now()<this.rateLimitedUntil)return;for(;this.buffer.length>0;){let t=this.buffer.splice(0,this.config.maxBatchSize);if(!this.limiter.allow())break;this.transport.send(t,true);}return}if(Date.now()<this.rateLimitedUntil){this.scheduleFlush();return}if(!this.processing){this.processing=true;try{for(;this.buffer.length>0;){let t=this.buffer.splice(0,this.config.maxBatchSize);if(!this.limiter.allow()){this.debug("rate limited; dropping",t.length,"events"),this.discard("rate_limited",t.length);break}if(!await this.deliver(t)){this.queue.push(t);break}}}finally{this.processing=false;}}}}async deliver(e){for(let t=0;t<=this.config.maxRetries;t++){let n=await this.transport.send(e,false);if(n.ok)return true;if(n.status===429)return this.rateLimitedUntil=Date.now()+(n.retryAfterMs??6e4),this.debug("rate limited by server; pausing for",n.retryAfterMs,"ms"),false;if(!n.retryable)return this.debug("non-retryable response",n.status,"\u2014 dropping batch"),this.discard("send_failed",e.length),true;if(t===this.config.maxRetries)return false;await ae(_(2**t*1e3+Math.random()*250,0,15e3));}return false}drainOffline(){let e=this.queue.drain();e.length>0&&(this.buffer.push(...e),this.scheduleFlush());}installLifecycle(){if(!u())return;let e=()=>{document.visibilityState==="hidden"&&this.flush(true);},t=()=>{this.flush(true);},n=()=>this.drainOffline();document.addEventListener("visibilitychange",e),window.addEventListener("pagehide",t),window.addEventListener("online",n),this.teardown.push(()=>{document.removeEventListener("visibilitychange",e),window.removeEventListener("pagehide",t),window.removeEventListener("online",n);});}debug(...e){this.config.debug&&console.warn("[cherryboard]",...e);}};var X="__CHERRYBOARD__";function l(){let r=globalThis,e=r[X];return e||(e={},r[X]=e),e}function L(r){let e=l();return e.client||(e.client=new R(r)),e.client}function $(){return l().client??null}function de(){return l().client!=null}function p(r,e){l().client?.captureException(r,e);}function le(r,e,t){l().client?.captureMessage(r,e,t);}function pe(r){l().client?.addBreadcrumb(r);}function fe(r){l().client?.setUser(r);}function he(r,e){l().client?.setTag(r,e);}function me(r,e){l().client?.setContext(r,e);}function ge(){let r=l().client;return r?r.flush():Promise.resolve()}function ye(r,e,t){l().client?.captureException(r,{severity:"Error",context:{source:"nextjs-server",...e?.path?{requestPath:e.path}:{},...e?.method?{requestMethod:e.method}:{},...t??{}}});}function ve(){return l().client?.getDiscardedEvents()??{}}function be(){let r=l();r.client?.close(),r.client=void 0;}var h=class extends Component{constructor(){super(...arguments);this.state={error:null};this.reset=()=>{this.setState({error:null});};}static getDerivedStateFromError(t){return {error:t}}componentDidCatch(t,n){p(t,{severity:"Error",componentStack:n.componentStack??void 0,context:{source:"react-error-boundary"}}),this.props.onError?.(t,n);}componentDidUpdate(t){this.state.error&&!xe(t.resetKeys,this.props.resetKeys)&&this.reset();}render(){let{error:t}=this.state;if(t){let{fallback:n}=this.props;return typeof n=="function"?n({error:t,reset:this.reset}):n??null}return this.props.children}};function xe(r,e){return r===e?true:!r||!e||r.length!==e.length?false:r.every((t,n)=>Object.is(t,e[n]))}function Ce({config:r,children:e,withBoundary:t=false,fallback:n}){let s=useRef(r);return useEffect(()=>{typeof window<"u"&&L(s.current);},[]),t?jsx(h,{fallback:n,children:e}):jsx(Fragment,{children:e})}function Be(){return $()}function Pe(){return useCallback((r,e)=>p(r,e),[])}function Te(r){p(r,{severity:"Error",digest:r.digest,context:{source:"next-error-boundary"}});}export{Ce as CherryBoardProvider,h as ErrorBoundary,m as SDK_VERSION,pe as addBreadcrumb,p as captureException,le as captureMessage,ye as captureRequestError,Te as captureRouteError,be as close,ge as flush,$ as getClient,ve as getDiscardedEvents,L as init,de as isInitialized,me as setContext,he as setTag,fe as setUser,Pe as useCaptureError,Be as useCherryBoard};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cherrypeak-org/cherryboard-web",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"description": "Browser & React error tracking client for the CherryBoard dashboard (CherryPeak).",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"sideEffects": false,
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
"files": [
|
|
10
10
|
"dist",
|
|
11
11
|
"!dist/**/*.map",
|
|
12
|
+
"!dist/routes.test.mjs",
|
|
12
13
|
"cli",
|
|
13
14
|
"README.md",
|
|
14
15
|
"CHANGELOG.md",
|
|
@@ -35,8 +36,9 @@
|
|
|
35
36
|
"build": "rm -rf dist && tsup && node scripts/postbuild.mjs",
|
|
36
37
|
"dev": "tsup --watch",
|
|
37
38
|
"typecheck": "tsc --noEmit",
|
|
38
|
-
"test": "node test/smoke.mjs && node test/ratelimit.mjs",
|
|
39
|
-
"prepublishOnly": "npm run build"
|
|
39
|
+
"test": "node test/smoke.mjs && node test/ratelimit.mjs && node test/routes.mjs",
|
|
40
|
+
"prepublishOnly": "npm run build",
|
|
41
|
+
"pretest": "esbuild src/core/routes.ts --bundle --format=esm --outfile=dist/routes.test.mjs"
|
|
40
42
|
},
|
|
41
43
|
"peerDependencies": {
|
|
42
44
|
"react": ">=18",
|