gameflow-deck/src/bun/api/jobs/launch-game-job.ts
Simeon Radivoev 816d50ae4d
fix: Fixed romm login, now uses token
feat: Moved romm to internal plugin
fix: Made focusing and navigation more reliable
fix: Loading errors on first time launch
2026-03-28 17:32:51 +02:00

123 lines
No EOL
4.1 KiB
TypeScript

import z from "zod";
import { IJob, JobContext } from "../task-queue";
import { ActiveGameSchema, ActiveGameType } from "@/bun/types/typesc.schema";
import { db, events, plugins } from "../app";
import * as appSchema from "@schema/app";
import { eq, sql } from "drizzle-orm";
import { spawn } from 'node:child_process';
export class LaunchGameJob implements IJob<z.infer<typeof LaunchGameJob.dataSchema>, "playing">
{
static id = "launch-game" as const;
static dataSchema = z.optional(ActiveGameSchema);
group = "launch-game";
activeGame?: ActiveGameType;
gameId: number;
validCommand: CommandEntry;
gameSource: string;
gameSourceId: string;
constructor(gameId: number, validCommand: CommandEntry, source: string, sourceId: string)
{
this.gameId = gameId;
this.validCommand = validCommand;
this.gameSource = source;
this.gameSourceId = sourceId;
}
async start (context: JobContext<IJob<ActiveGameType, "playing">, ActiveGameType, "playing">)
{
const localGame = await db.query.games.findFirst({
where: eq(appSchema.games.id, this.gameId), columns: {
name: true,
source_id: true,
source: true
}
});
const commandArgs = await plugins.hooks.games.emulatorLaunch.promise({
autoValidCommand: this.validCommand,
game: { source: this.gameSource, id: this.gameId }
});
const command = commandArgs ? this.validCommand.metadata.emulatorBin ?? this.validCommand.command : this.validCommand.command;
await new Promise((resolve, reject) =>
{
const game = spawn(command, commandArgs, {
shell: true,
cwd: this.validCommand.startDir,
signal: context.abortSignal
});
game.stdout.on('data', data => console.log(data));
game.on('close', (code) =>
{
resolve(code);
});
game.on('error', e =>
{
console.error(e);
reject(e);
});
this.activeGame = {
process: game,
name: localGame?.name ?? "Unknown",
gameId: this.gameId,
command: this.validCommand
};
const updatePlayed = async (source: string, id: string) =>
{
await db.update(appSchema.games).set({ last_played: new Date() }).where(eq(appSchema.games.id, this.gameId));
await plugins.hooks.games.updatePlayed.promise({ source, id }).then(v =>
{
if (v) events.emit('notification', { message: "Updated Last Played", type: 'success' });
});
};
if (this.gameSource !== 'local')
{
updatePlayed(this.gameSource, this.gameSourceId);
}
else if (localGame?.source && localGame?.source !== 'local' && localGame.source_id)
{
updatePlayed(localGame.source, localGame.source_id);
}
});
/* Old spawn lanching, cases issues, needs to be ran as shell
const cmd = Array.from(validCommand.command.command.matchAll(/(".*?"|[^\s"]+)/g)).map(m => m[0]);
const game = setActiveGame({
process: Bun.spawn({
cmd,
env: {
...process.env
},
onExit (subprocess, exitCode, signalCode, error)
{
events.emit('activegameexit', { subprocess, exitCode, signalCode, error });
},
stdin: "ignore",
stdout: "inherit",
stderr: "inherit",
}),
name: localGame?.name ?? "Unknown",
gameId: validCommand.gameId,
command: validCommand.command.command
});
await game.process.exited;
if (game.process.exitCode && game.process.exitCode > 0)
{
return status('Internal Server Error');
}*/
}
exposeData ()
{
return this.activeGame;
}
}