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