diff --git a/.gitea/workflows/desktop.yaml b/.gitea/workflows/desktop.yaml index 620325a..228729b 100644 --- a/.gitea/workflows/desktop.yaml +++ b/.gitea/workflows/desktop.yaml @@ -49,6 +49,7 @@ jobs: uses: akkuman/gitea-release-action@v1 with: name: Focus ${{ steps.tagName.outputs.tag }} + prerelease: true files: |- frontend/src-tauri/target/release/focus frontend/src-tauri/target/release/bundle/deb/*.deb diff --git a/frontend/run.sh b/frontend/run.sh index 69e09df..b8ce412 100644 --- a/frontend/run.sh +++ b/frontend/run.sh @@ -1,5 +1,8 @@ PUBLIC_BACKEND_URL=${PUBLIC_BACKEND_URL:-http://localhost:3000} +PUBLIC_BACKEND_WSURL=${PUBLIC_BACKEND_WSURL:-${PUBLIC_BACKEND_URL/http:/ws:}} +PUBLIC_BACKEND_WSURL=${PUBLIC_BACKEND_WSURL/https:/wss:} find /usr/share/nginx/html -type f -exec sed -i "s|http://localhost:3000|$PUBLIC_BACKEND_URL|g" {} + +find /usr/share/nginx/html -type f -exec sed -i "s|ws://localhost:3000|$PUBLIC_BACKEND_WSURL|g" {} + nginx -g 'daemon off;' diff --git a/frontend/src/lib/api/websocket.ts b/frontend/src/lib/api/websocket.ts deleted file mode 100644 index a57ac7b..0000000 --- a/frontend/src/lib/api/websocket.ts +++ /dev/null @@ -1,62 +0,0 @@ -import Project, { projects } from '$lib/types/Project'; -import { hasPendingRequests } from '$lib/utils/api'; -import { toastWarning } from '$lib/utils/toasts'; -import { get } from 'svelte/store'; - -let socket: WebSocket; - -export function connectWebSocket() { - socket = new WebSocket('ws://localhost:3000/api/v1/ws'); - - socket.onopen = () => { - console.log('WebSocket connected'); - }; - - socket.onclose = () => { - console.log('WebSocket disconnected'); - toastWarning( - 'WebSocket disconnected', - 'You may experience sync issues. You can try to reload the page.' - ); - - const reconnectTimer = setTimeout(() => { - connectWebSocket(); - clearTimeout(reconnectTimer); - }, 5000); - }; - - socket.onerror = (err) => { - console.error('WebSocket error:', err); - }; - - socket.onmessage = async (event) => { - const parsed = JSON.parse(event.data); - while (hasPendingRequests()) { - await new Promise((resolve) => setTimeout(resolve, 100)); - } - applyMessage(parsed); - }; -} - -function applyMessage(data: any) { - switch (data.object) { - case 'project': - applyProject(data); - break; - default: - console.log('Unknown object:', data); - } -} - -function applyProject(data: any) { - switch (data.action) { - case 'create': - if (get(projects).find((p) => p.id === data.id)) break; - case 'update': - Project.parse(data.value); - break; - case 'delete': - projects.set(get(projects).filter((p) => p.id !== data.id)); - break; - } -} diff --git a/frontend/src/lib/components/project/Sidebar.svelte b/frontend/src/lib/components/project/Sidebar.svelte index 48d76d2..c9b590b 100644 --- a/frontend/src/lib/components/project/Sidebar.svelte +++ b/frontend/src/lib/components/project/Sidebar.svelte @@ -46,7 +46,7 @@
Focus. - v0.0.1 + v0.3.2
diff --git a/frontend/src/lib/utils/webSocketManager.ts b/frontend/src/lib/utils/webSocketManager.ts new file mode 100644 index 0000000..0bac38e --- /dev/null +++ b/frontend/src/lib/utils/webSocketManager.ts @@ -0,0 +1,93 @@ +import Project, { projects } from '$lib/types/Project'; +import { hasPendingRequests } from '$lib/utils/api'; +import { toastAlert, toastWarning } from '$lib/utils/toasts'; +import { get } from 'svelte/store'; + +export default class WebSocketManager { + _socket: WebSocket | null; + _queue: any[]; + _reconnectAttempts: number; + _maxReconnectAttempts: number; + _hasBeenConnected: boolean; + + constructor() { + this._socket = null; + this._queue = []; + this._reconnectAttempts = 0; + this._maxReconnectAttempts = 5; + this._hasBeenConnected = false; + } + + connect() { + if ( + this._socket && + (this._socket.readyState === WebSocket.OPEN || + this._socket.readyState === WebSocket.CONNECTING) + ) { + return; + } + + this._socket = new WebSocket('ws://localhost:3000/api/v1/ws'); + + this._socket.onopen = () => { + this._reconnectAttempts = 0; + this._hasBeenConnected = true; + console.log('WebSocket connected'); + }; + + this._socket.onclose = () => { + console.log('WebSocket disconnected'); + + if (this._reconnectAttempts < this._maxReconnectAttempts) { + toastWarning( + 'WebSocket disconnected', + `You may experience sync issues. Trying to reconnect... (${this._reconnectAttempts + 1}/${ + this._maxReconnectAttempts + })` + ); + this._reconnectAttempts++; + setTimeout(() => { + this._socket?.close(); + this.connect(); + }, 5000); + } else { + toastAlert('Failed to connect to WebSocket', 'Please refresh the page to try again.'); + } + }; + + this._socket.onerror = (err) => { + console.warn('WebSocket error:', err); + }; + + this._socket.onmessage = async (event) => { + const data = JSON.parse(event.data); + while (hasPendingRequests()) { + await new Promise((resolve) => setTimeout(resolve, 100)); + } + applyMessage(data); + }; + } +} + +function applyMessage(data: any) { + switch (data.object) { + case 'project': + applyProject(data); + break; + default: + console.log('Unknown object:', data); + } +} + +function applyProject(data: any) { + switch (data.action) { + case 'create': + if (get(projects).find((p) => p.id === data.id)) break; + case 'update': + Project.parse(data.value); + break; + case 'delete': + projects.set(get(projects).filter((p) => p.id !== data.id)); + break; + } +} diff --git a/frontend/src/routes/+page.svelte b/frontend/src/routes/+page.svelte index 07abb3d..4533a71 100644 --- a/frontend/src/routes/+page.svelte +++ b/frontend/src/routes/+page.svelte @@ -1,16 +1,17 @@