Feature engineering improvements #376

Merged
LukeGus merged 20 commits from feature-engineering-improvements into dev-1.8.0 2025-10-08 01:06:01 +00:00
46 changed files with 1973 additions and 321 deletions

21
.commitlintrc.json Normal file
View File

@@ -0,0 +1,21 @@
{
"extends": ["@commitlint/config-conventional"],
"rules": {
"type-enum": [
2,
"always",
[
"feat",
"fix",
"docs",
"style",
"refactor",
"perf",
"test",
"chore",
"revert"
]
],
"subject-case": [0]
}
}

20
.editorconfig Normal file
View File

@@ -0,0 +1,20 @@
# EditorConfig is awesome: https://EditorConfig.org
# top-most EditorConfig file
root = true
# Unix-style newlines with a newline ending every file
[*]
charset = utf-8
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true
# Matches multiple files with brace expansion notation
[*.{js,jsx,ts,tsx,json,css,scss,md,yml,yaml}]
indent_style = space
indent_size = 2
# Markdown files
[*.md]
trim_trailing_whitespace = false

36
.gitattributes vendored Normal file
View File

@@ -0,0 +1,36 @@
# Auto detect text files and perform LF normalization
* text=auto eol=lf
# Source code
*.js text eol=lf
*.jsx text eol=lf
*.ts text eol=lf
*.tsx text eol=lf
*.json text eol=lf
*.css text eol=lf
*.scss text eol=lf
*.html text eol=lf
*.md text eol=lf
*.yaml text eol=lf
*.yml text eol=lf
# Scripts
*.sh text eol=lf
*.bash text eol=lf
# Windows scripts should use CRLF
*.bat text eol=crlf
*.cmd text eol=crlf
*.ps1 text eol=crlf
# Binary files
*.png binary
*.jpg binary
*.jpeg binary
*.gif binary
*.ico binary
*.svg binary
*.woff binary
*.woff2 binary
*.ttf binary
*.eot binary

34
.github/workflows/pr-check.yml vendored Normal file
View File

@@ -0,0 +1,34 @@
name: PR Check
on:
pull_request:
branches: [main, dev-*]
jobs:
lint-and-build:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: "20"
cache: "npm"
- name: Install dependencies
run: npm ci
- name: Run ESLint
run: npx eslint .
- name: Run Prettier check
run: npx prettier --check .
- name: Type check
run: npx tsc --noEmit
- name: Build
run: npm run build

1
.husky/commit-msg Normal file
View File

@@ -0,0 +1 @@
npx --no -- commitlint --edit $1

1
.husky/pre-commit Normal file
View File

@@ -0,0 +1 @@
npx lint-staged

2
.nvmrc
View File

@@ -1 +1 @@
22
20

View File

@@ -1,3 +1,23 @@
# Ignore artifacts:
build
coverage
dist
dist-ssr
release
# Dependencies
node_modules
package-lock.json
pnpm-lock.yaml
yarn.lock
# Database
db
# Environment
.env
# Misc
*.min.js
*.min.css
openapi.json

View File

@@ -1 +1,9 @@
{}
{
"semi": true,
"singleQuote": false,
"tabWidth": 2,
"trailingComma": "all",
"printWidth": 80,
"arrowParens": "always",
"endOfLine": "lf"
}

7
.vscode/extensions.json vendored Normal file
View File

@@ -0,0 +1,7 @@
{
"recommendations": [
"dbaeumer.vscode-eslint",
"esbenp.prettier-vscode",
"editorconfig.editorconfig"
]
}

1483
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -8,6 +8,11 @@
"type": "module",
"scripts": {
"clean": "npx prettier . --write",
"format": "prettier --write .",
"format:check": "prettier --check .",
"lint": "eslint .",
"lint:fix": "eslint --fix .",
"type-check": "tsc --noEmit",
"dev": "vite",
"build": "vite build && tsc -p tsconfig.node.json",
"build:backend": "tsc -p tsconfig.node.json",
@@ -20,7 +25,8 @@
"build:linux-appimage": "npm run build && electron-builder --linux AppImage",
"build:linux-targz": "npm run build && electron-builder --linux tar.gz",
"test:encryption": "tsc -p tsconfig.node.json && node ./dist/backend/backend/utils/encryption-test.js",
"migrate:encryption": "tsc -p tsconfig.node.json && node ./dist/backend/backend/utils/encryption-migration.js"
"migrate:encryption": "tsc -p tsconfig.node.json && node ./dist/backend/backend/utils/encryption-migration.js",
"prepare": "husky"
},
"dependencies": {
"@codemirror/autocomplete": "^6.18.7",
@@ -105,6 +111,8 @@
"zod": "^4.0.5"
},
"devDependencies": {
"@commitlint/cli": "^20.1.0",
"@commitlint/config-conventional": "^20.0.0",
"@eslint/js": "^9.34.0",
"@types/better-sqlite3": "^7.6.13",
"@types/cors": "^2.8.19",
@@ -123,9 +131,19 @@
"eslint-plugin-react-hooks": "^5.2.0",
"eslint-plugin-react-refresh": "^0.4.20",
"globals": "^16.3.0",
"husky": "^9.1.7",
"lint-staged": "^16.2.3",
"prettier": "3.6.2",
"typescript": "~5.9.2",
"typescript-eslint": "^8.40.0",
"vite": "^7.1.5"
},
"lint-staged": {
"*.{js,jsx,ts,tsx}": [
"prettier --write"
],
"*.{json,css,md}": [
"prettier --write"
]
}
}

View File

