mirror of
https://github.com/Nezumi-2711/9router.git
synced 2026-09-22 13:38:31 +00:00
fix(cursor): remove sql.js dependency from auto-import route (#368)
* fix(usage): track lifetime request total beyond history cap * fix(ui): restore provider assets and model availability endpoint * fix(cursor): remove sql.js dependency from auto-import route
This commit is contained in:
@@ -1,5 +1,5 @@
|
|||||||
import { NextResponse } from "next/server";
|
import { NextResponse } from "next/server";
|
||||||
import { access, constants, readFile } from "fs/promises";
|
import { access, constants } from "fs/promises";
|
||||||
import { homedir } from "os";
|
import { homedir } from "os";
|
||||||
import { join } from "path";
|
import { join } from "path";
|
||||||
import { execFile } from "child_process";
|
import { execFile } from "child_process";
|
||||||
@@ -8,7 +8,11 @@ import { promisify } from "util";
|
|||||||
const execFileAsync = promisify(execFile);
|
const execFileAsync = promisify(execFile);
|
||||||
|
|
||||||
const ACCESS_TOKEN_KEYS = ["cursorAuth/accessToken", "cursorAuth/token"];
|
const ACCESS_TOKEN_KEYS = ["cursorAuth/accessToken", "cursorAuth/token"];
|
||||||
const MACHINE_ID_KEYS = ["storage.serviceMachineId", "storage.machineId", "telemetry.machineId"];
|
const MACHINE_ID_KEYS = [
|
||||||
|
"storage.serviceMachineId",
|
||||||
|
"storage.machineId",
|
||||||
|
"telemetry.machineId",
|
||||||
|
];
|
||||||
|
|
||||||
/** Get candidate db paths by platform */
|
/** Get candidate db paths by platform */
|
||||||
function getCandidatePaths(platform) {
|
function getCandidatePaths(platform) {
|
||||||
@@ -16,19 +20,39 @@ function getCandidatePaths(platform) {
|
|||||||
|
|
||||||
if (platform === "darwin") {
|
if (platform === "darwin") {
|
||||||
return [
|
return [
|
||||||
join(home, "Library/Application Support/Cursor/User/globalStorage/state.vscdb"),
|
join(
|
||||||
join(home, "Library/Application Support/Cursor - Insiders/User/globalStorage/state.vscdb"),
|
home,
|
||||||
|
"Library/Application Support/Cursor/User/globalStorage/state.vscdb",
|
||||||
|
),
|
||||||
|
join(
|
||||||
|
home,
|
||||||
|
"Library/Application Support/Cursor - Insiders/User/globalStorage/state.vscdb",
|
||||||
|
),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
if (platform === "win32") {
|
if (platform === "win32") {
|
||||||
const appData = process.env.APPDATA || join(home, "AppData", "Roaming");
|
const appData = process.env.APPDATA || join(home, "AppData", "Roaming");
|
||||||
const localAppData = process.env.LOCALAPPDATA || join(home, "AppData", "Local");
|
const localAppData =
|
||||||
|
process.env.LOCALAPPDATA || join(home, "AppData", "Local");
|
||||||
return [
|
return [
|
||||||
join(appData, "Cursor", "User", "globalStorage", "state.vscdb"),
|
join(appData, "Cursor", "User", "globalStorage", "state.vscdb"),
|
||||||
join(appData, "Cursor - Insiders", "User", "globalStorage", "state.vscdb"),
|
join(
|
||||||
|
appData,
|
||||||
|
"Cursor - Insiders",
|
||||||
|
"User",
|
||||||
|
"globalStorage",
|
||||||
|
"state.vscdb",
|
||||||
|
),
|
||||||
join(localAppData, "Cursor", "User", "globalStorage", "state.vscdb"),
|
join(localAppData, "Cursor", "User", "globalStorage", "state.vscdb"),
|
||||||
join(localAppData, "Programs", "Cursor", "User", "globalStorage", "state.vscdb"),
|
join(
|
||||||
|
localAppData,
|
||||||
|
"Programs",
|
||||||
|
"Cursor",
|
||||||
|
"User",
|
||||||
|
"globalStorage",
|
||||||
|
"state.vscdb",
|
||||||
|
),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -48,54 +72,9 @@ const normalize = (value) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/** Extract tokens using sql.js (pure JS, cross-platform) */
|
|
||||||
async function extractTokens(dbPath) {
|
|
||||||
const initSqlJs = (await import("sql.js")).default;
|
|
||||||
const SQL = await initSqlJs();
|
|
||||||
const db = new SQL.Database(await readFile(dbPath));
|
|
||||||
|
|
||||||
const queryAll = (sql, params = []) => {
|
|
||||||
const stmt = db.prepare(sql);
|
|
||||||
stmt.bind(params);
|
|
||||||
const rows = [];
|
|
||||||
while (stmt.step()) rows.push(stmt.getAsObject());
|
|
||||||
stmt.free();
|
|
||||||
return rows;
|
|
||||||
};
|
|
||||||
|
|
||||||
const desiredKeys = [...ACCESS_TOKEN_KEYS, ...MACHINE_ID_KEYS];
|
|
||||||
const placeholders = desiredKeys.map(() => "?").join(",");
|
|
||||||
const rows = queryAll(`SELECT key, value FROM itemTable WHERE key IN (${placeholders})`, desiredKeys);
|
|
||||||
|
|
||||||
const tokens = {};
|
|
||||||
for (const row of rows) {
|
|
||||||
if (ACCESS_TOKEN_KEYS.includes(row.key) && !tokens.accessToken) {
|
|
||||||
tokens.accessToken = normalize(row.value);
|
|
||||||
} else if (MACHINE_ID_KEYS.includes(row.key) && !tokens.machineId) {
|
|
||||||
tokens.machineId = normalize(row.value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fuzzy fallback
|
|
||||||
if (!tokens.accessToken || !tokens.machineId) {
|
|
||||||
const fallbackRows = queryAll(
|
|
||||||
"SELECT key, value FROM itemTable WHERE key LIKE '%cursorAuth/%' OR key LIKE '%machineId%' OR key LIKE '%serviceMachineId%'"
|
|
||||||
);
|
|
||||||
for (const row of fallbackRows) {
|
|
||||||
const key = row.key || "";
|
|
||||||
const value = normalize(row.value);
|
|
||||||
if (!tokens.accessToken && key.toLowerCase().includes("accesstoken")) tokens.accessToken = value;
|
|
||||||
if (!tokens.machineId && key.toLowerCase().includes("machineid")) tokens.machineId = value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
db.close();
|
|
||||||
return tokens;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Extract tokens via sqlite3 CLI (fallback for Windows when native addon fails)
|
* Extract tokens via sqlite3 CLI.
|
||||||
* Queries each key individually and parses output
|
* Keeps the route build-safe by avoiding optional sql.js bundling.
|
||||||
*/
|
*/
|
||||||
async function extractTokensViaCLI(dbPath) {
|
async function extractTokensViaCLI(dbPath) {
|
||||||
const normalize = (raw) => {
|
const normalize = (raw) => {
|
||||||
@@ -109,7 +88,9 @@ async function extractTokensViaCLI(dbPath) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const query = async (sql) => {
|
const query = async (sql) => {
|
||||||
const { stdout } = await execFileAsync("sqlite3", [dbPath, sql], { timeout: 10000 });
|
const { stdout } = await execFileAsync("sqlite3", [dbPath, sql], {
|
||||||
|
timeout: 10000,
|
||||||
|
});
|
||||||
return stdout.trim();
|
return stdout.trim();
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -117,17 +98,31 @@ async function extractTokensViaCLI(dbPath) {
|
|||||||
let accessToken = null;
|
let accessToken = null;
|
||||||
for (const key of ACCESS_TOKEN_KEYS) {
|
for (const key of ACCESS_TOKEN_KEYS) {
|
||||||
try {
|
try {
|
||||||
const raw = await query(`SELECT value FROM itemTable WHERE key='${key}' LIMIT 1`);
|
const raw = await query(
|
||||||
if (raw) { accessToken = normalize(raw); break; }
|
`SELECT value FROM itemTable WHERE key='${key}' LIMIT 1`,
|
||||||
} catch { /* try next */ }
|
);
|
||||||
|
if (raw) {
|
||||||
|
accessToken = normalize(raw);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
/* try next */
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let machineId = null;
|
let machineId = null;
|
||||||
for (const key of MACHINE_ID_KEYS) {
|
for (const key of MACHINE_ID_KEYS) {
|
||||||
try {
|
try {
|
||||||
const raw = await query(`SELECT value FROM itemTable WHERE key='${key}' LIMIT 1`);
|
const raw = await query(
|
||||||
if (raw) { machineId = normalize(raw); break; }
|
`SELECT value FROM itemTable WHERE key='${key}' LIMIT 1`,
|
||||||
} catch { /* try next */ }
|
);
|
||||||
|
if (raw) {
|
||||||
|
machineId = normalize(raw);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
/* try next */
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return { accessToken, machineId };
|
return { accessToken, machineId };
|
||||||
@@ -136,7 +131,7 @@ async function extractTokensViaCLI(dbPath) {
|
|||||||
/**
|
/**
|
||||||
* GET /api/oauth/cursor/auto-import
|
* GET /api/oauth/cursor/auto-import
|
||||||
* Auto-detect and extract Cursor tokens from local SQLite database.
|
* Auto-detect and extract Cursor tokens from local SQLite database.
|
||||||
* Strategy: better-sqlite3 (native, fast) → sqlite3 CLI (fallback) → windowsManual
|
* Strategy: sqlite3 CLI → manual fallback
|
||||||
*/
|
*/
|
||||||
export async function GET() {
|
export async function GET() {
|
||||||
try {
|
try {
|
||||||
@@ -161,26 +156,27 @@ export async function GET() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Strategy 1: sql.js (pure JS WASM, cross-platform)
|
// Strategy 1: sqlite3 CLI
|
||||||
try {
|
|
||||||
const tokens = await extractTokens(dbPath);
|
|
||||||
if (tokens.accessToken && tokens.machineId) {
|
|
||||||
return NextResponse.json({ found: true, accessToken: tokens.accessToken, machineId: tokens.machineId });
|
|
||||||
}
|
|
||||||
} catch { /* fall through to sqlite3 CLI strategy */ }
|
|
||||||
|
|
||||||
// Strategy 2: sqlite3 CLI (works on Windows if sqlite3 is installed)
|
|
||||||
try {
|
try {
|
||||||
const tokens = await extractTokensViaCLI(dbPath);
|
const tokens = await extractTokensViaCLI(dbPath);
|
||||||
if (tokens.accessToken && tokens.machineId) {
|
if (tokens.accessToken && tokens.machineId) {
|
||||||
return NextResponse.json({ found: true, accessToken: tokens.accessToken, machineId: tokens.machineId });
|
return NextResponse.json({
|
||||||
|
found: true,
|
||||||
|
accessToken: tokens.accessToken,
|
||||||
|
machineId: tokens.machineId,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// sqlite3 CLI not available
|
||||||
}
|
}
|
||||||
} catch { /* sqlite3 CLI not available */ }
|
|
||||||
|
|
||||||
// Strategy 3: ask user to paste manually
|
// Strategy 2: ask user to paste manually
|
||||||
return NextResponse.json({ found: false, windowsManual: true, dbPath });
|
return NextResponse.json({ found: false, windowsManual: true, dbPath });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log("Cursor auto-import error:", error);
|
console.log("Cursor auto-import error:", error);
|
||||||
return NextResponse.json({ found: false, error: error.message }, { status: 500 });
|
return NextResponse.json(
|
||||||
|
{ found: false, error: error.message },
|
||||||
|
{ status: 500 },
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user