fix(slack): bound username cache with ttl and lru eviction

This commit is contained in:
William Valentin
2026-02-15 22:21:06 -08:00
parent a525ec7b2d
commit 05d8abc79d
4 changed files with 99 additions and 7 deletions
+25 -3
View File
@@ -31,6 +31,11 @@ export interface SlackAdapterConfig {
pairingManager?: PairingManager;
}
interface CachedUserName {
name: string;
expiresAt: number;
}
/** Minimal shape of a Slack message event from Bolt. */
interface SlackMessageEvent {
ts?: string;
@@ -63,8 +68,10 @@ export class SlackAdapter implements ChannelAdapter {
private app: App | null = null;
private messageHandler?: (msg: InboundMessage) => void;
private config: SlackAdapterConfig;
private userNameCache: Map<string, string> = new Map();
private userNameCache: Map<string, CachedUserName> = new Map();
private botUserId?: string;
private readonly userNameCacheTtlMs = 60 * 60 * 1_000;
private readonly userNameCacheMaxEntries = 1_000;
get status(): ChannelStatus {
return this._status;
@@ -199,13 +206,28 @@ export class SlackAdapter implements ChannelAdapter {
/** Resolve a Slack user ID to a display name, with caching. */
private async resolveUserName(userId: string): Promise<string> {
const now = Date.now();
const cached = this.userNameCache.get(userId);
if (cached) {return cached;}
if (cached && cached.expiresAt > now) {
// Refresh LRU order on cache hit.
this.userNameCache.delete(userId);
this.userNameCache.set(userId, cached);
return cached.name;
}
if (cached) {
this.userNameCache.delete(userId);
}
try {
const result = await this.app!.client.users.info({ user: userId });
const name = result.user?.real_name || result.user?.name || userId;
this.userNameCache.set(userId, name);
this.userNameCache.set(userId, { name, expiresAt: now + this.userNameCacheTtlMs });
if (this.userNameCache.size > this.userNameCacheMaxEntries) {
const oldestKey = this.userNameCache.keys().next().value;
if (typeof oldestKey === 'string') {
this.userNameCache.delete(oldestKey);
}
}
return name;
} catch {
return userId;