perplexity-user-mcp 0.8.36
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 +192 -0
- package/dist/attachments.d.ts +20 -0
- package/dist/attachments.mjs +43 -0
- package/dist/checks/browser.d.ts +100 -0
- package/dist/checks/browser.mjs +89 -0
- package/dist/checks/config.d.ts +91 -0
- package/dist/checks/config.mjs +88 -0
- package/dist/checks/ide.d.ts +89 -0
- package/dist/checks/ide.mjs +80 -0
- package/dist/checks/mcp.d.ts +61 -0
- package/dist/checks/mcp.mjs +56 -0
- package/dist/checks/native-deps.d.ts +131 -0
- package/dist/checks/native-deps.mjs +115 -0
- package/dist/checks/network.d.ts +71 -0
- package/dist/checks/network.mjs +70 -0
- package/dist/checks/probe.d.ts +93 -0
- package/dist/checks/probe.mjs +82 -0
- package/dist/checks/profiles.d.ts +99 -0
- package/dist/checks/profiles.mjs +90 -0
- package/dist/checks/runtime.d.ts +89 -0
- package/dist/checks/runtime.mjs +90 -0
- package/dist/checks/vault.d.ts +101 -0
- package/dist/checks/vault.mjs +90 -0
- package/dist/chunk-3B276PGG.mjs +115 -0
- package/dist/chunk-4UEJOM6W.mjs +9 -0
- package/dist/chunk-6EP2BLTV.mjs +205 -0
- package/dist/chunk-6YMQVLFX.mjs +146 -0
- package/dist/chunk-7JL36EBH.mjs +118 -0
- package/dist/chunk-DPGMKSSA.mjs +57 -0
- package/dist/chunk-H4BUAPPO.mjs +1950 -0
- package/dist/chunk-HMKLWVXB.mjs +109 -0
- package/dist/chunk-HTUAQRKH.mjs +125 -0
- package/dist/chunk-HU5B4FXS.mjs +139 -0
- package/dist/chunk-KCXM2M4B.mjs +1006 -0
- package/dist/chunk-LKJMLGFP.mjs +237 -0
- package/dist/chunk-LZPLNZ5U.mjs +67 -0
- package/dist/chunk-MTDFKNXX.mjs +19 -0
- package/dist/chunk-OF4DMAPJ.mjs +511 -0
- package/dist/chunk-PE23RMXY.mjs +43 -0
- package/dist/chunk-Q2VY4R5F.mjs +175 -0
- package/dist/chunk-S5VD7WTU.mjs +2540 -0
- package/dist/chunk-SVPRB62V.mjs +106 -0
- package/dist/chunk-TQLCLE4L.mjs +345 -0
- package/dist/chunk-U3DGFLXZ.mjs +43 -0
- package/dist/chunk-X45O6YD3.mjs +688 -0
- package/dist/chunk-XKSWCEGI.mjs +168 -0
- package/dist/chunk-Z7DAACGZ.mjs +534 -0
- package/dist/chunk-ZQFUZPLO.mjs +257 -0
- package/dist/cli.d.ts +952 -0
- package/dist/cli.mjs +827 -0
- package/dist/client.d.ts +355 -0
- package/dist/client.mjs +27 -0
- package/dist/cloud-sync.d-Cqt6y18U.d.ts +42 -0
- package/dist/cloud-sync.d.ts +42 -0
- package/dist/cloud-sync.mjs +17 -0
- package/dist/config.d.ts +186 -0
- package/dist/config.mjs +54 -0
- package/dist/daemon/attach.d.ts +36 -0
- package/dist/daemon/attach.mjs +25 -0
- package/dist/daemon/audit.d.ts +23 -0
- package/dist/daemon/audit.mjs +12 -0
- package/dist/daemon/client-http.d.ts +42 -0
- package/dist/daemon/client-http.mjs +29 -0
- package/dist/daemon/index.d.ts +14 -0
- package/dist/daemon/index.mjs +110 -0
- package/dist/daemon/install-tunnel.d.ts +46 -0
- package/dist/daemon/install-tunnel.mjs +14 -0
- package/dist/daemon/launcher.d.ts +163 -0
- package/dist/daemon/launcher.mjs +50 -0
- package/dist/daemon/lockfile.d.ts +29 -0
- package/dist/daemon/lockfile.mjs +18 -0
- package/dist/daemon/server.d.ts +159 -0
- package/dist/daemon/server.mjs +20 -0
- package/dist/daemon/token.d.ts +17 -0
- package/dist/daemon/token.mjs +17 -0
- package/dist/daemon/tunnel-providers/index.d.ts +330 -0
- package/dist/daemon/tunnel-providers/index.mjs +57 -0
- package/dist/daemon/tunnel.d.ts +23 -0
- package/dist/daemon/tunnel.mjs +9 -0
- package/dist/doctor-report.d.ts +24 -0
- package/dist/doctor-report.mjs +14 -0
- package/dist/doctor.d-CXmUqOXX.d.ts +43 -0
- package/dist/doctor.d.ts +44 -0
- package/dist/doctor.mjs +16 -0
- package/dist/export.d.ts +19 -0
- package/dist/export.mjs +15 -0
- package/dist/health-check.d.ts +108 -0
- package/dist/health-check.mjs +92 -0
- package/dist/history-store.d-BzjBF2m3.d.ts +65 -0
- package/dist/history-store.d.ts +65 -0
- package/dist/history-store.mjs +48 -0
- package/dist/impit-login-runner.d.ts +469 -0
- package/dist/impit-login-runner.mjs +685 -0
- package/dist/index.d.ts +159 -0
- package/dist/index.mjs +236 -0
- package/dist/login-runner.d.ts +333 -0
- package/dist/login-runner.mjs +320 -0
- package/dist/logout.d.ts +28 -0
- package/dist/logout.mjs +45 -0
- package/dist/manual-login-runner.d.ts +150 -0
- package/dist/manual-login-runner.mjs +146 -0
- package/dist/native-deps-BNThFHxa.d.ts +175 -0
- package/dist/native-deps-YNKXITRY.mjs +139 -0
- package/dist/profiles.d-DqS1oZWr.d.ts +41 -0
- package/dist/profiles.d.ts +41 -0
- package/dist/profiles.mjs +33 -0
- package/dist/redact.d.ts +159 -0
- package/dist/redact.mjs +11 -0
- package/dist/refresh.d.ts +118 -0
- package/dist/refresh.mjs +21 -0
- package/dist/reinit-watcher.d.ts +15 -0
- package/dist/reinit-watcher.mjs +8 -0
- package/dist/session-metadata-B9aV_n5g.d.ts +148 -0
- package/dist/tty-prompt.d.ts +44 -0
- package/dist/tty-prompt.mjs +39 -0
- package/dist/vault.d-BtRSLZiM.d.ts +8 -0
- package/dist/vault.d.ts +37 -0
- package/dist/vault.mjs +21 -0
- package/dist/viewer-detect.d-HWGnyFAA.d.ts +4 -0
- package/dist/viewer-detect.d.ts +4 -0
- package/dist/viewer-detect.mjs +37 -0
- package/dist/viewers.d-BGCK6sw6.d.ts +10 -0
- package/dist/viewers.d.ts +18 -0
- package/dist/viewers.mjs +122 -0
- package/package.json +152 -0
|
@@ -0,0 +1,685 @@
|
|
|
1
|
+
import {
|
|
2
|
+
buildRuntimeEndpoints
|
|
3
|
+
} from "./chunk-HU5B4FXS.mjs";
|
|
4
|
+
import {
|
|
5
|
+
redact
|
|
6
|
+
} from "./chunk-HMKLWVXB.mjs";
|
|
7
|
+
import {
|
|
8
|
+
isImpitAvailable,
|
|
9
|
+
loadImpit
|
|
10
|
+
} from "./chunk-Z7DAACGZ.mjs";
|
|
11
|
+
import {
|
|
12
|
+
PERPLEXITY_URL,
|
|
13
|
+
findBrowser,
|
|
14
|
+
getOrCreateContext,
|
|
15
|
+
resolveBrowserExecutable
|
|
16
|
+
} from "./chunk-LKJMLGFP.mjs";
|
|
17
|
+
import {
|
|
18
|
+
Vault
|
|
19
|
+
} from "./chunk-TQLCLE4L.mjs";
|
|
20
|
+
import "./chunk-MTDFKNXX.mjs";
|
|
21
|
+
import {
|
|
22
|
+
getActiveName,
|
|
23
|
+
getProfilePaths,
|
|
24
|
+
recordLoginSuccess
|
|
25
|
+
} from "./chunk-XKSWCEGI.mjs";
|
|
26
|
+
import "./chunk-4UEJOM6W.mjs";
|
|
27
|
+
|
|
28
|
+
// src/impit-login-runner.js
|
|
29
|
+
import { writeFileSync, existsSync, mkdirSync } from "fs";
|
|
30
|
+
|
|
31
|
+
// src/cookie-jar.js
|
|
32
|
+
var SESSION_EXPIRES = -1;
|
|
33
|
+
function parseSetCookie(header, requestUrlObj) {
|
|
34
|
+
if (typeof header !== "string" || header.length === 0) return null;
|
|
35
|
+
const parts = header.split(";");
|
|
36
|
+
const first = parts.shift();
|
|
37
|
+
if (!first) return null;
|
|
38
|
+
const eq = first.indexOf("=");
|
|
39
|
+
if (eq < 0) return null;
|
|
40
|
+
const name = first.slice(0, eq).trim();
|
|
41
|
+
const value = first.slice(eq + 1).trim();
|
|
42
|
+
if (!name) return null;
|
|
43
|
+
let domain = requestUrlObj.hostname.toLowerCase();
|
|
44
|
+
let hostOnly = true;
|
|
45
|
+
let path = defaultPath(requestUrlObj.pathname);
|
|
46
|
+
let expires = SESSION_EXPIRES;
|
|
47
|
+
let maxAgeSeconds = null;
|
|
48
|
+
let secure = false;
|
|
49
|
+
let httpOnly = false;
|
|
50
|
+
let sameSite;
|
|
51
|
+
for (const raw of parts) {
|
|
52
|
+
if (!raw) continue;
|
|
53
|
+
const trimmed = raw.trim();
|
|
54
|
+
if (!trimmed) continue;
|
|
55
|
+
const aEq = trimmed.indexOf("=");
|
|
56
|
+
const attrName = (aEq < 0 ? trimmed : trimmed.slice(0, aEq)).trim().toLowerCase();
|
|
57
|
+
const attrValue = aEq < 0 ? "" : trimmed.slice(aEq + 1).trim();
|
|
58
|
+
switch (attrName) {
|
|
59
|
+
case "domain": {
|
|
60
|
+
if (!attrValue) break;
|
|
61
|
+
const stripped = attrValue.replace(/^\./, "").toLowerCase();
|
|
62
|
+
if (stripped) {
|
|
63
|
+
domain = stripped;
|
|
64
|
+
hostOnly = false;
|
|
65
|
+
}
|
|
66
|
+
break;
|
|
67
|
+
}
|
|
68
|
+
case "path": {
|
|
69
|
+
if (attrValue.startsWith("/")) path = attrValue;
|
|
70
|
+
break;
|
|
71
|
+
}
|
|
72
|
+
case "expires": {
|
|
73
|
+
const ts = Date.parse(attrValue);
|
|
74
|
+
if (!Number.isNaN(ts)) expires = Math.floor(ts / 1e3);
|
|
75
|
+
break;
|
|
76
|
+
}
|
|
77
|
+
case "max-age": {
|
|
78
|
+
if (/^-?\d+$/.test(attrValue)) {
|
|
79
|
+
maxAgeSeconds = Number(attrValue);
|
|
80
|
+
}
|
|
81
|
+
break;
|
|
82
|
+
}
|
|
83
|
+
case "secure":
|
|
84
|
+
secure = true;
|
|
85
|
+
break;
|
|
86
|
+
case "httponly":
|
|
87
|
+
httpOnly = true;
|
|
88
|
+
break;
|
|
89
|
+
case "samesite": {
|
|
90
|
+
const v = attrValue.toLowerCase();
|
|
91
|
+
if (v === "strict") sameSite = "Strict";
|
|
92
|
+
else if (v === "lax") sameSite = "Lax";
|
|
93
|
+
else if (v === "none") sameSite = "None";
|
|
94
|
+
break;
|
|
95
|
+
}
|
|
96
|
+
default:
|
|
97
|
+
break;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
if (maxAgeSeconds !== null) {
|
|
101
|
+
expires = maxAgeSeconds <= 0 ? 0 : Math.floor(Date.now() / 1e3) + maxAgeSeconds;
|
|
102
|
+
}
|
|
103
|
+
return { name, value, domain, path, expires, secure, httpOnly, sameSite, hostOnly };
|
|
104
|
+
}
|
|
105
|
+
function defaultPath(uriPath) {
|
|
106
|
+
if (!uriPath || !uriPath.startsWith("/")) return "/";
|
|
107
|
+
const lastSlash = uriPath.lastIndexOf("/");
|
|
108
|
+
if (lastSlash <= 0) return "/";
|
|
109
|
+
return uriPath.slice(0, lastSlash);
|
|
110
|
+
}
|
|
111
|
+
function isExpired(cookie, nowSeconds) {
|
|
112
|
+
if (cookie.expires === void 0) return false;
|
|
113
|
+
if (cookie.expires === SESSION_EXPIRES) return false;
|
|
114
|
+
return cookie.expires <= nowSeconds;
|
|
115
|
+
}
|
|
116
|
+
function domainMatches(cookieDomain, hostOnly, requestHost) {
|
|
117
|
+
if (cookieDomain === requestHost) return true;
|
|
118
|
+
if (hostOnly) return false;
|
|
119
|
+
if (!requestHost.endsWith(cookieDomain)) return false;
|
|
120
|
+
const idx = requestHost.length - cookieDomain.length;
|
|
121
|
+
if (idx <= 0) return false;
|
|
122
|
+
return requestHost[idx - 1] === ".";
|
|
123
|
+
}
|
|
124
|
+
function pathMatches(cookiePath, requestPath) {
|
|
125
|
+
if (cookiePath === requestPath) return true;
|
|
126
|
+
if (!requestPath.startsWith(cookiePath)) return false;
|
|
127
|
+
if (cookiePath.endsWith("/")) return true;
|
|
128
|
+
return requestPath[cookiePath.length] === "/";
|
|
129
|
+
}
|
|
130
|
+
function cookieKey(name, domain, path) {
|
|
131
|
+
return `${name}\0${domain.toLowerCase()}\0${path}`;
|
|
132
|
+
}
|
|
133
|
+
function entryFromPlaywright(c) {
|
|
134
|
+
let domain = (c.domain || "").toLowerCase();
|
|
135
|
+
let hostOnly = true;
|
|
136
|
+
if (domain.startsWith(".")) {
|
|
137
|
+
domain = domain.slice(1);
|
|
138
|
+
hostOnly = false;
|
|
139
|
+
}
|
|
140
|
+
const expires = typeof c.expires === "number" ? c.expires : SESSION_EXPIRES;
|
|
141
|
+
return {
|
|
142
|
+
name: String(c.name),
|
|
143
|
+
value: String(c.value),
|
|
144
|
+
domain,
|
|
145
|
+
path: c.path || "/",
|
|
146
|
+
expires,
|
|
147
|
+
secure: !!c.secure,
|
|
148
|
+
httpOnly: !!c.httpOnly,
|
|
149
|
+
sameSite: c.sameSite,
|
|
150
|
+
hostOnly
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
var CookieJar = class {
|
|
154
|
+
/**
|
|
155
|
+
* @param {PlaywrightCookie[]} [initialCookies]
|
|
156
|
+
*/
|
|
157
|
+
constructor(initialCookies = []) {
|
|
158
|
+
this._store = /* @__PURE__ */ new Map();
|
|
159
|
+
if (Array.isArray(initialCookies)) {
|
|
160
|
+
for (const c of initialCookies) {
|
|
161
|
+
if (!c || typeof c.name !== "string") continue;
|
|
162
|
+
const entry = entryFromPlaywright(c);
|
|
163
|
+
this._store.set(cookieKey(entry.name, entry.domain, entry.path), entry);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* Apply one or more Set-Cookie headers from a response. Accepts either a
|
|
169
|
+
* single string or an array (e.g. `res.headers.getSetCookie?.()` /
|
|
170
|
+
* `res.headers.raw()['set-cookie']`).
|
|
171
|
+
*
|
|
172
|
+
* @param {string|string[]|null|undefined} header
|
|
173
|
+
* @param {string} requestUrl
|
|
174
|
+
*/
|
|
175
|
+
consumeSetCookieHeader(header, requestUrl) {
|
|
176
|
+
if (!header) return;
|
|
177
|
+
let url;
|
|
178
|
+
try {
|
|
179
|
+
url = new URL(requestUrl);
|
|
180
|
+
} catch {
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
const headers = Array.isArray(header) ? header : [header];
|
|
184
|
+
for (const h of headers) {
|
|
185
|
+
const parsed = parseSetCookie(h, url);
|
|
186
|
+
if (!parsed) continue;
|
|
187
|
+
const key = cookieKey(parsed.name, parsed.domain, parsed.path);
|
|
188
|
+
if (parsed.expires !== SESSION_EXPIRES && parsed.expires <= 0) {
|
|
189
|
+
this._store.delete(key);
|
|
190
|
+
continue;
|
|
191
|
+
}
|
|
192
|
+
this._store.set(key, parsed);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* Build a `Cookie:` header value for `requestUrl`. Returns "" if no cookies
|
|
197
|
+
* apply (so the caller can do `if (h) headers.cookie = h`).
|
|
198
|
+
*
|
|
199
|
+
* @param {string} requestUrl
|
|
200
|
+
* @returns {string}
|
|
201
|
+
*/
|
|
202
|
+
buildCookieHeader(requestUrl) {
|
|
203
|
+
let url;
|
|
204
|
+
try {
|
|
205
|
+
url = new URL(requestUrl);
|
|
206
|
+
} catch {
|
|
207
|
+
return "";
|
|
208
|
+
}
|
|
209
|
+
const isHttps = url.protocol === "https:";
|
|
210
|
+
const host = url.hostname.toLowerCase();
|
|
211
|
+
const reqPath = url.pathname || "/";
|
|
212
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
213
|
+
const matched = [];
|
|
214
|
+
for (const cookie of this._store.values()) {
|
|
215
|
+
if (isExpired(cookie, now)) continue;
|
|
216
|
+
if (cookie.secure && !isHttps) continue;
|
|
217
|
+
if (!domainMatches(cookie.domain, cookie.hostOnly, host)) continue;
|
|
218
|
+
if (!pathMatches(cookie.path, reqPath)) continue;
|
|
219
|
+
matched.push(cookie);
|
|
220
|
+
}
|
|
221
|
+
matched.sort((a, b) => b.path.length - a.path.length);
|
|
222
|
+
return matched.map((c) => `${c.name}=${c.value}`).join("; ");
|
|
223
|
+
}
|
|
224
|
+
/**
|
|
225
|
+
* Snapshot of every cookie in the jar (including expired ones — callers that
|
|
226
|
+
* persist to disk want the full set so a refresh can simply overwrite).
|
|
227
|
+
*
|
|
228
|
+
* @returns {PlaywrightCookie[]}
|
|
229
|
+
*/
|
|
230
|
+
toPlaywrightShape() {
|
|
231
|
+
const out = [];
|
|
232
|
+
for (const c of this._store.values()) {
|
|
233
|
+
const pc = {
|
|
234
|
+
name: c.name,
|
|
235
|
+
value: c.value,
|
|
236
|
+
// Round-trip leading-dot for domain cookies so Patchright + the
|
|
237
|
+
// existing `getSavedCookies()` consumers see the same shape they do
|
|
238
|
+
// today.
|
|
239
|
+
domain: c.hostOnly ? c.domain : `.${c.domain}`,
|
|
240
|
+
path: c.path,
|
|
241
|
+
expires: c.expires,
|
|
242
|
+
secure: c.secure,
|
|
243
|
+
httpOnly: c.httpOnly
|
|
244
|
+
};
|
|
245
|
+
if (c.sameSite) pc.sameSite = c.sameSite;
|
|
246
|
+
out.push(pc);
|
|
247
|
+
}
|
|
248
|
+
return out;
|
|
249
|
+
}
|
|
250
|
+
/**
|
|
251
|
+
* Explicit setter, mostly for tests and for the rare case where the caller
|
|
252
|
+
* already has a fully-formed cookie record (e.g. seeding cf_clearance from
|
|
253
|
+
* the vault). `attributes` may include any of the PlaywrightCookie fields
|
|
254
|
+
* except name/value.
|
|
255
|
+
*
|
|
256
|
+
* @param {string} name
|
|
257
|
+
* @param {string} value
|
|
258
|
+
* @param {Partial<PlaywrightCookie> & { hostOnly?: boolean }} [attributes]
|
|
259
|
+
*/
|
|
260
|
+
set(name, value, attributes = {}) {
|
|
261
|
+
const domainRaw = (attributes.domain || "").toLowerCase();
|
|
262
|
+
let domain = domainRaw;
|
|
263
|
+
let hostOnly;
|
|
264
|
+
if (typeof attributes.hostOnly === "boolean") {
|
|
265
|
+
hostOnly = attributes.hostOnly;
|
|
266
|
+
if (domain.startsWith(".")) domain = domain.slice(1);
|
|
267
|
+
} else if (domain.startsWith(".")) {
|
|
268
|
+
hostOnly = false;
|
|
269
|
+
domain = domain.slice(1);
|
|
270
|
+
} else {
|
|
271
|
+
hostOnly = true;
|
|
272
|
+
}
|
|
273
|
+
if (!domain) {
|
|
274
|
+
return;
|
|
275
|
+
}
|
|
276
|
+
const entry = {
|
|
277
|
+
name: String(name),
|
|
278
|
+
value: String(value),
|
|
279
|
+
domain,
|
|
280
|
+
path: attributes.path || "/",
|
|
281
|
+
expires: typeof attributes.expires === "number" ? attributes.expires : SESSION_EXPIRES,
|
|
282
|
+
secure: !!attributes.secure,
|
|
283
|
+
httpOnly: !!attributes.httpOnly,
|
|
284
|
+
sameSite: attributes.sameSite,
|
|
285
|
+
hostOnly
|
|
286
|
+
};
|
|
287
|
+
this._store.set(cookieKey(entry.name, entry.domain, entry.path), entry);
|
|
288
|
+
}
|
|
289
|
+
};
|
|
290
|
+
|
|
291
|
+
// src/cf-warmup.ts
|
|
292
|
+
var DEFAULTS = {
|
|
293
|
+
navigationTimeoutMs: 15e3,
|
|
294
|
+
pollIntervalMs: 250,
|
|
295
|
+
cookieWaitMs: 1e4
|
|
296
|
+
};
|
|
297
|
+
async function warmCloudflare(opts = {}) {
|
|
298
|
+
const navigationTimeoutMs = opts.navigationTimeoutMs ?? DEFAULTS.navigationTimeoutMs;
|
|
299
|
+
const pollIntervalMs = opts.pollIntervalMs ?? DEFAULTS.pollIntervalMs;
|
|
300
|
+
const cookieWaitMs = opts.cookieWaitMs ?? DEFAULTS.cookieWaitMs;
|
|
301
|
+
const started = Date.now();
|
|
302
|
+
let resolved;
|
|
303
|
+
try {
|
|
304
|
+
resolved = await resolveBrowserExecutable();
|
|
305
|
+
} catch (err) {
|
|
306
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
307
|
+
const oneLine = msg.split("\n")[0] ?? msg;
|
|
308
|
+
console.error(`[cf-warmup] error: ${oneLine}`);
|
|
309
|
+
return {
|
|
310
|
+
ok: false,
|
|
311
|
+
cookies: [],
|
|
312
|
+
hasCfClearance: false,
|
|
313
|
+
elapsedMs: Date.now() - started,
|
|
314
|
+
error: oneLine
|
|
315
|
+
};
|
|
316
|
+
}
|
|
317
|
+
let chromium;
|
|
318
|
+
try {
|
|
319
|
+
chromium = (await import("patchright")).chromium;
|
|
320
|
+
} catch (err) {
|
|
321
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
322
|
+
console.error(`[cf-warmup] error: patchright import failed: ${msg}`);
|
|
323
|
+
return {
|
|
324
|
+
ok: false,
|
|
325
|
+
cookies: [],
|
|
326
|
+
hasCfClearance: false,
|
|
327
|
+
elapsedMs: Date.now() - started,
|
|
328
|
+
error: `patchright missing: ${msg}`
|
|
329
|
+
};
|
|
330
|
+
}
|
|
331
|
+
console.error(`[cf-warmup] launching ${resolved.path}`);
|
|
332
|
+
const probe = findBrowser();
|
|
333
|
+
let browser = null;
|
|
334
|
+
let cookies = [];
|
|
335
|
+
let hasCfClearance = false;
|
|
336
|
+
let ok = false;
|
|
337
|
+
let error;
|
|
338
|
+
try {
|
|
339
|
+
browser = await chromium.launch({
|
|
340
|
+
headless: true,
|
|
341
|
+
// Minimal arg set — reusing the launch shape from refresh.ts::tierBrowser
|
|
342
|
+
// (which already proves out CF resolution under headless). Notably we
|
|
343
|
+
// strip Playwright's default `--enable-automation` flag since CF
|
|
344
|
+
// fingerprints it.
|
|
345
|
+
...probe ? { executablePath: probe.path } : {},
|
|
346
|
+
...probe && (probe.channel === "chrome" || probe.channel === "msedge" || probe.channel === "chromium") ? { channel: probe.channel } : {},
|
|
347
|
+
ignoreDefaultArgs: ["--enable-automation"]
|
|
348
|
+
});
|
|
349
|
+
const ctx = await getOrCreateContext(browser);
|
|
350
|
+
const page = await ctx.newPage();
|
|
351
|
+
await page.goto(PERPLEXITY_URL, {
|
|
352
|
+
waitUntil: "domcontentloaded",
|
|
353
|
+
timeout: navigationTimeoutMs
|
|
354
|
+
});
|
|
355
|
+
ok = true;
|
|
356
|
+
const pollStarted = Date.now();
|
|
357
|
+
while (Date.now() - pollStarted < cookieWaitMs) {
|
|
358
|
+
const current = await ctx.cookies();
|
|
359
|
+
if (current.some((c) => c.name === "cf_clearance")) {
|
|
360
|
+
cookies = current;
|
|
361
|
+
hasCfClearance = true;
|
|
362
|
+
break;
|
|
363
|
+
}
|
|
364
|
+
await new Promise((resolve) => setTimeout(resolve, pollIntervalMs));
|
|
365
|
+
}
|
|
366
|
+
if (!hasCfClearance) {
|
|
367
|
+
cookies = await ctx.cookies();
|
|
368
|
+
}
|
|
369
|
+
} catch (err) {
|
|
370
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
371
|
+
console.error(`[cf-warmup] error: ${msg}`);
|
|
372
|
+
ok = false;
|
|
373
|
+
error = msg;
|
|
374
|
+
} finally {
|
|
375
|
+
if (browser) {
|
|
376
|
+
try {
|
|
377
|
+
await browser.close();
|
|
378
|
+
} catch {
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
const elapsedMs = Date.now() - started;
|
|
383
|
+
console.error(
|
|
384
|
+
`[cf-warmup] complete: cf_clearance=${hasCfClearance} cookies=${cookies.length} elapsedMs=${elapsedMs}`
|
|
385
|
+
);
|
|
386
|
+
return { ok, cookies, hasCfClearance, elapsedMs, ...error ? { error } : {} };
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
// src/impit-login-runner.js
|
|
390
|
+
var ORIGIN = process.env.PERPLEXITY_ORIGIN || "https://www.perplexity.ai";
|
|
391
|
+
var EMAIL = process.env.PERPLEXITY_EMAIL;
|
|
392
|
+
var OTP_TIMEOUT_MS = Number(process.env.PERPLEXITY_OTP_TIMEOUT_MS ?? 3e5);
|
|
393
|
+
var MAX_RETRIES = Number(process.env.PERPLEXITY_LOGIN_MAX_RETRIES ?? 2);
|
|
394
|
+
var USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36";
|
|
395
|
+
var CHROME_CLIENT_HINTS = {
|
|
396
|
+
"sec-ch-ua": '"Not)A;Brand";v="8", "Chromium";v="138", "Google Chrome";v="138"',
|
|
397
|
+
"sec-ch-ua-mobile": "?0",
|
|
398
|
+
"sec-ch-ua-platform": '"Windows"'
|
|
399
|
+
};
|
|
400
|
+
function resolveProfile() {
|
|
401
|
+
return process.env.PERPLEXITY_PROFILE || getActiveName() || "default";
|
|
402
|
+
}
|
|
403
|
+
function ipc(msg) {
|
|
404
|
+
if (process.send) process.send(msg);
|
|
405
|
+
}
|
|
406
|
+
function emit(obj) {
|
|
407
|
+
process.stdout.write(JSON.stringify(obj) + "\n");
|
|
408
|
+
}
|
|
409
|
+
async function awaitOtp() {
|
|
410
|
+
return new Promise((resolve, reject) => {
|
|
411
|
+
const timer = setTimeout(() => {
|
|
412
|
+
process.removeListener("message", handler);
|
|
413
|
+
reject(new Error("otp_timeout"));
|
|
414
|
+
}, OTP_TIMEOUT_MS);
|
|
415
|
+
const handler = (m) => {
|
|
416
|
+
if (m && typeof m.otp === "string") {
|
|
417
|
+
clearTimeout(timer);
|
|
418
|
+
process.removeListener("message", handler);
|
|
419
|
+
resolve(m.otp);
|
|
420
|
+
}
|
|
421
|
+
};
|
|
422
|
+
process.on("message", handler);
|
|
423
|
+
});
|
|
424
|
+
}
|
|
425
|
+
async function impitJsonRequest(client, jar, url, init = {}) {
|
|
426
|
+
const cookieHeader = jar.buildCookieHeader(url);
|
|
427
|
+
const headers = {
|
|
428
|
+
"user-agent": USER_AGENT,
|
|
429
|
+
accept: "application/json, text/plain, */*",
|
|
430
|
+
"accept-language": "en-US,en;q=0.9",
|
|
431
|
+
...CHROME_CLIENT_HINTS,
|
|
432
|
+
...cookieHeader ? { cookie: cookieHeader } : {},
|
|
433
|
+
...init.body !== void 0 ? { "content-type": "application/json" } : {},
|
|
434
|
+
referer: `${ORIGIN}/`,
|
|
435
|
+
origin: ORIGIN,
|
|
436
|
+
...init.headers ?? {}
|
|
437
|
+
};
|
|
438
|
+
const ctrl = new AbortController();
|
|
439
|
+
const to = setTimeout(() => ctrl.abort(), init.timeoutMs ?? 3e4);
|
|
440
|
+
try {
|
|
441
|
+
const res = await client.fetch(url, {
|
|
442
|
+
method: init.method ?? "GET",
|
|
443
|
+
headers,
|
|
444
|
+
body: init.body !== void 0 ? JSON.stringify(init.body) : void 0,
|
|
445
|
+
signal: ctrl.signal,
|
|
446
|
+
redirect: init.redirect ?? "manual"
|
|
447
|
+
});
|
|
448
|
+
const text = await res.text();
|
|
449
|
+
const setCookies = readSetCookies(res.headers);
|
|
450
|
+
if (setCookies.length) jar.consumeSetCookieHeader(setCookies, url);
|
|
451
|
+
let json;
|
|
452
|
+
if (text) {
|
|
453
|
+
try {
|
|
454
|
+
json = JSON.parse(text);
|
|
455
|
+
} catch {
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
return { status: res.status, headers: res.headers, text, json };
|
|
459
|
+
} finally {
|
|
460
|
+
clearTimeout(to);
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
function readSetCookies(headers) {
|
|
464
|
+
if (!headers) return [];
|
|
465
|
+
if (typeof headers.getSetCookie === "function") {
|
|
466
|
+
return headers.getSetCookie();
|
|
467
|
+
}
|
|
468
|
+
if (typeof headers.raw === "function") {
|
|
469
|
+
const raw = headers.raw();
|
|
470
|
+
return raw["set-cookie"] ?? [];
|
|
471
|
+
}
|
|
472
|
+
const lower = Object.keys(headers).find((k) => k.toLowerCase() === "set-cookie");
|
|
473
|
+
if (!lower) return [];
|
|
474
|
+
const v = headers[lower];
|
|
475
|
+
return Array.isArray(v) ? v : [v];
|
|
476
|
+
}
|
|
477
|
+
async function followRedirectsManually(client, jar, startUrl, init = {}, maxHops = 10) {
|
|
478
|
+
let url = startUrl;
|
|
479
|
+
let lastStatus = 0;
|
|
480
|
+
for (let hop = 0; hop < maxHops; hop++) {
|
|
481
|
+
const result = await impitJsonRequest(client, jar, url, { ...init, redirect: "manual", method: hop === 0 ? init.method ?? "GET" : "GET" });
|
|
482
|
+
lastStatus = result.status;
|
|
483
|
+
if (result.status >= 300 && result.status < 400) {
|
|
484
|
+
const loc = readHeader(result.headers, "location");
|
|
485
|
+
if (!loc) return { ok: false, status: result.status, finalUrl: url };
|
|
486
|
+
url = new URL(loc, url).toString();
|
|
487
|
+
init.body = void 0;
|
|
488
|
+
continue;
|
|
489
|
+
}
|
|
490
|
+
return { ok: result.status >= 200 && result.status < 300, status: result.status, finalUrl: url, json: result.json, text: result.text };
|
|
491
|
+
}
|
|
492
|
+
return { ok: false, status: lastStatus, finalUrl: url, error: "max_redirects" };
|
|
493
|
+
}
|
|
494
|
+
function readHeader(headers, name) {
|
|
495
|
+
if (!headers) return null;
|
|
496
|
+
if (typeof headers.get === "function") return headers.get(name);
|
|
497
|
+
const key = Object.keys(headers).find((k) => k.toLowerCase() === name.toLowerCase());
|
|
498
|
+
return key ? headers[key] : null;
|
|
499
|
+
}
|
|
500
|
+
async function startEmailFlow(client, jar) {
|
|
501
|
+
const endpoints = buildRuntimeEndpoints(ORIGIN);
|
|
502
|
+
const csrf = await impitJsonRequest(client, jar, endpoints.csrf);
|
|
503
|
+
if (csrf.status !== 200 || !csrf.json?.csrfToken) {
|
|
504
|
+
return { kind: "unsupported", detail: { step: "csrf", status: csrf.status } };
|
|
505
|
+
}
|
|
506
|
+
const sso = await impitJsonRequest(client, jar, endpoints.ssoDetails, {
|
|
507
|
+
method: "POST",
|
|
508
|
+
body: { email: EMAIL }
|
|
509
|
+
});
|
|
510
|
+
if (sso.status === 200 && sso.json?.organization) {
|
|
511
|
+
return { kind: "sso_required" };
|
|
512
|
+
}
|
|
513
|
+
const redirectUrl = `${ORIGIN}/account?login-source=settings`;
|
|
514
|
+
const signIn = await impitJsonRequest(client, jar, endpoints.signInEmail, {
|
|
515
|
+
method: "POST",
|
|
516
|
+
body: {
|
|
517
|
+
email: EMAIL,
|
|
518
|
+
useNumericOtp: "true",
|
|
519
|
+
csrfToken: csrf.json.csrfToken,
|
|
520
|
+
callbackUrl: `${redirectUrl}#locale=en-US`,
|
|
521
|
+
json: "true"
|
|
522
|
+
}
|
|
523
|
+
});
|
|
524
|
+
if (signIn.status !== 200 || !signIn.json?.url) {
|
|
525
|
+
return {
|
|
526
|
+
kind: signIn.status >= 400 && signIn.status < 500 ? "email_rejected" : "unsupported",
|
|
527
|
+
detail: { step: "signin_email", status: signIn.status }
|
|
528
|
+
};
|
|
529
|
+
}
|
|
530
|
+
return { kind: "live", redirectUrl, csrfToken: csrf.json.csrfToken };
|
|
531
|
+
}
|
|
532
|
+
async function submitOtp(client, jar, flow, otp) {
|
|
533
|
+
const endpoints = buildRuntimeEndpoints(ORIGIN);
|
|
534
|
+
const redirectResp = await impitJsonRequest(client, jar, endpoints.otpRedirectLink, {
|
|
535
|
+
method: "POST",
|
|
536
|
+
body: {
|
|
537
|
+
email: EMAIL,
|
|
538
|
+
otp,
|
|
539
|
+
redirectUrl: flow.redirectUrl,
|
|
540
|
+
emailLoginMethod: "web-otp",
|
|
541
|
+
loginSource: null
|
|
542
|
+
}
|
|
543
|
+
});
|
|
544
|
+
if (redirectResp.status !== 200 || !redirectResp.json?.redirect) {
|
|
545
|
+
return { ok: false };
|
|
546
|
+
}
|
|
547
|
+
const callbackUrl = new URL(redirectResp.json.redirect, ORIGIN).toString();
|
|
548
|
+
const callback = await followRedirectsManually(client, jar, callbackUrl, { method: "GET" });
|
|
549
|
+
if (!callback.ok) return { ok: false };
|
|
550
|
+
const cookies = jar.toPlaywrightShape();
|
|
551
|
+
const hasSession = cookies.some((c) => c.name === "__Secure-next-auth.session-token");
|
|
552
|
+
if (!hasSession) return { ok: false };
|
|
553
|
+
return { ok: true, cookies };
|
|
554
|
+
}
|
|
555
|
+
async function fetchSessionInfo(client, jar) {
|
|
556
|
+
const session = await impitJsonRequest(client, jar, `${ORIGIN}/api/auth/session?version=2.18&source=default`);
|
|
557
|
+
return session.json ?? {};
|
|
558
|
+
}
|
|
559
|
+
async function fetchModelsCache(client, jar) {
|
|
560
|
+
const result = {};
|
|
561
|
+
for (const [key, path] of [
|
|
562
|
+
["models", "/rest/configs/models?version=2.18&source=default"],
|
|
563
|
+
["asi", "/rest/configs/asi-access?version=2.18&source=default"],
|
|
564
|
+
["rateLimits", "/rest/rate-limits?version=2.18&source=default"],
|
|
565
|
+
["experiments", "/rest/experiments?version=2.18&source=default"],
|
|
566
|
+
["userInfo", "/rest/user/info?version=2.18&source=default"]
|
|
567
|
+
]) {
|
|
568
|
+
try {
|
|
569
|
+
const r = await impitJsonRequest(client, jar, `${ORIGIN}${path}`);
|
|
570
|
+
if (r.status === 200 && r.json) result[key] = r.json;
|
|
571
|
+
} catch {
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
return result;
|
|
575
|
+
}
|
|
576
|
+
function deriveTier(modelsCache) {
|
|
577
|
+
if (modelsCache.userInfo?.subscription_tier === "enterprise") return "Enterprise";
|
|
578
|
+
if (modelsCache.userInfo?.subscription_tier === "max") return "Max";
|
|
579
|
+
if (modelsCache.userInfo?.subscription_tier === "pro") return "Pro";
|
|
580
|
+
if (modelsCache.experiments?.server_is_enterprise) return "Enterprise";
|
|
581
|
+
if (modelsCache.experiments?.server_is_max) return "Max";
|
|
582
|
+
if (modelsCache.experiments?.server_is_pro) return "Pro";
|
|
583
|
+
if (modelsCache.asi?.can_use_computer) return "Pro";
|
|
584
|
+
return "Authenticated";
|
|
585
|
+
}
|
|
586
|
+
async function main() {
|
|
587
|
+
if (!EMAIL) {
|
|
588
|
+
emit({ ok: false, reason: "no_email" });
|
|
589
|
+
process.exit(1);
|
|
590
|
+
}
|
|
591
|
+
if (!isImpitAvailable()) {
|
|
592
|
+
emit({ ok: false, reason: "impit_missing", error: "Speed Boost (impit) not installed" });
|
|
593
|
+
process.exit(4);
|
|
594
|
+
}
|
|
595
|
+
const PROFILE = resolveProfile();
|
|
596
|
+
const vault = new Vault();
|
|
597
|
+
const jar = new CookieJar([]);
|
|
598
|
+
try {
|
|
599
|
+
const stored = await vault.get(PROFILE, "cookies").catch(() => null);
|
|
600
|
+
if (stored) {
|
|
601
|
+
const parsed = JSON.parse(stored);
|
|
602
|
+
if (Array.isArray(parsed)) {
|
|
603
|
+
for (const c of parsed) jar.set?.(c.name, c.value, { domain: c.domain, path: c.path, expires: c.expires, secure: c.secure, httpOnly: c.httpOnly, sameSite: c.sameSite });
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
} catch {
|
|
607
|
+
}
|
|
608
|
+
if (!jar.toPlaywrightShape().some((c) => c.name === "cf_clearance")) {
|
|
609
|
+
const warmup = await warmCloudflare();
|
|
610
|
+
if (warmup.cookies.length) {
|
|
611
|
+
for (const c of warmup.cookies) {
|
|
612
|
+
jar.set?.(c.name, c.value, { domain: c.domain, path: c.path, expires: c.expires, secure: c.secure, httpOnly: c.httpOnly, sameSite: c.sameSite });
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
if (!warmup.hasCfClearance) {
|
|
616
|
+
emit({ ok: false, reason: "cf_blocked", detail: { warmupOk: warmup.ok } });
|
|
617
|
+
process.exit(3);
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
const impitMod = await loadImpit();
|
|
621
|
+
if (!impitMod) {
|
|
622
|
+
emit({ ok: false, reason: "impit_load_failed" });
|
|
623
|
+
process.exit(4);
|
|
624
|
+
}
|
|
625
|
+
const client = new impitMod.Impit({ browser: "chrome", ignoreTlsErrors: false });
|
|
626
|
+
let authFlow;
|
|
627
|
+
try {
|
|
628
|
+
authFlow = await startEmailFlow(client, jar);
|
|
629
|
+
} catch (err) {
|
|
630
|
+
emit({ ok: false, reason: "auto_unsupported", detail: { step: "start", error: redact(String(err?.message ?? err)) } });
|
|
631
|
+
process.exit(2);
|
|
632
|
+
}
|
|
633
|
+
if (authFlow.kind === "sso_required") {
|
|
634
|
+
emit({ ok: false, reason: "sso_required" });
|
|
635
|
+
process.exit(2);
|
|
636
|
+
}
|
|
637
|
+
if (authFlow.kind === "email_rejected") {
|
|
638
|
+
emit({ ok: false, reason: "email_rejected", detail: authFlow.detail });
|
|
639
|
+
process.exit(2);
|
|
640
|
+
}
|
|
641
|
+
if (authFlow.kind !== "live") {
|
|
642
|
+
emit({ ok: false, reason: "auto_unsupported", detail: authFlow.detail });
|
|
643
|
+
process.exit(2);
|
|
644
|
+
}
|
|
645
|
+
for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
|
|
646
|
+
ipc({ phase: "awaiting_otp", attempt });
|
|
647
|
+
let otp;
|
|
648
|
+
try {
|
|
649
|
+
otp = await awaitOtp();
|
|
650
|
+
} catch {
|
|
651
|
+
emit({ ok: false, reason: "otp_timeout" });
|
|
652
|
+
process.exit(2);
|
|
653
|
+
}
|
|
654
|
+
const submitResp = await submitOtp(client, jar, authFlow, otp);
|
|
655
|
+
if (submitResp.ok) {
|
|
656
|
+
const sessionInfo = await fetchSessionInfo(client, jar).catch(() => ({}));
|
|
657
|
+
const modelsCache = await fetchModelsCache(client, jar).catch(() => ({}));
|
|
658
|
+
const tier = deriveTier(modelsCache);
|
|
659
|
+
await vault.set(PROFILE, "cookies", JSON.stringify(submitResp.cookies));
|
|
660
|
+
if (sessionInfo?.user?.email) await vault.set(PROFILE, "email", sessionInfo.user.email);
|
|
661
|
+
if (sessionInfo?.user?.id) await vault.set(PROFILE, "userId", sessionInfo.user.id);
|
|
662
|
+
const paths = getProfilePaths(PROFILE);
|
|
663
|
+
if (!existsSync(paths.dir)) mkdirSync(paths.dir, { recursive: true });
|
|
664
|
+
writeFileSync(paths.modelsCache, JSON.stringify(modelsCache, null, 2));
|
|
665
|
+
recordLoginSuccess(PROFILE, { tier, loginMode: "auto", lastLogin: (/* @__PURE__ */ new Date()).toISOString() });
|
|
666
|
+
writeFileSync(paths.reinit, String(Date.now()));
|
|
667
|
+
emit({ ok: true, tier, modelCount: Object.keys(modelsCache?.models?.models ?? {}).length, transport: "impit" });
|
|
668
|
+
process.exit(0);
|
|
669
|
+
}
|
|
670
|
+
if (attempt === MAX_RETRIES) {
|
|
671
|
+
emit({ ok: false, reason: "otp_rejected" });
|
|
672
|
+
process.exit(2);
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
main().catch((err) => {
|
|
677
|
+
const msg = err?.message ?? err;
|
|
678
|
+
emit({
|
|
679
|
+
ok: false,
|
|
680
|
+
reason: "crash",
|
|
681
|
+
error: redact(String(msg ?? "unknown error")),
|
|
682
|
+
...err?.stack ? { stack: redact(String(err.stack)) } : {}
|
|
683
|
+
});
|
|
684
|
+
process.exit(5);
|
|
685
|
+
});
|