@karan171996/metricflow 0.5.2 → 0.6.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/.next/BUILD_ID +1 -1
- package/.next/build-manifest.json +3 -3
- package/.next/fallback-build-manifest.json +3 -3
- package/.next/next-server.js.nft.json +1 -1
- package/.next/prerender-manifest.json +1 -1
- package/.next/server/app/_global-error.html +1 -1
- package/.next/server/app/_global-error.rsc +1 -1
- package/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
- package/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/.next/server/app/_not-found/page.js.nft.json +1 -1
- package/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/.next/server/app/_not-found.html +2 -2
- package/.next/server/app/_not-found.rsc +9 -9
- package/.next/server/app/_not-found.segments/_full.segment.rsc +9 -9
- package/.next/server/app/_not-found.segments/_index.segment.rsc +8 -8
- package/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
- package/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/api/analyze/route.js +1 -1
- package/.next/server/app/api/analyze/route.js.nft.json +1 -1
- package/.next/server/app/api/connect/route.js +2 -2
- package/.next/server/app/api/connect/route.js.nft.json +1 -1
- package/.next/server/app/api/health/route.js.nft.json +1 -1
- package/.next/server/app/api/metrics/route.js +2 -2
- package/.next/server/app/api/metrics/route.js.nft.json +1 -1
- package/.next/server/app/api/settings/route.js +1 -1
- package/.next/server/app/api/settings/route.js.nft.json +1 -1
- package/.next/server/app/api/setup/ai/route.js +1 -1
- package/.next/server/app/api/setup/ai/route.js.nft.json +1 -1
- package/.next/server/app/api/setup/route.js +3 -2
- package/.next/server/app/api/setup/route.js.nft.json +1 -1
- package/.next/server/app/connect/page.js.nft.json +1 -1
- package/.next/server/app/connect/page_client-reference-manifest.js +1 -1
- package/.next/server/app/connect.html +19 -66
- package/.next/server/app/connect.rsc +11 -11
- package/.next/server/app/connect.segments/_full.segment.rsc +11 -11
- package/.next/server/app/connect.segments/_index.segment.rsc +8 -8
- package/.next/server/app/connect.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/connect.segments/connect/__PAGE__.segment.rsc +6 -6
- package/.next/server/app/icon.svg/route.js +1 -1
- package/.next/server/app/icon.svg/route.js.nft.json +1 -1
- package/.next/server/app/index.html +2 -2
- package/.next/server/app/index.rsc +11 -11
- package/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
- package/.next/server/app/index.segments/_full.segment.rsc +11 -11
- package/.next/server/app/index.segments/_index.segment.rsc +8 -8
- package/.next/server/app/index.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/page.js.nft.json +1 -1
- package/.next/server/app/page_client-reference-manifest.js +1 -1
- package/.next/server/app/performance/[slug]/page.js +2 -2
- package/.next/server/app/performance/[slug]/page.js.nft.json +1 -1
- package/.next/server/app/performance/[slug]/page_client-reference-manifest.js +1 -1
- package/.next/server/app/performance/page.js.nft.json +1 -1
- package/.next/server/app/performance/page_client-reference-manifest.js +1 -1
- package/.next/server/app/performance.html +2 -2
- package/.next/server/app/performance.rsc +12 -12
- package/.next/server/app/performance.segments/_full.segment.rsc +12 -12
- package/.next/server/app/performance.segments/_index.segment.rsc +8 -8
- package/.next/server/app/performance.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/performance.segments/performance/__PAGE__.segment.rsc +7 -7
- package/.next/server/app/settings/page.js.nft.json +1 -1
- package/.next/server/app/settings/page_client-reference-manifest.js +1 -1
- package/.next/server/app/settings.html +2 -2
- package/.next/server/app/settings.rsc +20 -20
- package/.next/server/app/settings.segments/_full.segment.rsc +20 -20
- package/.next/server/app/settings.segments/_index.segment.rsc +8 -8
- package/.next/server/app/settings.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/settings.segments/settings/__PAGE__.segment.rsc +16 -16
- package/.next/server/app/setup/page.js.nft.json +1 -1
- package/.next/server/app/setup/page_client-reference-manifest.js +1 -1
- package/.next/server/app/setup.html +2 -2
- package/.next/server/app/setup.rsc +12 -12
- package/.next/server/app/setup.segments/_full.segment.rsc +12 -12
- package/.next/server/app/setup.segments/_index.segment.rsc +8 -8
- package/.next/server/app/setup.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/setup.segments/setup/__PAGE__.segment.rsc +7 -7
- package/.next/server/app/tools/[tool]/page.js +1 -1
- package/.next/server/app/tools/[tool]/page.js.nft.json +1 -1
- package/.next/server/app/tools/[tool]/page_client-reference-manifest.js +1 -1
- package/.next/server/chunks/[externals]__0k9wda_._.js +3 -0
- package/.next/server/chunks/{[externals]__1o8qm3w._.js → [externals]__0lanngu._.js} +1 -1
- package/.next/server/chunks/[root-of-the-server]__0fkj1-q._.js +1 -1
- package/.next/server/chunks/[root-of-the-server]__0o2ceqr._.js +18 -0
- package/.next/server/chunks/[root-of-the-server]__0pil1g6._.js +3 -0
- package/.next/server/chunks/[root-of-the-server]__0xmaf60._.js +18 -0
- package/.next/server/chunks/[root-of-the-server]__18s35pf._.js +3 -0
- package/.next/server/chunks/[root-of-the-server]__1f_fo0j._.js +3 -0
- package/.next/server/chunks/{[root-of-the-server]__1xo4bin._.js → [root-of-the-server]__1naf7j4._.js} +2 -2
- package/.next/server/chunks/[root-of-the-server]__1o9k4jf._.js +10 -0
- package/.next/server/chunks/[root-of-the-server]__1rx5s6r._.js +3 -0
- package/.next/server/chunks/_08xo8nu._.js +1 -1
- package/.next/server/chunks/_0w8wvdn._.js +1 -1
- package/.next/server/chunks/_0wct7-r._.js +1 -16
- package/.next/server/chunks/_1l9d64o._.js +2 -2
- package/.next/server/chunks/ssr/0x0c_next_dist_1icgwks._.js +1 -1
- package/.next/server/chunks/ssr/{[root-of-the-server]__0o_fd5z._.js → [root-of-the-server]__1jr0qvs._.js} +1 -1
- package/.next/server/chunks/ssr/{[root-of-the-server]__1s_ikmb._.js → [root-of-the-server]__1xhr2zy._.js} +1 -1
- package/.next/server/chunks/ssr/_0bxxvzb._.js +1 -1
- package/.next/server/chunks/ssr/_0cq6qjs._.js +9 -63
- package/.next/server/chunks/ssr/_0htqt7_._.js +10 -5
- package/.next/server/chunks/ssr/_13m9nev._.js +1 -1
- package/.next/server/chunks/ssr/_14aoqb2._.js +1 -1
- package/.next/server/functions-config-manifest.json +11 -1
- package/.next/server/middleware/middleware-manifest.json +6 -0
- package/.next/server/middleware-build-manifest.js +3 -3
- package/.next/server/middleware.js +5 -0
- package/.next/server/middleware.js.nft.json +1 -0
- package/.next/server/pages/404.html +2 -2
- package/.next/server/pages/500.html +1 -1
- package/.next/static/cTCo8pLtIDkS2Pw1mc2pP/_clientMiddlewareManifest.js +6 -0
- package/.next/static/chunks/{332-owz6utwp5.js → 05xbzyep2vlp0.js} +1 -1
- package/.next/static/chunks/0icg-e95qmpn-.css +1 -0
- package/.next/static/chunks/1wbaplet994dj.js +11 -0
- package/.next/static/chunks/2jgqio91axkic.js +1 -0
- package/.next/static/chunks/{0_xh0vh4uh0vk.js → 3dncuj6lhk80h.js} +1 -1
- package/.next/trace +1 -1
- package/.next/trace-build +1 -1
- package/README.md +48 -7
- package/bin/cli.mjs +2 -1
- package/dist/browser/index.d.mts +27 -0
- package/dist/browser/index.mjs +110 -0
- package/dist/browser/newrelic-agent.d.mts +4 -0
- package/dist/browser/newrelic-agent.mjs +32 -0
- package/dist/browser/newrelic.d.mts +2 -0
- package/dist/browser/newrelic.mjs +35 -0
- package/dist/browser/sentry-sdk.d.mts +2 -0
- package/dist/browser/sentry-sdk.mjs +22 -0
- package/dist/browser/sentry.d.mts +2 -0
- package/dist/browser/sentry.mjs +45 -0
- package/package.json +14 -3
- package/.next/server/chunks/[root-of-the-server]__00bbqkl._.js +0 -3
- package/.next/server/chunks/[root-of-the-server]__0rfr72x._.js +0 -3
- package/.next/server/chunks/[root-of-the-server]__186pj_c._.js +0 -3
- package/.next/server/chunks/[root-of-the-server]__19yigji._.js +0 -3
- package/.next/server/chunks/[root-of-the-server]__1igrw77._.js +0 -3
- package/.next/static/chunks/2bt9wtnw1g0g_.css +0 -1
- package/.next/static/chunks/39h0f3s2ddxz_.js +0 -65
- package/.next/static/chunks/3ab7oxjuh1asy.js +0 -1
- package/.next/static/wZ6L7QBGYuFD01WhrymMz/_clientMiddlewareManifest.js +0 -1
- /package/.next/static/{wZ6L7QBGYuFD01WhrymMz → cTCo8pLtIDkS2Pw1mc2pP}/_buildManifest.js +0 -0
- /package/.next/static/{wZ6L7QBGYuFD01WhrymMz → cTCo8pLtIDkS2Pw1mc2pP}/_ssgManifest.js +0 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
(globalThis.TURBOPACK||(globalThis.TURBOPACK=[])).push(["object"==typeof document?document.currentScript:void 0,55370,e=>{"use strict";var t=e.i(40798),a=e.i(96341),r=e.i(70065),s=e.i(67881),i=e.i(23750);let n=[{id:"gemini",label:"Google Gemini",help:"Create a key at aistudio.google.com/apikey."},{id:"claude",label:"Anthropic Claude",help:"Create a key at console.anthropic.com > API keys."},{id:"openai",label:"OpenAI",help:"Create a key at platform.openai.com/api-keys."}];e.s(["AiKeyForm",0,function(){let[e,o]=(0,a.useState)(n[0].id),[l,d]=(0,a.useState)(""),[c,u]=(0,a.useState)(null),[h,m]=(0,a.useState)({}),[x,p]=(0,a.useState)(null),[g,f]=(0,a.useState)(!1);async function v(t){t.preventDefault(),f(!0),p(null);try{let t=await fetch("/api/setup/ai",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({provider:e,key:l})}),a=await t.json();a.saved?(d(""),u(e),m({...h,[e]:!0}),p({ok:!0,text:"Saved. AI suggestions now use this provider. No restart needed."})):p({ok:!1,text:a.error??"Nothing was saved."})}catch{p({ok:!1,text:"Could not reach the dashboard server."})}finally{f(!1)}}(0,a.useEffect)(()=>{fetch("/api/setup/ai").then(e=>e.json()).then(e=>{e.keys&&(m(e.keys),u(e.provider),e.provider&&o(e.provider))}).catch(()=>{})},[]);let b=n.find(t=>t.id===e);return(0,t.jsxs)(r.Card,{className:"border-[#2d3748] bg-[#1a202c] shadow-md max-w-2xl mt-6",children:[(0,t.jsxs)(r.CardHeader,{children:[(0,t.jsx)(r.CardTitle,{className:"text-[18px] font-bold text-white tracking-tight",children:"AI suggestions (optional)"}),(0,t.jsx)(r.CardDescription,{className:"text-sm text-gray-400",children:"Add a Gemini, Claude or OpenAI key to get AI insights and recommendations for your pages. Only page names, load times and error counts are sent to the provider you pick. Without a key, the dashboard shows sample suggestions."})]}),(0,t.jsx)(r.CardContent,{children:(0,t.jsxs)("form",{onSubmit:v,className:"flex flex-col gap-5",children:[(0,t.jsxs)("div",{className:"flex flex-col gap-1.5",children:[(0,t.jsx)("label",{htmlFor:"ai-provider",className:"text-sm font-medium text-gray-300",children:"Provider"}),(0,t.jsx)("select",{id:"ai-provider",value:e,onChange:e=>o(e.target.value),className:"h-9 rounded-md border border-[#2d3748] bg-[#0f1419] px-3 text-sm text-gray-200",children:n.map(e=>(0,t.jsxs)("option",{value:e.id,children:[e.label,c===e.id?" (in use)":h[e.id]?" (key saved)":""]},e.id))})]}),(0,t.jsxs)("div",{className:"flex flex-col gap-1.5",children:[(0,t.jsxs)("label",{htmlFor:"ai-key",className:"text-sm font-medium text-gray-300",children:[b.label," API key ",h[e]&&(0,t.jsx)("span",{className:"ml-2 text-xs text-[#3ee0a1]",children:"✓ Set"})]}),(0,t.jsx)(i.Input,{id:"ai-key",type:"password",autoComplete:"off",spellCheck:!1,placeholder:h[e]?"•••••••• (type to replace)":"",value:l,onChange:e=>d(e.target.value),"aria-describedby":"ai-key-help",className:"bg-[#0f1419] border-[#2d3748] text-gray-200 font-mono text-xs"}),(0,t.jsx)("p",{id:"ai-key-help",className:"text-xs text-gray-400",children:b.help})]}),(0,t.jsxs)("div",{className:"flex items-center gap-4",children:[(0,t.jsx)(s.Button,{type:"submit",disabled:g||!l.trim(),children:g?"Checking…":"Check and save"}),(0,t.jsx)("span",{"aria-live":"polite",className:`text-sm ${x?.ok?"text-[#3ee0a1]":"text-[#f87171]"}`,children:x?.text})]})]})})]})}])},4603,e=>{"use strict";var t=e.i(40798),a=e.i(96341),r=e.i(3547),s=e.i(70065),i=e.i(67881),n=e.i(23750),o=e.i(94179),l=e.i(93834);let d=l.TOOL_IDS.flatMap(e=>l.TOOLS[e].keys.required.map(t=>({name:t,tool:e,...l.KEY_FIELDS[t]})));e.s(["SetupForm",0,function(){let[e,c]=(0,a.useState)({}),[u,h]=(0,a.useState)({}),[m,x]=(0,a.useState)({}),[p,g]=(0,a.useState)(null),[f,v]=(0,a.useState)(!1),[b,y]=(0,a.useState)(!1),[k,j]=(0,a.useState)(null),N=(0,a.useRef)(null);async function w(t){t.preventDefault(),v(!0),g(null);try{let t=await fetch("/api/setup",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(e)}),a=await t.json(),r=a.results??{};x(r);let s=d.find(e=>r[e.name]&&!r[e.name].ok);if(s&&setTimeout(()=>N.current?.querySelector(`#${s.name}`)?.focus(),0),a.saved){y(!0),c({});let t=new Set(d.filter(t=>e[t.name]?.trim()).map(e=>e.tool));h(e=>({...e,...Object.fromEntries(d.filter(e=>t.has(e.tool)).map(e=>[e.name,!0]))}));let a=l.TOOL_IDS.filter(e=>t.has(e)||k?.includes(e));j(a),g({ok:!0,text:`Saved. ${a.map(e=>l.TOOLS[e].label).join(" and ")} ${a.length>1?"are":"is"} connected. No restart needed.`})}else g({ok:!1,text:a.error??"Nothing was saved. Fix the fields marked below."})}catch{g({ok:!1,text:"Could not reach the dashboard server."})}finally{v(!1)}}return(0,a.useEffect)(()=>{fetch("/api/setup").then(e=>e.json()).then(e=>{e.keys&&h(e.keys),Array.isArray(e.tools)?j(e.tools):e.error&&g({ok:!1,text:e.error})}).catch(()=>{})},[]),(0,t.jsxs)(s.Card,{className:"border-[#2d3748] bg-[#1a202c] shadow-md max-w-2xl",children:[(0,t.jsxs)(s.CardHeader,{children:[(0,t.jsx)(s.CardTitle,{className:"text-[18px] font-bold text-white tracking-tight",children:"Connect your tools"}),(0,t.jsx)(s.CardDescription,{className:"text-sm text-gray-400",children:"Add keys for the tools you use. You only need one. Keys are checked once, saved to .env.local in this folder, and never shown again."})]}),(0,t.jsx)(s.CardContent,{children:(0,t.jsxs)("form",{ref:N,onSubmit:w,className:"flex flex-col gap-5",children:[l.TOOL_IDS.map(a=>(0,t.jsxs)("fieldset",{id:a,className:"flex flex-col gap-5 rounded-lg border border-[#2d3748] p-4",children:[(0,t.jsxs)("legend",{className:"flex items-center gap-2 px-2 text-sm font-semibold text-white",children:[l.TOOLS[a].label,k?.includes(a)?(0,t.jsx)(o.Badge,{variant:"outline",className:"border-[#3ee0a1] text-[#3ee0a1] bg-[#3ee0a1]/10",children:"✓ Connected"}):(0,t.jsx)(o.Badge,{variant:"outline",className:"border-[#4a5568] text-gray-400",children:"Optional"})]}),d.filter(e=>e.tool===a).map(a=>{let r=m[a.name];return(0,t.jsxs)("div",{className:"flex flex-col gap-1.5",children:[(0,t.jsxs)("label",{htmlFor:a.name,className:"text-sm font-medium text-gray-300",children:[a.label," ",u[a.name]&&(0,t.jsx)("span",{className:"ml-2 text-xs text-[#3ee0a1]",children:"✓ Set"})]}),(0,t.jsx)(n.Input,{id:a.name,name:a.name,type:a.secret?"password":"text",autoComplete:"off",spellCheck:!1,placeholder:u[a.name]?"•••••••• (leave blank to keep, type to replace)":"",value:e[a.name]??"",onChange:t=>c({...e,[a.name]:t.target.value}),"aria-invalid":!!r&&!r.ok||void 0,"aria-describedby":`${a.name}-help ${a.name}-result`,className:"bg-[#0f1419] border-[#2d3748] text-gray-200 font-mono text-xs"}),(0,t.jsx)("p",{id:`${a.name}-help`,className:"text-xs text-gray-400",children:a.help}),(0,t.jsxs)("div",{id:`${a.name}-result`,"aria-live":"polite",children:[r&&!r.ok&&(0,t.jsxs)("p",{className:"text-xs text-[#f87171]",children:["✗ ",r.error]}),r?.ok&&(0,t.jsx)("p",{className:"text-xs text-[#3ee0a1]",children:"✓ Checked"})]})]},a.name)}),l.TOOLS[a].setupNote&&(0,t.jsx)("p",{className:"text-xs text-gray-400",children:l.TOOLS[a].setupNote})]},a)),(0,t.jsxs)("div",{className:"flex items-center gap-4",children:[(0,t.jsx)(i.Button,{type:"submit",disabled:f||!d.some(t=>e[t.name]?.trim()),children:f?"Checking…":"Check and save"}),(0,t.jsx)("span",{"aria-live":"polite",className:`text-sm ${p?.ok?"text-[#3ee0a1]":"text-[#f87171]"}`,children:p?.text}),!d.some(t=>e[t.name]?.trim())&&!p&&!b&&(0,t.jsx)("span",{className:"text-sm text-gray-400",children:k?.length?"Type a new key to change what is saved.":"Fill in both fields for at least one tool."}),b&&(0,t.jsx)(r.default,{href:"/connect",className:(0,i.buttonVariants)({variant:"outline"}),children:"Next: connect your app"})]})]})})]})}])},94179,e=>{"use strict";var t=e.i(46987),a=e.i(10063),r=e.i(94237),s=e.i(28603);let i=(0,r.cva)("group/badge inline-flex h-5 w-fit shrink-0 items-center justify-center gap-1 overflow-hidden rounded-4xl border border-transparent px-2 py-0.5 text-xs font-medium whitespace-nowrap transition-all focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 [&>svg]:pointer-events-none [&>svg]:size-3!",{variants:{variant:{default:"bg-primary text-primary-foreground [a]:hover:bg-primary/80",secondary:"bg-secondary text-secondary-foreground [a]:hover:bg-secondary/80",destructive:"bg-destructive/10 text-destructive focus-visible:ring-destructive/20 dark:bg-destructive/20 dark:focus-visible:ring-destructive/40 [a]:hover:bg-destructive/20",outline:"border-border text-foreground [a]:hover:bg-muted [a]:hover:text-muted-foreground",ghost:"hover:bg-muted hover:text-muted-foreground dark:hover:bg-muted/50",link:"text-primary underline-offset-4 hover:underline"}},defaultVariants:{variant:"default"}});e.s(["Badge",0,function({className:e,variant:r="default",render:n,...o}){return(0,a.useRender)({defaultTagName:"span",props:(0,t.mergeProps)({className:(0,s.cn)(i({variant:r}),e)},o),render:n,state:{slot:"badge",variant:r}})}])},70065,e=>{"use strict";var t=e.i(40798),a=e.i(28603);e.s(["Card",0,function({className:e,size:r="default",...s}){return(0,t.jsx)("div",{"data-slot":"card","data-size":r,className:(0,a.cn)("group/card flex flex-col gap-(--card-spacing) overflow-hidden rounded-xl bg-card py-(--card-spacing) text-sm text-card-foreground ring-1 ring-foreground/10 [--card-spacing:--spacing(4)] has-data-[slot=card-footer]:pb-0 has-[>img:first-child]:pt-0 data-[size=sm]:[--card-spacing:--spacing(3)] data-[size=sm]:has-data-[slot=card-footer]:pb-0 *:[img:first-child]:rounded-t-xl *:[img:last-child]:rounded-b-xl",e),...s})},"CardContent",0,function({className:e,...r}){return(0,t.jsx)("div",{"data-slot":"card-content",className:(0,a.cn)("px-(--card-spacing)",e),...r})},"CardDescription",0,function({className:e,...r}){return(0,t.jsx)("div",{"data-slot":"card-description",className:(0,a.cn)("text-sm text-muted-foreground",e),...r})},"CardHeader",0,function({className:e,...r}){return(0,t.jsx)("div",{"data-slot":"card-header",className:(0,a.cn)("group/card-header @container/card-header grid auto-rows-min items-start gap-1 rounded-t-xl px-(--card-spacing) has-data-[slot=card-action]:grid-cols-[1fr_auto] has-data-[slot=card-description]:grid-rows-[auto_auto] [.border-b]:pb-(--card-spacing)",e),...r})},"CardTitle",0,function({className:e,...r}){return(0,t.jsx)("div",{"data-slot":"card-title",className:(0,a.cn)("font-heading text-base leading-snug font-medium group-data-[size=sm]/card:text-sm",e),...r})}])},91153,e=>{"use strict";var t=e.i(96341);let a=(...e)=>e.filter((e,t,a)=>!!e&&""!==e.trim()&&a.indexOf(e)===t).join(" ").trim(),r={xmlns:"http://www.w3.org/2000/svg",width:24,height:24,viewBox:"0 0 24 24",fill:"none",stroke:"currentColor","stroke-width":2,"stroke-linecap":"round","stroke-linejoin":"round"},s=(0,t.createContext)({}),i=(0,t.forwardRef)(({color:e,size:i,width:n,height:o,strokeWidth:l,absoluteStrokeWidth:d,nonScalingStroke:c,className:u="",children:h,iconNode:m=[],icon:x={node:m,aliases:[],size:24},...p},g)=>{let{size:f=24,strokeWidth:v=2,absoluteStrokeWidth:b=!1,nonScalingStroke:y=!1,color:k="currentColor",className:j=""}=(0,t.useContext)(s)??{},N=!!h||(e=>{for(let t in e)if(t.startsWith("aria-")||"role"===t||"title"===t)return!0;return!1})(p),[w,C,S=[]]=function(e,t={}){return function(e,t={}){let s=t.attributeNames??{},i=e=>s[e]??e,n=e.size??e.width??r.width,o=e.size??e.height??r.height,l=e.aliases?.filter(e=>"string"==typeof e&&""!==e.trim()).map(e=>`lucide-${e}`)??[],d=[...e.name?[`lucide-${e.name}`]:[],...l],c=t.className?.split(" ").filter(Boolean)??[],u=!1===t.includeDefaultClasses?a(...c):a("lucide",...d,...c),h=t.absoluteStrokeWidth?Number(t.strokeWidth??r["stroke-width"])*Number(e.size??e.width??r.width)/Number(t.size??t.width??r.width):t.strokeWidth??r["stroke-width"];return["svg",{...Object.entries(r).reduce((e,[t,a])=>(e[i(t)]=a,e),{}),..."color"in t&&t.color&&{[i("stroke")]:t.color},..."size"in t&&null!=t.size&&{[i("width")]:t.size,[i("height")]:t.size},..."width"in t&&null!=t.width&&{[i("width")]:t.width},..."height"in t&&null!=t.height&&{[i("height")]:t.height},[i("stroke-width")]:h,...u&&{[i("class")]:u},[i("viewBox")]:`0 0 ${n} ${o}`,...!1===t.hasA11yProp?{[i("aria-hidden")]:"true"}:{},..."attributes"in t&&t.attributes},e.node.map(e=>{let[a,r,s]=e,n=t.nonScalingStroke?{[i("vector-effect")]:"non-scaling-stroke",...r}:r;return s?[a,n,s]:[a,n]})]}(e,{...t,attributeNames:{...t.attributeNames,class:"className","stroke-width":"strokeWidth","stroke-linecap":"strokeLinecap","stroke-linejoin":"strokeLinejoin","vector-effect":"vectorEffect"}})}(x,{color:e??k,width:n??i??f,height:o??i??f,strokeWidth:l??v,absoluteStrokeWidth:d??b,nonScalingStroke:c??y,className:a(j,u),hasA11yProp:N,attributes:p});return(0,t.createElement)(w,{ref:g,...C},[...S.map(([e,a])=>(0,t.createElement)(e,a)),...Array.isArray(h)?h:[h]])});e.s(["default",0,i],91153)}]);
|
|
1
|
+
(globalThis.TURBOPACK||(globalThis.TURBOPACK=[])).push(["object"==typeof document?document.currentScript:void 0,55370,e=>{"use strict";var t=e.i(40798),a=e.i(96341),r=e.i(70065),s=e.i(67881),i=e.i(23750);let n=[{id:"gemini",label:"Google Gemini",help:"Create a key at aistudio.google.com/apikey."},{id:"claude",label:"Anthropic Claude",help:"Create a key at console.anthropic.com > API keys."},{id:"openai",label:"OpenAI",help:"Create a key at platform.openai.com/api-keys."}];e.s(["AiKeyForm",0,function(){let[e,o]=(0,a.useState)(n[0].id),[l,d]=(0,a.useState)(""),[c,u]=(0,a.useState)(null),[h,m]=(0,a.useState)({}),[x,p]=(0,a.useState)(null),[g,f]=(0,a.useState)(!1);async function v(t){t.preventDefault(),f(!0),p(null);try{let t=await fetch("/api/setup/ai",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({provider:e,key:l})}),a=await t.json();a.saved?(d(""),u(e),m({...h,[e]:!0}),p({ok:!0,text:"Saved. AI suggestions now use this provider. No restart needed."})):p({ok:!1,text:a.error??"Nothing was saved."})}catch{p({ok:!1,text:"Could not reach the dashboard server."})}finally{f(!1)}}(0,a.useEffect)(()=>{fetch("/api/setup/ai").then(e=>e.json()).then(e=>{e.keys&&(m(e.keys),u(e.provider),e.provider&&o(e.provider))}).catch(()=>{})},[]);let b=n.find(t=>t.id===e);return(0,t.jsxs)(r.Card,{className:"border-[#2d3748] bg-[#1a202c] shadow-md max-w-2xl mt-6",children:[(0,t.jsxs)(r.CardHeader,{children:[(0,t.jsx)(r.CardTitle,{className:"text-[18px] font-bold text-white tracking-tight",children:"AI suggestions (optional)"}),(0,t.jsx)(r.CardDescription,{className:"text-sm text-gray-400",children:"Add a Gemini, Claude or OpenAI key to get AI insights and recommendations for your pages. Only page names, load times and error counts are sent to the provider you pick. Without a key, the dashboard shows sample suggestions."})]}),(0,t.jsx)(r.CardContent,{children:(0,t.jsxs)("form",{onSubmit:v,className:"flex flex-col gap-5",children:[(0,t.jsxs)("div",{className:"flex flex-col gap-1.5",children:[(0,t.jsx)("label",{htmlFor:"ai-provider",className:"text-sm font-medium text-gray-300",children:"Provider"}),(0,t.jsx)("select",{id:"ai-provider",value:e,onChange:e=>o(e.target.value),className:"h-9 rounded-md border border-[#2d3748] bg-[#0f1419] px-3 text-sm text-gray-200",children:n.map(e=>(0,t.jsxs)("option",{value:e.id,children:[e.label,c===e.id?" (in use)":h[e.id]?" (key saved)":""]},e.id))})]}),(0,t.jsxs)("div",{className:"flex flex-col gap-1.5",children:[(0,t.jsxs)("label",{htmlFor:"ai-key",className:"text-sm font-medium text-gray-300",children:[b.label," API key ",h[e]&&(0,t.jsx)("span",{className:"ml-2 text-xs text-[#3ee0a1]",children:"✓ Set"})]}),(0,t.jsx)(i.Input,{id:"ai-key",type:"password",autoComplete:"off",spellCheck:!1,placeholder:h[e]?"•••••••• (type to replace)":"",value:l,onChange:e=>d(e.target.value),"aria-describedby":"ai-key-help",className:"bg-[#0f1419] border-[#2d3748] text-gray-200 font-mono text-xs"}),(0,t.jsx)("p",{id:"ai-key-help",className:"text-xs text-gray-400",children:b.help})]}),(0,t.jsxs)("div",{className:"flex items-center gap-4",children:[(0,t.jsx)(s.Button,{type:"submit",disabled:g||!l.trim(),children:g?"Checking…":"Check and save"}),(0,t.jsx)("span",{"aria-live":"polite",className:`text-sm ${x?.ok?"text-[#3ee0a1]":"text-[#f87171]"}`,children:x?.text})]})]})})]})}])},4603,e=>{"use strict";var t=e.i(40798),a=e.i(96341),r=e.i(3547),s=e.i(70065),i=e.i(67881),n=e.i(23750),o=e.i(94179),l=e.i(93834);let d=l.TOOL_IDS.flatMap(e=>l.TOOLS[e].keys.required.map(t=>({name:t,tool:e,...l.KEY_FIELDS[t]})));e.s(["SetupForm",0,function(){let[e,c]=(0,a.useState)({}),[u,h]=(0,a.useState)({}),[m,x]=(0,a.useState)({}),[p,g]=(0,a.useState)(null),[f,v]=(0,a.useState)(!1),[b,y]=(0,a.useState)(!1),[k,j]=(0,a.useState)(null),N=(0,a.useRef)(null);async function w(t){t.preventDefault(),v(!0),g(null);try{let t=await fetch("/api/setup",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(e)}),a=await t.json(),r=a.results??{};x(r);let s=d.find(e=>r[e.name]&&!r[e.name].ok);if(s&&setTimeout(()=>N.current?.querySelector(`#${s.name}`)?.focus(),0),a.saved){y(!0),c({});let t=new Set(d.filter(t=>e[t.name]?.trim()).map(e=>e.tool));h(e=>({...e,...Object.fromEntries(d.filter(e=>t.has(e.tool)).map(e=>[e.name,!0]))}));let a=l.TOOL_IDS.filter(e=>t.has(e)||k?.includes(e));j(a),g({ok:!0,text:`Saved. ${a.map(e=>l.TOOLS[e].label).join(" and ")} ${a.length>1?"are":"is"} connected. No restart needed.`})}else g({ok:!1,text:a.error??"Nothing was saved. Fix the fields marked below."})}catch{g({ok:!1,text:"Could not reach the dashboard server."})}finally{v(!1)}}return(0,a.useEffect)(()=>{fetch("/api/setup").then(e=>e.json()).then(e=>{e.keys&&h(e.keys),Array.isArray(e.tools)?j(e.tools):e.error&&g({ok:!1,text:e.error})}).catch(()=>{})},[]),(0,t.jsxs)(s.Card,{className:"border-[#2d3748] bg-[#1a202c] shadow-md max-w-2xl",children:[(0,t.jsxs)(s.CardHeader,{children:[(0,t.jsx)(s.CardTitle,{className:"text-[18px] font-bold text-white tracking-tight",children:"Connect your tools"}),(0,t.jsx)(s.CardDescription,{className:"text-sm text-gray-400",children:"Add keys for the tools you use. You only need one. Keys are checked once, saved to .env.local in this folder, and never shown again."})]}),(0,t.jsx)(s.CardContent,{children:(0,t.jsxs)("form",{ref:N,onSubmit:w,className:"flex flex-col gap-5",children:[l.TOOL_IDS.map(a=>(0,t.jsxs)("fieldset",{id:a,className:"flex flex-col gap-5 rounded-lg border border-[#2d3748] p-4",children:[(0,t.jsxs)("legend",{className:"flex items-center gap-2 px-2 text-sm font-semibold text-white",children:[l.TOOLS[a].label,k?.includes(a)?(0,t.jsx)(o.Badge,{variant:"outline",className:"border-[#3ee0a1] text-[#3ee0a1] bg-[#3ee0a1]/10",children:"✓ Connected"}):(0,t.jsx)(o.Badge,{variant:"outline",className:"border-[#4a5568] text-gray-400",children:"Optional"})]}),d.filter(e=>e.tool===a).map(a=>{let r=m[a.name];return(0,t.jsxs)("div",{className:"flex flex-col gap-1.5",children:[(0,t.jsxs)("label",{htmlFor:a.name,className:"text-sm font-medium text-gray-300",children:[a.label," ",u[a.name]&&(0,t.jsx)("span",{className:"ml-2 text-xs text-[#3ee0a1]",children:"✓ Set"})]}),(0,t.jsx)(n.Input,{id:a.name,name:a.name,type:a.secret?"password":"text",autoComplete:"off",spellCheck:!1,placeholder:u[a.name]?"•••••••• (leave blank to keep, type to replace)":"",value:e[a.name]??"",onChange:t=>c({...e,[a.name]:t.target.value}),"aria-invalid":!!r&&!r.ok||void 0,"aria-describedby":`${a.name}-help ${a.name}-result`,className:"bg-[#0f1419] border-[#2d3748] text-gray-200 font-mono text-xs"}),(0,t.jsx)("p",{id:`${a.name}-help`,className:"text-xs text-gray-400",children:a.help}),(0,t.jsxs)("div",{id:`${a.name}-result`,"aria-live":"polite",children:[r&&!r.ok&&(0,t.jsxs)("p",{className:"text-xs text-[#f87171]",children:["✗ ",r.error]}),r?.ok&&(0,t.jsx)("p",{className:"text-xs text-[#3ee0a1]",children:"✓ Checked"}),r?.notice&&(0,t.jsx)("p",{className:"text-xs text-[#fbbf24]",children:r.notice})]})]},a.name)}),l.TOOLS[a].setupNote&&(0,t.jsx)("p",{className:"text-xs text-gray-400",children:l.TOOLS[a].setupNote})]},a)),(0,t.jsxs)("div",{className:"flex items-center gap-4",children:[(0,t.jsx)(i.Button,{type:"submit",disabled:f||!d.some(t=>e[t.name]?.trim()),children:f?"Checking…":"Check and save"}),(0,t.jsx)("span",{"aria-live":"polite",className:`text-sm ${p?.ok?"text-[#3ee0a1]":"text-[#f87171]"}`,children:p?.text}),!d.some(t=>e[t.name]?.trim())&&!p&&!b&&(0,t.jsx)("span",{className:"text-sm text-gray-400",children:k?.length?"Type a new key to change what is saved.":"Fill in both fields for at least one tool."}),b&&(0,t.jsx)(r.default,{href:"/connect",className:(0,i.buttonVariants)({variant:"outline"}),children:"Next: connect your app"})]})]})})]})}])},94179,e=>{"use strict";var t=e.i(46987),a=e.i(10063),r=e.i(94237),s=e.i(28603);let i=(0,r.cva)("group/badge inline-flex h-5 w-fit shrink-0 items-center justify-center gap-1 overflow-hidden rounded-4xl border border-transparent px-2 py-0.5 text-xs font-medium whitespace-nowrap transition-all focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 [&>svg]:pointer-events-none [&>svg]:size-3!",{variants:{variant:{default:"bg-primary text-primary-foreground [a]:hover:bg-primary/80",secondary:"bg-secondary text-secondary-foreground [a]:hover:bg-secondary/80",destructive:"bg-destructive/10 text-destructive focus-visible:ring-destructive/20 dark:bg-destructive/20 dark:focus-visible:ring-destructive/40 [a]:hover:bg-destructive/20",outline:"border-border text-foreground [a]:hover:bg-muted [a]:hover:text-muted-foreground",ghost:"hover:bg-muted hover:text-muted-foreground dark:hover:bg-muted/50",link:"text-primary underline-offset-4 hover:underline"}},defaultVariants:{variant:"default"}});e.s(["Badge",0,function({className:e,variant:r="default",render:n,...o}){return(0,a.useRender)({defaultTagName:"span",props:(0,t.mergeProps)({className:(0,s.cn)(i({variant:r}),e)},o),render:n,state:{slot:"badge",variant:r}})}])},70065,e=>{"use strict";var t=e.i(40798),a=e.i(28603);e.s(["Card",0,function({className:e,size:r="default",...s}){return(0,t.jsx)("div",{"data-slot":"card","data-size":r,className:(0,a.cn)("group/card flex flex-col gap-(--card-spacing) overflow-hidden rounded-xl bg-card py-(--card-spacing) text-sm text-card-foreground ring-1 ring-foreground/10 [--card-spacing:--spacing(4)] has-data-[slot=card-footer]:pb-0 has-[>img:first-child]:pt-0 data-[size=sm]:[--card-spacing:--spacing(3)] data-[size=sm]:has-data-[slot=card-footer]:pb-0 *:[img:first-child]:rounded-t-xl *:[img:last-child]:rounded-b-xl",e),...s})},"CardContent",0,function({className:e,...r}){return(0,t.jsx)("div",{"data-slot":"card-content",className:(0,a.cn)("px-(--card-spacing)",e),...r})},"CardDescription",0,function({className:e,...r}){return(0,t.jsx)("div",{"data-slot":"card-description",className:(0,a.cn)("text-sm text-muted-foreground",e),...r})},"CardHeader",0,function({className:e,...r}){return(0,t.jsx)("div",{"data-slot":"card-header",className:(0,a.cn)("group/card-header @container/card-header grid auto-rows-min items-start gap-1 rounded-t-xl px-(--card-spacing) has-data-[slot=card-action]:grid-cols-[1fr_auto] has-data-[slot=card-description]:grid-rows-[auto_auto] [.border-b]:pb-(--card-spacing)",e),...r})},"CardTitle",0,function({className:e,...r}){return(0,t.jsx)("div",{"data-slot":"card-title",className:(0,a.cn)("font-heading text-base leading-snug font-medium group-data-[size=sm]/card:text-sm",e),...r})}])},91153,e=>{"use strict";var t=e.i(96341);let a=(...e)=>e.filter((e,t,a)=>!!e&&""!==e.trim()&&a.indexOf(e)===t).join(" ").trim(),r={xmlns:"http://www.w3.org/2000/svg",width:24,height:24,viewBox:"0 0 24 24",fill:"none",stroke:"currentColor","stroke-width":2,"stroke-linecap":"round","stroke-linejoin":"round"},s=(0,t.createContext)({}),i=(0,t.forwardRef)(({color:e,size:i,width:n,height:o,strokeWidth:l,absoluteStrokeWidth:d,nonScalingStroke:c,className:u="",children:h,iconNode:m=[],icon:x={node:m,aliases:[],size:24},...p},g)=>{let{size:f=24,strokeWidth:v=2,absoluteStrokeWidth:b=!1,nonScalingStroke:y=!1,color:k="currentColor",className:j=""}=(0,t.useContext)(s)??{},N=!!h||(e=>{for(let t in e)if(t.startsWith("aria-")||"role"===t||"title"===t)return!0;return!1})(p),[w,C,S=[]]=function(e,t={}){return function(e,t={}){let s=t.attributeNames??{},i=e=>s[e]??e,n=e.size??e.width??r.width,o=e.size??e.height??r.height,l=e.aliases?.filter(e=>"string"==typeof e&&""!==e.trim()).map(e=>`lucide-${e}`)??[],d=[...e.name?[`lucide-${e.name}`]:[],...l],c=t.className?.split(" ").filter(Boolean)??[],u=!1===t.includeDefaultClasses?a(...c):a("lucide",...d,...c),h=t.absoluteStrokeWidth?Number(t.strokeWidth??r["stroke-width"])*Number(e.size??e.width??r.width)/Number(t.size??t.width??r.width):t.strokeWidth??r["stroke-width"];return["svg",{...Object.entries(r).reduce((e,[t,a])=>(e[i(t)]=a,e),{}),..."color"in t&&t.color&&{[i("stroke")]:t.color},..."size"in t&&null!=t.size&&{[i("width")]:t.size,[i("height")]:t.size},..."width"in t&&null!=t.width&&{[i("width")]:t.width},..."height"in t&&null!=t.height&&{[i("height")]:t.height},[i("stroke-width")]:h,...u&&{[i("class")]:u},[i("viewBox")]:`0 0 ${n} ${o}`,...!1===t.hasA11yProp?{[i("aria-hidden")]:"true"}:{},..."attributes"in t&&t.attributes},e.node.map(e=>{let[a,r,s]=e,n=t.nonScalingStroke?{[i("vector-effect")]:"non-scaling-stroke",...r}:r;return s?[a,n,s]:[a,n]})]}(e,{...t,attributeNames:{...t.attributeNames,class:"className","stroke-width":"strokeWidth","stroke-linecap":"strokeLinecap","stroke-linejoin":"strokeLinejoin","vector-effect":"vectorEffect"}})}(x,{color:e??k,width:n??i??f,height:o??i??f,strokeWidth:l??v,absoluteStrokeWidth:d??b,nonScalingStroke:c??y,className:a(j,u),hasA11yProp:N,attributes:p});return(0,t.createElement)(w,{ref:g,...C},[...S.map(([e,a])=>(0,t.createElement)(e,a)),...Array.isArray(h)?h:[h]])});e.s(["default",0,i],91153)}]);
|
package/.next/trace
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
[{"name":"generate-buildid","duration":223,"timestamp":194218851877,"id":4,"parentId":1,"tags":{},"startTime":1791457255164,"traceId":"51980e2c0a9951ad"},{"name":"load-custom-routes","duration":358,"timestamp":194218852207,"id":5,"parentId":1,"tags":{},"startTime":1791457255165,"traceId":"51980e2c0a9951ad"},{"name":"create-dist-dir","duration":192,"timestamp":194218852587,"id":6,"parentId":1,"tags":{},"startTime":1791457255165,"traceId":"51980e2c0a9951ad"},{"name":"clean","duration":62088,"timestamp":194218853351,"id":7,"parentId":1,"tags":{},"startTime":1791457255166,"traceId":"51980e2c0a9951ad"},{"name":"discover-routes","duration":6242,"timestamp":194218923980,"id":8,"parentId":1,"tags":{},"startTime":1791457255236,"traceId":"51980e2c0a9951ad"},{"name":"create-root-mapping","duration":59,"timestamp":194218930256,"id":9,"parentId":1,"tags":{},"startTime":1791457255243,"traceId":"51980e2c0a9951ad"},{"name":"generate-route-types","duration":16792,"timestamp":194218931602,"id":10,"parentId":1,"tags":{},"startTime":1791457255244,"traceId":"51980e2c0a9951ad"},{"name":"public-dir-conflict-check","duration":98,"timestamp":194218948466,"id":11,"parentId":1,"tags":{},"startTime":1791457255261,"traceId":"51980e2c0a9951ad"},{"name":"generate-routes-manifest","duration":2641,"timestamp":194218948672,"id":12,"parentId":1,"tags":{},"startTime":1791457255261,"traceId":"51980e2c0a9951ad"},{"name":"run-turbopack","duration":3620987,"timestamp":194218960749,"id":14,"parentId":1,"tags":{},"startTime":1791457255273,"traceId":"51980e2c0a9951ad"},{"name":"turbopack-build-events","duration":72,"timestamp":194219323428,"id":15,"parentId":1,"tags":{},"startTime":1791457255636,"traceId":"51980e2c0a9951ad"},{"name":"turbopack-persistence","duration":279000,"timestamp":194222024004,"id":16,"parentId":15,"tags":{"reason":"stop","snapshot_duration_ms":8.965435000000001,"persist_duration_ms":270.17936100000003,"task_count":4088,"bytes_written":21466291,"bytes_deleted":0},"startTime":1791457258615,"traceId":"51980e2c0a9951ad"},{"name":"run-typescript","duration":4063415,"timestamp":194222584533,"id":16,"parentId":1,"tags":{},"startTime":1791457258897,"traceId":"51980e2c0a9951ad"},{"name":"generate-required-server-files","duration":835,"timestamp":194226648623,"id":18,"parentId":1,"tags":{},"startTime":1791457262961,"traceId":"51980e2c0a9951ad"},{"name":"check-static-error-page","duration":9245,"timestamp":194226669467,"id":20,"parentId":19,"tags":{},"startTime":1791457262982,"traceId":"51980e2c0a9951ad"},{"name":"is-page-static","duration":840746,"timestamp":194226680026,"id":40,"parentId":22,"tags":{},"startTime":1791457262993,"traceId":"51980e2c0a9951ad"},{"name":"check-page","duration":848959,"timestamp":194226671886,"id":22,"parentId":19,"tags":{"page":"/_global-error"},"startTime":1791457262984,"traceId":"51980e2c0a9951ad"},{"name":"is-page-static","duration":1096523,"timestamp":194226685068,"id":50,"parentId":31,"tags":{},"startTime":1791457262998,"traceId":"51980e2c0a9951ad"},{"name":"check-page","duration":1108531,"timestamp":194226673105,"id":31,"parentId":19,"tags":{"page":"/connect"},"startTime":1791457262986,"traceId":"51980e2c0a9951ad"},{"name":"is-page-static","duration":1100232,"timestamp":194226685087,"id":51,"parentId":33,"tags":{},"startTime":1791457262998,"traceId":"51980e2c0a9951ad"},{"name":"check-page","duration":1111956,"timestamp":194226673404,"id":33,"parentId":19,"tags":{"page":"/"},"startTime":1791457262986,"traceId":"51980e2c0a9951ad"},{"name":"is-page-static","duration":1104396,"timestamp":194226685101,"id":52,"parentId":35,"tags":{},"startTime":1791457262998,"traceId":"51980e2c0a9951ad"},{"name":"check-page","duration":1111319,"timestamp":194226678219,"id":35,"parentId":19,"tags":{"page":"/performance"},"startTime":1791457262991,"traceId":"51980e2c0a9951ad"},{"name":"is-page-static","duration":1111947,"timestamp":194226679769,"id":39,"parentId":21,"tags":{},"startTime":1791457262992,"traceId":"51980e2c0a9951ad"},{"name":"check-page","duration":1121353,"timestamp":194226670401,"id":21,"parentId":19,"tags":{"page":"/_not-found"},"startTime":1791457262983,"traceId":"51980e2c0a9951ad"},{"name":"is-page-static","duration":1118292,"timestamp":194226680484,"id":48,"parentId":30,"tags":{},"startTime":1791457262993,"traceId":"51980e2c0a9951ad"},{"name":"check-page","duration":1125819,"timestamp":194226673001,"id":30,"parentId":19,"tags":{"page":"/api/timings"},"startTime":1791457262986,"traceId":"51980e2c0a9951ad"},{"name":"is-page-static","duration":1114426,"timestamp":194226685109,"id":53,"parentId":36,"tags":{},"startTime":1791457262998,"traceId":"51980e2c0a9951ad"},{"name":"check-page","duration":1121229,"timestamp":194226678342,"id":36,"parentId":19,"tags":{"page":"/settings"},"startTime":1791457262991,"traceId":"51980e2c0a9951ad"},{"name":"is-page-static","duration":1131809,"timestamp":194226680193,"id":43,"parentId":25,"tags":{},"startTime":1791457262993,"traceId":"51980e2c0a9951ad"},{"name":"check-page","duration":1139630,"timestamp":194226672413,"id":25,"parentId":19,"tags":{"page":"/api/health"},"startTime":1791457262985,"traceId":"51980e2c0a9951ad"},{"name":"is-page-static","duration":1137118,"timestamp":194226680299,"id":45,"parentId":27,"tags":{},"startTime":1791457262993,"traceId":"51980e2c0a9951ad"},{"name":"check-page","duration":1144824,"timestamp":194226672650,"id":27,"parentId":19,"tags":{"page":"/api/settings"},"startTime":1791457262985,"traceId":"51980e2c0a9951ad"},{"name":"is-page-static","duration":1152034,"timestamp":194226680093,"id":41,"parentId":23,"tags":{},"startTime":1791457262993,"traceId":"51980e2c0a9951ad"},{"name":"check-page","duration":1160027,"timestamp":194226672161,"id":23,"parentId":19,"tags":{"page":"/api/analyze"},"startTime":1791457262985,"traceId":"51980e2c0a9951ad"},{"name":"is-page-static","duration":1152284,"timestamp":194226680540,"id":49,"parentId":32,"tags":{},"startTime":1791457262993,"traceId":"51980e2c0a9951ad"},{"name":"check-page","duration":1159571,"timestamp":194226673281,"id":32,"parentId":19,"tags":{"page":"/icon.svg"},"startTime":1791457262986,"traceId":"51980e2c0a9951ad"},{"name":"is-page-static","duration":1155970,"timestamp":194226680361,"id":46,"parentId":28,"tags":{},"startTime":1791457262993,"traceId":"51980e2c0a9951ad"},{"name":"check-page","duration":1163618,"timestamp":194226672746,"id":28,"parentId":19,"tags":{"page":"/api/setup/ai"},"startTime":1791457262985,"traceId":"51980e2c0a9951ad"},{"name":"is-page-static","duration":1151399,"timestamp":194226685116,"id":54,"parentId":37,"tags":{},"startTime":1791457262998,"traceId":"51980e2c0a9951ad"},{"name":"check-page","duration":1158087,"timestamp":194226678441,"id":37,"parentId":19,"tags":{"page":"/setup"},"startTime":1791457262991,"traceId":"51980e2c0a9951ad"},{"name":"is-page-static","duration":1154001,"timestamp":194226686590,"id":56,"parentId":38,"tags":{},"startTime":1791457262999,"traceId":"51980e2c0a9951ad"},{"name":"check-page","duration":1162091,"timestamp":194226678534,"id":38,"parentId":19,"tags":{"page":"/tools/[tool]"},"startTime":1791457262991,"traceId":"51980e2c0a9951ad"},{"name":"is-page-static","duration":1169773,"timestamp":194226680145,"id":42,"parentId":24,"tags":{},"startTime":1791457262993,"traceId":"51980e2c0a9951ad"},{"name":"check-page","duration":1177662,"timestamp":194226672289,"id":24,"parentId":19,"tags":{"page":"/api/connect"},"startTime":1791457262985,"traceId":"51980e2c0a9951ad"},{"name":"is-page-static","duration":1174212,"timestamp":194226680242,"id":44,"parentId":26,"tags":{},"startTime":1791457262993,"traceId":"51980e2c0a9951ad"},{"name":"check-page","duration":1181951,"timestamp":194226672527,"id":26,"parentId":19,"tags":{"page":"/api/metrics"},"startTime":1791457262985,"traceId":"51980e2c0a9951ad"},{"name":"is-page-static","duration":1174093,"timestamp":194226680432,"id":47,"parentId":29,"tags":{},"startTime":1791457262993,"traceId":"51980e2c0a9951ad"},{"name":"check-page","duration":1181686,"timestamp":194226672854,"id":29,"parentId":19,"tags":{"page":"/api/setup"},"startTime":1791457262985,"traceId":"51980e2c0a9951ad"},{"name":"is-page-static","duration":1196811,"timestamp":194226686004,"id":55,"parentId":34,"tags":{},"startTime":1791457262999,"traceId":"51980e2c0a9951ad"},{"name":"check-page","duration":1208136,"timestamp":194226674700,"id":34,"parentId":19,"tags":{"page":"/performance/[slug]"},"startTime":1791457262987,"traceId":"51980e2c0a9951ad"},{"name":"static-check","duration":1214965,"timestamp":194226667892,"id":19,"parentId":1,"tags":{},"startTime":1791457262980,"traceId":"51980e2c0a9951ad"},{"name":"write-routes-manifest","duration":406,"timestamp":194227883492,"id":58,"parentId":1,"tags":{},"startTime":1791457264196,"traceId":"51980e2c0a9951ad"},{"name":"load-dotenv","duration":26,"timestamp":194227892204,"id":61,"parentId":60,"tags":{},"startTime":1791457264205,"traceId":"51980e2c0a9951ad"},{"name":"run-export-path-map","duration":363,"timestamp":194227893387,"id":62,"parentId":60,"tags":{},"startTime":1791457264206,"traceId":"51980e2c0a9951ad"},{"name":"next-export","duration":464868,"timestamp":194227891442,"id":60,"parentId":1,"tags":{},"startTime":1791457264204,"traceId":"51980e2c0a9951ad"},{"name":"move-exported-app-not-found-","duration":800,"timestamp":194228358650,"id":63,"parentId":59,"tags":{},"startTime":1791457264671,"traceId":"51980e2c0a9951ad"},{"name":"move-exported-app-global-error-","duration":772,"timestamp":194228359526,"id":64,"parentId":59,"tags":{},"startTime":1791457264672,"traceId":"51980e2c0a9951ad"},{"name":"static-generation","duration":474857,"timestamp":194227885835,"id":59,"parentId":1,"tags":{},"startTime":1791457264198,"traceId":"51980e2c0a9951ad"},{"name":"write-routes-manifest","duration":324,"timestamp":194228360895,"id":65,"parentId":1,"tags":{},"startTime":1791457264673,"traceId":"51980e2c0a9951ad"},{"name":"print-tree-view","duration":2499,"timestamp":194228400949,"id":66,"parentId":1,"tags":{},"startTime":1791457264714,"traceId":"51980e2c0a9951ad"},{"name":"write-route-bundle-stats","duration":7102,"timestamp":194228403462,"id":67,"parentId":1,"tags":{},"startTime":1791457264716,"traceId":"51980e2c0a9951ad"},{"name":"telemetry-flush","duration":452727,"timestamp":194228410579,"id":68,"parentId":1,"tags":{},"startTime":1791457264723,"traceId":"51980e2c0a9951ad"},{"name":"next-build","duration":10110952,"timestamp":194218752371,"id":1,"tags":{"buildMode":"default","version":"16.3.8","bundler":"turbopack","has-custom-webpack-config":"false","use-build-worker":"true"},"startTime":1791457255065,"traceId":"51980e2c0a9951ad"}]
|
|
1
|
+
[{"name":"generate-buildid","duration":195,"timestamp":225574729878,"id":4,"parentId":1,"tags":{},"startTime":1791488611532,"traceId":"d667950cb0b8d0fa"},{"name":"load-custom-routes","duration":358,"timestamp":225574730182,"id":5,"parentId":1,"tags":{},"startTime":1791488611532,"traceId":"d667950cb0b8d0fa"},{"name":"create-dist-dir","duration":197,"timestamp":225574730561,"id":6,"parentId":1,"tags":{},"startTime":1791488611533,"traceId":"d667950cb0b8d0fa"},{"name":"clean","duration":70352,"timestamp":225574731438,"id":7,"parentId":1,"tags":{},"startTime":1791488611534,"traceId":"d667950cb0b8d0fa"},{"name":"discover-routes","duration":6459,"timestamp":225574810483,"id":8,"parentId":1,"tags":{},"startTime":1791488611613,"traceId":"d667950cb0b8d0fa"},{"name":"create-root-mapping","duration":105,"timestamp":225574816980,"id":9,"parentId":1,"tags":{},"startTime":1791488611619,"traceId":"d667950cb0b8d0fa"},{"name":"generate-route-types","duration":18668,"timestamp":225574818636,"id":10,"parentId":1,"tags":{},"startTime":1791488611621,"traceId":"d667950cb0b8d0fa"},{"name":"public-dir-conflict-check","duration":91,"timestamp":225574837378,"id":11,"parentId":1,"tags":{},"startTime":1791488611639,"traceId":"d667950cb0b8d0fa"},{"name":"generate-routes-manifest","duration":2802,"timestamp":225574837580,"id":12,"parentId":1,"tags":{},"startTime":1791488611640,"traceId":"d667950cb0b8d0fa"},{"name":"run-turbopack","duration":2461637,"timestamp":225574844912,"id":14,"parentId":1,"tags":{},"startTime":1791488611647,"traceId":"d667950cb0b8d0fa"},{"name":"turbopack-build-events","duration":89,"timestamp":225575219629,"id":15,"parentId":1,"tags":{},"startTime":1791488612022,"traceId":"d667950cb0b8d0fa"},{"name":"turbopack-persistence","duration":132000,"timestamp":225576771967,"id":16,"parentId":15,"tags":{"reason":"stop","snapshot_duration_ms":3.495223,"persist_duration_ms":128.436506,"task_count":1010,"bytes_written":1061108,"bytes_deleted":0},"startTime":1791488613708,"traceId":"d667950cb0b8d0fa"},{"name":"run-typescript","duration":3376122,"timestamp":225577311004,"id":18,"parentId":1,"tags":{},"startTime":1791488614113,"traceId":"d667950cb0b8d0fa"},{"name":"generate-required-server-files","duration":903,"timestamp":225580687784,"id":20,"parentId":1,"tags":{},"startTime":1791488617490,"traceId":"d667950cb0b8d0fa"},{"name":"check-static-error-page","duration":12632,"timestamp":225580713918,"id":22,"parentId":21,"tags":{},"startTime":1791488617516,"traceId":"d667950cb0b8d0fa"},{"name":"is-page-static","duration":921749,"timestamp":225580734428,"id":42,"parentId":24,"tags":{},"startTime":1791488617537,"traceId":"d667950cb0b8d0fa"},{"name":"check-page","duration":939692,"timestamp":225580716610,"id":24,"parentId":21,"tags":{"page":"/_global-error"},"startTime":1791488617519,"traceId":"d667950cb0b8d0fa"},{"name":"is-page-static","duration":1159079,"timestamp":225580734709,"id":46,"parentId":28,"tags":{},"startTime":1791488617537,"traceId":"d667950cb0b8d0fa"},{"name":"check-page","duration":1176579,"timestamp":225580717251,"id":28,"parentId":21,"tags":{"page":"/api/metrics"},"startTime":1791488617520,"traceId":"d667950cb0b8d0fa"},{"name":"is-page-static","duration":1218907,"timestamp":225580738566,"id":53,"parentId":35,"tags":{},"startTime":1791488617541,"traceId":"d667950cb0b8d0fa"},{"name":"check-page","duration":1235827,"timestamp":225580721686,"id":35,"parentId":21,"tags":{"page":"/"},"startTime":1791488617524,"traceId":"d667950cb0b8d0fa"},{"name":"is-page-static","duration":1266431,"timestamp":225580738553,"id":52,"parentId":33,"tags":{},"startTime":1791488617541,"traceId":"d667950cb0b8d0fa"},{"name":"check-page","duration":1283563,"timestamp":225580721464,"id":33,"parentId":21,"tags":{"page":"/connect"},"startTime":1791488617524,"traceId":"d667950cb0b8d0fa"},{"name":"is-page-static","duration":1299488,"timestamp":225580738585,"id":55,"parentId":38,"tags":{},"startTime":1791488617541,"traceId":"d667950cb0b8d0fa"},{"name":"check-page","duration":1311929,"timestamp":225580726193,"id":38,"parentId":21,"tags":{"page":"/settings"},"startTime":1791488617528,"traceId":"d667950cb0b8d0fa"},{"name":"is-page-static","duration":1306361,"timestamp":225580738591,"id":56,"parentId":39,"tags":{},"startTime":1791488617541,"traceId":"d667950cb0b8d0fa"},{"name":"check-page","duration":1318714,"timestamp":225580726290,"id":39,"parentId":21,"tags":{"page":"/setup"},"startTime":1791488617529,"traceId":"d667950cb0b8d0fa"},{"name":"is-page-static","duration":1326280,"timestamp":225580734654,"id":45,"parentId":27,"tags":{},"startTime":1791488617537,"traceId":"d667950cb0b8d0fa"},{"name":"check-page","duration":1343853,"timestamp":225580717133,"id":27,"parentId":21,"tags":{"page":"/api/health"},"startTime":1791488617519,"traceId":"d667950cb0b8d0fa"},{"name":"is-page-static","duration":1334372,"timestamp":225580735054,"id":50,"parentId":32,"tags":{},"startTime":1791488617537,"traceId":"d667950cb0b8d0fa"},{"name":"check-page","duration":1348119,"timestamp":225580721348,"id":32,"parentId":21,"tags":{"page":"/api/timings"},"startTime":1791488617524,"traceId":"d667950cb0b8d0fa"},{"name":"is-page-static","duration":1331153,"timestamp":225580738578,"id":54,"parentId":37,"tags":{},"startTime":1791488617541,"traceId":"d667950cb0b8d0fa"},{"name":"check-page","duration":1343693,"timestamp":225580726076,"id":37,"parentId":21,"tags":{"page":"/performance"},"startTime":1791488617528,"traceId":"d667950cb0b8d0fa"},{"name":"is-page-static","duration":1356377,"timestamp":225580734099,"id":41,"parentId":23,"tags":{},"startTime":1791488617536,"traceId":"d667950cb0b8d0fa"},{"name":"check-page","duration":1375685,"timestamp":225580714852,"id":23,"parentId":21,"tags":{"page":"/_not-found"},"startTime":1791488617517,"traceId":"d667950cb0b8d0fa"},{"name":"is-page-static","duration":1372132,"timestamp":225580734766,"id":47,"parentId":29,"tags":{},"startTime":1791488617537,"traceId":"d667950cb0b8d0fa"},{"name":"check-page","duration":1386016,"timestamp":225580720920,"id":29,"parentId":21,"tags":{"page":"/api/settings"},"startTime":1791488617523,"traceId":"d667950cb0b8d0fa"},{"name":"is-page-static","duration":1379312,"timestamp":225580735108,"id":51,"parentId":34,"tags":{},"startTime":1791488617537,"traceId":"d667950cb0b8d0fa"},{"name":"check-page","duration":1392863,"timestamp":225580721586,"id":34,"parentId":21,"tags":{"page":"/icon.svg"},"startTime":1791488617524,"traceId":"d667950cb0b8d0fa"},{"name":"is-page-static","duration":1391141,"timestamp":225580734919,"id":48,"parentId":30,"tags":{},"startTime":1791488617537,"traceId":"d667950cb0b8d0fa"},{"name":"check-page","duration":1404995,"timestamp":225580721094,"id":30,"parentId":21,"tags":{"page":"/api/setup/ai"},"startTime":1791488617523,"traceId":"d667950cb0b8d0fa"},{"name":"is-page-static","duration":1394169,"timestamp":225580734494,"id":43,"parentId":25,"tags":{},"startTime":1791488617537,"traceId":"d667950cb0b8d0fa"},{"name":"check-page","duration":1411780,"timestamp":225580716910,"id":25,"parentId":21,"tags":{"page":"/api/analyze"},"startTime":1791488617519,"traceId":"d667950cb0b8d0fa"},{"name":"is-page-static","duration":1397227,"timestamp":225580734541,"id":44,"parentId":26,"tags":{},"startTime":1791488617537,"traceId":"d667950cb0b8d0fa"},{"name":"check-page","duration":1414770,"timestamp":225580717028,"id":26,"parentId":21,"tags":{"page":"/api/connect"},"startTime":1791488617519,"traceId":"d667950cb0b8d0fa"},{"name":"is-page-static","duration":1394265,"timestamp":225580739378,"id":57,"parentId":36,"tags":{},"startTime":1791488617542,"traceId":"d667950cb0b8d0fa"},{"name":"check-page","duration":1411861,"timestamp":225580721809,"id":36,"parentId":21,"tags":{"page":"/performance/[slug]"},"startTime":1791488617524,"traceId":"d667950cb0b8d0fa"},{"name":"is-page-static","duration":1403370,"timestamp":225580734992,"id":49,"parentId":31,"tags":{},"startTime":1791488617537,"traceId":"d667950cb0b8d0fa"},{"name":"check-page","duration":1417188,"timestamp":225580721213,"id":31,"parentId":21,"tags":{"page":"/api/setup"},"startTime":1791488617523,"traceId":"d667950cb0b8d0fa"},{"name":"is-page-static","duration":1408600,"timestamp":225580739893,"id":58,"parentId":40,"tags":{},"startTime":1791488617542,"traceId":"d667950cb0b8d0fa"},{"name":"check-page","duration":1422148,"timestamp":225580726375,"id":40,"parentId":21,"tags":{"page":"/tools/[tool]"},"startTime":1791488617529,"traceId":"d667950cb0b8d0fa"},{"name":"static-check","duration":1435105,"timestamp":225580713444,"id":21,"parentId":1,"tags":{},"startTime":1791488617516,"traceId":"d667950cb0b8d0fa"},{"name":"write-routes-manifest","duration":563,"timestamp":225582152538,"id":60,"parentId":1,"tags":{},"startTime":1791488618955,"traceId":"d667950cb0b8d0fa"},{"name":"load-dotenv","duration":36,"timestamp":225582163227,"id":63,"parentId":62,"tags":{},"startTime":1791488618966,"traceId":"d667950cb0b8d0fa"},{"name":"run-export-path-map","duration":763,"timestamp":225582173166,"id":64,"parentId":62,"tags":{},"startTime":1791488618975,"traceId":"d667950cb0b8d0fa"},{"name":"next-export","duration":565282,"timestamp":225582162201,"id":62,"parentId":1,"tags":{},"startTime":1791488618964,"traceId":"d667950cb0b8d0fa"},{"name":"move-exported-app-not-found-","duration":885,"timestamp":225582729509,"id":65,"parentId":61,"tags":{},"startTime":1791488619532,"traceId":"d667950cb0b8d0fa"},{"name":"move-exported-app-global-error-","duration":657,"timestamp":225582730465,"id":66,"parentId":61,"tags":{},"startTime":1791488619533,"traceId":"d667950cb0b8d0fa"},{"name":"static-generation","duration":576449,"timestamp":225582154941,"id":61,"parentId":1,"tags":{},"startTime":1791488618957,"traceId":"d667950cb0b8d0fa"},{"name":"write-routes-manifest","duration":263,"timestamp":225582731530,"id":67,"parentId":1,"tags":{},"startTime":1791488619534,"traceId":"d667950cb0b8d0fa"},{"name":"print-tree-view","duration":2766,"timestamp":225582793552,"id":68,"parentId":1,"tags":{},"startTime":1791488619596,"traceId":"d667950cb0b8d0fa"},{"name":"write-route-bundle-stats","duration":7697,"timestamp":225582796335,"id":69,"parentId":1,"tags":{},"startTime":1791488619599,"traceId":"d667950cb0b8d0fa"},{"name":"telemetry-flush","duration":468791,"timestamp":225582804048,"id":70,"parentId":1,"tags":{},"startTime":1791488619606,"traceId":"d667950cb0b8d0fa"},{"name":"next-build","duration":8658056,"timestamp":225574614817,"id":1,"tags":{"buildMode":"default","version":"16.3.8","bundler":"turbopack","has-custom-webpack-config":"false","use-build-worker":"true"},"startTime":1791488611417,"traceId":"d667950cb0b8d0fa"}]
|
package/.next/trace-build
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
[{"name":"run-turbopack","duration":
|
|
1
|
+
[{"name":"run-turbopack","duration":2461637,"timestamp":225574844912,"id":14,"parentId":1,"tags":{},"startTime":1791488611647,"traceId":"d667950cb0b8d0fa"},{"name":"turbopack-build-events","duration":89,"timestamp":225575219629,"id":15,"parentId":1,"tags":{},"startTime":1791488612022,"traceId":"d667950cb0b8d0fa"},{"name":"turbopack-persistence","duration":132000,"timestamp":225576771967,"id":16,"parentId":15,"tags":{"reason":"stop","snapshot_duration_ms":3.495223,"persist_duration_ms":128.436506,"task_count":1010,"bytes_written":1061108,"bytes_deleted":0},"startTime":1791488613708,"traceId":"d667950cb0b8d0fa"},{"name":"run-typescript","duration":3376122,"timestamp":225577311004,"id":18,"parentId":1,"tags":{},"startTime":1791488614113,"traceId":"d667950cb0b8d0fa"},{"name":"static-check","duration":1435105,"timestamp":225580713444,"id":21,"parentId":1,"tags":{},"startTime":1791488617516,"traceId":"d667950cb0b8d0fa"},{"name":"static-generation","duration":576449,"timestamp":225582154941,"id":61,"parentId":1,"tags":{},"startTime":1791488618957,"traceId":"d667950cb0b8d0fa"},{"name":"telemetry-flush","duration":468791,"timestamp":225582804048,"id":70,"parentId":1,"tags":{},"startTime":1791488619606,"traceId":"d667950cb0b8d0fa"},{"name":"next-build","duration":8658056,"timestamp":225574614817,"id":1,"tags":{"buildMode":"default","version":"16.3.8","bundler":"turbopack","has-custom-webpack-config":"false","use-build-worker":"true"},"startTime":1791488611417,"traceId":"d667950cb0b8d0fa"}]
|
package/README.md
CHANGED
|
@@ -64,7 +64,7 @@ You need five values. Where to find each one:
|
|
|
64
64
|
| Sentry **organization slug** | The name in your Sentry URL: `sentry.io/organizations/<slug>/`. |
|
|
65
65
|
| Sentry **project slug** | Sentry → Project settings. |
|
|
66
66
|
|
|
67
|
-
Two more values are needed only to send data *from* your site (the
|
|
67
|
+
Two more values are needed only to send data *from* your site (the `init` call shown on `/connect`, which fills them in when it can read them):
|
|
68
68
|
|
|
69
69
|
| Value | Where to find it |
|
|
70
70
|
| --- | --- |
|
|
@@ -91,20 +91,60 @@ Save them in one of two ways:
|
|
|
91
91
|
|
|
92
92
|
### 3. Send data from your site
|
|
93
93
|
|
|
94
|
-
MetricFlow only reads data. Your website has to send it to New Relic and Sentry first. After saving your keys, click **Next: connect your app** (or open `/connect`). That page gives you copy
|
|
94
|
+
MetricFlow only reads data. Your website has to send it to New Relic and Sentry first. After saving your keys, click **Next: connect your app** (or open `/connect`). That page gives you one snippet to copy: a single `init` call with a block for each tool you connected, already filled in with the public values the dashboard could read (your Sentry DSN, your New Relic Browser key, application ID and account ID). Anything it could not read is shown as an example marked `replace`.
|
|
95
95
|
|
|
96
|
-
|
|
97
|
-
2. **`emitMetric()` helper** (optional): sends your own numbers through the Browser agent.
|
|
98
|
-
3. **Sentry**: sends JavaScript errors. Use your project's DSN.
|
|
96
|
+
Add it to your site, deploy or run it, and open a few pages.
|
|
99
97
|
|
|
100
|
-
|
|
98
|
+
#### One install, one call
|
|
99
|
+
|
|
100
|
+
Your site does not have to install `@sentry/browser` or `@newrelic/browser-agent`. MetricFlow ships both and loads each one only when you ask for it:
|
|
101
|
+
|
|
102
|
+
```bash
|
|
103
|
+
npm i @karan171996/metricflow
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
```ts
|
|
107
|
+
// instrumentation-client.ts (Next.js 15.3 or newer; in any other app, a file that runs only in the browser)
|
|
108
|
+
import { init } from '@karan171996/metricflow/browser';
|
|
109
|
+
|
|
110
|
+
init({
|
|
111
|
+
sentry: { dsn: 'https://0123456789abcdef0123456789abcdef@o123.ingest.sentry.io/456' },
|
|
112
|
+
'new-relic': { browserKey: 'NRJS-fake0000', applicationId: '123456789', accountId: '1234567' },
|
|
113
|
+
});
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
Name only the tools you use. Call `init` once. It never throws, and it does nothing during server rendering or a build.
|
|
117
|
+
|
|
118
|
+
| Option | Value |
|
|
119
|
+
|---|---|
|
|
120
|
+
| `sentry.dsn` | Sentry > Project settings > Client Keys (DSN) |
|
|
121
|
+
| `sentry.tracesSampleRate` | Optional, 0 to 1. Default: `1` on `localhost` and `127.0.0.1`, `0.1` everywhere else |
|
|
122
|
+
| `'new-relic'.browserKey` | The **Ingest - Browser** key. It starts with `NRJS-` |
|
|
123
|
+
| `'new-relic'.applicationId` | The Browser app's application ID (a number, as a string) |
|
|
124
|
+
| `'new-relic'.accountId` | Your account ID (a number, as a string) |
|
|
125
|
+
| `'new-relic'.region` | Optional: `'us'` (default) or `'eu'` |
|
|
126
|
+
|
|
127
|
+
To record your own numbers (New Relic only): `import { send } from '@karan171996/metricflow/browser'; send('checkout_step', 2, { step: 'payment' });`. Before `init`, `send` does nothing.
|
|
128
|
+
|
|
129
|
+
Things to know:
|
|
130
|
+
|
|
131
|
+
- **Public identifiers only.** Everything you pass to `init` is published in your site's JavaScript. If `init` sees a secret (a New Relic `NRAK-` User key or any other non-browser New Relic key, a Sentry auth token, a legacy DSN with a secret in it), it starts nothing and prints one console warning. That key has already been published: revoke it and create a new one.
|
|
132
|
+
- **One bad value starts no tool.** The console warning names the tool and the reason, never the value.
|
|
133
|
+
- **You already run Sentry or New Relic.** MetricFlow leaves it alone and says so once in the console. For Sentry, call MetricFlow's `init` after your own Sentry has started; if yours starts later, MetricFlow cannot see it and the page reports twice.
|
|
134
|
+
- **Content Security Policy.** Add these hosts to `connect-src`: the host in your Sentry DSN (for example `o123.ingest.sentry.io`), and `bam.nr-data.net` for New Relic (`bam.eu01.nr-data.net` for an EU account). The vendor code itself is bundled with your site, so `script-src` needs nothing new.
|
|
135
|
+
- **Page names are real paths.** Sentry page loads are named by `location.pathname` (`/orders/8841`, not `/orders/[id]`), so the dashboard can match them. Any ID or token that sits in a path is sent to Sentry.
|
|
136
|
+
- **Privacy defaults, and what is still sent.** MetricFlow turns off what the dashboard does not read, but both tools still collect data about your visitors. Check it against your own privacy notice.
|
|
137
|
+
- **Sentry sends:** JavaScript errors with stack traces; breadcrumbs leading up to an error (console output, the selector of clicked elements, fetch/XHR URLs, page navigations); the page URL, referrer and user agent; the browser's language and time zone; one session record per page load; and performance spans for page loads, in-app navigations, web vitals, fetch/XHR requests and loaded resources. It adds `sentry-trace` and `baggage` headers to requests to your own origin. **Off:** `sendDefaultPii` is `false`, and session replay, the feedback widget and profiling are not included.
|
|
138
|
+
- **New Relic sends:** page views with the page URL (query string and hash removed); load timing and web vitals, with the selector of the element behind LCP and INP; JavaScript errors with stack traces; and for each fetch/XHR request the host, path, method, status, size and timing (no bodies, no headers, no query strings). **Off:** cookies and session storage, session replay and session trace (not included at all), click/keyboard "user action" events, Content-Security-Policy violation events, and tracing headers on your requests.
|
|
139
|
+
- **One audit finding you will see.** `npm audit` reports a moderate advisory, GHSA-px8p-9vwx-vf98, in `fflate`, which comes with New Relic's agent. It does not apply here: the affected function, `unzipSync`, is never called. The agent uses `fflate` only inside its session-replay feature, which MetricFlow's build leaves out. It clears when New Relic ships `fflate` 0.8.3 or later.
|
|
140
|
+
- **Bundler needed.** The entry is ESM only and uses `import()`, which webpack, Turbopack, Vite and Rollup split into separate files: a site that never calls `init` downloads no vendor code, and a New Relic-only site never downloads Sentry.
|
|
101
141
|
|
|
102
142
|
### 4. Check that metrics arrive
|
|
103
143
|
|
|
104
144
|
On `/connect`, the **Events received** panel shows a row for each source:
|
|
105
145
|
|
|
106
146
|
- **Browser agent (page views)**
|
|
107
|
-
- **Custom events (
|
|
147
|
+
- **Custom events (send / test event)**
|
|
108
148
|
- **Sentry errors**
|
|
109
149
|
|
|
110
150
|
Reload a few pages of your site and watch the rows turn green. New Relic can take a minute or two to show new data.
|
|
@@ -228,6 +268,7 @@ pnpm send-test-data # one dummy event to Sentry and New Relic
|
|
|
228
268
|
app/ Next.js App Router: pages and api/ route handlers
|
|
229
269
|
components/ UI, one folder per feature; ui/ is generated shadcn code
|
|
230
270
|
lib/ Data fetching (New Relic, Sentry), transforms, hooks
|
|
271
|
+
browser/ What a consumer site imports (init, send). Sealed: imports nothing from lib/
|
|
231
272
|
bin/ CLI (cli.mjs) and output formatter (output.mjs)
|
|
232
273
|
scripts/ send-test-data.mjs, test-env.mjs
|
|
233
274
|
test/ node:test suites
|
package/bin/cli.mjs
CHANGED
|
@@ -60,7 +60,8 @@ const server = spawn(process.execPath, [nextBin, "start", "-p", port, "-H", host
|
|
|
60
60
|
METRICFLOW_PROJECT_NAME: projectName, // set it yourself to override the title
|
|
61
61
|
METRICFLOW_PROJECT_DIR: process.cwd(), // server cwd is the package; writes (.env.local, settings) belong here
|
|
62
62
|
...process.env,
|
|
63
|
-
|
|
63
|
+
// app's isLocalRequest honors this. On loopback it is emptied, so a METRICFLOW_EXPOSED left in the user's shell cannot switch the API guard off.
|
|
64
|
+
METRICFLOW_EXPOSED: loopback ? "" : "1",
|
|
64
65
|
...(process.argv.includes("--no-color") || wantsJson ? { FORCE_COLOR: undefined, NO_COLOR: "1" } : {}),
|
|
65
66
|
},
|
|
66
67
|
});
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export type Attrs = Record<string, string | number | boolean>;
|
|
2
|
+
export interface SentryOptions {
|
|
3
|
+
dsn: string;
|
|
4
|
+
tracesSampleRate?: number;
|
|
5
|
+
}
|
|
6
|
+
export interface NewRelicOptions {
|
|
7
|
+
browserKey: string;
|
|
8
|
+
applicationId: string;
|
|
9
|
+
accountId: string;
|
|
10
|
+
region?: 'us' | 'eu';
|
|
11
|
+
}
|
|
12
|
+
/** Public identifiers only. A value that looks like a secret is refused and no tool starts. */
|
|
13
|
+
export interface InitOptions {
|
|
14
|
+
sentry?: SentryOptions;
|
|
15
|
+
'new-relic'?: NewRelicOptions;
|
|
16
|
+
}
|
|
17
|
+
export interface BrowserTool<O> {
|
|
18
|
+
/** Allow-list. Returns a fixed reason string if a value is malformed. Runs before any vendor code loads. */
|
|
19
|
+
check(options: O): string | null;
|
|
20
|
+
/** May throw; index catches and warns once. */
|
|
21
|
+
init(options: O): Promise<void>;
|
|
22
|
+
send(name: string, value: number, attrs: Attrs): void;
|
|
23
|
+
}
|
|
24
|
+
/** Starts the named tools. Call once, in the browser. Never throws, never rejects. No-op without `window`. */
|
|
25
|
+
export declare function init(options: InitOptions): Promise<void>;
|
|
26
|
+
/** Records a custom event on every started tool that can take it. No-op before init. Never throws. */
|
|
27
|
+
export declare function send(name: string, value: number, attrs?: Attrs): void;
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
// Consumer entry point, published as the "./browser" export of this package (init and send).
|
|
2
|
+
// This tree is sealed (tsconfig.browser.json, test/browser-entry.test.mjs): it imports nothing
|
|
3
|
+
// outside browser/, reads no environment and has no static import of a vendor or tool module,
|
|
4
|
+
// so a site that never calls init downloads no vendor code.
|
|
5
|
+
const LABELS = { sentry: 'Sentry', 'new-relic': 'New Relic' };
|
|
6
|
+
const LOADERS = {
|
|
7
|
+
sentry: () => import('./sentry.mjs'),
|
|
8
|
+
'new-relic': () => import('./newrelic.mjs'),
|
|
9
|
+
};
|
|
10
|
+
// Secrets that must never reach a browser. Tested against every string in the options and
|
|
11
|
+
// against each part of it split on URL delimiters, so a token in a DSN's user part is caught.
|
|
12
|
+
const DENY = [
|
|
13
|
+
/^NR(?!JS-)[A-Z]{2}-/i, // every New Relic key prefix except the public browser key
|
|
14
|
+
/^sntry[a-z0-9]?_/i, // every Sentry token prefix (sntrys_ org, sntryu_ user, and any later kind)
|
|
15
|
+
/NRAL$/i, // New Relic ingest licence key
|
|
16
|
+
/^[a-f0-9]{64}$/i, // legacy Sentry auth token
|
|
17
|
+
];
|
|
18
|
+
// Not a known secret format, but shaped like a key: 40 or more key characters in one piece (a 40-hex
|
|
19
|
+
// legacy New Relic licence key, for one). Only consulted once a value has already failed a tool's
|
|
20
|
+
// allow-list, so a valid 32-hex DSN key or NRJS- key never gets here.
|
|
21
|
+
const KEY_SHAPED = [/^(?!NRJS-)[a-z0-9_-]{40,}$/i];
|
|
22
|
+
function hasSecret(value, rules = DENY, depth = 0) {
|
|
23
|
+
if (typeof value === 'string')
|
|
24
|
+
return [value, ...value.split(/[:/@?#&=\s]+/)].some(part => rules.some(rule => rule.test(part)));
|
|
25
|
+
if (value !== null && typeof value === 'object' && depth < 4)
|
|
26
|
+
return Object.values(value).some(v => hasSecret(v, rules, depth + 1));
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
29
|
+
// Every warning is a fixed string. Nothing caught and no option value is ever passed to console.
|
|
30
|
+
const warn = (text) => {
|
|
31
|
+
try {
|
|
32
|
+
console.warn(`MetricFlow: ${text}`);
|
|
33
|
+
}
|
|
34
|
+
catch { /* a broken console must not break the site */ }
|
|
35
|
+
};
|
|
36
|
+
let called = false;
|
|
37
|
+
let warnedAgain = false;
|
|
38
|
+
const started = [];
|
|
39
|
+
/** Starts the named tools. Call once, in the browser. Never throws, never rejects. No-op without `window`. */
|
|
40
|
+
export async function init(options) {
|
|
41
|
+
try {
|
|
42
|
+
if (typeof window === 'undefined')
|
|
43
|
+
return;
|
|
44
|
+
if (called) {
|
|
45
|
+
if (!warnedAgain)
|
|
46
|
+
warn('init was already called; this call did nothing.');
|
|
47
|
+
warnedAgain = true;
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
called = true;
|
|
51
|
+
if (options === null || typeof options !== 'object')
|
|
52
|
+
return warn('init needs an options object. No tool was started.');
|
|
53
|
+
// 1. Deny rules, over everything that was passed, before any other module is requested.
|
|
54
|
+
for (const [key, value] of Object.entries(options)) {
|
|
55
|
+
if (hasSecret(value)) {
|
|
56
|
+
const label = Object.hasOwn(LABELS, key) ? LABELS[key] : 'an unknown tool'; // never the caller's own text
|
|
57
|
+
return warn(`a secret key was passed to init for ${label}. Nothing was sent. This key is already in your site's public JavaScript: revoke it now and create a new one.`);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
// A mistyped tool name ("newrelic") must not fail silently. The key itself is the caller's text: never printed.
|
|
61
|
+
if (Object.keys(options).some(key => !Object.hasOwn(LABELS, key)))
|
|
62
|
+
return warn('init was given an option that is not a tool. The tools are "sentry" and "new-relic". No tool was started.');
|
|
63
|
+
// 2. Load only the named tools' own (small) modules. Vendor code is not requested yet.
|
|
64
|
+
const ids = Object.keys(LABELS).filter(id => options[id] !== undefined);
|
|
65
|
+
if (ids.length === 0)
|
|
66
|
+
return warn('init was not given a tool. Name "sentry", "new-relic" or both. No tool was started.');
|
|
67
|
+
const loaded = [];
|
|
68
|
+
await Promise.all(ids.map(async (id) => {
|
|
69
|
+
try {
|
|
70
|
+
loaded.push({ id, tool: (await LOADERS[id]()).tool });
|
|
71
|
+
}
|
|
72
|
+
catch {
|
|
73
|
+
warn(`${LABELS[id]} could not be loaded. Your site is not affected.`);
|
|
74
|
+
}
|
|
75
|
+
}));
|
|
76
|
+
// 3. Allow-list. One bad value starts no tool at all.
|
|
77
|
+
for (const { id, tool } of loaded) {
|
|
78
|
+
const reason = tool.check(options[id]);
|
|
79
|
+
if (!reason)
|
|
80
|
+
continue;
|
|
81
|
+
if (hasSecret(options[id], KEY_SHAPED))
|
|
82
|
+
return warn(`${LABELS[id]} was not started: a value passed to init looks like a secret key, not a public identifier. If it is one, it is already in your site's public JavaScript: revoke it now and create a new one. No tool was started.`);
|
|
83
|
+
return warn(`${LABELS[id]} was not started: ${reason} No tool was started.`);
|
|
84
|
+
}
|
|
85
|
+
// 4. Start in parallel; one failing does not stop the other or the page.
|
|
86
|
+
await Promise.all(loaded.map(async ({ id, tool }) => {
|
|
87
|
+
try {
|
|
88
|
+
await tool.init(options[id]);
|
|
89
|
+
started.push(tool);
|
|
90
|
+
}
|
|
91
|
+
catch {
|
|
92
|
+
warn(`${LABELS[id]} could not be started. Your site is not affected.`);
|
|
93
|
+
}
|
|
94
|
+
}));
|
|
95
|
+
}
|
|
96
|
+
catch {
|
|
97
|
+
warn('init failed. Your site is not affected.');
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
/** Records a custom event on every started tool that can take it. No-op before init. Never throws. */
|
|
101
|
+
export function send(name, value, attrs = {}) {
|
|
102
|
+
if (typeof name !== 'string' || typeof value !== 'number')
|
|
103
|
+
return;
|
|
104
|
+
for (const tool of started) {
|
|
105
|
+
try {
|
|
106
|
+
tool.send(name, value, attrs);
|
|
107
|
+
}
|
|
108
|
+
catch { /* never break the caller */ }
|
|
109
|
+
}
|
|
110
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
// The only file that names @newrelic/browser-agent. newrelic.mts loads it lazily, after its own checks.
|
|
2
|
+
// The agent is composed from only the features the dashboard reads, instead of the full BrowserAgent:
|
|
3
|
+
// session replay, session trace, logging and soft navigation are never bundled.
|
|
4
|
+
// Static imports on purpose: one lazy chunk group. Six separate dynamic imports made the consumer's
|
|
5
|
+
// bundler repeat shared code and download more than the full agent.
|
|
6
|
+
import { Agent } from '@newrelic/browser-agent/loaders/agent';
|
|
7
|
+
import { Ajax } from '@newrelic/browser-agent/features/ajax';
|
|
8
|
+
import { GenericEvents } from '@newrelic/browser-agent/features/generic_events'; // recordCustomEvent
|
|
9
|
+
import { JSErrors } from '@newrelic/browser-agent/features/jserrors';
|
|
10
|
+
import { PageViewEvent } from '@newrelic/browser-agent/features/page_view_event';
|
|
11
|
+
import { PageViewTiming } from '@newrelic/browser-agent/features/page_view_timing'; // web vitals
|
|
12
|
+
export function start(browserKey, applicationId, accountId, beacon) {
|
|
13
|
+
return new Agent({
|
|
14
|
+
features: [Ajax, GenericEvents, JSErrors, PageViewEvent, PageViewTiming],
|
|
15
|
+
init: {
|
|
16
|
+
// Off: it adds newrelic/traceparent headers to the site's own requests, and the dashboard reads no traces.
|
|
17
|
+
// AJAX events are recorded either way; only their trace and span IDs go.
|
|
18
|
+
distributed_tracing: { enabled: false },
|
|
19
|
+
session_replay: { enabled: false }, // also absent from the bundle; stated for the day the feature list changes
|
|
20
|
+
privacy: { cookies_enabled: false },
|
|
21
|
+
// GenericEvents is here for recordCustomEvent only. Its two automatic sources are on by default
|
|
22
|
+
// and the dashboard reads neither, so both are turned off:
|
|
23
|
+
user_actions: { enabled: false }, // clicks, key presses, copy/paste, focus, with the target's selector, id and class
|
|
24
|
+
feature_flags: ['no_spv'], // Content-Security-Policy violation events (blocked and document URLs)
|
|
25
|
+
// Must stay on: with every source off the agent never loads the code behind recordCustomEvent.
|
|
26
|
+
// On its own it collects nothing; it only enables the addPageAction call.
|
|
27
|
+
page_action: { enabled: true },
|
|
28
|
+
},
|
|
29
|
+
info: { beacon, errorBeacon: beacon, licenseKey: browserKey, applicationID: applicationId, sa: 1 },
|
|
30
|
+
loader_config: { accountID: accountId, trustKey: accountId, agentID: applicationId, licenseKey: browserKey, applicationID: applicationId },
|
|
31
|
+
});
|
|
32
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
const BAD_KEY = 'browserKey must be the "Ingest - Browser" key, which starts with NRJS-. Copy the key value, not the ID shown beside it.';
|
|
2
|
+
const BAD_ID = 'applicationId and accountId must be plain numbers, passed as strings.';
|
|
3
|
+
const BAD_REGION = 'region must be "us" or "eu".';
|
|
4
|
+
const BAD_OPTION = 'only "browserKey", "applicationId", "accountId" and "region" are accepted.';
|
|
5
|
+
const EXISTING = 'MetricFlow: New Relic is already running on this page; MetricFlow left it as it is.';
|
|
6
|
+
const ALLOWED = ['browserKey', 'applicationId', 'accountId', 'region'];
|
|
7
|
+
// The EU host is from New Relic's documentation; it has not been tried against an EU account.
|
|
8
|
+
const BEACONS = { us: 'bam.nr-data.net', eu: 'bam.eu01.nr-data.net' };
|
|
9
|
+
function check(options) {
|
|
10
|
+
if (options === null || typeof options !== 'object')
|
|
11
|
+
return BAD_OPTION;
|
|
12
|
+
if (Object.keys(options).some(k => !ALLOWED.includes(k)))
|
|
13
|
+
return BAD_OPTION;
|
|
14
|
+
const { browserKey, applicationId, accountId, region } = options;
|
|
15
|
+
if (typeof browserKey !== 'string' || !/^NRJS-[a-z0-9]+$/i.test(browserKey))
|
|
16
|
+
return BAD_KEY;
|
|
17
|
+
if (![applicationId, accountId].every(id => typeof id === 'string' && /^\d+$/.test(id)))
|
|
18
|
+
return BAD_ID;
|
|
19
|
+
if (region !== undefined && region !== 'us' && region !== 'eu')
|
|
20
|
+
return BAD_REGION;
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
let agent;
|
|
24
|
+
async function init({ browserKey, applicationId, accountId, region }) {
|
|
25
|
+
// Checked before the import, so a site with its own agent or copy-paste snippet gets no second agent.
|
|
26
|
+
if ('NREUM' in window || 'newrelic' in window)
|
|
27
|
+
return console.warn(EXISTING);
|
|
28
|
+
const { start } = await import('./newrelic-agent.mjs');
|
|
29
|
+
agent = start(browserKey, applicationId, accountId, BEACONS[region ?? 'us']);
|
|
30
|
+
}
|
|
31
|
+
function send(name, value, attrs) {
|
|
32
|
+
// attrs first: a caller's own "name", "value" or "page" must not replace the fields the dashboard reads.
|
|
33
|
+
agent?.recordCustomEvent('MetricFlowEvent', { ...attrs, name, value, page: window.location.pathname });
|
|
34
|
+
}
|
|
35
|
+
export const tool = { check, init, send };
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
// The only file that names @sentry/browser. sentry.mts loads it lazily, after its own checks.
|
|
2
|
+
// Static, named imports on purpose: a bundler can then drop what is not named here. With
|
|
3
|
+
// `await import('@sentry/browser')` the whole package, session replay and feedback included,
|
|
4
|
+
// lands in the consumer's chunk (measured with Next 16 / Turbopack: 439 kB against 140 kB).
|
|
5
|
+
import { browserTracingIntegration, getClient, init } from '@sentry/browser';
|
|
6
|
+
/** Starts Sentry. Returns false, and changes nothing, if a client is already running. */
|
|
7
|
+
export function start(dsn, tracesSampleRate) {
|
|
8
|
+
if (getClient())
|
|
9
|
+
return false; // a de-duplicated copy that started while ours loaded
|
|
10
|
+
init({
|
|
11
|
+
dsn,
|
|
12
|
+
sendDefaultPii: false, // explicit, not left to the SDK default
|
|
13
|
+
tracesSampleRate,
|
|
14
|
+
// SDK defaults plus tracing only. No replay, feedback or profiling integration is imported.
|
|
15
|
+
integrations: [browserTracingIntegration({
|
|
16
|
+
// Pins the page-load name to the real path so the dashboard can match it. Any ID in the path is sent (see README).
|
|
17
|
+
// Page loads only: a navigation span starts before pushState is applied, so the path is still the page being left.
|
|
18
|
+
beforeStartSpan: ctx => (ctx.op === 'pageload' ? { ...ctx, name: window.location.pathname } : ctx),
|
|
19
|
+
})],
|
|
20
|
+
});
|
|
21
|
+
return true;
|
|
22
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
const BAD_DSN = 'the DSN is not a public Sentry DSN. Copy it from Sentry > Project settings > Client Keys (DSN).';
|
|
2
|
+
const LEGACY_DSN = 'the DSN is a legacy one that contains a secret key after ":". Copy the DSN from Sentry > Project settings > Client Keys (DSN), and revoke the old key.';
|
|
3
|
+
const HTTP_DSN = 'the DSN must use https. http is accepted only for a Sentry on localhost.';
|
|
4
|
+
const LOCAL_HOSTS = ['localhost', '127.0.0.1', '[::1]']; // the same list as the dashboard's own DSN rule
|
|
5
|
+
const BAD_RATE = 'tracesSampleRate must be a number from 0 to 1.';
|
|
6
|
+
const BAD_OPTION = 'only "dsn" and "tracesSampleRate" are accepted.';
|
|
7
|
+
const EXISTING = 'MetricFlow: Sentry is already running on this page; MetricFlow left it as it is.';
|
|
8
|
+
function check(options) {
|
|
9
|
+
if (options === null || typeof options !== 'object')
|
|
10
|
+
return BAD_OPTION;
|
|
11
|
+
if (Object.keys(options).some(k => k !== 'dsn' && k !== 'tracesSampleRate'))
|
|
12
|
+
return BAD_OPTION;
|
|
13
|
+
const { dsn, tracesSampleRate: rate } = options;
|
|
14
|
+
if (typeof dsn !== 'string')
|
|
15
|
+
return BAD_DSN;
|
|
16
|
+
let url;
|
|
17
|
+
try {
|
|
18
|
+
url = new URL(dsn);
|
|
19
|
+
}
|
|
20
|
+
catch {
|
|
21
|
+
return BAD_DSN;
|
|
22
|
+
} // the caught error holds the input in some browsers: never log it
|
|
23
|
+
if (url.password !== '')
|
|
24
|
+
return LEGACY_DSN;
|
|
25
|
+
if (url.protocol === 'http:' && !LOCAL_HOSTS.includes(url.hostname))
|
|
26
|
+
return HTTP_DSN;
|
|
27
|
+
if (!/^https?:$/.test(url.protocol) || !/^[a-f0-9]{32}$/i.test(url.username) || !/^(\/[\w-]+)*\/\d+$/.test(url.pathname) || url.search !== '' || url.hash !== '')
|
|
28
|
+
return BAD_DSN;
|
|
29
|
+
if (rate !== undefined && !(typeof rate === 'number' && rate >= 0 && rate <= 1))
|
|
30
|
+
return BAD_RATE; // NaN fails both comparisons
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
// The dashboard lists local pages only, where sampling 1 in 10 looks like "nothing arrives".
|
|
34
|
+
const defaultRate = () => (['localhost', '127.0.0.1'].includes(window.location.hostname) ? 1 : 0.1);
|
|
35
|
+
async function init(options) {
|
|
36
|
+
// Checked before the import, so a site with its own Sentry downloads no second copy.
|
|
37
|
+
// Known false positive: an SDK that was loaded but never initialised is skipped too.
|
|
38
|
+
if ('__SENTRY__' in window)
|
|
39
|
+
return console.warn(EXISTING);
|
|
40
|
+
const { start } = await import('./sentry-sdk.mjs');
|
|
41
|
+
if (!start(options.dsn, options.tracesSampleRate ?? defaultRate()))
|
|
42
|
+
console.warn(EXISTING);
|
|
43
|
+
}
|
|
44
|
+
// The dashboard reads no custom events from Sentry, so send does nothing.
|
|
45
|
+
export const tool = { check, init, send() { } };
|