Skip to content

Commit 39599ab

Browse files
committed
feat: enhance Socket.IO configuration and API handling
- Introduced a new function to build Socket.IO client options, streamlining the connection setup. - Updated the resolveSocketApiOrigin function to improve API origin resolution based on public configuration. - Enhanced logging during Socket.IO connection events to include transport details. - Refactored socket connection logic in various components to utilize the new client options function for better maintainability.
1 parent 54baff7 commit 39599ab

6 files changed

Lines changed: 92 additions & 21 deletions

File tree

apps/web/nuxt.config.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,11 @@ import setupApp from './src/server/extension.setup'
77
import { loadingBarHijackFilter } from './src/composables/useLoadingBarHijackFilter'
88

99
const SESAME_APP_API_URL = process.env.SESAME_APP_API_URL || 'http://127.0.0.1:4000'
10-
/** URL API exposée au navigateur (WebSocket). Ex. http://mactacx:4002 si l'API est joignable sur ce host. */
10+
const SESAME_APP_API_URL_PARSED = new URL(SESAME_APP_API_URL)
11+
/** URL API exposée au navigateur (WebSocket). Ex. http://mactacx:4002 (Docker) ou :4000 (API native). */
1112
const SESAME_APP_PUBLIC_API_URL = process.env.SESAME_APP_PUBLIC_API_URL || ''
13+
/** Port API vu depuis le navigateur (Docker : 4002 mappé, interne API : 4000). */
14+
const SESAME_APP_PUBLIC_API_PORT = process.env.SESAME_APP_PUBLIC_API_PORT || ''
1215
const SESAME_ALLOWED_HOSTS = process.env.SESAME_ALLOWED_HOSTS ? process.env.SESAME_ALLOWED_HOSTS.split(',') : []
1316
const SOCKET_IO_PROXY_TARGET = SESAME_APP_API_URL.replace(/\/$/, '')
1417
const IS_DEV = process.env.NODE_ENV === 'development'
@@ -18,6 +21,7 @@ if (SESAME_ALLOWED_HOSTS.length === 0 && !/localhost/.test(SESAME_APP_API_URL) &
1821
}
1922

2023
consola.info(`[Nuxt] SESAME_APP_API_URL: ${SESAME_APP_API_URL}`)
24+
consola.info(`[Nuxt] Socket.IO public port: ${SESAME_APP_PUBLIC_API_PORT || SESAME_APP_API_URL_PARSED.port || '4000'}`)
2125
consola.info(`[Nuxt] SESAME_ALLOWED_HOSTS: ${SESAME_ALLOWED_HOSTS}`)
2226

