167 lines · 5.8 KB · TypeScript
RawDownload
import type { EnvironmentConnectionPhase } from "@t3tools/client-runtime/connection";import {  AuthOrchestrationOperateScope,  type AuthSessionState,  type EnvironmentId,} from "@t3tools/contracts"; export interface ProviderEnvironmentOptionLike {  readonly environmentId: EnvironmentId;  readonly label: string;} export function isProviderSettingsEnvironmentAvailable(input: {  readonly connectionPhase: EnvironmentConnectionPhase;  readonly hasServerConfig: boolean;}): boolean {  return input.connectionPhase === "connected" && input.hasServerConfig;} export function buildProviderEnvironmentOptions<T extends ProviderEnvironmentOptionLike>(  environments: ReadonlyArray<T>,  primaryEnvironmentId: EnvironmentId | null,): ReadonlyArray<T> {  return environments.toSorted((left, right) => {    const leftIsPrimary = left.environmentId === primaryEnvironmentId;    const rightIsPrimary = right.environmentId === primaryEnvironmentId;    if (leftIsPrimary !== rightIsPrimary) {      return leftIsPrimary ? -1 : 1;    }    return (      left.label.localeCompare(right.label) ||      String(left.environmentId).localeCompare(String(right.environmentId))    );  });} export function resolveSelectedProviderEnvironmentId(  environments: ReadonlyArray<ProviderEnvironmentOptionLike>,  selectedEnvironmentId: EnvironmentId | null,  primaryEnvironmentId: EnvironmentId | null,): EnvironmentId | null {  if (    selectedEnvironmentId !== null &&    environments.some((environment) => environment.environmentId === selectedEnvironmentId)  ) {    return selectedEnvironmentId;  }  if (    primaryEnvironmentId !== null &&    environments.some((environment) => environment.environmentId === primaryEnvironmentId)  ) {    return primaryEnvironmentId;  }  return environments[0]?.environmentId ?? null;} export type ProviderEnvironmentAccess =  | { readonly kind: "editable" }  /** `reason` distinguishes waiting on the device from waiting on permissions. */  | { readonly kind: "loading"; readonly reason: "config" | "permissions" }  | { readonly kind: "read-only" }  | { readonly kind: "unavailable" }  | { readonly kind: "error" }; /** * Whether the session may change provider configuration on an environment. * `pending` means the answer is still unknown, which must not be presented as * editable: rendering controls we already know might be rejected only turns a * permission problem into a failed write. */export type ProviderOperateAccess = "granted" | "denied" | "pending"; /** * Resolve operate access from an environment's `/api/auth/session` answer. * * Cached session data wins over an in-flight revalidation. The session atoms * are SWR-backed, so they report `isPending` on every background refresh; * treating that as unknown would flip a working panel back to loading and * discard in-progress edits. * * `missingScopesAccess` decides the case where the session resolved but did * not report scopes: the primary serves the web app itself so its server * always reports them (absence means denial), while a remote device may run an * older server version that predates scope reporting, where denial would lock * out a legitimate session. The environment RPC layer stays authoritative * either way. */function resolveSessionOperateAccess(input: {  readonly session: Pick<AuthSessionState, "authenticated" | "scopes"> | null;  readonly isPending: boolean;  readonly hasError: boolean;  readonly missingScopesAccess: "granted" | "denied";}): ProviderOperateAccess {  if (input.session === null) {    if (input.isPending) {      return "pending";    }    // A failed session fetch is a transport problem, not a permission    // decision — locking the panel read-only would misreport it. Stay    // optimistic; the environment RPC layer still rejects unauthorized writes.    return input.hasError ? "granted" : "denied";  }  if (!input.session.authenticated) {    return "denied";  }  if (input.session.scopes === undefined) {    return input.missingScopesAccess;  }  return input.session.scopes.includes(AuthOrchestrationOperateScope) ? "granted" : "denied";} /** Operate access for the primary environment's own browser session. */export function resolvePrimaryOperateAccess(input: {  readonly isPrimary: boolean;  readonly hasDesktopBridge: boolean;  readonly session: Pick<AuthSessionState, "authenticated" | "scopes"> | null;  readonly isPending: boolean;  readonly hasError: boolean;}): ProviderOperateAccess {  if (!input.isPrimary || input.hasDesktopBridge) {    return "granted";  }  return resolveSessionOperateAccess({    session: input.session,    isPending: input.isPending,    hasError: input.hasError,    missingScopesAccess: "denied",  });} /** * Operate access for a non-primary environment, derived from the scopes its * `/api/auth/session` endpoint reports for this client's credential. */export function resolveRemoteOperateAccess(input: {  readonly session: Pick<AuthSessionState, "authenticated" | "scopes"> | null;  readonly isPending: boolean;  readonly hasError: boolean;}): ProviderOperateAccess {  return resolveSessionOperateAccess({    ...input,    missingScopesAccess: "granted",  });} export function classifyProviderEnvironmentAccess(input: {  readonly connectionPhase: EnvironmentConnectionPhase;  readonly hasServerConfig: boolean;  readonly operateAccess: ProviderOperateAccess;}): ProviderEnvironmentAccess {  if (input.connectionPhase === "error") {    return { kind: "error" };  }  if (input.connectionPhase !== "connected") {    return { kind: "unavailable" };  }  if (!input.hasServerConfig) {    return { kind: "loading", reason: "config" };  }  if (input.operateAccess === "pending") {    return { kind: "loading", reason: "permissions" };  }  if (input.operateAccess === "denied") {    return { kind: "read-only" };  }  return { kind: "editable" };}