import { useDocumentStore } from "@/stores/documents" import type { DirEntry, UpdateEntry, errorEvent } from "./Document" export const controlUrl = '/api/control' export const uploadUrl = '/api/upload' export const watchUrl = '/api/watch' let tree = null as DirEntry | null let reconnectDuration = 500 let wsWatch = null as WebSocket | null export const connect = (path: string, handlers: Partial>) => { const webSocket = new WebSocket(new URL(path, location.origin.replace(/^http/, 'ws'))) for (const [event, handler] of Object.entries(handlers)) webSocket.addEventListener(event, handler) return webSocket } export const watchConnect = async () => { wsWatch = connect(watchUrl, { open() { console.log("Connected to", watchUrl)}, message: handleWatchMessage, close: watchReconnect, }) await wsWatch } export const watchDisconnect = () => { if (!wsWatch) return wsWatch.close() wsWatch = null } const watchReconnect = (event: MessageEvent) => { const store = useDocumentStore() if (store.connected) { console.warn("Disconnected from server", event) store.connected = false } reconnectDuration = Math.min(5000, reconnectDuration + 500) // The server closes the websocket after errors, so we need to reopen it setTimeout(() => { wsWatch = connect(watchUrl, { message: handleWatchMessage, close: watchReconnect, }) console.log("Attempting to reconnect...") }, reconnectDuration) } const handleWatchMessage = (event: MessageEvent) => { const store = useDocumentStore() const msg = JSON.parse(event.data) if ('error' in msg) { if (msg.error.code === 401) { store.user.isLoggedIn = false store.user.isOpenLoginModal = true } else { store.error = msg.error.message } } switch (true) { case !!msg.root: handleRootMessage(msg) break case !!msg.update: handleUpdateMessage(msg) break case !!msg.space: console.log('Watch space', msg.space) break case !!msg.error: handleError(msg) break default: } } function handleRootMessage({ root }: { root: DirEntry }) { const store = useDocumentStore() console.log('Watch root', root) reconnectDuration = 500 store.connected = true store.user.isLoggedIn = true store.updateRoot(root) tree = root } function handleUpdateMessage(updateData: { update: UpdateEntry[] }) { const store = useDocumentStore() console.log('Watch update', updateData.update) if (!tree) return console.error('Watch update before root') let node: DirEntry = tree for (const elem of updateData.update) { if (elem.deleted) { delete node.dir[elem.name] break // Deleted elements can't have further children } if (elem.name !== undefined) { // @ts-ignore node = node.dir[elem.name] ||= {} } if (elem.key !== undefined) node.key = elem.key if (elem.size !== undefined) node.size = elem.size if (elem.mtime !== undefined) node.mtime = elem.mtime if (elem.dir !== undefined) node.dir = elem.dir } store.updateRoot(tree) } function handleError(msg: errorEvent) { const store = useDocumentStore() if (msg.error.code === 401) { store.user.isOpenLoginModal = true store.user.isLoggedIn = false return } }