Experiment to rebuild Diffuse using web applets.
0

Configure Feed

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

1import * as Uint8 from "uint8arrays"; 2import * as Comlink from "comlink"; 3import { xxh32 } from "xxh32"; 4import { getTransferables } from "@okikio/transferables"; 5 6import type { Track } from "@applets/core/types"; 7 8// export { SharedWorkerPolyfill as SharedWorker } from "@okikio/sharedworker"; 9export const SharedWorker = globalThis.SharedWorker; 10 11//////////////////////////////////////////// 12// 🌳 13//////////////////////////////////////////// 14 15export type WorkerTasks = { 16 listenForActions: ReturnType<typeof handleWorkerActions>; 17}; 18 19//////////////////////////////////////////// 20// 🛠️ 21//////////////////////////////////////////// 22 23export function arrayShuffle<T>(array: Array<T>): Array<T> { 24 if (array.length === 0) { 25 return []; 26 } 27 28 array = [...array]; 29 30 for (let index = array.length - 1; index > 0; index--) { 31 const randArr = crypto.getRandomValues(new Uint32Array(1)); 32 const randVal = randArr[0] / 2 ** 32; 33 const newIndex = Math.floor(randVal * (index + 1)); 34 [array[index], array[newIndex]] = [array[newIndex], array[index]]; 35 } 36 37 return array; 38} 39 40export function cleanUndefinedValuesForTracks(tracks: Track[]): Track[] { 41 return tracks.map((track) => { 42 const t = { ...track }; 43 44 if (t.tags) { 45 if ("album" in t.tags && t.tags.album === undefined) delete t.tags.album; 46 if ("artist" in t.tags && t.tags.artist === undefined) delete t.tags.artist; 47 if ("genre" in t.tags && t.tags.genre === undefined) delete t.tags.genre; 48 if ("year" in t.tags && t.tags.year === undefined) delete t.tags.year; 49 50 if ("of" in t.tags.disc && t.tags.disc.of === undefined) delete t.tags.disc.of; 51 if ("of" in t.tags.track && t.tags.track.of === undefined) delete t.tags.track.of; 52 } 53 54 return t; 55 }); 56} 57 58export function comparable(value: unknown) { 59 return xxh32(JSON.stringify(value)); 60} 61 62export function endpoint<T extends Record<string, any> = WorkerTasks>(ini: Comlink.Endpoint) { 63 const e = Comlink.wrap<T>(ini); 64 if ("start" in ini && typeof ini.start === "function") ini.start(); 65 return e; 66} 67 68export function expose<A extends Record<string, any>>(tasks: A): A { 69 if (globalThis.SharedWorkerGlobalScope && self instanceof SharedWorkerGlobalScope) { 70 self.onconnect = (event: MessageEvent) => { 71 const port = event.ports[0]; 72 Comlink.expose(tasks, port); 73 port.start(); 74 }; 75 76 (self as any).connected = true; 77 } else { 78 Comlink.expose(tasks, self); 79 } 80 81 return tasks; 82} 83 84export function groupTracksPerScheme( 85 tracks: Track[], 86 initial: Record<string, Track[]> = {}, 87): Record<string, Track[]> { 88 return tracks.reduce((acc: Record<string, Track[]>, track: Track) => { 89 const scheme = track.uri.split(":", 1)[0]; 90 return { ...acc, [scheme]: [...(acc[scheme] || []), track] }; 91 }, initial); 92} 93 94export function inIframe() { 95 return window.self !== window.top; 96} 97 98export function isPrimitive(test: unknown) { 99 return test !== Object(test); 100} 101 102export function jsonDecode<T>(a: any): T { 103 return JSON.parse(new TextDecoder().decode(a)); 104} 105 106export function jsonEncode<T>(a: T): Uint8Array { 107 return new TextEncoder().encode(JSON.stringify(a)); 108} 109 110export function provide<A extends Record<string, any>, B extends Record<string, any>>( 111 actions: A, 112 tasks: B = {} as B, 113) { 114 return expose<WorkerTasks & B>({ 115 listenForActions: handleWorkerActions(actions), 116 ...tasks, 117 }); 118} 119 120export async function trackArtworkCacheId(track: Track): Promise<string> { 121 return await crypto.subtle 122 .digest("SHA-256", new TextEncoder().encode(track.uri)) 123 .then((a) => Uint8.toString(new Uint8Array(a), "base64url")); 124} 125 126export function transfer<T = unknown>(a: T) { 127 const b = getTransferables(a); 128 return Comlink.transfer(a, b); 129} 130 131// PRIVATE 132 133function handleWorkerActions<A extends Record<string, any>>(actions: A) { 134 async function handleAction( 135 port: MessagePort, 136 action: { 137 type: "action"; 138 id: string; 139 actionId: string; 140 arguments: any; 141 }, 142 ) { 143 const result = await actions[action.actionId]?.(action.arguments); 144 return postMessage(port, action.id, result); 145 } 146 147 function postMessage<T>(port: MessagePort, id: string, result: T) { 148 port.postMessage( 149 { 150 type: "actioncomplete", 151 id, 152 result, 153 }, 154 { 155 transfer: getTransferables(result), 156 }, 157 ); 158 } 159 160 return (port: MessagePort) => { 161 Comlink.expose(actions, port); 162 163 port.onmessage = async (message) => { 164 switch (message.data?.type) { 165 case "action": 166 return handleAction(port, message.data); 167 } 168 }; 169 }; 170}