返回 DeepSeek-Reasonix
grants.ts
根目录 / desktop / electron / src / main / browser / grants.ts
1 import { noGrant } from "./errors.js";
2
3 export interface BrowserGrant {
4 grantId: string;
5 taskId: string;
6 sessionId: string;
7 generation: string;
8 createdAt: number;
9 }
10
11 export interface GrantRegistryDeps {
12 generation(): string;
13 now?(): number;
14 onRevoked?(grant: BrowserGrant): void;
15 }
16
17 // Grants are minted by Go per task (its "tabId") and die with the service
18 // generation that minted them, so a restarted service can never act on a
19 // grant it does not remember.
20 export class GrantRegistry {
21 private readonly grants = new Map<string, BrowserGrant>();
22 private generation = "";
23
24 constructor(private readonly deps: GrantRegistryDeps) {}
25
26 install(input: { grantId: string; taskId: string; sessionId: string }): BrowserGrant {
27 if (input.grantId === "" || input.taskId === "") throw noGrant("grantId and tabId are required");
28 const generation = this.deps.generation();
29 if (generation === "") throw noGrant("desktop service is not running");
30 this.generation = generation;
31 const grant: BrowserGrant = { ...input, generation, createdAt: (this.deps.now ?? Date.now)() };
32 this.grants.set(input.grantId, grant);
33 return grant;
34 }
35
36 revoke(grantId: string): BrowserGrant | null {
37 const grant = this.grants.get(grantId);
38 if (!grant) return null;
39 this.grants.delete(grantId);
40 this.deps.onRevoked?.(grant);
41 return grant;
42 }
43
44 // A generation change (service restart) retires every grant at once.
45 observeGeneration(generation: string): void {
46 if (generation === this.generation) return;
47 this.generation = generation;
48 for (const grantId of [...this.grants.keys()]) this.revoke(grantId);
49 }
50
51 verify(grantId: string): BrowserGrant {
52 const grant = this.grants.get(grantId);
53 if (!grant) throw noGrant(`unknown grant ${grantId || "(empty)"}`);
54 if (grant.generation !== this.deps.generation()) {
55 this.revoke(grantId);
56 throw noGrant("grant belongs to an earlier service generation");
57 }
58 return grant;
59 }
60
61 // The tab must belong to the grant's task; a grant never reaches across.
62 verifyTab(grantId: string, tabTaskId: string | undefined): BrowserGrant {
63 const grant = this.verify(grantId);
64 if (tabTaskId === undefined) throw noGrant("unknown browser tab");
65 if (tabTaskId !== grant.taskId) throw noGrant("browser tab belongs to another task");
66 return grant;
67 }
68
69 get size(): number {
70 return this.grants.size;
71 }
72 }
73
73 lines TYPESCRIPT