Experiment to rebuild Diffuse using web applets.
1import * as Uint8 from "uint8arrays";
2import { createEndpoint, type MessageEndpoint } from "@remote-ui/rpc";
3import { xxh32 } from "xxh32";
4
5import type { Track } from "@applets/core/types";
6
7export { SharedWorkerPolyfill as SharedWorker } from "@okikio/sharedworker";
8
9export function arrayShuffle<T>(array: Array<T>): Array<T> {
10 if (array.length === 0) {
11 return [];
12 }
13
14 array = [...array];
15
16 for (let index = array.length - 1; index > 0; index--) {
17 const randArr = crypto.getRandomValues(new Uint32Array(1));
18 const randVal = randArr[0] / 2 ** 32;
19 const newIndex = Math.floor(randVal * (index + 1));
20 [array[index], array[newIndex]] = [array[newIndex], array[index]];
21 }
22
23 return array;
24}
25
26export function cleanUndefinedValuesForTracks(tracks: Track[]): Track[] {
27 return tracks.map((track) => {
28 const t = { ...track };
29
30 if (t.tags) {
31 if ("album" in t.tags && t.tags.album === undefined) delete t.tags.album;
32 if ("artist" in t.tags && t.tags.artist === undefined) delete t.tags.artist;
33 if ("genre" in t.tags && t.tags.genre === undefined) delete t.tags.genre;
34 if ("year" in t.tags && t.tags.year === undefined) delete t.tags.year;
35
36 if ("of" in t.tags.disc && t.tags.disc.of === undefined) delete t.tags.disc.of;
37 if ("of" in t.tags.track && t.tags.track.of === undefined) delete t.tags.track.of;
38 }
39
40 return t;
41 });
42}
43
44export function comparable(value: unknown) {
45 return xxh32(JSON.stringify(value));
46}
47
48export function endpoint<T extends Record<string, any>>(port: MessagePort) {
49 const e = createEndpoint<T>(port);
50 port.start();
51 return e;
52}
53
54export function expose<T extends Record<string, any>>(actions: T): T {
55 (self as unknown as SharedWorkerGlobalScope).onconnect = (event: MessageEvent) => {
56 const port = event.ports[0];
57 createEndpoint<T>(port).expose(actions);
58 port.start();
59 };
60
61 return actions;
62}
63
64export function groupTracksPerScheme(
65 tracks: Track[],
66 initial: Record<string, Track[]> = {},
67): Record<string, Track[]> {
68 return tracks.reduce((acc: Record<string, Track[]>, track: Track) => {
69 const scheme = track.uri.split(":", 1)[0];
70 return { ...acc, [scheme]: [...(acc[scheme] || []), track] };
71 }, initial);
72}
73
74export function inIframe() {
75 return window.self !== window.top;
76}
77
78export function isPrimitive(test: unknown) {
79 return test !== Object(test);
80}
81
82export function jsonDecode<T>(a: any): T {
83 return JSON.parse(new TextDecoder().decode(a));
84}
85
86export function jsonEncode<T>(a: T): Uint8Array {
87 return new TextEncoder().encode(JSON.stringify(a));
88}
89
90export async function trackArtworkCacheId(track: Track): Promise<string> {
91 return await crypto.subtle
92 .digest("SHA-256", new TextEncoder().encode(track.uri))
93 .then((a) => Uint8.toString(new Uint8Array(a), "base64url"));
94}