Compare commits

..

1 Commits

Author SHA1 Message Date
Zamitto
3c79a2dcb9 feat: dont setup ww feedback widget if user has no token 2026-01-15 08:41:52 -03:00
4 changed files with 21 additions and 190 deletions

View File

@@ -1,36 +1,10 @@
from flask import Flask, request, jsonify
import sys, json, urllib.parse, psutil, socket
import sys, json, urllib.parse, psutil
from torrent_downloader import TorrentDownloader
from http_downloader import HttpDownloader
from profile_image_processor import ProfileImageProcessor
import libtorrent as lt
RPC_PORT_MIN = 8080
RPC_PORT_MAX = 9000
def find_available_port(preferred_port, start=RPC_PORT_MIN, end=RPC_PORT_MAX):
"""Find an available port, trying the preferred port first."""
# Try preferred port first
try:
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
s.bind(('0.0.0.0', preferred_port))
return preferred_port
except OSError:
pass
# Try ports in range
for port in range(start, end + 1):
if port == preferred_port:
continue
try:
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
s.bind(('0.0.0.0', port))
return port
except OSError:
continue
raise RuntimeError(f"No available ports in range {start}-{end}")
app = Flask(__name__)
# Retrieve command line arguments
@@ -218,7 +192,4 @@ def action():
return "", 200
if __name__ == "__main__":
actual_port = find_available_port(int(http_port))
# Print port for Node.js to capture - must be flushed immediately
print(f"RPC_PORT:{actual_port}", flush=True)
app.run(host="0.0.0.0", port=actual_port)
app.run(host="0.0.0.0", port=int(http_port))

View File

