@shopkit/observability 0.0.0-stage → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +101 -2
- package/dist/bin/move-sourcemaps.d.mts +1 -0
- package/dist/bin/move-sourcemaps.d.ts +1 -0
- package/dist/bin/move-sourcemaps.js +2 -0
- package/dist/bin/move-sourcemaps.mjs +2 -0
- package/dist/chunk-6E7DD34Z.mjs +1 -0
- package/dist/chunk-YDBHIXG5.mjs +1 -0
- package/dist/chunk-ZNVBPNNQ.mjs +2 -0
- package/dist/client/index.d.mts +52 -0
- package/dist/client/index.d.ts +52 -0
- package/dist/client/index.js +1 -0
- package/dist/client/index.mjs +1 -0
- package/dist/head-stub/index.d.mts +34 -0
- package/dist/head-stub/index.d.ts +34 -0
- package/dist/head-stub/index.js +40 -0
- package/dist/head-stub/index.mjs +40 -0
- package/dist/index.d.mts +23 -0
- package/dist/index.d.ts +23 -0
- package/dist/index.js +1 -0
- package/dist/index.mjs +1 -0
- package/dist/react/index.d.mts +39 -0
- package/dist/react/index.d.ts +39 -0
- package/dist/react/index.js +2 -0
- package/dist/react/index.mjs +2 -0
- package/dist/relay/index.d.mts +39 -0
- package/dist/relay/index.d.ts +39 -0
- package/dist/relay/index.js +2 -0
- package/dist/relay/index.mjs +2 -0
- package/dist/server/index.d.mts +26 -0
- package/dist/server/index.d.ts +26 -0
- package/dist/server/index.js +2 -0
- package/dist/server/index.mjs +1 -0
- package/package.json +98 -4
package/README.md
CHANGED
|
@@ -1,3 +1,102 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @shopkit/observability
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Frontend monitoring for storefronts and the editor. Browser errors, failed requests, broken images and web vitals are sent as OTLP traces to a relay on the app's own server, which forwards them to SigNoz.
|
|
4
|
+
|
|
5
|
+
The package knows no framework: each app passes its own paths and values.
|
|
6
|
+
|
|
7
|
+
## Browser: `@shopkit/observability/client`
|
|
8
|
+
|
|
9
|
+
```ts
|
|
10
|
+
import { initObservability } from "@shopkit/observability/client";
|
|
11
|
+
|
|
12
|
+
initObservability({
|
|
13
|
+
app: "storefront",
|
|
14
|
+
serviceName: "storefront-web",
|
|
15
|
+
relayUrl: "/api/obs",
|
|
16
|
+
ignore: { params: ["previewKey"], errors: [/ResizeObserver loop/] },
|
|
17
|
+
});
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
About 4.6 KB gzip up front; `web-vitals` loads after page load. Never throws into the page. Each page view is its own trace; same-origin API calls join it. Next.js link prefetches are marked unsampled, so the server doesn't trace them.
|
|
21
|
+
|
|
22
|
+
Every signal carries `page.type` (from the optional `pageType(path)` callback) and, where the browser knows it, `network.effective_type`. Other options: `ignore.paths` / `ignore.cookies` (non-shopper traffic), `slowFetchMs` (default 2000), `sessionCap` (default 100 per page), `apiOrigins` (URLs of your own APIs on other domains, e.g. the commerce backend: their 5xx and slow calls count as yours).
|
|
23
|
+
|
|
24
|
+
Business flows (checkout, add to cart):
|
|
25
|
+
|
|
26
|
+
```ts
|
|
27
|
+
import { startFlow } from "@shopkit/observability/client";
|
|
28
|
+
|
|
29
|
+
const flow = startFlow("checkout"); // later: flow.success() or flow.fail("checkout_api", err)
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Each flow is recorded once as `flow.<name>` with `flow.outcome` and `flow.failure_reason`.
|
|
33
|
+
|
|
34
|
+
## Before the app starts: `@shopkit/observability/head-stub`
|
|
35
|
+
|
|
36
|
+
```tsx
|
|
37
|
+
import { headStubScript } from "@shopkit/observability/head-stub";
|
|
38
|
+
|
|
39
|
+
// First child of <head>, server-rendered
|
|
40
|
+
<script dangerouslySetInnerHTML={{ __html: headStubScript({ relayUrl, serviceName, app }) }} />
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
An inline ES5 script (~1.2 KB gzip). It queues errors that happen before the app's JS runs, and `initObservability` sends them on. If the app never starts (its scripts failed), the stub itself reports `browser.app_not_started` with what it caught, once the page has loaded.
|
|
44
|
+
|
|
45
|
+
## Containing crashes: `@shopkit/observability/react`
|
|
46
|
+
|
|
47
|
+
```tsx
|
|
48
|
+
import { ObservabilityBoundary } from "@shopkit/observability/react";
|
|
49
|
+
|
|
50
|
+
<ObservabilityBoundary kind="widget" id={widget.id} type={widget.type}>
|
|
51
|
+
{widget}
|
|
52
|
+
</ObservabilityBoundary>
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
A crash renders nothing (or `fallback`); the rest of the page stays up, and the crash is reported as `browser.render_error` with the component's kind, id and type. Plain React, already marked `"use client"`. Reports go through `initObservability`; before it runs, crashes are still contained.
|
|
56
|
+
|
|
57
|
+
For Next.js `error.tsx` / `global-error.tsx`: `useReportError(error, "app-error")` (the id names which boundary caught it).
|
|
58
|
+
|
|
59
|
+
## Relay: `@shopkit/observability/relay` (Node)
|
|
60
|
+
|
|
61
|
+
```ts
|
|
62
|
+
import { createRelayHandler, fileSourceMaps } from "@shopkit/observability/relay";
|
|
63
|
+
|
|
64
|
+
export const POST = createRelayHandler({
|
|
65
|
+
endpoint: "http://collector:4318/v1/traces",
|
|
66
|
+
// Set by the server; overwrites anything the browser sent.
|
|
67
|
+
// Release and environment belong here too: the head stub's reports carry neither.
|
|
68
|
+
trusted: { "merchant.id": merchantId, "service.version": release, "deployment.environment": env },
|
|
69
|
+
sourceMaps: fileSourceMaps({ urlPath: "/_next/static/", dir: ".next/obs-sourcemaps" }),
|
|
70
|
+
});
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
Checks size (64 KB) and shape, rate-limits per server instance (client IPs can be faked), drops bots, strips query strings, URL fragments, emails and phone numbers, stamps `trusted` plus `device.type` and `browser.in_app` from the User-Agent, resolves minified stacks (at most 30 new maps a minute), forwards to the collector.
|
|
74
|
+
|
|
75
|
+
## Server tracing: `@shopkit/observability/server` (Node)
|
|
76
|
+
|
|
77
|
+
Generic OpenTelemetry pieces for the app's own SDK setup (e.g. `@vercel/otel`'s `registerOTel`). Needs `@opentelemetry/api`.
|
|
78
|
+
|
|
79
|
+
```ts
|
|
80
|
+
registerOTel({
|
|
81
|
+
serviceName: "storefront-server",
|
|
82
|
+
traceSampler: skipPathsSampler(/^\/(api\/obs$|_next\/)/), // never trace these paths
|
|
83
|
+
spanProcessors: [scrubSpanProcessor(), "auto"], // strip query strings, emails, phones
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
// Next's instrumentation hook: an error in a streamed page render is otherwise unrecorded.
|
|
87
|
+
export const onRequestError = (err: unknown) => recordServerError(err);
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
`recordServerError` puts the error in the request's trace with `error.digest`, the same digest the browser's `useReportError` report carries.
|
|
91
|
+
|
|
92
|
+
## Keeping source maps private: `shopkit-move-sourcemaps`
|
|
93
|
+
|
|
94
|
+
```sh
|
|
95
|
+
shopkit-move-sourcemaps <public build dir> <private dir>
|
|
96
|
+
# e.g. after next build:
|
|
97
|
+
shopkit-move-sourcemaps .next/static .next/obs-sourcemaps
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
Moves every `*.map` out of the served folder. Fails (exit 1) if it can't, so maps are never shipped publicly.
|
|
101
|
+
|
|
102
|
+
With Sentry, set `sourcemaps: { deleteSourcemapsAfterUpload: false }` in `withSentryConfig`: by default Sentry deletes the maps at the end of the build, before this command runs, and browser stacks stay minified.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';var promises=require('fs/promises'),path=require('path');async function m(e,i){let u=await promises.readdir(e,{recursive:true});await promises.mkdir(i,{recursive:true});let t=u.filter(r=>r.endsWith(".map"));for(let r of t){let a=path.join(i,r);await promises.mkdir(path.dirname(a),{recursive:true}),await promises.rename(path.join(e,r),a);}return t.length}var[o,s]=process.argv.slice(2);(!o||!s)&&(console.error("usage: shopkit-move-sourcemaps <public build dir> <private dir>"),process.exit(1));m(o,s).then(e=>{e===0?console.warn(`observability: no source maps found in ${o}`):console.log(`observability: moved ${e} source maps from ${o} to ${s}`);},e=>{console.error(`observability: could not move source maps: ${e.message}`),process.exit(1);});
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {readdir,mkdir,rename}from'fs/promises';import {join,dirname}from'path';async function m(e,i){let u=await readdir(e,{recursive:true});await mkdir(i,{recursive:true});let t=u.filter(r=>r.endsWith(".map"));for(let r of t){let a=join(i,r);await mkdir(dirname(a),{recursive:true}),await rename(join(e,r),a);}return t.length}var[o,s]=process.argv.slice(2);(!o||!s)&&(console.error("usage: shopkit-move-sourcemaps <public build dir> <private dir>"),process.exit(1));m(o,s).then(e=>{e===0?console.warn(`observability: no source maps found in ${o}`):console.log(`observability: moved ${e} source maps from ${o} to ${s}`);},e=>{console.error(`observability: could not move source maps: ${e.message}`),process.exit(1);});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
var t="__shopkitObsStub",s="__shopkitObsBooted";export{t as a,s as b};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
var r={SERVICE_NAME:"service.name",SERVICE_VERSION:"service.version",DEPLOYMENT_ENVIRONMENT:"deployment.environment",APP:"app",MERCHANT_ID:"merchant.id"},e={JS_ERROR:"browser.js_error",UNHANDLED_REJECTION:"browser.unhandled_rejection",RESOURCE_ERROR:"browser.resource_error",RENDER_ERROR:"browser.render_error",HYDRATION_ERROR:"browser.hydration_error",CHUNK_ERROR:"browser.chunk_error",FETCH:"browser.fetch",WEB_VITAL:"web_vital",RAGE_CLICK:"ui.rage_click",TELEMETRY_DROPPED:"telemetry.dropped",FLOW_PREFIX:"flow."};export{r as a,e as b};
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
var t=/\?[^\s#)'"]*?(?=(?::\d+:\d+)?(?:[\s#)'"]|$))/g,r=/(?<![\w.+-])[\w.+-]+@[\w-]+(?:\.[\w-]+)+/g,i=/(?<!\w)(?:\+?91[\s-]?)?[6-9]\d{9}(?!\w)/g;function l(s){return s.replace(/\S+/g,e=>{let n=e.indexOf("#");if(n<0||!e.slice(0,n).includes("://"))return e;let c=e.slice(n).search(/[)'"]/);return e.slice(0,n)+(c<0?"":e.slice(n+c))})}function g(s){return l(s.replace(t,"")).replace(r,"[email]").replace(i,"[phone]")}
|
|
2
|
+
export{g as a};
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { App } from '../index.mjs';
|
|
2
|
+
|
|
3
|
+
interface ObservabilityConfig {
|
|
4
|
+
app: App;
|
|
5
|
+
serviceName: string;
|
|
6
|
+
/** Same-origin route on storefronts ("/api/obs"); the editor's backend URL. */
|
|
7
|
+
relayUrl: string;
|
|
8
|
+
release?: string;
|
|
9
|
+
environment?: string;
|
|
10
|
+
ignore?: {
|
|
11
|
+
/** Pages where nothing is installed, e.g. editor preview routes. */
|
|
12
|
+
paths?: RegExp[];
|
|
13
|
+
/** URL params that mark non-shopper traffic, e.g. "editorPreview". */
|
|
14
|
+
params?: string[];
|
|
15
|
+
/** Cookies whose presence marks non-shopper traffic, e.g. a preview session. */
|
|
16
|
+
cookies?: string[];
|
|
17
|
+
/** Error messages that are noise, e.g. /ResizeObserver loop/. */
|
|
18
|
+
errors?: RegExp[];
|
|
19
|
+
};
|
|
20
|
+
/** Max spans per page load. */
|
|
21
|
+
sessionCap?: number;
|
|
22
|
+
/** Successful calls to our own APIs at least this slow are recorded. Default 2000. */
|
|
23
|
+
slowFetchMs?: number;
|
|
24
|
+
/** URLs of our own APIs on other origins (e.g. the commerce backend): their 5xx and slow calls count as ours. */
|
|
25
|
+
apiOrigins?: string[];
|
|
26
|
+
/** Names the kind of page from its path ("product", "collection"), for filtering. */
|
|
27
|
+
pageType?: (path: string) => string | undefined;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Starts browser capture. No-op on the server, on ignored pages, and when already started.
|
|
31
|
+
* Returns a function that stops capture. Never throws: e.g. `document.cookie` throws in
|
|
32
|
+
* sandboxed frames.
|
|
33
|
+
*/
|
|
34
|
+
declare function initObservability(config: ObservabilityConfig): () => void;
|
|
35
|
+
|
|
36
|
+
type AttrValue = string | number | boolean;
|
|
37
|
+
type Attributes = Record<string, AttrValue | undefined>;
|
|
38
|
+
|
|
39
|
+
interface Flow {
|
|
40
|
+
success(attrs?: Attributes): void;
|
|
41
|
+
/** `reason` is a short fixed code (e.g. "checkout_api") to group failures by. */
|
|
42
|
+
fail(reason: string, err?: unknown, attrs?: Attributes): void;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Times one attempt of a business flow (add to cart, checkout) and records it once as
|
|
46
|
+
* a `flow.<name>` span with its outcome, so dashboards get attempts and failure rate.
|
|
47
|
+
* No exception event: an error the app also reports must not dedupe the flow away.
|
|
48
|
+
* Safe before init and on ignored pages (nothing is recorded); never throws.
|
|
49
|
+
*/
|
|
50
|
+
declare function startFlow(name: string, attrs?: Attributes): Flow;
|
|
51
|
+
|
|
52
|
+
export { type Flow, type ObservabilityConfig, initObservability, startFlow };
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { App } from '../index.js';
|
|
2
|
+
|
|
3
|
+
interface ObservabilityConfig {
|
|
4
|
+
app: App;
|
|
5
|
+
serviceName: string;
|
|
6
|
+
/** Same-origin route on storefronts ("/api/obs"); the editor's backend URL. */
|
|
7
|
+
relayUrl: string;
|
|
8
|
+
release?: string;
|
|
9
|
+
environment?: string;
|
|
10
|
+
ignore?: {
|
|
11
|
+
/** Pages where nothing is installed, e.g. editor preview routes. */
|
|
12
|
+
paths?: RegExp[];
|
|
13
|
+
/** URL params that mark non-shopper traffic, e.g. "editorPreview". */
|
|
14
|
+
params?: string[];
|
|
15
|
+
/** Cookies whose presence marks non-shopper traffic, e.g. a preview session. */
|
|
16
|
+
cookies?: string[];
|
|
17
|
+
/** Error messages that are noise, e.g. /ResizeObserver loop/. */
|
|
18
|
+
errors?: RegExp[];
|
|
19
|
+
};
|
|
20
|
+
/** Max spans per page load. */
|
|
21
|
+
sessionCap?: number;
|
|
22
|
+
/** Successful calls to our own APIs at least this slow are recorded. Default 2000. */
|
|
23
|
+
slowFetchMs?: number;
|
|
24
|
+
/** URLs of our own APIs on other origins (e.g. the commerce backend): their 5xx and slow calls count as ours. */
|
|
25
|
+
apiOrigins?: string[];
|
|
26
|
+
/** Names the kind of page from its path ("product", "collection"), for filtering. */
|
|
27
|
+
pageType?: (path: string) => string | undefined;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Starts browser capture. No-op on the server, on ignored pages, and when already started.
|
|
31
|
+
* Returns a function that stops capture. Never throws: e.g. `document.cookie` throws in
|
|
32
|
+
* sandboxed frames.
|
|
33
|
+
*/
|
|
34
|
+
declare function initObservability(config: ObservabilityConfig): () => void;
|
|
35
|
+
|
|
36
|
+
type AttrValue = string | number | boolean;
|
|
37
|
+
type Attributes = Record<string, AttrValue | undefined>;
|
|
38
|
+
|
|
39
|
+
interface Flow {
|
|
40
|
+
success(attrs?: Attributes): void;
|
|
41
|
+
/** `reason` is a short fixed code (e.g. "checkout_api") to group failures by. */
|
|
42
|
+
fail(reason: string, err?: unknown, attrs?: Attributes): void;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Times one attempt of a business flow (add to cart, checkout) and records it once as
|
|
46
|
+
* a `flow.<name>` span with its outcome, so dashboards get attempts and failure rate.
|
|
47
|
+
* No exception event: an error the app also reports must not dedupe the flow away.
|
|
48
|
+
* Safe before init and on ignored pages (nothing is recorded); never throws.
|
|
49
|
+
*/
|
|
50
|
+
declare function startFlow(name: string, attrs?: Attributes): Flow;
|
|
51
|
+
|
|
52
|
+
export { type Flow, type ObservabilityConfig, initObservability, startFlow };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
'use strict';var A={SERVICE_NAME:"service.name",SERVICE_VERSION:"service.version",DEPLOYMENT_ENVIRONMENT:"deployment.environment",APP:"app"},p={JS_ERROR:"browser.js_error",UNHANDLED_REJECTION:"browser.unhandled_rejection",RESOURCE_ERROR:"browser.resource_error",RENDER_ERROR:"browser.render_error",HYDRATION_ERROR:"browser.hydration_error",CHUNK_ERROR:"browser.chunk_error",FETCH:"browser.fetch",WEB_VITAL:"web_vital",RAGE_CLICK:"ui.rage_click",TELEMETRY_DROPPED:"telemetry.dropped",FLOW_PREFIX:"flow."};function R(){return performance.timeOrigin+performance.now()}function T(e){let t=crypto.getRandomValues(new Uint8Array(e)),r="";for(let o of t)r+=o.toString(16).padStart(2,"0");return /^0+$/.test(r)?T(e):r}var v=()=>T(16),h=()=>T(8);var P=1e3,z=8e3;function Q(e){return e instanceof Error?{"exception.type":e.name,"exception.message":e.message.slice(0,P),"exception.stacktrace":e.stack?.slice(0,z)}:{"exception.type":"Error","exception.message":String(e).slice(0,P)}}var Z=new RegExp(["Loading (CSS )?chunk \\S+ failed","Failed to fetch dynamically imported module","Importing a module script failed","error loading dynamically imported module"].join("|"),"i");function ee(e){return e instanceof Error?e.name==="ChunkLoadError"||Z.test(e.message):false}var te=/Minified React error #(418|423|425)\b|Hydration failed|error while hydrating|did not match\. Server/i;function re(e,t){return ee(t)?p.CHUNK_ERROR:t instanceof Error&&te.test(t.message)?p.HYDRATION_ERROR:e}function x(e){function t(o,n,i={}){try{let c=R();e.emit({name:o,traceId:e.traceId,spanId:h(),parentSpanId:e.parentSpanId,startMs:c,endMs:c,...i,attributes:{"page.path":location.pathname,...n}});}catch{}}function r(o,n,i={},c={}){try{let u={name:"exception",timeMs:R(),attributes:Q(n)};t(re(o,n),i,{...c,error:!0,events:[u]});}catch{}}return {record:t,recordException:r}}var V=e=>e.split(/[?#]/)[0];function ne(e){if(e instanceof HTMLImageElement||e instanceof HTMLScriptElement)return e.src;if(e instanceof HTMLLinkElement)return e.href}function M(e,t,r,o={}){let n=t!=="img"&&new URL(r,location.href).origin!==location.origin;e(p.RESOURCE_ERROR,{"resource.tag":t,"resource.url":V(r),"resource.third_party":n||void 0},{...o,error:!n});}function H(e){let{record:t,recordException:r}=x(e),o=e.slowFetchMs??2e3;function n(s){if(s instanceof ErrorEvent){r(p.JS_ERROR,s.error??s.message);return}let a=ne(s.target);a!==void 0&&M(t,s.target.tagName.toLowerCase(),a);}function i(s){r(p.UNHANDLED_REJECTION,s.reason);}let c=new URL(e.relayUrl,location.href).href,u=new Set(e.apiOrigins?.flatMap(s=>{try{return [new URL(s).origin]}catch{return []}})),l=new Set,f=window.fetch,S=f.bind(window);function w(s,a){try{let d=s instanceof Request?s:void 0,m=new URL(d?.url??String(s),location.href),g=V(m.href);if(g===c)return;let E=(a?.method??d?.method??"GET").toUpperCase(),_=m.origin===location.origin;return {method:E,url:g,sameOrigin:_,ours:_||u.has(m.origin)}}catch{return}}function I(s,a,d){try{let m=a?.headers??(s instanceof Request?s.headers:{}),g=new Headers(m);if(g.has("traceparent"))return a;let E=g.has("next-router-prefetch")?"00":"01";return g.set("traceparent",`00-${e.traceId}-${d}-${E}`),{...a,headers:g}}catch{return a}}async function y(s,a){let d=w(s,a);if(!d)return S(s,a);let m=`${p.FETCH} ${d.method}`,g={"http.method":d.method,"url.full":d.url},E=h(),_={kind:"client",startMs:R(),spanId:E};d.sameOrigin&&(a=I(s,a,E));try{let b=await S(s,a),O={...g,"http.status_code":b.status};if(!b.ok&&b.type!=="opaque"){let D=`${m} ${d.url} ${b.status}`;l.has(D)||(l.add(D),t(m,O,{..._,error:d.ours&&b.status>=500}));}else b.ok&&d.ours&&R()-_.startMs>=o&&t(m,O,{..._,error:!1});return b}catch(b){throw b instanceof DOMException&&b.name==="AbortError"||r(m,b,g,_),b}}return window.addEventListener("error",n,true),window.addEventListener("unhandledrejection",i),window.fetch=y,()=>{window.removeEventListener("error",n,true),window.removeEventListener("unhandledrejection",i),window.fetch===y&&(window.fetch=f);}}function oe(e){let t=e.events?.find(n=>n.name==="exception")?.attributes;if(!t)return;let{"exception.type":r,"exception.message":o}=t;return `${r}|${o}|${String(t["exception.stacktrace"]??"").slice(0,500)}`}function F(e=Date.now){let t=new Map;return function(o){let n=oe(o);if(n===void 0)return false;let i=e(),c=o.attributes?.["component.id"]!==void 0,u=t.get(n),l=u!==void 0&&i-u.at<1e3;return l&&(!c||u.boundary)?true:(t.size>=100&&t.clear(),t.set(n,{at:i,boundary:c||l&&u.boundary}),false)}}var N="__shopkitObsStub",B="__shopkitObsBooted";function $(){try{let e=window;e[B]=!0;let t=e[N];return delete e[N],t?.take()??[]}catch{return []}}function G(e,t){for(let r of t){let o={startMs:r.ts,endMs:r.ts};if(r.k==="r")M(e.record,r.tag??"",r.url??"",o);else {let n=new Error(r.message);n.name=r.type??"Error",n.stack=r.stack;let i=r.k==="u"?p.UNHANDLED_REJECTION:p.JS_ERROR;e.recordException(i,n,{},o);}}}var ie=1e3,se=3,ae="input, textarea, select, [contenteditable]",ce="button, a, [role=button], summary, label";function ue(e){let t=[],r=e;for(let o=0;r&&r!==document.body&&o<3;o++){let n=r.tagName.toLowerCase();if(r.id){t.unshift(`${n}#${r.id}`);break}let i=Array.from(r.classList).slice(0,2);t.unshift(i.length?`${n}.${i.join(".")}`:n),r=r.parentElement;}return t.join(" > ").slice(0,200)}function K(e){let t=null,r=[],o=false;function n(i){try{let c=i.target instanceof Element?i.target:null;if(!c||c===document.body||c===document.documentElement||c.closest(ae))return;let u=c.closest(ce)??c,l=Date.now();u!==t&&(t=u,r=[]),r=r.filter(f=>l-f<ie),r.push(l),r.length===1&&(o=!1),r.length>=se&&!o&&(o=!0,e(p.RAGE_CLICK,{"ui.target":ue(u)}));}catch{}}return document.addEventListener("click",n,true),()=>document.removeEventListener("click",n,true)}var q=Symbol.for("@shopkit/observability.reporter");function L(e){globalThis[q]=e;}function W(){return globalThis[q]}var de={internal:1,client:3},pe=0,le=2;function k(e){let t=Math.floor(e),r=Math.round((e-t)*1e6);return r===1e6&&(t+=1,r=0),t===0?String(r):`${t}${String(r).padStart(6,"0")}`}function fe(e){if(typeof e=="string")return {stringValue:e};if(typeof e=="boolean")return {boolValue:e};if(Number.isFinite(e))return Number.isInteger(e)?{intValue:String(e)}:{doubleValue:e}}function C(e){let t=[];if(!e)return t;for(let r in e){let o=e[r],n=o===void 0?void 0:fe(o);n&&t.push({key:r,value:n});}return t}function me(e){return {traceId:e.traceId,spanId:e.spanId,...e.parentSpanId&&{parentSpanId:e.parentSpanId},name:e.name,kind:de[e.kind??"internal"],startTimeUnixNano:k(e.startMs),endTimeUnixNano:k(e.endMs),attributes:C(e.attributes),events:(e.events??[]).map(t=>({name:t.name,timeUnixNano:k(t.timeMs),attributes:C(t.attributes)})),status:{code:e.error?le:pe}}}function j(e,t,r){return {resourceSpans:[{resource:{attributes:C(e)},scopeSpans:[{scope:t,spans:r.map(me)}]}]}}var ge=6e4,Y="text/plain;charset=UTF-8";function X(e){let{url:t,resource:r,scope:o}=e,n=e.maxBatch??30,i=e.flushMs??5e3,c=e.sessionCap??100,u=[],l=0,f=0,S=0,w;function I(){if(f===S)return;let a=R(),d=f-S;return S=f,{name:p.TELEMETRY_DROPPED,traceId:e.traceId??v(),spanId:h(),startMs:a,endMs:a,attributes:{"telemetry.dropped_count":d,"page.path":location.pathname}}}function y(a,d){let m=JSON.stringify(j(r,o,a)),g=new Blob([m],{type:Y});if(g.size>ge){if(a.length===1)return;let E=a.length>>1;y(a.slice(0,E),d),y(a.slice(E),d);return}d&&navigator.sendBeacon?.(t,g)||fetch(t,{method:"POST",body:m,headers:{"content-type":Y},keepalive:d,credentials:"omit"}).catch(()=>{});}function s(a){clearTimeout(w),w=void 0;let d=a?I():void 0;if(d&&u.push(d),u.length===0)return;let m=u;u=[];try{y(m,a);}catch{}}return document.addEventListener("visibilitychange",()=>{document.visibilityState==="hidden"&&s(true);}),window.addEventListener("pagehide",()=>s(true)),{add(a){if(l>=c){f++;return}l++,u.push(a),document.visibilityState==="hidden"?s(true):u.length>=n?s(false):w??(w=setTimeout(()=>s(false),i));}}}function J(e){let t=false;function r(n){if(t)return;let i=R();try{e.emit({name:p.WEB_VITAL,traceId:e.traceId,spanId:h(),parentSpanId:e.parentSpanId,startMs:i,endMs:i,attributes:{"page.path":n.navigationURL?new URL(n.navigationURL).pathname:location.pathname,"web_vital.name":n.name,"web_vital.value":n.value,"web_vital.rating":n.rating,"web_vital.navigation_type":n.navigationType}});}catch{}}function o(){import('web-vitals').then(({onCLS:n,onFCP:i,onINP:c,onLCP:u,onTTFB:l})=>{[n,i,c,u,l].forEach(f=>f(r));}).catch(()=>{});}return document.readyState==="complete"?o():window.addEventListener("load",o,{once:true}),()=>{t=true,window.removeEventListener("load",o);}}var U=Symbol.for("@shopkit/observability"),be=/^00-([0-9a-f]{32})-([0-9a-f]{16})-[0-9a-f]{2}$/;function Re(e){let t=e?be.exec(e.trim()):null;if(!(!t||/^0+$/.test(t[1])||/^0+$/.test(t[2])))return {traceId:t[1],parentSpanId:t[2]}}function Ee(){return document.cookie.split(";").map(e=>e.split("=")[0].trim())}function Se(e){if(e?.paths?.some(o=>o.test(location.pathname)))return true;let t=new URLSearchParams(location.search);if(e?.params?.some(o=>t.has(o)))return true;let r=e?.cookies?.length?Ee():[];return e?.cookies?.some(o=>r.includes(o))??false}function ye(e){return String(e.events?.[0]?.attributes?.["exception.message"]??"")}function he(e){try{return we(e)}catch{return ()=>{}}}function we(e){let t=globalThis;if(typeof window>"u")return ()=>{};let r=$();if(t[U]||Se(e.ignore))return ()=>{};t[U]=true;let o=document.querySelector('meta[name="traceparent"]')?.getAttribute("content"),n=Re(o)?.traceId,i={traceId:v()},c=X({url:e.relayUrl,resource:{[A.SERVICE_NAME]:e.serviceName,[A.APP]:e.app,[A.SERVICE_VERSION]:e.release,[A.DEPLOYMENT_ENVIRONMENT]:e.environment},scope:{name:"@shopkit/observability",version:"0.1.1"},sessionCap:e.sessionCap,traceId:i.traceId}),u=e.ignore?.errors??[],l=F(),f=s=>{let a=ye(s);a&&u.some(d=>d.test(a))||l(s)||(s.attributes={...s.attributes,"page.type":e.pageType?.(String(s.attributes?.["page.path"])),"page.render_trace_id":n,"network.effective_type":navigator.connection?.effectiveType},c.add(s));},S=H({emit:f,...i,relayUrl:e.relayUrl,slowFetchMs:e.slowFetchMs,apiOrigins:e.apiOrigins}),w=J({emit:f,...i}),I=x({emit:f,...i});L(I),G(I,r);let y=K(I.record);return ()=>{S(),w(),y(),L(void 0),t[U]=false;}}var Ie=1e3;function _e(e,t){let r=R(),o=false;function n(i,c){if(!o){o=true;try{W()?.record(p.FLOW_PREFIX+e,{...t,...c,"flow.outcome":i},{startMs:r,error:i==="failure"});}catch{}}}return {success:i=>n("success",{...i}),fail:(i,c,u)=>n("failure",{...u,"flow.failure_reason":i,"flow.error_message":c===void 0?void 0:(c instanceof Error?c.message:String(c)).slice(0,Ie)})}}exports.initObservability=he;exports.startFlow=_e;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import {a,b}from'../chunk-YDBHIXG5.mjs';import {b as b$1,a as a$1}from'../chunk-6E7DD34Z.mjs';function R(){return performance.timeOrigin+performance.now()}function _(e){let t=crypto.getRandomValues(new Uint8Array(e)),r="";for(let o of t)r+=o.toString(16).padStart(2,"0");return /^0+$/.test(r)?_(e):r}var A=()=>_(16),E=()=>_(8);var D=1e3,z=8e3;function Q(e){return e instanceof Error?{"exception.type":e.name,"exception.message":e.message.slice(0,D),"exception.stacktrace":e.stack?.slice(0,z)}:{"exception.type":"Error","exception.message":String(e).slice(0,D)}}var Z=new RegExp(["Loading (CSS )?chunk \\S+ failed","Failed to fetch dynamically imported module","Importing a module script failed","error loading dynamically imported module"].join("|"),"i");function ee(e){return e instanceof Error?e.name==="ChunkLoadError"||Z.test(e.message):false}var te=/Minified React error #(418|423|425)\b|Hydration failed|error while hydrating|did not match\. Server/i;function re(e,t){return ee(t)?b.CHUNK_ERROR:t instanceof Error&&te.test(t.message)?b.HYDRATION_ERROR:e}function T(e){function t(o,n,i={}){try{let c=R();e.emit({name:o,traceId:e.traceId,spanId:E(),parentSpanId:e.parentSpanId,startMs:c,endMs:c,...i,attributes:{"page.path":location.pathname,...n}});}catch{}}function r(o,n,i={},c={}){try{let u={name:"exception",timeMs:R(),attributes:Q(n)};t(re(o,n),i,{...c,error:!0,events:[u]});}catch{}}return {record:t,recordException:r}}var H=e=>e.split(/[?#]/)[0];function ne(e){if(e instanceof HTMLImageElement||e instanceof HTMLScriptElement)return e.src;if(e instanceof HTMLLinkElement)return e.href}function N(e,t,r,o={}){let n=t!=="img"&&new URL(r,location.href).origin!==location.origin;e(b.RESOURCE_ERROR,{"resource.tag":t,"resource.url":H(r),"resource.third_party":n||void 0},{...o,error:!n});}function F(e){let{record:t,recordException:r}=T(e),o=e.slowFetchMs??2e3;function n(a){if(a instanceof ErrorEvent){r(b.JS_ERROR,a.error??a.message);return}let s=ne(a.target);s!==void 0&&N(t,a.target.tagName.toLowerCase(),s);}function i(a){r(b.UNHANDLED_REJECTION,a.reason);}let c=new URL(e.relayUrl,location.href).href,u=new Set(e.apiOrigins?.flatMap(a=>{try{return [new URL(a).origin]}catch{return []}})),l=new Set,f=window.fetch,y=f.bind(window);function w(a,s){try{let d=a instanceof Request?a:void 0,m=new URL(d?.url??String(a),location.href),g=H(m.href);if(g===c)return;let S=(s?.method??d?.method??"GET").toUpperCase(),v=m.origin===location.origin;return {method:S,url:g,sameOrigin:v,ours:v||u.has(m.origin)}}catch{return}}function I(a,s,d){try{let m=s?.headers??(a instanceof Request?a.headers:{}),g=new Headers(m);if(g.has("traceparent"))return s;let S=g.has("next-router-prefetch")?"00":"01";return g.set("traceparent",`00-${e.traceId}-${d}-${S}`),{...s,headers:g}}catch{return s}}async function h(a,s){let d=w(a,s);if(!d)return y(a,s);let m=`${b.FETCH} ${d.method}`,g={"http.method":d.method,"url.full":d.url},S=E(),v={kind:"client",startMs:R(),spanId:S};d.sameOrigin&&(s=I(a,s,S));try{let b=await y(a,s),O={...g,"http.status_code":b.status};if(!b.ok&&b.type!=="opaque"){let V=`${m} ${d.url} ${b.status}`;l.has(V)||(l.add(V),t(m,O,{...v,error:d.ours&&b.status>=500}));}else b.ok&&d.ours&&R()-v.startMs>=o&&t(m,O,{...v,error:!1});return b}catch(b){throw b instanceof DOMException&&b.name==="AbortError"||r(m,b,g,v),b}}return window.addEventListener("error",n,true),window.addEventListener("unhandledrejection",i),window.fetch=h,()=>{window.removeEventListener("error",n,true),window.removeEventListener("unhandledrejection",i),window.fetch===h&&(window.fetch=f);}}function oe(e){let t=e.events?.find(n=>n.name==="exception")?.attributes;if(!t)return;let{"exception.type":r,"exception.message":o}=t;return `${r}|${o}|${String(t["exception.stacktrace"]??"").slice(0,500)}`}function $(e=Date.now){let t=new Map;return function(o){let n=oe(o);if(n===void 0)return false;let i=e(),c=o.attributes?.["component.id"]!==void 0,u=t.get(n),l=u!==void 0&&i-u.at<1e3;return l&&(!c||u.boundary)?true:(t.size>=100&&t.clear(),t.set(n,{at:i,boundary:c||l&&u.boundary}),false)}}function B(){try{let e=window;e[b$1]=!0;let t=e[a$1];return delete e[a$1],t?.take()??[]}catch{return []}}function K(e,t){for(let r of t){let o={startMs:r.ts,endMs:r.ts};if(r.k==="r")N(e.record,r.tag??"",r.url??"",o);else {let n=new Error(r.message);n.name=r.type??"Error",n.stack=r.stack;let i=r.k==="u"?b.UNHANDLED_REJECTION:b.JS_ERROR;e.recordException(i,n,{},o);}}}var ie=1e3,ae=3,se="input, textarea, select, [contenteditable]",ce="button, a, [role=button], summary, label";function ue(e){let t=[],r=e;for(let o=0;r&&r!==document.body&&o<3;o++){let n=r.tagName.toLowerCase();if(r.id){t.unshift(`${n}#${r.id}`);break}let i=Array.from(r.classList).slice(0,2);t.unshift(i.length?`${n}.${i.join(".")}`:n),r=r.parentElement;}return t.join(" > ").slice(0,200)}function G(e){let t=null,r=[],o=false;function n(i){try{let c=i.target instanceof Element?i.target:null;if(!c||c===document.body||c===document.documentElement||c.closest(se))return;let u=c.closest(ce)??c,l=Date.now();u!==t&&(t=u,r=[]),r=r.filter(f=>l-f<ie),r.push(l),r.length===1&&(o=!1),r.length>=ae&&!o&&(o=!0,e(b.RAGE_CLICK,{"ui.target":ue(u)}));}catch{}}return document.addEventListener("click",n,true),()=>document.removeEventListener("click",n,true)}var q=Symbol.for("@shopkit/observability.reporter");function k(e){globalThis[q]=e;}function W(){return globalThis[q]}var de={internal:1,client:3},pe=0,le=2;function L(e){let t=Math.floor(e),r=Math.round((e-t)*1e6);return r===1e6&&(t+=1,r=0),t===0?String(r):`${t}${String(r).padStart(6,"0")}`}function fe(e){if(typeof e=="string")return {stringValue:e};if(typeof e=="boolean")return {boolValue:e};if(Number.isFinite(e))return Number.isInteger(e)?{intValue:String(e)}:{doubleValue:e}}function C(e){let t=[];if(!e)return t;for(let r in e){let o=e[r],n=o===void 0?void 0:fe(o);n&&t.push({key:r,value:n});}return t}function me(e){return {traceId:e.traceId,spanId:e.spanId,...e.parentSpanId&&{parentSpanId:e.parentSpanId},name:e.name,kind:de[e.kind??"internal"],startTimeUnixNano:L(e.startMs),endTimeUnixNano:L(e.endMs),attributes:C(e.attributes),events:(e.events??[]).map(t=>({name:t.name,timeUnixNano:L(t.timeMs),attributes:C(t.attributes)})),status:{code:e.error?le:pe}}}function j(e,t,r){return {resourceSpans:[{resource:{attributes:C(e)},scopeSpans:[{scope:t,spans:r.map(me)}]}]}}var ge=6e4,X="text/plain;charset=UTF-8";function Y(e){let{url:t,resource:r,scope:o}=e,n=e.maxBatch??30,i=e.flushMs??5e3,c=e.sessionCap??100,u=[],l=0,f=0,y=0,w;function I(){if(f===y)return;let s=R(),d=f-y;return y=f,{name:b.TELEMETRY_DROPPED,traceId:e.traceId??A(),spanId:E(),startMs:s,endMs:s,attributes:{"telemetry.dropped_count":d,"page.path":location.pathname}}}function h(s,d){let m=JSON.stringify(j(r,o,s)),g=new Blob([m],{type:X});if(g.size>ge){if(s.length===1)return;let S=s.length>>1;h(s.slice(0,S),d),h(s.slice(S),d);return}d&&navigator.sendBeacon?.(t,g)||fetch(t,{method:"POST",body:m,headers:{"content-type":X},keepalive:d,credentials:"omit"}).catch(()=>{});}function a(s){clearTimeout(w),w=void 0;let d=s?I():void 0;if(d&&u.push(d),u.length===0)return;let m=u;u=[];try{h(m,s);}catch{}}return document.addEventListener("visibilitychange",()=>{document.visibilityState==="hidden"&&a(true);}),window.addEventListener("pagehide",()=>a(true)),{add(s){if(l>=c){f++;return}l++,u.push(s),document.visibilityState==="hidden"?a(true):u.length>=n?a(false):w??(w=setTimeout(()=>a(false),i));}}}function J(e){let t=false;function r(n){if(t)return;let i=R();try{e.emit({name:b.WEB_VITAL,traceId:e.traceId,spanId:E(),parentSpanId:e.parentSpanId,startMs:i,endMs:i,attributes:{"page.path":n.navigationURL?new URL(n.navigationURL).pathname:location.pathname,"web_vital.name":n.name,"web_vital.value":n.value,"web_vital.rating":n.rating,"web_vital.navigation_type":n.navigationType}});}catch{}}function o(){import('web-vitals').then(({onCLS:n,onFCP:i,onINP:c,onLCP:u,onTTFB:l})=>{[n,i,c,u,l].forEach(f=>f(r));}).catch(()=>{});}return document.readyState==="complete"?o():window.addEventListener("load",o,{once:true}),()=>{t=true,window.removeEventListener("load",o);}}var U=Symbol.for("@shopkit/observability"),be=/^00-([0-9a-f]{32})-([0-9a-f]{16})-[0-9a-f]{2}$/;function Re(e){let t=e?be.exec(e.trim()):null;if(!(!t||/^0+$/.test(t[1])||/^0+$/.test(t[2])))return {traceId:t[1],parentSpanId:t[2]}}function Se(){return document.cookie.split(";").map(e=>e.split("=")[0].trim())}function ye(e){if(e?.paths?.some(o=>o.test(location.pathname)))return true;let t=new URLSearchParams(location.search);if(e?.params?.some(o=>t.has(o)))return true;let r=e?.cookies?.length?Se():[];return e?.cookies?.some(o=>r.includes(o))??false}function he(e){return String(e.events?.[0]?.attributes?.["exception.message"]??"")}function Ee(e){try{return we(e)}catch{return ()=>{}}}function we(e){let t=globalThis;if(typeof window>"u")return ()=>{};let r=B();if(t[U]||ye(e.ignore))return ()=>{};t[U]=true;let o=document.querySelector('meta[name="traceparent"]')?.getAttribute("content"),n=Re(o)?.traceId,i={traceId:A()},c=Y({url:e.relayUrl,resource:{[a.SERVICE_NAME]:e.serviceName,[a.APP]:e.app,[a.SERVICE_VERSION]:e.release,[a.DEPLOYMENT_ENVIRONMENT]:e.environment},scope:{name:"@shopkit/observability",version:"0.1.1"},sessionCap:e.sessionCap,traceId:i.traceId}),u=e.ignore?.errors??[],l=$(),f=a=>{let s=he(a);s&&u.some(d=>d.test(s))||l(a)||(a.attributes={...a.attributes,"page.type":e.pageType?.(String(a.attributes?.["page.path"])),"page.render_trace_id":n,"network.effective_type":navigator.connection?.effectiveType},c.add(a));},y=F({emit:f,...i,relayUrl:e.relayUrl,slowFetchMs:e.slowFetchMs,apiOrigins:e.apiOrigins}),w=J({emit:f,...i}),I=T({emit:f,...i});k(I),K(I,r);let h=G(I.record);return ()=>{y(),w(),h(),k(void 0),t[U]=false;}}var Ie=1e3;function ve(e,t){let r=R(),o=false;function n(i,c){if(!o){o=true;try{W()?.record(b.FLOW_PREFIX+e,{...t,...c,"flow.outcome":i},{startMs:r,error:i==="failure"});}catch{}}}return {success:i=>n("success",{...i}),fail:(i,c,u)=>n("failure",{...u,"flow.failure_reason":i,"flow.error_message":c===void 0?void 0:(c instanceof Error?c.message:String(c)).slice(0,Ie)})}}export{Ee as initObservability,ve as startFlow};
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { App } from '../index.mjs';
|
|
2
|
+
|
|
3
|
+
/** Global the stub publishes; initObservability takes the queue from it. */
|
|
4
|
+
declare const HEAD_STUB_GLOBAL = "__shopkitObsStub";
|
|
5
|
+
/**
|
|
6
|
+
* Set by initObservability. Next puts its async chunk scripts above the layout's
|
|
7
|
+
* <head> content, so a cached chunk can start the app before the stub runs; the
|
|
8
|
+
* stub must then stay off, or it would report a healthy page as never started.
|
|
9
|
+
*/
|
|
10
|
+
declare const BOOTED_GLOBAL = "__shopkitObsBooted";
|
|
11
|
+
/** An error the stub caught before the app started. */
|
|
12
|
+
interface HeadStubEntry {
|
|
13
|
+
/** j: uncaught error, u: unhandled rejection, r: failed <img>/<script>/<link> */
|
|
14
|
+
k: "j" | "u" | "r";
|
|
15
|
+
ts: number;
|
|
16
|
+
type?: string;
|
|
17
|
+
message?: string;
|
|
18
|
+
stack?: string;
|
|
19
|
+
tag?: string;
|
|
20
|
+
url?: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
interface HeadStubConfig {
|
|
24
|
+
relayUrl: string;
|
|
25
|
+
serviceName: string;
|
|
26
|
+
app: App;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Inline script for the first child of <head>. Catches errors before the app's JS
|
|
30
|
+
* loads, and reports by itself when the app never starts (blank page).
|
|
31
|
+
*/
|
|
32
|
+
declare function headStubScript(config: HeadStubConfig, graceMs?: number): string;
|
|
33
|
+
|
|
34
|
+
export { BOOTED_GLOBAL, HEAD_STUB_GLOBAL, type HeadStubConfig, type HeadStubEntry, headStubScript };
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { App } from '../index.js';
|
|
2
|
+
|
|
3
|
+
/** Global the stub publishes; initObservability takes the queue from it. */
|
|
4
|
+
declare const HEAD_STUB_GLOBAL = "__shopkitObsStub";
|
|
5
|
+
/**
|
|
6
|
+
* Set by initObservability. Next puts its async chunk scripts above the layout's
|
|
7
|
+
* <head> content, so a cached chunk can start the app before the stub runs; the
|
|
8
|
+
* stub must then stay off, or it would report a healthy page as never started.
|
|
9
|
+
*/
|
|
10
|
+
declare const BOOTED_GLOBAL = "__shopkitObsBooted";
|
|
11
|
+
/** An error the stub caught before the app started. */
|
|
12
|
+
interface HeadStubEntry {
|
|
13
|
+
/** j: uncaught error, u: unhandled rejection, r: failed <img>/<script>/<link> */
|
|
14
|
+
k: "j" | "u" | "r";
|
|
15
|
+
ts: number;
|
|
16
|
+
type?: string;
|
|
17
|
+
message?: string;
|
|
18
|
+
stack?: string;
|
|
19
|
+
tag?: string;
|
|
20
|
+
url?: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
interface HeadStubConfig {
|
|
24
|
+
relayUrl: string;
|
|
25
|
+
serviceName: string;
|
|
26
|
+
app: App;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Inline script for the first child of <head>. Catches errors before the app's JS
|
|
30
|
+
* loads, and reports by itself when the app never starts (blank page).
|
|
31
|
+
*/
|
|
32
|
+
declare function headStubScript(config: HeadStubConfig, graceMs?: number): string;
|
|
33
|
+
|
|
34
|
+
export { BOOTED_GLOBAL, HEAD_STUB_GLOBAL, type HeadStubConfig, type HeadStubEntry, headStubScript };
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
'use strict';var r="__shopkitObsStub",n="__shopkitObsBooted";var o=`
|
|
2
|
+
// C = config: u relay URL, s service name, a app, g grace ms, v package version.
|
|
3
|
+
// w window, q early errors, b app took over, d report sent, T grace timer.
|
|
4
|
+
if(window.${n})return;
|
|
5
|
+
var w=window,q=[],b=false,d=false,T;
|
|
6
|
+
// N: now. P: queue an entry (at most 20).
|
|
7
|
+
function N(){return Date.now()}
|
|
8
|
+
function P(x){if(q.length<20)q.push(x)}
|
|
9
|
+
// E: uncaught error, or a failed <img>/<script>/<link> (needs the capture phase).
|
|
10
|
+
function E(e){try{var t=e.target;
|
|
11
|
+
if(t&&t!==w&&t.tagName){P({k:"r",ts:N(),tag:t.tagName.toLowerCase(),url:String(t.currentSrc||t.src||t.href||"").split(/[?#]/)[0]});return}
|
|
12
|
+
var x=e.error;P({k:"j",ts:N(),type:(x&&x.name)||"Error",message:String((x&&x.message)||e.message),stack:x&&x.stack})}catch(_){}}
|
|
13
|
+
// R: unhandled promise rejection.
|
|
14
|
+
function R(e){try{var r=e.reason;P({k:"u",ts:N(),type:(r&&r.name)||"Error",message:String((r&&r.message)||r),stack:r&&r.stack})}catch(_){}}
|
|
15
|
+
// X: stop listening.
|
|
16
|
+
function X(){w.removeEventListener("error",E,true);w.removeEventListener("unhandledrejection",R);w.removeEventListener("pagehide",F);clearTimeout(T)}
|
|
17
|
+
// H: random hex id. K: OTLP string attribute, short so 20 entries fit one 64 KB beacon.
|
|
18
|
+
// S: OTLP span (always an error).
|
|
19
|
+
function H(n){var a=new Uint8Array(n),s="";crypto.getRandomValues(a);for(var i=0;i<n;i++)s+=(a[i]|256).toString(16).slice(1);return s}
|
|
20
|
+
function K(k,v){return{key:k,value:{stringValue:String(v).slice(0,1000)}}}
|
|
21
|
+
function S(name,trace,ts,attrs,ex){var n=ts+"000000";return{traceId:trace,spanId:H(8),name:name,kind:1,startTimeUnixNano:n,endTimeUnixNano:n,attributes:[K("page.path",location.pathname)].concat(attrs),events:ex?[{name:"exception",timeUnixNano:n,attributes:ex}]:[],status:{code:2}}}
|
|
22
|
+
// F: the app never took over: send app_not_started plus the queue, by beacon.
|
|
23
|
+
function F(){if(b||d)return;d=true;X();try{
|
|
24
|
+
var tr=H(16),sp=[S("browser.app_not_started",tr,N(),[])];
|
|
25
|
+
for(var i=0;i<q.length;i++){var x=q[i];
|
|
26
|
+
if(x.k==="r")sp.push(S("browser.resource_error",tr,x.ts,[K("resource.tag",x.tag),K("resource.url",x.url)]));
|
|
27
|
+
else sp.push(S(x.k==="u"?"browser.unhandled_rejection":"browser.js_error",tr,x.ts,[],[K("exception.type",x.type),K("exception.message",x.message)].concat(x.stack?[K("exception.stacktrace",x.stack)]:[])))}
|
|
28
|
+
var body=JSON.stringify({resourceSpans:[{resource:{attributes:[K("service.name",C.s),K("app",C.a)]},scopeSpans:[{scope:{name:"@shopkit/observability",version:C.v},spans:sp}]}]});
|
|
29
|
+
var ty="text/plain;charset=UTF-8";
|
|
30
|
+
if(!(navigator.sendBeacon&&navigator.sendBeacon(C.u,new Blob([body],{type:ty}))))fetch(C.u,{method:"POST",body:body,keepalive:true,credentials:"omit",headers:{"content-type":ty}})["catch"](function(){});
|
|
31
|
+
}catch(_){}}
|
|
32
|
+
w.addEventListener("error",E,true);
|
|
33
|
+
w.addEventListener("unhandledrejection",R);
|
|
34
|
+
// Only after load can "never started" be told apart from "still loading".
|
|
35
|
+
w.addEventListener("load",function(){T=setTimeout(F,C.g);w.addEventListener("pagehide",F)});
|
|
36
|
+
// initObservability calls take(): the app started, hand the queue over.
|
|
37
|
+
w.${r}={take:function(){b=true;X();return q}};
|
|
38
|
+
`,i=t=>t.split(`
|
|
39
|
+
`).map(e=>e.trim()).filter(e=>e&&!e.startsWith("//")).join(""),c=i(o);function d(t,e=3e3){let a={u:t.relayUrl,s:t.serviceName,a:t.app,g:e,v:"0.1.1"},s=JSON.stringify(a).replace(/</g,"\\u003c");return `(function(C){${c}})(${s});`}
|
|
40
|
+
exports.BOOTED_GLOBAL=n;exports.HEAD_STUB_GLOBAL=r;exports.headStubScript=d;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import {b,a}from'../chunk-6E7DD34Z.mjs';export{b as BOOTED_GLOBAL,a as HEAD_STUB_GLOBAL}from'../chunk-6E7DD34Z.mjs';var o=`
|
|
2
|
+
// C = config: u relay URL, s service name, a app, g grace ms, v package version.
|
|
3
|
+
// w window, q early errors, b app took over, d report sent, T grace timer.
|
|
4
|
+
if(window.${b})return;
|
|
5
|
+
var w=window,q=[],b=false,d=false,T;
|
|
6
|
+
// N: now. P: queue an entry (at most 20).
|
|
7
|
+
function N(){return Date.now()}
|
|
8
|
+
function P(x){if(q.length<20)q.push(x)}
|
|
9
|
+
// E: uncaught error, or a failed <img>/<script>/<link> (needs the capture phase).
|
|
10
|
+
function E(e){try{var t=e.target;
|
|
11
|
+
if(t&&t!==w&&t.tagName){P({k:"r",ts:N(),tag:t.tagName.toLowerCase(),url:String(t.currentSrc||t.src||t.href||"").split(/[?#]/)[0]});return}
|
|
12
|
+
var x=e.error;P({k:"j",ts:N(),type:(x&&x.name)||"Error",message:String((x&&x.message)||e.message),stack:x&&x.stack})}catch(_){}}
|
|
13
|
+
// R: unhandled promise rejection.
|
|
14
|
+
function R(e){try{var r=e.reason;P({k:"u",ts:N(),type:(r&&r.name)||"Error",message:String((r&&r.message)||r),stack:r&&r.stack})}catch(_){}}
|
|
15
|
+
// X: stop listening.
|
|
16
|
+
function X(){w.removeEventListener("error",E,true);w.removeEventListener("unhandledrejection",R);w.removeEventListener("pagehide",F);clearTimeout(T)}
|
|
17
|
+
// H: random hex id. K: OTLP string attribute, short so 20 entries fit one 64 KB beacon.
|
|
18
|
+
// S: OTLP span (always an error).
|
|
19
|
+
function H(n){var a=new Uint8Array(n),s="";crypto.getRandomValues(a);for(var i=0;i<n;i++)s+=(a[i]|256).toString(16).slice(1);return s}
|
|
20
|
+
function K(k,v){return{key:k,value:{stringValue:String(v).slice(0,1000)}}}
|
|
21
|
+
function S(name,trace,ts,attrs,ex){var n=ts+"000000";return{traceId:trace,spanId:H(8),name:name,kind:1,startTimeUnixNano:n,endTimeUnixNano:n,attributes:[K("page.path",location.pathname)].concat(attrs),events:ex?[{name:"exception",timeUnixNano:n,attributes:ex}]:[],status:{code:2}}}
|
|
22
|
+
// F: the app never took over: send app_not_started plus the queue, by beacon.
|
|
23
|
+
function F(){if(b||d)return;d=true;X();try{
|
|
24
|
+
var tr=H(16),sp=[S("browser.app_not_started",tr,N(),[])];
|
|
25
|
+
for(var i=0;i<q.length;i++){var x=q[i];
|
|
26
|
+
if(x.k==="r")sp.push(S("browser.resource_error",tr,x.ts,[K("resource.tag",x.tag),K("resource.url",x.url)]));
|
|
27
|
+
else sp.push(S(x.k==="u"?"browser.unhandled_rejection":"browser.js_error",tr,x.ts,[],[K("exception.type",x.type),K("exception.message",x.message)].concat(x.stack?[K("exception.stacktrace",x.stack)]:[])))}
|
|
28
|
+
var body=JSON.stringify({resourceSpans:[{resource:{attributes:[K("service.name",C.s),K("app",C.a)]},scopeSpans:[{scope:{name:"@shopkit/observability",version:C.v},spans:sp}]}]});
|
|
29
|
+
var ty="text/plain;charset=UTF-8";
|
|
30
|
+
if(!(navigator.sendBeacon&&navigator.sendBeacon(C.u,new Blob([body],{type:ty}))))fetch(C.u,{method:"POST",body:body,keepalive:true,credentials:"omit",headers:{"content-type":ty}})["catch"](function(){});
|
|
31
|
+
}catch(_){}}
|
|
32
|
+
w.addEventListener("error",E,true);
|
|
33
|
+
w.addEventListener("unhandledrejection",R);
|
|
34
|
+
// Only after load can "never started" be told apart from "still loading".
|
|
35
|
+
w.addEventListener("load",function(){T=setTimeout(F,C.g);w.addEventListener("pagehide",F)});
|
|
36
|
+
// initObservability calls take(): the app started, hand the queue over.
|
|
37
|
+
w.${a}={take:function(){b=true;X();return q}};
|
|
38
|
+
`,i=t=>t.split(`
|
|
39
|
+
`).map(e=>e.trim()).filter(e=>e&&!e.startsWith("//")).join(""),c=i(o);function u(t,e=3e3){let a={u:t.relayUrl,s:t.serviceName,a:t.app,g:e,v:"0.1.1"},s=JSON.stringify(a).replace(/</g,"\\u003c");return `(function(C){${c}})(${s});`}
|
|
40
|
+
export{u as headStubScript};
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
declare const RESOURCE_ATTR: {
|
|
2
|
+
readonly SERVICE_NAME: "service.name";
|
|
3
|
+
readonly SERVICE_VERSION: "service.version";
|
|
4
|
+
readonly DEPLOYMENT_ENVIRONMENT: "deployment.environment";
|
|
5
|
+
readonly APP: "app";
|
|
6
|
+
readonly MERCHANT_ID: "merchant.id";
|
|
7
|
+
};
|
|
8
|
+
declare const SPAN: {
|
|
9
|
+
readonly JS_ERROR: "browser.js_error";
|
|
10
|
+
readonly UNHANDLED_REJECTION: "browser.unhandled_rejection";
|
|
11
|
+
readonly RESOURCE_ERROR: "browser.resource_error";
|
|
12
|
+
readonly RENDER_ERROR: "browser.render_error";
|
|
13
|
+
readonly HYDRATION_ERROR: "browser.hydration_error";
|
|
14
|
+
readonly CHUNK_ERROR: "browser.chunk_error";
|
|
15
|
+
readonly FETCH: "browser.fetch";
|
|
16
|
+
readonly WEB_VITAL: "web_vital";
|
|
17
|
+
readonly RAGE_CLICK: "ui.rage_click";
|
|
18
|
+
readonly TELEMETRY_DROPPED: "telemetry.dropped";
|
|
19
|
+
readonly FLOW_PREFIX: "flow.";
|
|
20
|
+
};
|
|
21
|
+
type App = "storefront" | "editor" | "admin";
|
|
22
|
+
|
|
23
|
+
export { type App, RESOURCE_ATTR, SPAN };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
declare const RESOURCE_ATTR: {
|
|
2
|
+
readonly SERVICE_NAME: "service.name";
|
|
3
|
+
readonly SERVICE_VERSION: "service.version";
|
|
4
|
+
readonly DEPLOYMENT_ENVIRONMENT: "deployment.environment";
|
|
5
|
+
readonly APP: "app";
|
|
6
|
+
readonly MERCHANT_ID: "merchant.id";
|
|
7
|
+
};
|
|
8
|
+
declare const SPAN: {
|
|
9
|
+
readonly JS_ERROR: "browser.js_error";
|
|
10
|
+
readonly UNHANDLED_REJECTION: "browser.unhandled_rejection";
|
|
11
|
+
readonly RESOURCE_ERROR: "browser.resource_error";
|
|
12
|
+
readonly RENDER_ERROR: "browser.render_error";
|
|
13
|
+
readonly HYDRATION_ERROR: "browser.hydration_error";
|
|
14
|
+
readonly CHUNK_ERROR: "browser.chunk_error";
|
|
15
|
+
readonly FETCH: "browser.fetch";
|
|
16
|
+
readonly WEB_VITAL: "web_vital";
|
|
17
|
+
readonly RAGE_CLICK: "ui.rage_click";
|
|
18
|
+
readonly TELEMETRY_DROPPED: "telemetry.dropped";
|
|
19
|
+
readonly FLOW_PREFIX: "flow.";
|
|
20
|
+
};
|
|
21
|
+
type App = "storefront" | "editor" | "admin";
|
|
22
|
+
|
|
23
|
+
export { type App, RESOURCE_ATTR, SPAN };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
'use strict';var r={SERVICE_NAME:"service.name",SERVICE_VERSION:"service.version",DEPLOYMENT_ENVIRONMENT:"deployment.environment",APP:"app",MERCHANT_ID:"merchant.id"},e={JS_ERROR:"browser.js_error",UNHANDLED_REJECTION:"browser.unhandled_rejection",RESOURCE_ERROR:"browser.resource_error",RENDER_ERROR:"browser.render_error",HYDRATION_ERROR:"browser.hydration_error",CHUNK_ERROR:"browser.chunk_error",FETCH:"browser.fetch",WEB_VITAL:"web_vital",RAGE_CLICK:"ui.rage_click",TELEMETRY_DROPPED:"telemetry.dropped",FLOW_PREFIX:"flow."};exports.RESOURCE_ATTR=r;exports.SPAN=e;
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export{a as RESOURCE_ATTR,b as SPAN}from'./chunk-YDBHIXG5.mjs';
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { Component, ReactNode, ErrorInfo } from 'react';
|
|
2
|
+
|
|
3
|
+
interface ObservabilityBoundaryProps {
|
|
4
|
+
/** What failed, for dashboards: a storefront widget, an editor panel, a route. */
|
|
5
|
+
kind: "widget" | "panel" | "route";
|
|
6
|
+
id: string;
|
|
7
|
+
type?: string;
|
|
8
|
+
/** Shown instead of the failed subtree; defaults to nothing. */
|
|
9
|
+
fallback?: ReactNode;
|
|
10
|
+
children?: ReactNode;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Contains a render crash to its subtree (the rest of the page stays up) and reports
|
|
14
|
+
* it, named, as a browser.render_error span. Plain React: works in any app.
|
|
15
|
+
*/
|
|
16
|
+
declare class ObservabilityBoundary extends Component<ObservabilityBoundaryProps, {
|
|
17
|
+
failed: boolean;
|
|
18
|
+
}> {
|
|
19
|
+
state: {
|
|
20
|
+
failed: boolean;
|
|
21
|
+
};
|
|
22
|
+
static getDerivedStateFromError(): {
|
|
23
|
+
failed: boolean;
|
|
24
|
+
};
|
|
25
|
+
componentDidCatch(error: Error, info: ErrorInfo): void;
|
|
26
|
+
render(): ReactNode;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* For an error page or fallback that receives an error another boundary caught (e.g. a
|
|
31
|
+
* route's error page): reports it once as a browser.render_error. React doesn't pass
|
|
32
|
+
* caught errors to the page's global error handlers, so they're otherwise unseen.
|
|
33
|
+
* `digest` (set when the error came from the server) matches the server's log line.
|
|
34
|
+
*/
|
|
35
|
+
declare function useReportError(error: Error & {
|
|
36
|
+
digest?: string;
|
|
37
|
+
}, id: string): void;
|
|
38
|
+
|
|
39
|
+
export { ObservabilityBoundary, type ObservabilityBoundaryProps, useReportError };
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { Component, ReactNode, ErrorInfo } from 'react';
|
|
2
|
+
|
|
3
|
+
interface ObservabilityBoundaryProps {
|
|
4
|
+
/** What failed, for dashboards: a storefront widget, an editor panel, a route. */
|
|
5
|
+
kind: "widget" | "panel" | "route";
|
|
6
|
+
id: string;
|
|
7
|
+
type?: string;
|
|
8
|
+
/** Shown instead of the failed subtree; defaults to nothing. */
|
|
9
|
+
fallback?: ReactNode;
|
|
10
|
+
children?: ReactNode;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Contains a render crash to its subtree (the rest of the page stays up) and reports
|
|
14
|
+
* it, named, as a browser.render_error span. Plain React: works in any app.
|
|
15
|
+
*/
|
|
16
|
+
declare class ObservabilityBoundary extends Component<ObservabilityBoundaryProps, {
|
|
17
|
+
failed: boolean;
|
|
18
|
+
}> {
|
|
19
|
+
state: {
|
|
20
|
+
failed: boolean;
|
|
21
|
+
};
|
|
22
|
+
static getDerivedStateFromError(): {
|
|
23
|
+
failed: boolean;
|
|
24
|
+
};
|
|
25
|
+
componentDidCatch(error: Error, info: ErrorInfo): void;
|
|
26
|
+
render(): ReactNode;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* For an error page or fallback that receives an error another boundary caught (e.g. a
|
|
31
|
+
* route's error page): reports it once as a browser.render_error. React doesn't pass
|
|
32
|
+
* caught errors to the page's global error handlers, so they're otherwise unseen.
|
|
33
|
+
* `digest` (set when the error came from the server) matches the server's log line.
|
|
34
|
+
*/
|
|
35
|
+
declare function useReportError(error: Error & {
|
|
36
|
+
digest?: string;
|
|
37
|
+
}, id: string): void;
|
|
38
|
+
|
|
39
|
+
export { ObservabilityBoundary, type ObservabilityBoundaryProps, useReportError };
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
"use strict";var R=Object.defineProperty;var m=Object.getOwnPropertyDescriptor;var f=Object.getOwnPropertyNames;var b=Object.prototype.hasOwnProperty;var _=(e,r)=>{for(var o in r)R(e,o,{get:r[o],enumerable:!0})},u=(e,r,o,n)=>{if(r&&typeof r=="object"||typeof r=="function")for(let t of f(r))!b.call(e,t)&&t!==o&&R(e,t,{get:()=>r[t],enumerable:!(n=m(r,t))||n.enumerable});return e};var y=e=>u(R({},"__esModule",{value:!0}),e);var h={};_(h,{ObservabilityBoundary:()=>s,useReportError:()=>a});module.exports=y(h);var c=require("react");var i={JS_ERROR:"browser.js_error",UNHANDLED_REJECTION:"browser.unhandled_rejection",RESOURCE_ERROR:"browser.resource_error",RENDER_ERROR:"browser.render_error",HYDRATION_ERROR:"browser.hydration_error",CHUNK_ERROR:"browser.chunk_error",FETCH:"browser.fetch",WEB_VITAL:"web_vital",RAGE_CLICK:"ui.rage_click",TELEMETRY_DROPPED:"telemetry.dropped",FLOW_PREFIX:"flow."};var N=Symbol.for("@shopkit/observability.reporter");function O(){return globalThis[N]}function p(e,r,o){try{O()?.recordException(e,r,o)}catch{}}var T=4e3,s=class extends c.Component{constructor(){super(...arguments);this.state={failed:!1}}static getDerivedStateFromError(){return{failed:!0}}componentDidCatch(o,n){let{kind:t,id:d,type:l}=this.props;p(i.RENDER_ERROR,o,{"component.kind":t,"component.id":d,"component.type":l,"react.component_stack":n.componentStack?.slice(0,T)})}render(){return this.state.failed?this.props.fallback??null:this.props.children}};var E=require("react");function a(e,r){(0,E.useEffect)(()=>{p(i.RENDER_ERROR,e,{"component.kind":"route","component.id":r,"error.digest":e.digest})},[e,r])}0&&(module.exports={ObservabilityBoundary,useReportError});
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import{Component as d}from"react";var o={JS_ERROR:"browser.js_error",UNHANDLED_REJECTION:"browser.unhandled_rejection",RESOURCE_ERROR:"browser.resource_error",RENDER_ERROR:"browser.render_error",HYDRATION_ERROR:"browser.hydration_error",CHUNK_ERROR:"browser.chunk_error",FETCH:"browser.fetch",WEB_VITAL:"web_vital",RAGE_CLICK:"ui.rage_click",TELEMETRY_DROPPED:"telemetry.dropped",FLOW_PREFIX:"flow."};var E=Symbol.for("@shopkit/observability.reporter");function a(){return globalThis[E]}function t(r,e,n){try{a()?.recordException(r,e,n)}catch{}}var l=4e3,i=class extends d{constructor(){super(...arguments);this.state={failed:!1}}static getDerivedStateFromError(){return{failed:!0}}componentDidCatch(n,p){let{kind:s,id:R,type:c}=this.props;t(o.RENDER_ERROR,n,{"component.kind":s,"component.id":R,"component.type":c,"react.component_stack":p.componentStack?.slice(0,l)})}render(){return this.state.failed?this.props.fallback??null:this.props.children}};import{useEffect as m}from"react";function f(r,e){m(()=>{t(o.RENDER_ERROR,r,{"component.kind":"route","component.id":e,"error.digest":r.digest})},[r,e])}export{i as ObservabilityBoundary,f as useReportError};
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { RawSourceMap } from 'source-map-js';
|
|
2
|
+
|
|
3
|
+
/** Returns the source map for a script URL, or undefined when there is none. */
|
|
4
|
+
type SourceMapLookup = (scriptUrl: string) => Promise<RawSourceMap | undefined>;
|
|
5
|
+
/**
|
|
6
|
+
* Maps on disk: scripts served under `urlPath` have their maps in `dir`, at the same
|
|
7
|
+
* relative path plus ".map" (the layout `shopkit-move-sourcemaps` writes). Node only.
|
|
8
|
+
* e.g. { urlPath: "/_next/static/", dir: ".next/obs-sourcemaps" }
|
|
9
|
+
*/
|
|
10
|
+
declare function fileSourceMaps(o: {
|
|
11
|
+
urlPath: string;
|
|
12
|
+
dir: string;
|
|
13
|
+
}): SourceMapLookup;
|
|
14
|
+
|
|
15
|
+
interface RelayOptions {
|
|
16
|
+
/** Collector OTLP/HTTP traces endpoint, e.g. http://collector:4318/v1/traces. */
|
|
17
|
+
endpoint: string;
|
|
18
|
+
/** Extra collector headers, e.g. an ingestion key. */
|
|
19
|
+
headers?: Record<string, string>;
|
|
20
|
+
/** Resource attributes set by the server; they overwrite what the browser sent. */
|
|
21
|
+
trusted: Record<string, string | undefined>;
|
|
22
|
+
/** Resolves minified stack traces. Create it once and reuse it, so parsed maps are cached. */
|
|
23
|
+
sourceMaps?: SourceMapLookup;
|
|
24
|
+
}
|
|
25
|
+
type RelayStatus = 204 | 400 | 413 | 502;
|
|
26
|
+
declare const MAX_BODY_BYTES: number;
|
|
27
|
+
/**
|
|
28
|
+
* Validates, scrubs and stamps one browser batch, then forwards it to the collector.
|
|
29
|
+
* Collector errors are never echoed back to the browser.
|
|
30
|
+
*/
|
|
31
|
+
declare function processBatch(body: string, options: RelayOptions): Promise<RelayStatus>;
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Web-standard POST handler for the browser relay, e.g. a Next.js route:
|
|
35
|
+
* `export const POST = createRelayHandler({ endpoint, trusted })`.
|
|
36
|
+
*/
|
|
37
|
+
declare function createRelayHandler(options: RelayOptions): (req: Request) => Promise<Response>;
|
|
38
|
+
|
|
39
|
+
export { MAX_BODY_BYTES, type RelayOptions, type RelayStatus, type SourceMapLookup, createRelayHandler, fileSourceMaps, processBatch };
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { RawSourceMap } from 'source-map-js';
|
|
2
|
+
|
|
3
|
+
/** Returns the source map for a script URL, or undefined when there is none. */
|
|
4
|
+
type SourceMapLookup = (scriptUrl: string) => Promise<RawSourceMap | undefined>;
|
|
5
|
+
/**
|
|
6
|
+
* Maps on disk: scripts served under `urlPath` have their maps in `dir`, at the same
|
|
7
|
+
* relative path plus ".map" (the layout `shopkit-move-sourcemaps` writes). Node only.
|
|
8
|
+
* e.g. { urlPath: "/_next/static/", dir: ".next/obs-sourcemaps" }
|
|
9
|
+
*/
|
|
10
|
+
declare function fileSourceMaps(o: {
|
|
11
|
+
urlPath: string;
|
|
12
|
+
dir: string;
|
|
13
|
+
}): SourceMapLookup;
|
|
14
|
+
|
|
15
|
+
interface RelayOptions {
|
|
16
|
+
/** Collector OTLP/HTTP traces endpoint, e.g. http://collector:4318/v1/traces. */
|
|
17
|
+
endpoint: string;
|
|
18
|
+
/** Extra collector headers, e.g. an ingestion key. */
|
|
19
|
+
headers?: Record<string, string>;
|
|
20
|
+
/** Resource attributes set by the server; they overwrite what the browser sent. */
|
|
21
|
+
trusted: Record<string, string | undefined>;
|
|
22
|
+
/** Resolves minified stack traces. Create it once and reuse it, so parsed maps are cached. */
|
|
23
|
+
sourceMaps?: SourceMapLookup;
|
|
24
|
+
}
|
|
25
|
+
type RelayStatus = 204 | 400 | 413 | 502;
|
|
26
|
+
declare const MAX_BODY_BYTES: number;
|
|
27
|
+
/**
|
|
28
|
+
* Validates, scrubs and stamps one browser batch, then forwards it to the collector.
|
|
29
|
+
* Collector errors are never echoed back to the browser.
|
|
30
|
+
*/
|
|
31
|
+
declare function processBatch(body: string, options: RelayOptions): Promise<RelayStatus>;
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Web-standard POST handler for the browser relay, e.g. a Next.js route:
|
|
35
|
+
* `export const POST = createRelayHandler({ endpoint, trusted })`.
|
|
36
|
+
*/
|
|
37
|
+
declare function createRelayHandler(options: RelayOptions): (req: Request) => Promise<Response>;
|
|
38
|
+
|
|
39
|
+
export { MAX_BODY_BYTES, type RelayOptions, type RelayStatus, type SourceMapLookup, createRelayHandler, fileSourceMaps, processBatch };
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
'use strict';var sourceMapJs=require('source-map-js');function p(e,t=()=>Date.now()){let n=t(),r=0;return function(){return t()-n>=6e4&&(n=t(),r=0),r++<e}}var h=/\?[^\s#)'"]*?(?=(?::\d+:\d+)?(?:[\s#)'"]|$))/g,w=/(?<![\w.+-])[\w.+-]+@[\w-]+(?:\.[\w-]+)+/g,A=/(?<!\w)(?:\+?91[\s-]?)?[6-9]\d{9}(?!\w)/g;function M(e){return e.replace(/\S+/g,t=>{let n=t.indexOf("#");if(n<0||!t.slice(0,n).includes("://"))return t;let r=t.slice(n).search(/[)'"]/);return t.slice(0,n)+(r<0?"":t.slice(n+r))})}function f(e){return M(e.replace(h,"")).replace(w,"[email]").replace(A,"[phone]")}var k=/\.js:(\d+):(\d+)\)?$/;function m(e){let t=k.exec(e);if(!t)return;let n=e.indexOf("http"),r=e.slice(n,t.index+3);if(!(n<0||!/^https?:\/\//.test(r)))return {url:r,line:Number(t[1]),column:Number(t[2]),before:e.slice(0,n),after:t[0].endsWith(")")?")":""}}var O=30,v=p(30),g=new WeakMap;async function x(e,t){let n=g.get(e);if(n||g.set(e,n=new Map),n.has(t))return n.get(t);if(!v())return null;let r=null;try{let s=await e(t);s&&(r=new sourceMapJs.SourceMapConsumer(s));}catch{}return n.size>=O&&n.clear(),n.set(t,r),r}var P=e=>e.replace(/^webpack:\/\/[^/]*\//,"").replace(/^\.\//,"");async function y(e,t){let n=new Set;for(let s of e.match(/\S+/g)??[]){let o=m(s);o&&n.add(o.url);}let r=new Map;for(let s of n)r.set(s,await x(t,s));return e.replace(/\S+/g,s=>{let o=m(s),a=o&&r.get(o.url)?.originalPositionFor({line:o.line,column:o.column-1});return !o||!a?.source||!a.line?s:`${o.before}${P(a.source)}:${a.line}:${a.column+1}${o.after}`})}function B(e){return async t=>{let n=new URL(t).pathname;if(!n.startsWith(e.urlPath))return;let{readFile:r}=await import('fs/promises'),s=await import('path'),o=s.resolve(e.dir),a=decodeURIComponent(n.slice(e.urlPath.length)).replace(/^\/+/,""),c=s.resolve(o,`${a}.map`);if(c.startsWith(o+s.sep))return JSON.parse(await r(c,"utf8"))}}var u=64*1024,_=200,L=5e3,i=e=>typeof e=="object"&&e!==null&&!Array.isArray(e);function N(e){let t;try{t=JSON.parse(e);}catch{return}if(!i(t)||!Array.isArray(t.resourceSpans))return;let n=0;for(let r of t.resourceSpans){if(!i(r)||!Array.isArray(r.scopeSpans)||r.resource!==void 0&&!i(r.resource))return;for(let s of r.scopeSpans){if(!i(s)||!Array.isArray(s.spans)||!s.spans.every(i))return;n+=s.spans.length;}}return n>0&&n<=_?t:void 0}function l(e){if(Array.isArray(e))for(let t of e){let n=t?.value;typeof n?.stringValue=="string"&&(n.stringValue=f(n.stringValue));}}function T(e){if(typeof e.name=="string"&&(e.name=f(e.name)),l(e.attributes),!!Array.isArray(e.events))for(let t of e.events)i(t)&&l(t.attributes);}async function E(e,t){if(Array.isArray(e.events)){for(let n of e.events)if(!(!i(n)||!Array.isArray(n.attributes)))for(let r of n.attributes){let s=r?.value;if(!(r?.key!=="exception.stacktrace"||typeof s?.stringValue!="string"))try{s.stringValue=await y(s.stringValue,t);}catch{}}}}function C(e,t){let n=new Set(Object.keys(t)),r=e.resource?.attributes,s=Array.isArray(r)?r.filter(a=>i(a)&&!n.has(String(a.key))):[],o=Object.entries(t).filter(([,a])=>a!==void 0).map(([a,c])=>({key:a,value:{stringValue:c}}));e.resource={...e.resource,attributes:[...s,...o]};}async function d(e,t){if(new TextEncoder().encode(e).length>u)return 413;let n=N(e);if(!n)return 400;for(let r of n.resourceSpans){l(r.resource?.attributes),C(r,t.trusted);for(let s of r.scopeSpans)for(let o of s.spans)T(o),t.sourceMaps&&await E(o,t.sourceMaps);}try{return (await fetch(t.endpoint,{method:"POST",headers:{...t.headers,"content-type":"application/json"},body:JSON.stringify(n),signal:AbortSignal.timeout(L)})).ok?204:502}catch{return 502}}var V=/\+https?:\/\/|Googlebot|bingbot|Applebot|crawler|spider|HeadlessChrome|Lighthouse|facebookexternalhit|WhatsApp\//i,I=[["instagram",/\bInstagram\b/],["facebook",/\bFB(AN|AV|_IAB)\b/],["snapchat",/\bSnapchat\b/],["linkedin",/\bLinkedInApp\b/],["android_webview",/; wv\)/]],b=e=>V.test(e);function S(e){return {"device.type":/iPad|Tablet/i.test(e)?"tablet":/Mobi|Android|iPhone|iPod/i.test(e)?"mobile":"desktop","browser.in_app":I.find(([,t])=>t.test(e))?.[0]}}var W=6e3;async function D(e,t){if(!e.body)return "";let n=e.body.getReader(),r=new TextDecoder,s="",o=0;for(;;){let{done:a,value:c}=await n.read();if(a)return s+r.decode();if(o+=c.byteLength,o>t){n.cancel();return}s+=r.decode(c,{stream:true});}}function F(e){let t=p(W);async function n(r){if(Number(r.headers.get("content-length"))>u)return 413;if(!t())return 429;let s=r.headers.get("user-agent")??"";if(b(s))return 204;try{let o=await D(r,u);return o===void 0?413:await d(o,{...e,trusted:{...S(s),...e.trusted}})}catch{return 400}}return async function(s){return new Response(null,{status:await n(s)})}}
|
|
2
|
+
exports.MAX_BODY_BYTES=u;exports.createRelayHandler=F;exports.fileSourceMaps=B;exports.processBatch=d;
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import {a}from'../chunk-ZNVBPNNQ.mjs';import {SourceMapConsumer}from'source-map-js';function p(e,t=()=>Date.now()){let r=t(),n=0;return function(){return t()-r>=6e4&&(r=t(),n=0),n++<e}}var w=/\.js:(\d+):(\d+)\)?$/;function m(e){let t=w.exec(e);if(!t)return;let r=e.indexOf("http"),n=e.slice(r,t.index+3);if(!(r<0||!/^https?:\/\//.test(n)))return {url:n,line:Number(t[1]),column:Number(t[2]),before:e.slice(0,r),after:t[0].endsWith(")")?")":""}}var A=30,M=p(30),y=new WeakMap;async function R(e,t){let r=y.get(e);if(r||y.set(e,r=new Map),r.has(t))return r.get(t);if(!M())return null;let n=null;try{let o=await e(t);o&&(n=new SourceMapConsumer(o));}catch{}return r.size>=A&&r.clear(),r.set(t,n),n}var k=e=>e.replace(/^webpack:\/\/[^/]*\//,"").replace(/^\.\//,"");async function g(e,t){let r=new Set;for(let o of e.match(/\S+/g)??[]){let s=m(o);s&&r.add(s.url);}let n=new Map;for(let o of r)n.set(o,await R(t,o));return e.replace(/\S+/g,o=>{let s=m(o),a=s&&n.get(s.url)?.originalPositionFor({line:s.line,column:s.column-1});return !s||!a?.source||!a.line?o:`${s.before}${k(a.source)}:${a.line}:${a.column+1}${s.after}`})}function v(e){return async t=>{let r=new URL(t).pathname;if(!r.startsWith(e.urlPath))return;let{readFile:n}=await import('fs/promises'),o=await import('path'),s=o.resolve(e.dir),a=decodeURIComponent(r.slice(e.urlPath.length)).replace(/^\/+/,""),u=o.resolve(s,`${a}.map`);if(u.startsWith(s+o.sep))return JSON.parse(await n(u,"utf8"))}}var c=64*1024,O=200,x=5e3,i=e=>typeof e=="object"&&e!==null&&!Array.isArray(e);function P(e){let t;try{t=JSON.parse(e);}catch{return}if(!i(t)||!Array.isArray(t.resourceSpans))return;let r=0;for(let n of t.resourceSpans){if(!i(n)||!Array.isArray(n.scopeSpans)||n.resource!==void 0&&!i(n.resource))return;for(let o of n.scopeSpans){if(!i(o)||!Array.isArray(o.spans)||!o.spans.every(i))return;r+=o.spans.length;}}return r>0&&r<=O?t:void 0}function l(e){if(Array.isArray(e))for(let t of e){let r=t?.value;typeof r?.stringValue=="string"&&(r.stringValue=a(r.stringValue));}}function B(e){if(typeof e.name=="string"&&(e.name=a(e.name)),l(e.attributes),!!Array.isArray(e.events))for(let t of e.events)i(t)&&l(t.attributes);}async function _(e,t){if(Array.isArray(e.events)){for(let r of e.events)if(!(!i(r)||!Array.isArray(r.attributes)))for(let n of r.attributes){let o=n?.value;if(!(n?.key!=="exception.stacktrace"||typeof o?.stringValue!="string"))try{o.stringValue=await g(o.stringValue,t);}catch{}}}}function L(e,t){let r=new Set(Object.keys(t)),n=e.resource?.attributes,o=Array.isArray(n)?n.filter(a=>i(a)&&!r.has(String(a.key))):[],s=Object.entries(t).filter(([,a])=>a!==void 0).map(([a,u])=>({key:a,value:{stringValue:u}}));e.resource={...e.resource,attributes:[...o,...s]};}async function d(e,t){if(new TextEncoder().encode(e).length>c)return 413;let r=P(e);if(!r)return 400;for(let n of r.resourceSpans){l(n.resource?.attributes),L(n,t.trusted);for(let o of n.scopeSpans)for(let s of o.spans)B(s),t.sourceMaps&&await _(s,t.sourceMaps);}try{return (await fetch(t.endpoint,{method:"POST",headers:{...t.headers,"content-type":"application/json"},body:JSON.stringify(r),signal:AbortSignal.timeout(x)})).ok?204:502}catch{return 502}}var T=/\+https?:\/\/|Googlebot|bingbot|Applebot|crawler|spider|HeadlessChrome|Lighthouse|facebookexternalhit|WhatsApp\//i,N=[["instagram",/\bInstagram\b/],["facebook",/\bFB(AN|AV|_IAB)\b/],["snapchat",/\bSnapchat\b/],["linkedin",/\bLinkedInApp\b/],["android_webview",/; wv\)/]],b=e=>T.test(e);function S(e){return {"device.type":/iPad|Tablet/i.test(e)?"tablet":/Mobi|Android|iPhone|iPod/i.test(e)?"mobile":"desktop","browser.in_app":N.find(([,t])=>t.test(e))?.[0]}}var C=6e3;async function V(e,t){if(!e.body)return "";let r=e.body.getReader(),n=new TextDecoder,o="",s=0;for(;;){let{done:a,value:u}=await r.read();if(a)return o+n.decode();if(s+=u.byteLength,s>t){r.cancel();return}o+=n.decode(u,{stream:true});}}function E(e){let t=p(C);async function r(n){if(Number(n.headers.get("content-length"))>c)return 413;if(!t())return 429;let o=n.headers.get("user-agent")??"";if(b(o))return 204;try{let s=await V(n,c);return s===void 0?413:await d(s,{...e,trusted:{...S(o),...e.trusted}})}catch{return 400}}return async function(o){return new Response(null,{status:await r(o)})}}
|
|
2
|
+
export{c as MAX_BODY_BYTES,E as createRelayHandler,v as fileSourceMaps,d as processBatch};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { Sampler, SpanProcessor } from '@opentelemetry/sdk-trace-base';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Parent-based, always-on sampler that never traces requests whose path matches `skip`
|
|
5
|
+
* (static assets, the telemetry relay): high volume, no diagnostic value.
|
|
6
|
+
*/
|
|
7
|
+
declare function skipPathsSampler(skip: RegExp): Sampler;
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Strips query strings, emails and phone numbers from span names, string attributes
|
|
11
|
+
* (frameworks put full URLs, with ad-click IDs and tokens, in both), status and events: an
|
|
12
|
+
* exception's message and stack can quote user input. Register it before
|
|
13
|
+
* the exporting processor. Mutates in onEnd: still before batch export, and some bundled
|
|
14
|
+
* SDKs never call onEnding.
|
|
15
|
+
*/
|
|
16
|
+
declare function scrubSpanProcessor(): SpanProcessor;
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Records a server error in the request's trace, for hooks like Next's `onRequestError`.
|
|
20
|
+
* An error in a streamed page render doesn't fail the request (it's already a 200), so the
|
|
21
|
+
* framework records nothing; the browser gets only a generic message and the `digest`,
|
|
22
|
+
* kept here as `error.digest` to match the two.
|
|
23
|
+
*/
|
|
24
|
+
declare function recordServerError(err: unknown): void;
|
|
25
|
+
|
|
26
|
+
export { recordServerError, scrubSpanProcessor, skipPathsSampler };
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { Sampler, SpanProcessor } from '@opentelemetry/sdk-trace-base';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Parent-based, always-on sampler that never traces requests whose path matches `skip`
|
|
5
|
+
* (static assets, the telemetry relay): high volume, no diagnostic value.
|
|
6
|
+
*/
|
|
7
|
+
declare function skipPathsSampler(skip: RegExp): Sampler;
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Strips query strings, emails and phone numbers from span names, string attributes
|
|
11
|
+
* (frameworks put full URLs, with ad-click IDs and tokens, in both), status and events: an
|
|
12
|
+
* exception's message and stack can quote user input. Register it before
|
|
13
|
+
* the exporting processor. Mutates in onEnd: still before batch export, and some bundled
|
|
14
|
+
* SDKs never call onEnding.
|
|
15
|
+
*/
|
|
16
|
+
declare function scrubSpanProcessor(): SpanProcessor;
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Records a server error in the request's trace, for hooks like Next's `onRequestError`.
|
|
20
|
+
* An error in a streamed page render doesn't fail the request (it's already a 200), so the
|
|
21
|
+
* framework records nothing; the browser gets only a generic message and the `digest`,
|
|
22
|
+
* kept here as `error.digest` to match the two.
|
|
23
|
+
*/
|
|
24
|
+
declare function recordServerError(err: unknown): void;
|
|
25
|
+
|
|
26
|
+
export { recordServerError, scrubSpanProcessor, skipPathsSampler };
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
'use strict';var api=require('@opentelemetry/api');var p=0,u=2;function d(e){let t=e["url.path"]??e["http.target"];return typeof t=="string"?t.split("?")[0]:void 0}function S(e){return {shouldSample(t,r,s,o,g){let i=d(g);if(i&&e.test(i))return {decision:p};let a=api.trace.getSpanContext(t);return a?{decision:(a.traceFlags&api.TraceFlags.SAMPLED)===api.TraceFlags.SAMPLED?u:p}:{decision:u}},toString:()=>`SkipPathsSampler(${e})`}}var b=/\?[^\s#)'"]*?(?=(?::\d+:\d+)?(?:[\s#)'"]|$))/g,E=/(?<![\w.+-])[\w.+-]+@[\w-]+(?:\.[\w-]+)+/g,R=/(?<!\w)(?:\+?91[\s-]?)?[6-9]\d{9}(?!\w)/g;function h(e){return e.replace(/\S+/g,t=>{let r=t.indexOf("#");if(r<0||!t.slice(0,r).includes("://"))return t;let s=t.slice(r).search(/[)'"]/);return t.slice(0,r)+(s<0?"":t.slice(r+s))})}function n(e){return h(e.replace(b,"")).replace(E,"[email]").replace(R,"[phone]")}function m(e){if(e)for(let[t,r]of Object.entries(e))typeof r=="string"&&(e[t]=n(r));}function P(){return {onStart(){},onEnd(e){e.name=n(e.name),m(e.attributes);for(let r of e.events)m(r.attributes);let t=e.status;t.message&&(t.message=n(t.message));},forceFlush:()=>Promise.resolve(),shutdown:()=>Promise.resolve()}}function v(e){let t=api.trace.getActiveSpan(),r=t?.isRecording()?t:api.trace.getTracer("@shopkit/observability").startSpan("server.error"),s=e instanceof Error?e.message:String(e),o=e?.digest;r.recordException(e instanceof Error?e:s),r.setStatus({code:api.SpanStatusCode.ERROR,message:s}),typeof o=="string"&&r.setAttribute("error.digest",o),r!==t&&r.end();}
|
|
2
|
+
exports.recordServerError=v;exports.scrubSpanProcessor=P;exports.skipPathsSampler=S;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import {a}from'../chunk-ZNVBPNNQ.mjs';import {trace,TraceFlags,SpanStatusCode}from'@opentelemetry/api';var c=0,m=2;function g(e){let t=e["url.path"]??e["http.target"];return typeof t=="string"?t.split("?")[0]:void 0}function l(e){return {shouldSample(t,r,n,o,d){let i=g(d);if(i&&e.test(i))return {decision:c};let a=trace.getSpanContext(t);return a?{decision:(a.traceFlags&TraceFlags.SAMPLED)===TraceFlags.SAMPLED?m:c}:{decision:m}},toString:()=>`SkipPathsSampler(${e})`}}function u(e){if(e)for(let[t,r]of Object.entries(e))typeof r=="string"&&(e[t]=a(r));}function b(){return {onStart(){},onEnd(e){e.name=a(e.name),u(e.attributes);for(let r of e.events)u(r.attributes);let t=e.status;t.message&&(t.message=a(t.message));},forceFlush:()=>Promise.resolve(),shutdown:()=>Promise.resolve()}}function E(e){let t=trace.getActiveSpan(),r=t?.isRecording()?t:trace.getTracer("@shopkit/observability").startSpan("server.error"),n=e instanceof Error?e.message:String(e),o=e?.digest;r.recordException(e instanceof Error?e:n),r.setStatus({code:SpanStatusCode.ERROR,message:n}),typeof o=="string"&&r.setAttribute("error.digest",o),r!==t&&r.end();}export{E as recordServerError,b as scrubSpanProcessor,l as skipPathsSampler};
|
package/package.json
CHANGED
|
@@ -1,6 +1,100 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@shopkit/observability",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"description": "Frontend observability for storefronts and the editor: browser errors, failed requests, web vitals and flows, sent as OTLP traces",
|
|
5
|
+
"main": "./dist/index.js",
|
|
6
|
+
"module": "./dist/index.mjs",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"bin": {
|
|
9
|
+
"shopkit-move-sourcemaps": "./dist/bin/move-sourcemaps.js"
|
|
10
|
+
},
|
|
11
|
+
"sideEffects": false,
|
|
12
|
+
"exports": {
|
|
13
|
+
".": {
|
|
14
|
+
"types": "./dist/index.d.ts",
|
|
15
|
+
"import": "./dist/index.mjs",
|
|
16
|
+
"require": "./dist/index.js"
|
|
17
|
+
},
|
|
18
|
+
"./client": {
|
|
19
|
+
"types": "./dist/client/index.d.ts",
|
|
20
|
+
"import": "./dist/client/index.mjs",
|
|
21
|
+
"require": "./dist/client/index.js"
|
|
22
|
+
},
|
|
23
|
+
"./relay": {
|
|
24
|
+
"types": "./dist/relay/index.d.ts",
|
|
25
|
+
"import": "./dist/relay/index.mjs",
|
|
26
|
+
"require": "./dist/relay/index.js"
|
|
27
|
+
},
|
|
28
|
+
"./server": {
|
|
29
|
+
"types": "./dist/server/index.d.ts",
|
|
30
|
+
"import": "./dist/server/index.mjs",
|
|
31
|
+
"require": "./dist/server/index.js"
|
|
32
|
+
},
|
|
33
|
+
"./react": {
|
|
34
|
+
"types": "./dist/react/index.d.ts",
|
|
35
|
+
"import": "./dist/react/index.mjs",
|
|
36
|
+
"require": "./dist/react/index.js"
|
|
37
|
+
},
|
|
38
|
+
"./head-stub": {
|
|
39
|
+
"types": "./dist/head-stub/index.d.ts",
|
|
40
|
+
"import": "./dist/head-stub/index.mjs",
|
|
41
|
+
"require": "./dist/head-stub/index.js"
|
|
42
|
+
}
|
|
43
|
+
},
|
|
44
|
+
"files": [
|
|
45
|
+
"dist",
|
|
46
|
+
"README.md"
|
|
47
|
+
],
|
|
48
|
+
"scripts": {
|
|
49
|
+
"build": "rm -rf dist && tsup",
|
|
50
|
+
"dev": "tsup --watch",
|
|
51
|
+
"typecheck": "tsc --noEmit",
|
|
52
|
+
"clean": "rm -rf dist",
|
|
53
|
+
"test": "vitest run",
|
|
54
|
+
"test:watch": "vitest"
|
|
55
|
+
},
|
|
56
|
+
"devDependencies": {
|
|
57
|
+
"@opentelemetry/api": "^1.9.0",
|
|
58
|
+
"@opentelemetry/sdk-trace-base": "^2.12.0",
|
|
59
|
+
"@types/node": "^20.8.10",
|
|
60
|
+
"@types/react": "^18.2.0",
|
|
61
|
+
"@types/react-dom": "^18.2.0",
|
|
62
|
+
"jsdom": "^23.0.0",
|
|
63
|
+
"react": "^18.2.0",
|
|
64
|
+
"react-dom": "^18.2.0",
|
|
65
|
+
"tsup": "^8.0.0",
|
|
66
|
+
"typescript": "^5.3.0",
|
|
67
|
+
"vitest": "^1.0.0"
|
|
68
|
+
},
|
|
69
|
+
"peerDependencies": {
|
|
70
|
+
"@opentelemetry/api": ">=1.9.0 <2.0.0",
|
|
71
|
+
"@opentelemetry/sdk-trace-base": ">=2.0.0 <3.0.0",
|
|
72
|
+
"react": ">=18.0.0"
|
|
73
|
+
},
|
|
74
|
+
"peerDependenciesMeta": {
|
|
75
|
+
"@opentelemetry/api": {
|
|
76
|
+
"optional": true
|
|
77
|
+
},
|
|
78
|
+
"@opentelemetry/sdk-trace-base": {
|
|
79
|
+
"optional": true
|
|
80
|
+
},
|
|
81
|
+
"react": {
|
|
82
|
+
"optional": true
|
|
83
|
+
}
|
|
84
|
+
},
|
|
85
|
+
"keywords": [
|
|
86
|
+
"observability",
|
|
87
|
+
"opentelemetry",
|
|
88
|
+
"otlp",
|
|
89
|
+
"monitoring",
|
|
90
|
+
"shopkit"
|
|
91
|
+
],
|
|
92
|
+
"license": "MIT",
|
|
93
|
+
"publishConfig": {
|
|
94
|
+
"access": "public"
|
|
95
|
+
},
|
|
96
|
+
"dependencies": {
|
|
97
|
+
"source-map-js": "^1.2.1",
|
|
98
|
+
"web-vitals": "^6.2.3"
|
|
99
|
+
}
|
|
100
|
+
}
|