import * as Reference from "../reference/implementation.js" import type { Maybe } from "../../common/types.js" // TYPES export type Channel = { close: () => void send: (data: ChannelData) => void } export type ChannelOptions = { handleMessage: (event: MessageEvent) => void username: string } export type ChannelData = string | ArrayBufferLike | Blob | ArrayBufferView // FUNCTIONS export const createWssChannel = async ( reference: Reference.Implementation, socketEndpoint: ({ rootDID }: { rootDID: string }) => string, options: ChannelOptions ): Promise => { const { username, handleMessage } = options const rootDID = await waitForRootDid(reference, username) if (!rootDID) { throw new Error(`Failed to lookup DID for ${username}`) } const topic = `deviceLink#${rootDID}` console.log("Opening channel", topic) const socket: Maybe = new WebSocket(socketEndpoint({ rootDID })) await waitForOpenConnection(socket) socket.onmessage = handleMessage const send = publishOnWssChannel(socket) const close = closeWssChannel(socket) return { send, close } } const waitForRootDid = async ( reference: Reference.Implementation, username: string, ): Promise => { let rootDid = await reference.didRoot.lookup(username).catch(() => { console.warn("Could not fetch root DID. Retrying.") return null }) if (rootDid) { return rootDid } return new Promise((resolve, reject) => { const maxRetries = 10 let tries = 0 const rootDidInterval = setInterval(async () => { rootDid = await reference.didRoot.lookup(username).catch(() => { console.warn("Could not fetch root DID. Retrying.") return null }) if (!rootDid && tries < maxRetries) { tries++ return } else if (!rootDid && tries === maxRetries) { reject("Failed to fetch root DID.") } clearInterval(rootDidInterval) resolve(rootDid) }, 1000) }) } const waitForOpenConnection = async (socket: WebSocket): Promise => { return new Promise((resolve, reject) => { socket.onopen = () => resolve() socket.onerror = () => reject("Websocket channel could not be opened") }) } export const closeWssChannel = (socket: Maybe): () => void => { return function () { if (socket) socket.close(1000) } } export const publishOnWssChannel = (socket: WebSocket): (data: ChannelData) => void => { return function (data: ChannelData) { const binary = typeof data === "string" ? new TextEncoder().encode(data).buffer : data socket?.send(binary) } }