Wait for homeserver when bridge is first started

This commit is contained in:
Cadence Ember 2026-07-12 02:47:07 +12:00
commit f92d280494
4 changed files with 84 additions and 11 deletions

View file

@ -14,6 +14,8 @@ const {sync} = passthrough
/** @type {import("./discord-packets")} */ /** @type {import("./discord-packets")} */
const discordPackets = sync.require("./discord-packets") const discordPackets = sync.require("./discord-packets")
const CONNECTION_DEBUG = false
class DiscordClient { class DiscordClient {
/** /**
* @param {string} discordToken * @param {string} discordToken
@ -59,6 +61,7 @@ class DiscordClient {
}) })
} }
if (CONNECTION_DEBUG) {
const addEventLogger = (eventName, logName) => { const addEventLogger = (eventName, logName) => {
this.cloud.on(eventName, (...args) => { this.cloud.on(eventName, (...args) => {
const d = new Date().toISOString().slice(0, 19) const d = new Date().toISOString().slice(0, 19)
@ -69,6 +72,7 @@ class DiscordClient {
addEventLogger("disconnected", "Disconnected") addEventLogger("disconnected", "Disconnected")
addEventLogger("ready", "Ready") addEventLogger("ready", "Ready")
} }
}
} }
module.exports = DiscordClient module.exports = DiscordClient

View file

@ -1,12 +1,15 @@
// @ts-check // @ts-check
const assert = require("assert") const assert = require("assert")
const {scheduler} = require("timers/promises")
const passthrough = require("../passthrough") const passthrough = require("../passthrough")
const {sync} = passthrough const {sync} = passthrough
/** @type {import("../matrix/homeserver-status")} */ /** @type {import("../matrix/homeserver-status")} */
const homeserverStatus = sync.require("../matrix/homeserver-status") const homeserverStatus = sync.require("../matrix/homeserver-status")
let checkedHomeserver = false
/** /**
* @param {import("./discord-client")} client * @param {import("./discord-client")} client
* @param {import("cloudstorm").IGatewayMessage} message * @param {import("cloudstorm").IGatewayMessage} message
@ -48,6 +51,31 @@ async function onPacket(client, message, listen) {
if (listen === "full") { if (listen === "full") {
try { try {
/*
Info about guilds is populated one guild at a time.
For m->d bridging to work, the guild needs to be populated, so we need to have GUILD_CREATE for the guild.
If we ping the homeserver, it will send us any pending events, so we need to wait for all GUILD_CREATES before we ping.
We must attempt a ping because we don't want to try sending missed d->m messages to an offline homeserver.
This delay can be removed if ONE of the following is done:
1. m->d can queue incoming events until their guild exists in memory
2. d->m missed messages can have their errors handled and added to queue, rather than pinging first
*/
let isMainCharacter = false
if (!checkedHomeserver) {
checkedHomeserver = true
isMainCharacter = true
console.log("Warming up guilds~")
}
await scheduler.wait(5000)
if (isMainCharacter) {
checkedHomeserver = true
process.stdout.write("Connecting to homeserver... ")
await homeserverStatus.homeserverStatus.waitForOnline(true)
console.log("ok.\nReplaying past events. Welcome to Out Of Your Element.")
} else {
await homeserverStatus.homeserverStatus.waitForOnline(false)
}
await eventDispatcher.checkMissedExpressions(message.d) await eventDispatcher.checkMissedExpressions(message.d)
await eventDispatcher.checkMissedMessages(client, message.d) await eventDispatcher.checkMissedMessages(client, message.d)
await eventDispatcher.checkMissedPins(client, message.d) await eventDispatcher.checkMissedPins(client, message.d)

View file

@ -11,7 +11,7 @@ const discordPackets = sync.require("../d2m/discord-packets")
/** @type {import("../matrix/api")} */ /** @type {import("../matrix/api")} */
const api = sync.require("../matrix/api") const api = sync.require("../matrix/api")
const DEBUG_HOMESERVER_STATUS = true const DEBUG_HOMESERVER_STATUS = false
function debugHomeserverStatus(message) { function debugHomeserverStatus(message) {
if (DEBUG_HOMESERVER_STATUS) { if (DEBUG_HOMESERVER_STATUS) {
@ -31,6 +31,19 @@ const homeserverStatus = new class HomeserverStatus {
this.sm = new StateMachine("online") this.sm = new StateMachine("online")
.defineState("online") .defineState("online")
.defineState("checking", {
onEnter: [async () => {
const pingResult = await api.ping().catch(e => ({ok: false}))
if (pingResult.ok) {
this.sm.doTransition("check ok")
} else {
this.sm.doTransition("check fail")
}
}],
onLeave: [],
transitions: new Map()
})
.defineState("offline", { .defineState("offline", {
onEnter: [() => { onEnter: [() => {
this.pingInterval = setInterval(async () => { this.pingInterval = setInterval(async () => {
@ -64,9 +77,12 @@ const homeserverStatus = new class HomeserverStatus {
.defineUniversalTransition("error", "offline") .defineUniversalTransition("error", "offline")
.defineTransition("offline", "ping ok", "recovering") .defineTransition("offline", "ping ok", "recovering")
.defineTransition("recovering", "recovered", "online") .defineTransition("recovering", "recovered", "online")
.defineTransition("online", "check", "checking")
.defineTransition("checking", "check ok", "recovering")
.defineTransition("checking", "check fail", "offline")
this.sm.on("enter", st => debugHomeserverStatus(`homeserver status: ${st}`)) this.sm.on("enter", st => debugHomeserverStatus(`homeserver status: ${st}`))
this.sm.setMaxListeners(101)
this.sm.freeze() this.sm.freeze()
} }
@ -74,6 +90,30 @@ const homeserverStatus = new class HomeserverStatus {
return this.sm.currentStateName === "online" return this.sm.currentStateName === "online"
} }
/** @param {boolean} forceCheck */
waitForOnline(forceCheck) {
const onlinePromise = new Promise(resolve => {
// Already online? Start check or just done
if (this.sm.currentStateName === "online") {
if (forceCheck) {
this.sm.doTransition("check")
} else {
return resolve(null)
}
}
// Checking or not online. Wait for online.
const onlineListener = stateName => {
if (stateName === "online") {
this.sm.removeListener("enter", onlineListener)
resolve(null)
}
}
this.sm.on("enter", onlineListener)
})
return onlinePromise
}
/** /**
* When offline or recovering, call this for incoming packets to queue them to be sent in order later. * When offline or recovering, call this for incoming packets to queue them to be sent in order later.
*/ */

View file

@ -31,8 +31,9 @@ sync.require("./src/m2d/event-dispatcher")
;(async () => { ;(async () => {
await migrate.migrate(db) await migrate.migrate(db)
process.stdout.write("Connecting to Discord... ")
await discord.cloud.connect() await discord.cloud.connect()
console.log("Discord gateway started") console.log("ok.")
sync.require("./src/web/server") sync.require("./src/web/server")
await power.applyPower() await power.applyPower()