add: the backend server for the app

it is used for communication between Web & Electron.
This commit is contained in:
alikia2x (寒寒) 2024-12-28 22:12:34 +08:00
parent 8cf17838f4
commit fb70acab00
Signed by: alikia2x
GPG Key ID: 56209E0CCD8420C6
8 changed files with 64 additions and 8 deletions

BIN
bun.lockb Executable file

Binary file not shown.

2
bunfig.toml Normal file
View File

@ -0,0 +1,2 @@
[install.scopes]
"@jsr" = "https://npm.jsr.io"

View File

@ -17,9 +17,12 @@
"author": "",
"license": "MIT",
"dependencies": {
"@alikia/random-key": "npm:@jsr/alikia__random-key",
"@electron/remote": "^2.1.2",
"@hono/node-server": "^1.13.7",
"@unly/universal-language-detector": "^2.0.3",
"better-sqlite3": "^11.6.0",
"detect-port": "^2.1.0",
"electron-context-menu": "^4.0.4",
"electron-reloader": "^1.2.3",
"electron-screencapture": "^1.1.0",
@ -27,6 +30,7 @@
"electron-store": "^10.0.0",
"electron-window-state": "^5.0.3",
"execa": "^9.5.1",
"hono": "^4.6.15",
"i18next": "^24.0.2",
"i18next-browser-languagedetector": "^8.0.0",
"i18next-electron-fs-backend": "^3.0.2",

View File

@ -76,7 +76,7 @@ export async function deleteEncodedScreenshots(db: Database) {
// Check and process encoding task
export function processEncodingTasks(db: Database) {
const tasksPerforming = cache.get("tasksPerforming") as string[] || [];
const tasksPerforming = cache.get("backend:encodingTasksPerforming") as string[] || [];
if (tasksPerforming.length >= CONCURRENCY) return;
const stmt = db.prepare(`
@ -111,7 +111,7 @@ export function processEncodingTasks(db: Database) {
const metaFilePath = path.join(getEncodingTempDir(), `${taskId}_meta.txt`);
const metaContent = frames.map(frame => `file '${path.join(getScreenshotsDir(), frame.imgFilename)}'\nduration 0.03333`).join("\n");
fs.writeFileSync(metaFilePath, metaContent);
cache.put("tasksPerforming", [...tasksPerforming, taskId.toString()]);
cache.put("backend:encodingTasksPerforming", [...tasksPerforming, taskId.toString()]);
const videoPath = path.join(getRecordingsDir(), `${taskId}.mp4`);
const ffmpegCommand = `ffmpeg -f concat -safe 0 -i "${metaFilePath}" -c:v libx264 -r 30 -threads 1 "${videoPath}"`;
@ -138,7 +138,7 @@ export function processEncodingTasks(db: Database) {
db.prepare(`COMMIT;`).run();
}
cache.put("tasksPerforming", tasksPerforming.filter(id => id !== taskId.toString()));
cache.put("backend:encodingTasksPerforming", tasksPerforming.filter(id => id !== taskId.toString()));
fs.unlinkSync(metaFilePath);
});
}

View File

@ -9,6 +9,11 @@ import { startScreenshotLoop } from "./backend/screenshot.js";
import { __dirname } from "./dirname.js";
import { hideDock } from "./utils/electron.js";
import { checkFramesForEncoding, deleteEncodedScreenshots, processEncodingTasks } from "./backend/encoding.js";
import honoApp from "./server/index.js";
import { serve } from "@hono/node-server";
import { findAvailablePort } from "./utils/server.js";
import cache from "memory-cache";
import { generate } from '@alikia/random-key';
const i18n = initI18n();
@ -70,15 +75,26 @@ contextMenu({
app.once("ready", () => {
hideDock();
});
app.on("activate", () => {});
app.on("activate", () => {
});
app.on("ready", () => {
createTray();
findAvailablePort(12412).then((port) => {
generate().then((key) => {
cache.put("server:APIKey",key);
cache.put("server:port", port);
if (dev)
console.log(`API Key: ${key}`);
serve({ fetch: honoApp.fetch, port: port });
console.log(`App server running on port ${port}`);
});
})
initDatabase().then((db) => {
screenshotInterval = startScreenshotLoop(db);
setInterval(checkFramesForEncoding, 5000, db);
setInterval(processEncodingTasks, 10000, db);
setInterval(deleteEncodedScreenshots, 5000, db)
setInterval(deleteEncodedScreenshots, 5000, db);
dbConnection = db;
});
mainWindow = createMainWindow(port, () => (mainWindow = null));
@ -97,6 +113,6 @@ app.on("will-quit", ()=> {
// if (process.platform !== "darwin") app.quit();
// });
ipcMain.on('close-settings', () => {
ipcMain.on("close-settings", () => {
settingsWindow?.hide();
});

View File

@ -0,0 +1,17 @@
import { Hono } from 'hono'
import cache from "memory-cache";
const app = new Hono();
app.use(async (c, next) => {
const key = cache.get("server:APIKey");
if (key && c.req.header("x-api-key") !== key) {
c.res = undefined
c.res = c.json({ error: "Invalid API key" }, 401);
}
await next();
})
app.get('/ping', (c) => c.text('pong'))
export default app;

View File

@ -0,0 +1,17 @@
import { detect } from 'detect-port';
/**
* Finds an available port starting from a given port.
* @param startingFrom - The port number to start searching from.
* @returns A Promise that resolves to the first available port number.
*/
export async function findAvailablePort(startingFrom: number): Promise<number> {
return detect(startingFrom)
.then(realPort => {
return realPort; // Return the available port
})
.catch(err => {
console.error(`Error detecting port: ${err.message}`);
throw err; // Rethrow the error for further handling if needed
});
}

View File

@ -2,7 +2,7 @@
"compilerOptions": {
"target": "ESNext",
"module": "ESNext",
"moduleResolution": "node",
"moduleResolution": "bundler",
"outDir": "./dist/electron",
"rootDir": "./src/electron",
"strict": true,