audio streaming app plyr.fm
38
fork

Configure Feed

Select the types of activity you want to include in your feed.

add top-level AT-URI resolution route (#1206)

support URLs like https://plyr.fm/at://did:plc:xxx/fm.plyr.track/rkey
by resolving them via backend by-uri endpoints and 301 redirecting
to the canonical page (/track/{id}, /playlist/{id}).

uses AtUri from @atproto/api for proper URI parsing and validation.

ref: streamplace/streamplace#1012

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>

authored by

nate nowack
Claude Opus 4.6
and committed by
GitHub
6512dcef f7aa0310

+56
+56
frontend/src/routes/at/[...uri]/+server.ts
··· 1 + import { error, redirect } from '@sveltejs/kit'; 2 + import { AtUri } from '@atproto/api'; 3 + import { API_URL } from '$lib/config'; 4 + import type { RequestHandler } from './$types'; 5 + 6 + /** 7 + * resolve AT-URIs to canonical plyr.fm pages. 8 + * 9 + * handles URLs like: https://plyr.fm/at://did:plc:xxx/fm.plyr.track/rkey 10 + * browsers may normalize the :// so we accept multiple path forms. 11 + */ 12 + export const GET: RequestHandler = async ({ params, fetch }) => { 13 + // reconstruct AT-URI from the catch-all path segment. 14 + // browsers may collapse "://" to ":/" or strip it entirely, 15 + // so we normalize by stripping any leading ":/" or "://" prefix. 16 + const raw = params.uri; 17 + const cleaned = raw.replace(/^:\/{1,2}/, ''); 18 + const atUriStr = `at://${cleaned}`; 19 + 20 + let uri: AtUri; 21 + try { 22 + uri = new AtUri(atUriStr); 23 + } catch { 24 + throw error(400, 'invalid AT-URI'); 25 + } 26 + 27 + if (!uri.collection || !uri.rkey) { 28 + throw error(400, 'AT-URI must include collection and rkey'); 29 + } 30 + 31 + // route by collection suffix — handles environment-aware namespaces 32 + // (fm.plyr.track, fm.plyr.stg.track, fm.plyr.dev.track, etc.) 33 + if (uri.collection.endsWith('.track')) { 34 + const response = await fetch( 35 + `${API_URL}/tracks/by-uri?uri=${encodeURIComponent(uri.toString())}` 36 + ); 37 + if (!response.ok) { 38 + throw error(404, 'track not found'); 39 + } 40 + const track: { id: number } = await response.json(); 41 + throw redirect(301, `/track/${track.id}`); 42 + } 43 + 44 + if (uri.collection.endsWith('.list')) { 45 + const response = await fetch( 46 + `${API_URL}/lists/playlists/by-uri?uri=${encodeURIComponent(uri.toString())}` 47 + ); 48 + if (!response.ok) { 49 + throw error(404, 'playlist not found'); 50 + } 51 + const playlist: { id: number } = await response.json(); 52 + throw redirect(301, `/playlist/${playlist.id}`); 53 + } 54 + 55 + throw error(404, `unsupported collection: ${uri.collection}`); 56 + };