···2424import { metadata } from './metadata';
2525import { getDetailedProfile } from './methods';
2626import { signUpPDS } from './settings';
2727+import { SvelteURLSearchParams } from 'svelte/reactivity';
27282829import type { ActorIdentifier, Did } from '@atcute/lexicons';
2930···6869 })
6970 });
70717171- const params = new URLSearchParams(location.hash.slice(1));
7272+ const params = new SvelteURLSearchParams(location.hash.slice(1));
72737374 const did = (localStorage.getItem('current-login') as Did) ?? undefined;
7475···151152 }
152153}
153154154154-async function finalizeLogin(params: URLSearchParams, did?: Did) {
155155+async function finalizeLogin(params: SvelteURLSearchParams, did?: Did) {
155156 try {
156157 const { session } = await finalizeAuthorization(params);
157158 replaceState(location.pathname + location.search, {});
+1-1
src/lib/atproto/index.ts
···1414 uploadBlob,
1515 describeRepo,
1616 getBlobURL,
1717- getCDNImageBlobUrl as getImageBlobUrl,
1717+ getCDNImageBlobUrl,
1818 searchActorsTypeahead
1919} from './methods';
+113-10
src/lib/atproto/methods.ts
···11-import type { Did, Handle } from '@atcute/lexicons';
11+import { parseResourceUri, type Did, type Handle } from '@atcute/lexicons';
22import { user } from './auth.svelte';
33import type { AllowedCollection } from './settings';
44import {
···1010 WellKnownHandleResolver
1111} from '@atcute/identity-resolver';
1212import { Client, simpleFetchHandler } from '@atcute/client';
1313-import type { AppBskyActorDefs } from '@atcute/bluesky';
1313+import { type AppBskyActorDefs } from '@atcute/bluesky';
14141515export type Collection = `${string}.${string}.${string}`;
1616+import * as TID from '@atcute/tid';
16171818+/**
1919+ * Parses an AT Protocol URI into its components.
2020+ * @param uri - The AT URI to parse (e.g., "at://did:plc:xyz/app.bsky.feed.post/abc123")
2121+ * @returns An object containing the repo, collection, and rkey or undefined if not an AT uri
2222+ */
1723export function parseUri(uri: string) {
1818- const [did, collection, rkey] = uri.replace('at://', '').split('/');
1919- return { did, collection, rkey } as {
2020- collection: Collection;
2121- rkey: string;
2222- did: string;
2323- };
2424+ const parts = parseResourceUri(uri);
2525+ if (!parts.ok) return;
2626+ return parts.value;
2427}
25282929+/**
3030+ * Resolves a handle to a DID using DNS and HTTP methods.
3131+ * @param handle - The handle to resolve (e.g., "alice.bsky.social")
3232+ * @returns The DID associated with the handle
3333+ */
2634export async function resolveHandle({ handle }: { handle: Handle }) {
2735 const handleResolver = new CompositeHandleResolver({
2836 methods: {
···4250 }
4351});
44525353+/**
5454+ * Gets the PDS (Personal Data Server) URL for a given DID.
5555+ * @param did - The DID to look up
5656+ * @returns The PDS service endpoint URL
5757+ * @throws If no PDS is found in the DID document
5858+ */
4559export async function getPDS(did: Did) {
4646- const doc = await didResolver.resolve(did as `did:plc:${string}` | `did:web:${string}`);
6060+ const doc = await didResolver.resolve(did as Did<'plc'> | Did<'web'>);
4761 if (!doc.service) throw new Error('No PDS found');
4862 for (const service of doc.service) {
4963 if (service.id === '#atproto_pds') {
···5266 }
5367}
54686969+/**
7070+ * Fetches a detailed Bluesky profile for a user.
7171+ * @param data - Optional object with did and client
7272+ * @param data.did - The DID to fetch the profile for (defaults to current user)
7373+ * @param data.client - The client to use (defaults to public Bluesky API)
7474+ * @returns The profile data or undefined if not found
7575+ */
5576export async function getDetailedProfile(data?: { did?: Did; client?: Client }) {
5677 data ??= {};
5778 data.did ??= user.did;
···7192 return response.data;
7293}
73949595+/**
9696+ * Creates an AT Protocol client for a user's PDS.
9797+ * @param did - The DID of the user
9898+ * @returns A client configured for the user's PDS
9999+ * @throws If the PDS cannot be found
100100+ */
74101export async function getClient({ did }: { did: Did }) {
75102 const pds = await getPDS(did);
76103 if (!pds) throw new Error('PDS not found');
···82109 return client;
83110}
84111112112+/**
113113+ * Lists records from a repository collection with pagination support.
114114+ * @param did - The DID of the repository (defaults to current user)
115115+ * @param collection - The collection to list records from
116116+ * @param cursor - Pagination cursor for continuing from a previous request
117117+ * @param limit - Maximum number of records to return (default 100, set to 0 for all records)
118118+ * @param client - The client to use (defaults to user's PDS client)
119119+ * @returns An array of records from the collection
120120+ */
85121export async function listRecords({
86122 did,
87123 collection,
···129165 return allRecords;
130166}
131167168168+/**
169169+ * Fetches a single record from a repository.
170170+ * @param did - The DID of the repository (defaults to current user)
171171+ * @param collection - The collection the record belongs to
172172+ * @param rkey - The record key (defaults to "self")
173173+ * @param client - The client to use (defaults to user's PDS client)
174174+ * @returns The record data
175175+ */
132176export async function getRecord({
133177 did,
134178 collection,
···162206 return JSON.parse(JSON.stringify(record.data));
163207}
164208209209+/**
210210+ * Creates or updates a record in the current user's repository.
211211+ * Only accepts collections that are configured in permissions.
212212+ * @param collection - The collection to write to (must be in permissions.collections)
213213+ * @param rkey - The record key (defaults to "self")
214214+ * @param record - The record data to write
215215+ * @returns The response from the PDS
216216+ * @throws If the user is not logged in
217217+ */
165218export async function putRecord({
166219 collection,
167220 rkey = 'self',
···187240 return response;
188241}
189242243243+/**
244244+ * Deletes a record from the current user's repository.
245245+ * Only accepts collections that are configured in permissions.
246246+ * @param collection - The collection the record belongs to (must be in permissions.collections)
247247+ * @param rkey - The record key (defaults to "self")
248248+ * @returns True if the deletion was successful
249249+ * @throws If the user is not logged in
250250+ */
190251export async function deleteRecord({
191252 collection,
192253 rkey = 'self'
···207268 return response.ok;
208269}
209270271271+/**
272272+ * Uploads a blob to the current user's PDS.
273273+ * @param blob - The blob data to upload
274274+ * @returns The blob metadata including ref, mimeType, and size, or undefined on failure
275275+ * @throws If the user is not logged in
276276+ */
210277export async function uploadBlob({ blob }: { blob: Blob }) {
211278 if (!user.did || !user.client) throw new Error("Can't upload blob: Not logged in");
212279···231298 return blobInfo;
232299}
233300301301+/**
302302+ * Gets metadata about a repository.
303303+ * @param client - The client to use
304304+ * @param did - The DID of the repository (defaults to current user)
305305+ * @returns Repository metadata or undefined on failure
306306+ */
234307export async function describeRepo({ client, did }: { client: Client; did?: Did }) {
235308 did ??= user.did;
236309 if (!did) {
···248321 return repo.data;
249322}
250323324324+/**
325325+ * Constructs a URL to fetch a blob directly from a user's PDS.
326326+ * @param did - The DID of the user who owns the blob
327327+ * @param blob - The blob reference object
328328+ * @returns The URL to fetch the blob
329329+ */
251330export async function getBlobURL({
252331 did,
253332 blob
···264343 return `${pds}/xrpc/com.atproto.sync.getBlob?did=${did}&cid=${blob.ref.$link}`;
265344}
266345346346+/**
347347+ * Constructs a Bluesky CDN URL for an image blob.
348348+ * @param did - The DID of the user who owns the blob (defaults to current user)
349349+ * @param blob - The blob reference object
350350+ * @returns The CDN URL for the image in webp format
351351+ */
267352export function getCDNImageBlobUrl({
268353 did,
269354 blob
270355}: {
271271- did: string;
356356+ did?: string;
272357 blob: {
273358 $type: 'blob';
274359 ref: {
···276361 };
277362 };
278363}) {
364364+ did ??= user.did;
365365+279366 return `https://cdn.bsky.app/img/feed_thumbnail/plain/${did}/${blob.ref.$link}@webp`;
280367}
281368369369+/**
370370+ * Searches for actors with typeahead/autocomplete functionality.
371371+ * @param q - The search query
372372+ * @param limit - Maximum number of results (default 10)
373373+ * @param host - The API host to use (defaults to public Bluesky API)
374374+ * @returns An object containing matching actors and the original query
375375+ */
282376export async function searchActorsTypeahead(
283377 q: string,
284378 limit: number = 10,
···301395302396 return { actors: response.data.actors, q };
303397}
398398+399399+/**
400400+ * Return a TID based on current time
401401+ *
402402+ * @returns TID for current time
403403+ */
404404+export function createTID() {
405405+ return TID.now();
406406+}