veryfront 0.1.746 → 0.1.748
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/esm/deno.js +1 -1
- package/esm/src/cache/backends/api.d.ts.map +1 -1
- package/esm/src/cache/backends/api.js +9 -5
- package/esm/src/middleware/builtin/security/redis-rate-limit.d.ts +2 -0
- package/esm/src/middleware/builtin/security/redis-rate-limit.d.ts.map +1 -1
- package/esm/src/middleware/builtin/security/redis-rate-limit.js +46 -13
- package/esm/src/observability/auto-instrument/http-instrumentation.d.ts.map +1 -1
- package/esm/src/observability/auto-instrument/http-instrumentation.js +10 -6
- package/esm/src/utils/logger/redact.d.ts +10 -0
- package/esm/src/utils/logger/redact.d.ts.map +1 -1
- package/esm/src/utils/logger/redact.js +47 -0
- package/esm/src/utils/version-constant.d.ts +1 -1
- package/esm/src/utils/version-constant.js +1 -1
- package/package.json +1 -1
package/esm/deno.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"api.d.ts","sourceRoot":"","sources":["../../../../src/src/cache/backends/api.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"api.d.ts","sourceRoot":"","sources":["../../../../src/src/cache/backends/api.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AA2BhD,qBAAa,eAAgB,YAAW,YAAY;IAClD,QAAQ,CAAC,IAAI,EAAG,KAAK,CAAU;IAC/B,OAAO,CAAC,UAAU,CAAS;IAC3B,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,cAAc,CAAC;gBAGrB,OAAO,GAAE;QACP,UAAU,CAAC,EAAE,MAAM,CAAC;QACpB,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,kBAAkB,CAAC,EAAE,MAAM,CAAC;KACxB;IAgBR,OAAO,CAAC,SAAS;YAIH,OAAO;IA8Ff,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;IAQxC,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC,CAAC;YAuBrD,eAAe;IAKvB,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,UAAU,SAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAQhE,QAAQ,CAAC,OAAO,EAAE,KAAK,CAAC;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,GAAG,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;IAYrF,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAI/B,YAAY,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;CAMrD"}
|
|
@@ -7,6 +7,7 @@ import { CircuitBreakerOpen, getCircuitBreaker } from "../../utils/circuit-break
|
|
|
7
7
|
import { getEnvValue } from "./helpers.js";
|
|
8
8
|
import { buildBatchResults } from "../batch-results.js";
|
|
9
9
|
import { REQUEST_ERROR } from "../../errors/index.js";
|
|
10
|
+
import { sanitizeUrlForSpan } from "../../utils/logger/redact.js";
|
|
10
11
|
const logger = baseLogger.component("api-cache-backend");
|
|
11
12
|
const DEFAULT_TIMEOUT_MS = 10_000;
|
|
12
13
|
const CIRCUIT_BREAKER_RESET_TIMEOUT_MS = 15_000;
|
|
@@ -56,7 +57,10 @@ export class ApiCacheBackend {
|
|
|
56
57
|
}
|
|
57
58
|
try {
|
|
58
59
|
return await this.circuitBreaker.execute(async () => {
|
|
59
|
-
const
|
|
60
|
+
const encodedProjectRef = encodeURIComponent(projectRef);
|
|
61
|
+
const url = `${this.apiBaseUrl}/projects/${encodedProjectRef}/cache${path}`;
|
|
62
|
+
const spanUrl = sanitizeUrlForSpan(url);
|
|
63
|
+
const cacheOperation = sanitizeUrlForSpan(path);
|
|
60
64
|
const controller = new AbortController();
|
|
61
65
|
const timeoutId = dntShim.setTimeout(() => controller.abort(), this.timeoutMs);
|
|
62
66
|
try {
|
|
@@ -70,9 +74,9 @@ export class ApiCacheBackend {
|
|
|
70
74
|
signal: controller.signal,
|
|
71
75
|
}), {
|
|
72
76
|
"http.method": method,
|
|
73
|
-
"http.url":
|
|
77
|
+
"http.url": spanUrl,
|
|
74
78
|
"http.host": new URL(this.apiBaseUrl).host,
|
|
75
|
-
"cache.operation":
|
|
79
|
+
"cache.operation": cacheOperation,
|
|
76
80
|
"cache.project_slug": projectRef,
|
|
77
81
|
});
|
|
78
82
|
if (!response.ok) {
|
|
@@ -100,7 +104,7 @@ export class ApiCacheBackend {
|
|
|
100
104
|
catch (error) {
|
|
101
105
|
if (error instanceof CircuitBreakerOpen) {
|
|
102
106
|
logger.info("Circuit breaker open, failing fast", {
|
|
103
|
-
path,
|
|
107
|
+
path: sanitizeUrlForSpan(path),
|
|
104
108
|
nextAttemptMs: error.nextAttemptMs,
|
|
105
109
|
});
|
|
106
110
|
return null;
|
|
@@ -108,7 +112,7 @@ export class ApiCacheBackend {
|
|
|
108
112
|
const isTimeout = error instanceof Error && error.name === "AbortError";
|
|
109
113
|
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
110
114
|
logger.info(`Request ${isTimeout ? "timeout" : "error"}`, {
|
|
111
|
-
path,
|
|
115
|
+
path: sanitizeUrlForSpan(path),
|
|
112
116
|
error: errorMsg,
|
|
113
117
|
isTimeout,
|
|
114
118
|
tokenSource,
|
|
@@ -12,6 +12,8 @@ export declare class RedisRateLimitStore implements RateLimitStore {
|
|
|
12
12
|
private readonly keyPrefix;
|
|
13
13
|
constructor(options?: RedisRateLimitOptions);
|
|
14
14
|
private ensureClient;
|
|
15
|
+
private clearCachedClient;
|
|
16
|
+
private attachClientLifecycleHandlers;
|
|
15
17
|
private connectClient;
|
|
16
18
|
private storageKey;
|
|
17
19
|
increment(key: string, windowMs: number): Promise<RateLimitEntry>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"redis-rate-limit.d.ts","sourceRoot":"","sources":["../../../../../src/src/middleware/builtin/security/redis-rate-limit.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"redis-rate-limit.d.ts","sourceRoot":"","sources":["../../../../../src/src/middleware/builtin/security/redis-rate-limit.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AA4BjE,4CAA4C;AAC5C,MAAM,WAAW,qBAAqB;IACpC,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,wCAAwC;AACxC,qBAAa,mBAAoB,YAAW,cAAc;IACxD,OAAO,CAAC,MAAM,CAA4B;IAC1C,OAAO,CAAC,aAAa,CAAqC;IAC1D,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAS;IAC9B,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;gBAEvB,OAAO,GAAE,qBAA0B;IAK/C,OAAO,CAAC,YAAY;IAMpB,OAAO,CAAC,iBAAiB;IAKzB,OAAO,CAAC,6BAA6B;YAWvB,aAAa;IAiC3B,OAAO,CAAC,UAAU;IAIZ,SAAS,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,CAAC;IAcjE,KAAK,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAKjC,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;CAK/B"}
|
|
@@ -1,6 +1,15 @@
|
|
|
1
1
|
import { createError, toError } from "../../../errors/veryfront-error.js";
|
|
2
2
|
import { serverLogger } from "../../../utils/index.js";
|
|
3
3
|
const logger = serverLogger.component("redis-ratelimit");
|
|
4
|
+
const INCREMENT_WITH_TTL_SCRIPT = `
|
|
5
|
+
local count = redis.call("INCR", KEYS[1])
|
|
6
|
+
local ttl = redis.call("PTTL", KEYS[1])
|
|
7
|
+
if ttl < 0 then
|
|
8
|
+
redis.call("PEXPIRE", KEYS[1], ARGV[1])
|
|
9
|
+
ttl = tonumber(ARGV[1])
|
|
10
|
+
end
|
|
11
|
+
return { count, ttl }
|
|
12
|
+
`;
|
|
4
13
|
/** Implement redis rate limit store. */
|
|
5
14
|
export class RedisRateLimitStore {
|
|
6
15
|
client = null;
|
|
@@ -17,6 +26,19 @@ export class RedisRateLimitStore {
|
|
|
17
26
|
this.clientPromise ??= this.connectClient();
|
|
18
27
|
return this.clientPromise;
|
|
19
28
|
}
|
|
29
|
+
clearCachedClient() {
|
|
30
|
+
this.client = null;
|
|
31
|
+
this.clientPromise = null;
|
|
32
|
+
}
|
|
33
|
+
attachClientLifecycleHandlers(client) {
|
|
34
|
+
client.on?.("error", (err) => {
|
|
35
|
+
logger.error("client error", err);
|
|
36
|
+
this.clearCachedClient();
|
|
37
|
+
});
|
|
38
|
+
client.on?.("end", () => {
|
|
39
|
+
this.clearCachedClient();
|
|
40
|
+
});
|
|
41
|
+
}
|
|
20
42
|
async connectClient() {
|
|
21
43
|
let createClient;
|
|
22
44
|
try {
|
|
@@ -34,11 +56,10 @@ export class RedisRateLimitStore {
|
|
|
34
56
|
}
|
|
35
57
|
try {
|
|
36
58
|
const client = createClient({ url: this.url });
|
|
37
|
-
|
|
38
|
-
logger.error("client error", err);
|
|
39
|
-
});
|
|
59
|
+
this.attachClientLifecycleHandlers(client);
|
|
40
60
|
await client.connect();
|
|
41
61
|
this.client = client;
|
|
62
|
+
this.clientPromise = null;
|
|
42
63
|
return client;
|
|
43
64
|
}
|
|
44
65
|
catch (error) {
|
|
@@ -52,15 +73,10 @@ export class RedisRateLimitStore {
|
|
|
52
73
|
async increment(key, windowMs) {
|
|
53
74
|
const client = await this.ensureClient();
|
|
54
75
|
const redisKey = this.storageKey(key);
|
|
55
|
-
const count = await client.
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
}
|
|
59
|
-
const pttl = await client.pTTL(redisKey);
|
|
60
|
-
if (pttl === -1) {
|
|
61
|
-
await client.pExpire(redisKey, windowMs);
|
|
62
|
-
return { count, resetAt: Date.now() + windowMs };
|
|
63
|
-
}
|
|
76
|
+
const [count, pttl] = parseIncrementResult(await client.eval(INCREMENT_WITH_TTL_SCRIPT, {
|
|
77
|
+
keys: [redisKey],
|
|
78
|
+
arguments: [String(windowMs)],
|
|
79
|
+
}));
|
|
64
80
|
const ttl = pttl > 0 ? pttl : windowMs;
|
|
65
81
|
return { count, resetAt: Date.now() + ttl };
|
|
66
82
|
}
|
|
@@ -72,6 +88,23 @@ export class RedisRateLimitStore {
|
|
|
72
88
|
if (!this.client)
|
|
73
89
|
return;
|
|
74
90
|
await this.client.disconnect();
|
|
75
|
-
this.
|
|
91
|
+
this.clearCachedClient();
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
function parseIncrementResult(result) {
|
|
95
|
+
if (!Array.isArray(result) || result.length < 2) {
|
|
96
|
+
throw toError(createError({
|
|
97
|
+
type: "config",
|
|
98
|
+
message: "Redis rate limit eval returned an invalid result.",
|
|
99
|
+
}));
|
|
100
|
+
}
|
|
101
|
+
const count = Number(result[0]);
|
|
102
|
+
const ttl = Number(result[1]);
|
|
103
|
+
if (!Number.isFinite(count) || !Number.isFinite(ttl)) {
|
|
104
|
+
throw toError(createError({
|
|
105
|
+
type: "config",
|
|
106
|
+
message: "Redis rate limit eval returned non-numeric values.",
|
|
107
|
+
}));
|
|
76
108
|
}
|
|
109
|
+
return [count, ttl];
|
|
77
110
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"http-instrumentation.d.ts","sourceRoot":"","sources":["../../../../src/src/observability/auto-instrument/http-instrumentation.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"http-instrumentation.d.ts","sourceRoot":"","sources":["../../../../src/src/observability/auto-instrument/http-instrumentation.ts"],"names":[],"mappings":"AAoCA,mCAAmC;AACnC,wBAAgB,qBAAqB,CACnC,OAAO,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,OAAO,CAAC,QAAQ,CAAC,GAAG,QAAQ,GAC1D,CAAC,OAAO,EAAE,OAAO,KAAK,OAAO,CAAC,QAAQ,CAAC,CAiCzC;AACD,2EAA2E;AAC3E,wBAAgB,uBAAuB,CACrC,SAAS,GAAE,OAAO,KAAwB,GACzC,OAAO,KAAK,CAsDd"}
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
import { serverLogger } from "../../utils/index.js";
|
|
2
|
+
import { sanitizeUrlForSpan } from "../../utils/logger/redact.js";
|
|
2
3
|
import { context as otContext, propagation, SpanKind, SpanStatusCode, trace, } from "../tracing/api-shim.js";
|
|
3
4
|
const logger = serverLogger.component("auto-instrument");
|
|
4
|
-
|
|
5
|
+
function getHttpTracer() {
|
|
6
|
+
return trace.getTracer("veryfront-http");
|
|
7
|
+
}
|
|
5
8
|
const headersGetter = {
|
|
6
9
|
keys(carrier) {
|
|
7
10
|
return [...carrier.keys()];
|
|
@@ -27,7 +30,7 @@ export function instrumentHttpHandler(handler) {
|
|
|
27
30
|
const httpAttrs = buildHttpAttributes(request, url);
|
|
28
31
|
const parentContext = extractParentContext(request.headers);
|
|
29
32
|
try {
|
|
30
|
-
return await
|
|
33
|
+
return await getHttpTracer().startActiveSpan("http.server.request", { kind: SpanKind.SERVER, attributes: httpAttrs }, parentContext, async (span) => {
|
|
31
34
|
try {
|
|
32
35
|
const response = await handler(request);
|
|
33
36
|
recordResponseSuccess(span, response, performance.now() - startTime, httpAttrs);
|
|
@@ -54,10 +57,11 @@ export function createInstrumentedFetch(baseFetch = globalThis.fetch) {
|
|
|
54
57
|
const startTime = performance.now();
|
|
55
58
|
const urlString = extractFetchUrl(input);
|
|
56
59
|
const method = init?.method ?? "GET";
|
|
60
|
+
const spanUrl = sanitizeUrlForSpan(urlString);
|
|
57
61
|
const fetchAttrs = {
|
|
58
62
|
"http.method": method,
|
|
59
|
-
"http.url":
|
|
60
|
-
"http.target":
|
|
63
|
+
"http.url": spanUrl,
|
|
64
|
+
"http.target": spanUrl,
|
|
61
65
|
"http.host": "",
|
|
62
66
|
"http.scheme": "",
|
|
63
67
|
};
|
|
@@ -71,7 +75,7 @@ export function createInstrumentedFetch(baseFetch = globalThis.fetch) {
|
|
|
71
75
|
/* expected: relative URLs cannot be parsed, leave defaults */
|
|
72
76
|
}
|
|
73
77
|
try {
|
|
74
|
-
return await
|
|
78
|
+
return await getHttpTracer().startActiveSpan("http.client.fetch", { kind: SpanKind.CLIENT, attributes: fetchAttrs }, async (span) => {
|
|
75
79
|
try {
|
|
76
80
|
const headers = new Headers(init?.headers);
|
|
77
81
|
propagation.inject(otContext.active(), headers, {
|
|
@@ -99,7 +103,7 @@ export function createInstrumentedFetch(baseFetch = globalThis.fetch) {
|
|
|
99
103
|
function buildHttpAttributes(request, url) {
|
|
100
104
|
return {
|
|
101
105
|
"http.method": request.method,
|
|
102
|
-
"http.url": request.url,
|
|
106
|
+
"http.url": sanitizeUrlForSpan(request.url),
|
|
103
107
|
"http.target": url.pathname,
|
|
104
108
|
"http.host": url.host,
|
|
105
109
|
"http.scheme": url.protocol.replace(":", ""),
|
|
@@ -50,6 +50,16 @@ export declare function redactSensitive<T>(context: T): T;
|
|
|
50
50
|
* Non-URL strings pass through unchanged.
|
|
51
51
|
*/
|
|
52
52
|
export declare function sanitizeUrlCredentials(input: string): string;
|
|
53
|
+
/**
|
|
54
|
+
* Return the URL form safe to attach to observability span attributes.
|
|
55
|
+
*
|
|
56
|
+
* Span attributes bypass the logger's structured redaction pass, so `http.url`
|
|
57
|
+
* must not include query strings, fragments, or URL userinfo. This intentionally
|
|
58
|
+
* strips every query parameter instead of selectively redacting credential-like
|
|
59
|
+
* names because cache keys and callback state can be sensitive even when the
|
|
60
|
+
* parameter name is not obviously a credential.
|
|
61
|
+
*/
|
|
62
|
+
export declare function sanitizeUrlForSpan(input: string): string;
|
|
53
63
|
/**
|
|
54
64
|
* Apply {@link sanitizeUrlCredentials} to the `message` and `stack` of a
|
|
55
65
|
* serialized-error-shaped object, returning a new object. Used by the logger's
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"redact.d.ts","sourceRoot":"","sources":["../../../../src/src/utils/logger/redact.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAIH,6DAA6D;AAC7D,eAAO,MAAM,QAAQ,eAAe,CAAC;AA2CrC;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAGnD;AA6ED;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,CAAC,EAAE,OAAO,EAAE,CAAC,GAAG,CAAC,CAEhD;AA2BD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CA4B5D;AAED;;;;;;GAMG;AACH,wBAAgB,uBAAuB,CACrC,CAAC,SAAS;IAAE,OAAO,CAAC,EAAE,OAAO,CAAC;IAAC,KAAK,CAAC,EAAE,OAAO,CAAA;CAAE,GAAG,SAAS,EAC5D,KAAK,EAAE,CAAC,GAAG,CAAC,CAMb"}
|
|
1
|
+
{"version":3,"file":"redact.d.ts","sourceRoot":"","sources":["../../../../src/src/utils/logger/redact.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAIH,6DAA6D;AAC7D,eAAO,MAAM,QAAQ,eAAe,CAAC;AA2CrC;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAGnD;AA6ED;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,CAAC,EAAE,OAAO,EAAE,CAAC,GAAG,CAAC,CAEhD;AA2BD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CA4B5D;AAqBD;;;;;;;;GAQG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAgBxD;AAED;;;;;;GAMG;AACH,wBAAgB,uBAAuB,CACrC,CAAC,SAAS;IAAE,OAAO,CAAC,EAAE,OAAO,CAAC;IAAC,KAAK,CAAC,EAAE,OAAO,CAAA;CAAE,GAAG,SAAS,EAC5D,KAAK,EAAE,CAAC,GAAG,CAAC,CAMb"}
|
|
@@ -216,6 +216,53 @@ export function sanitizeUrlCredentials(input) {
|
|
|
216
216
|
});
|
|
217
217
|
return out;
|
|
218
218
|
}
|
|
219
|
+
function firstUrlDelimiterIndex(input) {
|
|
220
|
+
const queryIndex = input.indexOf("?");
|
|
221
|
+
const hashIndex = input.indexOf("#");
|
|
222
|
+
if (queryIndex === -1)
|
|
223
|
+
return hashIndex;
|
|
224
|
+
if (hashIndex === -1)
|
|
225
|
+
return queryIndex;
|
|
226
|
+
return Math.min(queryIndex, hashIndex);
|
|
227
|
+
}
|
|
228
|
+
function sanitizeProtocolRelativeUrlForSpan(input) {
|
|
229
|
+
if (!input.startsWith("//"))
|
|
230
|
+
return null;
|
|
231
|
+
try {
|
|
232
|
+
const url = new URL(`https:${input}`);
|
|
233
|
+
return `//${url.host}${url.pathname}`;
|
|
234
|
+
}
|
|
235
|
+
catch (_) {
|
|
236
|
+
return null;
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
/**
|
|
240
|
+
* Return the URL form safe to attach to observability span attributes.
|
|
241
|
+
*
|
|
242
|
+
* Span attributes bypass the logger's structured redaction pass, so `http.url`
|
|
243
|
+
* must not include query strings, fragments, or URL userinfo. This intentionally
|
|
244
|
+
* strips every query parameter instead of selectively redacting credential-like
|
|
245
|
+
* names because cache keys and callback state can be sensitive even when the
|
|
246
|
+
* parameter name is not obviously a credential.
|
|
247
|
+
*/
|
|
248
|
+
export function sanitizeUrlForSpan(input) {
|
|
249
|
+
if (typeof input !== "string" || input.length === 0)
|
|
250
|
+
return input;
|
|
251
|
+
try {
|
|
252
|
+
const url = new URL(input);
|
|
253
|
+
if (url.origin !== "null")
|
|
254
|
+
return `${url.origin}${url.pathname}`;
|
|
255
|
+
}
|
|
256
|
+
catch (_) {
|
|
257
|
+
// Relative or malformed URL-shaped strings are handled by the fallback.
|
|
258
|
+
}
|
|
259
|
+
const delimiterIndex = firstUrlDelimiterIndex(input);
|
|
260
|
+
const withoutQueryOrFragment = delimiterIndex === -1 ? input : input.slice(0, delimiterIndex);
|
|
261
|
+
const protocolRelativeUrl = sanitizeProtocolRelativeUrlForSpan(withoutQueryOrFragment);
|
|
262
|
+
if (protocolRelativeUrl)
|
|
263
|
+
return protocolRelativeUrl;
|
|
264
|
+
return sanitizeUrlCredentials(withoutQueryOrFragment);
|
|
265
|
+
}
|
|
219
266
|
/**
|
|
220
267
|
* Apply {@link sanitizeUrlCredentials} to the `message` and `stack` of a
|
|
221
268
|
* serialized-error-shaped object, returning a new object. Used by the logger's
|