@opentelemetry/browser-instrumentation 0.7.0 → 0.8.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.
Files changed (50) hide show
  1. package/README.md +93 -0
  2. package/dist/console/instrumentation.d.ts +1 -2
  3. package/dist/console/instrumentation.js.map +1 -1
  4. package/dist/console/types.d.ts +2 -3
  5. package/dist/errors/instrumentation.d.ts +1 -2
  6. package/dist/errors/instrumentation.js.map +1 -1
  7. package/dist/errors/types.d.ts +2 -3
  8. package/dist/fetch/index.d.ts +3 -0
  9. package/dist/fetch/index.js +2 -0
  10. package/dist/fetch/instrumentation.d.ts +35 -0
  11. package/dist/fetch/instrumentation.js +223 -0
  12. package/dist/fetch/instrumentation.js.map +1 -0
  13. package/dist/fetch/semconv.js +13 -0
  14. package/dist/fetch/semconv.js.map +1 -0
  15. package/dist/fetch/types.d.ts +51 -0
  16. package/dist/navigation/instrumentation.d.ts +1 -2
  17. package/dist/navigation/instrumentation.js.map +1 -1
  18. package/dist/navigation/types.d.ts +4 -5
  19. package/dist/navigation-timing/instrumentation.d.ts +1 -2
  20. package/dist/navigation-timing/instrumentation.js.map +1 -1
  21. package/dist/navigation-timing/types.d.ts +1 -2
  22. package/dist/package.js +1 -1
  23. package/dist/resource-timing/instrumentation.d.ts +1 -2
  24. package/dist/resource-timing/instrumentation.js +12 -11
  25. package/dist/resource-timing/instrumentation.js.map +1 -1
  26. package/dist/resource-timing/types.d.ts +1 -2
  27. package/dist/user-action/instrumentation.d.ts +1 -2
  28. package/dist/user-action/instrumentation.js.map +1 -1
  29. package/dist/user-action/types.d.ts +3 -4
  30. package/dist/utils/ContextRegistry.js +77 -0
  31. package/dist/utils/ContextRegistry.js.map +1 -0
  32. package/dist/utils/NetworkContextRegistry.js +23 -0
  33. package/dist/utils/NetworkContextRegistry.js.map +1 -0
  34. package/dist/utils/getElementCSSSelector.js.map +1 -1
  35. package/dist/utils/request.js +131 -0
  36. package/dist/utils/request.js.map +1 -0
  37. package/dist/utils/url.js +36 -0
  38. package/dist/utils/url.js.map +1 -0
  39. package/dist/web-vitals/instrumentation.d.ts +1 -2
  40. package/dist/web-vitals/instrumentation.js.map +1 -1
  41. package/dist/web-vitals/types.d.ts +1 -2
  42. package/dist/xhr/index.d.ts +3 -0
  43. package/dist/xhr/index.js +2 -0
  44. package/dist/xhr/instrumentation.d.ts +38 -0
  45. package/dist/xhr/instrumentation.js +187 -0
  46. package/dist/xhr/instrumentation.js.map +1 -0
  47. package/dist/xhr/semconv.js +13 -0
  48. package/dist/xhr/semconv.js.map +1 -0
  49. package/dist/xhr/types.d.ts +22 -0
  50. package/package.json +21 -8
