Experiment to rebuild Diffuse using web applets.
0
fork

Configure Feed

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

feat: manage worker connections automatically

+75 -74
+2 -25
src/pages/processor/artwork/_applet.astro
··· 1 1 <script> 2 - import type { Actions } from "@scripts/processor/artwork/worker"; 3 - import type { ArtworkRequest } from "./types.d.ts"; 4 2 import { register } from "@scripts/applet/common"; 5 3 import { endpoint, SharedWorker } from "@scripts/common"; 6 - import { transfer } from "@scripts/common"; 7 4 import manifest from "./_manifest.json"; 8 5 9 6 //////////////////////////////////////////// 10 7 // SETUP 11 8 //////////////////////////////////////////// 12 - const worker = endpoint<Actions>( 9 + const worker = endpoint( 13 10 new SharedWorker(new URL("../../../scripts/processor/artwork/worker", import.meta.url), { 14 11 type: "module", 15 12 name: manifest.name, ··· 17 14 ); 18 15 19 16 // Register 20 - const context = register(); 21 - 22 - context.scope.onworkerport = (event) => { 23 - if (!event.port) return; 24 - worker.connect(transfer(event.port)); 25 - }; 26 - 27 - //////////////////////////////////////////// 28 - // ACTIONS 29 - //////////////////////////////////////////// 30 - function artwork(request: ArtworkRequest) { 31 - // return worker.artwork(transfer(request)); 32 - return []; 33 - } 34 - 35 - function supply(items: ArtworkRequest[]) { 36 - // return worker.supply(transfer(items)); 37 - } 38 - 39 - context.setActionHandler("artwork", artwork); 40 - context.setActionHandler("supply", supply); 17 + const context = register({ worker }); 41 18 </script>
+11 -1
src/scripts/applet/common.ts
··· 1 1 import type { Applet, AppletEvent, AppletScope } from "@web-applets/sdk"; 2 + import type * as Comlink from "comlink"; 2 3 3 4 import { applets } from "@web-applets/sdk"; 4 5 import { type ElementConfigurator, h } from "spellcaster/hyperscript.js"; ··· 6 7 import QS from "query-string"; 7 8 8 9 import type { ResolvedUri } from "@applets/core/types"; 10 + import { transfer, type WorkerActions } from "@scripts/common"; 9 11 10 12 //////////////////////////////////////////// 11 13 // 🪟 Applet connecting ··· 106 108 setActionHandler<H extends Function>(actionId: string, actionHandler: H): void; 107 109 }; 108 110 109 - export function register<DataType = any>(): BroadcastedApplet<DataType> { 111 + export function register<DataType = any>( 112 + options: { worker?: Comlink.Remote<WorkerActions> } = {}, 113 + ): BroadcastedApplet<DataType> { 110 114 const url = new URL(location.href); 111 115 const scope = applets.register<DataType>(); 112 116 ··· 280 284 scope.setActionHandler(actionId, handler); 281 285 }, 282 286 }; 287 + 288 + if (options.worker !== undefined) 289 + context.scope.onworkerport = (event) => { 290 + if (!event.port) return; 291 + options.worker?.perform(transfer(event.port)); 292 + }; 283 293 284 294 return context; 285 295 }
+59 -2
src/scripts/common.ts
··· 8 8 // export { SharedWorkerPolyfill as SharedWorker } from "@okikio/sharedworker"; 9 9 export const SharedWorker = globalThis.SharedWorker; 10 10 11 + //////////////////////////////////////////// 12 + // 🌳 13 + //////////////////////////////////////////// 14 + 15 + export type WorkerActions = { 16 + perform: ReturnType<typeof handleWorkerActions>; 17 + }; 18 + 19 + //////////////////////////////////////////// 20 + // 🛠️ 21 + //////////////////////////////////////////// 22 + 11 23 export function arrayShuffle<T>(array: Array<T>): Array<T> { 12 24 if (array.length === 0) { 13 25 return []; ··· 47 59 return xxh32(JSON.stringify(value)); 48 60 } 49 61 50 - export function endpoint<T extends Record<string, any>>(ini: Comlink.Endpoint) { 62 + export function endpoint<T extends Record<string, any> = WorkerActions>(ini: Comlink.Endpoint) { 51 63 const e = Comlink.wrap<T>(ini); 52 64 if ("start" in ini && typeof ini.start === "function") ini.start(); 53 65 return e; 54 66 } 55 67 56 - export function expose<T extends Record<string, any>>(actions: T): T { 68 + export function expose<A extends Record<string, any>>(actions: A): A { 57 69 if (globalThis.SharedWorkerGlobalScope && self instanceof SharedWorkerGlobalScope) { 58 70 self.onconnect = (event: MessageEvent) => { 59 71 const port = event.ports[0]; ··· 95 107 return new TextEncoder().encode(JSON.stringify(a)); 96 108 } 97 109 110 + export function provide<A extends Record<string, any>>(actions: A) { 111 + return expose({ 112 + perform: handleWorkerActions(actions), 113 + }); 114 + } 115 + 98 116 export async function trackArtworkCacheId(track: Track): Promise<string> { 99 117 return await crypto.subtle 100 118 .digest("SHA-256", new TextEncoder().encode(track.uri)) ··· 105 123 const b = getTransferables(a); 106 124 return Comlink.transfer(a, b); 107 125 } 126 + 127 + // PRIVATE 128 + 129 + function handleWorkerActions<A extends Record<string, any>>(actions: A) { 130 + async function handleAction( 131 + port: MessagePort, 132 + action: { 133 + type: "action"; 134 + id: string; 135 + actionId: string; 136 + arguments: any; 137 + }, 138 + ) { 139 + const result = await actions[action.actionId]?.(action.arguments); 140 + return postMessage(port, action.id, result); 141 + } 142 + 143 + function postMessage<T>(port: MessagePort, id: string, result: T) { 144 + port.postMessage( 145 + { 146 + type: "actioncomplete", 147 + id, 148 + result, 149 + }, 150 + { 151 + transfer: getTransferables(result), 152 + }, 153 + ); 154 + } 155 + 156 + return (port: MessagePort) => { 157 + port.onmessage = async (message) => { 158 + switch (message.data?.type) { 159 + case "action": 160 + return handleAction(port, message.data); 161 + } 162 + }; 163 + }; 164 + }
+3 -46
src/scripts/processor/artwork/worker.ts
··· 2 2 import * as IDB from "idb-keyval"; 3 3 4 4 import type { Artwork, ArtworkRequest } from "./types"; 5 - import { expose, transfer } from "@scripts/common"; 5 + import { expose, provide, transfer } from "@scripts/common"; 6 6 import { IDB_ARTWORK_PREFIX } from "./constants"; 7 7 import { musicMetadataTags } from "../metadata/common"; 8 8 import { getTransferables } from "@okikio/transferables"; ··· 13 13 //////////////////////////////////////////// 14 14 // ACTIONS 15 15 //////////////////////////////////////////// 16 - const c = expose({ 17 - connect, 18 - }); 19 - 20 - export type Actions = typeof c; 21 - 22 - const actions: { [key: string]: Function } = { 16 + provide({ 23 17 artwork, 24 18 supply, 25 - }; 26 - 27 - // ⚡️ 28 - 29 - async function connect(port: MessagePort) { 30 - port.onmessage = async (message) => { 31 - switch (message.data?.type) { 32 - case "action": 33 - return handleAction(port, message.data); 34 - } 35 - }; 36 - } 37 - 38 - async function handleAction( 39 - port: MessagePort, 40 - action: { 41 - type: "action"; 42 - id: string; 43 - actionId: string; 44 - arguments: any; 45 - }, 46 - ) { 47 - const result = await actions[action.actionId]?.(action.arguments); 48 - return postMessage(port, action.id, result); 49 - } 50 - 51 - function postMessage<T>(port: MessagePort, id: string, result: T) { 52 - port.postMessage( 53 - { 54 - type: "actioncomplete", 55 - id, 56 - result, 57 - }, 58 - { 59 - transfer: getTransferables(result), 60 - }, 61 - ); 62 - } 19 + }); 63 20 64 21 // Actions 65 22