A music player that connects to your cloud/distributed storage.
0
fork

Configure Feed

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

at e68f727ec6d8bde0a9a06f73af2ebae7f10ffc5f 150 lines 3.4 kB view raw
1import { ostiary, rpc } from "~/common/worker.js"; 2import { detach as detachUtil, groupKey } from "~/components/input/common.js"; 3 4import { 5 consultHostCached, 6 groupTracksByHost, 7 groupUrisByHost, 8 parseURI, 9} from "./common.js"; 10import { SCHEME } from "./constants.js"; 11 12/** 13 * @import { InputActions as Actions, ConsultGrouping } from "~/components/input/types.d.ts"; 14 */ 15 16//////////////////////////////////////////// 17// ACTIONS 18//////////////////////////////////////////// 19 20/** 21 * @type {Actions['artwork']} 22 */ 23export async function artwork(_uri) { 24 return null; 25} 26 27/** 28 * @type {Actions['consult']} 29 */ 30export async function consult(fileUriOrScheme) { 31 if (!fileUriOrScheme.includes(":")) { 32 return { supported: true, consult: "undetermined" }; 33 } 34 35 const parsed = parseURI(fileUriOrScheme); 36 if (!parsed) { 37 return { supported: false, reason: "Invalid HTTPS URL" }; 38 } 39 40 const consult = await consultHostCached(parsed.url); 41 return { supported: true, consult }; 42} 43 44/** 45 * @type {Actions['detach']} 46 */ 47export async function detach(args) { 48 return detachUtil({ 49 ...args, 50 51 inputScheme: SCHEME, 52 handleFileUri: ({ fileURI, tracks }) => { 53 const result = parseURI(fileURI); 54 if (!result) return tracks; 55 56 const did = result.host; 57 const groups = groupTracksByHost(tracks); 58 59 delete groups[did]; 60 61 return Object.values(groups).map((a) => a.tracks).flat(1); 62 }, 63 }); 64} 65 66/** 67 * @type {Actions['groupConsult']} 68 */ 69export async function groupConsult(uris) { 70 const groups = groupUrisByHost(uris); 71 72 const promises = Object.entries(groups).map( 73 async ([_domainId, { host, uris }]) => { 74 const testUri = uris[0]; 75 const available = testUri ? await consultHostCached(testUri) : false; 76 77 /** @type {ConsultGrouping} */ 78 const grouping = available 79 ? { available, scheme: SCHEME, uris } 80 : { available, reason: "Host unreachable", scheme: SCHEME, uris }; 81 82 return { 83 key: groupKey(SCHEME, host), 84 grouping, 85 }; 86 }, 87 ); 88 89 const entries = (await Promise.all(promises)).map(( 90 entry, 91 ) => [entry.key, entry.grouping]); 92 93 return Object.fromEntries(entries); 94} 95 96/** 97 * @type {Actions['list']} 98 */ 99export async function list(cachedTracks = []) { 100 return cachedTracks.map((track) => { 101 const t = { ...track }; 102 103 if (t.kind === "placeholder") { 104 t.kind = undefined; 105 } 106 107 return t; 108 }); 109} 110 111/** 112 * @type {Actions['resolve']} 113 */ 114export async function resolve({ method, uri }) { 115 if (uri.startsWith("blob:")) { 116 const expiresInSeconds = 60 * 60 * 24 * 365; 117 const expiresAtSeconds = Math.round(Date.now() / 1000) + expiresInSeconds; 118 return { url: uri, expiresAt: expiresAtSeconds }; 119 } 120 121 const parsed = parseURI(uri); 122 if (!parsed) return undefined; 123 124 // HTTPS URLs don't need resolution - they're already accessible. 125 // Just return the URL as-is with a far-future expiration. 126 const expiresInSeconds = 60 * 60 * 24 * 365; // 1 year 127 const expiresAtSeconds = Math.round(Date.now() / 1000) + expiresInSeconds; 128 129 return { 130 url: parsed.url, 131 expiresAt: expiresAtSeconds, 132 }; 133} 134 135//////////////////////////////////////////// 136// ⚡️ 137//////////////////////////////////////////// 138 139ostiary((context) => { 140 // Setup RPC 141 142 rpc(context, { 143 artwork, 144 consult, 145 detach, 146 groupConsult, 147 list, 148 resolve, 149 }); 150});