diff --git a/tdrive/backend/node/src/services/user/web/controller.ts b/tdrive/backend/node/src/services/user/web/controller.ts index b350df56..d13283aa 100644 --- a/tdrive/backend/node/src/services/user/web/controller.ts +++ b/tdrive/backend/node/src/services/user/web/controller.ts @@ -36,6 +36,7 @@ import { formatCompany, getCompanyStats } from "../utils"; import { formatUser } from "../../../utils/users"; import gr from "../../global-resolver"; import config from "config"; +import { getLogger } from "../../../core/platform/framework"; export class UsersCrudController implements @@ -93,6 +94,30 @@ export class UsersCrudController }; } + /** This allows a logged in user to upload log entries */ + async reportClientLog( + request: FastifyRequest<{ Body: { [key: string]: string } }>, + ): Promise { + const headers = { ...request.headers }; + const boringOrSecretHeaders = + "cookie authorization cache-control connection pragma content-length content-type accept accept-encoding accept-language".split( + /\s+/g, + ); + boringOrSecretHeaders.forEach(header => delete headers[header]); + const message = + request.body.message || + "(missing message property in UsersCrudController.reportClientLog request body)"; + delete request.body.message; + getLogger("FromBrowser").error( + { + headers, + ...request.body, + }, + message, + ); + return {}; + } + async setPreferences( request: FastifyRequest<{ Body: User["preferences"] }>, ): Promise { diff --git a/tdrive/backend/node/src/services/user/web/routes.ts b/tdrive/backend/node/src/services/user/web/routes.ts index beb3d659..6937e816 100644 --- a/tdrive/backend/node/src/services/user/web/routes.ts +++ b/tdrive/backend/node/src/services/user/web/routes.ts @@ -9,6 +9,7 @@ import { getUserSchema, getUsersSchema, postDevicesSchema, + sendUserClientReportSchema, setUserPreferencesSchema, } from "./schemas"; @@ -52,6 +53,15 @@ const routes: FastifyPluginCallback = (fastify: FastifyInstance, options, next) handler: usersController.setPreferences.bind(usersController), }); + fastify.route({ + method: "POST", + url: `${usersUrl}/me/reportLog`, + preHandler: accessControl, + preValidation: [fastify.authenticate], + schema: sendUserClientReportSchema, + handler: usersController.reportClientLog.bind(usersController), + }); + fastify.route({ method: "POST", url: `${usersUrl}/me`, diff --git a/tdrive/backend/node/src/services/user/web/schemas.ts b/tdrive/backend/node/src/services/user/web/schemas.ts index 94b9d3f9..4f195317 100644 --- a/tdrive/backend/node/src/services/user/web/schemas.ts +++ b/tdrive/backend/node/src/services/user/web/schemas.ts @@ -191,6 +191,19 @@ export const setUserPreferencesSchema = { }, }; +export const sendUserClientReportSchema = { + request: { + type: "object", + properties: { + message: { type: "string" }, + }, + required: ["message"], + }, + response: { + "2xx": {}, + }, +}; + export const getUsersSchema = { type: "object", properties: { diff --git a/tdrive/frontend/src/app/features/users/api/user-api-client.ts b/tdrive/frontend/src/app/features/users/api/user-api-client.ts index 12a51bfd..335851c6 100644 --- a/tdrive/frontend/src/app/features/users/api/user-api-client.ts +++ b/tdrive/frontend/src/app/features/users/api/user-api-client.ts @@ -8,6 +8,7 @@ import CurrentUser from '../../../deprecated/user/CurrentUser'; import { setUserList } from '../hooks/use-user-list'; import Logger from 'features/global/framework/logger-service'; import { UserQuota } from "features/users/types/user-quota"; +import version from '../../../environment/version'; export type SearchContextType = { scope: 'company' | 'workspace' | 'all'; @@ -142,6 +143,30 @@ class UserAPIClientService { }); } + /** Send log entry to the server's logs. Try to include a `message` property please */ + async reportLog(body: any | { message: string, error?: Error, err?: Error, e?: Error, }): Promise { + const actualBody = { + version: version.version_detail || version.version, + ...body, + } as any; + for (const [key, val] of Object.entries(actualBody)) { + const error = val as Error; + if ((error as Error).stack) { + actualBody[key] = { + message: error.message, + stack: error.stack, + error, + }; + if (!actualBody.message) actualBody.message = "Error: " + error.message; + } + } + if (!actualBody.message) actualBody.message = "(missing message property in UserAPIClient.reportLog)"; + return Api.post( + '/internal/services/users/v1/users/me/reportLog', + actualBody, + ).then(result => undefined); + } + async getQuota(companyId: string, userId: string): Promise { return Api.get( `/internal/services/users/v1/users/${userId}/quota?companyId=${companyId}`, @@ -255,4 +280,10 @@ class UserAPIClientService { } } const UserAPIClient = new UserAPIClientService(); + +window.onerror = (message, url, line, col, error) => { + UserAPIClient.reportLog({ message, url, line, col, error }); + return false; // don't suppress normal alert +} + export default UserAPIClient;