Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,5 +25,11 @@
],
"css.validate": false,
"tailwindCSS.validate": true,
"tailwindCSS.emmetCompletions": true
"tailwindCSS.emmetCompletions": true,
"typescript.preferences.autoImportSpecifierExcludeRegexes": [
"^server/"
],
"javascript.preferences.autoImportSpecifierExcludeRegexes": [
"^server/"
]
}
2 changes: 1 addition & 1 deletion application/electron.vite.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ export default defineConfig({
'@': resolve(__dirname, 'src'),
},
},
plugins: [externalizeDepsPlugin({ exclude: ['ogi-addon', 'webtorrent'] })],
plugins: [externalizeDepsPlugin({ exclude: ['ogi-addon'] })],
build: {
rollupOptions: {
input: {
Expand Down
17 changes: 10 additions & 7 deletions application/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,19 +32,20 @@
},
"scripts": {
"build": "electron-vite build",
"build:executor": "cd ../packages/executor && bun run build",
"build:ogiaddon": "cd ../packages/ogi-addon/ && bun x tsdown && cd ../real-debrid/ && bun x tsdown && cd ../all-debrid/ && bun x tsdown",
"dev": "bun run build:ogiaddon && ELECTRON_EXEC_PATH=\"$(node -p \"require('electron')\" )\" electron-vite dev",
"dev": "bun run build:executor && bun run build:ogiaddon && ELECTRON_EXEC_PATH=\"$(node -p \"require('electron')\" )\" electron-vite dev",
"preview": "electron-vite preview",
"dev:server": "nodemon --watch server --ext ts,json --exec \"tsc --p tsconfig.addonserver.json && bun run --bun build-addons/dev-server.js\"",
"preelectron-pack": "bun run build:ogiaddon && bun run build",
"preelectron-pack": "bun run build:executor && bun run build:ogiaddon && bun run build",
"electron-pack": "electron-builder",
"format": "prettier --write \"src/**/*.{ts,svelte,json}\"",
"electron-pack:linux": "electron-builder -l",
"check": "bun run build:ogiaddon && svelte-check --tsconfig ./tsconfig.svelte.json",
"check": "bun run build:executor && bun run build:ogiaddon && svelte-check --tsconfig ./tsconfig.svelte.json",
"typecheck": "bun run typecheck:svelte && bun run typecheck:electron && bun run typecheck:addonserver",
"typecheck:svelte": "svelte-check --tsconfig ./tsconfig.svelte.json",
"typecheck:electron": "tsc -p tsconfig.electron.json --noEmit",
"typecheck:addonserver": "tsc -p tsconfig.addonserver.json --noEmit",
"typecheck:svelte": "svelte-check --tsconfig ./tsconfig.svelte.json --threshold error",
"typecheck:electron": "tsc -p tsconfig.electron.json --noEmit --pretty false",
"typecheck:addonserver": "tsc -p tsconfig.addonserver.json --noEmit --pretty false",
"rebuild": "npm rebuild"
},
"dependencies": {
Expand All @@ -62,7 +63,9 @@
"node-datachannel": "^0.32.0",
"ogi-addon": "workspace:*",
"parse-torrent": "^11.0.19",
"real-debrid-js": "*",
"real-debrid-js": "workspace:*",
"@ogi-sdk/addon-server": "workspace:*",
"@ogi-sdk/executor": "workspace:*",
"sanitize-html": "^2.17.0",
"semver": "^7.7.3",
"shell-quote": "^1.8.3",
Expand Down
79 changes: 41 additions & 38 deletions application/src/electron/handlers/handler.addon.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,16 @@ import { BrowserWindow, ipcMain } from 'electron';
import fs from 'fs';
import { join } from 'path';
import { exec, spawn } from 'child_process';
import {
processes,
setupAddon,
startAddon,
} from '@/electron/manager/manager.addon.js';
import { Addon } from '@/electron/manager/manager.addon.js';
import { __dirname } from '@/electron/manager/manager.paths.js';
import { server, clients, port } from '@/electron/server/addon-server.js';
import {
addonServer,
port,
startAddonServer,
} from '@/electron/server/addon-server.js';
import { sendNotification } from '@/electron/main.js';
import axios from 'axios';
import { AddonConnection } from '@/electron/server/AddonConnection.js';
import { AddonConnection } from '@ogi-sdk/addon-server';

export async function startAddons(): Promise<void> {
// start all of the addons
Expand Down Expand Up @@ -48,7 +48,15 @@ export async function startAddons(): Promise<void> {
}

console.log(`Starting addon ${addonPath}`);
promises.push(startAddon(addonPath, addon));
promises.push(
(async () => {
const instance = await Addon.load(addonPath);
if (!instance) {
return undefined;
}
return instance.startRegistered(addon);
})()
);
}
await Promise.allSettled(promises);
console.log('All addons started');
Expand All @@ -57,24 +65,16 @@ export async function startAddons(): Promise<void> {
export async function restartAddonServer(): Promise<void> {
// stop the server
console.log('Stopping server...');
server.close();
clients.clear();
addonServer.stop();
// stop all of the addons
for (const process of Object.keys(processes)) {
console.log(`Killing process ${process}`);
const killed = processes[process].kill('SIGKILL');
console.log(`Killed process ${process}: ${killed}`);
for (const instance of [...Addon.running.values()]) {
console.log(`Stopping addon ${instance.config.path}`);
instance.stop();
}
// start the server and wait for it to be listening before starting addons
await new Promise<void>((resolve, reject) => {
server.once('error', reject);
server.listen(port, () => {
server.removeListener('error', reject);
console.log(`Addon Server is running on http://localhost:${port}`);
console.log(`Server is being executed by electron!`);
resolve();
});
});
await startAddonServer();
Comment on lines +68 to +75
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== restartAddonServer callsite =="
sed -n '61,76p' application/src/electron/handlers/handler.addon.ts

echo
echo "== addon server start/stop definitions =="
rg -n -C3 'class AddonServer|stop\s*\(|startAddonServer|listen\s*\(' \
  packages/addon-server \
  application/src/electron/server

Repository: Nat3z/OpenGameInstaller

Length of output: 4155


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== Full AddonServer class implementation =="
rg -n -A 150 'export class AddonServer' packages/addon-server/lib/addon.ts | head -200

echo
echo "== Full startAddonServer implementation =="
rg -n -A 30 'function startAddonServer' application/src/electron/server/addon-server.ts

echo
echo "== Server object initialization in addon-server.ts =="
rg -n -B 5 'server\.listen|new.*Server|import.*Server|const.*server' application/src/electron/server/addon-server.ts | head -50

Repository: Nat3z/OpenGameInstaller

Length of output: 4447


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== Full addonServer initialization and setup =="
rg -n -A 20 'const addonServer = new AddonServer' application/src/electron/server/addon-server.ts

echo
echo "== Search for addonServer.extend calls =="
rg -n 'addonServer\.extend|\.extend\(' application/src/electron/server/addon-server.ts

Repository: Nat3z/OpenGameInstaller

Length of output: 733


Make addonServer.stop() awaitable or add proper close handling before restarting the server.

The addonServer.stop() method calls this.server.close() synchronously (line 100 of packages/addon-server/lib/addon.ts), but http.Server.close() is asynchronous and doesn't block. In restartAddonServer(), calling stop() without awaiting means await startAddonServer() runs before the port is fully released, creating a potential EADDRINUSE race condition. Either make stop() return a Promise that waits for the close callback, or add explicit close handling in the restart flow.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@application/src/electron/handlers/handler.addon.ts` around lines 64 - 72, The
call to addonServer.stop() is currently synchronous but this.server.close() is
async, causing a race where await startAddonServer() may run before the port is
freed; update the stop() implementation (in addon server class where
this.server.close() is called) to return a Promise that resolves in the close
callback (and rejects on error), and then change the restart flow
(restartAddonServer or the handler using addonServer.stop()) to await
addonServer.stop() before killing processes and calling await
startAddonServer(); alternatively, instead of changing stop(), wrap the existing
close call at the restart site in a Promise and await it before proceeding so
startAddonServer() only runs after the server is fully closed.

console.log(`Addon Server is running on http://localhost:${port}`);
console.log(`Server is being executed by electron!`);
await startAddons();

sendNotification({
Expand Down Expand Up @@ -185,7 +185,8 @@ export default function AddonManagerHandler(mainWindow: BrowserWindow) {
});
}

const hasAddonBeenSetup = await setupAddon(addonPath);
const instance = await Addon.load(addonPath);
const hasAddonBeenSetup = instance ? await instance.install() : false;
if (!hasAddonBeenSetup) {
sendNotification({
message: `An error occurred when setting up ${addonName}`,
Expand All @@ -209,10 +210,9 @@ export default function AddonManagerHandler(mainWindow: BrowserWindow) {
});

ipcMain.handle('clean-addons', async (_) => {
// stop all of the addons
for (const process of Object.keys(processes)) {
console.log(`Killing process ${process}`);
processes[process].kill('SIGKILL');
for (const instance of [...Addon.running.values()]) {
console.log(`Stopping addon ${instance.config.path}`);
instance.stop();
}

// delete all of the addons
Expand Down Expand Up @@ -243,10 +243,9 @@ export default function AddonManagerHandler(mainWindow: BrowserWindow) {
return;
}

// stop all of the addons
for (const process of Object.keys(processes)) {
console.log(`Killing process ${process}`);
processes[process].kill('SIGKILL');
for (const instance of [...Addon.running.values()]) {
console.log(`Stopping addon ${instance.config.path}`);
instance.stop();
}

// pull all of the addons
Expand Down Expand Up @@ -322,9 +321,13 @@ export default function AddonManagerHandler(mainWindow: BrowserWindow) {
});

mainWindow!!.webContents.send('addon:updated', addon);
// setup the addon
setupAddon(addonPath)
.then((success) => {
void Addon.load(addonPath).then(async (instance) => {
if (!instance) {
reject(new Error(`Failed to load addon ${addon}`));
return;
}
try {
const success = await instance.install();
if (!success) {
sendNotification({
message: `An error occurred when setting up ${addon}`,
Expand All @@ -336,15 +339,15 @@ export default function AddonManagerHandler(mainWindow: BrowserWindow) {
}
console.log(`Addon ${addon} updated successfully.`);
resolve();
})
.catch((setupErr) => {
} catch (setupErr) {
sendNotification({
message: `An error occurred when setting up ${addon}`,
id: Math.random().toString(36).substring(7),
type: 'error',
});
reject(setupErr);
});
}
});
}
);
});
Expand Down
19 changes: 12 additions & 7 deletions application/src/electron/handlers/handler.app.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
import axios from 'axios';
import { ipcMain, app } from 'electron';
import { currentScreens } from '@/electron/main.js';
import { currentScreens, screenInputCallbacks } from '@/electron/main.js';
import * as fs from 'fs';
import { join } from 'path';
import * as os from 'os';
import * as path from 'path';
import { createReadStream, createWriteStream } from 'fs';
import { isDev } from '@/electron/manager/manager.paths.js';
import { __dirname } from '@/electron/manager/manager.paths.js';
import { clients } from '@/electron/server/addon-server.js';
import { registerSteamHandlers } from '@/electron/handlers/handler.steam.js';
import { registerLibraryHandlers } from '@/electron/handlers/handler.library.js';
import { registerRedistributableHandlers } from '@/electron/handlers/handler.redists.js';
import { getCurrentUsername } from './helpers.app/platform.js';
import { getEffectiveOnlineState } from '@/electron/lib/online.js';
import { addonServer } from '@/electron/server/addon-server.js';

/**
* Escapes a string for safe use in shell commands by escaping special characters
Expand Down Expand Up @@ -189,6 +189,11 @@ export default function handler(mainWindow: Electron.BrowserWindow) {
});
ipcMain.handle('app:screen-input', async (_, data) => {
currentScreens.set(data.id, data.data);
const callback = screenInputCallbacks.get(data.id);
if (callback) {
callback(data.data);
screenInputCallbacks.delete(data.id);
Comment on lines +192 to +195
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Always clear one-shot screen callbacks even if they throw.

If callback(data.data) throws, the map entry survives and this IPC handler rejects, so a stale callback can hang around indefinitely.

Suggested fix
     const callback = screenInputCallbacks.get(data.id);
     if (callback) {
-      callback(data.data);
-      screenInputCallbacks.delete(data.id);
+      screenInputCallbacks.delete(data.id);
+      callback(data.data);
     }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const callback = screenInputCallbacks.get(data.id);
if (callback) {
callback(data.data);
screenInputCallbacks.delete(data.id);
const callback = screenInputCallbacks.get(data.id);
if (callback) {
screenInputCallbacks.delete(data.id);
callback(data.data);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@application/src/electron/handlers/handler.app.ts` around lines 192 - 195, The
one-shot screen input callback stored in screenInputCallbacks is not removed if
callback(data.data) throws, leaving a stale entry; update the IPC handler around
the retrieval of callback (using screenInputCallbacks.get(data.id)) so you
always remove the entry (screenInputCallbacks.delete(data.id)) in a finally-like
guarantee: get the callback, if present run it inside a try/catch but ensure
screenInputCallbacks.delete(data.id) is executed regardless of success or error,
and rethrow or handle the error after deletion so behavior is preserved.

}
return;
});

Expand All @@ -198,14 +203,14 @@ export default function handler(mainWindow: Electron.BrowserWindow) {

// Addon helpers
ipcMain.handle('app:get-addon-path', async (_, addonID: string) => {
let client = clients.get(addonID);
let client = addonServer.getClient(addonID);
if (!client || !client.filePath) {
return null;
}
return client.filePath;
});
ipcMain.handle('app:get-addon-icon', async (_, addonID: string) => {
let client = clients.get(addonID);
let client = addonServer.getClient(addonID);
if (!client || !client.filePath) {
return null;
}
Expand Down Expand Up @@ -239,9 +244,9 @@ export default function handler(mainWindow: Electron.BrowserWindow) {
join(__dirname, 'public'),
join(__dirname, 'config'),
// for each addon, add the basedir of the addon to the allowed dirs
...Array.from(clients.values())
.filter((client) => client.filePath)
.map((client) => client.filePath + '/'),
...Array.from(addonServer.getConnections().values())
.filter((connection) => connection.filePath)
.map((connection) => connection.filePath + '/'),
];

let realPath: string;
Expand Down
6 changes: 3 additions & 3 deletions application/src/electron/handlers/handler.rest.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { ipcMain } from 'electron';
import { addonServer } from '@/electron/server/addon-server.js';
import { requestSchema } from '@/electron/server/serve.js';
import { addonIPC } from '@/electron/server/addon-server.js';
import { requestSchema } from '@/electron/server/ipc.js';

export default function handler() {
ipcMain.handle('addon:request', async (_, request) => {
Expand All @@ -12,7 +12,7 @@ export default function handler() {
}

const parsedRequest = parsedRequestSafe.data;
const response = await addonServer.handleRequest(parsedRequest);
const response = await addonIPC.handleRequest(parsedRequest);
if (response.tag === 'defer') {
return {
status: response.status,
Expand Down
Loading
Loading