~ knotless Search/
↑↓ move↵ open⇧↵ all results

chadtmiller.com/pds.js

An atproto PDS in JavaScript, for Cloudflare Workers and Node

0
0
main
123 lines · 4.5 KB Raw History
// The public web page that displays a record, when one is known. The mapping
// from an AT-URI to an app's web URL is app-specific and hand-kept, so only the
// collections with a known page get a link; everything else gets none, and the
// card simply shows no link. Apps are added here one at a time as their public
// URL schemes are pinned down.

const BSKY = 'https://bsky.app';
const GRAIN = 'https://grain.social';
const TANGLED = 'https://tangled.org';

/**
 * `at://<did>/<collection>/<rkey>` addresses one record. Split it into its
 * parts, or null if it isn't a full record URI.
 * @param {string} uri
 * @returns {{ did: string, collection: string, rkey: string } | null}
 */
export function parseAtUri(uri) {
  if (typeof uri !== 'string' || !uri.startsWith('at://')) return null;
  const [did, collection, rkey] = uri.slice('at://'.length).split('/');
  return did && collection && rkey ? { did, collection, rkey } : null;
}

// A post/list/feed AT-URI -> its bsky.app page. Likes, reposts, and the like
// point *at* one of these rather than having a page of their own.
/** @param {string} uri */
function atUriToBsky(uri) {
  const target = parseAtUri(uri);
  if (!target) return null;
  const { did, collection, rkey } = target;
  switch (collection) {
    case 'app.bsky.feed.post':
      return `${BSKY}/profile/${did}/post/${rkey}`;
    case 'app.bsky.graph.list':
      return `${BSKY}/profile/${did}/lists/${rkey}`;
    case 'app.bsky.feed.generator':
      return `${BSKY}/profile/${did}/feed/${rkey}`;
    default:
      return null;
  }
}

// A person's profile page, from the DID a follow/block/listitem points at.
/** @param {unknown} did */
function bskyProfile(did) {
  return typeof did === 'string' && did.startsWith('did:')
    ? `${BSKY}/profile/${did}`
    : null;
}

// A gallery or profile Grain AT-URI -> its grain.social page. A favorite has no
// page of its own; it points at the gallery it favorited.
/** @param {string} uri */
function atUriToGrain(uri) {
  const target = parseAtUri(uri);
  if (!target) return null;
  const { did, collection, rkey } = target;
  switch (collection) {
    case 'social.grain.gallery':
      return `${GRAIN}/profile/${did}/gallery/${rkey}`;
    case 'social.grain.actor.profile':
      return `${GRAIN}/profile/${did}`;
    default:
      return null;
  }
}

/**
 * The public web page for a record, or null when none is known. Records that
 * have a page of their own link to it; a like or repost links the post it
 * points at (its `subject`), and a follow/block/listitem links that person's
 * profile.
 * @param {string} uri the record's own AT-URI
 * @param {any} value the record body, for the subject-pointing types
 * @returns {string | null}
 */
export function recordWebLink(uri, value) {
  const target = parseAtUri(uri);
  if (!target) return null;
  const { did, collection, rkey } = target;
  switch (collection) {
    case 'app.bsky.feed.post':
    case 'app.bsky.graph.list':
    case 'app.bsky.feed.generator':
      return atUriToBsky(uri);
    case 'app.bsky.actor.profile':
      return bskyProfile(did);
    case 'app.bsky.graph.starterpack':
      return `${BSKY}/starter-pack/${did}/${rkey}`;
    // A like or repost has no page; link the post it points at.
    case 'app.bsky.feed.like':
    case 'app.bsky.feed.repost':
      return atUriToBsky(value?.subject?.uri);
    // These point at a person; link their profile.
    case 'app.bsky.graph.follow':
    case 'app.bsky.graph.block':
    case 'app.bsky.graph.listitem':
      return bskyProfile(value?.subject);

    // Grain — a photo app. Galleries and profiles have their own page.
    case 'social.grain.gallery':
    case 'social.grain.actor.profile':
      return atUriToGrain(uri);
    // A favorite has no page; link the gallery it points at. The subject is a
    // plain at-uri string here, not a strongRef object.
    case 'social.grain.favorite':
      return atUriToGrain(value?.subject);

    // Tangled — git on atproto; tangled.org takes a DID in the path. A repo's
    // URL is keyed by its cosmetic `name`, not its rkey, so the repo record
    // itself links (it carries the name) but a star can't — a star only
    // references the repo by knot/DID/at-uri, never the name.
    case 'sh.tangled.repo':
      return value?.name ? `${TANGLED}/${did}/${value.name}` : null;
    case 'sh.tangled.graph.follow':
      return typeof value?.subject === 'string' &&
        value.subject.startsWith('did:')
        ? `${TANGLED}/${value.subject}`
        : null;

    default:
      return null;
  }
}