You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
vore-rpg/src/ipc/restart.ts

201 lines
6.0 KiB

import {fork} from "child_process"
import {ChatInputCommandInteraction, Client, InteractionWebhook} from "discord.js"
export enum RestartReason {
RestartCommand = "restart",
}
export enum StartState {
NeverStarted = "never_started",
Errored = "errored",
Crashed = "crashed",
Started = "started",
Ready = "ready",
Failed = "failed",
}
export interface StartData {
state: StartState,
status?: string
}
export interface RestartData {
appId: string
token: string
reason: RestartReason
}
let restartState: RestartData | null = null
export async function checkIsRestart(): Promise<RestartData | null> {
if (!process.connected) {
return null
} else {
try {
const result = await new Promise<RestartData | null>((resolve) => {
process.on("message", (value: { restart?: RestartData }) => {
if (value && value.restart) {
resolve(value.restart)
} else {
resolve(null)
}
})
setTimeout(() => resolve(null), 50)
})
if (result !== null) {
restartState = result
}
return result
} catch (ex) {
return null
}
}
}
export async function reportStarted(): Promise<void> {
if (!process.send) {
return
}
const startData: StartData = {
state: StartState.Started,
}
process.send(startData)
}
export async function reportReady(c: Client<true>): Promise<boolean> {
if (restartState) {
try {
const hook = new InteractionWebhook(c, restartState.appId, restartState.token)
await hook.send({
content: "yawwwwn... Good morning...",
})
} catch (ex) {
console.log("followup failed to send", ex)
}
}
if (!process.send) {
return false
}
const startData: StartData = {
state: StartState.Ready,
}
process.send(startData)
return true
}
export async function reportFailed(c: Client, error: unknown): Promise<void> {
console.log("failed to start", error)
if (restartState) {
try {
const hook = new InteractionWebhook(c, restartState.appId, restartState.token)
await hook.send({
content: "Ugh... I can't get up... Head hurts... sorry...",
})
} catch (ex) {
console.log("followup failed to send", ex)
}
}
if (!process.send) {
return
}
const startData: StartData = {
state: StartState.Failed,
status: `${error}`,
}
process.send(startData)
}
export async function wrappedRestart(b: ChatInputCommandInteraction) {
try {
await doRestart(b.client, b.webhook.id, b.webhook.token, RestartReason.RestartCommand)
} catch (ex) {
console.log("failed doing restart", ex)
let data: StartData | null = null
if (typeof ex === "object" && ex !== null && ex.hasOwnProperty("state")) {
data = ex as StartData
}
if ((data && data.state)) {
try {
b.client.user.presence.set({
status: "invisible",
activities: [],
})
await b.followUp("Mhhhh... I don't feel good...")
} catch (ex) {
console.log("failed resetting presence and sending followup", ex)
}
}
} finally {
b.client.destroy()
}
}
export async function doRestart(client: Client, appId: string, token: string,
restartReason: RestartReason): Promise<StartData> {
return new Promise((resolve, reject) => {
const child = fork(process.argv[1], process.argv.slice(2), {
execPath: process.execPath,
execArgv: process.execArgv,
cwd: process.cwd(),
detached: true,
stdio: "inherit",
})
let result: StartData = {
state: StartState.NeverStarted,
}
child.once("error", (err) => {
result.state = StartState.Errored
result.status = `${err}`
if (child.connected) {
child.unref()
child.disconnect()
}
reject({...result})
})
child.once("exit", (code, signal) => {
result.state = StartState.Crashed
result.status = `Exited unexpectedly with code ${code} (signal ${signal})`
if (child.connected) {
child.unref()
child.disconnect()
}
reject({...result})
})
child.on("message", (message: StartData) => {
switch (typeof message === "object" && message.state) {
case StartState.Started:
result.state = StartState.Started
break
case StartState.Ready:
resolve({...message})
if (child.connected) {
child.unref()
child.disconnect()
}
break
case StartState.Failed:
reject({...message})
if (child.connected) {
child.unref()
child.disconnect()
}
break
default:
result.state = StartState.Failed
result.status = `Unexpected message: ${message}`
reject({...message})
if (child.connected) {
child.unref()
child.disconnect()
}
break
}
})
process.on("disconnect", () => {
result.state = StartState.Crashed
reject({...result})
child.unref()
})
child.send({restart: {appId, token, reason: restartReason}})
})
}