diff --git a/.changeset/loud-lions-stick.md b/.changeset/loud-lions-stick.md new file mode 100644 index 00000000..fd440910 --- /dev/null +++ b/.changeset/loud-lions-stick.md @@ -0,0 +1,7 @@ +--- +"draupnir": patch +--- + +Fixed an issue where sometimes Draupnir would log cryptic errors such as +`undefined: undefined`. +https://github.com/the-draupnir-project/Draupnir/issues/759. diff --git a/.changeset/witty-dancers-join.md b/.changeset/witty-dancers-join.md new file mode 100644 index 00000000..320c97c2 --- /dev/null +++ b/.changeset/witty-dancers-join.md @@ -0,0 +1,8 @@ +--- +"@the-draupnir-project/matrix-protection-suite": minor +"draupnir": patch +--- + +Tighten ActionException to only accept `Error` instead of `unknown`, leading to +less mistakes. We now also offer and use a `ensureThrowableIsError` to use when +checking if third-party apis are throwing junk, such as the matrix-bot-sdk. diff --git a/apps/draupnir/src/appservice/AppServiceDraupnirManager.ts b/apps/draupnir/src/appservice/AppServiceDraupnirManager.ts index de700d63..a6bd7dae 100644 --- a/apps/draupnir/src/appservice/AppServiceDraupnirManager.ts +++ b/apps/draupnir/src/appservice/AppServiceDraupnirManager.ts @@ -29,6 +29,7 @@ import { RoomCreator, RoomVersionMirror, Task, + assertThrowableIsError, isError, } from "matrix-protection-suite"; import { Draupnir } from "../Draupnir"; @@ -388,7 +389,7 @@ export class AppServiceDraupnirManager { return ActionException.Result( `Could not start draupnir ${clientUserID} for owner ${mjolnirRecord.owner}`, { - exception: e, + exception: assertThrowableIsError(e), exceptionKind: ActionExceptionKind.Unknown, } ); diff --git a/apps/draupnir/src/backingstore/better-sqlite3/SqliteRoomStateBackingStore.ts b/apps/draupnir/src/backingstore/better-sqlite3/SqliteRoomStateBackingStore.ts index e688183a..20871f20 100644 --- a/apps/draupnir/src/backingstore/better-sqlite3/SqliteRoomStateBackingStore.ts +++ b/apps/draupnir/src/backingstore/better-sqlite3/SqliteRoomStateBackingStore.ts @@ -13,6 +13,7 @@ import { RoomStateRevision, StateChange, StateEvent, + assertThrowableIsError, isError, } from "matrix-protection-suite"; import { @@ -232,7 +233,7 @@ export class SqliteRoomStateBackingStore } catch (e) { return Promise.resolve( ActionException.Result(`Unable to forget the room ${roomID}`, { - exception: e, + exception: assertThrowableIsError(e), exceptionKind: ActionExceptionKind.Unknown, }) ); diff --git a/apps/draupnir/src/commands/Rooms.tsx b/apps/draupnir/src/commands/Rooms.tsx index 64672e77..931014f5 100644 --- a/apps/draupnir/src/commands/Rooms.tsx +++ b/apps/draupnir/src/commands/Rooms.tsx @@ -15,6 +15,7 @@ import { Ok, Revision, WatchedPolicyRoom, + assertThrowableIsError, isError, } from "matrix-protection-suite"; import { @@ -244,7 +245,10 @@ export const DraupnirRoomsRemoveCommand = describeCommand({ } catch (exception) { return ActionException.Result( `Failed to leave ${roomRef.toPermalink()} - the room is no longer being protected, but the bot could not leave.`, - { exceptionKind: ActionExceptionKind.Unknown, exception } + { + exceptionKind: ActionExceptionKind.Unknown, + exception: assertThrowableIsError(exception), + } ); } return Ok(undefined); diff --git a/apps/draupnir/src/protections/DraupnirNews/DraupnirNews.tsx b/apps/draupnir/src/protections/DraupnirNews/DraupnirNews.tsx index c5fb0ed0..bd74f9ad 100644 --- a/apps/draupnir/src/protections/DraupnirNews/DraupnirNews.tsx +++ b/apps/draupnir/src/protections/DraupnirNews/DraupnirNews.tsx @@ -9,6 +9,7 @@ import { ActionException, ActionExceptionKind, allocateProtection, + assertThrowableIsError, ConstantPeriodBatch, describeProtection, EDStatic, @@ -136,7 +137,7 @@ async function fetchNews(newsURL: string): Promise> { (json) => Value.Decode(DraupnirNewsBlob, json), (error: unknown) => ActionException.Result("unable to fetch news", { - exception: error, + exception: assertThrowableIsError(error), exceptionKind: ActionExceptionKind.Unknown, }) ); diff --git a/apps/draupnir/src/queues/EventRedactionQueue.ts b/apps/draupnir/src/queues/EventRedactionQueue.ts index 53f28a85..9914c9f6 100644 --- a/apps/draupnir/src/queues/EventRedactionQueue.ts +++ b/apps/draupnir/src/queues/EventRedactionQueue.ts @@ -14,6 +14,7 @@ import ManagementRoomOutput from "../managementroom/ManagementRoomOutput"; import { MatrixSendClient } from "matrix-protection-suite-for-matrix-bot-sdk"; import { ActionExceptionKind, + assertThrowableIsError, RoomUpdateError, RoomUpdateException, } from "matrix-protection-suite"; @@ -157,7 +158,7 @@ export class EventRedactionQueue { const error = new RoomUpdateException( MatrixRoomReference.fromRoomID(redaction.roomID), ActionExceptionKind.Unknown, - e, + assertThrowableIsError(e), message ); errors.push(error); diff --git a/apps/draupnir/src/report/ReportPoller.ts b/apps/draupnir/src/report/ReportPoller.ts index 55fec39e..3a3619a5 100644 --- a/apps/draupnir/src/report/ReportPoller.ts +++ b/apps/draupnir/src/report/ReportPoller.ts @@ -23,6 +23,7 @@ import { Ok, RoomEvent, Task, + assertThrowableIsError, isError, } from "matrix-protection-suite"; @@ -120,7 +121,10 @@ export class ReportPoller { (exception: unknown) => ActionException.Result( `Failed to retrieve the context for an event ${report.event_id}`, - { exception, exceptionKind: ActionExceptionKind.Unknown } + { + exception: assertThrowableIsError(exception), + exceptionKind: ActionExceptionKind.Unknown, + } ) ); if (isError(eventContext)) { @@ -159,7 +163,7 @@ export class ReportPoller { } catch (e) { const error = new ActionException( ActionExceptionKind.Unknown, - e, + assertThrowableIsError(e), "Unable to set account data for report poller token" ); await this.draupnir.managementRoomOutput.logMessage( @@ -179,7 +183,7 @@ export class ReportPoller { } catch (e) { const error = new ActionException( ActionExceptionKind.Unknown, - e, + assertThrowableIsError(e), "failed to get abuse reports" ); await this.draupnir.managementRoomOutput.logMessage( diff --git a/apps/draupnir/test/integration/reportPollingTest.ts b/apps/draupnir/test/integration/reportPollingTest.ts index 2549055b..c9769357 100644 --- a/apps/draupnir/test/integration/reportPollingTest.ts +++ b/apps/draupnir/test/integration/reportPollingTest.ts @@ -82,7 +82,7 @@ describe("Test: Report polling", function (this: Mocha.Suite) { await reportEvent(); } // wait for them to come down the poll. - await new Promise((resolve) => setTimeout(resolve, 3000)); + await new Promise((resolve) => setTimeout(resolve, 5000)); expect(reportsFound.size).toBe(20); expect(duplicateReports.size).toBe(0); } as unknown as Mocha.AsyncFunc); diff --git a/packages/matrix-protection-suite-for-matrix-bot-sdk/src/Client/BotSDKBaseClient.ts b/packages/matrix-protection-suite-for-matrix-bot-sdk/src/Client/BotSDKBaseClient.ts index 74719a37..834c4661 100644 --- a/packages/matrix-protection-suite-for-matrix-bot-sdk/src/Client/BotSDKBaseClient.ts +++ b/packages/matrix-protection-suite-for-matrix-bot-sdk/src/Client/BotSDKBaseClient.ts @@ -1,8 +1,7 @@ -// SPDX-FileCopyrightText: 2024 Gnuxie +// SPDX-FileCopyrightText: 2024 - 2026 Gnuxie // // SPDX-License-Identifier: AFL-3.0 -import { StaticDecode, Type } from "@sinclair/typebox"; import { MatrixError } from "@vector-im/matrix-bot-sdk"; import { ActionException, @@ -54,17 +53,12 @@ import { } from "@the-draupnir-project/matrix-basic-types"; import { resolveRoomReferenceSafe } from "../SafeMatrixClient"; import { ResultError } from "@gnuxie/typescript-result"; -import util from "util"; import { RoomReactionSender } from "matrix-protection-suite/dist/Client/RoomReactionSender"; import { RoomEventGetter } from "matrix-protection-suite/dist/Client/RoomEventGetter"; +import { BotSDKJunkError, toMatrixJunkError } from "./BotSDKJunkErrors"; const log = new Logger("BotSDKBaseClient"); -const WeakError = Type.Object({ - message: Type.String(), - name: Type.String(), -}); - function toRoomID(room: MatrixRoomID | StringRoomID): StringRoomID { return typeof room === "string" ? room : room.toRoomIDOrAlias(); } @@ -80,39 +74,17 @@ function matrixExceptionFromMatrixError( }); } -function actionExceptionFromWeakError( - error: StaticDecode -): ActionResult { - return ActionException.Result(error.message, { +function matrixExceptionFromMatrixJunkError( + error: BotSDKJunkError +): ActionResult { + return MatrixException.R({ exception: error, - exceptionKind: ActionExceptionKind.Unknown, + matrixErrorCode: error.matrixErrorCode, + matrixErrorMessage: error.matrixErrorMessage, + message: error.message, }); } -function unknownError(error: unknown): never { - const printedError = (() => { - if (typeof error === "object" && error !== null) { - // eslint-disable-next-line @typescript-eslint/no-base-to-string - const toString = error.toString(); - if (toString !== "[object Object]") { - return toString; - } - } - try { - return JSON.stringify(error); - } catch { - return util.inspect(error, { - depth: 2, - maxArrayLength: 10, - breakLength: 80, - }); - } - })(); - throw new TypeError( - `What on earth are you throwing exactly? because it isn't an error: ${printedError}` - ); -} - /** * This is a utility to extract the raw matrix event from a wrapper type * that vector bot-sdk decided to use inappropriately. @@ -154,25 +126,31 @@ export function resultifyBotSDKRequestErrorWith404AsUndefined( if (is404(error)) { return Ok(undefined); } - if (error instanceof MatrixError) { - return matrixExceptionFromMatrixError(error); - } else if (Value.Check(WeakError, error)) { - return actionExceptionFromWeakError(error); - } else { - unknownError(error); + const coercedError = toMatrixJunkError(error); + if (coercedError instanceof MatrixError) { + return matrixExceptionFromMatrixError(coercedError); + } else if (coercedError instanceof BotSDKJunkError) { + return matrixExceptionFromMatrixJunkError(coercedError); } + return ActionException.Result(coercedError.message, { + exception: coercedError, + exceptionKind: ActionExceptionKind.Unknown, + }); } export function resultifyBotSDKRequestError( error: unknown ): ActionResult { - if (error instanceof MatrixError) { - return matrixExceptionFromMatrixError(error); - } else if (Value.Check(WeakError, error)) { - return actionExceptionFromWeakError(error); - } else { - unknownError(error); + const coercedError = toMatrixJunkError(error); + if (coercedError instanceof MatrixError) { + return matrixExceptionFromMatrixError(coercedError); + } else if (coercedError instanceof BotSDKJunkError) { + return matrixExceptionFromMatrixJunkError(coercedError); } + return ActionException.Result(coercedError.message, { + exception: coercedError, + exceptionKind: ActionExceptionKind.Unknown, + }); } export class BotSDKBaseClient diff --git a/packages/matrix-protection-suite-for-matrix-bot-sdk/src/Client/BotSDKJunkErrors.ts b/packages/matrix-protection-suite-for-matrix-bot-sdk/src/Client/BotSDKJunkErrors.ts new file mode 100644 index 00000000..1f9448d0 --- /dev/null +++ b/packages/matrix-protection-suite-for-matrix-bot-sdk/src/Client/BotSDKJunkErrors.ts @@ -0,0 +1,54 @@ +// SPDX-FileCopyrightText: 2026 Gnuxie +// +// SPDX-License-Identifier: Apache-2.0 + +import { Type } from "@sinclair/typebox"; +import { MatrixError } from "@vector-im/matrix-bot-sdk"; +import { Value, assertThrowableIsError } from "matrix-protection-suite"; + +const MatrixErrorBody = Type.Object({ + errcode: Type.String(), + error: Type.String(), + retry_after_ms: Type.Optional(Type.Number()), +}); + +// The matrix-bot-sdk appservice code sometimes throws junk like `{ body: result }` +// for example in`Intent.ensureRegistered()`. +const MatrixErrorContainer = Type.Object({ + body: MatrixErrorBody, + statusCode: Type.Optional(Type.Number()), + headers: Type.Optional(Type.Record(Type.String(), Type.String())), +}); + +/** + * This exists because the matrix-bot-sdk and matrix-appservice-bridge have + * various inconsistent ways of throwing errors. And Draupnir's own code + * probably makes matters worse in utils.ts. So this is a wrapper for all of + * the undesirable junk that gets thrown. Eventually we want to replace as + * many bot-sdk apis as possible with the matrix-protection-suite ClientPlatform + * an also then implement those apis using fetch. + */ +export class BotSDKJunkError extends Error { + public constructor( + public readonly raw: unknown, + public readonly matrixErrorCode: string, + public readonly matrixErrorMessage: string + ) { + super( + `matrix-bot-sdk threw a non-Error matrix payload: ${matrixErrorCode}: ${matrixErrorMessage}` + ); + } +} + +export function toMatrixJunkError(error: unknown): BotSDKJunkError | Error { + if (error instanceof MatrixError) { + return error; + } + if (Value.Check(MatrixErrorContainer, error)) { + return new BotSDKJunkError(error, error.body.errcode, error.body.error); + } + if (Value.Check(MatrixErrorBody, error)) { + return new BotSDKJunkError(error, error.errcode, error.error); + } + return assertThrowableIsError(error); +} diff --git a/packages/matrix-protection-suite-for-matrix-bot-sdk/src/ClientManagement/ClientManagement.ts b/packages/matrix-protection-suite-for-matrix-bot-sdk/src/ClientManagement/ClientManagement.ts index f59f881c..ce8179f3 100644 --- a/packages/matrix-protection-suite-for-matrix-bot-sdk/src/ClientManagement/ClientManagement.ts +++ b/packages/matrix-protection-suite-for-matrix-bot-sdk/src/ClientManagement/ClientManagement.ts @@ -9,6 +9,7 @@ import { ClientRooms, ClientsInRoomMap, Ok, + assertThrowableIsError, } from "matrix-protection-suite"; import { MatrixSendClient } from "../MatrixEmitter"; import { @@ -34,7 +35,7 @@ export async function joinedRoomsSafe( (rooms) => Ok(rooms as StringRoomID[]), (exception: unknown) => ActionException.Result(`Unable to get joined rooms`, { - exception, + exception: assertThrowableIsError(exception), exceptionKind: ActionExceptionKind.Unknown, }) ); diff --git a/packages/matrix-protection-suite-for-matrix-bot-sdk/src/ClientManagement/JoinedRoomsSafe.ts b/packages/matrix-protection-suite-for-matrix-bot-sdk/src/ClientManagement/JoinedRoomsSafe.ts index 82d9adec..2d62e154 100644 --- a/packages/matrix-protection-suite-for-matrix-bot-sdk/src/ClientManagement/JoinedRoomsSafe.ts +++ b/packages/matrix-protection-suite-for-matrix-bot-sdk/src/ClientManagement/JoinedRoomsSafe.ts @@ -7,6 +7,7 @@ import { ActionExceptionKind, JoinedRoomsSafe, Ok, + assertThrowableIsError, } from "matrix-protection-suite"; import { MatrixSendClient } from "../MatrixEmitter"; import { StringRoomID } from "@the-draupnir-project/matrix-basic-types"; @@ -22,7 +23,7 @@ export function makeJoinedRoomsSafe( ActionException.Result( `Unable to fetch the joined rooms for ${clientUserID}`, { - exception, + exception: assertThrowableIsError(exception), exceptionKind: ActionExceptionKind.Unknown, } ) diff --git a/packages/matrix-protection-suite-for-matrix-bot-sdk/src/Interface/MatrixData.ts b/packages/matrix-protection-suite-for-matrix-bot-sdk/src/Interface/MatrixData.ts index 12830aae..7d48a750 100644 --- a/packages/matrix-protection-suite-for-matrix-bot-sdk/src/Interface/MatrixData.ts +++ b/packages/matrix-protection-suite-for-matrix-bot-sdk/src/Interface/MatrixData.ts @@ -12,6 +12,7 @@ import { PersistentConfigBackend, RoomStateRevisionIssuer, Value, + assertThrowableIsError, isError, } from "matrix-protection-suite"; import { MatrixSendClient } from "../MatrixEmitter"; @@ -74,7 +75,10 @@ export class BotSDKMatrixAccountData implements MatrixAccountData { ? Ok(undefined) : ActionException.Result( `Encountered an error when requesting matrix account data ${this.eventType}`, - { exception: error, exceptionKind: ActionExceptionKind.Unknown } + { + exception: assertThrowableIsError(error), + exceptionKind: ActionExceptionKind.Unknown, + } ) ); } @@ -91,7 +95,7 @@ export class BotSDKMatrixAccountData implements MatrixAccountData { ActionException.Result( `Unable to store matrix account data ${this.eventType}`, { - exception, + exception: assertThrowableIsError(exception), exceptionKind: ActionExceptionKind.Unknown, } ) @@ -162,7 +166,7 @@ export class BotSDKMatrixStateData implements MatrixStateData { ActionException.Result( `Unable to store the matrix state data ${this.eventType}`, { - exception, + exception: assertThrowableIsError(exception), exceptionKind: ActionExceptionKind.Known, } ) diff --git a/packages/matrix-protection-suite-for-matrix-bot-sdk/src/PolicyList/PolicyListManager.ts b/packages/matrix-protection-suite-for-matrix-bot-sdk/src/PolicyList/PolicyListManager.ts index 1c418284..61f12320 100644 --- a/packages/matrix-protection-suite-for-matrix-bot-sdk/src/PolicyList/PolicyListManager.ts +++ b/packages/matrix-protection-suite-for-matrix-bot-sdk/src/PolicyList/PolicyListManager.ts @@ -19,6 +19,7 @@ import { ActionException, ActionExceptionKind, Ok, + assertThrowableIsError, isError, PolicyRuleEvent, isPolicyRuleEvent, @@ -96,7 +97,10 @@ export class BotSDKPolicyRoomManager implements PolicyRoomManager { (exception: unknown) => ActionException.Result( "Could not create a list because we could not find the mxid of the list creator.", - { exception, exceptionKind: ActionExceptionKind.Unknown } + { + exception: assertThrowableIsError(exception), + exceptionKind: ActionExceptionKind.Unknown, + } ) ); if (isError(creator)) { @@ -173,7 +177,10 @@ export class BotSDKPolicyRoomManager implements PolicyRoomManager { (exception: unknown) => ActionException.Result( "Could not create a matrix room to serve as the new policy list.", - { exception, exceptionKind: ActionExceptionKind.Unknown } + { + exception: assertThrowableIsError(exception), + exceptionKind: ActionExceptionKind.Unknown, + } ) ); } diff --git a/packages/matrix-protection-suite-for-matrix-bot-sdk/src/Protection/MjolnirProtectedRoomsStore.ts b/packages/matrix-protection-suite-for-matrix-bot-sdk/src/Protection/MjolnirProtectedRoomsStore.ts index c7d2c20e..fede0c71 100644 --- a/packages/matrix-protection-suite-for-matrix-bot-sdk/src/Protection/MjolnirProtectedRoomsStore.ts +++ b/packages/matrix-protection-suite-for-matrix-bot-sdk/src/Protection/MjolnirProtectedRoomsStore.ts @@ -12,6 +12,7 @@ import { Value, isError, Ok, + assertThrowableIsError, } from "matrix-protection-suite"; import { MatrixSendClient } from "../MatrixEmitter"; @@ -29,7 +30,10 @@ export class BotSDKMjolnirProtectedRoomsStore implements PersistentMatrixData< (exception: unknown) => ActionException.Result( `Unable to load the account data for mjolnir protected_rooms`, - { exception, exceptionKind: ActionExceptionKind.Unknown } + { + exception: assertThrowableIsError(exception), + exceptionKind: ActionExceptionKind.Unknown, + } ) ); } @@ -47,7 +51,10 @@ export class BotSDKMjolnirProtectedRoomsStore implements PersistentMatrixData< (exception: unknown) => ActionException.Result( `Unable to set account data for mjolnir protected_rooms event`, - { exception, exceptionKind: ActionExceptionKind.Unknown } + { + exception: assertThrowableIsError(exception), + exceptionKind: ActionExceptionKind.Unknown, + } ) ); } diff --git a/packages/matrix-protection-suite-for-matrix-bot-sdk/src/Protection/MjolnirWatchedPolicyRoomsStore.ts b/packages/matrix-protection-suite-for-matrix-bot-sdk/src/Protection/MjolnirWatchedPolicyRoomsStore.ts index d051cdec..0d6f3639 100644 --- a/packages/matrix-protection-suite-for-matrix-bot-sdk/src/Protection/MjolnirWatchedPolicyRoomsStore.ts +++ b/packages/matrix-protection-suite-for-matrix-bot-sdk/src/Protection/MjolnirWatchedPolicyRoomsStore.ts @@ -12,6 +12,7 @@ import { Ok, MjolnirWatchedPolicyRoomsEvent, MJOLNIR_WATCHED_POLICY_ROOMS_EVENT_TYPE, + assertThrowableIsError, } from "matrix-protection-suite"; import { MatrixSendClient } from "../MatrixEmitter"; @@ -31,7 +32,10 @@ export class BotSDKMjolnirWatchedPolicyRoomsStore implements PersistentMatrixDat (exception: unknown) => ActionException.Result( `Unable to load the account data for mjolnir watched_lists`, - { exception, exceptionKind: ActionExceptionKind.Unknown } + { + exception: assertThrowableIsError(exception), + exceptionKind: ActionExceptionKind.Unknown, + } ) ); } @@ -49,7 +53,10 @@ export class BotSDKMjolnirWatchedPolicyRoomsStore implements PersistentMatrixDat (exception: unknown) => ActionException.Result( `Unable to set account data for mjolnir watched_lists`, - { exception, exceptionKind: ActionExceptionKind.Unknown } + { + exception: assertThrowableIsError(exception), + exceptionKind: ActionExceptionKind.Unknown, + } ) ); } diff --git a/packages/matrix-protection-suite-for-matrix-bot-sdk/src/StateTracking/RoomMembershipManager.ts b/packages/matrix-protection-suite-for-matrix-bot-sdk/src/StateTracking/RoomMembershipManager.ts index d2bf4227..7bc7c0f7 100644 --- a/packages/matrix-protection-suite-for-matrix-bot-sdk/src/StateTracking/RoomMembershipManager.ts +++ b/packages/matrix-protection-suite-for-matrix-bot-sdk/src/StateTracking/RoomMembershipManager.ts @@ -12,6 +12,7 @@ import { RoomMembershipManager, RoomMembershipRevisionIssuer, Value, + assertThrowableIsError, isError, } from "matrix-protection-suite"; import { MembershipEvent } from "matrix-protection-suite"; @@ -40,7 +41,10 @@ async function getRoomMembershipEvents( (exception: unknown) => ActionException.Result( `Unable to query room members from ${room.toPermalink()}`, - { exception, exceptionKind: ActionExceptionKind.Unknown } + { + exception: assertThrowableIsError(exception), + exceptionKind: ActionExceptionKind.Unknown, + } ) ); if (isError(rawMembersResult)) { diff --git a/packages/matrix-protection-suite-for-matrix-bot-sdk/src/SynapseAdmin/SynapseAdminClient.ts b/packages/matrix-protection-suite-for-matrix-bot-sdk/src/SynapseAdmin/SynapseAdminClient.ts index 423edfa8..7babb782 100644 --- a/packages/matrix-protection-suite-for-matrix-bot-sdk/src/SynapseAdmin/SynapseAdminClient.ts +++ b/packages/matrix-protection-suite-for-matrix-bot-sdk/src/SynapseAdmin/SynapseAdminClient.ts @@ -19,6 +19,7 @@ import { SynapseAdminPostUserDeactivateRequest, SynapseReport, Value, + assertThrowableIsError, isError, } from "matrix-protection-suite"; import { MatrixSendClient } from "../MatrixEmitter"; @@ -76,7 +77,10 @@ export class SynapseAdminClient { (exception: unknown) => ActionException.Result( `Unable to query whether the user ${this.clientUserID} is a Synapse Admin`, - { exception, exceptionKind: ActionExceptionKind.Unknown } + { + exception: assertThrowableIsError(exception), + exceptionKind: ActionExceptionKind.Unknown, + } ) ); if (isError(response)) { @@ -107,7 +111,10 @@ export class SynapseAdminClient { (exception: unknown) => ActionException.Result( `Unable to deactivate the user ${targetUserID}`, - { exception, exceptionKind: ActionExceptionKind.Unknown } + { + exception: assertThrowableIsError(exception), + exceptionKind: ActionExceptionKind.Unknown, + } ) ); } @@ -127,7 +134,7 @@ export class SynapseAdminClient { (_) => Ok(undefined), (exception: unknown) => ActionException.Result(`Unable to delete the room ${roomID}`, { - exception, + exception: assertThrowableIsError(exception), exceptionKind: ActionExceptionKind.Unknown, }) ); @@ -155,7 +162,7 @@ export class SynapseAdminClient { ActionException.Result( `Unable to make the user ${userID} admin in room ${roomID}`, { - exception, + exception: assertThrowableIsError(exception), exceptionKind: ActionExceptionKind.Unknown, } ) diff --git a/packages/matrix-protection-suite-for-matrix-bot-sdk/src/index.ts b/packages/matrix-protection-suite-for-matrix-bot-sdk/src/index.ts index 44635f07..1458585a 100644 --- a/packages/matrix-protection-suite-for-matrix-bot-sdk/src/index.ts +++ b/packages/matrix-protection-suite-for-matrix-bot-sdk/src/index.ts @@ -5,6 +5,7 @@ export * from "./Client/BotSDKAllClient"; export * from "./Client/BotSDKBaseClient"; export * from "./Client/BotSDKClientPlatform"; +export * from "./Client/BotSDKJunkErrors"; export * from "./ClientManagement/ClientCapabilityFactory"; export * from "./ClientManagement/ClientManagement"; diff --git a/packages/matrix-protection-suite/src/Interface/ActionException.ts b/packages/matrix-protection-suite/src/Interface/ActionException.ts index 6c7cac16..c6e4d6ec 100644 --- a/packages/matrix-protection-suite/src/Interface/ActionException.ts +++ b/packages/matrix-protection-suite/src/Interface/ActionException.ts @@ -26,6 +26,41 @@ export enum ActionExceptionKind { Unknown = "Unknown", } +function describeThrowable(throwable: unknown): string { + if (typeof throwable === "string") { + return throwable; + } + if ( + typeof throwable === "number" || + typeof throwable === "boolean" || + typeof throwable === "bigint" || + typeof throwable === "symbol" || + throwable === null || + throwable === undefined + ) { + return String(throwable); + } + if (Array.isArray(throwable)) { + return `array(length=${throwable.length})`; + } + if (typeof throwable === "object") { + const keys = Object.keys(throwable); + return keys.length === 0 + ? "object with no enumerable keys" + : `object with keys: ${keys.join(",")}`; + } + return typeof throwable; +} + +export function assertThrowableIsError(throwable: unknown): Error { + if (throwable instanceof Error) { + return throwable; + } + throw new TypeError( + `Expected a thrown Error instance but received ${describeThrowable(throwable)}` + ); +} + // TODO: I wonder if we could allow message to be JSX? /** * `ActionExceptions` are used to convert throwables into `ActionError`s. @@ -39,8 +74,7 @@ export class ActionException extends ActionError { constructor( public readonly exceptionKind: ActionExceptionKind, - // make a call to only allow Error in a moment. - public readonly exception: unknown, + public readonly exception: Error, message: string, { uuid = randomUUID(), @@ -69,7 +103,7 @@ export class ActionException extends ActionError { */ public static Result( message: string, - options: { exception: unknown; exceptionKind: ActionExceptionKind } + options: { exception: Error; exceptionKind: ActionExceptionKind } ): ActionResult { return ResultError( new ActionException(options.exceptionKind, options.exception, message) @@ -93,11 +127,6 @@ export class ActionException extends ActionError { public toReadableString(): string { const mainDetail = `ActionException: ${this.uuid}\n${super.toReadableString()}`; - if (this.exception instanceof Error) { - return `${mainDetail}\nfrom error: ${this.exception.name}: ${this.exception.message}\n${this.exception.stack}`; - } - // @typescript-eslint/restrict-template-expressions - // eslint-disable-next-line @typescript-eslint/restrict-template-expressions - return `${mainDetail}\nfrom unknown: ${this.exception}`; + return `${mainDetail}\nfrom error: ${this.exception.name}: ${this.exception.message}\n${this.exception.stack}`; } } diff --git a/packages/matrix-protection-suite/src/Interface/MatrixException.ts b/packages/matrix-protection-suite/src/Interface/MatrixException.ts index 6853bf21..8742654d 100644 --- a/packages/matrix-protection-suite/src/Interface/MatrixException.ts +++ b/packages/matrix-protection-suite/src/Interface/MatrixException.ts @@ -7,7 +7,7 @@ import { ActionException, ActionExceptionKind } from "./ActionException"; export class MatrixException extends ActionException implements ActionError { public constructor( - exception: unknown, + exception: Error, public readonly matrixErrorCode: string, public readonly matrixErrorMessage: string, message: string = matrixErrorMessage, @@ -30,7 +30,7 @@ export class MatrixException extends ActionException implements ActionError { }): ActionResult { return ResultError( new MatrixException( - options.exceptionKind, + options.exception, options.matrixErrorCode, options.matrixErrorMessage, options.message, diff --git a/packages/matrix-protection-suite/src/Interface/Task.ts b/packages/matrix-protection-suite/src/Interface/Task.ts index 08908bd0..67f2ff7e 100644 --- a/packages/matrix-protection-suite/src/Interface/Task.ts +++ b/packages/matrix-protection-suite/src/Interface/Task.ts @@ -5,7 +5,11 @@ import { ResultError } from "@gnuxie/typescript-result"; import { Logger } from "../Logging/Logger"; import { ActionError } from "./Action"; -import { ActionException, ActionExceptionKind } from "./ActionException"; +import { + ActionException, + ActionExceptionKind, + assertThrowableIsError, +} from "./ActionException"; const log = new Logger("Task"); @@ -67,7 +71,7 @@ export async function Task( } catch (exception) { const actionException = new ActionException( ActionExceptionKind.Unknown, - exception, + assertThrowableIsError(exception), "A Task failed with an unknown exception" ); globalTaskReporter(actionException, options); diff --git a/packages/matrix-protection-suite/src/Interface/Value.ts b/packages/matrix-protection-suite/src/Interface/Value.ts index f56ad385..2c323025 100644 --- a/packages/matrix-protection-suite/src/Interface/Value.ts +++ b/packages/matrix-protection-suite/src/Interface/Value.ts @@ -16,14 +16,18 @@ import { ValueErrorIterator, } from "@sinclair/typebox/compiler"; import { ActionResult, Ok, ResultError } from "./Action"; -import { ActionException, ActionExceptionKind } from "./ActionException"; +import { + ActionException, + ActionExceptionKind, + assertThrowableIsError, +} from "./ActionException"; import { Logger } from "../Logging/Logger"; export class DecodeException extends ActionException { private static log = new Logger("DecodeException"); constructor( message: string, - exception: unknown, + exception: Error, public readonly errors: ValueError[], suppressLog?: boolean ) { @@ -108,7 +112,7 @@ export class Value { throw e; } else { return ActionException.Result(`Unable to decode schema from value`, { - exception: e, + exception: assertThrowableIsError(e), exceptionKind: ActionExceptionKind.Unknown, }); }