/** Open Graph / oEmbed HTML for `/:actor/:name`, `/:actor/:name/embed`, and the oEmbed JSON * endpoint, where `:name` is the project's slug when it has one, else its rkey. Social crawlers * don't run JavaScript, so these pages carry the metadata in plain tags; a human is sent on to * the app (D22). Every interpolated value is HTML-escaped. */ export interface ProjectCardData { /** The handle or DID as given in the URL, used to build the app link verbatim. */ actor: string; did: string; rkey: string; slug?: string; name: string; description?: string; /** A `com.atproto.sync.getBlob` URL for the thumbnail, already resolved by the caller. */ thumbnailUrl?: string; } export function escapeHtml(value: string): string { return value .replace(/&/g, '&') .replace(//g, '>') .replace(/"/g, '"') .replace(/'/g, '''); } function document(options: { title: string; description?: string; thumbnailUrl?: string; pageUrl: string; oembedUrl: string; body: string; }): string { const title = escapeHtml(options.title); const description = options.description ? escapeHtml(options.description) : undefined; const image = options.thumbnailUrl ? escapeHtml(options.thumbnailUrl) : undefined; const pageUrl = escapeHtml(options.pageUrl); const oembedUrl = escapeHtml(options.oembedUrl); return `
Redirecting to ${escapeHtml(data.name)}…
`, }); } /** `/:actor/:name/embed`: an iframe-friendly shell that boots the app in embed mode. The page * itself is the embed; nothing navigates it away. */ export function renderEmbedPage(data: ProjectCardData, appOrigin: string): string { const name = projectName(data); const embedSrc = embedPageUrl(appOrigin, data.actor, name); const escapedSrc = escapeHtml(embedSrc); return document({ title: data.name, description: data.description, thumbnailUrl: data.thumbnailUrl, pageUrl: embedSrc, oembedUrl: oembedJsonUrl(appOrigin, data.actor, name), body: ``, }); } export interface OEmbedJson { type: 'rich'; version: '1.0'; provider_name: 'nibling.dev'; title: string; html: string; width: number; height: number; } /** `GET /oembed?url=...`. `actor`/`rkey` are the ones the caller parsed out of `url`. */ export function renderOEmbedJson(data: ProjectCardData, appOrigin: string): OEmbedJson { const embedSrc = embedPageUrl(appOrigin, data.actor, projectName(data)); const width = 600; const height = 400; return { type: 'rich', version: '1.0', provider_name: 'nibling.dev', title: data.name, html: ``, width, height, }; }