Make src/index.ts use the BotModeToggle to manage the bot.

Now we need to change the integration test's makeMjolnir do the same.
This commit is contained in:
gnuxie
2024-09-18 18:08:36 +01:00
parent d82a0b0731
commit a92c95a0dd
3 changed files with 107 additions and 24 deletions
+97 -7
View File
@@ -15,6 +15,10 @@ import {
RoomStateBackingStore,
ClientsInRoomMap,
Task,
Logger,
Ok,
ActionException,
ActionExceptionKind,
} from "matrix-protection-suite";
import {
BotSDKLogServiceLogger,
@@ -43,17 +47,34 @@ import {
} from "./safemode/SafeModeToggle";
import { SafeModeDraupnir } from "./safemode/DraupnirSafeMode";
import { ResultError } from "@gnuxie/typescript-result";
import { SafeModeCause } from "./safemode/SafeModeCause";
import { SafeModeCause, SafeModeReason } from "./safemode/SafeModeCause";
setGlobalLoggerProvider(new BotSDKLogServiceLogger());
const log = new Logger("DraupnirBotMode");
export function constructWebAPIs(draupnir: Draupnir): WebAPIs {
return new WebAPIs(draupnir.reportManager, draupnir.config);
}
export class DraupnirBotModeToggle implements SafeModeToggle {
/**
* The bot mode toggle allows the entrypoint, either src/index.ts or
* manual test scripts, to setup and control Draupnir.
* This includes the webAPIS that accompany Draupnir in bot mode.
*
* The appservice also implements `SafeModeToggle` but has different requirements.
* This interface is exlusively used by the entrypoints to draupnir's bot mode.
*/
interface BotModeTogle extends SafeModeToggle {
encryptionInitialized(): Promise<void>;
stopEverything(): void;
startFromScratch(options?: SafeModeToggleOptions): Promise<Result<void>>;
}
export class DraupnirBotModeToggle implements BotModeTogle {
private draupnir: Draupnir | null = null;
private safeModeDraupnir: SafeModeDraupnir | null = null;
private webAPIs: WebAPIs | null = null;
private constructor(
private readonly clientUserID: StringUserID,
@@ -161,13 +182,27 @@ export class DraupnirBotModeToggle implements SafeModeToggle {
if (isError(draupnirResult)) {
return draupnirResult;
}
this.safeModeDraupnir?.stop();
this.safeModeDraupnir = null;
this.draupnir = draupnirResult.ok;
void Task(this.draupnir.start());
if (options?.sendStatusOnStart) {
void Task(this.draupnir.startupComplete());
try {
this.webAPIs = constructWebAPIs(this.draupnir);
await this.webAPIs.start();
} catch (e) {
if (e instanceof Error) {
this.stopDraupnir();
log.error("Failed to start webAPIs", e);
return ActionException.Result("Failed to start webAPIs", {
exceptionKind: ActionExceptionKind.Unknown,
exception: e,
});
} else {
throw new TypeError("Someone is throwing garbage.");
}
}
}
this.stopSafeModeDraupnir();
return draupnirResult;
}
public async switchToSafeMode(
@@ -189,13 +224,68 @@ export class DraupnirBotModeToggle implements SafeModeToggle {
if (isError(safeModeResult)) {
return safeModeResult;
}
this.draupnir?.stop();
this.draupnir = null;
this.stopDraupnir();
this.safeModeDraupnir = safeModeResult.ok;
this.safeModeDraupnir.start();
if (options?.sendStatusOnStart) {
this.safeModeDraupnir.sendStartupComplete();
this.safeModeDraupnir.startupComplete();
}
return safeModeResult;
}
public async startFromScratch(
options?: SafeModeToggleOptions
): Promise<Result<void>> {
const draupnirResult = await this.switchToDraupnir(options ?? {});
if (isError(draupnirResult)) {
if (this.config.safeMode?.bootOnStartupFailure) {
log.error(
"Failed to start draupnir, switching to safe mode as configured",
draupnirResult.error
);
return (await this.switchToSafeMode(
{
reason: SafeModeReason.InitializationError,
error: draupnirResult.error,
},
options ?? {}
)) as Result<void>;
} else {
return draupnirResult;
}
}
return Ok(undefined);
}
public async encryptionInitialized(): Promise<void> {
if (this.draupnir !== null) {
try {
this.webAPIs = constructWebAPIs(this.draupnir);
await this.webAPIs.start();
await this.draupnir.startupComplete();
} catch (e) {
this.stopEverything();
throw e;
}
} else if (this.safeModeDraupnir !== null) {
this.safeModeDraupnir.startupComplete();
}
}
private stopDraupnir(): void {
this.draupnir?.stop();
this.draupnir = null;
this.webAPIs?.stop();
this.webAPIs = null;
}
private stopSafeModeDraupnir(): void {
this.safeModeDraupnir?.stop();
this.safeModeDraupnir = null;
}
public stopEverything(): void {
this.stopDraupnir();
this.stopSafeModeDraupnir();
}
}
+9 -16
View File
@@ -9,9 +9,7 @@
// </text>
import * as path from "path";
import { Healthz } from "./health/healthz";
import {
LogLevel,
LogService,
@@ -24,11 +22,9 @@ import {
import { StoreType } from "@matrix-org/matrix-sdk-crypto-nodejs";
import { read as configRead } from "./config";
import { initializeSentry, patchMatrixClient } from "./utils";
import { DraupnirBotModeToggle, constructWebAPIs } from "./DraupnirBotMode";
import { Draupnir } from "./Draupnir";
import { DraupnirBotModeToggle } from "./DraupnirBotMode";
import { SafeMatrixEmitterWrapper } from "matrix-protection-suite-for-matrix-bot-sdk";
import { DefaultEventDecoder, Task } from "matrix-protection-suite";
import { WebAPIs } from "./webapis/WebAPIs";
import { DefaultEventDecoder } from "matrix-protection-suite";
import { SqliteRoomStateBackingStore } from "./backingstore/better-sqlite3/SqliteRoomStateBackingStore";
void (async function () {
@@ -51,8 +47,7 @@ void (async function () {
healthz.listen();
}
let bot: Draupnir | null = null;
let apis: WebAPIs | null = null;
let bot: DraupnirBotModeToggle | null = null;
try {
const storagePath = path.isAbsolute(config.dataPath)
? config.dataPath
@@ -101,7 +96,7 @@ void (async function () {
eventDecoder
)
: undefined;
const toggle = await DraupnirBotModeToggle.create(
bot = await DraupnirBotModeToggle.create(
client,
new SafeMatrixEmitterWrapper(client, eventDecoder),
config,
@@ -109,25 +104,23 @@ void (async function () {
);
// We don't want to send the status on start, as we need to initialize e2ee first (using client.start);
bot = (await toggle.switchToDraupnir({ sendStatusOnStart: false })).expect(
"Failed to initialize Draupnir"
(await bot.startFromScratch({ sendStatusOnStart: false })).expect(
"Failed to start Draupnir"
);
apis = constructWebAPIs(bot);
} catch (err) {
console.error(
`Failed to setup mjolnir from the config ${config.dataPath}: ${err}`
);
bot?.stopEverything();
throw err;
}
try {
await config.RUNTIME.client.start();
void Task(bot.startupComplete());
await apis.start();
await bot.encryptionInitialized();
healthz.isHealthy = true;
} catch (err) {
console.error(`Mjolnir failed to start: ${err}`);
bot.stop();
apis.stop();
bot.stopEverything();
throw err;
}
})();
+1 -1
View File
@@ -101,7 +101,7 @@ export class SafeModeDraupnir implements MatrixAdaptorContext {
this.clientRooms.off("timeline", this.timelineEventListener);
}
public sendStartupComplete(): void {
public startupComplete(): void {
void Task(
sendMatrixEventsFromDeadDocument(
this.clientPlatform.toRoomMessageSender(),