|
|
|
@ -1,7 +1,11 @@ |
|
|
|
import { redactPaths } from '@ghostfolio/api/helper/object.helper'; |
|
|
|
|
|
|
|
import { Injectable, Logger } from '@nestjs/common'; |
|
|
|
|
|
|
|
@Injectable() |
|
|
|
export class FetchService { |
|
|
|
private static readonly REDACTED_QUERY_PARAM_NAMES = ['apikey', 'api_token']; |
|
|
|
|
|
|
|
public async fetch( |
|
|
|
input: RequestInfo | URL, |
|
|
|
init?: RequestInit |
|
|
|
@ -13,20 +17,21 @@ export class FetchService { |
|
|
|
).toUpperCase(); |
|
|
|
|
|
|
|
const url = input instanceof Request ? input.url : input.toString(); |
|
|
|
const urlRedacted = this.redactUrl(url); |
|
|
|
|
|
|
|
Logger.debug(`${method} ${url}`, 'FetchService'); |
|
|
|
Logger.debug(`${method} ${urlRedacted}`, 'FetchService'); |
|
|
|
|
|
|
|
try { |
|
|
|
return await globalThis.fetch(input, init); |
|
|
|
} catch (error) { |
|
|
|
if (error instanceof Error) { |
|
|
|
Logger.error( |
|
|
|
`${method} ${url} failed: [${error.name}] ${error.message}`, |
|
|
|
`${method} ${urlRedacted} failed: [${error.name}] ${error.message}`, |
|
|
|
'FetchService' |
|
|
|
); |
|
|
|
} else { |
|
|
|
Logger.error( |
|
|
|
`${method} ${url} failed: ${String(error)}`, |
|
|
|
`${method} ${urlRedacted} failed: ${String(error)}`, |
|
|
|
'FetchService' |
|
|
|
); |
|
|
|
} |
|
|
|
@ -34,4 +39,25 @@ export class FetchService { |
|
|
|
throw error; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
private redactUrl(rawUrl: string): string { |
|
|
|
try { |
|
|
|
const url = new URL(rawUrl); |
|
|
|
|
|
|
|
const redacted = redactPaths({ |
|
|
|
object: Object.fromEntries(url.searchParams), |
|
|
|
paths: FetchService.REDACTED_QUERY_PARAM_NAMES |
|
|
|
}); |
|
|
|
|
|
|
|
for (const [key, value] of Object.entries(redacted)) { |
|
|
|
if (value === null) { |
|
|
|
url.searchParams.set(key, '*******'); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
return url.toString(); |
|
|
|
} catch { |
|
|
|
return rawUrl; |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|