SaylorTwift's picture
SaylorTwift HF Staff
Add files using upload-large-folder tool
3d700dd verified
Raw History Blame Contribute Delete
6.59 kB
import {
SEEDED_DEFAULT_BACKEND_ID,
makeDefaultLocalBackend,
makeLockedCloudBackend,
} from "./default-backend";
import type {
Backend,
BackendAuthMode,
BackendKind,
BackendSelection,
} from "./types";
export const BACKENDS_STORAGE_KEY = "openhands-backends";
export const ACTIVE_BACKEND_STORAGE_KEY = "openhands-active-backend";
function isValidKind(value: unknown): value is BackendKind {
return value === "local" || value === "cloud";
}
function isValidAuthMode(value: unknown): value is BackendAuthMode {
return value === undefined || value === "api-key" || value === "cookie";
}
function isValidBackend(value: unknown): value is Backend {
if (typeof value !== "object" || value === null) return false;
const v = value as Partial<Backend>;
return (
typeof v.id === "string" &&
v.id.length > 0 &&
typeof v.name === "string" &&
typeof v.host === "string" &&
typeof v.apiKey === "string" &&
isValidKind(v.kind) &&
isValidAuthMode(v.authMode) &&
(v.connectionRevision === undefined ||
(typeof v.connectionRevision === "number" &&
Number.isSafeInteger(v.connectionRevision) &&
v.connectionRevision >= 0))
);
}
function isLoopbackUrl(value: string): boolean {
try {
const { hostname } = new URL(value);
return (
hostname === "localhost" ||
hostname === "127.0.0.1" ||
hostname === "::1" ||
hostname === "[::1]"
);
} catch {
return false;
}
}
function shouldSyncLauncherDefaultLocalBackend(
backend: Backend,
defaultBackend: Backend,
): boolean {
if (backend.id !== SEEDED_DEFAULT_BACKEND_ID || backend.kind !== "local") {
return false;
}
return (
backend.host === defaultBackend.host ||
(isLoopbackUrl(backend.host) && isLoopbackUrl(defaultBackend.host))
);
}
function syncLauncherDefaultLocalBackend(backends: Backend[]): Backend[] {
const defaultBackend = makeDefaultLocalBackend();
if (!defaultBackend) return backends;
let didSync = false;
const syncedBackends = backends.map((backend) => {
if (!shouldSyncLauncherDefaultLocalBackend(backend, defaultBackend)) {
return backend;
}
if (backend.apiKey === defaultBackend.apiKey) return backend;
didSync = true;
return {
...backend,
apiKey: defaultBackend.apiKey,
};
});
if (!didSync) return backends;
writeStoredBackends(syncedBackends);
return syncedBackends;
}
export function writeStoredBackends(backends: Backend[]): void {
if (typeof window === "undefined") return;
try {
window.localStorage.setItem(BACKENDS_STORAGE_KEY, JSON.stringify(backends));
} catch {
/* ignore quota / serialization errors */
}
}
export function readStoredBackends(): Backend[] {
if (typeof window === "undefined") return [];
try {
const lockedCloudBackend = makeLockedCloudBackend();
if (lockedCloudBackend) {
writeStoredBackends([lockedCloudBackend]);
const activeSelection = readStoredActiveBackend();
if (activeSelection?.backendId !== lockedCloudBackend.id) {
writeStoredActiveBackend({ backendId: lockedCloudBackend.id });
}
return [lockedCloudBackend];
}
const raw = window.localStorage.getItem(BACKENDS_STORAGE_KEY);
// First install: seed only when the launcher supplied enough information
// for a usable local backend.
if (raw === null) {
const defaultBackend = makeDefaultLocalBackend();
if (!defaultBackend) return [];
writeStoredBackends([defaultBackend]);
return [defaultBackend];
}
const parsed = JSON.parse(raw);
if (!Array.isArray(parsed)) return [];
const valid = parsed.filter(isValidBackend);
// If the stored array is empty (or everything in it failed validation),
// only re-seed when the launcher supplied both a host and API key.
if (valid.length === 0) {
const defaultBackend = makeDefaultLocalBackend();
if (!defaultBackend) return [];
writeStoredBackends([defaultBackend]);
return [defaultBackend];
}
const synced = syncLauncherDefaultLocalBackend(valid);
return synced;
} catch {
return [];
}
}
function parseBackendSelection(raw: string | null): BackendSelection | null {
if (!raw) return null;
try {
const parsed = JSON.parse(raw);
if (
typeof parsed !== "object" ||
parsed === null ||
typeof (parsed as BackendSelection).backendId !== "string"
) {
return null;
}
const orgIdRaw = (parsed as BackendSelection).orgId;
return {
backendId: (parsed as BackendSelection).backendId,
orgId:
typeof orgIdRaw === "string" && orgIdRaw.length > 0 ? orgIdRaw : null,
};
} catch {
return null;
}
}
function readStorageItem(
storage: Storage | undefined,
key: string,
): string | null {
try {
return storage?.getItem(key) ?? null;
} catch {
return null;
}
}
function writeStorageItem(
storage: Storage | undefined,
key: string,
value: string,
): void {
try {
storage?.setItem(key, value);
} catch {
/* ignore */
}
}
function removeStorageItem(storage: Storage | undefined, key: string): void {
try {
storage?.removeItem(key);
} catch {
/* ignore */
}
}
export function readStoredActiveBackend(): BackendSelection | null {
if (typeof window === "undefined") return null;
// Active backend is tab-scoped so reloading tab A does not adopt tab B's
// backend. localStorage remains a last-used fallback for fresh tabs and old
// persisted state.
const sessionSelection = parseBackendSelection(
readStorageItem(window.sessionStorage, ACTIVE_BACKEND_STORAGE_KEY),
);
if (sessionSelection) return sessionSelection;
return parseBackendSelection(
readStorageItem(window.localStorage, ACTIVE_BACKEND_STORAGE_KEY),
);
}
export function writeStoredActiveBackend(
selection: BackendSelection | null,
): void {
if (typeof window === "undefined") return;
if (!selection) {
removeStorageItem(window.sessionStorage, ACTIVE_BACKEND_STORAGE_KEY);
removeStorageItem(window.localStorage, ACTIVE_BACKEND_STORAGE_KEY);
return;
}
const serialized = JSON.stringify({
backendId: selection.backendId,
orgId: selection.orgId ?? null,
});
// Mirror to localStorage only as the default for new tabs/backward
// compatibility; reads in existing tabs prefer sessionStorage.
writeStorageItem(
window.sessionStorage,
ACTIVE_BACKEND_STORAGE_KEY,
serialized,
);
writeStorageItem(window.localStorage, ACTIVE_BACKEND_STORAGE_KEY, serialized);
}