use lexicon.ts rpc on the client

This commit is contained in:
Charlotte Som 2025-06-07 04:20:00 +01:00
parent 1d41bc2711
commit 1dd74500f2
4 changed files with 33 additions and 25 deletions

View file

@ -4,42 +4,47 @@ import type { VideoLexiconUniverse } from "../common/lexicons.ts";
import { resolveDid, resolveHandle } from "../common/identity.ts";
import { VIDEO_PATTERN } from "../common/routes.ts";
import type { ATProtoUniverse } from "@char/lexicon.ts/atproto";
import { XRPC } from "@char/lexicon.ts/rpc";
type VideoRecord = Infer<VideoLexiconUniverse, "blue.cerulea.video.video">;
const fetchVideoRecord = async (
did: string,
rkey: string
): Promise<VideoRecord> => {
// TODO: we do lots of casting here that we shouldn't need once we have lexicon.ts validations
const didDoc = await resolveDid(did);
const pds = didDoc.service?.find((it) => it.id === "#atproto_pds")
?.serviceEndpoint as string | undefined;
if (!pds) throw new Error("could not resolve pds for requested repo");
const getRecordURL = new URL("/xrpc/com.atproto.repo.getRecord", pds);
getRecordURL.searchParams.set("collection", "blue.cerulea.video.video");
getRecordURL.searchParams.set("repo", did);
getRecordURL.searchParams.set("rkey", rkey);
const xrpc = new XRPC<ATProtoUniverse>(pds);
const record = await xrpc.get("com.atproto.repo.getRecord", {
parameters: {
collection: "blue.cerulea.video.video",
repo: did,
rkey,
},
});
const recordResponse = await fetch(getRecordURL);
if (recordResponse.status !== 200)
throw new Error("got error fetching record");
const record = (await recordResponse.json()).value as VideoRecord;
return record;
// TODO: replace this cast with a lexicon.ts validation check
return record.value as VideoRecord;
};
const resolveVideoURL = async (did: string, blob: string): Promise<string> => {
const res = await fetch("/xrpc/blue.cerulea.video.fetchVideo", {
method: "POST",
headers: { "content-encoding": "application/json" },
body: JSON.stringify({ repo: did, blob }),
const resolveVideoURL = async (
did: `did:${string}`,
blob: string
): Promise<string> => {
const xrpc = new XRPC<VideoLexiconUniverse>(globalThis.location.href);
try {
const res = await xrpc.call("blue.cerulea.video.fetchVideo", {
input: { repo: did, blob },
});
if (res.status !== 200) throw new Error("got error fetching video cdn url");
const data = await res.json();
return `/user-content/${data.filename}`;
return `/user-content/${res.filename}`;
} catch {
throw new Error("got error fetching URL of video at CDN");
}
};
const main = async () => {
@ -52,7 +57,8 @@ const main = async () => {
rkey: string;
};
const did = repo.startsWith("did:") ? repo : await resolveHandle(repo);
const isDid = (s: string): s is `did:${string}` => s.startsWith("did:");
const did = isDid(repo) ? repo : await resolveHandle(repo);
const record = await fetchVideoRecord(did, rkey);
const videoURL = await resolveVideoURL(

View file

@ -17,7 +17,7 @@ const didResolver = new CompositeDidDocumentResolver({
},
});
export function resolveHandle(handle: string): Promise<string> {
export function resolveHandle(handle: string): Promise<`did:${string}`> {
return handleResolver.resolve(handle as `${string}.${string}`);
}

View file

@ -13,7 +13,9 @@
"@std/http": "jsr:@std/http@^1.0.17",
"@std/path": "jsr:@std/path@^1.1.0",
"@zod/mini": "npm:@zod/mini@^4.0.0-beta.20250505T195954",
"@char/lexicon.ts": "./vendor/lexicon.ts/lib/mod.ts"
"@char/lexicon.ts": "./vendor/lexicon.ts/lib/mod.ts",
"@char/lexicon.ts/atproto": "./vendor/lexicon.ts/pkg/atproto-lexica/mod.ts",
"@char/lexicon.ts/rpc": "./vendor/lexicon.ts/pkg/rpc/rpc.ts"
},
"compilerOptions": {
"lib": ["deno.window", "dom"],

2
vendor/lexicon.ts vendored

@ -1 +1 @@
Subproject commit f070c8b56df7d3335a970118a401dfd7ee2f63f2
Subproject commit 1edb68e5eac3ddfabdd77674ed8193ef9af05649