@@ -25,7 +25,7 @@ const getLibrary = async (): Promise<LibraryGame[]> => {
const achievements = await gameAchievementsSublevel.get(key);
unlockedAchievementCount =
achievements?.unlockedAchievements?.length ?? 0;
achievements?.unlockedAchievements.length ?? 0;
}
return {

View File

@@ -27,19 +27,11 @@ const binaryNameByPlatform: Partial<Record<NodeJS.Platform, string>> = {
win32: "hydra-python-rpc.exe",
};
const RPC_PORT_PREFIX = "RPC_PORT:";
const PORT_DISCOVERY_TIMEOUT_MS = 30000;
const HEALTH_CHECK_INTERVAL_MS = 100;
const HEALTH_CHECK_TIMEOUT_MS = 10000;
export class PythonRPC {
public static readonly BITTORRENT_PORT = "5881";
public static readonly DEFAULT_RPC_PORT = "8084";
private static currentPort: string = this.DEFAULT_RPC_PORT;
public static readonly RPC_PORT = "8084";
public static readonly rpc = axios.create({
baseURL: `http://localhost:${this.DEFAULT_RPC_PORT}`,
baseURL: `http://localhost:${this.RPC_PORT}`,
httpAgent: new http.Agent({
family: 4, // Force IPv4
}),
@@ -70,102 +62,6 @@ export class PythonRPC {
return newPassword;
}
private static updateBaseURL(port: string) {
this.currentPort = port;
this.rpc.defaults.baseURL = `http://localhost:${port}`;
pythonRpcLogger.log(`RPC baseURL updated to port ${port}`);
}
private static parsePortFromStdout(data: string): string | null {
const lines = data.split("\n");
for (const line of lines) {
if (line.startsWith(RPC_PORT_PREFIX)) {
return line.slice(RPC_PORT_PREFIX.length).trim();
}
}
return null;
}
private static async waitForHealthCheck(): Promise<void> {
const startTime = Date.now();
while (Date.now() - startTime < HEALTH_CHECK_TIMEOUT_MS) {
try {
const response = await this.rpc.get("/healthcheck", { timeout: 1000 });
if (response.status === 200) {
pythonRpcLogger.log("RPC health check passed");
return;
}
} catch {
// Server not ready yet, continue polling
}
await new Promise((resolve) =>
setTimeout(resolve, HEALTH_CHECK_INTERVAL_MS)
);
}
throw new Error("RPC health check timed out");
}
private static waitForPort(
childProcess: cp.ChildProcess
): Promise<string | null> {
return new Promise((resolve, reject) => {
let resolved = false;
let stdoutBuffer = "";
const timeout = setTimeout(() => {
if (!resolved) {
resolved = true;
reject(
new Error(
`Port discovery timed out after ${PORT_DISCOVERY_TIMEOUT_MS}ms`
)
);
}
}, PORT_DISCOVERY_TIMEOUT_MS);
const cleanup = () => {
clearTimeout(timeout);
};
if (childProcess.stdout) {
childProcess.stdout.setEncoding("utf-8");
childProcess.stdout.on("data", (data: string) => {
stdoutBuffer += data;
pythonRpcLogger.log(data);
const port = this.parsePortFromStdout(stdoutBuffer);
if (port && !resolved) {
resolved = true;
cleanup();
resolve(port);
}
});
}
childProcess.on("error", (err) => {
if (!resolved) {
resolved = true;
cleanup();
reject(err);
}
});
childProcess.on("exit", (code) => {
if (!resolved) {
resolved = true;
cleanup();
if (code === 0) {
resolve(null);
} else {
reject(new Error(`Python RPC process exited with code ${code}`));
}
}
});
});
}
public static async spawn(
initialDownload?: GamePayload,
initialSeeding?: GamePayload[]
@@ -174,14 +70,12 @@ export class PythonRPC {
const commonArgs = [
this.BITTORRENT_PORT,
this.DEFAULT_RPC_PORT,
this.RPC_PORT,
rpcPassword,
initialDownload ? JSON.stringify(initialDownload) : "",
initialSeeding ? JSON.stringify(initialSeeding) : "",
];
let childProcess: cp.ChildProcess;
if (app.isPackaged) {
const binaryName = binaryNameByPlatform[process.platform]!;
const binaryPath = path.join(
@@ -197,13 +91,16 @@ export class PythonRPC {
);
app.quit();
return;
}
childProcess = cp.spawn(binaryPath, commonArgs, {
const childProcess = cp.spawn(binaryPath, commonArgs, {
windowsHide: true,
stdio: ["inherit", "pipe", "pipe"],
stdio: ["inherit", "inherit"],
});
this.logStderr(childProcess.stderr);
this.pythonProcess = childProcess;
} else {
const scriptPath = path.join(
__dirname,
@@ -213,44 +110,16 @@ export class PythonRPC {
"main.py"
);
childProcess = cp.spawn("python", [scriptPath, ...commonArgs], {
stdio: ["inherit", "pipe", "pipe"],
const childProcess = cp.spawn("python", [scriptPath, ...commonArgs], {
stdio: ["inherit", "inherit"],
});
this.logStderr(childProcess.stderr);
this.pythonProcess = childProcess;
}
this.logStderr(childProcess.stderr);
this.pythonProcess = childProcess;
try {
const port = await this.waitForPort(childProcess);
if (port) {
this.updateBaseURL(port);
} else {
pythonRpcLogger.log(
`No port received, using default port ${this.DEFAULT_RPC_PORT}`
);
this.updateBaseURL(this.DEFAULT_RPC_PORT);
}
this.rpc.defaults.headers.common["x-hydra-rpc-password"] = rpcPassword;
await this.waitForHealthCheck();
pythonRpcLogger.log(
`Python RPC started successfully on port ${this.currentPort}`
);
} catch (err) {
pythonRpcLogger.log(`Failed to start Python RPC: ${err}`);
dialog.showErrorBox(
"RPC Error",
`Failed to start download service. ${err instanceof Error ? err.message : String(err)}\n\nPlease ensure no other application is using ports 8080-9000 and try restarting Hydra.`
);
this.kill();
throw err;
}
this.rpc.defaults.headers.common["x-hydra-rpc-password"] = rpcPassword;
}
public static kill() {

View File

@@ -138,21 +138,12 @@ export class WindowManager {
(details, callback) => {
if (
details.webContentsId !== this.mainWindow?.webContents.id ||
details.url.includes("chatwoot")
details.url.includes("chatwoot") ||
details.url.includes("workwonders")
) {
return callback(details);
}
if (details.url.includes("workwonders")) {
return callback({
...details,
requestHeaders: {
Origin: "https://workwonders.app",
...details.requestHeaders,
},
});
}
const userAgent = new UserAgent();
callback({