feat: configurable appKey, URL validation, connection diagnostics
- types: add appKey to WsClientConfig - nuxt: read PUSHER_APP_KEY from runtimeConfig, validate wsUrl/appKey - ws: URL validation, debug logging, 5s connection_established timeout
This commit is contained in:
parent
e80b3d09b1
commit
46ce034aba
35
src/nuxt.ts
35
src/nuxt.ts
|
|
@ -18,6 +18,15 @@ export interface NuxtNetworkingOptions {
|
||||||
wsUrlKey?: string
|
wsUrlKey?: string
|
||||||
/** runtimeConfig key for WS protocol — `'wss'` or `'ws'` (default: `'WS_PROTOCOL'`) */
|
/** runtimeConfig key for WS protocol — `'wss'` or `'ws'` (default: `'WS_PROTOCOL'`) */
|
||||||
wsProtocolKey?: string
|
wsProtocolKey?: string
|
||||||
|
/** runtimeConfig key for the WebSocket app key (default: `'PUSHER_APP_KEY'`) */
|
||||||
|
appKeyConfigKey?: string
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pass in `useRuntimeConfig()` from the calling plugin/composable.
|
||||||
|
* If omitted the function falls back to the Nuxt auto-import (works only
|
||||||
|
* when Nuxt treats this file as part of the app's auto-import context).
|
||||||
|
*/
|
||||||
|
runtimeConfig?: Record<string, any>
|
||||||
|
|
||||||
/** Additional ApiClientConfig overrides */
|
/** Additional ApiClientConfig overrides */
|
||||||
apiConfig?: Partial<ApiClientConfig>
|
apiConfig?: Partial<ApiClientConfig>
|
||||||
|
|
@ -45,20 +54,24 @@ export function createFromNuxtConfig(options: NuxtNetworkingOptions = {}): {
|
||||||
api: ApiClient
|
api: ApiClient
|
||||||
ws: WsClient
|
ws: WsClient
|
||||||
} {
|
} {
|
||||||
// Nuxt auto-imports — these are available at plugin/composable scope
|
// Use provided runtimeConfig or fall back to Nuxt auto-import
|
||||||
|
const config = options.runtimeConfig
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
// @ts-expect-error Nuxt auto-import
|
// @ts-expect-error Nuxt auto-import
|
||||||
const config = useRuntimeConfig()
|
?? (typeof useRuntimeConfig === 'function' ? useRuntimeConfig() : {})
|
||||||
|
|
||||||
const serverUrlKey = options.serverUrlKey ?? 'SERVER_URL'
|
const serverUrlKey = options.serverUrlKey ?? 'SERVER_URL'
|
||||||
const serverUrlInternalKey = options.serverUrlInternalKey ?? 'SERVER_URL_INTERNAL'
|
const serverUrlInternalKey = options.serverUrlInternalKey ?? 'SERVER_URL_INTERNAL'
|
||||||
const wsUrlKey = options.wsUrlKey ?? 'WEBS_URL'
|
const wsUrlKey = options.wsUrlKey ?? 'WEBS_URL'
|
||||||
const wsProtocolKey = options.wsProtocolKey ?? 'WS_PROTOCOL'
|
const wsProtocolKey = options.wsProtocolKey ?? 'WS_PROTOCOL'
|
||||||
|
const appKeyConfigKey = options.appKeyConfigKey ?? 'PUSHER_APP_KEY'
|
||||||
|
|
||||||
const pub = config.public ?? config
|
const pub = config.public ?? config
|
||||||
const serverUrl: string = pub[serverUrlKey] ?? ''
|
const serverUrl: string = pub[serverUrlKey] ?? ''
|
||||||
const serverUrlInternal: string = pub[serverUrlInternalKey] ?? ''
|
const serverUrlInternal: string = pub[serverUrlInternalKey] ?? ''
|
||||||
const wsUrl: string = pub[wsUrlKey] ?? ''
|
const wsUrl: string = pub[wsUrlKey] ?? ''
|
||||||
const wsProtocol: string = pub[wsProtocolKey] ?? 'wss'
|
const wsProtocol: string = pub[wsProtocolKey] ?? 'wss'
|
||||||
|
const appKey: string = pub[appKeyConfigKey] ?? ''
|
||||||
|
|
||||||
// @ts-expect-error Nuxt/Vite global
|
// @ts-expect-error Nuxt/Vite global
|
||||||
const isServer: boolean = import.meta.server ?? false
|
const isServer: boolean = import.meta.server ?? false
|
||||||
|
|
@ -84,9 +97,25 @@ export function createFromNuxtConfig(options: NuxtNetworkingOptions = {}): {
|
||||||
// Nuxt always has Vue — use ref directly for reactive state
|
// Nuxt always has Vue — use ref directly for reactive state
|
||||||
const vueRef = <T>(initial: T): ReactiveRef<T> => ref(initial) as ReactiveRef<T>
|
const vueRef = <T>(initial: T): ReactiveRef<T> => ref(initial) as ReactiveRef<T>
|
||||||
|
|
||||||
|
// Validate WebSocket configuration
|
||||||
|
if (!wsUrl) {
|
||||||
|
console.error(
|
||||||
|
`[blax-networking] Missing WebSocket URL. Set runtimeConfig.public.${wsUrlKey} ` +
|
||||||
|
`or the NUXT_PUBLIC_${wsUrlKey} environment variable.`,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if (!appKey) {
|
||||||
|
console.error(
|
||||||
|
`[blax-networking] Missing WebSocket app key. Set runtimeConfig.public.${appKeyConfigKey} ` +
|
||||||
|
`or the NUXT_PUBLIC_${appKeyConfigKey} environment variable. ` +
|
||||||
|
`This must match PUSHER_APP_KEY on the backend.`,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
const ws = createWsClient(
|
const ws = createWsClient(
|
||||||
{
|
{
|
||||||
url: `${wsProtocol === 'wss' ? 'wss' : 'ws'}://${wsUrl}/app/ws`,
|
url: `${wsProtocol === 'wss' ? 'wss' : 'ws'}://${wsUrl}/app/${appKey}`,
|
||||||
|
appKey,
|
||||||
isServer: () => isServer,
|
isServer: () => isServer,
|
||||||
...options.wsConfig,
|
...options.wsConfig,
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -131,9 +131,12 @@ export interface ApiClientConfig {
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
export interface WsClientConfig {
|
export interface WsClientConfig {
|
||||||
/** Full WebSocket URL (e.g. `'wss://example.com/app/ws'`). String or getter. */
|
/** Full WebSocket URL (e.g. `'wss://example.com/app/mykey'`). String or getter. */
|
||||||
url: string | (() => string)
|
url: string | (() => string)
|
||||||
|
|
||||||
|
/** Application key used in the WebSocket path (e.g. `/app/{appKey}`). */
|
||||||
|
appKey?: string
|
||||||
|
|
||||||
/** Called on each channel establishment to get the current auth token. */
|
/** Called on each channel establishment to get the current auth token. */
|
||||||
getAuthToken?: () => string | null | undefined
|
getAuthToken?: () => string | null | undefined
|
||||||
|
|
||||||
|
|
|
||||||
33
src/ws.ts
33
src/ws.ts
|
|
@ -327,6 +327,7 @@ class WsClientImpl extends EventTarget implements WsClient {
|
||||||
if (force_reset) this.channels = []
|
if (force_reset) this.channels = []
|
||||||
|
|
||||||
const url = this._getUrl()
|
const url = this._getUrl()
|
||||||
|
console.debug('[blax-networking] Connecting to', url)
|
||||||
this.socket = new WebSocket(url) as WsSocket
|
this.socket = new WebSocket(url) as WsSocket
|
||||||
this.is_connecting_socket.value = true
|
this.is_connecting_socket.value = true
|
||||||
this._config.onConnectionStateChange?.('connecting')
|
this._config.onConnectionStateChange?.('connecting')
|
||||||
|
|
@ -377,6 +378,21 @@ class WsClientImpl extends EventTarget implements WsClient {
|
||||||
this._config.onConnectionStateChange?.('connected')
|
this._config.onConnectionStateChange?.('connected')
|
||||||
// Warmup ping
|
// Warmup ping
|
||||||
socket.send('{"event":"websocket.ping","data":{}}')
|
socket.send('{"event":"websocket.ping","data":{}}')
|
||||||
|
|
||||||
|
// Detect missing connection_established response (usually app key mismatch)
|
||||||
|
setTimeout(() => {
|
||||||
|
if (this.socket === socket && !socket.socket_id) {
|
||||||
|
const wsUrl = this._getUrl()
|
||||||
|
console.error(
|
||||||
|
'[blax-networking] WebSocket opened but server did not send ' +
|
||||||
|
'"websocket.connection_established" within 5s.\n' +
|
||||||
|
'Connected to: ' + wsUrl + '\n' +
|
||||||
|
'This usually means the app key in the URL does not match any app ' +
|
||||||
|
'configured on the backend (PUSHER_APP_KEY). Check that the path ' +
|
||||||
|
'segment after /app/ matches the server\'s PUSHER_APP_KEY.',
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}, 5000)
|
||||||
})
|
})
|
||||||
|
|
||||||
socket.addEventListener('message', (raw) => {
|
socket.addEventListener('message', (raw) => {
|
||||||
|
|
@ -625,5 +641,22 @@ export function createWsClient(
|
||||||
? config.isServer()
|
? config.isServer()
|
||||||
: (config.isServer ?? false)
|
: (config.isServer ?? false)
|
||||||
if (isServer) return createSsrStub(createRef)
|
if (isServer) return createSsrStub(createRef)
|
||||||
|
|
||||||
|
// Validate configuration and warn developers about common issues
|
||||||
|
const url = typeof config.url === 'function' ? config.url() : config.url
|
||||||
|
if (!url || url === 'wss:///app/' || url === 'ws:///app/') {
|
||||||
|
console.error(
|
||||||
|
'[blax-networking] WebSocket URL is empty or malformed: ' + JSON.stringify(url) + '\n' +
|
||||||
|
'Ensure WEBS_URL and PUSHER_APP_KEY are configured. ' +
|
||||||
|
'Expected format: wss://your-ws-host/app/{appKey}',
|
||||||
|
)
|
||||||
|
} else if (url.endsWith('/app/') || url.endsWith('/app')) {
|
||||||
|
console.error(
|
||||||
|
'[blax-networking] WebSocket URL is missing the app key: ' + url + '\n' +
|
||||||
|
'Ensure PUSHER_APP_KEY is set. The URL must end with /app/{appKey} ' +
|
||||||
|
'where {appKey} matches the PUSHER_APP_KEY on the backend.',
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
return new WsClientImpl(config, createRef)
|
return new WsClientImpl(config, createRef)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue