@workleap/logrocket 4.0.5 → 4.0.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +12 -0
- package/dist/js/createRequestSanitizer.d.ts +2 -1
- package/dist/js/createRequestSanitizer.js +7 -4
- package/dist/js/createRequestSanitizer.js.map +1 -1
- package/dist/js/createResponseSanitizer.d.ts +2 -1
- package/dist/js/createResponseSanitizer.js +9 -1
- package/dist/js/createResponseSanitizer.js.map +1 -1
- package/dist/js/createUrlSanitizer.d.ts +2 -1
- package/dist/js/createUrlSanitizer.js +3 -2
- package/dist/js/createUrlSanitizer.js.map +1 -1
- package/dist/js/registerLogRocketInstrumentation.js +4 -3
- package/dist/js/registerLogRocketInstrumentation.js.map +1 -1
- package/dist/js/sanitizeHeaders.d.ts +3 -0
- package/dist/js/sanitizeHeaders.js +32 -0
- package/dist/js/sanitizeHeaders.js.map +1 -0
- package/package.json +2 -3
- package/src/js/createRequestSanitizer.ts +5 -4
- package/src/js/createResponseSanitizer.ts +9 -1
- package/src/js/createUrlSanitizer.ts +6 -3
- package/src/js/registerLogRocketInstrumentation.ts +5 -3
- package/src/js/sanitizeHeaders.ts +26 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# @workleap/logrocket
|
|
2
2
|
|
|
3
|
+
## 4.0.7
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- [#278](https://github.com/workleap/wl-telemetry/pull/278) [`c75b3cf`](https://github.com/workleap/wl-telemetry/commit/c75b3cfc89799f2cc76170066cfc512e1db7055a) Thanks [@patricklafrance](https://github.com/patricklafrance)! - Redact the private query parameters from the URL of the network requests recorded in LogRocket session replays. Previously, the private query parameters were only redacted from the page URLs. Every occurrence of a private query parameter is now redacted, previously only the first occurrence was.
|
|
8
|
+
|
|
9
|
+
## 4.0.6
|
|
10
|
+
|
|
11
|
+
### Patch Changes
|
|
12
|
+
|
|
13
|
+
- [#275](https://github.com/workleap/wl-telemetry/pull/275) [`0128608`](https://github.com/workleap/wl-telemetry/commit/012860830e4e4a17dc6960c9e3d310e7aac98b8e) Thanks [@patricklafrance](https://github.com/patricklafrance)! - Redact the credential headers of the network requests and responses recorded in LogRocket session replays, regardless of the casing of the header name. A header is now redacted when its name contains `auth`, `cookie`, `token`, `session`, `secret`, `key`, `csrf`, `xsrf` or `x-honeycomb-team`. Previously, only a request header named exactly `Authorization` was redacted.
|
|
14
|
+
|
|
3
15
|
## 4.0.5
|
|
4
16
|
|
|
5
17
|
### Patch Changes
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { UrlSanitizerFunction } from "./createUrlSanitizer.js";
|
|
1
2
|
import type { LogRocketRequest } from "./logRocketTypes.js";
|
|
2
3
|
export type RequestSanitizerFunction = (req: LogRocketRequest) => any;
|
|
3
|
-
export declare function createRequestSanitizer(fuzzyRequestSanitizer: RequestSanitizerFunction): (req: LogRocketRequest) => any;
|
|
4
|
+
export declare function createRequestSanitizer(fuzzyRequestSanitizer: RequestSanitizerFunction, urlSanitizer: UrlSanitizerFunction): (req: LogRocketRequest) => any;
|
|
@@ -1,8 +1,11 @@
|
|
|
1
|
-
|
|
1
|
+
import { sanitizeHeaders } from "./sanitizeHeaders.js";
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
function createRequestSanitizer(fuzzyRequestSanitizer, urlSanitizer) {
|
|
2
6
|
return (req)=>{
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
}
|
|
7
|
+
req.url = urlSanitizer(req.url);
|
|
8
|
+
req.headers = sanitizeHeaders(req.headers);
|
|
6
9
|
if (req.body) {
|
|
7
10
|
// Do not return the request body, which could contain sensitive information.
|
|
8
11
|
req.body = "**redacted**";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"js/createRequestSanitizer.js","sources":["../../src/js/createRequestSanitizer.ts"],"sourcesContent":["import type { LogRocketRequest } from \"./logRocketTypes.ts\";\n\n// oxlint-disable-next-line typescript/no-explicit-any\nexport type RequestSanitizerFunction = (req: LogRocketRequest) => any;\n\nexport function createRequestSanitizer(fuzzyRequestSanitizer: RequestSanitizerFunction) {\n return (req: LogRocketRequest) => {\n
|
|
1
|
+
{"version":3,"file":"js/createRequestSanitizer.js","sources":["../../src/js/createRequestSanitizer.ts"],"sourcesContent":["import type { UrlSanitizerFunction } from \"./createUrlSanitizer.ts\";\nimport type { LogRocketRequest } from \"./logRocketTypes.ts\";\nimport { sanitizeHeaders } from \"./sanitizeHeaders.ts\";\n\n// oxlint-disable-next-line typescript/no-explicit-any\nexport type RequestSanitizerFunction = (req: LogRocketRequest) => any;\n\nexport function createRequestSanitizer(fuzzyRequestSanitizer: RequestSanitizerFunction, urlSanitizer: UrlSanitizerFunction) {\n return (req: LogRocketRequest) => {\n req.url = urlSanitizer(req.url);\n req.headers = sanitizeHeaders(req.headers);\n\n if (req.body) {\n // Do not return the request body, which could contain sensitive information.\n req.body = \"**redacted**\";\n }\n\n return fuzzyRequestSanitizer(req);\n };\n}\n"],"names":["sanitizeHeaders","createRequestSanitizer","fuzzyRequestSanitizer","urlSanitizer","req"],"mappings":";;;AAEuD;AAKhD,SAASC,uBAAuBC,qBAA+C,EAAEC,YAAkC;IACtH,OAAO,CAACC;QACJA,IAAI,GAAG,GAAGD,aAAaC,IAAI,GAAG;QAC9BA,IAAI,OAAO,GAAGJ,eAAeA,CAACI,IAAI,OAAO;QAEzC,IAAIA,IAAI,IAAI,EAAE;YACV,6EAA6E;YAC7EA,IAAI,IAAI,GAAG;QACf;QAEA,OAAOF,sBAAsBE;IACjC;AACJ"}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { UrlSanitizerFunction } from "./createUrlSanitizer.js";
|
|
1
2
|
import type { LogRocketResponse } from "./logRocketTypes.js";
|
|
2
3
|
export type ResponseSanitizerFunction = (res: LogRocketResponse) => any;
|
|
3
|
-
export declare function createResponseSanitizer(fuzzyResponseSanitizer: ResponseSanitizerFunction): (res: LogRocketResponse) => any;
|
|
4
|
+
export declare function createResponseSanitizer(fuzzyResponseSanitizer: ResponseSanitizerFunction, urlSanitizer: UrlSanitizerFunction): (res: LogRocketResponse) => any;
|
|
@@ -1,5 +1,13 @@
|
|
|
1
|
-
|
|
1
|
+
import { sanitizeHeaders } from "./sanitizeHeaders.js";
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
function createResponseSanitizer(fuzzyResponseSanitizer, urlSanitizer) {
|
|
2
6
|
return (res)=>{
|
|
7
|
+
if (res.url) {
|
|
8
|
+
res.url = urlSanitizer(res.url);
|
|
9
|
+
}
|
|
10
|
+
res.headers = sanitizeHeaders(res.headers);
|
|
3
11
|
if (res.body) {
|
|
4
12
|
// Do not return the response body, which could contain sensitive information.
|
|
5
13
|
res.body = "**redacted**";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"js/createResponseSanitizer.js","sources":["../../src/js/createResponseSanitizer.ts"],"sourcesContent":["import type { LogRocketResponse } from \"./logRocketTypes.ts\";\n\n// oxlint-disable-next-line typescript/no-explicit-any\nexport type ResponseSanitizerFunction = (res: LogRocketResponse) => any;\n\nexport function createResponseSanitizer(fuzzyResponseSanitizer: ResponseSanitizerFunction) {\n return (res: LogRocketResponse) => {\n if (res.body) {\n // Do not return the response body, which could contain sensitive information.\n res.body = \"**redacted**\";\n }\n\n return fuzzyResponseSanitizer(res);\n };\n}\n"],"names":["createResponseSanitizer","fuzzyResponseSanitizer","res"],"mappings":"
|
|
1
|
+
{"version":3,"file":"js/createResponseSanitizer.js","sources":["../../src/js/createResponseSanitizer.ts"],"sourcesContent":["import type { UrlSanitizerFunction } from \"./createUrlSanitizer.ts\";\nimport type { LogRocketResponse } from \"./logRocketTypes.ts\";\nimport { sanitizeHeaders } from \"./sanitizeHeaders.ts\";\n\n// oxlint-disable-next-line typescript/no-explicit-any\nexport type ResponseSanitizerFunction = (res: LogRocketResponse) => any;\n\nexport function createResponseSanitizer(fuzzyResponseSanitizer: ResponseSanitizerFunction, urlSanitizer: UrlSanitizerFunction) {\n return (res: LogRocketResponse) => {\n if (res.url) {\n res.url = urlSanitizer(res.url);\n }\n\n res.headers = sanitizeHeaders(res.headers);\n\n if (res.body) {\n // Do not return the response body, which could contain sensitive information.\n res.body = \"**redacted**\";\n }\n\n return fuzzyResponseSanitizer(res);\n };\n}\n"],"names":["sanitizeHeaders","createResponseSanitizer","fuzzyResponseSanitizer","urlSanitizer","res"],"mappings":";;;AAEuD;AAKhD,SAASC,wBAAwBC,sBAAiD,EAAEC,YAAkC;IACzH,OAAO,CAACC;QACJ,IAAIA,IAAI,GAAG,EAAE;YACTA,IAAI,GAAG,GAAGD,aAAaC,IAAI,GAAG;QAClC;QAEAA,IAAI,OAAO,GAAGJ,eAAeA,CAACI,IAAI,OAAO;QAEzC,IAAIA,IAAI,IAAI,EAAE;YACV,8EAA8E;YAC9EA,IAAI,IAAI,GAAG;QACf;QAEA,OAAOF,uBAAuBE;IAClC;AACJ"}
|
|
@@ -1 +1,2 @@
|
|
|
1
|
-
export
|
|
1
|
+
export type UrlSanitizerFunction = (url: string) => string;
|
|
2
|
+
export declare function createUrlSanitizer(queryParametersToRemove: string[]): (url: string | URL) => string;
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
function createUrlSanitizer(queryParametersToRemove) {
|
|
2
2
|
return (url)=>{
|
|
3
|
-
|
|
3
|
+
// A network request made with XMLHttpRequest can provide a URL object rather than a string.
|
|
4
|
+
let sanitizedUrl = String(url);
|
|
4
5
|
for (const x of queryParametersToRemove){
|
|
5
|
-
sanitizedUrl = sanitizedUrl.replace(new RegExp(`${x}=([^&]*)`, "
|
|
6
|
+
sanitizedUrl = sanitizedUrl.replace(new RegExp(`${x}=([^&]*)`, "gi"), `${x}=**redacted**`);
|
|
6
7
|
}
|
|
7
8
|
return sanitizedUrl;
|
|
8
9
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"js/createUrlSanitizer.js","sources":["../../src/js/createUrlSanitizer.ts"],"sourcesContent":["export function createUrlSanitizer(queryParametersToRemove: string[]) {\n return (url: string) => {\n let sanitizedUrl = url;\n\n for (const x of queryParametersToRemove) {\n sanitizedUrl = sanitizedUrl.replace(new RegExp(`${x}=([^&]*)`, \"
|
|
1
|
+
{"version":3,"file":"js/createUrlSanitizer.js","sources":["../../src/js/createUrlSanitizer.ts"],"sourcesContent":["export type UrlSanitizerFunction = (url: string) => string;\n\nexport function createUrlSanitizer(queryParametersToRemove: string[]) {\n return (url: string | URL) => {\n // A network request made with XMLHttpRequest can provide a URL object rather than a string.\n let sanitizedUrl = String(url);\n\n for (const x of queryParametersToRemove) {\n sanitizedUrl = sanitizedUrl.replace(new RegExp(`${x}=([^&]*)`, \"gi\"), `${x}=**redacted**`);\n }\n\n return sanitizedUrl;\n };\n}\n"],"names":["createUrlSanitizer","queryParametersToRemove","url","sanitizedUrl","String","x","RegExp"],"mappings":"AAEO,SAASA,mBAAmBC,uBAAiC;IAChE,OAAO,CAACC;QACJ,4FAA4F;QAC5F,IAAIC,eAAeC,OAAOF;QAE1B,KAAK,MAAMG,KAAKJ,wBAAyB;YACrCE,eAAeA,aAAa,OAAO,CAAC,IAAIG,OAAO,GAAGD,EAAE,QAAQ,CAAC,EAAE,OAAO,GAAGA,EAAE,aAAa,CAAC;QAC7F;QAEA,OAAOF;IACX;AACJ"}
|
|
@@ -54,6 +54,7 @@ function getLogRocketSdkOptions(userOptions, logger) {
|
|
|
54
54
|
// No clue why the "setup" method is not available. This package is shady.
|
|
55
55
|
// @ts-ignore
|
|
56
56
|
const { requestSanitizer: fuzzyRequestSanitizer, responseSanitizer: fuzzyResponseSanitizer } = logrocket_fuzzy_search_sanitizer.setup(mergedPrivateFieldNames);
|
|
57
|
+
const urlSanitizer = createUrlSanitizer(mergedPrivateQueryParameterNames);
|
|
57
58
|
const sdkOptions = {
|
|
58
59
|
console: {
|
|
59
60
|
// Prevent console logs from being visible in LogRocket session replays and leaking PII.
|
|
@@ -66,11 +67,11 @@ function getLogRocketSdkOptions(userOptions, logger) {
|
|
|
66
67
|
inputSanitizer: true
|
|
67
68
|
},
|
|
68
69
|
network: {
|
|
69
|
-
requestSanitizer: createRequestSanitizer(fuzzyRequestSanitizer),
|
|
70
|
-
responseSanitizer: createResponseSanitizer(fuzzyResponseSanitizer)
|
|
70
|
+
requestSanitizer: createRequestSanitizer(fuzzyRequestSanitizer, urlSanitizer),
|
|
71
|
+
responseSanitizer: createResponseSanitizer(fuzzyResponseSanitizer, urlSanitizer)
|
|
71
72
|
},
|
|
72
73
|
browser: {
|
|
73
|
-
urlSanitizer
|
|
74
|
+
urlSanitizer
|
|
74
75
|
},
|
|
75
76
|
shouldDebugLog: verbose
|
|
76
77
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"js/registerLogRocketInstrumentation.js","sources":["../../src/js/registerLogRocketInstrumentation.ts"],"sourcesContent":["import { HasExecutedGuard, type TelemetryContext } from \"@workleap-telemetry/core\";\nimport { createCompositeLogger, type RootLogger } from \"@workleap/logging\";\nimport LogRocket from \"logrocket\";\nimport LogrocketFuzzySanitizer from \"logrocket-fuzzy-search-sanitizer\";\nimport { applyTransformers, type LogRocketSdkOptionsTransformer } from \"./applyTransformers.ts\";\nimport { createRequestSanitizer } from \"./createRequestSanitizer.ts\";\nimport { createResponseSanitizer } from \"./createResponseSanitizer.ts\";\nimport { createUrlSanitizer } from \"./createUrlSanitizer.ts\";\nimport { DeviceIdTrait, type LogRocketInstrumentationClient, LogRocketInstrumentationClientImpl, TelemetryIdTrait } from \"./LogRocketInstrumentationClient.ts\";\nimport type { LogRocketSdkOptions } from \"./logRocketTypes.ts\";\n\nconst DefaultPrivateFieldNames = [\"email\", \"personalEmail\", \"phoneNumber\", \"password\", \"firstName\", \"lastName\", \"givenName\", \"fullName\", \"gender\", \"timeZone\", \"birthday\"];\n\nconst DefaultPrivateQueryParameterNames = [\"token\", \"email\", \"password\"];\n\n///////////////////////////\n\n/**\n * @see {@link https://workleap.github.io/wl-telemetry}\n */\nexport interface RegisterLogRocketInstrumentationOptions {\n /**\n * A root hostname to track sessions across subdomains. Set this option to capture traffic from all subdomains under one session: https://docs.logrocket.com/reference/roothostname.\n * @see {@link https://workleap.github.io/wl-telemetry}\n */\n rootHostname?: LogRocketSdkOptions[\"rootHostname\"];\n /**\n * Names of additional fields to exclude from session replays. These fields will be removed from network requests, responses using a fuzzy-matching algorithm.\n * @see {@link https://workleap.github.io/wl-telemetry}\n */\n privateFieldNames?: string[];\n /**\n * Names of additional fields to exclude from session replays. These fields will be removed from query parameters using a fuzzy-matching algorithm.\n * @see {@link https://workleap.github.io/wl-telemetry}\n */\n privateQueryParameterNames?: string[];\n /**\n * Context including telemetry correlation ids.\n * @see {@link https://workleap.github.io/wl-telemetry}\n */\n telemetryContext?: TelemetryContext;\n /**\n * Hooks to transform the resulting LogRocket SDK options.\n * @see {@link https://workleap.github.io/wl-telemetry}\n */\n transformers?: LogRocketSdkOptionsTransformer[];\n /**\n * Indicates whether or not debug information should be logged to the console.\n * @see {@link https://workleap.github.io/wl-telemetry}\n */\n verbose?: boolean;\n /**\n * The logger instances that will output messages.\n * @see {@link https://workleap.github.io/wl-telemetry}\n */\n loggers?: RootLogger[];\n}\n\n// The function return type is mandatory, otherwise we got an error TS4058.\nexport function getLogRocketSdkOptions(userOptions: RegisterLogRocketInstrumentationOptions, logger: RootLogger): LogRocketSdkOptions {\n const { rootHostname = \"workleap.com\", privateFieldNames = [], privateQueryParameterNames = [], verbose = false, transformers = [] } = userOptions;\n\n const mergedPrivateFieldNames = DefaultPrivateFieldNames.concat(privateFieldNames);\n const mergedPrivateQueryParameterNames = DefaultPrivateQueryParameterNames.concat(privateQueryParameterNames);\n\n // The \"LogrocketFuzzySearch.setup\" code is awkward. For now, we prefer to play it safe and call it once here, then forward the sanitize functions\n // to the appropriate factories.\n // No clue why the \"setup\" method is not available. This package is shady.\n // @ts-ignore\n const { requestSanitizer: fuzzyRequestSanitizer, responseSanitizer: fuzzyResponseSanitizer } = LogrocketFuzzySanitizer.setup(mergedPrivateFieldNames);\n\n const sdkOptions = {\n console: {\n // Prevent console logs from being visible in LogRocket session replays and leaking PII.\n // To capture logs in a session replay, use the LogRocketLogger class.\n isEnabled: false\n },\n rootHostname,\n dom: {\n textSanitizer: true,\n inputSanitizer: true\n },\n network: {\n requestSanitizer: createRequestSanitizer(fuzzyRequestSanitizer),\n responseSanitizer: createResponseSanitizer(fuzzyResponseSanitizer)\n },\n browser: {\n urlSanitizer: createUrlSanitizer(mergedPrivateQueryParameterNames)\n },\n shouldDebugLog: verbose\n } satisfies LogRocketSdkOptions;\n\n return applyTransformers(sdkOptions, transformers, {\n verbose,\n logger\n });\n}\n\n///////////////////////////\n\nlet registrationGuardInstance: HasExecutedGuard | undefined;\n\n// It's important to use a lazy singleton instead of a singleton to avoid\n// any future probleme related to HMR, tree-shaking, SSR, etc..\nfunction getRegistrationGuard() {\n if (!registrationGuardInstance) {\n registrationGuardInstance = new HasExecutedGuard();\n }\n\n return registrationGuardInstance;\n}\n\n// This function should only be used by tests.\nexport function __resetRegistrationGuard() {\n if (registrationGuardInstance) {\n registrationGuardInstance.reset();\n }\n}\n\n///////////////////////////\n\nexport class LogRocketInstrumentationRegistrator {\n register(appId: string, options: RegisterLogRocketInstrumentationOptions = {}) {\n const { telemetryContext, verbose = false, loggers = [] } = options;\n\n const logger = createCompositeLogger(verbose, loggers);\n const sdkOptions = getLogRocketSdkOptions(options, logger);\n\n // Session starts anonymously when LogRocket.init() is called.\n LogRocket.init(appId, sdkOptions);\n\n // Identify the user with the correlation ids.\n if (telemetryContext) {\n // LogRocket maintains the same session even if the user starts as anonymous and later becomes identified via LogRocket.identify().\n // If LogRocket.identify is called multiple times during a recording, you can search for any of the identified users in the session.\n LogRocket.identify(telemetryContext.deviceId, {\n [DeviceIdTrait]: telemetryContext.deviceId,\n [TelemetryIdTrait]: telemetryContext.telemetryId\n });\n }\n\n LogRocket.getSessionURL(url => {\n logger.withText(\"[logrocket] Session replay URL is now available:\").withText(url).debug();\n });\n\n logger.information(\"[logrocket] LogRocket instrumentation is registered.\");\n\n const client = new LogRocketInstrumentationClientImpl(telemetryContext);\n\n // The cast is to normalize the inferred return type to the \"LogRocketInstrumentationClient\" type.\n return client as LogRocketInstrumentationClient;\n }\n}\n\n/**\n * Register the LogRocket instrumentation.\n * @param appId LogRocket application id.\n * @param options LogRocket instrumentation options.\n * @returns {LogRocketInstrumentationClient} A LogRocket instrumentation client instance.\n * @see {@link https://workleap.github.io/wl-telemetry}\n */\nexport function registerLogRocketInstrumentation(appId: string, options?: RegisterLogRocketInstrumentationOptions) {\n getRegistrationGuard().throw('[logrocket] The LogRocket instrumentation has already been registered. Did you call the \"registerLogRocketInstrumentation\" function twice?');\n\n return new LogRocketInstrumentationRegistrator().register(appId, options);\n}\n"],"names":["HasExecutedGuard","createCompositeLogger","LogRocket","LogrocketFuzzySanitizer","applyTransformers","createRequestSanitizer","createResponseSanitizer","createUrlSanitizer","DeviceIdTrait","LogRocketInstrumentationClientImpl","TelemetryIdTrait","DefaultPrivateFieldNames","DefaultPrivateQueryParameterNames","getLogRocketSdkOptions","userOptions","logger","rootHostname","privateFieldNames","privateQueryParameterNames","verbose","transformers","mergedPrivateFieldNames","mergedPrivateQueryParameterNames","fuzzyRequestSanitizer","fuzzyResponseSanitizer","sdkOptions","registrationGuardInstance","getRegistrationGuard","__resetRegistrationGuard","LogRocketInstrumentationRegistrator","appId","options","telemetryContext","loggers","url","client","registerLogRocketInstrumentation"],"mappings":";;;;;;;;;;;;;;;;;;;AAAmF;AACR;AACzC;AACqC;AACyB;AAC3B;AACE;AACV;AACkG;AAG/J,MAAMW,2BAA2B;IAAC;IAAS;IAAiB;IAAe;IAAY;IAAa;IAAY;IAAa;IAAY;IAAU;IAAY;CAAW;AAE1K,MAAMC,oCAAoC;IAAC;IAAS;IAAS;CAAW;AA6CxE,2EAA2E;AACpE,SAASC,uBAAuBC,WAAoD,EAAEC,MAAkB;IAC3G,MAAM,EAAEC,eAAe,cAAc,EAAEC,oBAAoB,EAAE,EAAEC,6BAA6B,EAAE,EAAEC,UAAU,KAAK,EAAEC,eAAe,EAAE,EAAE,GAAGN;IAEvI,MAAMO,0BAA0BV,yBAAyB,MAAM,CAACM;IAChE,MAAMK,mCAAmCV,kCAAkC,MAAM,CAACM;IAElF,kJAAkJ;IAClJ,gCAAgC;IAChC,0EAA0E;IAC1E,aAAa;IACb,MAAM,EAAE,kBAAkBK,qBAAqB,EAAE,mBAAmBC,sBAAsB,EAAE,GAAGrB,sCAA6B,CAACkB;IAE7H,MAAMI,aAAa;QACf,SAAS;YACL,wFAAwF;YACxF,sEAAsE;YACtE,WAAW;QACf;QACAT;QACA,KAAK;YACD,eAAe;YACf,gBAAgB;QACpB;QACA,SAAS;YACL,kBAAkBX,sBAAsBA,CAACkB;YACzC,mBAAmBjB,uBAAuBA,CAACkB;QAC/C;QACA,SAAS;YACL,cAAcjB,kBAAkBA,CAACe;QACrC;QACA,gBAAgBH;IACpB;IAEA,OAAOf,iBAAiBA,CAACqB,YAAYL,cAAc;QAC/CD;QACAJ;IACJ;AACJ;AAEA,2BAA2B;AAE3B,IAAIW;AAEJ,yEAAyE;AACzE,+DAA+D;AAC/D,SAASC;IACL,IAAI,CAACD,2BAA2B;QAC5BA,4BAA4B,IAAI1B,gBAAgBA;IACpD;IAEA,OAAO0B;AACX;AAEA,8CAA8C;AACvC,SAASE;IACZ,IAAIF,2BAA2B;QAC3BA,0BAA0B,KAAK;IACnC;AACJ;AAEA,2BAA2B;AAEpB,MAAMG;IACT,SAASC,KAAa,EAAEC,UAAmD,CAAC,CAAC,EAAE;QAC3E,MAAM,EAAEC,gBAAgB,EAAEb,UAAU,KAAK,EAAEc,UAAU,EAAE,EAAE,GAAGF;QAE5D,MAAMhB,SAASd,qBAAqBA,CAACkB,SAASc;QAC9C,MAAMR,aAAaZ,uBAAuBkB,SAAShB;QAEnD,8DAA8D;QAC9Db,cAAc,CAAC4B,OAAOL;QAEtB,8CAA8C;QAC9C,IAAIO,kBAAkB;YAClB,mIAAmI;YACnI,oIAAoI;YACpI9B,kBAAkB,CAAC8B,iBAAiB,QAAQ,EAAE;gBAC1C,CAACxB,aAAaA,CAAC,EAAEwB,iBAAiB,QAAQ;gBAC1C,CAACtB,gBAAgBA,CAAC,EAAEsB,iBAAiB,WAAW;YACpD;QACJ;QAEA9B,uBAAuB,CAACgC,CAAAA;YACpBnB,OAAO,QAAQ,CAAC,oDAAoD,QAAQ,CAACmB,KAAK,KAAK;QAC3F;QAEAnB,OAAO,WAAW,CAAC;QAEnB,MAAMoB,SAAS,IAAI1B,kCAAkCA,CAACuB;QAEtD,kGAAkG;QAClG,OAAOG;IACX;AACJ;AAEA;;;;;;CAMC,GACM,SAASC,iCAAiCN,KAAa,EAAEC,OAAiD;IAC7GJ,uBAAuB,KAAK,CAAC;IAE7B,OAAO,IAAIE,sCAAsC,QAAQ,CAACC,OAAOC;AACrE"}
|
|
1
|
+
{"version":3,"file":"js/registerLogRocketInstrumentation.js","sources":["../../src/js/registerLogRocketInstrumentation.ts"],"sourcesContent":["import { HasExecutedGuard, type TelemetryContext } from \"@workleap-telemetry/core\";\nimport { createCompositeLogger, type RootLogger } from \"@workleap/logging\";\nimport LogRocket from \"logrocket\";\nimport LogrocketFuzzySanitizer from \"logrocket-fuzzy-search-sanitizer\";\nimport { applyTransformers, type LogRocketSdkOptionsTransformer } from \"./applyTransformers.ts\";\nimport { createRequestSanitizer } from \"./createRequestSanitizer.ts\";\nimport { createResponseSanitizer } from \"./createResponseSanitizer.ts\";\nimport { createUrlSanitizer } from \"./createUrlSanitizer.ts\";\nimport { DeviceIdTrait, type LogRocketInstrumentationClient, LogRocketInstrumentationClientImpl, TelemetryIdTrait } from \"./LogRocketInstrumentationClient.ts\";\nimport type { LogRocketSdkOptions } from \"./logRocketTypes.ts\";\n\nconst DefaultPrivateFieldNames = [\"email\", \"personalEmail\", \"phoneNumber\", \"password\", \"firstName\", \"lastName\", \"givenName\", \"fullName\", \"gender\", \"timeZone\", \"birthday\"];\n\nconst DefaultPrivateQueryParameterNames = [\"token\", \"email\", \"password\"];\n\n///////////////////////////\n\n/**\n * @see {@link https://workleap.github.io/wl-telemetry}\n */\nexport interface RegisterLogRocketInstrumentationOptions {\n /**\n * A root hostname to track sessions across subdomains. Set this option to capture traffic from all subdomains under one session: https://docs.logrocket.com/reference/roothostname.\n * @see {@link https://workleap.github.io/wl-telemetry}\n */\n rootHostname?: LogRocketSdkOptions[\"rootHostname\"];\n /**\n * Names of additional fields to exclude from session replays. These fields will be removed from network requests, responses using a fuzzy-matching algorithm.\n * @see {@link https://workleap.github.io/wl-telemetry}\n */\n privateFieldNames?: string[];\n /**\n * Names of additional fields to exclude from session replays. These fields will be removed from query parameters using a fuzzy-matching algorithm.\n * @see {@link https://workleap.github.io/wl-telemetry}\n */\n privateQueryParameterNames?: string[];\n /**\n * Context including telemetry correlation ids.\n * @see {@link https://workleap.github.io/wl-telemetry}\n */\n telemetryContext?: TelemetryContext;\n /**\n * Hooks to transform the resulting LogRocket SDK options.\n * @see {@link https://workleap.github.io/wl-telemetry}\n */\n transformers?: LogRocketSdkOptionsTransformer[];\n /**\n * Indicates whether or not debug information should be logged to the console.\n * @see {@link https://workleap.github.io/wl-telemetry}\n */\n verbose?: boolean;\n /**\n * The logger instances that will output messages.\n * @see {@link https://workleap.github.io/wl-telemetry}\n */\n loggers?: RootLogger[];\n}\n\n// The function return type is mandatory, otherwise we got an error TS4058.\nexport function getLogRocketSdkOptions(userOptions: RegisterLogRocketInstrumentationOptions, logger: RootLogger): LogRocketSdkOptions {\n const { rootHostname = \"workleap.com\", privateFieldNames = [], privateQueryParameterNames = [], verbose = false, transformers = [] } = userOptions;\n\n const mergedPrivateFieldNames = DefaultPrivateFieldNames.concat(privateFieldNames);\n const mergedPrivateQueryParameterNames = DefaultPrivateQueryParameterNames.concat(privateQueryParameterNames);\n\n // The \"LogrocketFuzzySearch.setup\" code is awkward. For now, we prefer to play it safe and call it once here, then forward the sanitize functions\n // to the appropriate factories.\n // No clue why the \"setup\" method is not available. This package is shady.\n // @ts-ignore\n const { requestSanitizer: fuzzyRequestSanitizer, responseSanitizer: fuzzyResponseSanitizer } = LogrocketFuzzySanitizer.setup(mergedPrivateFieldNames);\n\n const urlSanitizer = createUrlSanitizer(mergedPrivateQueryParameterNames);\n\n const sdkOptions = {\n console: {\n // Prevent console logs from being visible in LogRocket session replays and leaking PII.\n // To capture logs in a session replay, use the LogRocketLogger class.\n isEnabled: false\n },\n rootHostname,\n dom: {\n textSanitizer: true,\n inputSanitizer: true\n },\n network: {\n requestSanitizer: createRequestSanitizer(fuzzyRequestSanitizer, urlSanitizer),\n responseSanitizer: createResponseSanitizer(fuzzyResponseSanitizer, urlSanitizer)\n },\n browser: {\n urlSanitizer\n },\n shouldDebugLog: verbose\n } satisfies LogRocketSdkOptions;\n\n return applyTransformers(sdkOptions, transformers, {\n verbose,\n logger\n });\n}\n\n///////////////////////////\n\nlet registrationGuardInstance: HasExecutedGuard | undefined;\n\n// It's important to use a lazy singleton instead of a singleton to avoid\n// any future probleme related to HMR, tree-shaking, SSR, etc..\nfunction getRegistrationGuard() {\n if (!registrationGuardInstance) {\n registrationGuardInstance = new HasExecutedGuard();\n }\n\n return registrationGuardInstance;\n}\n\n// This function should only be used by tests.\nexport function __resetRegistrationGuard() {\n if (registrationGuardInstance) {\n registrationGuardInstance.reset();\n }\n}\n\n///////////////////////////\n\nexport class LogRocketInstrumentationRegistrator {\n register(appId: string, options: RegisterLogRocketInstrumentationOptions = {}) {\n const { telemetryContext, verbose = false, loggers = [] } = options;\n\n const logger = createCompositeLogger(verbose, loggers);\n const sdkOptions = getLogRocketSdkOptions(options, logger);\n\n // Session starts anonymously when LogRocket.init() is called.\n LogRocket.init(appId, sdkOptions);\n\n // Identify the user with the correlation ids.\n if (telemetryContext) {\n // LogRocket maintains the same session even if the user starts as anonymous and later becomes identified via LogRocket.identify().\n // If LogRocket.identify is called multiple times during a recording, you can search for any of the identified users in the session.\n LogRocket.identify(telemetryContext.deviceId, {\n [DeviceIdTrait]: telemetryContext.deviceId,\n [TelemetryIdTrait]: telemetryContext.telemetryId\n });\n }\n\n LogRocket.getSessionURL(url => {\n logger.withText(\"[logrocket] Session replay URL is now available:\").withText(url).debug();\n });\n\n logger.information(\"[logrocket] LogRocket instrumentation is registered.\");\n\n const client = new LogRocketInstrumentationClientImpl(telemetryContext);\n\n // The cast is to normalize the inferred return type to the \"LogRocketInstrumentationClient\" type.\n return client as LogRocketInstrumentationClient;\n }\n}\n\n/**\n * Register the LogRocket instrumentation.\n * @param appId LogRocket application id.\n * @param options LogRocket instrumentation options.\n * @returns {LogRocketInstrumentationClient} A LogRocket instrumentation client instance.\n * @see {@link https://workleap.github.io/wl-telemetry}\n */\nexport function registerLogRocketInstrumentation(appId: string, options?: RegisterLogRocketInstrumentationOptions) {\n getRegistrationGuard().throw('[logrocket] The LogRocket instrumentation has already been registered. Did you call the \"registerLogRocketInstrumentation\" function twice?');\n\n return new LogRocketInstrumentationRegistrator().register(appId, options);\n}\n"],"names":["HasExecutedGuard","createCompositeLogger","LogRocket","LogrocketFuzzySanitizer","applyTransformers","createRequestSanitizer","createResponseSanitizer","createUrlSanitizer","DeviceIdTrait","LogRocketInstrumentationClientImpl","TelemetryIdTrait","DefaultPrivateFieldNames","DefaultPrivateQueryParameterNames","getLogRocketSdkOptions","userOptions","logger","rootHostname","privateFieldNames","privateQueryParameterNames","verbose","transformers","mergedPrivateFieldNames","mergedPrivateQueryParameterNames","fuzzyRequestSanitizer","fuzzyResponseSanitizer","urlSanitizer","sdkOptions","registrationGuardInstance","getRegistrationGuard","__resetRegistrationGuard","LogRocketInstrumentationRegistrator","appId","options","telemetryContext","loggers","url","client","registerLogRocketInstrumentation"],"mappings":";;;;;;;;;;;;;;;;;;;AAAmF;AACR;AACzC;AACqC;AACyB;AAC3B;AACE;AACV;AACkG;AAG/J,MAAMW,2BAA2B;IAAC;IAAS;IAAiB;IAAe;IAAY;IAAa;IAAY;IAAa;IAAY;IAAU;IAAY;CAAW;AAE1K,MAAMC,oCAAoC;IAAC;IAAS;IAAS;CAAW;AA6CxE,2EAA2E;AACpE,SAASC,uBAAuBC,WAAoD,EAAEC,MAAkB;IAC3G,MAAM,EAAEC,eAAe,cAAc,EAAEC,oBAAoB,EAAE,EAAEC,6BAA6B,EAAE,EAAEC,UAAU,KAAK,EAAEC,eAAe,EAAE,EAAE,GAAGN;IAEvI,MAAMO,0BAA0BV,yBAAyB,MAAM,CAACM;IAChE,MAAMK,mCAAmCV,kCAAkC,MAAM,CAACM;IAElF,kJAAkJ;IAClJ,gCAAgC;IAChC,0EAA0E;IAC1E,aAAa;IACb,MAAM,EAAE,kBAAkBK,qBAAqB,EAAE,mBAAmBC,sBAAsB,EAAE,GAAGrB,sCAA6B,CAACkB;IAE7H,MAAMI,eAAelB,kBAAkBA,CAACe;IAExC,MAAMI,aAAa;QACf,SAAS;YACL,wFAAwF;YACxF,sEAAsE;YACtE,WAAW;QACf;QACAV;QACA,KAAK;YACD,eAAe;YACf,gBAAgB;QACpB;QACA,SAAS;YACL,kBAAkBX,sBAAsBA,CAACkB,uBAAuBE;YAChE,mBAAmBnB,uBAAuBA,CAACkB,wBAAwBC;QACvE;QACA,SAAS;YACLA;QACJ;QACA,gBAAgBN;IACpB;IAEA,OAAOf,iBAAiBA,CAACsB,YAAYN,cAAc;QAC/CD;QACAJ;IACJ;AACJ;AAEA,2BAA2B;AAE3B,IAAIY;AAEJ,yEAAyE;AACzE,+DAA+D;AAC/D,SAASC;IACL,IAAI,CAACD,2BAA2B;QAC5BA,4BAA4B,IAAI3B,gBAAgBA;IACpD;IAEA,OAAO2B;AACX;AAEA,8CAA8C;AACvC,SAASE;IACZ,IAAIF,2BAA2B;QAC3BA,0BAA0B,KAAK;IACnC;AACJ;AAEA,2BAA2B;AAEpB,MAAMG;IACT,SAASC,KAAa,EAAEC,UAAmD,CAAC,CAAC,EAAE;QAC3E,MAAM,EAAEC,gBAAgB,EAAEd,UAAU,KAAK,EAAEe,UAAU,EAAE,EAAE,GAAGF;QAE5D,MAAMjB,SAASd,qBAAqBA,CAACkB,SAASe;QAC9C,MAAMR,aAAab,uBAAuBmB,SAASjB;QAEnD,8DAA8D;QAC9Db,cAAc,CAAC6B,OAAOL;QAEtB,8CAA8C;QAC9C,IAAIO,kBAAkB;YAClB,mIAAmI;YACnI,oIAAoI;YACpI/B,kBAAkB,CAAC+B,iBAAiB,QAAQ,EAAE;gBAC1C,CAACzB,aAAaA,CAAC,EAAEyB,iBAAiB,QAAQ;gBAC1C,CAACvB,gBAAgBA,CAAC,EAAEuB,iBAAiB,WAAW;YACpD;QACJ;QAEA/B,uBAAuB,CAACiC,CAAAA;YACpBpB,OAAO,QAAQ,CAAC,oDAAoD,QAAQ,CAACoB,KAAK,KAAK;QAC3F;QAEApB,OAAO,WAAW,CAAC;QAEnB,MAAMqB,SAAS,IAAI3B,kCAAkCA,CAACwB;QAEtD,kGAAkG;QAClG,OAAOG;IACX;AACJ;AAEA;;;;;;CAMC,GACM,SAASC,iCAAiCN,KAAa,EAAEC,OAAiD;IAC7GJ,uBAAuB,KAAK,CAAC;IAE7B,OAAO,IAAIE,sCAAsC,QAAQ,CAACC,OAAOC;AACrE"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
// A header is redacted when its lowercased name contains one of the following values.
|
|
2
|
+
const PrivateHeaderNames = [
|
|
3
|
+
"auth",
|
|
4
|
+
"cookie",
|
|
5
|
+
"token",
|
|
6
|
+
"session",
|
|
7
|
+
"secret",
|
|
8
|
+
"key",
|
|
9
|
+
"csrf",
|
|
10
|
+
"xsrf",
|
|
11
|
+
"x-honeycomb-team"
|
|
12
|
+
];
|
|
13
|
+
// When the headers of a fetch request are provided as an array of tuples, LogRocket indexes the headers by their position
|
|
14
|
+
// and the header name ends up in the value, e.g. { "0": "Authorization,Bearer 123" }.
|
|
15
|
+
const IndexedHeaderNameRegEx = /^\d+$/;
|
|
16
|
+
function isPrivateHeader(name) {
|
|
17
|
+
const lowercasedName = name.toLowerCase();
|
|
18
|
+
return IndexedHeaderNameRegEx.test(name) || PrivateHeaderNames.some((x)=>lowercasedName.includes(x));
|
|
19
|
+
}
|
|
20
|
+
function sanitizeHeaders(headers) {
|
|
21
|
+
const sanitizedHeaders = {};
|
|
22
|
+
if (headers !== null && typeof headers === "object") {
|
|
23
|
+
for (const [name, value] of Object.entries(headers)){
|
|
24
|
+
sanitizedHeaders[name] = isPrivateHeader(name) ? "**redacted**" : value;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
return sanitizedHeaders;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export { sanitizeHeaders };
|
|
31
|
+
|
|
32
|
+
//# sourceMappingURL=sanitizeHeaders.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"js/sanitizeHeaders.js","sources":["../../src/js/sanitizeHeaders.ts"],"sourcesContent":["import type { LogRocketRequest } from \"./logRocketTypes.ts\";\n\n// A header is redacted when its lowercased name contains one of the following values.\nconst PrivateHeaderNames = [\"auth\", \"cookie\", \"token\", \"session\", \"secret\", \"key\", \"csrf\", \"xsrf\", \"x-honeycomb-team\"];\n\n// When the headers of a fetch request are provided as an array of tuples, LogRocket indexes the headers by their position\n// and the header name ends up in the value, e.g. { \"0\": \"Authorization,Bearer 123\" }.\nconst IndexedHeaderNameRegEx = /^\\d+$/;\n\nfunction isPrivateHeader(name: string) {\n const lowercasedName = name.toLowerCase();\n\n return IndexedHeaderNameRegEx.test(name) || PrivateHeaderNames.some(x => lowercasedName.includes(x));\n}\n\nexport function sanitizeHeaders(headers: unknown) {\n const sanitizedHeaders: LogRocketRequest[\"headers\"] = {};\n\n if (headers !== null && typeof headers === \"object\") {\n for (const [name, value] of Object.entries(headers)) {\n sanitizedHeaders[name] = isPrivateHeader(name) ? \"**redacted**\" : value;\n }\n }\n\n return sanitizedHeaders;\n}\n"],"names":["PrivateHeaderNames","IndexedHeaderNameRegEx","isPrivateHeader","name","lowercasedName","x","sanitizeHeaders","headers","sanitizedHeaders","value","Object"],"mappings":"AAEA,sFAAsF;AACtF,MAAMA,qBAAqB;IAAC;IAAQ;IAAU;IAAS;IAAW;IAAU;IAAO;IAAQ;IAAQ;CAAmB;AAEtH,0HAA0H;AAC1H,sFAAsF;AACtF,MAAMC,yBAAyB;AAE/B,SAASC,gBAAgBC,IAAY;IACjC,MAAMC,iBAAiBD,KAAK,WAAW;IAEvC,OAAOF,uBAAuB,IAAI,CAACE,SAASH,mBAAmB,IAAI,CAACK,CAAAA,IAAKD,eAAe,QAAQ,CAACC;AACrG;AAEO,SAASC,gBAAgBC,OAAgB;IAC5C,MAAMC,mBAAgD,CAAC;IAEvD,IAAID,YAAY,QAAQ,OAAOA,YAAY,UAAU;QACjD,KAAK,MAAM,CAACJ,MAAMM,MAAM,IAAIC,OAAO,OAAO,CAACH,SAAU;YACjDC,gBAAgB,CAACL,KAAK,GAAGD,gBAAgBC,QAAQ,iBAAiBM;QACtE;IACJ;IAEA,OAAOD;AACX"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@workleap/logrocket",
|
|
3
|
-
"version": "4.0.
|
|
3
|
+
"version": "4.0.7",
|
|
4
4
|
"description": "A package containing utilities for LogRocket.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"author": "Workleap",
|
|
@@ -30,8 +30,7 @@
|
|
|
30
30
|
},
|
|
31
31
|
"publishConfig": {
|
|
32
32
|
"access": "public",
|
|
33
|
-
"provenance": true
|
|
34
|
-
"registry": "https://registry.npmjs.org/"
|
|
33
|
+
"provenance": true
|
|
35
34
|
},
|
|
36
35
|
"dependencies": {
|
|
37
36
|
"@workleap-telemetry/core": "^2.0.7",
|
|
@@ -1,13 +1,14 @@
|
|
|
1
|
+
import type { UrlSanitizerFunction } from "./createUrlSanitizer.ts";
|
|
1
2
|
import type { LogRocketRequest } from "./logRocketTypes.ts";
|
|
3
|
+
import { sanitizeHeaders } from "./sanitizeHeaders.ts";
|
|
2
4
|
|
|
3
5
|
// oxlint-disable-next-line typescript/no-explicit-any
|
|
4
6
|
export type RequestSanitizerFunction = (req: LogRocketRequest) => any;
|
|
5
7
|
|
|
6
|
-
export function createRequestSanitizer(fuzzyRequestSanitizer: RequestSanitizerFunction) {
|
|
8
|
+
export function createRequestSanitizer(fuzzyRequestSanitizer: RequestSanitizerFunction, urlSanitizer: UrlSanitizerFunction) {
|
|
7
9
|
return (req: LogRocketRequest) => {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
}
|
|
10
|
+
req.url = urlSanitizer(req.url);
|
|
11
|
+
req.headers = sanitizeHeaders(req.headers);
|
|
11
12
|
|
|
12
13
|
if (req.body) {
|
|
13
14
|
// Do not return the request body, which could contain sensitive information.
|
|
@@ -1,10 +1,18 @@
|
|
|
1
|
+
import type { UrlSanitizerFunction } from "./createUrlSanitizer.ts";
|
|
1
2
|
import type { LogRocketResponse } from "./logRocketTypes.ts";
|
|
3
|
+
import { sanitizeHeaders } from "./sanitizeHeaders.ts";
|
|
2
4
|
|
|
3
5
|
// oxlint-disable-next-line typescript/no-explicit-any
|
|
4
6
|
export type ResponseSanitizerFunction = (res: LogRocketResponse) => any;
|
|
5
7
|
|
|
6
|
-
export function createResponseSanitizer(fuzzyResponseSanitizer: ResponseSanitizerFunction) {
|
|
8
|
+
export function createResponseSanitizer(fuzzyResponseSanitizer: ResponseSanitizerFunction, urlSanitizer: UrlSanitizerFunction) {
|
|
7
9
|
return (res: LogRocketResponse) => {
|
|
10
|
+
if (res.url) {
|
|
11
|
+
res.url = urlSanitizer(res.url);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
res.headers = sanitizeHeaders(res.headers);
|
|
15
|
+
|
|
8
16
|
if (res.body) {
|
|
9
17
|
// Do not return the response body, which could contain sensitive information.
|
|
10
18
|
res.body = "**redacted**";
|
|
@@ -1,9 +1,12 @@
|
|
|
1
|
+
export type UrlSanitizerFunction = (url: string) => string;
|
|
2
|
+
|
|
1
3
|
export function createUrlSanitizer(queryParametersToRemove: string[]) {
|
|
2
|
-
return (url: string) => {
|
|
3
|
-
|
|
4
|
+
return (url: string | URL) => {
|
|
5
|
+
// A network request made with XMLHttpRequest can provide a URL object rather than a string.
|
|
6
|
+
let sanitizedUrl = String(url);
|
|
4
7
|
|
|
5
8
|
for (const x of queryParametersToRemove) {
|
|
6
|
-
sanitizedUrl = sanitizedUrl.replace(new RegExp(`${x}=([^&]*)`, "
|
|
9
|
+
sanitizedUrl = sanitizedUrl.replace(new RegExp(`${x}=([^&]*)`, "gi"), `${x}=**redacted**`);
|
|
7
10
|
}
|
|
8
11
|
|
|
9
12
|
return sanitizedUrl;
|
|
@@ -69,6 +69,8 @@ export function getLogRocketSdkOptions(userOptions: RegisterLogRocketInstrumenta
|
|
|
69
69
|
// @ts-ignore
|
|
70
70
|
const { requestSanitizer: fuzzyRequestSanitizer, responseSanitizer: fuzzyResponseSanitizer } = LogrocketFuzzySanitizer.setup(mergedPrivateFieldNames);
|
|
71
71
|
|
|
72
|
+
const urlSanitizer = createUrlSanitizer(mergedPrivateQueryParameterNames);
|
|
73
|
+
|
|
72
74
|
const sdkOptions = {
|
|
73
75
|
console: {
|
|
74
76
|
// Prevent console logs from being visible in LogRocket session replays and leaking PII.
|
|
@@ -81,11 +83,11 @@ export function getLogRocketSdkOptions(userOptions: RegisterLogRocketInstrumenta
|
|
|
81
83
|
inputSanitizer: true
|
|
82
84
|
},
|
|
83
85
|
network: {
|
|
84
|
-
requestSanitizer: createRequestSanitizer(fuzzyRequestSanitizer),
|
|
85
|
-
responseSanitizer: createResponseSanitizer(fuzzyResponseSanitizer)
|
|
86
|
+
requestSanitizer: createRequestSanitizer(fuzzyRequestSanitizer, urlSanitizer),
|
|
87
|
+
responseSanitizer: createResponseSanitizer(fuzzyResponseSanitizer, urlSanitizer)
|
|
86
88
|
},
|
|
87
89
|
browser: {
|
|
88
|
-
urlSanitizer
|
|
90
|
+
urlSanitizer
|
|
89
91
|
},
|
|
90
92
|
shouldDebugLog: verbose
|
|
91
93
|
} satisfies LogRocketSdkOptions;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { LogRocketRequest } from "./logRocketTypes.ts";
|
|
2
|
+
|
|
3
|
+
// A header is redacted when its lowercased name contains one of the following values.
|
|
4
|
+
const PrivateHeaderNames = ["auth", "cookie", "token", "session", "secret", "key", "csrf", "xsrf", "x-honeycomb-team"];
|
|
5
|
+
|
|
6
|
+
// When the headers of a fetch request are provided as an array of tuples, LogRocket indexes the headers by their position
|
|
7
|
+
// and the header name ends up in the value, e.g. { "0": "Authorization,Bearer 123" }.
|
|
8
|
+
const IndexedHeaderNameRegEx = /^\d+$/;
|
|
9
|
+
|
|
10
|
+
function isPrivateHeader(name: string) {
|
|
11
|
+
const lowercasedName = name.toLowerCase();
|
|
12
|
+
|
|
13
|
+
return IndexedHeaderNameRegEx.test(name) || PrivateHeaderNames.some(x => lowercasedName.includes(x));
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function sanitizeHeaders(headers: unknown) {
|
|
17
|
+
const sanitizedHeaders: LogRocketRequest["headers"] = {};
|
|
18
|
+
|
|
19
|
+
if (headers !== null && typeof headers === "object") {
|
|
20
|
+
for (const [name, value] of Object.entries(headers)) {
|
|
21
|
+
sanitizedHeaders[name] = isPrivateHeader(name) ? "**redacted**" : value;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return sanitizedHeaders;
|
|
26
|
+
}
|