-
-
Notifications
You must be signed in to change notification settings - Fork 14
Expand file tree
/
Copy pathsession.ts
More file actions
159 lines (135 loc) · 5.1 KB
/
Copy pathsession.ts
File metadata and controls
159 lines (135 loc) · 5.1 KB
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
import { existsSync, readFileSync, statSync } from "node:fs";
import type { AccountSnapshot, DaemonAuthStatus, ModelsConfigSource, RefreshTier } from "@perplexity-user-mcp/shared";
import { MODELS_FALLBACK, MODELS_FALLBACK_CAPTURED_AT } from "@perplexity-user-mcp/shared";
import { getConfigDir, getProfilePaths, getActiveName, getProfile } from "perplexity-user-mcp/profiles";
import type { AccountInfo } from "../browser/runtime.js";
import { getImpitStatus } from "../native-deps.js";
let lastRefreshTier: RefreshTier | null = null;
function getActiveProfileSnapshotPaths() {
const name = getActiveName() ?? "default";
return getProfilePaths(name);
}
export function setLastRefreshTier(tier: RefreshTier | null): void {
lastRefreshTier = tier;
}
export function getLastRefreshTier(): RefreshTier | null {
return lastRefreshTier;
}
function readJsonFile<T>(filePath: string): T | null {
if (!existsSync(filePath)) {
return null;
}
try {
return JSON.parse(readFileSync(filePath, "utf8")) as T;
} catch {
return null;
}
}
/** True when `pid` is a live process. EPERM = alive, just owned by someone else. */
function isProcessAlive(pid: number): boolean {
try {
process.kill(pid, 0);
return true;
} catch (err) {
return (err as NodeJS.ErrnoException).code === "EPERM";
}
}
/**
* daemon-status.json, but only when the daemon that wrote it is still alive.
*
* The daemon's shutdown() sets authenticated:false and writes the file on its
* way out, and a crashed/killed daemon just leaves its last write behind. Both
* strand a `{authenticated:false, reason:"not-logged-in"}` file on disk, which
* the dashboard renders forever as "Daemon sees an anonymous session — run
* login to connect." — telling users to run a login that cannot possibly clear
* it (issue #10 follow-ups).
*
* ponytail: pid reuse could theoretically resurrect a stale status. It is
* cosmetic and self-corrects on the next daemon write — cross-check daemon.lock
* only if it ever shows up in practice.
*
* @internal Exported only so unit tests can assert the liveness branches; not
* part of the supported API.
*/
export function readLiveDaemonStatus(filePath: string): DaemonAuthStatus | null {
const status = readJsonFile<DaemonAuthStatus>(filePath);
if (!status) return null;
// Pre-pid daemons: nothing to verify, so trust the file as before.
if (typeof status.pid !== "number") return status;
return isProcessAlive(status.pid) ? status : null;
}
function deriveTier(accountInfo: AccountInfo | null, loggedIn: boolean): AccountSnapshot["tier"] {
if (!loggedIn) {
return "Anonymous";
}
if (!accountInfo) {
return "Authenticated";
}
if (accountInfo.isMax) {
return "Max";
}
if (accountInfo.isPro) {
return "Pro";
}
if (accountInfo.isEnterprise) {
return "Enterprise";
}
return "Authenticated";
}
export function getModelsCachePath(): string {
return getActiveProfileSnapshotPaths().modelsCache;
}
export function hasStoredLogin(): boolean {
// NOT "vault.enc exists": soft logout deletes only the vault's `cookies`
// key (email/userId survive by design), so the file outlives every logout
// and a file-existence check reports "logged in" forever. `meta.lastLogin`
// is the signal both sides maintain — every login runner records it and
// softLogout deletes it. (This must stay sync; the cookie-aware async
// variant lives in perplexity-user-mcp's config.ts.)
const name = getActiveName() ?? "default";
return Boolean(getProfile(name)?.lastLogin);
}
export function getAccountSnapshot(): AccountSnapshot {
const paths = getActiveProfileSnapshotPaths();
const modelsCacheFile = paths.modelsCache;
const accountInfo = readJsonFile<AccountInfo>(modelsCacheFile);
const loggedIn = hasStoredLogin() || !!accountInfo;
const cacheMtime = existsSync(modelsCacheFile)
? statSync(modelsCacheFile).mtime
: null;
let modelsConfig = accountInfo?.modelsConfig ?? null;
let modelsConfigSource: ModelsConfigSource;
let lastUpdated: string | null;
if (modelsConfig) {
modelsConfigSource = cacheMtime && Date.now() - cacheMtime.getTime() < 60_000 ? "live" : "cache";
lastUpdated = cacheMtime ? cacheMtime.toISOString() : null;
} else {
modelsConfig = MODELS_FALLBACK;
modelsConfigSource = "fallback";
lastUpdated = MODELS_FALLBACK_CAPTURED_AT;
}
const speedBoost = getImpitStatus();
// Read live daemon auth state — null when the file is absent (stdio mode /
// first run) OR when the daemon that wrote it is no longer running.
const daemonAuth = readLiveDaemonStatus(paths.daemonStatus);
return {
loggedIn,
userId: null,
tier: deriveTier(accountInfo, loggedIn),
canUseComputer: accountInfo?.canUseComputer ?? false,
modelsConfig,
modelsConfigSource,
rateLimits: accountInfo?.rateLimits ?? null,
configDir: getConfigDir(),
browserProfileDir: paths.browserData,
lastUpdated,
lastRefreshTier,
speedBoost: {
installed: speedBoost.installed,
version: speedBoost.version,
installedAt: speedBoost.installedAt,
runtimeDir: speedBoost.runtimeDir,
},
daemonAuth,
};
}