Files
openchamber/packages/ui/src/lib/runtime-auth.ts
T
Bohdan Triapitsyn 85400459e9 perf: overhaul session loading, caching, and runtime isolation (#2360)
Improve OpenChamber responsiveness under large session workloads while fixing
cache, synchronization, and persistence correctness across runtimes, projects,
directories, and worktrees.

- prioritize selected and visible sessions during bootstrap and defer
  non-critical enrichment work
- reduce redundant message loading, event processing, store publication, and
  hidden sidebar work
- prevent stale session and message requests from overwriting newer
  authoritative state
- preserve existing data when authoritative fetches fail instead of treating
  failures as successful empty responses
- scope session materialization, messages, drafts, queues, todos, pins,
  permissions, folders, tabs, Git state, and pull request data by runtime and
  directory identity
- harden runtime switching, reconnect, cleanup, mutation reconciliation, and
  persisted-state ordering
- preserve live subagent Task linkage when metadata arrives after an older
  message request or while streaming parts are suspended
- coalesce overlapping tail refreshes without losing newer refresh demand
- improve cold-session loading by moving deferrable work out of the critical
  bootstrap path
- isolate URL authentication, mobile credentials, native secrets, and other
  runtime-owned state across endpoint changes
- bound long-lived caches and remove avoidable allocations from event and
  rendering hot paths
- limit virtualization to archive collections where it improves rendering
  without disrupting active sidebar layout
- stabilize session folders, pin ordering, expanded state, and persisted
  sidebar behavior
- open skill files through the same secure editor and outside-workspace grant
  flow used by file navigation, including worktree sessions
- expand regression coverage for stale completions, runtime collisions,
  reconnect behavior, persistence races, authoritative empty results, and
  subagent refresh ordering
- document the updated synchronization, cache ownership, performance, and
  runtime-isolation invariants
2026-07-21 20:52:20 +03:00

437 lines
17 KiB
TypeScript

import { getActiveRelayTunnel } from '@/lib/relay/runtime-tunnel';
type RuntimeAuthCredential =
| { type: 'bearer'; token: string }
| null;
export type RuntimeAuthCredentialProvider = () => RuntimeAuthCredential | Promise<RuntimeAuthCredential>;
let credentialProvider: RuntimeAuthCredentialProvider = () => null;
let runtimeBearerToken = '';
let runtimeExtraHeaders: Record<string, string> = {};
let runtimeUrlAuthToken = '';
let runtimeUrlAuthTokenExpiresAt = 0;
let runtimeUrlAuthRefreshPromise: Promise<string> | null = null;
let localRuntimeUrlAuthToken = '';
let localRuntimeUrlAuthTokenExpiresAt = 0;
let localRuntimeUrlAuthOrigin = '';
let localRuntimeUrlAuthRefreshPromise: Promise<string> | null = null;
let localRuntimeUrlAuthRefreshOrigin = '';
let localRuntimeUrlAuthGeneration = 0;
let runtimeAuthGeneration = 0;
const URL_AUTH_REFRESH_SKEW_MS = 10_000;
const isReservedRuntimeExtraHeaderName = (name: string): boolean => name.toLowerCase() === 'authorization';
const sanitizeRuntimeExtraHeaders = (headers: Record<string, string> | null | undefined): Record<string, string> => {
const next: Record<string, string> = {};
for (const [key, value] of Object.entries(headers || {})) {
const name = key.trim();
const headerValue = value.trim();
if (name && headerValue && !isReservedRuntimeExtraHeaderName(name)) next[name] = headerValue;
}
return next;
};
const runtimeExtraHeadersEqual = (left: Record<string, string>, right: Record<string, string>): boolean => {
const leftEntries = Object.entries(left);
if (leftEntries.length !== Object.keys(right).length) return false;
return leftEntries.every(([key, value]) => right[key] === value);
};
const normalizeBearerToken = (token: string | null | undefined): string => {
if (typeof token !== 'string') return '';
return token.trim();
};
const readInjectedBearerToken = (): string => {
if (typeof window === 'undefined') return '';
const injected = (window as typeof window & { __OPENCHAMBER_CLIENT_TOKEN__?: string }).__OPENCHAMBER_CLIENT_TOKEN__;
return normalizeBearerToken(injected);
};
const readInjectedApiBaseUrl = (): string => {
if (typeof window === 'undefined') return '';
const injected = (window as typeof window & { __OPENCHAMBER_API_BASE_URL__?: string }).__OPENCHAMBER_API_BASE_URL__;
return typeof injected === 'string' ? injected.trim() : '';
};
const buildAuthUrl = (apiBaseUrl: string | null | undefined, path: string): string => {
const base = typeof apiBaseUrl === 'string' && apiBaseUrl.trim()
? apiBaseUrl.trim()
: readInjectedApiBaseUrl();
if (!base) return path;
try {
return new URL(path, `${base.replace(/\/+$/, '')}/`).toString();
} catch {
return path;
}
};
const normalizeOrigin = (value: string): string => {
try {
return new URL(value).origin;
} catch {
return '';
}
};
const clearLocalRuntimeUrlAuthToken = (): void => {
localRuntimeUrlAuthToken = '';
localRuntimeUrlAuthTokenExpiresAt = 0;
localRuntimeUrlAuthOrigin = '';
localRuntimeUrlAuthRefreshPromise = null;
localRuntimeUrlAuthRefreshOrigin = '';
localRuntimeUrlAuthGeneration += 1;
};
export const clearRuntimeUrlAuthToken = (): void => {
runtimeUrlAuthToken = '';
runtimeUrlAuthTokenExpiresAt = 0;
clearLocalRuntimeUrlAuthToken();
};
const resetRuntimeAuthGeneration = (): void => {
runtimeAuthGeneration += 1;
runtimeUrlAuthRefreshPromise = null;
clearRuntimeUrlAuthToken();
// Credentials changed: if a consumer is active, re-mint promptly.
scheduleUrlAuthRefresh();
};
export const setRuntimeAuthCredentialProvider = (provider: RuntimeAuthCredentialProvider): void => {
runtimeBearerToken = '';
resetRuntimeAuthGeneration();
credentialProvider = provider;
};
export const clearRuntimeAuthCredentialProvider = (): void => {
runtimeBearerToken = '';
resetRuntimeAuthGeneration();
credentialProvider = () => null;
};
export const setRuntimeBearerToken = (token: string | null | undefined): void => {
const normalized = normalizeBearerToken(token);
runtimeBearerToken = normalized;
resetRuntimeAuthGeneration();
credentialProvider = () => normalized ? { type: 'bearer', token: normalized } : null;
};
export const setRuntimeExtraHeaders = (headers: Record<string, string> | null | undefined): void => {
// These headers are for runtime HTTP fetches and URL-token minting. Browser-owned
// realtime transports (EventSource/WebSocket) cannot attach arbitrary headers.
const next = sanitizeRuntimeExtraHeaders(headers);
if (runtimeExtraHeadersEqual(runtimeExtraHeaders, next)) return;
runtimeExtraHeaders = next;
resetRuntimeAuthGeneration();
};
export const getRuntimeExtraHeadersSync = (): Record<string, string> => {
if (Object.keys(runtimeExtraHeaders).length > 0) return runtimeExtraHeaders;
if (typeof window === 'undefined') return {};
const injected = (window as typeof window & { __OPENCHAMBER_RUNTIME_HEADERS__?: Record<string, string> }).__OPENCHAMBER_RUNTIME_HEADERS__;
return injected && typeof injected === 'object' ? sanitizeRuntimeExtraHeaders(injected) : {};
};
export const getRuntimeBearerTokenSync = (): string => runtimeBearerToken || readInjectedBearerToken();
export const setRuntimeUrlAuthToken = (token: string | null | undefined, expiresAt: number | null | undefined): void => {
const normalized = normalizeBearerToken(token);
if (!normalized || typeof expiresAt !== 'number' || !Number.isFinite(expiresAt)) {
clearRuntimeUrlAuthToken();
return;
}
const previous = runtimeUrlAuthToken;
runtimeUrlAuthToken = normalized;
runtimeUrlAuthTokenExpiresAt = expiresAt;
// Notify only on a real replacement (existing token swapped for a fresh one),
// not on the initial mint, so consumers remount token-bearing assets only
// when the URL token actually changed underneath them.
if (previous && previous !== normalized) {
notifyRuntimeUrlAuthListeners();
}
};
export const setLocalRuntimeUrlAuthToken = (
token: string | null | undefined,
expiresAt: number | null | undefined,
localOrigin?: string | null,
): void => {
const normalized = normalizeBearerToken(token);
const origin = typeof localOrigin === 'string' ? normalizeOrigin(localOrigin) : '';
if (!normalized || typeof expiresAt !== 'number' || !Number.isFinite(expiresAt) || !origin) {
clearLocalRuntimeUrlAuthToken();
return;
}
localRuntimeUrlAuthToken = normalized;
localRuntimeUrlAuthTokenExpiresAt = expiresAt;
localRuntimeUrlAuthOrigin = origin;
};
const readValidRuntimeUrlAuthTokenSync = (): string => {
if (!runtimeUrlAuthToken || runtimeUrlAuthTokenExpiresAt <= Date.now() + URL_AUTH_REFRESH_SKEW_MS) {
clearRuntimeUrlAuthToken();
return '';
}
return runtimeUrlAuthToken;
};
const readValidLocalRuntimeUrlAuthTokenSync = (localOrigin: string): string => {
const origin = normalizeOrigin(localOrigin);
if (!origin || localRuntimeUrlAuthOrigin !== origin) return '';
if (!localRuntimeUrlAuthToken || localRuntimeUrlAuthTokenExpiresAt <= Date.now() + URL_AUTH_REFRESH_SKEW_MS) {
localRuntimeUrlAuthToken = '';
localRuntimeUrlAuthTokenExpiresAt = 0;
localRuntimeUrlAuthOrigin = '';
return '';
}
return localRuntimeUrlAuthToken;
};
export const getRuntimeUrlAuthTokenSync = (): string => {
const token = readValidRuntimeUrlAuthTokenSync();
if (!token && (getRuntimeBearerTokenSync() || typeof window !== 'undefined')) {
void refreshRuntimeUrlAuthToken().catch(() => {});
}
return token;
};
export const getLocalRuntimeUrlAuthTokenSync = (localOrigin?: string | null): string => {
const token = localOrigin ? readValidLocalRuntimeUrlAuthTokenSync(localOrigin) : '';
if (!token && localOrigin && typeof window !== 'undefined') {
void refreshLocalRuntimeUrlAuthToken(localOrigin).catch(() => {});
}
return token;
};
const getRuntimeAuthCredential = async (): Promise<RuntimeAuthCredential> => {
const credential = await credentialProvider();
const token = credential?.type === 'bearer'
? normalizeBearerToken(credential.token)
: getRuntimeBearerTokenSync();
return token ? { type: 'bearer', token } : null;
};
// Performs the actual network mint and swaps the new token in atomically (the
// previous token stays valid until `setRuntimeUrlAuthToken` replaces it — no
// empty-token window). Concurrent callers share one in-flight request.
const mintRuntimeUrlAuthToken = (apiBaseUrl?: string | null): Promise<string> => {
if (runtimeUrlAuthRefreshPromise) return runtimeUrlAuthRefreshPromise;
const generation = runtimeAuthGeneration;
const refreshPromise = (async () => {
const credential = await getRuntimeAuthCredential();
const headers = new Headers();
for (const [key, value] of Object.entries(getRuntimeExtraHeadersSync())) {
headers.set(key, value);
}
if (credential?.type === 'bearer') {
headers.set('Authorization', `Bearer ${credential.token}`);
}
// In relay mode the mint must ride the tunnel, not the network: there is no
// reachable network base URL. Same auth headers, same route, tunneled.
const relay = getActiveRelayTunnel();
const response = relay
? await relay.fetch('/auth/url-token', { method: 'POST', headers })
: await fetch(buildAuthUrl(apiBaseUrl, '/auth/url-token'), {
method: 'POST',
headers,
credentials: 'include',
});
if (!response.ok) {
if (generation === runtimeAuthGeneration) {
clearRuntimeUrlAuthToken();
}
throw new Error(`Failed to mint runtime URL auth token (${response.status})`);
}
const payload = await response.json().catch(() => null) as { token?: unknown; expiresAt?: unknown } | null;
const token = typeof payload?.token === 'string' ? payload.token.trim() : '';
const expiresAt = typeof payload?.expiresAt === 'number' ? payload.expiresAt : 0;
if (generation !== runtimeAuthGeneration) {
throw new Error('Runtime URL auth token response is stale');
}
setRuntimeUrlAuthToken(token, expiresAt);
if (!runtimeUrlAuthToken) {
throw new Error('Runtime URL auth token response was invalid');
}
return runtimeUrlAuthToken;
})();
const trackedPromise = refreshPromise.finally(() => {
if (runtimeUrlAuthRefreshPromise === trackedPromise) {
runtimeUrlAuthRefreshPromise = null;
}
});
runtimeUrlAuthRefreshPromise = trackedPromise;
return runtimeUrlAuthRefreshPromise;
};
const mintLocalRuntimeUrlAuthToken = (localOrigin: string): Promise<string> => {
const origin = normalizeOrigin(localOrigin);
if (!origin) return Promise.reject(new Error('Local runtime URL auth origin was invalid'));
if (localRuntimeUrlAuthRefreshPromise && localRuntimeUrlAuthRefreshOrigin === origin) {
return localRuntimeUrlAuthRefreshPromise;
}
const generation = localRuntimeUrlAuthGeneration;
const refreshPromise = (async () => {
const response = await fetch(buildAuthUrl(origin, '/auth/url-token'), {
method: 'POST',
credentials: 'include',
});
if (!response.ok) {
if (generation === localRuntimeUrlAuthGeneration && origin === localRuntimeUrlAuthRefreshOrigin) {
localRuntimeUrlAuthToken = '';
localRuntimeUrlAuthTokenExpiresAt = 0;
localRuntimeUrlAuthOrigin = '';
}
throw new Error(`Failed to mint local runtime URL auth token (${response.status})`);
}
const payload = await response.json().catch(() => null) as { token?: unknown; expiresAt?: unknown } | null;
const token = typeof payload?.token === 'string' ? payload.token.trim() : '';
const expiresAt = typeof payload?.expiresAt === 'number' ? payload.expiresAt : 0;
if (!token || !Number.isFinite(expiresAt)) {
throw new Error('Local runtime URL auth token response was invalid');
}
if (generation !== localRuntimeUrlAuthGeneration || origin !== localRuntimeUrlAuthRefreshOrigin) {
throw new Error('Local runtime URL auth token response is stale');
}
localRuntimeUrlAuthToken = token;
localRuntimeUrlAuthTokenExpiresAt = expiresAt;
localRuntimeUrlAuthOrigin = origin;
return token;
})();
const trackedPromise = refreshPromise.finally(() => {
if (localRuntimeUrlAuthRefreshPromise === trackedPromise) {
localRuntimeUrlAuthRefreshPromise = null;
localRuntimeUrlAuthRefreshOrigin = '';
}
});
localRuntimeUrlAuthRefreshPromise = trackedPromise;
localRuntimeUrlAuthRefreshOrigin = origin;
return localRuntimeUrlAuthRefreshPromise;
};
// Returns a valid token without a network call, minting only when the current
// token is missing or already inside the skew window.
export const refreshRuntimeUrlAuthToken = async (apiBaseUrl?: string | null): Promise<string> => {
const existing = readValidRuntimeUrlAuthTokenSync();
if (existing) return existing;
return mintRuntimeUrlAuthToken(apiBaseUrl);
};
export const refreshLocalRuntimeUrlAuthToken = async (localOrigin: string): Promise<string> => {
const origin = normalizeOrigin(localOrigin);
if (!origin) throw new Error('Local runtime URL auth origin was invalid');
const existing = readValidLocalRuntimeUrlAuthTokenSync(origin);
if (existing) return existing;
if (
(localRuntimeUrlAuthOrigin && localRuntimeUrlAuthOrigin !== origin)
|| (localRuntimeUrlAuthRefreshOrigin && localRuntimeUrlAuthRefreshOrigin !== origin)
) {
clearLocalRuntimeUrlAuthToken();
}
return mintLocalRuntimeUrlAuthToken(origin);
};
// ── Proactive URL auth token refresh ──────────────────────────────────────
// The url token has a short server TTL. Instead of each consumer minting on its
// own timer (and clearing the shared token, which 401s other consumers during
// the refetch), a single scheduler refreshes it just before the skew window —
// but only while at least one consumer is active, so we never poll
// /auth/url-token in the background when nothing needs the token.
let urlAuthConsumerCount = 0;
let urlAuthRefreshTimer: ReturnType<typeof setTimeout> | null = null;
let urlAuthApiBaseUrl: string | null = null;
const urlAuthListeners = new Set<() => void>();
const URL_AUTH_PROACTIVE_BUFFER_MS = 5_000;
const notifyRuntimeUrlAuthListeners = (): void => {
for (const listener of urlAuthListeners) {
try {
listener();
} catch {
// A listener throwing must not break the refresh loop.
}
}
};
const clearUrlAuthRefreshTimer = (): void => {
if (urlAuthRefreshTimer !== null) {
clearTimeout(urlAuthRefreshTimer);
urlAuthRefreshTimer = null;
}
};
const scheduleUrlAuthRefresh = (): void => {
clearUrlAuthRefreshTimer();
if (urlAuthConsumerCount <= 0 || typeof window === 'undefined') return;
// Refresh before the skew window so the old token is still valid when the new
// one swaps in. With no token yet (expiry 0), refresh immediately.
const refreshAt = runtimeUrlAuthTokenExpiresAt - URL_AUTH_REFRESH_SKEW_MS - URL_AUTH_PROACTIVE_BUFFER_MS;
const delay = runtimeUrlAuthTokenExpiresAt > 0 ? Math.max(0, refreshAt - Date.now()) : 0;
urlAuthRefreshTimer = setTimeout(() => {
urlAuthRefreshTimer = null;
if (urlAuthConsumerCount <= 0) return;
void mintRuntimeUrlAuthToken(urlAuthApiBaseUrl)
.catch(() => {
// Transient — the reschedule below retries (token is cleared on
// failure → expiry 0 → delay 0 → prompt retry).
})
.finally(() => {
scheduleUrlAuthRefresh();
});
}, delay);
};
// Register an active url-token consumer. While any consumer is held, the token
// is proactively refreshed before it expires. Returns a release function;
// the proactive loop stops once the last consumer releases.
export const acquireRuntimeUrlAuthToken = (apiBaseUrl?: string | null): (() => void) => {
if (typeof apiBaseUrl === 'string' && apiBaseUrl.trim()) {
urlAuthApiBaseUrl = apiBaseUrl.trim();
}
urlAuthConsumerCount += 1;
scheduleUrlAuthRefresh();
let released = false;
return () => {
if (released) return;
released = true;
urlAuthConsumerCount = Math.max(0, urlAuthConsumerCount - 1);
if (urlAuthConsumerCount === 0) {
clearUrlAuthRefreshTimer();
}
};
};
// Subscribe to url-token *replacements* (an existing token swapped for a fresh
// one). Fires only on a real change — not the initial mint — so consumers can
// remount token-bearing assets without churning on first load. Returns an
// unsubscribe function.
export const subscribeRuntimeUrlAuthToken = (listener: () => void): (() => void) => {
urlAuthListeners.add(listener);
return () => {
urlAuthListeners.delete(listener);
};
};
export const buildRuntimeAuthHeaders = async (headers?: HeadersInit): Promise<Headers> => {
const next = new Headers(headers);
for (const [key, value] of Object.entries(getRuntimeExtraHeadersSync())) {
if (!next.has(key)) next.set(key, value);
}
if (next.has('Authorization')) {
return next;
}
const credential = await getRuntimeAuthCredential();
if (credential?.type === 'bearer') {
next.set('Authorization', `Bearer ${credential.token}`);
}
return next;
};