2327
let SESAME_APP_DARK_MODE: 'auto' | boolean = false
@@ -81,6 +85,9 @@ export default defineNuxtConfig({
8185
public: {
8286
release: process.env.npm_package_name + '@' + process.env.npm_package_version,
8387
socketApiUrl: SESAME_APP_PUBLIC_API_URL,
88+
socketApiPort: SESAME_APP_API_URL_PARSED.port || (SESAME_APP_API_URL_PARSED.protocol === 'https:' ? '443' : '4000'),
89+
socketPublicApiPort: SESAME_APP_PUBLIC_API_PORT || SESAME_APP_API_URL_PARSED.port || (SESAME_APP_API_URL_PARSED.protocol === 'https:' ? '443' : '4000'),
90+
socketApiProtocol: SESAME_APP_API_URL_PARSED.protocol,
8491
sentry: {
8592
dsn: process.env.SESAME_SENTRY_DSN,
8693
},

apps/web/src/composables/useSocketApiOrigin.ts

Lines changed: 71 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,84 @@
1+
import type { ManagerOptions, SocketOptions } from 'socket.io-client'
2+
import { tryUseNuxtApp, useAppConfig, useRuntimeConfig } from '#app'
3+
4+
/** WebSocket d'abord ; Socket.IO repasse en long-polling si le proxy bloque l'upgrade. */
5+
export const SOCKET_IO_TRANSPORTS = ['websocket', 'polling'] as const
6+
7+
export function buildSocketIoClientOptions(auth: { id: string; key: string }): Partial<ManagerOptions & SocketOptions> {
8+
return {
9+
path: '/socket.io',
10+
query: { id: String(auth.id), key: String(auth.key) },
11+
transports: [...SOCKET_IO_TRANSPORTS],
12+
reconnectionAttempts: 10,
13+
}
14+
}
15+
16+
type PublicRuntimeConfig = ReturnType<typeof useRuntimeConfig>['public']
17+
18+
function readPublicRuntimeConfig(): PublicRuntimeConfig {
19+
const nuxtApp = tryUseNuxtApp()
20+
if (nuxtApp?.$config?.public) {
21+
return nuxtApp.$config.public as PublicRuntimeConfig
22+
}
23+
24+
if (import.meta.client) {
25+
const injected = (window as { __NUXT__?: { config?: { public?: PublicRuntimeConfig } } }).__NUXT__?.config?.public
26+
if (injected) {
27+
return injected
28+
}
29+
}
30+
31+
return useRuntimeConfig().public
32+
}
33+
34+
function resolveBrowserApiPort(publicConfig: PublicRuntimeConfig): number {
35+
const publicPort = Number(publicConfig.socketPublicApiPort)
36+
if (publicPort > 0) {
37+
return publicPort
38+
}
39+
40+
const internalPort = Number(publicConfig.socketApiPort)
41+
if (internalPort > 0) {
42+
return internalPort
43+
}
44+
45+
return 4000
46+
}
47+
48+
function resolveDirectApiOrigin(hostname: string, publicConfig: PublicRuntimeConfig): string {
49+
const protocol = `${publicConfig.socketApiProtocol || 'http:'}`
50+
const port = resolveBrowserApiPort(publicConfig)
51+
return `${protocol}//${hostname}:${port}`
52+
}
53+
154
/**
255
* Origine Socket.IO côté navigateur.
3-
* Par défaut : même origine que le front (Nitro/Vite proxifient `/socket.io` vers l'API).
4-
* Si `SESAME_APP_PUBLIC_API_URL` est défini : connexion directe à l'API (requis quand le
5-
* reverse-proxy route `/socket.io` vers le port 4000 ou que le proxy Nitro n'est pas joignable).
56+
*
57+
* Priorité :
58+
* 1. `SESAME_APP_PUBLIC_API_URL` si défini (prod / reverse-proxy).
59+
* 2. En dev, si le front et l'API publique sont sur des ports différents : connexion directe
60+
* (ex. front `mactacx:3002` → API `mactacx:4002` via `SESAME_APP_PUBLIC_API_PORT`).
61+
* 3. Sinon : même origine que le front (Nitro proxifie `/socket.io` en long-polling).
62+
*
63+
* Note : `SESAME_APP_API_URL` (port interne, ex. 4000 dans Docker) sert au proxy serveur Nuxt,
64+
* pas à l'origine Socket.IO du navigateur.
665
*/
766
export function resolveSocketApiOrigin(): string {
8-
const runtimeConfig = useRuntimeConfig()
9-
const configuredPublic = `${runtimeConfig.public.socketApiUrl || ''}`.trim()
67+
const publicConfig = readPublicRuntimeConfig()
68+
const configuredPublic = `${publicConfig.socketApiUrl || ''}`.trim()
1069

1170
if (configuredPublic) {
1271
return new URL(configuredPublic).origin
1372
}
1473

1574
if (import.meta.client) {
75+
const apiPort = resolveBrowserApiPort(publicConfig)
76+
const frontPort = Number(window.location.port) || (window.location.protocol === 'https:' ? 443 : 80)
77+
78+
if (import.meta.dev && frontPort !== apiPort) {
79+
return resolveDirectApiOrigin(window.location.hostname, publicConfig)
80+
}
81+
1682
return window.location.origin
1783
}
1884

apps/web/src/composables/useSocketIoDebug.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,10 @@ export function attachSocketIoDebug(socket: Socket, namespace: string): void {
147147
})
148148

149149
socket.on('connect', () => {
150-
maybeLog('lifecycle', 'connect', { socketId: socket.id })
150+
maybeLog('lifecycle', 'connect', {
151+
socketId: socket.id,
152+
transport: socket.io.engine.transport.name,
153+
})
151154
})
152155

153156
socket.on('disconnect', (reason) => {

apps/web/src/layouts/default.vue

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ import { IdentityState } from '~/constants/enums'
2424
import { useIdentityStateStore } from '~/stores/identityState'
2525
import { loadingBarDefaults } from '~/composables/useLoadingBarHijackFilter'
2626
import { attachSocketIoDebug } from '~/composables/useSocketIoDebug'
27-
import { resolveSocketApiOrigin } from '~/composables/useSocketApiOrigin'
27+
import { buildSocketIoClientOptions, resolveSocketApiOrigin } from '~/composables/useSocketApiOrigin'
2828
import { io, type Socket } from 'socket.io-client'
2929
3030
export default defineNuxtComponent({
@@ -159,12 +159,7 @@ export default defineNuxtComponent({
159159
160160
this.disconnectBackendsSocket()
161161
162-
this.socket = io(`${resolveSocketApiOrigin()}/core/backends`, {
163-
path: '/socket.io',
164-
query: { id: String(id), key: String(key) },
165-
transports: ['polling'],
166-
reconnectionAttempts: 10,
167-
})
162+
this.socket = io(`${resolveSocketApiOrigin()}/core/backends`, buildSocketIoClientOptions({ id, key }))
168163
attachSocketIoDebug(this.socket, '/core/backends')
169164
this.socket.on('connect', () => {
170165
Object.assign(this.daemonStatus, { checking: true })

apps/web/src/pages/settings/cron.vue

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -220,7 +220,7 @@
220220
import type { LocationQueryValue } from 'vue-router'
221221
import { reactive, ref } from 'vue'
222222
import { attachSocketIoDebug } from '~/composables/useSocketIoDebug'
223-
import { resolveSocketApiOrigin } from '~/composables/useSocketApiOrigin'
223+
import { buildSocketIoClientOptions, resolveSocketApiOrigin } from '~/composables/useSocketApiOrigin'
224224
import { io, type Socket } from 'socket.io-client'
225225
import { NewTargetId } from '~/constants/variables'
226226
@@ -597,12 +597,7 @@ export default defineNuxtComponent({
597597
this.logsFollowTail = true
598598
this.logsLoading = true
599599
600-
this.logsSocket = io(`${resolveSocketApiOrigin()}/core/cron`, {
601-
path: '/socket.io',
602-
query: { id: String(id), key: String(key) },
603-
transports: ['polling'],
604-
reconnectionAttempts: 10,
605-
})
600+
this.logsSocket = io(`${resolveSocketApiOrigin()}/core/cron`, buildSocketIoClientOptions({ id, key }))
606601
attachSocketIoDebug(this.logsSocket, '/core/cron')
607602
608603
this.logsSocket.on('connect', () => {

apps/web/src/server/middleware/socket-io-proxy.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,5 +12,10 @@ export default defineEventHandler(async (event) => {
1212
return
1313
}
1414

15+
// proxyRequest (h3) ne gère pas l'upgrade WebSocket — laisser devProxy / reverse-proxy s'en charger.
16+
if (event.node.req.headers.upgrade?.toLowerCase() === 'websocket') {
17+
return
18+
}
19+
1520
return proxyRequest(event, `${resolveApiBaseUrl()}${url}`)
1621
})

0 commit comments

Comments
 (0)