Files
TJWaterAgent/src/session/registry.ts
T

81 lines
1.9 KiB
TypeScript

import crypto from "node:crypto";
export type SessionBinding = {
clientSessionId: string;
sessionId: string;
lastUsedAt: number;
};
export type SessionContext = {
clientSessionId: string;
accessToken?: string;
projectId?: string;
userId?: string;
};
export class SessionRegistry {
private readonly ttlMs: number;
private readonly bindings = new Map<string, SessionBinding>();
constructor(ttlSeconds: number) {
this.ttlMs = ttlSeconds * 1000;
}
upsert(context: SessionContext, sessionId: string): SessionBinding {
const binding: SessionBinding = {
clientSessionId: context.clientSessionId,
sessionId,
lastUsedAt: Date.now(),
};
this.bindings.set(this.makeKey(context), binding);
return binding;
}
get(context: SessionContext): SessionBinding | null {
const key = this.makeKey(context);
const binding = this.bindings.get(key);
if (!binding) {
return null;
}
if (Date.now() - binding.lastUsedAt > this.ttlMs) {
this.bindings.delete(key);
return null;
}
binding.lastUsedAt = Date.now();
return binding;
}
count(): number {
this.evictExpired();
return this.bindings.size;
}
evictExpired(): string[] {
const expired: string[] = [];
const now = Date.now();
for (const [key, binding] of this.bindings.entries()) {
if (now - binding.lastUsedAt > this.ttlMs) {
expired.push(binding.sessionId);
this.bindings.delete(key);
}
}
return expired;
}
private makeKey(context: SessionContext): string {
// 会话隔离不能只看前端 session_id;同一浏览器会话切换用户或项目时必须映射到不同 opencode session。
const digest = crypto
.createHash("sha256")
.update(
[
context.clientSessionId,
context.userId?.trim() ?? "",
context.projectId ?? "",
].join("|"),
)
.digest("hex");
return digest;
}
}