Seed client auth from token file and pin the data dir
This commit is contained in:
@@ -41,6 +41,20 @@ direction.
|
|||||||
- **Later policy:** human emulation — snappy first action (~30–80 ms), bulk actions randomized,
|
- **Later policy:** human emulation — snappy first action (~30–80 ms), bulk actions randomized,
|
||||||
opt-in risky fast mode, free-account limits by default, ability toggles.
|
opt-in risky fast mode, free-account limits by default, ability toggles.
|
||||||
|
|
||||||
|
## Auth persistence
|
||||||
|
|
||||||
|
- Durable credential: the token file, `VESKSLOP_TOKEN_FILE` (default `<DATA_DIR>/token`, mode 600,
|
||||||
|
gitignored). It is the **puppet account's** token; zzib is only the DM test target.
|
||||||
|
- `DATA_DIR` is pinned: `$VENCORD_USER_DATA_DIR`, else `$VESKSLOP_DATA_DIR`, else
|
||||||
|
`<appData>/veskslop` (`src/main/constants.ts`). It does not depend on the app/product name, so
|
||||||
|
renames and version bumps do not move the Electron profile where Discord stores the session.
|
||||||
|
- On startup the preload asks main for the token (sync IPC) and seeds `localStorage.token` before
|
||||||
|
the client boots, but only when no session exists. `VESKSLOP_FORCE_TOKEN=1` overrides.
|
||||||
|
- Recovery: if Discord invalidates the session, run `pnpm poc:qr` for a fresh token and update the
|
||||||
|
token file.
|
||||||
|
- Discord deletes the stored token when it thinks devtools opened; keep Vesktop's devtools detector
|
||||||
|
patch (`src/renderer/patches/devtoolsFixes.ts`) so this cannot cause random logouts.
|
||||||
|
|
||||||
## How to proceed
|
## How to proceed
|
||||||
|
|
||||||
**Current milestone — puppeting bridge (testable without Electron).**
|
**Current milestone — puppeting bridge (testable without Electron).**
|
||||||
@@ -88,7 +102,6 @@ MCP clients (agents) ──Streamable HTTP──▶ veskSlop control daemon
|
|||||||
## Open questions
|
## Open questions
|
||||||
|
|
||||||
- Headless Electron strategy: Xvfb vs offscreen rendering, and what to strip to lighten the client.
|
- Headless Electron strategy: Xvfb vs offscreen rendering, and what to strip to lighten the client.
|
||||||
- Token injection path for the official client (localStorage seeding vs client QR UI).
|
|
||||||
- Voice/video: what the client's native stack can be driven to do without UI interaction.
|
- Voice/video: what the client's native stack can be driven to do without UI interaction.
|
||||||
- Scope and auth model of the future remote proxy client and web debug UI.
|
- Scope and auth model of the future remote proxy client and web debug UI.
|
||||||
- How much of the Vesktop Electron tree gets deleted once the daemon is self-sufficient.
|
- How much of the Vesktop Electron tree gets deleted once the daemon is self-sufficient.
|
||||||
|
|||||||
+5
-1
@@ -27,10 +27,14 @@ TOKEN_FILE=/path/to/token DEBUG=1 pnpm poc:qr
|
|||||||
|
|
||||||
Connects to `wss://remote-auth-gateway.discord.gg/?v=2`, renders the approval QR directly in the
|
Connects to `wss://remote-auth-gateway.discord.gg/?v=2`, renders the approval QR directly in the
|
||||||
terminal, waits for a phone scan, exchanges the login ticket and writes the token to
|
terminal, waits for a phone scan, exchanges the login ticket and writes the token to
|
||||||
`poc/.token` (mode 600, gitignored; override with `TOKEN_FILE`). No existing token required.
|
`poc/.token` (mode 600, gitignored; override with `TOKEN_FILE`). This is the **puppet account's**
|
||||||
|
token. No existing token required.
|
||||||
If Discord responds with an hCaptcha challenge, the script prints the challenge and prompts for a
|
If Discord responds with an hCaptcha challenge, the script prints the challenge and prompts for a
|
||||||
`HCAPTCHA_KEY` (or takes it from the environment).
|
`HCAPTCHA_KEY` (or takes it from the environment).
|
||||||
|
|
||||||
|
The headless client consumes this file on startup via `VESKSLOP_TOKEN_FILE` (default
|
||||||
|
`<DATA_DIR>/token`, see [../AGENTS.md](../AGENTS.md) → Auth persistence).
|
||||||
|
|
||||||
### 1. Vesktop control bridge — `src/control-bridge.ts`
|
### 1. Vesktop control bridge — `src/control-bridge.ts`
|
||||||
|
|
||||||
The MCP daemon. Exposes Streamable HTTP and forwards tool calls over the JSON-lines protocol on
|
The MCP daemon. Exposes Streamable HTTP and forwards tool calls over the JSON-lines protocol on
|
||||||
|
|||||||
@@ -18,10 +18,15 @@ export const PORTABLE =
|
|||||||
!existsSync(join(vesktopDir, "Uninstall Vesktop.exe"));
|
!existsSync(join(vesktopDir, "Uninstall Vesktop.exe"));
|
||||||
|
|
||||||
export const DATA_DIR =
|
export const DATA_DIR =
|
||||||
process.env.VENCORD_USER_DATA_DIR || (PORTABLE ? join(vesktopDir, "Data") : join(app.getPath("userData")));
|
process.env.VENCORD_USER_DATA_DIR ||
|
||||||
|
process.env.VESKSLOP_DATA_DIR ||
|
||||||
|
(PORTABLE ? join(vesktopDir, "Data") : join(app.getPath("appData"), "veskslop"));
|
||||||
|
|
||||||
mkdirSync(DATA_DIR, { recursive: true });
|
mkdirSync(DATA_DIR, { recursive: true });
|
||||||
|
|
||||||
|
/** Durable auth credential. Seeded into the client before it boots. */
|
||||||
|
export const TOKEN_FILE = process.env.VESKSLOP_TOKEN_FILE || join(DATA_DIR, "token");
|
||||||
|
|
||||||
export const SESSION_DATA_DIR = join(DATA_DIR, "sessionData");
|
export const SESSION_DATA_DIR = join(DATA_DIR, "sessionData");
|
||||||
app.setPath("sessionData", SESSION_DATA_DIR);
|
app.setPath("sessionData", SESSION_DATA_DIR);
|
||||||
|
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ import { join } from "path";
|
|||||||
import { IpcEvents } from "../shared/IpcEvents";
|
import { IpcEvents } from "../shared/IpcEvents";
|
||||||
import { setBadgeCount } from "./appBadge";
|
import { setBadgeCount } from "./appBadge";
|
||||||
import { autoStart } from "./autoStart";
|
import { autoStart } from "./autoStart";
|
||||||
|
import { TOKEN_FILE } from "./constants";
|
||||||
import { enableHardwareAcceleration } from "./main";
|
import { enableHardwareAcceleration } from "./main";
|
||||||
import { mainWin } from "./mainWindow";
|
import { mainWin } from "./mainWindow";
|
||||||
import { Settings, State } from "./settings";
|
import { Settings, State } from "./settings";
|
||||||
@@ -60,6 +61,17 @@ handleSync(IpcEvents.GET_SETTINGS, () => Settings.plain);
|
|||||||
handleSync(IpcEvents.GET_VERSION, () => app.getVersion());
|
handleSync(IpcEvents.GET_VERSION, () => app.getVersion());
|
||||||
handleSync(IpcEvents.GET_ENABLE_HARDWARE_ACCELERATION, () => enableHardwareAcceleration);
|
handleSync(IpcEvents.GET_ENABLE_HARDWARE_ACCELERATION, () => enableHardwareAcceleration);
|
||||||
|
|
||||||
|
handleSync(IpcEvents.VESKSLOP_GET_TOKEN, () => {
|
||||||
|
let token: string | undefined;
|
||||||
|
try {
|
||||||
|
token = readFileSync(TOKEN_FILE, "utf8").trim() || undefined;
|
||||||
|
} catch {
|
||||||
|
// No token file: the client falls back to its normal login.
|
||||||
|
}
|
||||||
|
|
||||||
|
return { token, force: process.env.VESKSLOP_FORCE_TOKEN === "1" };
|
||||||
|
});
|
||||||
|
|
||||||
handleSync(
|
handleSync(
|
||||||
IpcEvents.SUPPORTS_WINDOWS_TRANSPARENCY,
|
IpcEvents.SUPPORTS_WINDOWS_TRANSPARENCY,
|
||||||
() => process.platform === "win32" && Number(release().split(".").pop()) >= 22621
|
() => process.platform === "win32" && Number(release().split(".").pop()) >= 22621
|
||||||
|
|||||||
@@ -9,6 +9,20 @@ import { contextBridge, ipcRenderer, webFrame } from "electron/renderer";
|
|||||||
import { IpcEvents } from "../shared/IpcEvents";
|
import { IpcEvents } from "../shared/IpcEvents";
|
||||||
import { VesktopNative } from "./VesktopNative";
|
import { VesktopNative } from "./VesktopNative";
|
||||||
|
|
||||||
|
// Veskslop: seed the Discord auth token before the client boots, unless a session already exists.
|
||||||
|
try {
|
||||||
|
const seed = ipcRenderer.sendSync(IpcEvents.VESKSLOP_GET_TOKEN) as
|
||||||
|
| { token?: string; force?: boolean }
|
||||||
|
| undefined;
|
||||||
|
|
||||||
|
if (seed?.token && (seed.force || !localStorage.getItem("token"))) {
|
||||||
|
localStorage.setItem("token", JSON.stringify(seed.token));
|
||||||
|
console.log("[veskslop] seeded auth token");
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.warn("[veskslop] failed to seed auth token", error);
|
||||||
|
}
|
||||||
|
|
||||||
contextBridge.exposeInMainWorld("VesktopNative", VesktopNative);
|
contextBridge.exposeInMainWorld("VesktopNative", VesktopNative);
|
||||||
|
|
||||||
// While sandboxed, Electron "polyfills" these APIs as local variables.
|
// While sandboxed, Electron "polyfills" these APIs as local variables.
|
||||||
|
|||||||
@@ -25,6 +25,8 @@ export const enum IpcEvents {
|
|||||||
GET_SETTINGS = "VCD_GET_SETTINGS",
|
GET_SETTINGS = "VCD_GET_SETTINGS",
|
||||||
SET_SETTINGS = "VCD_SET_SETTINGS",
|
SET_SETTINGS = "VCD_SET_SETTINGS",
|
||||||
|
|
||||||
|
VESKSLOP_GET_TOKEN = "VCD_VESKSLOP_GET_TOKEN",
|
||||||
|
|
||||||
IS_USING_CUSTOM_VENCORD_DIR = "VCD_IS_USING_CUSTOM_VENCORD_DIR",
|
IS_USING_CUSTOM_VENCORD_DIR = "VCD_IS_USING_CUSTOM_VENCORD_DIR",
|
||||||
SHOW_CUSTOM_VENCORD_DIR = "VCD_SHOW_CUSTOM_VENCORD_DIR",
|
SHOW_CUSTOM_VENCORD_DIR = "VCD_SHOW_CUSTOM_VENCORD_DIR",
|
||||||
SELECT_VENCORD_DIR = "VCD_SELECT_VENCORD_DIR",
|
SELECT_VENCORD_DIR = "VCD_SELECT_VENCORD_DIR",
|
||||||
|
|||||||
Reference in New Issue
Block a user