Something went wrong. Try again.
Monorepo for Tangled tangled.org
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115import { error } from "@sveltejs/kit";import { ok } from "@atcute/client";import { isBlob } from "@atcute/lexicons/interfaces";import { mainSchema as uploadBlobSchema } from "@atcute/atproto/types/repo/uploadBlob";import type { Did, ResourceUri } from "@atcute/lexicons/syntax";import type { OAuthUserAgent } from "@atcute/oauth-browser-client";import { createClient } from "#lib/auth/agent.ts";import { awaitDeletedRecord, awaitIndexedOrSaved } from "#lib/api/awaitRecord.ts";import type { BobbinContext } from "#lib/api/client.ts";import { resolveMiniDoc } from "#lib/api/identity.ts";import { orNull } from "#lib/api/load.ts";import { getString, type RecordView, type StringRecord } from "#lib/api/records.ts";import { cidOf, nsidOf, recordKeyOf } from "#lib/api/syntax.ts";import { rkeyFromUri } from "#lib/api/uri.ts";import { deleteRecord, putRecord, type WrittenRecord } from "#lib/api/write.ts";import { fetchPublic, readTextCapped } from "#lib/publicFetch.ts";
const STRING_COLLECTION = nsidOf("org.tangled.snippet.string");
export interface StringSubject { did: Did; handle: string; pds: string; uri: ResourceUri; record: RecordView<StringRecord>;}
export const loadStringSubject = async ( ctx: BobbinContext, handle: string, rkey: string): Promise<StringSubject> => { const doc = await orNull(resolveMiniDoc(ctx, handle)); if (!doc) error(404, "String not found");
const uri = `at://${doc.did}/${STRING_COLLECTION}/${rkey}` as ResourceUri; const record = await orNull(getString(ctx, uri)); if (!record) error(404, "String not found");
return { did: doc.did as Did, handle: doc.handle, pds: doc.pds, uri, record };};
/** Past this a string's contents aren't fetched: a pds could otherwise stream us anything. */const MAX_CONTENTS_BYTES = 10 * 1024 * 1024;const CONTENTS_TIMEOUT_MS = 10_000;
// bobbin indexes the record only; the content blob lives on the owner's pds. that pds is// whatever the owner's did document says, so the fetch only goes to a public https host// (redirects included), gives up after a while and stops reading past MAX_CONTENTS_BYTESexport const fetchStringContents = async ( fetch: typeof globalThis.fetch, { did, pds, record }: StringSubject): Promise<string> => { let url: URL; try { url = new URL("/xrpc/com.atproto.sync.getBlob", pds); } catch { error(502, "String contents unavailable"); } url.searchParams.set("did", did); const { content } = record.value.snippet; url.searchParams.set("cid", isBlob(content) ? content.ref.$link : content.cid); try { const res = await fetchPublic(fetch, url, { signal: AbortSignal.timeout(CONTENTS_TIMEOUT_MS) }); if (!res.ok) throw new Error(`getBlob: ${res.status}`); return await readTextCapped(res, MAX_CONTENTS_BYTES); } catch { error(502, "String contents unavailable"); }};
export const uploadStringContents = async ( agent: OAuthUserAgent, contents: string): Promise<StringRecord["snippet"]["content"]> => { const input = new Blob([contents], { type: "text/plain" }); const { blob } = await ok(createClient(agent).call(uploadBlobSchema, { input })); return blob;};
export const putString = async ( agent: OAuthUserAgent, bobbin: BobbinContext, rkey: string, record: StringRecord, swapRecord?: string): Promise<WrittenRecord> => { const written = await putRecord( agent, STRING_COLLECTION, recordKeyOf(rkey), record, swapRecord ? cidOf(swapRecord) : undefined ); await awaitIndexedOrSaved(bobbin, written); return written;};
export const deleteString = async ( agent: OAuthUserAgent, bobbin: BobbinContext, uri: ResourceUri, swapRecord?: string): Promise<boolean> => { await deleteRecord( agent, STRING_COLLECTION, rkeyFromUri(uri), swapRecord ? cidOf(swapRecord) : undefined ); return awaitDeletedRecord(bobbin, uri);};