2025-01-31 02:46:38 -05:00
|
|
|
/*
|
|
|
|
* SPDX-FileCopyrightText: hazelnoot and other Sharkey contributors
|
|
|
|
* SPDX-License-Identifier: AGPL-3.0-only
|
|
|
|
*/
|
|
|
|
|
2025-03-21 20:38:28 -04:00
|
|
|
import { Inject, Injectable } from '@nestjs/common';
|
2025-03-22 18:16:48 -04:00
|
|
|
import { FastifyRequest } from 'fastify';
|
2025-03-21 20:38:28 -04:00
|
|
|
import Logger from '@/logger.js';
|
2025-01-31 02:46:38 -05:00
|
|
|
import { LoggerService } from '@/core/LoggerService.js';
|
2025-03-21 20:38:28 -04:00
|
|
|
import { ApiError } from '@/server/api/error.js';
|
|
|
|
import { EnvService } from '@/core/EnvService.js';
|
2025-03-22 18:16:48 -04:00
|
|
|
import { getBaseUrl } from '@/server/api/mastodon/MastodonClientService.js';
|
2025-01-31 02:46:38 -05:00
|
|
|
|
|
|
|
@Injectable()
|
|
|
|
export class MastodonLogger {
|
|
|
|
public readonly logger: Logger;
|
|
|
|
|
2025-03-21 20:38:28 -04:00
|
|
|
constructor(
|
|
|
|
@Inject(EnvService)
|
|
|
|
private readonly envService: EnvService,
|
|
|
|
|
|
|
|
loggerService: LoggerService,
|
|
|
|
) {
|
2025-01-31 02:46:38 -05:00
|
|
|
this.logger = loggerService.getLogger('masto-api');
|
|
|
|
}
|
|
|
|
|
2025-03-21 20:38:28 -04:00
|
|
|
public error(request: FastifyRequest, error: MastodonError, status: number): void {
|
|
|
|
if ((status < 400 && status > 499) || this.envService.env.NODE_ENV === 'development') {
|
2025-03-22 18:16:48 -04:00
|
|
|
const path = new URL(request.url, getBaseUrl(request)).pathname;
|
|
|
|
this.logger.error(`Error in mastodon endpoint ${request.method} ${path}:`, error);
|
2025-03-21 20:38:28 -04:00
|
|
|
}
|
2025-01-31 02:46:38 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2025-03-21 20:38:28 -04:00
|
|
|
// TODO move elsewhere
|
|
|
|
export interface MastodonError {
|
|
|
|
error: string;
|
2025-03-27 20:30:04 -04:00
|
|
|
error_description?: string;
|
2025-03-21 20:38:28 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
export function getErrorData(error: unknown): MastodonError {
|
2025-03-24 12:20:18 -04:00
|
|
|
// Axios wraps errors from the backend
|
|
|
|
error = unpackAxiosError(error);
|
|
|
|
|
|
|
|
if (!error || typeof(error) !== 'object') {
|
|
|
|
return {
|
|
|
|
error: 'UNKNOWN_ERROR',
|
|
|
|
error_description: String(error),
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
if (error instanceof ApiError) {
|
|
|
|
return convertApiError(error);
|
|
|
|
}
|
|
|
|
|
|
|
|
if ('code' in error && typeof (error.code) === 'string') {
|
|
|
|
if ('message' in error && typeof (error.message) === 'string') {
|
|
|
|
return convertApiError(error as ApiError);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (error instanceof Error) {
|
|
|
|
return convertGenericError(error);
|
|
|
|
}
|
|
|
|
|
|
|
|
return convertUnknownError(error);
|
|
|
|
}
|
|
|
|
|
|
|
|
function unpackAxiosError(error: unknown): unknown {
|
2025-03-21 20:38:28 -04:00
|
|
|
if (error && typeof(error) === 'object') {
|
2025-03-24 12:20:18 -04:00
|
|
|
if ('response' in error && error.response && typeof (error.response) === 'object') {
|
|
|
|
if ('data' in error.response && error.response.data && typeof (error.response.data) === 'object') {
|
|
|
|
if ('error' in error.response.data && error.response.data.error && typeof(error.response.data.error) === 'object') {
|
|
|
|
return error.response.data.error;
|
2025-03-21 20:38:28 -04:00
|
|
|
}
|
2025-03-24 12:20:18 -04:00
|
|
|
|
|
|
|
return error.response.data;
|
2025-03-21 20:38:28 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
// No data - this is a fallback to avoid leaking request/response details in the error
|
2025-03-24 12:20:18 -04:00
|
|
|
return undefined;
|
2025-03-21 20:38:28 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2025-03-24 12:20:18 -04:00
|
|
|
return error;
|
2025-03-21 20:38:28 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
function convertApiError(apiError: ApiError): MastodonError {
|
|
|
|
const mastoError: MastodonError & Partial<ApiError> = {
|
|
|
|
error: apiError.code,
|
|
|
|
error_description: apiError.message,
|
|
|
|
...apiError,
|
|
|
|
};
|
|
|
|
|
|
|
|
delete mastoError.code;
|
|
|
|
delete mastoError.message;
|
2025-03-21 23:24:02 -04:00
|
|
|
delete mastoError.httpStatusCode;
|
2025-03-21 20:38:28 -04:00
|
|
|
|
|
|
|
return mastoError;
|
|
|
|
}
|
|
|
|
|
|
|
|
function convertUnknownError(data: object = {}): MastodonError {
|
|
|
|
return Object.assign({}, data, {
|
|
|
|
error: 'INTERNAL_ERROR',
|
|
|
|
error_description: 'Internal error occurred. Please contact us if the error persists.',
|
|
|
|
id: '5d37dbcb-891e-41ca-a3d6-e690c97775ac',
|
|
|
|
kind: 'server',
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
function convertGenericError(error: Error): MastodonError {
|
|
|
|
const mastoError: MastodonError & Partial<Error> = {
|
|
|
|
error: 'INTERNAL_ERROR',
|
|
|
|
error_description: String(error),
|
|
|
|
...error,
|
|
|
|
};
|
|
|
|
|
|
|
|
delete mastoError.name;
|
|
|
|
delete mastoError.message;
|
|
|
|
delete mastoError.stack;
|
|
|
|
|
|
|
|
return mastoError;
|
|
|
|
}
|
|
|
|
|
|
|
|
export function getErrorStatus(error: unknown): number {
|
2025-03-24 12:20:18 -04:00
|
|
|
if (error && typeof(error) === 'object') {
|
|
|
|
// Axios wraps errors from the backend
|
|
|
|
if ('response' in error && typeof (error.response) === 'object' && error.response) {
|
|
|
|
if ('status' in error.response && typeof(error.response.status) === 'number') {
|
|
|
|
return error.response.status;
|
2025-01-31 02:46:38 -05:00
|
|
|
}
|
|
|
|
}
|
2025-03-21 20:38:28 -04:00
|
|
|
|
2025-03-24 12:20:18 -04:00
|
|
|
if ('httpStatusCode' in error && typeof(error.httpStatusCode) === 'number') {
|
|
|
|
return error.httpStatusCode;
|
|
|
|
}
|
2025-03-21 20:38:28 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
return 500;
|
2025-01-31 02:46:38 -05:00
|
|
|
}
|