openhands / src /api /backend-registry /url-selection.ts
SaylorTwift's picture
SaylorTwift HF Staff
Add files using upload-large-folder tool
3d700dd verified
Raw History Blame Contribute Delete
3.16 kB
import type { Backend, BackendSelection, ResolvedActiveBackend } from "./types";
/**
* Query parameters that pin a link to the backend (and cloud org) that owns
* the linked resource.
*
* The active backend is tab-scoped: `readStoredActiveBackend()` prefers
* `sessionStorage` and only falls back to `localStorage`. A tab opened with
* cmd/ctrl-click or middle-click does not reliably inherit the opener's
* `sessionStorage`, so it can boot from the `localStorage` fallback — which
* holds whichever backend was selected *last in any tab*, not the one the
* sidebar we were just looking at belongs to. The new tab then resolves the
* conversation id against the wrong backend and shows "conversation not
* found". Carrying the identity in the URL makes the link self-describing so
* the new tab pins to the right backend regardless of what storage holds.
*/
export const BACKEND_QUERY_PARAM = "backend";
export const ORG_QUERY_PARAM = "org";
/**
* Append the active backend identity to an in-app path so opening it in a new
* browsing context resolves against the same backend. Any fragment on the
* path is preserved verbatim and kept after the query string.
*/
export function withBackendSelectionParams(
path: string,
active: ResolvedActiveBackend,
): string {
const { backend, orgId } = active;
if (!backend.id) return path;
const hashIndex = path.indexOf("#");
const fragment = hashIndex === -1 ? "" : path.slice(hashIndex);
const withoutFragment = hashIndex === -1 ? path : path.slice(0, hashIndex);
// Split at the *first* `?` only. A later `?` is ordinary query data (a
// `next=` redirect carrying its own query, say), and `split("?")` would
// silently drop everything past it.
const queryIndex = withoutFragment.indexOf("?");
const pathname =
queryIndex === -1 ? withoutFragment : withoutFragment.slice(0, queryIndex);
const existingSearch =
queryIndex === -1 ? "" : withoutFragment.slice(queryIndex + 1);
const params = new URLSearchParams(existingSearch);
params.set(BACKEND_QUERY_PARAM, backend.id);
if (orgId) params.set(ORG_QUERY_PARAM, orgId);
return `${pathname}?${params.toString()}${fragment}`;
}
/**
* Read a backend selection off the current URL, keeping it only when it names
* a registered backend. An unknown id (a link from another machine, or a
* backend that has since been removed) is ignored so the caller falls back to
* the stored selection.
*/
export function readBackendSelectionFromUrl(
backends: Backend[],
search: string,
): BackendSelection | null {
if (!search) return null;
let params: URLSearchParams;
try {
params = new URLSearchParams(search);
} catch {
return null;
}
const backendId = params.get(BACKEND_QUERY_PARAM);
if (!backendId) return null;
if (!backends.some((backend) => backend.id === backendId)) return null;
const orgId = params.get(ORG_QUERY_PARAM);
return { backendId, orgId: orgId || null };
}
/** The current tab's query string, or "" outside a browser. */
export function currentLocationSearch(): string {
if (typeof window === "undefined") return "";
return window.location.search;
}