Experiment to rebuild Diffuse using web applets.
0

Configure Feed

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

1import * as URI from "uri-js"; 2 3import type { Consult, ConsultGrouping, GroupConsult, Track } from "@applets/core/types.d.ts"; 4import { SCHEME } from "./constants"; 5import { 6 fetchHandles, 7 fetchHandlesList, 8 groupTracksByHandle, 9 recursiveList, 10 trackHandleId, 11} from "./common"; 12import { expose } from "@scripts/common"; 13 14//////////////////////////////////////////// 15// ACTIONS 16//////////////////////////////////////////// 17const actions = expose({ 18 consult, 19 contextualize, 20 groupConsult, 21 list, 22 resolve, 23}); 24 25export type Actions = typeof actions; 26 27// Actions 28 29export async function consult(fileUriOrScheme: string): Promise<Consult> { 30 if (!self.FileSystemDirectoryHandle) { 31 return { supported: false, reason: "File System Access API is not supported" }; 32 } 33 34 if (!fileUriOrScheme.includes(":")) { 35 if (fileUriOrScheme !== SCHEME) return { supported: false, reason: "Scheme does not match" }; 36 return { supported: true, consult: "undetermined" }; 37 } 38 39 const handles = await fetchHandles(); 40 const uri = URI.parse(fileUriOrScheme); 41 if (uri.scheme !== SCHEME) return { supported: false, reason: "Scheme does not match" }; 42 return { supported: true, consult: uri.host && !!handles[uri.host] ? true : false }; 43} 44 45export async function contextualize(cachedTracks: Track[]) {} 46 47async function groupConsult(tracks: Track[]): Promise<GroupConsult> { 48 const groups = groupTracksByHandle(tracks); 49 const handles = await fetchHandles(); 50 51 const promises = Object.entries(groups).map(async ([handleId, { tracks }]) => { 52 const handle = handles[handleId]; 53 const grouping: ConsultGrouping = handle 54 ? { available: true, tracks } 55 : { available: false, reason: "Handle not available" }; 56 57 return { 58 key: URI.serialize({ scheme: SCHEME, host: handleId }), 59 grouping, 60 }; 61 }); 62 63 const entries = (await Promise.all(promises)).map((entry) => [entry.key, entry.grouping]); 64 return Object.fromEntries(entries); 65} 66 67export async function list(cachedTracks: Track[] = []) { 68 const handles = await fetchHandlesList(); 69 70 // Recursive listing of all tracks of available handles 71 const processed: Track[][] = await Promise.all( 72 handles.map(({ id, handle }) => { 73 return recursiveList(handle, id, []); 74 }), 75 ); 76 77 // Group tracks by handle id & index by track uri 78 const cache = cachedTracks.reduce((acc: Record<string, Record<string, Track>>, track: Track) => { 79 const handleId = trackHandleId(track); 80 if (!handleId) return acc; 81 82 return { ...acc, [handleId]: { ...(acc[handleId] || {}), [track.uri]: track } }; 83 }, {}); 84 85 // Replace indexes in groups of which we have the handle. 86 // Keeping around tracks with handles we don't have access to, 87 // and removing tracks that are no longer available (for handles we do have access to). 88 const groups = processed.flat(1).reduce( 89 (acc, track) => { 90 const handleId = trackHandleId(track); 91 if (!handleId) throw new Error("New tracks are missing a handle id!"); 92 93 return { ...acc, [handleId]: { ...acc[handleId], [track.uri]: track } }; 94 }, 95 handles.reduce((acc: Record<string, Record<string, Track>>, handle) => { 96 return { ...acc, [handle.id]: {} }; 97 }, cache), 98 ); 99 100 // Transform in track list and sort by uri 101 const data = Object.values(groups) 102 .map((tracks) => Object.values(tracks)) 103 .flat(1) 104 .sort((a: any, b: any) => { 105 if (a.uri < b.uri) return -1; 106 if (a.uri > b.uri) return 1; 107 return 0; 108 }); 109 110 // Fin 111 return data; 112} 113 114export async function resolve(args: { uri: string }) { 115 const fileUri = args.uri; 116 117 const uri = URI.parse(fileUri); 118 if (uri.scheme !== SCHEME) return undefined; 119 if (!uri.host || !uri.path) return undefined; 120 121 const handles = await fetchHandles(); 122 const handle = handles[uri.host]; 123 if (!handle) return undefined; 124 125 const path = URI.unescapeComponent(uri.path); 126 const parts = (path.startsWith("/") ? path.slice(1) : path).split("/"); 127 const filename = parts[parts.length - 1]; 128 129 const dirHandle = await parts 130 .slice(0, -1) 131 .reduce( 132 async ( 133 acc: Promise<FileSystemDirectoryHandle>, 134 part: string, 135 ): Promise<FileSystemDirectoryHandle> => { 136 const h = await acc; 137 return await h.getDirectoryHandle(part); 138 }, 139 Promise.resolve(handle), 140 ); 141 142 const fileHandle = await dirHandle.getFileHandle(filename); 143 const file = await fileHandle.getFile(); 144 const url = URL.createObjectURL(file); 145 146 return { expiresAt: Infinity, url }; 147}