import { ClientLogCreate } from '@shared/models/server' import { peertubeLocalStorage } from './peertube-web-storage' import { OAuthUserTokens } from './users' export type LoggerHook = (message: LoggerMessage, meta?: LoggerMeta) => void export type LoggerLevel = 'info' | 'warn' | 'error' export type LoggerMessage = string | Error | object export type LoggerMeta = Error | { [ id: string ]: any, err?: Error } declare global { interface Window { logger: Logger } } class Logger { private readonly hooks: { level: LoggerLevel, hook: LoggerHook }[] = [] info (message: LoggerMessage, meta?: LoggerMeta) { this.runHooks('info', message, meta) if (meta) console.log(message, meta) else console.log(message) } warn (message: LoggerMessage, meta?: LoggerMeta) { this.runHooks('warn', message, meta) this.clientWarn(message, meta) } clientWarn (message: LoggerMessage, meta?: LoggerMeta) { if (meta) console.warn(message, meta) else console.warn(message) } error (message: LoggerMessage, meta?: LoggerMeta) { this.runHooks('error', message, meta) this.clientError(message, meta) } clientError (message: LoggerMessage, meta?: LoggerMeta) { if (meta) console.error(message, meta) else console.error(message) } addHook (level: LoggerLevel, hook: LoggerHook) { this.hooks.push({ level, hook }) } registerServerSending (serverUrl: string) { this.addHook('warn', (message, meta) => this.sendClientLog(serverUrl, this.buildServerLogPayload('warn', message, meta))) this.addHook('error', (message, meta) => this.sendClientLog(serverUrl, this.buildServerLogPayload('error', message, meta))) } sendClientLog (serverUrl: string, payload: ClientLogCreate | null) { if (!payload) return const headers = new Headers({ 'Accept': 'application/json', 'Content-Type': 'application/json' }) try { const tokens = OAuthUserTokens.getUserTokens(peertubeLocalStorage) if (tokens) headers.set('Authorization', `${tokens.tokenType} ${tokens.accessToken}`) } catch (err) { console.error('Cannot set tokens to client log sender.', { err }) } try { fetch(serverUrl + '/api/v1/server/logs/client', { headers, method: 'POST', body: JSON.stringify(payload) }) } catch (err) { console.error('Cannot send client warn/error to server.', err) } } private buildServerLogPayload (level: Extract, message: LoggerMessage, meta?: LoggerMeta) { if (!message) return null return { message: this.buildMessageServerLogPayload(message), userAgent: navigator.userAgent, url: window.location.href, level, stackTrace: this.buildStackServerLogPayload(message, meta), meta: this.buildMetaServerLogPayload(meta) } } private buildMessageServerLogPayload (message: LoggerMessage) { if (typeof message === 'string') return message if (message instanceof Error) return message.message return JSON.stringify(message) } private buildStackServerLogPayload (message: LoggerMessage, meta?: LoggerMeta) { if (message instanceof Error) return this.buildStack(message) if (meta instanceof Error) return this.buildStack(meta) if (meta?.err instanceof Error) return this.buildStack(meta.err) return undefined } private buildMetaServerLogPayload (meta?: LoggerMeta) { if (!meta) return undefined if (meta instanceof Error) return undefined let result: string try { result = JSON.stringify(meta, (key, value) => { if (key === 'err') return undefined return value }) } catch (err) { console.error('Cannot stringify meta.', err) } return result } private runHooks (level: LoggerLevel, message: LoggerMessage, meta?: LoggerMeta) { for (const hookObj of this.hooks) { if (hookObj.level !== level) continue hookObj.hook(message, meta) } } private buildStack (err: Error) { return `${err.message}\n${err.stack || ''}` } } const logger = window.logger || new Logger() window.logger = logger export { logger }