Spaces:
Runtime error
Runtime error
File size: 4,999 Bytes
cd8bd0a | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 | import fs from "node:fs";
import { resolveDataDir, resolveStoragePath } from "./data-dir.mjs";
import { ensureProviderSchema } from "./provider-store.mjs";
import { ensureSettingsSchema, hashManagementPassword, updateSettings } from "./settings-store.mjs";
async function loadBetterSqlite() {
try {
return (await import("better-sqlite3")).default;
} catch {
throw new Error("better-sqlite3 is not installed. Run npm install before using setup.");
}
}
export function createSqliteNativeError(error) {
const message = error instanceof Error ? error.message : String(error);
if (message.includes("NODE_MODULE_VERSION") || message.includes("ERR_DLOPEN_FAILED")) {
return new Error(
"better-sqlite3 native binding is incompatible with this Node.js runtime. " +
"Run `npm rebuild better-sqlite3` in the OmniRoute project and try again. " +
"Or run: omniroute runtime repair " +
"(rebuilds into a user-writable runtime; works without a C++ toolchain)."
);
}
return error;
}
async function openSqliteDatabase(dbPath, options = {}) {
const Database = await loadBetterSqlite();
try {
return new Database(dbPath, options);
} catch (error) {
throw createSqliteNativeError(error);
}
}
export async function openOmniRouteDb() {
const dataDir = resolveDataDir();
const dbPath = resolveStoragePath(dataDir);
fs.mkdirSync(dataDir, { recursive: true });
const db = await openSqliteDatabase(dbPath);
db.pragma("journal_mode = WAL");
ensureSettingsSchema(db);
ensureProviderSchema(db);
return { db, dataDir, dbPath };
}
export async function withReadonlySqlite(dbPath, callback) {
const db = await openSqliteDatabase(dbPath, { readonly: true, fileMustExist: true });
try {
return await callback(db);
} finally {
db.close();
}
}
export async function backupSqliteFile(sourcePath, destPath) {
const db = await openSqliteDatabase(sourcePath, { readonly: true });
try {
await db.backup(destPath);
} finally {
db.close();
}
}
export async function readDatabaseHealth(dbPath) {
return withReadonlySqlite(dbPath, (db) => {
const quickCheck = db.prepare("PRAGMA quick_check").get();
const quickCheckValue = Object.values(quickCheck || {})[0];
const hasMigrationTable = !!db
.prepare("SELECT name FROM sqlite_master WHERE type = 'table' AND name = ?")
.get("_omniroute_migrations");
const appliedMigrationVersions = hasMigrationTable
? db
.prepare("SELECT version FROM _omniroute_migrations")
.all()
.map((row) => row.version)
: [];
return { quickCheckValue, hasMigrationTable, appliedMigrationVersions };
});
}
export async function readEncryptedCredentialSamples(dbPath) {
return withReadonlySqlite(dbPath, (db) => {
const hasProviderTable = !!db
.prepare("SELECT name FROM sqlite_master WHERE type = 'table' AND name = ?")
.get("provider_connections");
if (!hasProviderTable) {
return { hasProviderTable: false, encryptedValues: [] };
}
const rows = db
.prepare(
`SELECT api_key, access_token, refresh_token, id_token
FROM provider_connections
WHERE api_key LIKE 'enc:v1:%'
OR access_token LIKE 'enc:v1:%'
OR refresh_token LIKE 'enc:v1:%'
OR id_token LIKE 'enc:v1:%'
LIMIT 20`
)
.all();
const encryptedValues = rows.flatMap((row) =>
["api_key", "access_token", "refresh_token", "id_token"]
.filter((key) => typeof row[key] === "string" && row[key].startsWith("enc:v1:"))
.map((key) => row[key])
);
return { hasProviderTable: true, encryptedValues };
});
}
export async function readManagementPasswordState(dbPath = resolveStoragePath(resolveDataDir())) {
if (!fs.existsSync(dbPath)) {
return { exists: false, hasPassword: false };
}
return withReadonlySqlite(dbPath, (db) => {
const hasSettingsTable = !!db
.prepare("SELECT name FROM sqlite_master WHERE type = 'table' AND name = ?")
.get("key_value");
if (!hasSettingsTable) {
return { exists: true, hasPassword: false };
}
const row = db
.prepare("SELECT value FROM key_value WHERE namespace = 'settings' AND key = ?")
.get("password");
let password = row?.value;
if (typeof password === "string") {
try {
password = JSON.parse(password);
} catch {}
}
return {
exists: true,
hasPassword: typeof password === "string" && password.length > 0,
};
});
}
export async function resetManagementPassword(
password,
dbPath = resolveStoragePath(resolveDataDir())
) {
const db = await openSqliteDatabase(dbPath);
try {
db.pragma("journal_mode = WAL");
ensureSettingsSchema(db);
const hashedPassword = await hashManagementPassword(password);
updateSettings(db, { password: hashedPassword, requireLogin: true, setupComplete: true });
} finally {
db.close();
}
}
|