diff --git a/vuu-ui/packages/vuu-data/src/connectionTypes.ts b/vuu-ui/packages/vuu-data/src/connectionTypes.ts index 66b4cb9b1..27acd3bca 100644 --- a/vuu-ui/packages/vuu-data/src/connectionTypes.ts +++ b/vuu-ui/packages/vuu-data/src/connectionTypes.ts @@ -1,5 +1,10 @@ export interface Connection { requiresLogin?: boolean; send: (message: T) => void; - status: 'closed' | 'ready' | 'connected' | 'reconnected'; + status: + | "closed" + | "ready" + | "connection-open-awaiting-session" + | "connected" + | "reconnected"; } diff --git a/vuu-ui/packages/vuu-data/src/vuuUIMessageTypes.ts b/vuu-ui/packages/vuu-data/src/vuuUIMessageTypes.ts index 5383dc2b8..260e8601d 100644 --- a/vuu-ui/packages/vuu-data/src/vuuUIMessageTypes.ts +++ b/vuu-ui/packages/vuu-data/src/vuuUIMessageTypes.ts @@ -18,6 +18,7 @@ import { Selection } from "@finos/vuu-datagrid-types"; export type ConnectionStatus = | "connecting" + | "connection-open-awaiting-session" | "connected" | "disconnected" | "reconnected"; @@ -42,7 +43,7 @@ export interface ConnectionQualityMetrics { export const isConnectionQualityMetrics = ( msg: object ): msg is ConnectionQualityMetrics => - (msg as ConnectionQualityMetrics).type === "connection-metrics" + (msg as ConnectionQualityMetrics).type === "connection-metrics"; export interface ServerProxySubscribeMessage { aggregations: VuuAggregation[]; diff --git a/vuu-ui/packages/vuu-data/src/websocket-connection.ts b/vuu-ui/packages/vuu-data/src/websocket-connection.ts index 8dbc683da..a49181a4f 100644 --- a/vuu-ui/packages/vuu-data/src/websocket-connection.ts +++ b/vuu-ui/packages/vuu-data/src/websocket-connection.ts @@ -5,10 +5,16 @@ import { import { Connection } from "./connectionTypes"; import { logger } from "@finos/vuu-utils"; -import { ConnectionQualityMetrics, ConnectionStatus, ConnectionStatusMessage } from "./vuuUIMessageTypes"; +import { + ConnectionQualityMetrics, + ConnectionStatus, + ConnectionStatusMessage, +} from "./vuuUIMessageTypes"; export type ConnectionMessage = - ServerToClientMessage | ConnectionStatusMessage | ConnectionQualityMetrics; + | ServerToClientMessage + | ConnectionStatusMessage + | ConnectionQualityMetrics; export type ConnectionCallback = (msg: ConnectionMessage) => void; const { debug, debugEnabled, error, info, warn } = logger( @@ -68,7 +74,9 @@ async function makeConnection( const websocketConnection = connection ?? new WebsocketConnection(ws, url, callback); - const status = reconnecting ? "reconnected" : "connected"; + const status = reconnecting + ? "reconnected" + : "connection-open-awaiting-session"; callback({ type: "connection-status", status }); websocketConnection.status = status; @@ -134,11 +142,19 @@ export class WebsocketConnection implements Connection { close: () => void = closeWarn; requiresLogin = true; send: (msg: ClientToServerMessage) => void = sendWarn; - status: "closed" | "ready" | "connected" | "reconnected" = "ready"; + status: + | "closed" + | "ready" + | "connection-open-awaiting-session" + | "connected" + | "reconnected" = "ready"; public url: string; public messagesCount = 0; + private connectionMetricsInterval: ReturnType | null = + null; + constructor(ws: WebSocket, url: string, callback: ConnectionCallback) { this.url = url; this[connectionCallback] = callback; @@ -152,20 +168,18 @@ export class WebsocketConnection implements Connection { [setWebsocket](ws: WebSocket) { const callback = this[connectionCallback]; ws.onmessage = (evt) => { - const vuuMessageFromServer = parseMessage(evt.data); - this.messagesCount += 1; - if (process.env.NODE_ENV === "development") { - if (debugEnabled && vuuMessageFromServer.body.type !== "HB") { - debug?.(`<<< ${vuuMessageFromServer.body.type}`); - } - } - callback(vuuMessageFromServer); + this.status = "connected"; + ws.onmessage = this.handleWebsocketMessage; + this.handleWebsocketMessage(evt); }; - setInterval(() => { - callback({ type: "connection-metrics", messagesLength: this.messagesCount }); + this.connectionMetricsInterval = setInterval(() => { + callback({ + type: "connection-metrics", + messagesLength: this.messagesCount, + }); this.messagesCount = 0; - }, 1000) + }, 1000); ws.onerror = () => { error(`⚡ connection error`); @@ -174,7 +188,19 @@ export class WebsocketConnection implements Connection { status: "disconnected", reason: "error", }); - if (this.status !== "closed") { + + if (this.connectionMetricsInterval) { + clearInterval(this.connectionMetricsInterval); + this.connectionMetricsInterval = null; + } + + if (this.status === "connection-open-awaiting-session") { + // our connection has errored before first server message has been received. This + // is not a normal reconnect, more likely a websocket configuration issue + error( + `Websocket connection lost before Vuu session established, check websocket configuration` + ); + } else if (this.status !== "closed") { reconnect(this); this.send = queue; } @@ -187,6 +213,12 @@ export class WebsocketConnection implements Connection { status: "disconnected", reason: "close", }); + + if (this.connectionMetricsInterval) { + clearInterval(this.connectionMetricsInterval); + this.connectionMetricsInterval = null; + } + if (this.status !== "closed") { reconnect(this); this.send = queue; @@ -216,4 +248,15 @@ export class WebsocketConnection implements Connection { info?.("close websocket"); }; } + + handleWebsocketMessage = (evt) => { + const vuuMessageFromServer = parseMessage(evt.data); + this.messagesCount += 1; + if (process.env.NODE_ENV === "development") { + if (debugEnabled && vuuMessageFromServer.body.type !== "HB") { + debug?.(`<<< ${vuuMessageFromServer.body.type}`); + } + } + this[connectionCallback](vuuMessageFromServer); + }; }