@@ -259,7 +259,7 @@ app.get("/version", authenticateJWT, async (req, res) => {
localVersion = foundVersion;
break;
}
} catch (error) {
} catch {
continue;
}
}
@@ -374,7 +374,6 @@ app.get("/releases/rss", authenticateJWT, async (req, res) => {
app.get("/encryption/status", requireAdmin, async (req, res) => {
try {
const authManager = AuthManager.getInstance();
const securityStatus = {
initialized: true,
system: { hasSecret: true, isValid: true },
@@ -419,8 +418,6 @@ app.post("/encryption/initialize", requireAdmin, async (req, res) => {
app.post("/encryption/regenerate", requireAdmin, async (req, res) => {
try {
const authManager = AuthManager.getInstance();
apiLogger.warn("System JWT secret regenerated via API", {
operation: "jwt_regenerate_api",
});
@@ -442,8 +439,6 @@ app.post("/encryption/regenerate", requireAdmin, async (req, res) => {
app.post("/encryption/regenerate-jwt", requireAdmin, async (req, res) => {
try {
const authManager = AuthManager.getInstance();
apiLogger.warn("JWT secret regenerated via API", {
operation: "jwt_secret_regenerate_api",
});
@@ -970,7 +965,7 @@ app.post(
try {
importDb = new Database(req.file.path, { readonly: true });
const tables = importDb
importDb
.prepare("SELECT name FROM sqlite_master WHERE type='table'")
.all();
} catch (sqliteError) {
@@ -1061,7 +1056,7 @@ app.post(
);
}
}
} catch (tableError) {
} catch {
apiLogger.info("ssh_data table not found in import file, skipping");
}
@@ -1122,7 +1117,7 @@ app.post(
);
}
}
} catch (tableError) {
} catch {
apiLogger.info(
"ssh_credentials table not found in import file, skipping",
);
@@ -1193,7 +1188,7 @@ app.post(
);
}
}
} catch (tableError) {
} catch {
apiLogger.info(`${table} table not found in import file, skipping`);
}
}
@@ -1231,7 +1226,7 @@ app.post(
);
}
}
} catch (tableError) {
} catch {
apiLogger.info(
"dismissed_alerts table not found in import file, skipping",
);
@@ -1272,7 +1267,7 @@ app.post(
);
}
}
} catch (tableError) {
} catch {
apiLogger.info("settings table not found in import file, skipping");
}
} else {
@@ -1290,7 +1285,7 @@ app.post(
try {
fs.unlinkSync(req.file.path);
} catch (cleanupError) {
} catch {
apiLogger.warn("Failed to clean up uploaded file", {
operation: "file_cleanup_warning",
filePath: req.file.path,
@@ -1316,7 +1311,7 @@ app.post(
if (req.file?.path && fs.existsSync(req.file.path)) {
try {
fs.unlinkSync(req.file.path);
} catch (cleanupError) {
} catch {
apiLogger.warn("Failed to clean up uploaded file after error", {
operation: "file_cleanup_error",
filePath: req.file.path,
@@ -1339,11 +1334,7 @@ app.post(
app.post("/database/export/preview", authenticateJWT, async (req, res) => {
try {
const userId = (req as any).userId;
const {
format = "encrypted",
scope = "user_data",
includeCredentials = true,
} = req.body;
const { scope = "user_data", includeCredentials = true } = req.body;
const exportData = await UserDataExport.exportUserData(userId, {
format: "encrypted",
@@ -1420,7 +1411,8 @@ app.use(
err: unknown,
req: express.Request,
res: express.Response,
next: express.NextFunction,
// eslint-disable-next-line @typescript-eslint/no-unused-vars
_next: express.NextFunction,
) => {
apiLogger.error("Unhandled error in request", err, {
operation: "error_handler",
@@ -1433,7 +1425,6 @@ app.use(
);
const HTTP_PORT = 30001;
const HTTPS_PORT = process.env.SSL_PORT || 8443;
async function initializeSecurity() {
try {
@@ -1446,13 +1437,6 @@ async function initializeSecurity() {
if (!isValid) {
throw new Error("Security system validation failed");
}
const securityStatus = {
initialized: true,
system: { hasSecret: true, isValid: true },
activeSessions: {},
activeSessionCount: 0,
};
} catch (error) {
databaseLogger.error("Failed to initialize security system", error, {
operation: "security_init_error",
@@ -1484,13 +1468,17 @@ app.get(
if (status.hasUnencryptedDb) {
try {
unencryptedSize = fs.statSync(dbPath).size;
} catch (error) {}
} catch {
// Ignore file access errors
}
}
if (status.hasEncryptedDb) {
try {
encryptedSize = fs.statSync(encryptedDbPath).size;
} catch (error) {}
} catch {
// Ignore file access errors
}
}
res.json({

View File

@@ -23,7 +23,7 @@ const enableFileEncryption = process.env.DB_FILE_ENCRYPTION !== "false";
const dbPath = path.join(dataDir, "db.sqlite");
const encryptedDbPath = `${dbPath}.encrypted`;
let actualDbPath = ":memory:";
const actualDbPath = ":memory:";
let memoryDatabase: Database.Database;
let isNewDatabase = false;
let sqlite: Database.Database;
@@ -31,7 +31,8 @@ let sqlite: Database.Database;
async function initializeDatabaseAsync(): Promise<void> {
const systemCrypto = SystemCrypto.getInstance();
const dbKey = await systemCrypto.getDatabaseKey();
// Ensure database key is initialized
await systemCrypto.getDatabaseKey();
if (enableFileEncryption) {
try {
if (DatabaseFileEncryption.isEncryptedDatabaseFile(encryptedDbPath)) {
@@ -288,7 +289,7 @@ const addColumnIfNotExists = (
FROM ${table} LIMIT 1`,
)
.get();
} catch (e) {
} catch {
try {
sqlite.exec(`ALTER TABLE ${table}
ADD COLUMN ${column} ${definition};`);
@@ -487,21 +488,29 @@ async function cleanupDatabase() {
for (const file of files) {
try {
fs.unlinkSync(path.join(tempDir, file));
} catch {}
} catch {
// Ignore cleanup errors
}
}
try {
fs.rmdirSync(tempDir);
} catch {}
} catch {
// Ignore cleanup errors
}
}
} catch (error) {}
} catch {
// Ignore cleanup errors
}
}
process.on("exit", () => {
if (sqlite) {
try {
sqlite.close();
} catch {}
} catch {
// Ignore close errors on exit
}
}
});

View File

@@ -170,7 +170,7 @@ router.post("/dismiss", authenticateJWT, async (req, res) => {
return res.status(409).json({ error: "Alert already dismissed" });
}
const result = await db.insert(dismissedAlerts).values({
await db.insert(dismissedAlerts).values({
userId,
alertId,
});

View File

@@ -2,15 +2,13 @@ import express from "express";
import { db } from "../db/index.js";
import { sshCredentials, sshCredentialUsage, sshData } from "../db/schema.js";
import { eq, and, desc, sql } from "drizzle-orm";
import type { Request, Response, NextFunction } from "express";
import jwt from "jsonwebtoken";
import type { Request, Response } from "express";
import { authLogger } from "../../utils/logger.js";
import { SimpleDBOps } from "../../utils/simple-db-ops.js";
import { AuthManager } from "../../utils/auth-manager.js";
import {
parseSSHKey,
parsePublicKey,
detectKeyType,
validateKeyPair,
} from "../../utils/ssh-key-utils.js";
import crypto from "crypto";
@@ -970,7 +968,7 @@ router.post(
try {
let privateKeyObj;
let parseAttempts = [];
const parseAttempts = [];
try {
privateKeyObj = crypto.createPrivateKey({
@@ -1093,7 +1091,9 @@ router.post(
finalPublicKey = `${keyType} ${base64Data}`;
formatType = "ssh";
}
} catch (sshError) {}
} catch {
// Ignore validation errors
}
const response = {
success: true,
@@ -1119,13 +1119,13 @@ router.post(
async function deploySSHKeyToHost(
hostConfig: any,
publicKey: string,
credentialData: any,
// eslint-disable-next-line @typescript-eslint/no-unused-vars
_credentialData: any,
): Promise<{ success: boolean; message?: string; error?: string }> {
return new Promise((resolve) => {
const conn = new Client();
let connectionTimeout: NodeJS.Timeout;
connectionTimeout = setTimeout(() => {
const connectionTimeout = setTimeout(() => {
conn.destroy();
resolve({ success: false, error: "Connection timeout" });
}, 120000);
@@ -1158,7 +1158,9 @@ async function deploySSHKeyToHost(
}
});
stream.on("data", (data) => {});
stream.on("data", () => {
// Ignore output
});
},
);
});
@@ -1175,7 +1177,9 @@ async function deploySSHKeyToHost(
if (parsed.data) {
actualPublicKey = parsed.data;
}
} catch (e) {}
} catch {
// Ignore parse errors
}
const keyParts = actualPublicKey.trim().split(" ");
if (keyParts.length < 2) {
@@ -1202,7 +1206,7 @@ async function deploySSHKeyToHost(
output += data.toString();
});
stream.on("close", (code) => {
stream.on("close", () => {
clearTimeout(checkTimeout);
const exists = output.trim() === "0";
resolveCheck(exists);
@@ -1229,7 +1233,9 @@ async function deploySSHKeyToHost(
if (parsed.data) {
actualPublicKey = parsed.data;
}
} catch (e) {}
} catch {
// Ignore parse errors
}
const escapedKey = actualPublicKey
.replace(/\\/g, "\\\\")
@@ -1269,7 +1275,9 @@ async function deploySSHKeyToHost(
if (parsed.data) {
actualPublicKey = parsed.data;
}
} catch (e) {}
} catch {
// Ignore parse errors
}
const keyParts = actualPublicKey.trim().split(" ");
if (keyParts.length < 2) {
@@ -1295,7 +1303,7 @@ async function deploySSHKeyToHost(
output += data.toString();
});
stream.on("close", (code) => {
stream.on("close", () => {
clearTimeout(verifyTimeout);
const verified = output.trim() === "0";
resolveVerify(verified);
@@ -1521,7 +1529,7 @@ router.post(
const hostData = targetHost[0];
let hostConfig = {
const hostConfig = {
ip: hostData.ip,
port: hostData.port,
username: hostData.username,
@@ -1571,7 +1579,7 @@ router.post(
error: "Host credential not found",
});
}
} catch (error) {
} catch {
return res.status(500).json({
success: false,
error: "Failed to resolve host credentials",

View File

@@ -9,8 +9,7 @@ import {
fileManagerShortcuts,
} from "../db/schema.js";
import { eq, and, desc, isNotNull, or } from "drizzle-orm";
import type { Request, Response, NextFunction } from "express";
import jwt from "jsonwebtoken";
import type { Request, Response } from "express";
import multer from "multer";
import { sshLogger } from "../../utils/logger.js";
import { SimpleDBOps } from "../../utils/simple-db-ops.js";
@@ -816,7 +815,7 @@ router.delete(
),
);
const result = await db
await db
.delete(sshData)
.where(and(eq(sshData.id, numericHostId), eq(sshData.userId, userId)));
@@ -943,7 +942,7 @@ router.delete(
authenticateJWT,
async (req: Request, res: Response) => {
const userId = (req as any).userId;
const { hostId, path, name } = req.body;
const { hostId, path } = req.body;
if (!isNonEmptyString(userId) || !hostId || !path) {
sshLogger.warn("Invalid data for recent file deletion");
@@ -1063,7 +1062,7 @@ router.delete(
authenticateJWT,
async (req: Request, res: Response) => {
const userId = (req as any).userId;
const { hostId, path, name } = req.body;
const { hostId, path } = req.body;
if (!isNonEmptyString(userId) || !hostId || !path) {
sshLogger.warn("Invalid data for pinned file deletion");
@@ -1183,7 +1182,7 @@ router.delete(
authenticateJWT,
async (req: Request, res: Response) => {
const userId = (req as any).userId;
const { hostId, path, name } = req.body;
const { hostId, path } = req.body;
if (!isNonEmptyString(userId) || !hostId || !path) {
sshLogger.warn("Invalid data for shortcut deletion");
@@ -1573,7 +1572,7 @@ router.post(
}
}
const updateResult = await db
await db
.update(sshData)
.set({
autostartPassword: decryptedConfig.password || null,
@@ -1630,7 +1629,7 @@ router.delete(
}
try {
const result = await db
await db
.update(sshData)
.set({
autostartPassword: null,

View File

@@ -18,7 +18,6 @@ import QRCode from "qrcode";
import type { Request, Response } from "express";
import { authLogger } from "../../utils/logger.js";
import { AuthManager } from "../../utils/auth-manager.js";
import { UserCrypto } from "../../utils/user-crypto.js";
import { DataCrypto } from "../../utils/data-crypto.js";
import { LazyFieldEncryption } from "../../utils/lazy-field-encryption.js";
@@ -29,94 +28,89 @@ async function verifyOIDCToken(
issuerUrl: string,
clientId: string,
): Promise<any> {
const normalizedIssuerUrl = issuerUrl.endsWith("/")
? issuerUrl.slice(0, -1)
: issuerUrl;
const possibleIssuers = [
issuerUrl,
normalizedIssuerUrl,
issuerUrl.replace(/\/application\/o\/[^/]+$/, ""),
normalizedIssuerUrl.replace(/\/application\/o\/[^/]+$/, ""),
];
const jwksUrls = [
`${normalizedIssuerUrl}/.well-known/jwks.json`,
`${normalizedIssuerUrl}/jwks/`,
`${normalizedIssuerUrl.replace(/\/application\/o\/[^/]+$/, "")}/.well-known/jwks.json`,
];
try {
const normalizedIssuerUrl = issuerUrl.endsWith("/")
? issuerUrl.slice(0, -1)
: issuerUrl;
const possibleIssuers = [
issuerUrl,
normalizedIssuerUrl,
issuerUrl.replace(/\/application\/o\/[^\/]+$/, ""),
normalizedIssuerUrl.replace(/\/application\/o\/[^\/]+$/, ""),
];
const jwksUrls = [
`${normalizedIssuerUrl}/.well-known/jwks.json`,
`${normalizedIssuerUrl}/jwks/`,
`${normalizedIssuerUrl.replace(/\/application\/o\/[^\/]+$/, "")}/.well-known/jwks.json`,
];
try {
const discoveryUrl = `${normalizedIssuerUrl}/.well-known/openid-configuration`;
const discoveryResponse = await fetch(discoveryUrl);
if (discoveryResponse.ok) {
const discovery = (await discoveryResponse.json()) as any;
if (discovery.jwks_uri) {
jwksUrls.unshift(discovery.jwks_uri);
}
}
} catch (discoveryError) {
authLogger.error(`OIDC discovery failed: ${discoveryError}`);
}
let jwks: any = null;
let jwksUrl: string | null = null;
for (const url of jwksUrls) {
try {
const response = await fetch(url);
if (response.ok) {
const jwksData = (await response.json()) as any;
if (jwksData && jwksData.keys && Array.isArray(jwksData.keys)) {
jwks = jwksData;
jwksUrl = url;
break;
} else {
authLogger.error(
`Invalid JWKS structure from ${url}: ${JSON.stringify(jwksData)}`,
);
}
} else {
}
} catch (error) {
continue;
const discoveryUrl = `${normalizedIssuerUrl}/.well-known/openid-configuration`;
const discoveryResponse = await fetch(discoveryUrl);
if (discoveryResponse.ok) {
const discovery = (await discoveryResponse.json()) as any;
if (discovery.jwks_uri) {
jwksUrls.unshift(discovery.jwks_uri);
}
}
if (!jwks) {
throw new Error("Failed to fetch JWKS from any URL");
}
if (!jwks.keys || !Array.isArray(jwks.keys)) {
throw new Error(
`Invalid JWKS response structure. Expected 'keys' array, got: ${JSON.stringify(jwks)}`,
);
}
const header = JSON.parse(
Buffer.from(idToken.split(".")[0], "base64").toString(),
);
const keyId = header.kid;
const publicKey = jwks.keys.find((key: any) => key.kid === keyId);
if (!publicKey) {
throw new Error(
`No matching public key found for key ID: ${keyId}. Available keys: ${jwks.keys.map((k: any) => k.kid).join(", ")}`,
);
}
const { importJWK, jwtVerify } = await import("jose");
const key = await importJWK(publicKey);
const { payload } = await jwtVerify(idToken, key, {
issuer: possibleIssuers,
audience: clientId,
});
return payload;
} catch (error) {
throw error;
} catch (discoveryError) {
authLogger.error(`OIDC discovery failed: ${discoveryError}`);
}
let jwks: any = null;
for (const url of jwksUrls) {
try {
const response = await fetch(url);
if (response.ok) {
const jwksData = (await response.json()) as any;
if (jwksData && jwksData.keys && Array.isArray(jwksData.keys)) {
jwks = jwksData;
break;
} else {
authLogger.error(
`Invalid JWKS structure from ${url}: ${JSON.stringify(jwksData)}`,
);
}
} else {
// Non-200 response
}
} catch {
continue;
}
}
if (!jwks) {
throw new Error("Failed to fetch JWKS from any URL");
}
if (!jwks.keys || !Array.isArray(jwks.keys)) {
throw new Error(
`Invalid JWKS response structure. Expected 'keys' array, got: ${JSON.stringify(jwks)}`,
);
}
const header = JSON.parse(
Buffer.from(idToken.split(".")[0], "base64").toString(),
);
const keyId = header.kid;
const publicKey = jwks.keys.find((key: any) => key.kid === keyId);
if (!publicKey) {
throw new Error(
`No matching public key found for key ID: ${keyId}. Available keys: ${jwks.keys.map((k: any) => k.kid).join(", ")}`,
);
}
const { importJWK, jwtVerify } = await import("jose");
const key = await importJWK(publicKey);
const { payload } = await jwtVerify(idToken, key, {
issuer: possibleIssuers,
audience: clientId,
});
return payload;
}
const router = express.Router();
@@ -125,15 +119,8 @@ function isNonEmptyString(val: any): val is string {
return typeof val === "string" && val.trim().length > 0;
}
interface JWTPayload {
userId: string;
iat?: number;
exp?: number;
}
const authenticateJWT = authManager.createAuthMiddleware();
const requireAdmin = authManager.createAdminMiddleware();
const requireDataAccess = authManager.createDataAccessMiddleware();
// Route: Create traditional user (username/password)
// POST /users/create
@@ -451,7 +438,7 @@ router.get("/oidc-config", async (req, res) => {
} else {
config.client_secret = "[ENCRYPTED - PASSWORD REQUIRED]";
}
} catch (decryptError) {
} catch {
authLogger.warn("Failed to decrypt OIDC config for admin", {
operation: "oidc_config_decrypt_failed",
userId,
@@ -504,7 +491,7 @@ router.get("/oidc/authorize", async (req, res) => {
let origin =
req.get("Origin") ||
req.get("Referer")?.replace(/\/[^\/]*$/, "") ||
req.get("Referer")?.replace(/\/[^/]*$/, "") ||
"http://localhost:5173";
if (origin.includes("localhost")) {
@@ -606,15 +593,12 @@ router.get("/oidc/callback", async (req, res) => {
const tokenData = (await tokenResponse.json()) as any;
let userInfo: any = null;
let userInfoUrls: string[] = [];
const userInfoUrls: string[] = [];
const normalizedIssuerUrl = config.issuer_url.endsWith("/")
? config.issuer_url.slice(0, -1)
: config.issuer_url;
const baseUrl = normalizedIssuerUrl.replace(
/\/application\/o\/[^\/]+$/,
"",
);
const baseUrl = normalizedIssuerUrl.replace(/\/application\/o\/[^/]+$/, "");
try {
const discoveryUrl = `${normalizedIssuerUrl}/.well-known/openid-configuration`;
@@ -651,7 +635,8 @@ router.get("/oidc/callback", async (req, res) => {
config.issuer_url,
config.client_id,
);
} catch (error) {
} catch {
// Fallback to manual decoding
try {
const parts = tokenData.id_token.split(".");
if (parts.length === 3) {
@@ -911,7 +896,7 @@ router.post("/login", async (req, res) => {
if (kekSalt.length === 0) {
await authManager.registerUser(userRecord.id, password);
}
} catch (setupError) {
} catch {
// Continue if setup fails - authenticateUser will handle it
}
@@ -1615,7 +1600,7 @@ router.post("/totp/verify-login", async (req, res) => {
backupCodes = userRecord.totp_backup_codes
? JSON.parse(userRecord.totp_backup_codes)
: [];
} catch (parseError) {
} catch {
backupCodes = [];
}

View File

@@ -110,7 +110,9 @@ function cleanupSession(sessionId: string) {
if (session) {
try {
session.client.end();
} catch {}
} catch {
// Ignore connection close errors
}
clearTimeout(session.timeout);
delete sshSessions[sessionId];
}
@@ -598,13 +600,12 @@ app.get("/ssh/file_manager/ssh/listFiles", (req, res) => {
const parts = line.split(/\s+/);
if (parts.length >= 9) {
const permissions = parts[0];
const linkCount = parts[1];
const owner = parts[2];
const group = parts[3];
const size = parseInt(parts[4], 10);
let dateStr = "";
let nameStartIndex = 8;
const nameStartIndex = 8;
if (parts[5] && parts[6] && parts[7]) {
dateStr = `${parts[5]} ${parts[6]} ${parts[7]}`;
@@ -837,7 +838,7 @@ app.get("/ssh/file_manager/ssh/readFile", (req, res) => {
});
app.post("/ssh/file_manager/ssh/writeFile", async (req, res) => {
const { sessionId, path: filePath, content, hostId, userId } = req.body;
const { sessionId, path: filePath, content } = req.body;
const sshConn = sshSessions[sessionId];
if (!sessionId) {
@@ -1024,14 +1025,7 @@ app.post("/ssh/file_manager/ssh/writeFile", async (req, res) => {
});
app.post("/ssh/file_manager/ssh/uploadFile", async (req, res) => {
const {
sessionId,
path: filePath,
content,
fileName,
hostId,
userId,
} = req.body;
const { sessionId, path: filePath, content, fileName } = req.body;
const sshConn = sshSessions[sessionId];
if (!sessionId) {
@@ -1165,8 +1159,6 @@ app.post("/ssh/file_manager/ssh/uploadFile", async (req, res) => {
}
if (chunks.length === 1) {
const tempFile = `/tmp/upload_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
const escapedTempFile = tempFile.replace(/'/g, "'\"'\"'");
const escapedPath = fullPath.replace(/'/g, "'\"'\"'");
const writeCommand = `echo '${chunks[0]}' | base64 -d > '${escapedPath}' && echo "SUCCESS"`;
@@ -1231,13 +1223,11 @@ app.post("/ssh/file_manager/ssh/uploadFile", async (req, res) => {
});
});
} else {
const tempFile = `/tmp/upload_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
const escapedTempFile = tempFile.replace(/'/g, "'\"'\"'");
const escapedPath = fullPath.replace(/'/g, "'\"'\"'");
let writeCommand = `> '${escapedPath}'`;
chunks.forEach((chunk, index) => {
chunks.forEach((chunk) => {
writeCommand += ` && echo '${chunk}' | base64 -d >> '${escapedPath}'`;
});
@@ -1320,14 +1310,7 @@ app.post("/ssh/file_manager/ssh/uploadFile", async (req, res) => {
});
app.post("/ssh/file_manager/ssh/createFile", async (req, res) => {
const {
sessionId,
path: filePath,
fileName,
content = "",
hostId,
userId,
} = req.body;
const { sessionId, path: filePath, fileName } = req.body;
const sshConn = sshSessions[sessionId];
if (!sessionId) {
@@ -1428,7 +1411,7 @@ app.post("/ssh/file_manager/ssh/createFile", async (req, res) => {
});
app.post("/ssh/file_manager/ssh/createFolder", async (req, res) => {
const { sessionId, path: folderPath, folderName, hostId, userId } = req.body;
const { sessionId, path: folderPath, folderName } = req.body;
const sshConn = sshSessions[sessionId];
if (!sessionId) {
@@ -1529,7 +1512,7 @@ app.post("/ssh/file_manager/ssh/createFolder", async (req, res) => {
});
app.delete("/ssh/file_manager/ssh/deleteItem", async (req, res) => {
const { sessionId, path: itemPath, isDirectory, hostId, userId } = req.body;
const { sessionId, path: itemPath, isDirectory } = req.body;
const sshConn = sshSessions[sessionId];
if (!sessionId) {
@@ -1631,7 +1614,7 @@ app.delete("/ssh/file_manager/ssh/deleteItem", async (req, res) => {
});
app.put("/ssh/file_manager/ssh/renameItem", async (req, res) => {
const { sessionId, oldPath, newName, hostId, userId } = req.body;
const { sessionId, oldPath, newName } = req.body;
const sshConn = sshSessions[sessionId];
if (!sessionId) {
@@ -1739,7 +1722,7 @@ app.put("/ssh/file_manager/ssh/renameItem", async (req, res) => {
});
app.put("/ssh/file_manager/ssh/moveItem", async (req, res) => {
const { sessionId, oldPath, newPath, hostId, userId } = req.body;
const { sessionId, oldPath, newPath } = req.body;
const sshConn = sshSessions[sessionId];
if (!sessionId) {
@@ -2128,7 +2111,7 @@ app.post("/ssh/file_manager/ssh/copyItem", async (req, res) => {
});
app.post("/ssh/file_manager/ssh/executeFile", async (req, res) => {
const { sessionId, filePath, hostId, userId } = req.body;
const { sessionId, filePath } = req.body;
const sshConn = sshSessions[sessionId];
if (!sshConn || !sshConn.isConnected) {
@@ -2165,7 +2148,7 @@ app.post("/ssh/file_manager/ssh/executeFile", async (req, res) => {
checkResult += data.toString();
});
checkStream.on("close", (code) => {
checkStream.on("close", () => {
if (!checkResult.includes("EXECUTABLE")) {
return res.status(400).json({ error: "File is not executable" });
}

View File

@@ -60,7 +60,7 @@ class SSHConnectionPool {
return client;
}
return new Promise((resolve, reject) => {
return new Promise((resolve, _reject) => {
const checkAvailable = () => {
const available = connections.find((conn) => !conn.inUse);
if (available) {
@@ -157,7 +157,9 @@ class SSHConnectionPool {
if (!conn.inUse && now - conn.lastUsed > maxAge) {
try {
conn.client.end();
} catch {}
} catch {
// Ignore errors when closing stale connections
}
return false;
}
return true;
@@ -177,7 +179,9 @@ class SSHConnectionPool {
for (const conn of connections) {
try {
conn.client.end();
} catch {}
} catch {
// Ignore errors when closing connections during cleanup
}
}
}
this.connections.clear();
@@ -215,7 +219,9 @@ class RequestQueue {
if (request) {
try {
await request();
} catch (error) {}
} catch {
// Ignore errors from queued requests
}
}
}
@@ -871,7 +877,9 @@ function tcpPing(
settled = true;
try {
socket.destroy();
} catch {}
} catch {
// Ignore errors when destroying socket
}
resolve(result);
};

View File

@@ -217,7 +217,9 @@ function cleanupTunnelResources(
if (verification?.timeout) clearTimeout(verification.timeout);
try {
verification?.conn.end();
} catch (e) {}
} catch {
// Ignore errors
}
tunnelVerifications.delete(tunnelName);
}
@@ -282,7 +284,9 @@ function handleDisconnect(
const verification = tunnelVerifications.get(tunnelName);
if (verification?.timeout) clearTimeout(verification.timeout);
verification?.conn.end();
} catch (e) {}
} catch {
// Ignore errors
}
tunnelVerifications.delete(tunnelName);
}
@@ -518,9 +522,7 @@ async function connectSSHTunnel(
keyType: credential.key_type || credential.keyType,
authMethod: credential.auth_type || credential.authType,
};
} else {
}
} else {
}
} catch (error) {
tunnelLogger.warn("Failed to resolve source credentials from database", {
@@ -605,7 +607,6 @@ async function connectSSHTunnel(
credentialId: tunnelConfig.endpointCredentialId,
});
}
} else {
}
} catch (error) {
tunnelLogger.warn(
@@ -631,7 +632,9 @@ async function connectSSHTunnel(
try {
conn.end();
} catch (e) {}
} catch {
// Ignore errors
}
activeTunnels.delete(tunnelName);
@@ -771,7 +774,9 @@ async function connectSSHTunnel(
const verification = tunnelVerifications.get(tunnelName);
if (verification?.timeout) clearTimeout(verification.timeout);
verification?.conn.end();
} catch (e) {}
} catch {
// Ignore errors
}
tunnelVerifications.delete(tunnelName);
}
@@ -823,12 +828,12 @@ async function connectSSHTunnel(
});
stream.stdout?.on("data", (data: Buffer) => {
const output = data.toString().trim();
if (output) {
}
// Silently consume stdout data
});
stream.on("error", (err: Error) => {});
stream.on("error", () => {
// Silently consume stream errors
});
stream.stderr.on("data", (data) => {
const errorMsg = data.toString().trim();
@@ -1034,7 +1039,6 @@ async function killRemoteTunnelByMarker(
authMethod: credential.auth_type || credential.authType,
};
}
} else {
}
} catch (error) {
tunnelLogger.warn("Failed to resolve source credentials for cleanup", {
@@ -1122,7 +1126,7 @@ async function killRemoteTunnelByMarker(
conn.on("ready", () => {
const checkCmd = `ps aux | grep -E '(${tunnelMarker}|ssh.*-R.*${tunnelConfig.endpointPort}:localhost:${tunnelConfig.sourcePort}.*${tunnelConfig.endpointUsername}@${tunnelConfig.endpointIP}|sshpass.*ssh.*-R.*${tunnelConfig.endpointPort})' | grep -v grep`;
conn.exec(checkCmd, (err, stream) => {
conn.exec(checkCmd, (_err, stream) => {
let foundProcesses = false;
stream.on("data", (data) => {
@@ -1150,7 +1154,7 @@ async function killRemoteTunnelByMarker(
function executeNextKillCommand() {
if (commandIndex >= killCmds.length) {
conn.exec(checkCmd, (err, verifyStream) => {
conn.exec(checkCmd, (_err, verifyStream) => {
let stillRunning = false;
verifyStream.on("data", (data) => {
@@ -1183,18 +1187,15 @@ async function killRemoteTunnelByMarker(
tunnelLogger.warn(
`Kill command ${commandIndex + 1} failed for '${tunnelName}': ${err.message}`,
);
} else {
}
stream.on("close", (code) => {
stream.on("close", () => {
commandIndex++;
executeNextKillCommand();
});
stream.on("data", (data) => {
const output = data.toString().trim();
if (output) {
}
stream.on("data", () => {
// Silently consume stream data
});
stream.stderr.on("data", (data) => {

View File

@@ -21,7 +21,9 @@ import { systemLogger, versionLogger } from "./utils/logger.js";
if (persistentConfig.parsed) {
Object.assign(process.env, persistentConfig.parsed);
}
} catch {}
} catch {
// Ignore errors if .env file doesn't exist
}
let version = "unknown";

View File

@@ -108,7 +108,6 @@ class AuthManager {
if (migrationResult.migrated) {
await saveMemoryDatabaseToFile();
} else {
}
} catch (error) {
databaseLogger.error("Lazy encryption migration failed", error, {

View File

@@ -1,7 +1,6 @@
import { execSync } from "child_process";
import { promises as fs } from "fs";
import path from "path";
import crypto from "crypto";
import { systemLogger } from "./logger.js";
export class AutoSSLSetup {
@@ -234,7 +233,9 @@ IP.3 = 0.0.0.0
let envContent = "";
try {
envContent = await fs.readFile(this.ENV_FILE, "utf8");
} catch {}
} catch {
// File doesn't exist yet, will create with SSL config
}
let updatedContent = envContent;
let hasChanges = false;

View File

@@ -55,7 +55,6 @@ export class DatabaseMigration {
if (hasEncryptedDb && hasUnencryptedDb) {
const unencryptedSize = fs.statSync(this.unencryptedDbPath).size;
const encryptedSize = fs.statSync(this.encryptedDbPath).size;
if (unencryptedSize === 0) {
needsMigration = false;
@@ -168,9 +167,6 @@ export class DatabaseMigration {
return false;
}
let totalOriginalRows = 0;
let totalMemoryRows = 0;
for (const table of originalTables) {
const originalCount = originalDb
.prepare(`SELECT COUNT(*) as count FROM ${table.name}`)
@@ -179,9 +175,6 @@ export class DatabaseMigration {
.prepare(`SELECT COUNT(*) as count FROM ${table.name}`)
.get() as { count: number };
totalOriginalRows += originalCount.count;
totalMemoryRows += memoryCount.count;
if (originalCount.count !== memoryCount.count) {
databaseLogger.error(
"Row count mismatch for table during migration verification",

View File

@@ -21,8 +21,9 @@ class FieldCrypto {
"totp_secret",
"totp_backup_codes",
"oidc_identifier",
"oidcIdentifier",
]),
ssh_data: new Set(["password", "key", "key_password"]),
ssh_data: new Set(["password", "key", "key_password", "keyPassword"]),
ssh_credentials: new Set([
"password",
"private_key",
@@ -47,7 +48,11 @@ class FieldCrypto {
);
const iv = crypto.randomBytes(this.IV_LENGTH);
const cipher = crypto.createCipheriv(this.ALGORITHM, fieldKey, iv) as any;
const cipher = crypto.createCipheriv(
this.ALGORITHM,
fieldKey,
iv,
) as crypto.CipherGCM;
let encrypted = cipher.update(plaintext, "utf8", "hex");
encrypted += cipher.final("hex");
@@ -89,7 +94,7 @@ class FieldCrypto {
this.ALGORITHM,
fieldKey,
Buffer.from(encrypted.iv, "hex"),
) as any;
) as crypto.DecipherGCM;
decipher.setAuthTag(Buffer.from(encrypted.tag, "hex"));
let decrypted = decipher.update(encrypted.data, "hex", "utf8");

View File

@@ -39,7 +39,7 @@ export class LazyFieldEncryption {
return false;
}
return true;
} catch (jsonError) {
} catch {
return true;
}
}
@@ -74,7 +74,9 @@ export class LazyFieldEncryption {
legacyFieldName,
);
return decrypted;
} catch (legacyError) {}
} catch {
// Ignore legacy format errors
}
}
const sensitiveFields = [
@@ -145,7 +147,7 @@ export class LazyFieldEncryption {
wasPlaintext: false,
wasLegacyEncryption: false,
};
} catch (error) {
} catch {
const legacyFieldName = this.LEGACY_FIELD_NAME_MAP[fieldName];
if (legacyFieldName) {
try {
@@ -166,7 +168,9 @@ export class LazyFieldEncryption {
wasPlaintext: false,
wasLegacyEncryption: true,
};
} catch (legacyError) {}
} catch {
// Ignore legacy format errors
}
}
return {
encrypted: fieldValue,
@@ -253,7 +257,7 @@ export class LazyFieldEncryption {
try {
FieldCrypto.decryptField(fieldValue, userKEK, recordId, fieldName);
return false;
} catch (error) {
} catch {
const legacyFieldName = this.LEGACY_FIELD_NAME_MAP[fieldName];
if (legacyFieldName) {
try {
@@ -264,7 +268,7 @@ export class LazyFieldEncryption {
legacyFieldName,
);
return true;
} catch (legacyError) {
} catch {
return false;
}
}

View File

@@ -127,7 +127,7 @@ class SimpleDBOps {
table: SQLiteTable<any>,
tableName: TableName,
where: any,
userId: string,
_userId: string,
): Promise<any[]> {
const result = await getDb().delete(table).where(where).returning();
@@ -146,7 +146,7 @@ class SimpleDBOps {
static async selectEncrypted(
query: any,
tableName: TableName,
_tableName: TableName,
): Promise<any[]> {
const results = await query;

View File

@@ -84,7 +84,9 @@ function detectKeyTypeFromContent(keyContent: string): string {
} else if (decodedString.includes("1.3.101.112")) {
return "ssh-ed25519";
}
} catch (error) {}
} catch {
// Cannot decode key, fallback to length-based detection
}
if (content.length < 800) {
return "ssh-ed25519";
@@ -140,7 +142,9 @@ function detectPublicKeyTypeFromContent(publicKeyContent: string): string {
} else if (decodedString.includes("1.3.101.112")) {
return "ssh-ed25519";
}
} catch (error) {}
} catch {
// Cannot decode key, fallback to length-based detection
}
if (content.length < 400) {
return "ssh-ed25519";
@@ -242,7 +246,9 @@ export function parseSSHKey(
useSSH2 = true;
}
} catch (error) {}
} catch {
// SSH2 parsing failed, will use fallback method
}
}
if (!useSSH2) {
@@ -268,7 +274,9 @@ export function parseSSHKey(
success: true,
};
}
} catch (fallbackError) {}
} catch {
// Fallback parsing also failed
}
return {
privateKey: privateKeyData,

View File

@@ -37,7 +37,9 @@ class SystemCrypto {
process.env.JWT_SECRET = jwtMatch[1];
return;
}
} catch {}
} catch {
// Ignore file read errors, will generate new secret
}
await this.generateAndGuideUser();
} catch (error) {
@@ -74,7 +76,9 @@ class SystemCrypto {
process.env.DATABASE_KEY = dbKeyMatch[1];
return;
}
} catch {}
} catch {
// Ignore file read errors, will generate new key
}
await this.generateAndGuideDatabaseKey();
} catch (error) {
@@ -111,7 +115,9 @@ class SystemCrypto {
process.env.INTERNAL_AUTH_TOKEN = tokenMatch[1];
return;
}
} catch {}
} catch {
// Ignore file read errors, will generate new token
}
await this.generateAndGuideInternalAuthToken();
} catch (error) {

View File

@@ -1,6 +1,6 @@
import crypto from "crypto";
import { getDb } from "../database/db/index.js";
import { settings, users } from "../database/db/schema.js";
import { settings } from "../database/db/schema.js";
import { eq } from "drizzle-orm";
import { databaseLogger } from "./logger.js";

View File

@@ -12,7 +12,6 @@ import { eq, and } from "drizzle-orm";
import { DataCrypto } from "./data-crypto.js";
import { UserDataExport, type UserExportData } from "./user-data-export.js";
import { databaseLogger } from "./logger.js";
import { nanoid } from "nanoid";
interface ImportOptions {
replaceExisting?: boolean;

View File

@@ -79,7 +79,8 @@ export function CredentialEditor({
].sort() as string[];
setFolders(uniqueFolders);
} catch (error) {
} catch {
// Failed to load credentials
} finally {
setLoading(false);
}
@@ -636,10 +637,6 @@ export function CredentialEditor({
form.setValue("key", null);
form.setValue("keyPassword", "");
form.setValue("keyType", "auto");
if (newAuthType === "password") {
} else if (newAuthType === "key") {
}
}}
className="flex-1 flex flex-col h-full min-h-0"
>

View File

@@ -719,28 +719,24 @@ function FileManagerContent({ initialHost, onClose }: FileManagerProps) {
}
try {
let currentSessionId = sshSessionId;
try {
const status = await getSSHStatus(currentSessionId);
if (!status.connected) {
const result = await connectSSH(currentSessionId, {
hostId: currentHost.id,
host: currentHost.ip,
port: currentHost.port,
username: currentHost.username,
authType: currentHost.authType,
password: currentHost.password,
key: currentHost.key,
keyPassword: currentHost.keyPassword,
credentialId: currentHost.credentialId,
});
const currentSessionId = sshSessionId;
const status = await getSSHStatus(currentSessionId);
if (!status.connected) {
const result = await connectSSH(currentSessionId, {
hostId: currentHost.id,
host: currentHost.ip,
port: currentHost.port,
username: currentHost.username,
authType: currentHost.authType,
password: currentHost.password,
key: currentHost.key,
keyPassword: currentHost.keyPassword,
credentialId: currentHost.credentialId,
});
if (!result.success) {
throw new Error(t("fileManager.failedToReconnectSSH"));
}
if (!result.success) {
throw new Error(t("fileManager.failedToReconnectSSH"));
}
} catch (sessionErr) {
throw sessionErr;
}
const symlinkInfo = await identifySSHSymlink(currentSessionId, file.path);

View File

@@ -327,7 +327,6 @@ export function FileManagerGrid({
dragState.files[0].type === "file"
) {
onFileDiff?.(dragState.files[0], targetFile);
} else {
}
setDragState({ type: "none", files: [], counter: 0 });
@@ -458,8 +457,6 @@ export function FileManagerGrid({
type: "external",
counter: prev.counter + 1,
}));
if (e.dataTransfer.items && e.dataTransfer.items.length > 0) {
}
}
},
[dragState.type],

View File

@@ -62,22 +62,18 @@ export function DiffViewer({
});
}
} catch (error) {
try {
await connectSSH(sshSessionId, {
hostId: sshHost.id,
ip: sshHost.ip,
port: sshHost.port,
username: sshHost.username,
password: sshHost.password,
sshKey: sshHost.key,
keyPassword: sshHost.keyPassword,
authType: sshHost.authType,
credentialId: sshHost.credentialId,
userId: sshHost.userId,
});
} catch (reconnectError) {
throw reconnectError;
}
await connectSSH(sshSessionId, {
hostId: sshHost.id,
ip: sshHost.ip,
port: sshHost.port,
username: sshHost.username,
password: sshHost.password,
sshKey: sshHost.key,
keyPassword: sshHost.keyPassword,
authType: sshHost.authType,
credentialId: sshHost.credentialId,
userId: sshHost.userId,
});
}
};

View File

@@ -118,7 +118,8 @@ export function HostManagerEditor({
setFolders(uniqueFolders);
setSshConfigurations(uniqueConfigurations);
} catch (error) {
} catch {
// Failed to load hosts data
} finally {
setLoading(false);
}
@@ -152,7 +153,8 @@ export function HostManagerEditor({
setFolders(uniqueFolders);
setSshConfigurations(uniqueConfigurations);
} catch (error) {
} catch {
// Failed to reload hosts after credential change
} finally {
setLoading(false);
}

View File

@@ -102,7 +102,9 @@ export const Terminal = forwardRef<any, SSHTerminalProps>(function SSHTerminal(
if (terminal && typeof (terminal as any).refresh === "function") {
(terminal as any).refresh(0, terminal.rows - 1);
}
} catch (_) {}
} catch {
// Ignore terminal refresh errors
}
}
function handleTotpSubmit(code: string) {
@@ -183,7 +185,9 @@ export const Terminal = forwardRef<any, SSHTerminalProps>(function SSHTerminal(
scheduleNotify(cols, rows);
hardRefresh();
}
} catch (_) {}
} catch {
// Ignore resize notification errors
}
},
refresh: () => hardRefresh(),
}),
@@ -505,7 +509,9 @@ export const Terminal = forwardRef<any, SSHTerminalProps>(function SSHTerminal(
await navigator.clipboard.writeText(text);
return;
}
} catch (_) {}
} catch {
// Clipboard API not available, fallback to textarea method
}
const textarea = document.createElement("textarea");
textarea.value = text;
textarea.style.position = "fixed";
@@ -525,7 +531,9 @@ export const Terminal = forwardRef<any, SSHTerminalProps>(function SSHTerminal(
if (navigator.clipboard && navigator.clipboard.readText) {
return await navigator.clipboard.readText();
}
} catch (_) {}
} catch {
// Clipboard read not available or not permitted
}
return "";
}
@@ -585,7 +593,9 @@ export const Terminal = forwardRef<any, SSHTerminalProps>(function SSHTerminal(
const pasteText = await readTextFromClipboard();
if (pasteText) terminal.paste(pasteText);
}
} catch (_) {}
} catch {
// Ignore clipboard operation errors
}
};
element?.addEventListener("contextmenu", handleContextMenu);

View File

@@ -191,7 +191,8 @@ export function Tunnel({ filterHostKey }: SSHTunnelProps): React.ReactElement {
}
await fetchTunnelStatuses();
} catch (err) {
} catch {
// Ignore tunnel action errors
} finally {
setTunnelActions((prev) => ({ ...prev, [tunnelName]: false }));
}

View File

@@ -43,7 +43,9 @@ export function ServerConfig({
setServerUrl(config.serverUrl);
setConnectionStatus("success");
}
} catch (error) {}
} catch {
// Ignore config loading errors
}
};
const handleTestConnection = async () => {

View File

@@ -105,7 +105,9 @@ export function HomepageAuth({
const clearJWTOnLoad = async () => {
try {
await logoutUser();
} catch (error) {}
} catch {
// Ignore logout errors on initial load
}
};
clearJWTOnLoad();

View File

@@ -69,7 +69,7 @@ export function TabProvider({ children }: TabProviderProps) {
}
const m = t.title.match(
new RegExp(
`^${root.replace(/[-\/\\^$*+?.()|[\]{}]/g, "\\$&")} \\((\\d+)\\)$`,
`^${root.replace(/[-/\\^$*+?.()|[\]{}]/g, "\\$&")} \\((\\d+)\\)$`,
),
);
if (m) {

View File

@@ -73,7 +73,9 @@ export const Terminal = forwardRef<any, SSHTerminalProps>(function SSHTerminal(
if (terminal && typeof (terminal as any).refresh === "function") {
(terminal as any).refresh(0, terminal.rows - 1);
}
} catch (_) {}
} catch {
// Ignore terminal refresh errors
}
}
function scheduleNotify(cols: number, rows: number) {
@@ -122,7 +124,9 @@ export const Terminal = forwardRef<any, SSHTerminalProps>(function SSHTerminal(
scheduleNotify(cols, rows);
hardRefresh();
}
} catch (_) {}
} catch {
// Ignore resize notification errors
}
},
refresh: () => hardRefresh(),
}),
@@ -175,7 +179,9 @@ export const Terminal = forwardRef<any, SSHTerminalProps>(function SSHTerminal(
`\r\n[${msg.message || t("terminal.disconnected")}]`,
);
}
} catch (error) {}
} catch {
// Ignore message parsing errors
}
});
ws.addEventListener("close", (event) => {

View File

@@ -110,7 +110,9 @@ export function TerminalKeyboard({
if (navigator.vibrate) {
navigator.vibrate(20);
}
} catch (e) {}
} catch {
// Ignore vibration errors on unsupported devices
}
onSendInput(input);
},

View File

@@ -59,7 +59,9 @@ export function useDragToSystemDesktop({
};
});
}
} catch (error) {}
} catch {
// IndexedDB not available or failed to retrieve directory
}
return null;
};
@@ -75,7 +77,9 @@ export function useDragToSystemDesktop({
store.put({ handle: dirHandle }, "lastSaveDir");
};
}
} catch (error) {}
} catch {
// Failed to save directory handle
}
};
const isFileSystemAPISupported = () => {

View File

@@ -320,7 +320,7 @@ function isDev(): boolean {
);
}
let apiHost = import.meta.env.VITE_API_HOST || "localhost";
const apiHost = import.meta.env.VITE_API_HOST || "localhost";
let apiPort = 30001;
let configuredServerUrl: string | null = null;