@@ -0,0 +1,131 @@
1
+ import { diag } from "@opentelemetry/api";
2
+ //#region src/utils/request.ts
3
+ const DIAG_LOGGER = diag.createComponentLogger({ namespace: "@opentelemetry/browser-instrumentation/utils/request" });
4
+ function isDocument(value) {
5
+ return typeof Document !== "undefined" && value instanceof Document;
6
+ }
7
+ /**
8
+ * Helper function to determine payload content length for fetch requests
9
+ *
10
+ * The fetch API is kinda messy: there are a couple of ways the body can be passed in.
11
+ *
12
+ * In all cases, the body param can be some variation of ReadableStream,
13
+ * and ReadableStreams can only be read once! We want to avoid consuming the body here,
14
+ * because that would mean that the body never gets sent with the actual fetch request.
15
+ *
16
+ * Either the first arg is a Request object, which can be cloned
17
+ * so we can clone that object and read the body of the clone
18
+ * without disturbing the original argument
19
+ * However, reading the body here can only be done async; the body() method returns a promise
20
+ * this means this entire function has to return a promise
21
+ *
22
+ * OR the first arg is a url/string
23
+ * in which case the second arg has type RequestInit
24
+ * RequestInit is NOT cloneable, but RequestInit.body is writable
25
+ * so we can chain it into ReadableStream.pipeThrough()
26
+ *
27
+ * ReadableStream.pipeThrough() lets us process a stream and returns a new stream
28
+ * So we can measure the body length as it passes through the pipe, but need to attach
29
+ * the new stream to the original request
30
+ * so that the browser still has access to the body.
31
+ */
32
+ async function getFetchBodyLength(...args) {
33
+ if (args[0] instanceof URL || typeof args[0] === "string") {
34
+ const requestInit = args[1];
35
+ if (!requestInit?.body) return;
36
+ if (requestInit.body instanceof ReadableStream) {
37
+ const { body, length } = _getBodyNonDestructively(requestInit.body);
38
+ requestInit.body = body;
39
+ return await length;
40
+ } else return getXHRBodyLength(requestInit.body);
41
+ } else {
42
+ const info = args[0];
43
+ if (!info?.body) return;
44
+ return getByteLength(await info.clone().text());
45
+ }
46
+ }
47
+ function _getBodyNonDestructively(body) {
48
+ if (!body.pipeThrough) {
49
+ DIAG_LOGGER.warn("Platform has ReadableStream but not pipeThrough!");
50
+ return {
51
+ body,
52
+ length: Promise.resolve(void 0)
53
+ };
54
+ }
55
+ let length = 0;
56
+ let resolveLength;
57
+ const lengthPromise = new Promise((resolve) => {
58
+ resolveLength = resolve;
59
+ });
60
+ const bodyReader = body.getReader();
61
+ return {
62
+ body: new ReadableStream({
63
+ async pull(controller) {
64
+ const { value, done } = await bodyReader.read();
65
+ length += value?.byteLength || 0;
66
+ if (done) {
67
+ controller.close();
68
+ bodyReader.releaseLock();
69
+ resolveLength(length);
70
+ return;
71
+ }
72
+ controller.enqueue(value);
73
+ },
74
+ cancel(reason) {
75
+ resolveLength(void 0);
76
+ bodyReader.cancel(reason);
77
+ }
78
+ }),
79
+ length: lengthPromise
80
+ };
81
+ }
82
+ /**
83
+ * Helper function to determine payload content length for XHR requests
84
+ */
85
+ function getXHRBodyLength(body) {
86
+ if (isDocument(body)) return new XMLSerializer().serializeToString(body).length;
87
+ if (typeof body === "string") return getByteLength(body);
88
+ if (body instanceof Blob) return body.size;
89
+ if (body instanceof FormData) return getFormDataSize(body);
90
+ if (body instanceof URLSearchParams) return getByteLength(body.toString());
91
+ if (body.byteLength !== void 0) return body.byteLength;
92
+ DIAG_LOGGER.warn("unknown body type");
93
+ }
94
+ const TEXT_ENCODER = new TextEncoder();
95
+ function getByteLength(s) {
96
+ return TEXT_ENCODER.encode(s).byteLength;
97
+ }
98
+ function getFormDataSize(formData) {
99
+ let size = 0;
100
+ for (const [key, value] of formData.entries()) {
101
+ size += key.length;
102
+ if (value instanceof Blob) size += value.size;
103
+ else size += value.length;
104
+ }
105
+ return size;
106
+ }
107
+ const DEFAULT_KNOWN_METHODS = {
108
+ CONNECT: true,
109
+ DELETE: true,
110
+ GET: true,
111
+ HEAD: true,
112
+ OPTIONS: true,
113
+ PATCH: true,
114
+ POST: true,
115
+ PUT: true,
116
+ TRACE: true,
117
+ QUERY: true
118
+ };
119
+ /**
120
+ * Normalize an HTTP request method string per `http.request.method` spec
121
+ * https://github.com/open-telemetry/semantic-conventions/blob/main/docs/http/http-spans.md#http-client-span
122
+ */
123
+ function normalizeHttpRequestMethod(method) {
124
+ const methUpper = method.toUpperCase();
125
+ if (Object.hasOwn(DEFAULT_KNOWN_METHODS, methUpper)) return methUpper;
126
+ return "_OTHER";
127
+ }
128
+ //#endregion
129
+ export { getFetchBodyLength, getXHRBodyLength, normalizeHttpRequestMethod };
130
+
131
+ //# sourceMappingURL=request.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"request.js","names":[],"sources":["../../src/utils/request.ts"],"sourcesContent":["/*\n * Copyright The OpenTelemetry Authors\n * SPDX-License-Identifier: Apache-2.0\n */\n\nimport { diag } from '@opentelemetry/api';\n\nconst DIAG_LOGGER = diag.createComponentLogger({\n namespace: '@opentelemetry/browser-instrumentation/utils/request',\n});\n\nfunction isDocument(value: unknown): value is Document {\n return typeof Document !== 'undefined' && value instanceof Document;\n}\n\n/**\n * Helper function to determine payload content length for fetch requests\n *\n * The fetch API is kinda messy: there are a couple of ways the body can be passed in.\n *\n * In all cases, the body param can be some variation of ReadableStream,\n * and ReadableStreams can only be read once! We want to avoid consuming the body here,\n * because that would mean that the body never gets sent with the actual fetch request.\n *\n * Either the first arg is a Request object, which can be cloned\n * so we can clone that object and read the body of the clone\n * without disturbing the original argument\n * However, reading the body here can only be done async; the body() method returns a promise\n * this means this entire function has to return a promise\n *\n * OR the first arg is a url/string\n * in which case the second arg has type RequestInit\n * RequestInit is NOT cloneable, but RequestInit.body is writable\n * so we can chain it into ReadableStream.pipeThrough()\n *\n * ReadableStream.pipeThrough() lets us process a stream and returns a new stream\n * So we can measure the body length as it passes through the pipe, but need to attach\n * the new stream to the original request\n * so that the browser still has access to the body.\n */\nexport async function getFetchBodyLength(\n ...args: Parameters<typeof fetch>\n): Promise<number | undefined> {\n if (args[0] instanceof URL || typeof args[0] === 'string') {\n const requestInit = args[1];\n if (!requestInit?.body) {\n return undefined;\n }\n if (requestInit.body instanceof ReadableStream) {\n const { body, length } = _getBodyNonDestructively(requestInit.body);\n requestInit.body = body;\n\n return await length;\n } else {\n return getXHRBodyLength(requestInit.body);\n }\n } else {\n const info = args[0];\n // Request.body has limited availability. ref: https://developer.mozilla.org/en-US/docs/Web/API/Request/body\n // Accessing it here triggers a eslint error from the baseline plugin\n /* eslint-disable baseline-js/use-baseline */\n if (!info?.body) {\n return undefined;\n }\n /* eslint-enable baseline-js/use-baseline */\n\n const text = await info.clone().text();\n return getByteLength(text);\n }\n}\n\n// This function returns the body right away piped to a transform stream\n// if available. The length is returned as a promise because the original\n// fetch logic has to consume the body to get the final value.\nfunction _getBodyNonDestructively(body: ReadableStream): {\n body: ReadableStream;\n length: Promise<number | undefined>;\n} {\n // can't read a ReadableStream without destroying it\n // but we CAN pipe it through and return a new ReadableStream\n\n // some (older) platforms don't expose the pipeThrough method and in that scenario, we're out of luck;\n // there's no way to read the stream without consuming it.\n if (!body.pipeThrough) {\n DIAG_LOGGER.warn('Platform has ReadableStream but not pipeThrough!');\n return {\n body,\n length: Promise.resolve(undefined),\n };\n }\n\n let length = 0;\n let resolveLength: (l: number | undefined) => void;\n const lengthPromise = new Promise<number | undefined>((resolve) => {\n resolveLength = resolve;\n });\n\n const bodyReader = (body as ReadableStream<Uint8Array>).getReader();\n const bodyWrapper = new ReadableStream<Uint8Array>({\n async pull(controller) {\n const { value, done } = await bodyReader.read();\n\n length += value?.byteLength || 0;\n if (done) {\n controller.close();\n bodyReader.releaseLock();\n resolveLength(length);\n return;\n }\n controller.enqueue(value);\n },\n cancel(reason) {\n resolveLength(undefined);\n bodyReader.cancel(reason);\n },\n });\n\n return {\n body: bodyWrapper,\n length: lengthPromise,\n };\n}\n\n/**\n * Helper function to determine payload content length for XHR requests\n */\nexport function getXHRBodyLength(\n body: Document | XMLHttpRequestBodyInit,\n): number | undefined {\n if (isDocument(body)) {\n return new XMLSerializer().serializeToString(body).length;\n }\n\n // XMLHttpRequestBodyInit expands to the following:\n if (typeof body === 'string') {\n return getByteLength(body);\n }\n\n if (body instanceof Blob) {\n return body.size;\n }\n\n if (body instanceof FormData) {\n return getFormDataSize(body);\n }\n\n if (body instanceof URLSearchParams) {\n return getByteLength(body.toString());\n }\n\n // ArrayBuffer | ArrayBufferView\n if (body.byteLength !== undefined) {\n return body.byteLength;\n }\n\n DIAG_LOGGER.warn('unknown body type');\n return undefined;\n}\n\nconst TEXT_ENCODER = new TextEncoder();\nfunction getByteLength(s: string): number {\n return TEXT_ENCODER.encode(s).byteLength;\n}\n\nfunction getFormDataSize(formData: FormData): number {\n let size = 0;\n for (const [key, value] of formData.entries()) {\n size += key.length;\n if (value instanceof Blob) {\n size += value.size;\n } else {\n size += value.length;\n }\n }\n return size;\n}\n\nconst DEFAULT_KNOWN_METHODS = {\n CONNECT: true,\n DELETE: true,\n GET: true,\n HEAD: true,\n OPTIONS: true,\n PATCH: true,\n POST: true,\n PUT: true,\n TRACE: true,\n // QUERY from https://datatracker.ietf.org/doc/draft-ietf-httpbis-safe-method-w-body/\n QUERY: true,\n};\n/**\n * Normalize an HTTP request method string per `http.request.method` spec\n * https://github.com/open-telemetry/semantic-conventions/blob/main/docs/http/http-spans.md#http-client-span\n */\nexport function normalizeHttpRequestMethod(method: string): string {\n const methUpper = method.toUpperCase();\n if (Object.hasOwn(DEFAULT_KNOWN_METHODS, methUpper)) {\n return methUpper;\n }\n return '_OTHER';\n}\n"],"mappings":";;AAOA,MAAM,cAAc,KAAK,sBAAsB,EAC7C,WAAW,uDACb,CAAC;AAED,SAAS,WAAW,OAAmC;CACrD,OAAO,OAAO,aAAa,eAAe,iBAAiB;AAC7D;;;;;;;;;;;;;;;;;;;;;;;;;;AA2BA,eAAsB,mBACpB,GAAG,MAC0B;CAC7B,IAAI,KAAK,cAAc,OAAO,OAAO,KAAK,OAAO,UAAU;EACzD,MAAM,cAAc,KAAK;EACzB,IAAI,CAAC,aAAa,MAChB;EAEF,IAAI,YAAY,gBAAgB,gBAAgB;GAC9C,MAAM,EAAE,MAAM,WAAW,yBAAyB,YAAY,IAAI;GAClE,YAAY,OAAO;GAEnB,OAAO,MAAM;EACf,OACE,OAAO,iBAAiB,YAAY,IAAI;CAE5C,OAAO;EACL,MAAM,OAAO,KAAK;EAIlB,IAAI,CAAC,MAAM,MACT;EAKF,OAAO,cAAc,MADF,KAAK,MAAM,CAAC,CAAC,KAAK,CACZ;CAC3B;AACF;AAKA,SAAS,yBAAyB,MAGhC;CAMA,IAAI,CAAC,KAAK,aAAa;EACrB,YAAY,KAAK,kDAAkD;EACnE,OAAO;GACL;GACA,QAAQ,QAAQ,QAAQ,KAAA,CAAS;EACnC;CACF;CAEA,IAAI,SAAS;CACb,IAAI;CACJ,MAAM,gBAAgB,IAAI,SAA6B,YAAY;EACjE,gBAAgB;CAClB,CAAC;CAED,MAAM,aAAc,KAAoC,UAAU;CAoBlE,OAAO;EACL,MAAM,IApBgB,eAA2B;GACjD,MAAM,KAAK,YAAY;IACrB,MAAM,EAAE,OAAO,SAAS,MAAM,WAAW,KAAK;IAE9C,UAAU,OAAO,cAAc;IAC/B,IAAI,MAAM;KACR,WAAW,MAAM;KACjB,WAAW,YAAY;KACvB,cAAc,MAAM;KACpB;IACF;IACA,WAAW,QAAQ,KAAK;GAC1B;GACA,OAAO,QAAQ;IACb,cAAc,KAAA,CAAS;IACvB,WAAW,OAAO,MAAM;GAC1B;EACF,CAGkB;EAChB,QAAQ;CACV;AACF;;;;AAKA,SAAgB,iBACd,MACoB;CACpB,IAAI,WAAW,IAAI,GACjB,OAAO,IAAI,cAAc,CAAC,CAAC,kBAAkB,IAAI,CAAC,CAAC;CAIrD,IAAI,OAAO,SAAS,UAClB,OAAO,cAAc,IAAI;CAG3B,IAAI,gBAAgB,MAClB,OAAO,KAAK;CAGd,IAAI,gBAAgB,UAClB,OAAO,gBAAgB,IAAI;CAG7B,IAAI,gBAAgB,iBAClB,OAAO,cAAc,KAAK,SAAS,CAAC;CAItC,IAAI,KAAK,eAAe,KAAA,GACtB,OAAO,KAAK;CAGd,YAAY,KAAK,mBAAmB;AAEtC;AAEA,MAAM,eAAe,IAAI,YAAY;AACrC,SAAS,cAAc,GAAmB;CACxC,OAAO,aAAa,OAAO,CAAC,CAAC,CAAC;AAChC;AAEA,SAAS,gBAAgB,UAA4B;CACnD,IAAI,OAAO;CACX,KAAK,MAAM,CAAC,KAAK,UAAU,SAAS,QAAQ,GAAG;EAC7C,QAAQ,IAAI;EACZ,IAAI,iBAAiB,MACnB,QAAQ,MAAM;OAEd,QAAQ,MAAM;CAElB;CACA,OAAO;AACT;AAEA,MAAM,wBAAwB;CAC5B,SAAS;CACT,QAAQ;CACR,KAAK;CACL,MAAM;CACN,SAAS;CACT,OAAO;CACP,MAAM;CACN,KAAK;CACL,OAAO;CAEP,OAAO;AACT;;;;;AAKA,SAAgB,2BAA2B,QAAwB;CACjE,MAAM,YAAY,OAAO,YAAY;CACrC,IAAI,OAAO,OAAO,uBAAuB,SAAS,GAChD,OAAO;CAET,OAAO;AACT"}
@@ -0,0 +1,36 @@
1
+ //#region src/utils/url.ts
2
+ /**
3
+ * Parses a URL, resolving relative URLs against document.baseURI (or location.href)
4
+ * @param url
5
+ */
6
+ function parseUrl(url) {
7
+ return new URL(url, document?.baseURI || location?.href);
8
+ }
9
+ /**
10
+ * Tells if the given URL matches any of the string|RegExp provided in the list
11
+ * @param url
12
+ * @param urlsToMatch
13
+ */
14
+ function matchesUrl(url, urlsToMatch) {
15
+ if (!urlsToMatch) return false;
16
+ for (const urlToMatch of urlsToMatch) if (typeof urlToMatch === "string") {
17
+ if (url === urlToMatch) return true;
18
+ } else if (url.match(urlToMatch)) return true;
19
+ return false;
20
+ }
21
+ const HTTP_PORT_FROM_PROTOCOL = {
22
+ "https:": "443",
23
+ "http:": "80"
24
+ };
25
+ /**
26
+ * Extracts the server port from the given URL object
27
+ */
28
+ function serverPortFromUrl(url) {
29
+ const serverPort = Number(url.port || HTTP_PORT_FROM_PROTOCOL[url.protocol]);
30
+ if (serverPort && !Number.isNaN(serverPort)) return serverPort;
31
+ else return;
32
+ }
33
+ //#endregion
34
+ export { matchesUrl, parseUrl, serverPortFromUrl };
35
+
36
+ //# sourceMappingURL=url.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"url.js","names":[],"sources":["../../src/utils/url.ts"],"sourcesContent":["/*\n * Copyright The OpenTelemetry Authors\n * SPDX-License-Identifier: Apache-2.0\n */\n\nconst SENSITIVE_PARAMS = [\n 'password',\n 'passwd',\n 'secret',\n 'api_key',\n 'apikey',\n 'auth',\n 'authorization',\n 'token',\n 'access_token',\n 'refresh_token',\n 'jwt',\n 'session',\n 'sessionid',\n 'key',\n 'private_key',\n 'client_secret',\n 'client_id',\n 'signature',\n 'hash',\n];\n\n/**\n * Default URL sanitization function that redacts credentials and sensitive query parameters.\n * This is the default implementation used when no custom sanitizeUrl callback is provided.\n *\n * @param url - The URL to sanitize\n * @returns The sanitized URL with credentials and sensitive parameters redacted\n */\nexport function defaultSanitizeUrl(url: string): string {\n try {\n const urlObj = new URL(url);\n if (urlObj.username || urlObj.password) {\n urlObj.username = 'REDACTED';\n urlObj.password = 'REDACTED';\n }\n for (const param of SENSITIVE_PARAMS) {\n if (urlObj.searchParams.has(param)) {\n urlObj.searchParams.set(param, 'REDACTED');\n }\n }\n return urlObj.toString();\n } catch {\n let sanitized = url.replace(/\\/\\/[^:/@]+:[^/@]+@/, '//REDACTED:REDACTED@');\n for (const param of SENSITIVE_PARAMS) {\n const regex = new RegExp(`([?&]${param}(?:%3D|=))[^&]*`, 'gi');\n sanitized = sanitized.replace(regex, '$1REDACTED');\n }\n return sanitized;\n }\n}\n\n/**\n * Parses a URL, resolving relative URLs against document.baseURI (or location.href)\n * @param url\n */\nexport function parseUrl(url: string): URL {\n return new URL(url, document?.baseURI || location?.href);\n}\n\n/**\n * Tells if the given URL matches any of the string|RegExp provided in the list\n * @param url\n * @param urlsToMatch\n */\nexport function matchesUrl(\n url: string,\n urlsToMatch?: Array<string | RegExp>,\n): boolean {\n if (!urlsToMatch) {\n return false;\n }\n\n for (const urlToMatch of urlsToMatch) {\n if (typeof urlToMatch === 'string') {\n if (url === urlToMatch) {\n return true;\n }\n } else if (url.match(urlToMatch)) {\n return true;\n }\n }\n return false;\n}\n\nconst HTTP_PORT_FROM_PROTOCOL: { [key: string]: string } = {\n 'https:': '443',\n 'http:': '80',\n};\n/**\n * Extracts the server port from the given URL object\n */\nexport function serverPortFromUrl(url: URL): number | undefined {\n const serverPort = Number(url.port || HTTP_PORT_FROM_PROTOCOL[url.protocol]);\n // Guard with `if (serverPort)` because `Number('') === 0`.\n if (serverPort && !Number.isNaN(serverPort)) {\n return serverPort;\n } else {\n return undefined;\n }\n}\n"],"mappings":";;;;;AA6DA,SAAgB,SAAS,KAAkB;CACzC,OAAO,IAAI,IAAI,KAAK,UAAU,WAAW,UAAU,IAAI;AACzD;;;;;;AAOA,SAAgB,WACd,KACA,aACS;CACT,IAAI,CAAC,aACH,OAAO;CAGT,KAAK,MAAM,cAAc,aACvB,IAAI,OAAO,eAAe,UACpB;MAAA,QAAQ,YACV,OAAO;CAAA,OAEJ,IAAI,IAAI,MAAM,UAAU,GAC7B,OAAO;CAGX,OAAO;AACT;AAEA,MAAM,0BAAqD;CACzD,UAAU;CACV,SAAS;AACX;;;;AAIA,SAAgB,kBAAkB,KAA8B;CAC9D,MAAM,aAAa,OAAO,IAAI,QAAQ,wBAAwB,IAAI,SAAS;CAE3E,IAAI,cAAc,CAAC,OAAO,MAAM,UAAU,GACxC,OAAO;MAEP;AAEJ"}
@@ -9,7 +9,7 @@ import { InstrumentationBase } from "@opentelemetry/instrumentation";
9
9
  * registered. Calling `disable()` will stop emitting logs, but the underlying
