119 lines
3.3 KiB
TypeScript

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<Record<keyof WebSocketEventMap, any>>) => {
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
}
}