@netique/overleaf-mcp 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +47 -21
- package/dist/api/http.js +56 -28
- package/dist/api/http.js.map +1 -1
- package/dist/api/socket.js +129 -28
- package/dist/api/socket.js.map +1 -1
- package/dist/auth/browserLogin.js +148 -0
- package/dist/auth/browserLogin.js.map +1 -0
- package/dist/auth/cdp.js +124 -0
- package/dist/auth/cdp.js.map +1 -0
- package/dist/auth/chromeFinder.js +91 -0
- package/dist/auth/chromeFinder.js.map +1 -0
- package/dist/auth/cli.js +102 -0
- package/dist/auth/cli.js.map +1 -0
- package/dist/auth/cookieStore.js +65 -0
- package/dist/auth/cookieStore.js.map +1 -0
- package/dist/auth/discover.js +39 -0
- package/dist/auth/discover.js.map +1 -0
- package/dist/config.js +6 -8
- package/dist/config.js.map +1 -1
- package/dist/index.js +4 -1
- package/dist/index.js.map +1 -1
- package/dist/session/identity.js +12 -5
- package/dist/session/identity.js.map +1 -1
- package/dist/session/recovery.js +28 -0
- package/dist/session/recovery.js.map +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
// Spawn a Chrome window pointed at Overleaf, wait for the user to land on
|
|
2
|
+
// the dashboard (any path under /project), then read the session cookie
|
|
3
|
+
// straight out of the browser via CDP. Cookie expiry handling is just a
|
|
4
|
+
// re-run of the same flow.
|
|
5
|
+
import { promises as fs } from "node:fs";
|
|
6
|
+
import { spawn } from "node:child_process";
|
|
7
|
+
import path from "node:path";
|
|
8
|
+
import os from "node:os";
|
|
9
|
+
import { logger } from "../util/logger.js";
|
|
10
|
+
import { loadConfig } from "../config.js";
|
|
11
|
+
import { CdpClient } from "./cdp.js";
|
|
12
|
+
import { findChrome } from "./chromeFinder.js";
|
|
13
|
+
function configRoot() {
|
|
14
|
+
if (process.platform === "darwin")
|
|
15
|
+
return path.join(os.homedir(), "Library", "Application Support");
|
|
16
|
+
if (process.platform === "win32")
|
|
17
|
+
return process.env.APPDATA ?? path.join(os.homedir(), "AppData", "Roaming");
|
|
18
|
+
return process.env.XDG_CONFIG_HOME ?? path.join(os.homedir(), ".config");
|
|
19
|
+
}
|
|
20
|
+
export function profileDir() {
|
|
21
|
+
return path.join(configRoot(), "overleaf-mcp", "chrome-profile");
|
|
22
|
+
}
|
|
23
|
+
async function readDevToolsPort(profile, deadline) {
|
|
24
|
+
const file = path.join(profile, "DevToolsActivePort");
|
|
25
|
+
while (Date.now() < deadline) {
|
|
26
|
+
try {
|
|
27
|
+
const txt = await fs.readFile(file, "utf8");
|
|
28
|
+
const [portLine, pathLine] = txt.trim().split("\n");
|
|
29
|
+
const port = Number(portLine);
|
|
30
|
+
if (port > 0 && pathLine)
|
|
31
|
+
return { port, path: pathLine };
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
// not written yet
|
|
35
|
+
}
|
|
36
|
+
await new Promise((r) => setTimeout(r, 50));
|
|
37
|
+
}
|
|
38
|
+
throw new Error("Chrome did not write DevToolsActivePort within timeout");
|
|
39
|
+
}
|
|
40
|
+
function chromeFlags(profile, insecure) {
|
|
41
|
+
// Note: no URL argument here. We spawn Chrome with about:blank and then
|
|
42
|
+
// drive the single resulting tab via CDP. Passing the Overleaf URL on the
|
|
43
|
+
// command line in addition to a CDP-created target was racing two parallel
|
|
44
|
+
// /login requests through the same session, which Overleaf rejected with a
|
|
45
|
+
// generic "Session error" page.
|
|
46
|
+
const flags = [
|
|
47
|
+
"--remote-debugging-port=0",
|
|
48
|
+
`--user-data-dir=${profile}`,
|
|
49
|
+
"--no-first-run",
|
|
50
|
+
"--no-default-browser-check",
|
|
51
|
+
"--no-service-autorun",
|
|
52
|
+
"--disable-features=AutofillServerCommunication,OptimizationHints,MediaRouter",
|
|
53
|
+
"--password-store=basic",
|
|
54
|
+
"--use-mock-keychain",
|
|
55
|
+
"--new-window",
|
|
56
|
+
"--disk-cache-size=10000000",
|
|
57
|
+
];
|
|
58
|
+
if (insecure)
|
|
59
|
+
flags.push("--ignore-certificate-errors");
|
|
60
|
+
flags.push("about:blank");
|
|
61
|
+
return flags;
|
|
62
|
+
}
|
|
63
|
+
async function waitForDashboard(cdp, sessionId, baseUrl, child, timeoutMs) {
|
|
64
|
+
const deadline = Date.now() + timeoutMs;
|
|
65
|
+
const loginPath = new URL("/project", baseUrl).pathname; // canonical "/project"
|
|
66
|
+
while (Date.now() < deadline) {
|
|
67
|
+
if (child.exitCode !== null || cdp.isClosed()) {
|
|
68
|
+
throw new Error("Chrome window closed before login completed");
|
|
69
|
+
}
|
|
70
|
+
try {
|
|
71
|
+
const res = await cdp.send("Runtime.evaluate", { expression: "location.pathname", returnByValue: true }, sessionId, 5_000);
|
|
72
|
+
const pathname = res.result?.value;
|
|
73
|
+
if (typeof pathname === "string" && new RegExp(`^${loginPath}(/|$)`).test(pathname)) {
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
catch (err) {
|
|
78
|
+
// Page may be mid-navigation; just retry.
|
|
79
|
+
logger.debug(`waitForDashboard: ${err.message}`);
|
|
80
|
+
}
|
|
81
|
+
await new Promise((r) => setTimeout(r, 500));
|
|
82
|
+
}
|
|
83
|
+
throw new Error(`Login did not complete within ${Math.round(timeoutMs / 1000)}s`);
|
|
84
|
+
}
|
|
85
|
+
export async function captureCookie(baseUrl, opts = {}) {
|
|
86
|
+
const config = loadConfig();
|
|
87
|
+
const chromePath = await findChrome(config.browserPath);
|
|
88
|
+
const profile = profileDir();
|
|
89
|
+
await fs.mkdir(profile, { recursive: true });
|
|
90
|
+
const args = chromeFlags(profile, config.insecure);
|
|
91
|
+
logger.info(`launching ${chromePath} (profile=${profile})`);
|
|
92
|
+
const child = spawn(chromePath, args, { stdio: "ignore", detached: false });
|
|
93
|
+
let earlyExitErr = null;
|
|
94
|
+
child.once("exit", (code, signal) => {
|
|
95
|
+
if (code !== 0 && code !== null) {
|
|
96
|
+
earlyExitErr = new Error(`Chrome exited (code=${code}, signal=${signal ?? "none"})`);
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
const portDeadline = Date.now() + 10_000;
|
|
100
|
+
let cdp = null;
|
|
101
|
+
try {
|
|
102
|
+
const { port, path: browserWsPath } = await readDevToolsPort(profile, portDeadline);
|
|
103
|
+
if (earlyExitErr)
|
|
104
|
+
throw earlyExitErr;
|
|
105
|
+
const browserWsUrl = `ws://127.0.0.1:${port}${browserWsPath}`;
|
|
106
|
+
cdp = await CdpClient.connect(browserWsUrl);
|
|
107
|
+
// Reuse the about:blank tab Chrome opened on spawn. Creating a second
|
|
108
|
+
// target makes Overleaf see two concurrent /login requests, which it
|
|
109
|
+
// rejects with a generic "Session error" page.
|
|
110
|
+
const targets = await cdp.send("Target.getTargets");
|
|
111
|
+
const pageTarget = targets.targetInfos.find((t) => t.type === "page");
|
|
112
|
+
if (!pageTarget)
|
|
113
|
+
throw new Error("Chrome opened without a page target");
|
|
114
|
+
const targetId = pageTarget.targetId;
|
|
115
|
+
const attached = await cdp.send("Target.attachToTarget", {
|
|
116
|
+
targetId,
|
|
117
|
+
flatten: true,
|
|
118
|
+
});
|
|
119
|
+
const sessionId = attached.sessionId;
|
|
120
|
+
await cdp.send("Page.enable", {}, sessionId);
|
|
121
|
+
await cdp.send("Network.enable", {}, sessionId);
|
|
122
|
+
await cdp.send("Page.navigate", { url: `${baseUrl}/project` }, sessionId);
|
|
123
|
+
const timeoutMs = opts.timeoutMs ?? 5 * 60_000;
|
|
124
|
+
await waitForDashboard(cdp, sessionId, baseUrl, child, timeoutMs);
|
|
125
|
+
const cookieRes = await cdp.send("Network.getCookies", { urls: [baseUrl] }, sessionId);
|
|
126
|
+
const cookies = cookieRes.cookies ?? [];
|
|
127
|
+
if (!cookies.some((c) => c.name === "overleaf_session2")) {
|
|
128
|
+
throw new Error("captured page but no overleaf_session2 cookie present");
|
|
129
|
+
}
|
|
130
|
+
const header = cookies.map((c) => `${c.name}=${c.value}`).join("; ");
|
|
131
|
+
return header;
|
|
132
|
+
}
|
|
133
|
+
finally {
|
|
134
|
+
if (cdp)
|
|
135
|
+
cdp.close();
|
|
136
|
+
// Don't reach into the browser process; let it exit on its own when the
|
|
137
|
+
// user closes the window. The dedicated profile means it doesn't matter
|
|
138
|
+
// if the window lingers after login.
|
|
139
|
+
if (child.exitCode === null) {
|
|
140
|
+
// Gentle close — Chrome handles SIGTERM by saving session and quitting.
|
|
141
|
+
try {
|
|
142
|
+
child.kill("SIGTERM");
|
|
143
|
+
}
|
|
144
|
+
catch { /* ignore */ }
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
//# sourceMappingURL=browserLogin.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"browserLogin.js","sourceRoot":"","sources":["../../src/auth/browserLogin.ts"],"names":[],"mappings":"AAAA,0EAA0E;AAC1E,wEAAwE;AACxE,wEAAwE;AACxE,2BAA2B;AAE3B,OAAO,EAAE,QAAQ,IAAI,EAAE,EAAE,MAAM,SAAS,CAAC;AACzC,OAAO,EAAE,KAAK,EAAqB,MAAM,oBAAoB,CAAC;AAC9D,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,MAAM,SAAS,CAAC;AAEzB,OAAO,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAC3C,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,OAAO,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AACrC,OAAO,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAY/C,SAAS,UAAU;IACjB,IAAI,OAAO,CAAC,QAAQ,KAAK,QAAQ;QAAE,OAAO,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,EAAE,SAAS,EAAE,qBAAqB,CAAC,CAAC;IACpG,IAAI,OAAO,CAAC,QAAQ,KAAK,OAAO;QAAE,OAAO,OAAO,CAAC,GAAG,CAAC,OAAO,IAAI,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,EAAE,SAAS,EAAE,SAAS,CAAC,CAAC;IAC9G,OAAO,OAAO,CAAC,GAAG,CAAC,eAAe,IAAI,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,EAAE,SAAS,CAAC,CAAC;AAC3E,CAAC;AAED,MAAM,UAAU,UAAU;IACxB,OAAO,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,EAAE,cAAc,EAAE,gBAAgB,CAAC,CAAC;AACnE,CAAC;AAED,KAAK,UAAU,gBAAgB,CAAC,OAAe,EAAE,QAAgB;IAC/D,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,oBAAoB,CAAC,CAAC;IACtD,OAAO,IAAI,CAAC,GAAG,EAAE,GAAG,QAAQ,EAAE,CAAC;QAC7B,IAAI,CAAC;YACH,MAAM,GAAG,GAAG,MAAM,EAAE,CAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;YAC5C,MAAM,CAAC,QAAQ,EAAE,QAAQ,CAAC,GAAG,GAAG,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;YACpD,MAAM,IAAI,GAAG,MAAM,CAAC,QAAQ,CAAC,CAAC;YAC9B,IAAI,IAAI,GAAG,CAAC,IAAI,QAAQ;gBAAE,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC;QAC5D,CAAC;QAAC,MAAM,CAAC;YACP,kBAAkB;QACpB,CAAC;QACD,MAAM,IAAI,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,UAAU,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC;IAC9C,CAAC;IACD,MAAM,IAAI,KAAK,CAAC,wDAAwD,CAAC,CAAC;AAC5E,CAAC;AAED,SAAS,WAAW,CAAC,OAAe,EAAE,QAAiB;IACrD,wEAAwE;IACxE,0EAA0E;IAC1E,2EAA2E;IAC3E,2EAA2E;IAC3E,gCAAgC;IAChC,MAAM,KAAK,GAAG;QACZ,2BAA2B;QAC3B,mBAAmB,OAAO,EAAE;QAC5B,gBAAgB;QAChB,4BAA4B;QAC5B,sBAAsB;QACtB,8EAA8E;QAC9E,wBAAwB;QACxB,qBAAqB;QACrB,cAAc;QACd,4BAA4B;KAC7B,CAAC;IACF,IAAI,QAAQ;QAAE,KAAK,CAAC,IAAI,CAAC,6BAA6B,CAAC,CAAC;IACxD,KAAK,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC;IAC1B,OAAO,KAAK,CAAC;AACf,CAAC;AAED,KAAK,UAAU,gBAAgB,CAC7B,GAAc,EACd,SAAiB,EACjB,OAAe,EACf,KAAmB,EACnB,SAAiB;IAEjB,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,SAAS,CAAC;IACxC,MAAM,SAAS,GAAG,IAAI,GAAG,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC,QAAQ,CAAC,CAAC,uBAAuB;IAChF,OAAO,IAAI,CAAC,GAAG,EAAE,GAAG,QAAQ,EAAE,CAAC;QAC7B,IAAI,KAAK,CAAC,QAAQ,KAAK,IAAI,IAAI,GAAG,CAAC,QAAQ,EAAE,EAAE,CAAC;YAC9C,MAAM,IAAI,KAAK,CAAC,6CAA6C,CAAC,CAAC;QACjE,CAAC;QACD,IAAI,CAAC;YACH,MAAM,GAAG,GAAG,MAAM,GAAG,CAAC,IAAI,CACxB,kBAAkB,EAClB,EAAE,UAAU,EAAE,mBAAmB,EAAE,aAAa,EAAE,IAAI,EAAE,EACxD,SAAS,EACT,KAAK,CACN,CAAC;YACF,MAAM,QAAQ,GAAG,GAAG,CAAC,MAAM,EAAE,KAAK,CAAC;YACnC,IAAI,OAAO,QAAQ,KAAK,QAAQ,IAAI,IAAI,MAAM,CAAC,IAAI,SAAS,OAAO,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC;gBACpF,OAAO;YACT,CAAC;QACH,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,0CAA0C;YAC1C,MAAM,CAAC,KAAK,CAAC,qBAAsB,GAAa,CAAC,OAAO,EAAE,CAAC,CAAC;QAC9D,CAAC;QACD,MAAM,IAAI,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,UAAU,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC;IAC/C,CAAC;IACD,MAAM,IAAI,KAAK,CAAC,iCAAiC,IAAI,CAAC,KAAK,CAAC,SAAS,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC;AACpF,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,aAAa,CAAC,OAAe,EAAE,OAAoB,EAAE;IACzE,MAAM,MAAM,GAAG,UAAU,EAAE,CAAC;IAC5B,MAAM,UAAU,GAAG,MAAM,UAAU,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC;IACxD,MAAM,OAAO,GAAG,UAAU,EAAE,CAAC;IAC7B,MAAM,EAAE,CAAC,KAAK,CAAC,OAAO,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAE7C,MAAM,IAAI,GAAG,WAAW,CAAC,OAAO,EAAE,MAAM,CAAC,QAAQ,CAAC,CAAC;IACnD,MAAM,CAAC,IAAI,CAAC,aAAa,UAAU,aAAa,OAAO,GAAG,CAAC,CAAC;IAC5D,MAAM,KAAK,GAAG,KAAK,CAAC,UAAU,EAAE,IAAI,EAAE,EAAE,KAAK,EAAE,QAAQ,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAC,CAAC;IAE5E,IAAI,YAAY,GAAiB,IAAI,CAAC;IACtC,KAAK,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE;QAClC,IAAI,IAAI,KAAK,CAAC,IAAI,IAAI,KAAK,IAAI,EAAE,CAAC;YAChC,YAAY,GAAG,IAAI,KAAK,CAAC,uBAAuB,IAAI,YAAY,MAAM,IAAI,MAAM,GAAG,CAAC,CAAC;QACvF,CAAC;IACH,CAAC,CAAC,CAAC;IAEH,MAAM,YAAY,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,MAAM,CAAC;IACzC,IAAI,GAAG,GAAqB,IAAI,CAAC;IACjC,IAAI,CAAC;QACH,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,aAAa,EAAE,GAAG,MAAM,gBAAgB,CAAC,OAAO,EAAE,YAAY,CAAC,CAAC;QACpF,IAAI,YAAY;YAAE,MAAM,YAAY,CAAC;QACrC,MAAM,YAAY,GAAG,kBAAkB,IAAI,GAAG,aAAa,EAAE,CAAC;QAC9D,GAAG,GAAG,MAAM,SAAS,CAAC,OAAO,CAAC,YAAY,CAAC,CAAC;QAE5C,sEAAsE;QACtE,qEAAqE;QACrE,+CAA+C;QAC/C,MAAM,OAAO,GAAG,MAAM,GAAG,CAAC,IAAI,CAC5B,mBAAmB,CACpB,CAAC;QACF,MAAM,UAAU,GAAG,OAAO,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,MAAM,CAAC,CAAC;QACtE,IAAI,CAAC,UAAU;YAAE,MAAM,IAAI,KAAK,CAAC,qCAAqC,CAAC,CAAC;QACxE,MAAM,QAAQ,GAAG,UAAU,CAAC,QAAQ,CAAC;QACrC,MAAM,QAAQ,GAAG,MAAM,GAAG,CAAC,IAAI,CAAwB,uBAAuB,EAAE;YAC9E,QAAQ;YACR,OAAO,EAAE,IAAI;SACd,CAAC,CAAC;QACH,MAAM,SAAS,GAAG,QAAQ,CAAC,SAAS,CAAC;QAErC,MAAM,GAAG,CAAC,IAAI,CAAC,aAAa,EAAE,EAAE,EAAE,SAAS,CAAC,CAAC;QAC7C,MAAM,GAAG,CAAC,IAAI,CAAC,gBAAgB,EAAE,EAAE,EAAE,SAAS,CAAC,CAAC;QAChD,MAAM,GAAG,CAAC,IAAI,CAAC,eAAe,EAAE,EAAE,GAAG,EAAE,GAAG,OAAO,UAAU,EAAE,EAAE,SAAS,CAAC,CAAC;QAE1E,MAAM,SAAS,GAAG,IAAI,CAAC,SAAS,IAAI,CAAC,GAAG,MAAM,CAAC;QAC/C,MAAM,gBAAgB,CAAC,GAAG,EAAE,SAAS,EAAE,OAAO,EAAE,KAAK,EAAE,SAAS,CAAC,CAAC;QAElE,MAAM,SAAS,GAAG,MAAM,GAAG,CAAC,IAAI,CAC9B,oBAAoB,EACpB,EAAE,IAAI,EAAE,CAAC,OAAO,CAAC,EAAE,EACnB,SAAS,CACV,CAAC;QACF,MAAM,OAAO,GAAG,SAAS,CAAC,OAAO,IAAI,EAAE,CAAC;QACxC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,mBAAmB,CAAC,EAAE,CAAC;YACzD,MAAM,IAAI,KAAK,CAAC,uDAAuD,CAAC,CAAC;QAC3E,CAAC;QACD,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,KAAK,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAErE,OAAO,MAAM,CAAC;IAChB,CAAC;YAAS,CAAC;QACT,IAAI,GAAG;YAAE,GAAG,CAAC,KAAK,EAAE,CAAC;QACrB,wEAAwE;QACxE,wEAAwE;QACxE,qCAAqC;QACrC,IAAI,KAAK,CAAC,QAAQ,KAAK,IAAI,EAAE,CAAC;YAC5B,wEAAwE;YACxE,IAAI,CAAC;gBAAC,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;YAAC,CAAC;YAAC,MAAM,CAAC,CAAC,YAAY,CAAC,CAAC;QACvD,CAAC;IACH,CAAC;AACH,CAAC"}
|
package/dist/auth/cdp.js
ADDED
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
// Minimal Chrome DevTools Protocol client over ws@8. Just enough to drive
|
|
2
|
+
// a single visible Chrome window: create a tab, navigate, poll location,
|
|
3
|
+
// read cookies, close the tab. No retries, no domain abstractions —
|
|
4
|
+
// callers compose commands themselves.
|
|
5
|
+
import WebSocket from "ws";
|
|
6
|
+
import { logger } from "../util/logger.js";
|
|
7
|
+
export class CdpClient {
|
|
8
|
+
ws;
|
|
9
|
+
nextId = 1;
|
|
10
|
+
pending = new Map();
|
|
11
|
+
listeners = new Map();
|
|
12
|
+
closed = false;
|
|
13
|
+
closeWaiters = [];
|
|
14
|
+
constructor(ws) {
|
|
15
|
+
this.ws = ws;
|
|
16
|
+
ws.on("message", (data) => this.handleMessage(data.toString("utf8")));
|
|
17
|
+
ws.on("close", () => {
|
|
18
|
+
this.closed = true;
|
|
19
|
+
for (const [, p] of this.pending) {
|
|
20
|
+
clearTimeout(p.timer);
|
|
21
|
+
p.reject(new Error(`CDP socket closed before '${p.method}' completed`));
|
|
22
|
+
}
|
|
23
|
+
this.pending.clear();
|
|
24
|
+
const waiters = this.closeWaiters;
|
|
25
|
+
this.closeWaiters = [];
|
|
26
|
+
for (const w of waiters)
|
|
27
|
+
w();
|
|
28
|
+
});
|
|
29
|
+
ws.on("error", (err) => logger.debug(`cdp ws error: ${err.message}`));
|
|
30
|
+
}
|
|
31
|
+
static async connect(url, timeoutMs = 10_000) {
|
|
32
|
+
const ws = new WebSocket(url, { perMessageDeflate: false, maxPayload: 256 * 1024 * 1024 });
|
|
33
|
+
await new Promise((resolve, reject) => {
|
|
34
|
+
const timer = setTimeout(() => reject(new Error(`CDP connect to ${url} timed out`)), timeoutMs);
|
|
35
|
+
ws.once("open", () => { clearTimeout(timer); resolve(); });
|
|
36
|
+
ws.once("error", (err) => { clearTimeout(timer); reject(err); });
|
|
37
|
+
});
|
|
38
|
+
return new CdpClient(ws);
|
|
39
|
+
}
|
|
40
|
+
handleMessage(text) {
|
|
41
|
+
let msg;
|
|
42
|
+
try {
|
|
43
|
+
msg = JSON.parse(text);
|
|
44
|
+
}
|
|
45
|
+
catch {
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
if (msg.id !== undefined) {
|
|
49
|
+
const p = this.pending.get(msg.id);
|
|
50
|
+
if (!p)
|
|
51
|
+
return;
|
|
52
|
+
this.pending.delete(msg.id);
|
|
53
|
+
clearTimeout(p.timer);
|
|
54
|
+
if (msg.error)
|
|
55
|
+
p.reject(new Error(`CDP ${p.method} failed: ${msg.error.message}`));
|
|
56
|
+
else
|
|
57
|
+
p.resolve(msg.result ?? {});
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
if (msg.method) {
|
|
61
|
+
const ls = this.listeners.get(msg.method);
|
|
62
|
+
if (ls)
|
|
63
|
+
for (const l of ls)
|
|
64
|
+
try {
|
|
65
|
+
l(msg.params ?? {}, msg.sessionId);
|
|
66
|
+
}
|
|
67
|
+
catch (e) {
|
|
68
|
+
logger.warn(`cdp listener for ${msg.method} threw: ${e.message}`);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
on(event, listener) {
|
|
73
|
+
const arr = this.listeners.get(event) ?? [];
|
|
74
|
+
arr.push(listener);
|
|
75
|
+
this.listeners.set(event, arr);
|
|
76
|
+
}
|
|
77
|
+
async send(method, params = {}, sessionId, timeoutMs = 15_000) {
|
|
78
|
+
if (this.closed)
|
|
79
|
+
throw new Error(`CDP socket already closed (sending ${method})`);
|
|
80
|
+
const id = this.nextId++;
|
|
81
|
+
const frame = { id, method, params };
|
|
82
|
+
if (sessionId)
|
|
83
|
+
frame.sessionId = sessionId;
|
|
84
|
+
return new Promise((resolve, reject) => {
|
|
85
|
+
const timer = setTimeout(() => {
|
|
86
|
+
this.pending.delete(id);
|
|
87
|
+
reject(new Error(`CDP ${method} timed out after ${timeoutMs}ms`));
|
|
88
|
+
}, timeoutMs);
|
|
89
|
+
this.pending.set(id, {
|
|
90
|
+
resolve: (r) => resolve(r),
|
|
91
|
+
reject,
|
|
92
|
+
timer,
|
|
93
|
+
method,
|
|
94
|
+
});
|
|
95
|
+
try {
|
|
96
|
+
this.ws.send(JSON.stringify(frame));
|
|
97
|
+
}
|
|
98
|
+
catch (err) {
|
|
99
|
+
clearTimeout(timer);
|
|
100
|
+
this.pending.delete(id);
|
|
101
|
+
reject(err);
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
isClosed() {
|
|
106
|
+
return this.closed;
|
|
107
|
+
}
|
|
108
|
+
onClose(cb) {
|
|
109
|
+
if (this.closed) {
|
|
110
|
+
cb();
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
this.closeWaiters.push(cb);
|
|
114
|
+
}
|
|
115
|
+
close() {
|
|
116
|
+
if (this.closed)
|
|
117
|
+
return;
|
|
118
|
+
try {
|
|
119
|
+
this.ws.close();
|
|
120
|
+
}
|
|
121
|
+
catch { /* ignore */ }
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
//# sourceMappingURL=cdp.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cdp.js","sourceRoot":"","sources":["../../src/auth/cdp.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
// Locate a Chromium-family browser on the user's system. We need any
|
|
2
|
+
// engine that speaks the Chrome DevTools Protocol — Chrome, Chromium,
|
|
3
|
+
// Brave, Edge, Arc all qualify and share the same flag/CDP surface.
|
|
4
|
+
import { promises as fs } from "node:fs";
|
|
5
|
+
import { execFile } from "node:child_process";
|
|
6
|
+
import path from "node:path";
|
|
7
|
+
import os from "node:os";
|
|
8
|
+
import { promisify } from "node:util";
|
|
9
|
+
const execFileP = promisify(execFile);
|
|
10
|
+
async function exists(p) {
|
|
11
|
+
try {
|
|
12
|
+
await fs.access(p, fs.constants.X_OK);
|
|
13
|
+
return true;
|
|
14
|
+
}
|
|
15
|
+
catch {
|
|
16
|
+
return false;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
async function whichOne(names) {
|
|
20
|
+
for (const name of names) {
|
|
21
|
+
try {
|
|
22
|
+
const { stdout } = await execFileP("which", [name]);
|
|
23
|
+
const line = stdout.trim().split("\n")[0]?.trim();
|
|
24
|
+
if (line && (await exists(line)))
|
|
25
|
+
return line;
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
// not on PATH, try next
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return undefined;
|
|
32
|
+
}
|
|
33
|
+
function macCandidates() {
|
|
34
|
+
return [
|
|
35
|
+
"/Applications/Google Chrome.app/Contents/MacOS/Google Chrome",
|
|
36
|
+
"/Applications/Google Chrome Canary.app/Contents/MacOS/Google Chrome Canary",
|
|
37
|
+
"/Applications/Brave Browser.app/Contents/MacOS/Brave Browser",
|
|
38
|
+
"/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge",
|
|
39
|
+
"/Applications/Arc.app/Contents/MacOS/Arc",
|
|
40
|
+
"/Applications/Chromium.app/Contents/MacOS/Chromium",
|
|
41
|
+
];
|
|
42
|
+
}
|
|
43
|
+
function windowsCandidates() {
|
|
44
|
+
const programFiles = process.env["ProgramFiles"] ?? "C:\\Program Files";
|
|
45
|
+
const programFilesX86 = process.env["ProgramFiles(x86)"] ?? "C:\\Program Files (x86)";
|
|
46
|
+
const localAppData = process.env["LOCALAPPDATA"] ?? path.join(os.homedir(), "AppData", "Local");
|
|
47
|
+
return [
|
|
48
|
+
path.join(programFiles, "Google", "Chrome", "Application", "chrome.exe"),
|
|
49
|
+
path.join(programFilesX86, "Google", "Chrome", "Application", "chrome.exe"),
|
|
50
|
+
path.join(localAppData, "Google", "Chrome", "Application", "chrome.exe"),
|
|
51
|
+
path.join(programFiles, "BraveSoftware", "Brave-Browser", "Application", "brave.exe"),
|
|
52
|
+
path.join(programFilesX86, "BraveSoftware", "Brave-Browser", "Application", "brave.exe"),
|
|
53
|
+
path.join(programFiles, "Microsoft", "Edge", "Application", "msedge.exe"),
|
|
54
|
+
path.join(programFilesX86, "Microsoft", "Edge", "Application", "msedge.exe"),
|
|
55
|
+
];
|
|
56
|
+
}
|
|
57
|
+
export async function findChrome(override) {
|
|
58
|
+
if (override) {
|
|
59
|
+
if (await exists(override))
|
|
60
|
+
return override;
|
|
61
|
+
throw new Error(`OL_BROWSER points to a missing or non-executable path: ${override}`);
|
|
62
|
+
}
|
|
63
|
+
if (process.platform === "darwin") {
|
|
64
|
+
for (const p of macCandidates()) {
|
|
65
|
+
if (await exists(p))
|
|
66
|
+
return p;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
else if (process.platform === "win32") {
|
|
70
|
+
for (const p of windowsCandidates()) {
|
|
71
|
+
if (await exists(p))
|
|
72
|
+
return p;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
else {
|
|
76
|
+
const hit = await whichOne([
|
|
77
|
+
"google-chrome",
|
|
78
|
+
"google-chrome-stable",
|
|
79
|
+
"chromium",
|
|
80
|
+
"chromium-browser",
|
|
81
|
+
"brave-browser",
|
|
82
|
+
"microsoft-edge",
|
|
83
|
+
"microsoft-edge-stable",
|
|
84
|
+
]);
|
|
85
|
+
if (hit)
|
|
86
|
+
return hit;
|
|
87
|
+
}
|
|
88
|
+
throw new Error("No Chromium-family browser found (Chrome / Brave / Edge / Arc / Chromium). " +
|
|
89
|
+
"Install one, or set OL_BROWSER to an explicit binary path.");
|
|
90
|
+
}
|
|
91
|
+
//# sourceMappingURL=chromeFinder.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"chromeFinder.js","sourceRoot":"","sources":["../../src/auth/chromeFinder.ts"],"names":[],"mappings":"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"}
|
package/dist/auth/cli.js
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
// `overleaf-mcp login | logout | status` subcommands. Invoked before the
|
|
2
|
+
// MCP stdio server boots. `login` always launches a Chrome window pointed
|
|
3
|
+
// at Overleaf and captures the session cookie via CDP — no paste path.
|
|
4
|
+
import { stdin, stdout } from "node:process";
|
|
5
|
+
import { loadConfig } from "../config.js";
|
|
6
|
+
import { validateCookie, clearIdentity } from "../session/identity.js";
|
|
7
|
+
import { OverleafAuthError } from "../api/errors.js";
|
|
8
|
+
import { saveStored, clearStored, loadStored, cookieFilePath } from "./cookieStore.js";
|
|
9
|
+
import { captureCookie } from "./browserLogin.js";
|
|
10
|
+
function writeOut(line) {
|
|
11
|
+
stdout.write(`${line}\n`);
|
|
12
|
+
}
|
|
13
|
+
function hostOf(baseUrl) {
|
|
14
|
+
return new URL(baseUrl).host;
|
|
15
|
+
}
|
|
16
|
+
async function runLogin() {
|
|
17
|
+
const config = loadConfig();
|
|
18
|
+
writeOut(`opening a Chrome window for ${hostOf(config.baseUrl)} (a profile dedicated to overleaf-mcp).`);
|
|
19
|
+
writeOut("complete login normally — captcha, SSO, 2FA all work because it's a real browser.");
|
|
20
|
+
let cookie;
|
|
21
|
+
try {
|
|
22
|
+
cookie = await captureCookie(config.baseUrl);
|
|
23
|
+
}
|
|
24
|
+
catch (err) {
|
|
25
|
+
writeOut(`login failed: ${err.message}`);
|
|
26
|
+
return 1;
|
|
27
|
+
}
|
|
28
|
+
await saveStored(config.baseUrl, cookie);
|
|
29
|
+
clearIdentity();
|
|
30
|
+
try {
|
|
31
|
+
const id = await validateCookie(cookie);
|
|
32
|
+
writeOut(`logged in as ${id.userEmail || id.userId} on ${hostOf(config.baseUrl)}`);
|
|
33
|
+
writeOut(`saved to ${cookieFilePath()}`);
|
|
34
|
+
return 0;
|
|
35
|
+
}
|
|
36
|
+
catch (err) {
|
|
37
|
+
await clearStored(config.baseUrl).catch(() => undefined);
|
|
38
|
+
writeOut(`login failed (cookie rejected): ${err.message}`);
|
|
39
|
+
return 1;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
async function runLogout(argv) {
|
|
43
|
+
const confirmed = argv.includes("--confirm") || stdin.isTTY;
|
|
44
|
+
if (!confirmed) {
|
|
45
|
+
writeOut("logout refused: pass --confirm when stdin is not a TTY (avoids accidental wipes in scripts).");
|
|
46
|
+
return 2;
|
|
47
|
+
}
|
|
48
|
+
const config = loadConfig();
|
|
49
|
+
const removed = await clearStored(config.baseUrl);
|
|
50
|
+
clearIdentity();
|
|
51
|
+
writeOut(removed ? `cleared cookie for ${hostOf(config.baseUrl)}` : `no stored cookie for ${hostOf(config.baseUrl)}`);
|
|
52
|
+
return 0;
|
|
53
|
+
}
|
|
54
|
+
async function runStatus() {
|
|
55
|
+
const config = loadConfig();
|
|
56
|
+
const host = hostOf(config.baseUrl);
|
|
57
|
+
const stored = await loadStored(config.baseUrl);
|
|
58
|
+
if (!stored) {
|
|
59
|
+
writeOut(`no cookie for ${host}. Run \`overleaf-mcp login\` first.`);
|
|
60
|
+
return 1;
|
|
61
|
+
}
|
|
62
|
+
const ageMs = Date.now() - stored.savedAt;
|
|
63
|
+
const days = Math.floor(ageMs / (24 * 60 * 60 * 1000));
|
|
64
|
+
const hours = Math.floor((ageMs % (24 * 60 * 60 * 1000)) / (60 * 60 * 1000));
|
|
65
|
+
writeOut(`file: ${cookieFilePath()}`);
|
|
66
|
+
writeOut(`host: ${host}`);
|
|
67
|
+
writeOut(`saved: ${new Date(stored.savedAt).toISOString()} (${days}d ${hours}h ago)`);
|
|
68
|
+
try {
|
|
69
|
+
const id = await validateCookie(stored.cookie);
|
|
70
|
+
writeOut(`identity: ${id.userEmail || id.userId}`);
|
|
71
|
+
return 0;
|
|
72
|
+
}
|
|
73
|
+
catch (err) {
|
|
74
|
+
if (err instanceof OverleafAuthError) {
|
|
75
|
+
writeOut(`validation failed: ${err.message}`);
|
|
76
|
+
}
|
|
77
|
+
else {
|
|
78
|
+
writeOut(`validation failed: ${err.message}`);
|
|
79
|
+
}
|
|
80
|
+
return 1;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
export async function maybeRunCli(argv) {
|
|
84
|
+
const cmd = argv[2];
|
|
85
|
+
if (cmd !== "login" && cmd !== "logout" && cmd !== "status")
|
|
86
|
+
return false;
|
|
87
|
+
let code = 0;
|
|
88
|
+
try {
|
|
89
|
+
if (cmd === "login")
|
|
90
|
+
code = await runLogin();
|
|
91
|
+
else if (cmd === "logout")
|
|
92
|
+
code = await runLogout(argv);
|
|
93
|
+
else
|
|
94
|
+
code = await runStatus();
|
|
95
|
+
}
|
|
96
|
+
catch (err) {
|
|
97
|
+
writeOut(`${cmd} failed: ${err.message}`);
|
|
98
|
+
code = 1;
|
|
99
|
+
}
|
|
100
|
+
process.exit(code);
|
|
101
|
+
}
|
|
102
|
+
//# sourceMappingURL=cli.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../../src/auth/cli.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
// Plaintext cookie persistence keyed by Overleaf host.
|
|
2
|
+
//
|
|
3
|
+
// Stores a single JSON map at <configDir>/overleaf-mcp/cookie.json with mode
|
|
4
|
+
// 0600. Multi-host so the same machine can hold credentials for overleaf.com
|
|
5
|
+
// and a self-hosted CE side-by-side. No encryption — see README.
|
|
6
|
+
import { promises as fs } from "node:fs";
|
|
7
|
+
import os from "node:os";
|
|
8
|
+
import path from "node:path";
|
|
9
|
+
import { logger } from "../util/logger.js";
|
|
10
|
+
function configRoot() {
|
|
11
|
+
if (process.platform === "darwin") {
|
|
12
|
+
return path.join(os.homedir(), "Library", "Application Support");
|
|
13
|
+
}
|
|
14
|
+
if (process.platform === "win32") {
|
|
15
|
+
return process.env.APPDATA ?? path.join(os.homedir(), "AppData", "Roaming");
|
|
16
|
+
}
|
|
17
|
+
return process.env.XDG_CONFIG_HOME ?? path.join(os.homedir(), ".config");
|
|
18
|
+
}
|
|
19
|
+
export function cookieFilePath() {
|
|
20
|
+
return path.join(configRoot(), "overleaf-mcp", "cookie.json");
|
|
21
|
+
}
|
|
22
|
+
function hostKey(baseUrl) {
|
|
23
|
+
return new URL(baseUrl).host;
|
|
24
|
+
}
|
|
25
|
+
async function readStore() {
|
|
26
|
+
try {
|
|
27
|
+
const raw = await fs.readFile(cookieFilePath(), "utf8");
|
|
28
|
+
const parsed = JSON.parse(raw);
|
|
29
|
+
return { hosts: parsed.hosts ?? {} };
|
|
30
|
+
}
|
|
31
|
+
catch (err) {
|
|
32
|
+
if (err?.code === "ENOENT")
|
|
33
|
+
return { hosts: {} };
|
|
34
|
+
logger.warn(`cookie store unreadable, treating as empty: ${err.message}`);
|
|
35
|
+
return { hosts: {} };
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
async function writeStore(store) {
|
|
39
|
+
const file = cookieFilePath();
|
|
40
|
+
await fs.mkdir(path.dirname(file), { recursive: true });
|
|
41
|
+
const tmp = `${file}.tmp`;
|
|
42
|
+
await fs.writeFile(tmp, JSON.stringify(store, null, 2), { mode: 0o600 });
|
|
43
|
+
await fs.chmod(tmp, 0o600);
|
|
44
|
+
await fs.rename(tmp, file);
|
|
45
|
+
await fs.chmod(file, 0o600).catch(() => undefined);
|
|
46
|
+
}
|
|
47
|
+
export async function loadStored(baseUrl) {
|
|
48
|
+
const store = await readStore();
|
|
49
|
+
return store.hosts[hostKey(baseUrl)] ?? null;
|
|
50
|
+
}
|
|
51
|
+
export async function saveStored(baseUrl, cookie) {
|
|
52
|
+
const store = await readStore();
|
|
53
|
+
store.hosts[hostKey(baseUrl)] = { cookie, savedAt: Date.now() };
|
|
54
|
+
await writeStore(store);
|
|
55
|
+
}
|
|
56
|
+
export async function clearStored(baseUrl) {
|
|
57
|
+
const store = await readStore();
|
|
58
|
+
const key = hostKey(baseUrl);
|
|
59
|
+
if (!(key in store.hosts))
|
|
60
|
+
return false;
|
|
61
|
+
delete store.hosts[key];
|
|
62
|
+
await writeStore(store);
|
|
63
|
+
return true;
|
|
64
|
+
}
|
|
65
|
+
//# sourceMappingURL=cookieStore.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cookieStore.js","sourceRoot":"","sources":["../../src/auth/cookieStore.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
// Cookie discovery orchestrator: file first, browser-launch as fallback.
|
|
2
|
+
// No env-var path — the file is the only persistent store, and a stale
|
|
3
|
+
// cookie always recovers by re-launching Chrome. Concurrent callers for
|
|
4
|
+
// the same host share one in-flight promise.
|
|
5
|
+
import { logger } from "../util/logger.js";
|
|
6
|
+
import { loadStored, saveStored, clearStored } from "./cookieStore.js";
|
|
7
|
+
import { captureCookie } from "./browserLogin.js";
|
|
8
|
+
const pending = new Map();
|
|
9
|
+
function hostKey(baseUrl) {
|
|
10
|
+
return new URL(baseUrl).host;
|
|
11
|
+
}
|
|
12
|
+
async function runDiscovery(baseUrl) {
|
|
13
|
+
const stored = await loadStored(baseUrl);
|
|
14
|
+
if (stored?.cookie) {
|
|
15
|
+
logger.debug(`auth: using stored cookie for ${hostKey(baseUrl)} (saved ${new Date(stored.savedAt).toISOString()})`);
|
|
16
|
+
return stored.cookie;
|
|
17
|
+
}
|
|
18
|
+
logger.info(`auth: no stored cookie for ${hostKey(baseUrl)}, launching browser to capture one`);
|
|
19
|
+
const captured = await captureCookie(baseUrl);
|
|
20
|
+
await saveStored(baseUrl, captured);
|
|
21
|
+
return captured;
|
|
22
|
+
}
|
|
23
|
+
export async function discoverCookie(baseUrl) {
|
|
24
|
+
const key = hostKey(baseUrl);
|
|
25
|
+
const existing = pending.get(key);
|
|
26
|
+
if (existing)
|
|
27
|
+
return existing;
|
|
28
|
+
const p = runDiscovery(baseUrl).finally(() => {
|
|
29
|
+
pending.delete(key);
|
|
30
|
+
});
|
|
31
|
+
pending.set(key, p);
|
|
32
|
+
return p;
|
|
33
|
+
}
|
|
34
|
+
export async function evictAndRediscover(baseUrl) {
|
|
35
|
+
await clearStored(baseUrl).catch(() => undefined);
|
|
36
|
+
pending.delete(hostKey(baseUrl));
|
|
37
|
+
return discoverCookie(baseUrl);
|
|
38
|
+
}
|
|
39
|
+
//# sourceMappingURL=discover.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"discover.js","sourceRoot":"","sources":["../../src/auth/discover.ts"],"names":[],"mappings":"AAAA,yEAAyE;AACzE,uEAAuE;AACvE,wEAAwE;AACxE,6CAA6C;AAE7C,OAAO,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAC3C,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AACvE,OAAO,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAElD,MAAM,OAAO,GAAG,IAAI,GAAG,EAA2B,CAAC;AAEnD,SAAS,OAAO,CAAC,OAAe;IAC9B,OAAO,IAAI,GAAG,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC;AAC/B,CAAC;AAED,KAAK,UAAU,YAAY,CAAC,OAAe;IACzC,MAAM,MAAM,GAAG,MAAM,UAAU,CAAC,OAAO,CAAC,CAAC;IACzC,IAAI,MAAM,EAAE,MAAM,EAAE,CAAC;QACnB,MAAM,CAAC,KAAK,CAAC,iCAAiC,OAAO,CAAC,OAAO,CAAC,WAAW,IAAI,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,WAAW,EAAE,GAAG,CAAC,CAAC;QACpH,OAAO,MAAM,CAAC,MAAM,CAAC;IACvB,CAAC;IACD,MAAM,CAAC,IAAI,CAAC,8BAA8B,OAAO,CAAC,OAAO,CAAC,oCAAoC,CAAC,CAAC;IAChG,MAAM,QAAQ,GAAG,MAAM,aAAa,CAAC,OAAO,CAAC,CAAC;IAC9C,MAAM,UAAU,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC;IACpC,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,cAAc,CAAC,OAAe;IAClD,MAAM,GAAG,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAC7B,MAAM,QAAQ,GAAG,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;IAClC,IAAI,QAAQ;QAAE,OAAO,QAAQ,CAAC;IAC9B,MAAM,CAAC,GAAG,YAAY,CAAC,OAAO,CAAC,CAAC,OAAO,CAAC,GAAG,EAAE;QAC3C,OAAO,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;IACtB,CAAC,CAAC,CAAC;IACH,OAAO,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC;IACpB,OAAO,CAAC,CAAC;AACX,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,kBAAkB,CAAC,OAAe;IACtD,MAAM,WAAW,CAAC,OAAO,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC;IAClD,OAAO,CAAC,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC;IACjC,OAAO,cAAc,CAAC,OAAO,CAAC,CAAC;AACjC,CAAC"}
|
package/dist/config.js
CHANGED
|
@@ -1,13 +1,11 @@
|
|
|
1
1
|
export function loadConfig() {
|
|
2
2
|
const rawBase = process.env.OL_BASE_URL ?? "https://www.overleaf.com";
|
|
3
3
|
const baseUrl = rawBase.replace(/\/+$/, "");
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
}
|
|
11
|
-
return { baseUrl, cookie, csrfOverride: process.env.OL_CSRF?.trim() || undefined };
|
|
4
|
+
return {
|
|
5
|
+
baseUrl,
|
|
6
|
+
csrfOverride: process.env.OL_CSRF?.trim() || undefined,
|
|
7
|
+
browserPath: process.env.OL_BROWSER?.trim() || undefined,
|
|
8
|
+
insecure: process.env.OL_INSECURE === "1",
|
|
9
|
+
};
|
|
12
10
|
}
|
|
13
11
|
//# sourceMappingURL=config.js.map
|
package/dist/config.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAOA,MAAM,UAAU,UAAU;IACxB,MAAM,OAAO,GAAG,OAAO,CAAC,GAAG,CAAC,WAAW,IAAI,0BAA0B,CAAC;IACtE,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;IAC5C,OAAO;QACL,OAAO;QACP,YAAY,EAAE,OAAO,CAAC,GAAG,CAAC,OAAO,EAAE,IAAI,EAAE,IAAI,SAAS;QACtD,WAAW,EAAE,OAAO,CAAC,GAAG,CAAC,UAAU,EAAE,IAAI,EAAE,IAAI,SAAS;QACxD,QAAQ,EAAE,OAAO,CAAC,GAAG,CAAC,WAAW,KAAK,GAAG;KAC1C,CAAC;AACJ,CAAC"}
|