0
2
// Turning a failed space read into something a reader can act on.
//
// Three very different problems reach a handler as an exception, and until this
// existed only one of them was translated — the rest surfaced as a 500, which
// told a reader nothing and made "the author never added me", "sign in again"
// and "your server cannot do this" look identical.
import { InvalidRequestError } from "$hatk";
import { isNotAuthorized, SpaceCredentialError } from "@hatk/hatk/spaces";
import { getSpaceSupport, pdsEndpointFor } from "../helpers/spaceSupport.ts";
import { SpaceError } from "./client.ts";
type Db = {
query: (sql: string, params?: unknown[]) => Promise<unknown[]>;
run: (sql: string, params?: unknown[]) => Promise<void>;
};
/** The status a thrown error carries, whether it is ours or hatk's ProxyError. */
function statusOf(err: unknown): number | null {
const status = (err as { status?: unknown })?.status;
return typeof status === "number" ? status : null;
}
/**
* Rethrow a space failure as the closest thing to an explanation.
*
* Never returns — it either throws an InvalidRequestError naming the problem or
* rethrows what it was given.
*/
export async function throwSpaceError(err: unknown, db: Db, viewerDid: string): Promise<never> {
const status = statusOf(err);
// No session to trade for a credential. hatk drops the PDS session when a
// scope is refused, so this is what a reader hits on the request after the
// one that told them to authorize again — the browser still holds a viewer
// cookie, and nothing behind it works.
if (status === 401) {
throw new InvalidRequestError("Sign in again to open this gallery", "SessionExpired");
}
// The space's authority will not issue this reader a credential. hatk names
// the refusal, which the authority answers with a 400. The reference alpha,
// since 0.0.0-spaces-alpha-20261029171913, answers a refused reader as though
// the space did not exist, so as not to disclose that it does: to the reader
// the two are the same.
if (
isNotAuthorized(err) ||
(err instanceof SpaceCredentialError && err.code === "SpaceNotFound")
) {
throw new InvalidRequestError("Only members can see this", "NotAuthorized");
}
// A PDS with no permissioned data answers the very first call — a delegation
// token from the reader's own server — with a 404 for a method it does not
// serve. Indistinguishable from "no such space" by status alone, so ask.
if (status === 403 || status === 404) {
const endpoint = await pdsEndpointFor(db, viewerDid);
if (endpoint) {
const support = await getSpaceSupport(db, endpoint);
if (!support.supported) {
throw new InvalidRequestError(
"Private galleries aren't available for your account yet",
"SpacesUnsupported",
);
}
}
throw new InvalidRequestError("Only members can see this", "NotAuthorized");
}
// A repo host that cannot be reached or resolved is a failure of ours to
// report, not of the reader to fix.
if (err instanceof SpaceError || err instanceof SpaceCredentialError) {
throw new InvalidRequestError(
"This couldn't be loaded right now. Try again in a moment",
"UpstreamFailed",
);
}
throw err;
}