Experiment to rebuild Diffuse using web applets.
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
11export function arrayShuffle<T>(array: Array<T>): Array<T> {
12 if (array.length === 0) {
13 return [];
14 }
15
16 array = [...array];
17
18 for (let index = array.length - 1; index > 0; index--) {
19 const randArr = crypto.getRandomValues(new Uint32Array(1));
20 const randVal = randArr[0] / 2 ** 32;
21 const newIndex = Math.floor(randVal * (index + 1));
22 [array[index], array[newIndex]] = [array[newIndex], array[index]];
23 }
24
25 return array;
26}
27
28export function cleanUndefinedValuesForTracks(tracks: Track[]): Track[] {
29 return tracks.map((track) => {
30 const t = { ...track };
31
32 if (t.tags) {
33 if ("album" in t.tags && t.tags.album === undefined) delete t.tags.album;
34 if ("artist" in t.tags && t.tags.artist === undefined) delete t.tags.artist;
35 if ("genre" in t.tags && t.tags.genre === undefined) delete t.tags.genre;
36 if ("year" in t.tags && t.tags.year === undefined) delete t.tags.year;
37
38 if ("of" in t.tags.disc && t.tags.disc.of === undefined) delete t.tags.disc.of;
39 if ("of" in t.tags.track && t.tags.track.of === undefined) delete t.tags.track.of;
40 }
41
42 return t;
43 });
44}
45
46export function comparable(value: unknown) {
47 return xxh32(JSON.stringify(value));
48}
49
50export function endpoint<T extends Record<string, any>>(ini: Comlink.Endpoint) {
51 const e = Comlink.wrap<T>(ini);
52 if ("start" in ini && typeof ini.start === "function") ini.start();
53 return e;
54}
55
56export function expose<T extends Record<string, any>>(actions: T): T {
57 if (globalThis.SharedWorkerGlobalScope && self instanceof SharedWorkerGlobalScope) {
58 self.onconnect = (event: MessageEvent) => {
59 const port = event.ports[0];
60 Comlink.expose(actions, port);
61 port.start();
62 };
63
64 (self as any).connected = true;
65 } else {
66 Comlink.expose(actions, self);
67 }
68
69 return actions;
70}
71
72export function groupTracksPerScheme(
73 tracks: Track[],
74 initial: Record<string, Track[]> = {},
75): Record<string, Track[]> {
76 return tracks.reduce((acc: Record<string, Track[]>, track: Track) => {
77 const scheme = track.uri.split(":", 1)[0];
78 return { ...acc, [scheme]: [...(acc[scheme] || []), track] };
79 }, initial);
80}
81
82export function inIframe() {
83 return window.self !== window.top;
84}
85
86export function isPrimitive(test: unknown) {
87 return test !== Object(test);
88}
89
90export function jsonDecode<T>(a: any): T {
91 return JSON.parse(new TextDecoder().decode(a));
92}
93
94export function jsonEncode<T>(a: T): Uint8Array {
95 return new TextEncoder().encode(JSON.stringify(a));
96}
97
98export async function trackArtworkCacheId(track: Track): Promise<string> {
99 return await crypto.subtle
100 .digest("SHA-256", new TextEncoder().encode(track.uri))
101 .then((a) => Uint8.toString(new Uint8Array(a), "base64url"));
102}
103
104export function transfer<T = unknown>(a: T) {
105 const b = getTransferables(a);
106 return Comlink.transfer(a, b);
107}