10
10
  * listeners remain active. Calling `enable()` again will resume emission.
11
11
  */
12
- declare class WebVitalsInstrumentation extends InstrumentationBase<WebVitalsInstrumentationConfig> {
12
+ export declare class WebVitalsInstrumentation extends InstrumentationBase<WebVitalsInstrumentationConfig> {
13
13
  private _isEnabled;
14
14
  private _listenersRegistered;
15
15
  private _applyCustomLogRecordData?;
@@ -35,5 +35,4 @@ declare class WebVitalsInstrumentation extends InstrumentationBase<WebVitalsInst
35
35
  private _emitWebVital;
36
36
  }
37
37
  //#endregion
38
- export { WebVitalsInstrumentation };
39
38
  //# sourceMappingURL=instrumentation.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"instrumentation.js","names":[],"sources":["../../src/web-vitals/instrumentation.ts"],"sourcesContent":["/*\n * Copyright The OpenTelemetry Authors\n * SPDX-License-Identifier: Apache-2.0\n */\n\nimport type { Attributes } from '@opentelemetry/api';\nimport type { LogRecord } from '@opentelemetry/api-logs';\nimport { SeverityNumber } from '@opentelemetry/api-logs';\nimport {\n InstrumentationBase,\n safeExecuteInTheMiddle,\n} from '@opentelemetry/instrumentation';\nimport type {\n CLSMetricWithAttribution,\n INPMetricWithAttribution,\n MetricWithAttribution,\n} from 'web-vitals/attribution';\nimport { onCLS, onFCP, onINP, onLCP, onTTFB } from 'web-vitals/attribution';\nimport { version } from '../../package.json' with { type: 'json' };\nimport {\n ATTR_WEB_VITAL_DELTA,\n ATTR_WEB_VITAL_ID,\n ATTR_WEB_VITAL_NAME,\n ATTR_WEB_VITAL_NAVIGATION_TYPE,\n ATTR_WEB_VITAL_RATING,\n ATTR_WEB_VITAL_VALUE,\n WEB_VITAL_EVENT_NAME,\n} from './semconv.ts';\nimport type { WebVitalsInstrumentationConfig } from './types.ts';\n\n/**\n * Instrumentation for Core Web Vitals using the `web-vitals` library.\n * https://github.com/GoogleChrome/web-vitals\n *\n * Note: The `web-vitals` library does not support removing listeners once\n * registered. Calling `disable()` will stop emitting logs, but the underlying\n * listeners remain active. Calling `enable()` again will resume emission.\n */\nexport class WebVitalsInstrumentation extends InstrumentationBase<WebVitalsInstrumentationConfig> {\n // Using `declare` is required here: InstrumentationBase calls enable() during\n // construction, and standard field initialization would reset this flag after\n // super() returns, breaking the duplicate-registration guard.\n private declare _isEnabled: boolean;\n private declare _listenersRegistered: boolean;\n private _applyCustomLogRecordData?: (logRecord: LogRecord) => void;\n private _includeRawAttribution: boolean;\n\n constructor(config: WebVitalsInstrumentationConfig = {}) {\n super('@opentelemetry/browser-instrumentation/web-vitals', version, config);\n this._applyCustomLogRecordData = config.applyCustomLogRecordData;\n this._includeRawAttribution = config.includeRawAttribution ?? false;\n }\n\n protected override init() {\n return [];\n }\n\n /**\n * Enables the instrumentation and registers web-vitals listeners.\n * Listeners are registered only once. If disabled, subsequent calls resume emission.\n */\n override enable(): void {\n if (typeof PerformanceObserver === 'undefined') {\n this._diag.debug(\n 'PerformanceObserver not supported, web vitals will not be collected',\n );\n return;\n }\n\n this._isEnabled = true;\n\n if (this._listenersRegistered) {\n this._diag.debug('Listeners already registered, resuming emission');\n return;\n }\n\n this._listenersRegistered = true;\n this._diag.debug(`Registering listeners`);\n // CLS is only supported in Chromium. See:\n // https://github.com/GoogleChrome/web-vitals?tab=readme-ov-file#browser-support\n onCLS((metric) => this._emitWebVital(metric));\n onINP((metric) => this._emitWebVital(metric));\n onLCP((metric) => this._emitWebVital(metric));\n onFCP((metric) => this._emitWebVital(metric));\n onTTFB((metric) => this._emitWebVital(metric));\n }\n\n /**\n * Disables the instrumentation, pausing log emission.\n * Listeners remain active due to web-vitals library limitations.\n */\n override disable(): void {\n this._isEnabled = false;\n this._diag.debug('Instrumentation disabled, pausing emission');\n }\n\n /**\n * Gets the timestamp for a metric based on attribution timing.\n * Returns undefined to let OTel use the current time for metrics without\n * specific timing information.\n */\n private _getTimestampForMetric(\n metric: MetricWithAttribution,\n ): number | undefined {\n if (metric.name === 'CLS') {\n const { attribution } = metric as CLSMetricWithAttribution;\n if (attribution.largestShiftTime !== undefined) {\n return attribution.largestShiftTime;\n }\n return undefined;\n }\n if (metric.name === 'INP') {\n const { attribution } = metric as INPMetricWithAttribution;\n return attribution.interactionTime;\n }\n // FCP, LCP, TTFB: metric.value is already DOMHighResTimeStamp of the event\n return metric.value;\n }\n\n private _emitWebVital(metric: MetricWithAttribution): void {\n if (!this._isEnabled) {\n return;\n }\n const attributes: Attributes = {\n [ATTR_WEB_VITAL_NAME]: metric.name.toLowerCase(),\n [ATTR_WEB_VITAL_VALUE]: metric.value,\n // `delta` equals `value` on the first emission; subsequent emissions report only the change\n [ATTR_WEB_VITAL_DELTA]: metric.delta,\n [ATTR_WEB_VITAL_RATING]: metric.rating,\n [ATTR_WEB_VITAL_ID]: metric.id,\n [ATTR_WEB_VITAL_NAVIGATION_TYPE]: metric.navigationType,\n };\n\n const timestamp = this._getTimestampForMetric(metric);\n\n const logRecord: LogRecord = {\n eventName: WEB_VITAL_EVENT_NAME,\n severityNumber: SeverityNumber.INFO,\n attributes,\n ...(this._includeRawAttribution\n ? { body: JSON.stringify(metric.attribution) }\n : {}),\n ...(timestamp !== undefined ? { timestamp } : {}),\n };\n\n if (this._applyCustomLogRecordData) {\n safeExecuteInTheMiddle(\n () => this._applyCustomLogRecordData?.(logRecord),\n (error) => {\n if (error) {\n this._diag.error('applyCustomLogRecordData hook failed', error);\n }\n },\n true,\n );\n }\n\n this.logger.emit(logRecord);\n }\n}\n"],"mappings":";;;;;;;;;;;;;;AAsCA,IAAa,2BAAb,cAA8C,oBAAoD;CAMhG;CACA;CAEA,YAAY,SAAyC,CAAC,GAAG;EACvD,MAAM,qDAAqD,SAAS,MAAM;EAC1E,KAAK,4BAA4B,OAAO;EACxC,KAAK,yBAAyB,OAAO,yBAAyB;CAChE;CAEA,OAA0B;EACxB,OAAO,CAAC;CACV;;;;;CAMA,SAAwB;EACtB,IAAI,OAAO,wBAAwB,aAAa;GAC9C,KAAK,MAAM,MACT,qEACF;GACA;EACF;EAEA,KAAK,aAAa;EAElB,IAAI,KAAK,sBAAsB;GAC7B,KAAK,MAAM,MAAM,iDAAiD;GAClE;EACF;EAEA,KAAK,uBAAuB;EAC5B,KAAK,MAAM,MAAM,uBAAuB;EAGxC,OAAO,WAAW,KAAK,cAAc,MAAM,CAAC;EAC5C,OAAO,WAAW,KAAK,cAAc,MAAM,CAAC;EAC5C,OAAO,WAAW,KAAK,cAAc,MAAM,CAAC;EAC5C,OAAO,WAAW,KAAK,cAAc,MAAM,CAAC;EAC5C,QAAQ,WAAW,KAAK,cAAc,MAAM,CAAC;CAC/C;;;;;CAMA,UAAyB;EACvB,KAAK,aAAa;EAClB,KAAK,MAAM,MAAM,4CAA4C;CAC/D;;;;;;CAOA,uBACE,QACoB;EACpB,IAAI,OAAO,SAAS,OAAO;GACzB,MAAM,EAAE,gBAAgB;GACxB,IAAI,YAAY,qBAAqB,KAAA,GACnC,OAAO,YAAY;GAErB;EACF;EACA,IAAI,OAAO,SAAS,OAAO;GACzB,MAAM,EAAE,gBAAgB;GACxB,OAAO,YAAY;EACrB;EAEA,OAAO,OAAO;CAChB;CAEA,cAAsB,QAAqC;EACzD,IAAI,CAAC,KAAK,YACR;EAEF,MAAM,aAAyB;IAC5B,sBAAsB,OAAO,KAAK,YAAY;IAC9C,uBAAuB,OAAO;IAE9B,uBAAuB,OAAO;IAC9B,wBAAwB,OAAO;IAC/B,oBAAoB,OAAO;IAC3B,iCAAiC,OAAO;EAC3C;EAEA,MAAM,YAAY,KAAK,uBAAuB,MAAM;EAEpD,MAAM,YAAuB;GAC3B,WAAW;GACX,gBAAgB,eAAe;GAC/B;GACA,GAAI,KAAK,yBACL,EAAE,MAAM,KAAK,UAAU,OAAO,WAAW,EAAE,IAC3C,CAAC;GACL,GAAI,cAAc,KAAA,IAAY,EAAE,UAAU,IAAI,CAAC;EACjD;EAEA,IAAI,KAAK,2BACP,6BACQ,KAAK,4BAA4B,SAAS,IAC/C,UAAU;GACT,IAAI,OACF,KAAK,MAAM,MAAM,wCAAwC,KAAK;EAElE,GACA,IACF;EAGF,KAAK,OAAO,KAAK,SAAS;CAC5B;AACF"}
1
+ {"version":3,"file":"instrumentation.js","names":[],"sources":["../../src/web-vitals/instrumentation.ts"],"sourcesContent":["/*\n * Copyright The OpenTelemetry Authors\n * SPDX-License-Identifier: Apache-2.0\n */\n\nimport type { Attributes } from '@opentelemetry/api';\nimport type { LogRecord } from '@opentelemetry/api-logs';\nimport { SeverityNumber } from '@opentelemetry/api-logs';\nimport {\n InstrumentationBase,\n safeExecuteInTheMiddle,\n} from '@opentelemetry/instrumentation';\nimport type {\n CLSMetricWithAttribution,\n INPMetricWithAttribution,\n MetricWithAttribution,\n} from 'web-vitals/attribution';\nimport { onCLS, onFCP, onINP, onLCP, onTTFB } from 'web-vitals/attribution';\nimport { version } from '../../package.json' with { type: 'json' };\nimport {\n ATTR_WEB_VITAL_DELTA,\n ATTR_WEB_VITAL_ID,\n ATTR_WEB_VITAL_NAME,\n ATTR_WEB_VITAL_NAVIGATION_TYPE,\n ATTR_WEB_VITAL_RATING,\n ATTR_WEB_VITAL_VALUE,\n WEB_VITAL_EVENT_NAME,\n} from './semconv.ts';\nimport type { WebVitalsInstrumentationConfig } from './types.ts';\n\n/**\n * Instrumentation for Core Web Vitals using the `web-vitals` library.\n * https://github.com/GoogleChrome/web-vitals\n *\n * Note: The `web-vitals` library does not support removing listeners once\n * registered. Calling `disable()` will stop emitting logs, but the underlying\n * listeners remain active. Calling `enable()` again will resume emission.\n */\nexport class WebVitalsInstrumentation extends InstrumentationBase<WebVitalsInstrumentationConfig> {\n // Using `declare` is required here: InstrumentationBase calls enable() during\n // construction, and standard field initialization would reset this flag after\n // super() returns, breaking the duplicate-registration guard.\n declare private _isEnabled: boolean;\n declare private _listenersRegistered: boolean;\n private _applyCustomLogRecordData?: (logRecord: LogRecord) => void;\n private _includeRawAttribution: boolean;\n\n constructor(config: WebVitalsInstrumentationConfig = {}) {\n super('@opentelemetry/browser-instrumentation/web-vitals', version, config);\n this._applyCustomLogRecordData = config.applyCustomLogRecordData;\n this._includeRawAttribution = config.includeRawAttribution ?? false;\n }\n\n protected override init() {\n return [];\n }\n\n /**\n * Enables the instrumentation and registers web-vitals listeners.\n * Listeners are registered only once. If disabled, subsequent calls resume emission.\n */\n override enable(): void {\n if (typeof PerformanceObserver === 'undefined') {\n this._diag.debug(\n 'PerformanceObserver not supported, web vitals will not be collected',\n );\n return;\n }\n\n this._isEnabled = true;\n\n if (this._listenersRegistered) {\n this._diag.debug('Listeners already registered, resuming emission');\n return;\n }\n\n this._listenersRegistered = true;\n this._diag.debug(`Registering listeners`);\n // CLS is only supported in Chromium. See:\n // https://github.com/GoogleChrome/web-vitals?tab=readme-ov-file#browser-support\n onCLS((metric) => this._emitWebVital(metric));\n onINP((metric) => this._emitWebVital(metric));\n onLCP((metric) => this._emitWebVital(metric));\n onFCP((metric) => this._emitWebVital(metric));\n onTTFB((metric) => this._emitWebVital(metric));\n }\n\n /**\n * Disables the instrumentation, pausing log emission.\n * Listeners remain active due to web-vitals library limitations.\n */\n override disable(): void {\n this._isEnabled = false;\n this._diag.debug('Instrumentation disabled, pausing emission');\n }\n\n /**\n * Gets the timestamp for a metric based on attribution timing.\n * Returns undefined to let OTel use the current time for metrics without\n * specific timing information.\n */\n private _getTimestampForMetric(\n metric: MetricWithAttribution,\n ): number | undefined {\n if (metric.name === 'CLS') {\n const { attribution } = metric as CLSMetricWithAttribution;\n if (attribution.largestShiftTime !== undefined) {\n return attribution.largestShiftTime;\n }\n return undefined;\n }\n if (metric.name === 'INP') {\n const { attribution } = metric as INPMetricWithAttribution;\n return attribution.interactionTime;\n }\n // FCP, LCP, TTFB: metric.value is already DOMHighResTimeStamp of the event\n return metric.value;\n }\n\n private _emitWebVital(metric: MetricWithAttribution): void {\n if (!this._isEnabled) {\n return;\n }\n const attributes: Attributes = {\n [ATTR_WEB_VITAL_NAME]: metric.name.toLowerCase(),\n [ATTR_WEB_VITAL_VALUE]: metric.value,\n // `delta` equals `value` on the first emission; subsequent emissions report only the change\n [ATTR_WEB_VITAL_DELTA]: metric.delta,\n [ATTR_WEB_VITAL_RATING]: metric.rating,\n [ATTR_WEB_VITAL_ID]: metric.id,\n [ATTR_WEB_VITAL_NAVIGATION_TYPE]: metric.navigationType,\n };\n\n const timestamp = this._getTimestampForMetric(metric);\n\n const logRecord: LogRecord = {\n eventName: WEB_VITAL_EVENT_NAME,\n severityNumber: SeverityNumber.INFO,\n attributes,\n ...(this._includeRawAttribution\n ? { body: JSON.stringify(metric.attribution) }\n : {}),\n ...(timestamp !== undefined ? { timestamp } : {}),\n };\n\n if (this._applyCustomLogRecordData) {\n safeExecuteInTheMiddle(\n () => this._applyCustomLogRecordData?.(logRecord),\n (error) => {\n if (error) {\n this._diag.error('applyCustomLogRecordData hook failed', error);\n }\n },\n true,\n );\n }\n\n this.logger.emit(logRecord);\n }\n}\n"],"mappings":";;;;;;;;;;;;;;AAsCA,IAAa,2BAAb,cAA8C,oBAAoD;CAMhG;CACA;CAEA,YAAY,SAAyC,CAAC,GAAG;EACvD,MAAM,qDAAqD,SAAS,MAAM;EAC1E,KAAK,4BAA4B,OAAO;EACxC,KAAK,yBAAyB,OAAO,yBAAyB;CAChE;CAEA,OAA0B;EACxB,OAAO,CAAC;CACV;;;;;CAMA,SAAwB;EACtB,IAAI,OAAO,wBAAwB,aAAa;GAC9C,KAAK,MAAM,MACT,qEACF;GACA;EACF;EAEA,KAAK,aAAa;EAElB,IAAI,KAAK,sBAAsB;GAC7B,KAAK,MAAM,MAAM,iDAAiD;GAClE;EACF;EAEA,KAAK,uBAAuB;EAC5B,KAAK,MAAM,MAAM,uBAAuB;EAGxC,OAAO,WAAW,KAAK,cAAc,MAAM,CAAC;EAC5C,OAAO,WAAW,KAAK,cAAc,MAAM,CAAC;EAC5C,OAAO,WAAW,KAAK,cAAc,MAAM,CAAC;EAC5C,OAAO,WAAW,KAAK,cAAc,MAAM,CAAC;EAC5C,QAAQ,WAAW,KAAK,cAAc,MAAM,CAAC;CAC/C;;;;;CAMA,UAAyB;EACvB,KAAK,aAAa;EAClB,KAAK,MAAM,MAAM,4CAA4C;CAC/D;;;;;;CAOA,uBACE,QACoB;EACpB,IAAI,OAAO,SAAS,OAAO;GACzB,MAAM,EAAE,gBAAgB;GACxB,IAAI,YAAY,qBAAqB,KAAA,GACnC,OAAO,YAAY;GAErB;EACF;EACA,IAAI,OAAO,SAAS,OAAO;GACzB,MAAM,EAAE,gBAAgB;GACxB,OAAO,YAAY;EACrB;EAEA,OAAO,OAAO;CAChB;CAEA,cAAsB,QAAqC;EACzD,IAAI,CAAC,KAAK,YACR;EAEF,MAAM,aAAyB;IAC5B,sBAAsB,OAAO,KAAK,YAAY;IAC9C,uBAAuB,OAAO;IAE9B,uBAAuB,OAAO;IAC9B,wBAAwB,OAAO;IAC/B,oBAAoB,OAAO;IAC3B,iCAAiC,OAAO;EAC3C;EAEA,MAAM,YAAY,KAAK,uBAAuB,MAAM;EAEpD,MAAM,YAAuB;GAC3B,WAAW;GACX,gBAAgB,eAAe;GAC/B;GACA,GAAI,KAAK,yBACL,EAAE,MAAM,KAAK,UAAU,OAAO,WAAW,EAAE,IAC3C,CAAC;GACL,GAAI,cAAc,KAAA,IAAY,EAAE,UAAU,IAAI,CAAC;EACjD;EAEA,IAAI,KAAK,2BACP,6BACQ,KAAK,4BAA4B,SAAS,IAC/C,UAAU;GACT,IAAI,OACF,KAAK,MAAM,MAAM,wCAAwC,KAAK;EAElE,GACA,IACF;EAGF,KAAK,OAAO,KAAK,SAAS;CAC5B;AACF"}
@@ -4,7 +4,7 @@ import { InstrumentationConfig } from "@opentelemetry/instrumentation";
4
4
  /**
5
5
  * WebVitalsInstrumentation Configuration
6
6
  */
7
- interface WebVitalsInstrumentationConfig extends InstrumentationConfig {
7
+ export interface WebVitalsInstrumentationConfig extends InstrumentationConfig {
8
8
  /**
9
9
  * @experimental
10
10
  * When true, sets the log record body to the JSON-stringified
@@ -21,5 +21,4 @@ interface WebVitalsInstrumentationConfig extends InstrumentationConfig {
21
21
  applyCustomLogRecordData?: (logRecord: LogRecord) => void;
22
22
  }
23
23
  //#endregion
24
- export { WebVitalsInstrumentationConfig };
25
24
  //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1,3 @@
1
+ import { XhrInstrumentationConfig } from "./types.js";
2
+ import { XhrInstrumentation } from "./instrumentation.js";
3
+ export { XhrInstrumentation, type XhrInstrumentationConfig };
@@ -0,0 +1,2 @@
1
+ import { XhrInstrumentation } from "./instrumentation.js";
2
+ export { XhrInstrumentation };
@@ -0,0 +1,38 @@
1
+ import { XhrInstrumentationConfig } from "./types.js";
2
+ import { InstrumentationBase } from "@opentelemetry/instrumentation";
3
+ //#region src/xhr/instrumentation.d.ts
4
+ export declare class XhrInstrumentation extends InstrumentationBase<XhrInstrumentationConfig> {
5
+ private _isEnabled;
6
+ private _isXhrPatched;
7
+ private _xhrSpanMap;
8
+ constructor(config?: XhrInstrumentationConfig);
9
+ protected init(): never[];
10
+ enable(): void;
11
+ disable(): void;
12
+ /**
13
+ * Patches the "open" method of XmlHttpRequest
14
+ */
15
+ private _patchOpen;
16
+ /**
17
+ * Patches the "open" method of XmlHttpRequest
18
+ */
19
+ private _patchSend;
20
+ /**
21
+ * Creates a new span when method "send" is called
22
+ */
23
+ private _createSpan;
24
+ /**
25
+ * Finish span, add attributes, network events etc.
26
+ */
27
+ private _endSpan;
28
+ /**
29
+ * Runs the user provided `applyCustomAttributesOnSpan` function
30
+ */
31
+ private _applyAttributesAfterSend;
32
+ /**
33
+ * Adds custom headers to XMLHttpRequest
34
+ */
35
+ private _addHeaders;
36
+ }
37
+ //#endregion
38
+ //# sourceMappingURL=instrumentation.d.ts.map
@@ -0,0 +1,187 @@
1
+ import { version } from "../package.js";
2
+ import { getNetworkContextRegistry } from "../utils/NetworkContextRegistry.js";
3
+ import { getXHRBodyLength, normalizeHttpRequestMethod } from "../utils/request.js";
4
+ import { matchesUrl, parseUrl, serverPortFromUrl } from "../utils/url.js";
5
+ import { ATTR_HTTP_REQUEST_BODY_SIZE } from "./semconv.js";
6
+ import { SpanKind, SpanStatusCode, context, propagation, trace } from "@opentelemetry/api";
7
+ import { InstrumentationBase, safeExecuteInTheMiddle } from "@opentelemetry/instrumentation";
8
+ import { ATTR_ERROR_TYPE, ATTR_HTTP_REQUEST_METHOD, ATTR_HTTP_REQUEST_METHOD_ORIGINAL, ATTR_HTTP_RESPONSE_STATUS_CODE, ATTR_SERVER_ADDRESS, ATTR_SERVER_PORT, ATTR_URL_FULL } from "@opentelemetry/semantic-conventions";
9
+ //#region src/xhr/instrumentation.ts
10
+ var XhrInstrumentation = class extends InstrumentationBase {
11
+ _xhrSpanMap = /* @__PURE__ */ new WeakMap();
12
+ constructor(config = {}) {
13
+ super("@opentelemetry/browser-instrumentation/xhr", version, config);
14
+ }
15
+ init() {
16
+ return [];
17
+ }
18
+ enable() {
19
+ if (this._isEnabled) return;
20
+ if (this._isXhrPatched) {
21
+ this._diag.debug("XMLHttpRequest prototype already patched");
22
+ this._isEnabled = true;
23
+ return;
24
+ }
25
+ try {
26
+ this._wrap(XMLHttpRequest.prototype, "open", this._patchOpen());
27
+ } catch (err) {
28
+ this._diag.warn("Failed to patch XMLHttpRequest.prototype.open; instrumentation will not be enabled. Another script may have locked XMLHttpRequest.prototype.open via Object.defineProperty.", err);
29
+ return;
30
+ }
31
+ try {
32
+ this._wrap(XMLHttpRequest.prototype, "send", this._patchSend());
33
+ this._isXhrPatched = true;
34
+ this._isEnabled = true;
35
+ } catch (err) {
36
+ this._unwrap(XMLHttpRequest.prototype, "open");
37
+ this._diag.warn("Failed to patch XMLHttpRequest.prototype.send; instrumentation will not be enabled. Another script may have locked XMLHttpRequest.prototype.send via Object.defineProperty.", err);
38
+ }
39
+ }
40
+ disable() {
41
+ if (!this._isEnabled) return;
42
+ this._isEnabled = false;
43
+ }
44
+ /**
45
+ * Patches the "open" method of XmlHttpRequest
46
+ */
47
+ _patchOpen() {
48
+ return (original) => {
49
+ const instrumentation = this;
50
+ return function patchedOpen(...args) {
51
+ if (!instrumentation._isEnabled) return original.apply(this, args);
52
+ try {
53
+ const method = args[0];
54
+ const urlArgument = typeof args[1] === "string" ? args[1] : args[1].toString();
55
+ const url = parseUrl(urlArgument);
56
+ if (matchesUrl(url.href, instrumentation.getConfig().ignoreUrls)) return original.apply(this, args);
57
+ instrumentation._xhrSpanMap.set(this, {
58
+ method,
59
+ url
60
+ });
61
+ } catch (e) {
62
+ instrumentation._diag.error("Failed to instrument XmlHttpRequest.open", e);
63
+ }
64
+ return original.apply(this, args);
65
+ };
66
+ };
67
+ }
68
+ /**
69
+ * Patches the "open" method of XmlHttpRequest
70
+ */
71
+ _patchSend() {
72
+ return (original) => {
73
+ const instrumentation = this;
74
+ return function patchedSend(...args) {
75
+ if (!instrumentation._isEnabled) return original.apply(this, args);
76
+ const xhrRecord = instrumentation._xhrSpanMap.get(this);
77
+ if (xhrRecord) try {
78
+ const { method, url } = xhrRecord;
79
+ const span = instrumentation._createSpan(url, method);
80
+ xhrRecord.span = span;
81
+ xhrRecord.start = performance.now();
82
+ if (instrumentation.getConfig().measureRequestSize && args?.[0]) {
83
+ const bodyLength = getXHRBodyLength(args[0]);
84
+ if (bodyLength) span.setAttribute(ATTR_HTTP_REQUEST_BODY_SIZE, bodyLength);
85
+ }
86
+ const onAbort = () => instrumentation._endSpan(this, "abort");
87
+ const onError = () => instrumentation._endSpan(this, "error");
88
+ const onTimeout = () => instrumentation._endSpan(this, "timeout");
89
+ const onLoad = () => instrumentation._endSpan(this, "load");
90
+ xhrRecord.listeners = {
91
+ abort: onAbort,
92
+ error: onError,
93
+ timeout: onTimeout,
94
+ load: onLoad
95
+ };
96
+ const xhrContext = trace.setSpan(context.active(), span);
97
+ context.with(xhrContext, () => {
98
+ this.addEventListener("abort", onAbort);
99
+ this.addEventListener("error", onError);
100
+ this.addEventListener("timeout", onTimeout);
101
+ this.addEventListener("load", onLoad);
102
+ instrumentation._addHeaders(this, url, xhrContext);
103
+ });
104
+ } catch (e) {
105
+ instrumentation._diag.error("Failed to instrument fetch request", e);
106
+ instrumentation._xhrSpanMap.delete(this);
107
+ }
108
+ return original.apply(this, args);
109
+ };
110
+ };
111
+ }
112
+ /**
113
+ * Creates a new span when method "send" is called
114
+ */
115
+ _createSpan(url, method) {
116
+ const origMethod = method;
117
+ const normMethod = normalizeHttpRequestMethod(method);
118
+ const attributes = {};
119
+ const { sanitizeUrl } = this.getConfig();
120
+ attributes[ATTR_HTTP_REQUEST_METHOD] = normMethod;
121
+ if (normMethod !== origMethod) attributes[ATTR_HTTP_REQUEST_METHOD_ORIGINAL] = origMethod;
122
+ attributes[ATTR_URL_FULL] = sanitizeUrl ? sanitizeUrl(url.href) : url.href;
123
+ attributes[ATTR_SERVER_ADDRESS] = url.hostname;
124
+ const serverPort = serverPortFromUrl(url);
125
+ if (serverPort) attributes[ATTR_SERVER_PORT] = serverPort;
126
+ return this.tracer.startSpan(normMethod, {
127
+ kind: SpanKind.CLIENT,
128
+ attributes
129
+ });
130
+ }
131
+ /**
132
+ * Finish span, add attributes, network events etc.
133
+ */
134
+ _endSpan(xhr, eventName) {
135
+ const xhrRecord = this._xhrSpanMap.get(xhr);
136
+ if (xhrRecord?.listeners) for (const k of Object.keys(xhrRecord.listeners)) xhr.removeEventListener(k, xhrRecord.listeners[k]);
137
+ if (xhrRecord?.span) {
138
+ const { span, url, start } = xhrRecord;
139
+ if (eventName === "load") span.setAttribute(ATTR_HTTP_RESPONSE_STATUS_CODE, xhr.status);
140
+ if (eventName !== "abort") {
141
+ if (xhr.status >= 400 || eventName === "error" || eventName === "timeout") {
142
+ span.setStatus({ code: SpanStatusCode.ERROR });
143
+ const errorType = xhr.status >= 400 ? String(xhr.status) : eventName === "error" ? "Error" : "TimeoutError";
144
+ span.setAttribute(ATTR_ERROR_TYPE, errorType);
145
+ }
146
+ }
147
+ this._xhrSpanMap.delete(xhr);
148
+ this._applyAttributesAfterSend(span, xhr);
149
+ span.end();
150
+ getNetworkContextRegistry().register(span, {
151
+ key: url.href,
152
+ startPerfNow: Number(start),
153
+ endPerfNow: performance.now()
154
+ });
155
+ }
156
+ }
157
+ /**
158
+ * Runs the user provided `applyCustomAttributesOnSpan` function
159
+ */
160
+ _applyAttributesAfterSend(span, xhr) {
161
+ const applyCustomAttributesOnSpan = this.getConfig().applyCustomAttributesOnSpan;
162
+ if (applyCustomAttributesOnSpan) safeExecuteInTheMiddle(() => applyCustomAttributesOnSpan(span, xhr), (error) => {
163
+ if (!error) return;
164
+ this._diag.error("applyCustomAttributesOnSpan", error);
165
+ }, true);
166
+ }
167
+ /**
168
+ * Adds custom headers to XMLHttpRequest
169
+ */
170
+ _addHeaders(xhr, url, ctx) {
171
+ const urlsToPropagate = this.getConfig().propagateTraceHeaderCorsUrls;
172
+ if (location.origin === url.origin || matchesUrl(url.href, urlsToPropagate)) propagation.inject(ctx, xhr, { set: (x, k, v) => {
173
+ x.setRequestHeader(k, typeof v === "string" ? v : String(v));
174
+ } });
175
+ else {
176
+ const headers = {};
177
+ propagation.inject(ctx, headers, { set: (h, k, v) => {
178
+ h[k] = v;
179
+ } });
180
+ if (Object.keys(headers).length > 0) this._diag.debug("headers inject skipped due to CORS policy");
181
+ }
182
+ }
183
+ };
184
+ //#endregion
185
+ export { XhrInstrumentation };
186
+
187
+ //# sourceMappingURL=instrumentation.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"instrumentation.js","names":[],"sources":["../../src/xhr/instrumentation.ts"],"sourcesContent":["/*\n * Copyright The OpenTelemetry Authors\n * SPDX-License-Identifier: Apache-2.0\n */\n\nimport type { Attributes, Context, Span } from '@opentelemetry/api';\nimport {\n context,\n propagation,\n SpanKind,\n SpanStatusCode,\n trace,\n} from '@opentelemetry/api';\nimport {\n InstrumentationBase,\n safeExecuteInTheMiddle,\n} from '@opentelemetry/instrumentation';\nimport {\n ATTR_ERROR_TYPE,\n ATTR_HTTP_REQUEST_METHOD,\n ATTR_HTTP_REQUEST_METHOD_ORIGINAL,\n ATTR_HTTP_RESPONSE_STATUS_CODE,\n ATTR_SERVER_ADDRESS,\n ATTR_SERVER_PORT,\n ATTR_URL_FULL,\n} from '@opentelemetry/semantic-conventions';\nimport { version } from '../../package.json' with { type: 'json' };\nimport { getNetworkContextRegistry } from '../utils/NetworkContextRegistry.ts';\nimport {\n getXHRBodyLength,\n normalizeHttpRequestMethod,\n} from '../utils/request.ts';\nimport { matchesUrl, parseUrl, serverPortFromUrl } from '../utils/url.ts';\nimport { ATTR_HTTP_REQUEST_BODY_SIZE } from './semconv.ts';\nimport type { XhrInstrumentationConfig } from './types.ts';\n\ntype XhrOpenFunction = typeof XMLHttpRequest.prototype.open;\ntype XhrSendFunction = typeof XMLHttpRequest.prototype.send;\ntype XhrEventName = 'abort' | 'timeout' | 'error' | 'load';\ntype XhrRecord = {\n method: string;\n url: URL;\n start?: number;\n span?: Span;\n listeners?: Record<XhrEventName, (...args: unknown[]) => unknown>;\n};\n\nexport class XhrInstrumentation extends InstrumentationBase<XhrInstrumentationConfig> {\n // Note: Intentionally *not* using `_enabled` as the field name to avoid\n // any possible confusion with the `_enabled` field used on the *Node.js*\n // InstrumentationBase class.\n // Also not initializing the fields to `false` because the base class\n // constructor already call `enable` modifying their values and it will\n // set the instrumentaitons in a base state (enabled, patched but with flags set to false)\n declare private _isEnabled: boolean;\n declare private _isXhrPatched: boolean;\n\n // To keep references to span/xhr tuples across XHR events and stores\n // init data like URL and method\n private _xhrSpanMap: WeakMap<XMLHttpRequest, XhrRecord> = new WeakMap();\n\n constructor(config: XhrInstrumentationConfig = {}) {\n super('@opentelemetry/browser-instrumentation/xhr', version, config);\n }\n\n protected override init() {\n return [];\n }\n\n override enable(): void {\n if (this._isEnabled) {\n return;\n }\n\n if (this._isXhrPatched) {\n this._diag.debug('XMLHttpRequest prototype already patched');\n this._isEnabled = true;\n return;\n }\n\n // `_wrap` throws if a third-party script has locked the target methods via\n // Object.defineProperty(XMLHttpRequest.prototype, 'open', { writable: false, ... }).\n try {\n this._wrap(XMLHttpRequest.prototype, 'open', this._patchOpen());\n } catch (err) {\n this._diag.warn(\n 'Failed to patch XMLHttpRequest.prototype.open; instrumentation will not be enabled. ' +\n 'Another script may have locked XMLHttpRequest.prototype.open via Object.defineProperty.',\n err,\n );\n return;\n }\n\n // If 1st patch has succeded try the second. Unpatch `open` if error\n // here to avoid having multiple patches of `open`\n try {\n this._wrap(XMLHttpRequest.prototype, 'send', this._patchSend());\n this._isXhrPatched = true;\n this._isEnabled = true;\n } catch (err) {\n this._unwrap(XMLHttpRequest.prototype, 'open');\n this._diag.warn(\n 'Failed to patch XMLHttpRequest.prototype.send; instrumentation will not be enabled. ' +\n 'Another script may have locked XMLHttpRequest.prototype.send via Object.defineProperty.',\n err,\n );\n }\n }\n\n override disable(): void {\n if (!this._isEnabled) {\n return;\n }\n this._isEnabled = false;\n }\n\n /**\n * Patches the \"open\" method of XmlHttpRequest\n */\n private _patchOpen(): (original: XhrOpenFunction) => XhrOpenFunction {\n return (original) => {\n const instrumentation = this;\n\n return function patchedOpen(\n this: XMLHttpRequest,\n ...args: Parameters<XhrOpenFunction>\n ): ReturnType<XhrOpenFunction> {\n if (!instrumentation._isEnabled) {\n return original.apply(this, args);\n }\n try {\n const method = args[0];\n const urlArgument =\n typeof args[1] === 'string' ? args[1] : args[1].toString();\n const url = parseUrl(urlArgument);\n const shouldIgnoreUrl = matchesUrl(\n url.href,\n instrumentation.getConfig().ignoreUrls,\n );\n if (shouldIgnoreUrl) {\n return original.apply(this, args);\n }\n instrumentation._xhrSpanMap.set(this, { method, url });\n } catch (e: unknown) {\n instrumentation._diag.error(\n 'Failed to instrument XmlHttpRequest.open',\n e,\n );\n }\n return original.apply(this, args);\n } as XhrOpenFunction;\n };\n }\n\n /**\n * Patches the \"open\" method of XmlHttpRequest\n */\n private _patchSend(): (original: XhrSendFunction) => XhrSendFunction {\n return (original) => {\n const instrumentation = this;\n\n return function patchedSend(\n this: XMLHttpRequest,\n ...args: Parameters<XhrSendFunction>\n ): ReturnType<XhrSendFunction> {\n if (!instrumentation._isEnabled) {\n return original.apply(this, args);\n }\n\n // Instrument the method if the XHR was previously picked in `open`\n const xhrRecord = instrumentation._xhrSpanMap.get(this);\n if (xhrRecord) {\n try {\n const { method, url } = xhrRecord;\n const span = instrumentation._createSpan(url, method);\n xhrRecord.span = span;\n xhrRecord.start = performance.now();\n\n if (instrumentation.getConfig().measureRequestSize && args?.[0]) {\n const bodyLength = getXHRBodyLength(args[0]);\n if (bodyLength) {\n span.setAttribute(ATTR_HTTP_REQUEST_BODY_SIZE, bodyLength);\n }\n }\n\n const onAbort = () => instrumentation._endSpan(this, 'abort');\n const onError = () => instrumentation._endSpan(this, 'error');\n const onTimeout = () => instrumentation._endSpan(this, 'timeout');\n const onLoad = () => instrumentation._endSpan(this, 'load');\n xhrRecord.listeners = {\n abort: onAbort,\n error: onError,\n timeout: onTimeout,\n load: onLoad,\n };\n\n const xhrContext = trace.setSpan(context.active(), span);\n context.with(xhrContext, () => {\n this.addEventListener('abort', onAbort);\n this.addEventListener('error', onError);\n this.addEventListener('timeout', onTimeout);\n this.addEventListener('load', onLoad);\n instrumentation._addHeaders(this, url, xhrContext);\n });\n } catch (e: unknown) {\n // failed to instrument request, remove span\n instrumentation._diag.error(\n 'Failed to instrument fetch request',\n e,\n );\n instrumentation._xhrSpanMap.delete(this);\n }\n }\n return original.apply(this, args);\n } as XhrSendFunction;\n };\n }\n\n /**\n * Creates a new span when method \"send\" is called\n */\n private _createSpan(url: URL, method: string): Span {\n const origMethod = method;\n const normMethod = normalizeHttpRequestMethod(method);\n const attributes = {} as Attributes;\n const { sanitizeUrl } = this.getConfig();\n\n attributes[ATTR_HTTP_REQUEST_METHOD] = normMethod;\n if (normMethod !== origMethod) {\n attributes[ATTR_HTTP_REQUEST_METHOD_ORIGINAL] = origMethod;\n }\n attributes[ATTR_URL_FULL] = sanitizeUrl ? sanitizeUrl(url.href) : url.href;\n attributes[ATTR_SERVER_ADDRESS] = url.hostname;\n const serverPort = serverPortFromUrl(url);\n if (serverPort) {\n attributes[ATTR_SERVER_PORT] = serverPort;\n }\n\n return this.tracer.startSpan(normMethod, {\n kind: SpanKind.CLIENT,\n attributes,\n });\n }\n\n /**\n * Finish span, add attributes, network events etc.\n */\n private _endSpan(xhr: XMLHttpRequest, eventName: XhrEventName) {\n const xhrRecord = this._xhrSpanMap.get(xhr);\n\n if (xhrRecord?.listeners) {\n for (const k of Object.keys(xhrRecord.listeners)) {\n xhr.removeEventListener(\n k as XhrEventName,\n xhrRecord.listeners[k as XhrEventName],\n );\n }\n }\n\n if (xhrRecord?.span) {\n const { span, url, start } = xhrRecord;\n\n // Status code only has a meaningful value if request got a response\n if (eventName === 'load') {\n span.setAttribute(ATTR_HTTP_RESPONSE_STATUS_CODE, xhr.status);\n }\n\n if (eventName !== 'abort') {\n // https://github.com/open-telemetry/semantic-conventions/blob/main/docs/http/http-spans.md#status\n // 1xx/2xx/3xx MUST be left unset UNLESS there was another error (e.g. a\n // network error receiving the response body)\n const isErrorStatus =\n xhr.status >= 400 || eventName === 'error' || eventName === 'timeout';\n\n if (isErrorStatus) {\n span.setStatus({ code: SpanStatusCode.ERROR });\n const errorType =\n xhr.status >= 400\n ? String(xhr.status)\n : eventName === 'error'\n ? 'Error'\n : 'TimeoutError';\n span.setAttribute(ATTR_ERROR_TYPE, errorType);\n }\n }\n\n this._xhrSpanMap.delete(xhr);\n this._applyAttributesAfterSend(span, xhr);\n span.end();\n\n getNetworkContextRegistry().register(span, {\n key: url.href,\n startPerfNow: Number(start),\n endPerfNow: performance.now(),\n });\n }\n }\n\n /**\n * Runs the user provided `applyCustomAttributesOnSpan` function\n */\n private _applyAttributesAfterSend(span: Span, xhr: XMLHttpRequest) {\n const applyCustomAttributesOnSpan =\n this.getConfig().applyCustomAttributesOnSpan;\n if (applyCustomAttributesOnSpan) {\n safeExecuteInTheMiddle(\n () => applyCustomAttributesOnSpan(span, xhr),\n (error) => {\n if (!error) {\n return;\n }\n this._diag.error('applyCustomAttributesOnSpan', error);\n },\n true,\n );\n }\n }\n\n /**\n * Adds custom headers to XMLHttpRequest\n */\n private _addHeaders(xhr: XMLHttpRequest, url: URL, ctx: Context) {\n // Propagate only if in request goes to same origin or is in the allow list\n const urlsToPropagate = this.getConfig().propagateTraceHeaderCorsUrls;\n const sameOrigin = location.origin === url.origin;\n const shouldPropagate = sameOrigin || matchesUrl(url.href, urlsToPropagate);\n\n if (shouldPropagate) {\n propagation.inject(ctx, xhr, {\n set: (x, k, v) => {\n x.setRequestHeader(k, typeof v === 'string' ? v : String(v));\n },\n });\n } else {\n const headers: Partial<Record<string, unknown>> = {};\n propagation.inject(ctx, headers, {\n set: (h, k, v) => {\n h[k] = v;\n },\n });\n if (Object.keys(headers).length > 0) {\n this._diag.debug('headers inject skipped due to CORS policy');\n }\n }\n }\n}\n"],"mappings":"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"}
@@ -0,0 +1,13 @@
1
+ //#region src/xhr/semconv.ts
2
+ /**
3
+ * The size of the request payload body in bytes. This is the number of bytes transferred excluding headers and is often, but not always, present as the [Content-Length](https://www.rfc-editor.org/rfc/rfc9110.html#field.content-length) header. For requests using transport encoding, this should be the compressed size.
4
+ *
5
+ * @example 3495
6
+ *
7
+ * @experimental This attribute is experimental and is subject to breaking changes in minor releases of `@opentelemetry/semantic-conventions`.
8
+ */
9
+ const ATTR_HTTP_REQUEST_BODY_SIZE = "http.request.body.size";
10
+ //#endregion
11
+ export { ATTR_HTTP_REQUEST_BODY_SIZE };
12
+
13
+ //# sourceMappingURL=semconv.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"semconv.js","names":[],"sources":["../../src/xhr/semconv.ts"],"sourcesContent":["/*\n * Copyright The OpenTelemetry Authors\n * SPDX-License-Identifier: Apache-2.0\n */\n\n/*\n * This file contains a copy of unstable semantic convention definitions\n * used by this package.\n * @see https://github.com/open-telemetry/opentelemetry-js/tree/main/semantic-conventions#unstable-semconv\n */\n\n/**\n * The size of the request payload body in bytes. This is the number of bytes transferred excluding headers and is often, but not always, present as the [Content-Length](https://www.rfc-editor.org/rfc/rfc9110.html#field.content-length) header. For requests using transport encoding, this should be the compressed size.\n *\n * @example 3495\n *\n * @experimental This attribute is experimental and is subject to breaking changes in minor releases of `@opentelemetry/semantic-conventions`.\n */\nexport const ATTR_HTTP_REQUEST_BODY_SIZE = 'http.request.body.size' as const;\n"],"mappings":";;;;;;;;AAkBA,MAAa,8BAA8B"}
@@ -0,0 +1,22 @@
1
+ import { Span } from "@opentelemetry/api";
2
+ import { InstrumentationConfig } from "@opentelemetry/instrumentation";
3
+ //#region src/xhr/types.d.ts
4
+ type XhrCustomAttributeFunction = (span: Span, xhr: XMLHttpRequest) => void;
5
+ export interface XhrInstrumentationConfig extends InstrumentationConfig {
6
+ /** URLs which should include trace headers when origin doesn't match */
7
+ propagateTraceHeaderCorsUrls?: Array<string | RegExp>;
8
+ /**
9
+ * URLs that partially match any regex in ignoreUrls will not be traced.
10
+ * In addition, URLs that are _exact matches_ of strings in ignoreUrls will
11
+ * also not be traced.
12
+ */
13
+ ignoreUrls?: Array<string | RegExp>;
14
+ /** Function for adding custom attributes on the span */
15
+ applyCustomAttributesOnSpan?: XhrCustomAttributeFunction;
16
+ /** Measure outgoing request size */
17
+ measureRequestSize?: boolean;
18
+ /** Custom function to sanitize URLs before adding to log records. */
19
+ sanitizeUrl?: (url: string) => string;
20
+ }
21
+ //#endregion
22
+ //# sourceMappingURL=types.d.ts.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@opentelemetry/browser-instrumentation",
3
- "version": "0.7.0",
3
+ "version": "0.8.1",
4
4
  "description": "OpenTelemetry browser instrumentations.",
5
5
  "keywords": [
6
6
  "opentelemetry",
@@ -13,7 +13,9 @@
13
13
  "navigation-timing",
14
14
  "resource-timing",
15
15
  "user-action",
16
- "web-vitals"
16
+ "web-vitals",
17
+ "fetch",
18
+ "xhr"
17
19
  ],
18
20
  "homepage": "https://github.com/open-telemetry/opentelemetry-browser",
19
21
  "bugs": "https://github.com/open-telemetry/opentelemetry-browser/issues",
@@ -36,7 +38,9 @@
36
38
  "./experimental/navigation-timing": "./dist/navigation-timing/index.js",
37
39
  "./experimental/resource-timing": "./dist/resource-timing/index.js",
38
40
  "./experimental/user-action": "./dist/user-action/index.js",
39
- "./experimental/web-vitals": "./dist/web-vitals/index.js"
41
+ "./experimental/web-vitals": "./dist/web-vitals/index.js",
42
+ "./experimental/fetch": "./dist/fetch/index.js",
43
+ "./experimental/xhr": "./dist/xhr/index.js"
40
44
  },
41
45
  "sideEffects": false,
42
46
  "files": [
@@ -46,24 +50,33 @@
46
50
  "build": "tsdown",
47
51
  "watch": "tsdown --watch --no-clean",
48
52
  "check-types": "tsc",
53
+ "msw:init": "msw init public --save",
49
54
  "test": "vitest run",
50
55
  "test:watch": "vitest",
51
56
  "test:coverage": "vitest --coverage"
52
57
  },
53
58
  "dependencies": {
54
- "@opentelemetry/api-logs": "^0.221.0",
55
- "@opentelemetry/core": "^2.10.0",
56
- "@opentelemetry/instrumentation": "^0.221.0",
59
+ "@opentelemetry/api-logs": "^0.222.0",
60
+ "@opentelemetry/instrumentation": "^0.222.0",
57
61
  "@opentelemetry/semantic-conventions": "^1.43.0",
58
- "web-vitals": "^5.3.0"
62
+ "web-vitals": "^6.2.1"
59
63
  },
60
64
  "peerDependencies": {
61
65
  "@opentelemetry/api": "^1.9.1"
62
66
  },
63
67
  "devDependencies": {
64
- "@opentelemetry/sdk-logs": "0.221.0"
68
+ "@opentelemetry/core": "2.11.0",
69
+ "@opentelemetry/propagator-b3": "2.11.0",
70
+ "@opentelemetry/sdk-logs": "0.222.0",
71
+ "@opentelemetry/sdk-trace-base": "2.11.0",
72
+ "msw": "2.15.0"
65
73
  },
66
74
  "publishConfig": {
67
75
  "access": "public"
76
+ },
77
+ "msw": {
78
+ "workerDirectory": [
79
+ "public"
80
+ ]
68
81
  }
69
82
  }