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