@vuu-ui/vuu-data-remote 0.13.37 → 0.13.38

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -41,6 +41,8 @@ const _ConnectionManager = class _ConnectionManager extends vuuUtils.EventEmitte
41
41
  `[ConnectionManager] ${message.type} message received, viewport not found`
42
42
  );
43
43
  }
44
+ } else if (vuuUtils.isLoginResponse(message)) {
45
+ this.emit("session-status", message);
44
46
  } else if (WebSocketConnection.isWebSocketConnectionMessage(message)) {
45
47
  __privateSet(this, _connectionState, message);
46
48
  this.emit("connection-status", message);
@@ -1 +1 @@
1
- {"version":3,"file":"ConnectionManager.js","sources":["../../../packages/vuu-data-remote/src/ConnectionManager.ts"],"sourcesContent":["import {\n ConnectOptions,\n DataSourceCallbackMessage,\n ServerAPI,\n ServerProxySubscribeMessage,\n TableSchema,\n VuuUIMessageIn,\n} from \"@vuu-ui/vuu-data-types\";\nimport {\n VuuCreateVisualLink,\n VuuRemoveVisualLink,\n VuuRpcMenuRequest,\n VuuRpcServiceRequest,\n VuuTableList,\n VuuTableListRequest,\n VuuTableMetaRequest,\n} from \"@vuu-ui/vuu-protocol-types\";\nimport {\n DeferredPromise,\n EventEmitter,\n isConnectionQualityMetrics,\n isRequestResponse,\n isTableSchemaMessage,\n messageHasResult,\n uuid,\n} from \"@vuu-ui/vuu-utils\";\nimport {\n WebSocketConnectionEvents,\n WebSocketConnectionState,\n isWebSocketConnectionMessage,\n} from \"./WebSocketConnection\";\nimport { DedicatedWorker } from \"./DedicatedWorker\";\nimport { shouldMessageBeRoutedToDataSource } from \"./data-source\";\n\nimport { ConnectionQualityMetrics } from \"@vuu-ui/vuu-data-types\";\n\nexport type PostMessageToClientCallback = (\n msg: DataSourceCallbackMessage,\n) => void;\n\nexport type ConnectionEvents = WebSocketConnectionEvents & {\n \"connection-metrics\": (message: ConnectionQualityMetrics) => void;\n};\n\ntype RegisteredViewport = {\n postMessageToClientDataSource: PostMessageToClientCallback;\n request: ServerProxySubscribeMessage;\n status: \"subscribing\";\n};\n\nclass ConnectionManager extends EventEmitter<ConnectionEvents> {\n #connectionState: WebSocketConnectionState = {\n connectionPhase: \"connecting\",\n connectionStatus: \"closed\",\n retryAttemptsTotal: -1,\n retryAttemptsRemaining: -1,\n secondsToNextRetry: -1,\n };\n static #instance: ConnectionManager;\n #deferredServerAPI = new DeferredPromise<ServerAPI>();\n #pendingRequests = new Map();\n #viewports = new Map<string, RegisteredViewport>();\n // #worker?: Worker;\n #worker: DedicatedWorker;\n\n private constructor() {\n super();\n this.#worker = new DedicatedWorker(this.handleMessageFromWorker);\n }\n\n public static get instance(): ConnectionManager {\n if (!ConnectionManager.#instance) {\n ConnectionManager.#instance = new ConnectionManager();\n }\n return ConnectionManager.#instance;\n }\n\n /**\n * Open a connection to the VuuServer. This method opens the websocket connection\n * and logs in. It can be called from whichever client code has access to the auth\n * token (eg. the login page, or just a hardcoded login script in a sample).\n * This will unblock any DataSources which may have already tried to subscribe to data,\n * but lacked access to the auth token.\n *\n * @param serverUrl\n * @param token\n */\n async connect(options: ConnectOptions) {\n const result = await this.#worker.connect(options);\n if (result === \"connected\") {\n this.#deferredServerAPI.resolve(this.connectedServerAPI);\n }\n return result;\n }\n\n private handleMessageFromWorker = (\n message: VuuUIMessageIn | DataSourceCallbackMessage,\n ) => {\n if (shouldMessageBeRoutedToDataSource(message)) {\n const viewport = this.#viewports.get(message.clientViewportId);\n if (viewport) {\n viewport.postMessageToClientDataSource(message);\n } else {\n console.error(\n `[ConnectionManager] ${message.type} message received, viewport not found`,\n );\n }\n } else if (isWebSocketConnectionMessage(message)) {\n this.#connectionState = message;\n this.emit(\"connection-status\", message);\n } else if (isConnectionQualityMetrics(message)) {\n this.emit(\"connection-metrics\", message);\n } else if (isRequestResponse(message)) {\n const { requestId } = message;\n if (this.#pendingRequests.has(requestId)) {\n const { resolve } = this.#pendingRequests.get(requestId);\n this.#pendingRequests.delete(requestId);\n const { requestId: _, ...messageWithoutRequestId } = message;\n\n if (messageHasResult(message)) {\n resolve(message.result);\n } else if (\n message.type === \"VP_EDIT_RPC_RESPONSE\" ||\n message.type === \"VP_EDIT_RPC_REJECT\"\n ) {\n resolve(message);\n } else if (isTableSchemaMessage(message)) {\n resolve(message.tableSchema);\n } else {\n resolve(messageWithoutRequestId);\n }\n } else {\n console.warn(\n \"%cConnectionManager Unexpected message from the worker\",\n \"color:red;font-weight:bold;\",\n );\n }\n }\n };\n\n get connectionStatus() {\n return this.#connectionState.connectionStatus;\n }\n\n get serverAPI() {\n return this.#deferredServerAPI.promise;\n }\n\n private connectedServerAPI: ServerAPI = {\n subscribe: (message, callback) => {\n if (this.#viewports.get(message.viewport)) {\n throw Error(\n `ConnectionManager attempting to subscribe with an existing viewport id`,\n );\n }\n // TODO we never use this status\n this.#viewports.set(message.viewport, {\n status: \"subscribing\",\n request: message,\n postMessageToClientDataSource: callback,\n });\n this.#worker.send({ type: \"subscribe\", ...message });\n },\n\n unsubscribe: (viewport) => {\n this.#worker.send({ type: \"unsubscribe\", viewport });\n },\n\n send: (message) => {\n this.#worker.send(message);\n },\n\n destroy: (viewportId?: string) => {\n if (viewportId && this.#viewports.has(viewportId)) {\n this.#viewports.delete(viewportId);\n }\n },\n\n rpcCall: async <T = unknown>(\n message:\n | VuuRpcServiceRequest\n | VuuRpcMenuRequest\n | VuuCreateVisualLink\n | VuuRemoveVisualLink,\n ) => this.asyncRequest<T>(message),\n\n getTableList: async () =>\n this.asyncRequest<VuuTableList>({ type: \"GET_TABLE_LIST\" }),\n\n getTableSchema: async (table) =>\n this.asyncRequest<TableSchema>({\n type: \"GET_TABLE_META\",\n table,\n }),\n };\n\n private asyncRequest = <T = unknown>(\n msg:\n | VuuRpcServiceRequest\n | VuuRpcMenuRequest\n | VuuTableListRequest\n | VuuTableMetaRequest\n | VuuCreateVisualLink\n | VuuRemoveVisualLink,\n ): Promise<T> => {\n const requestId = uuid();\n this.#worker.send({\n requestId,\n ...msg,\n });\n return new Promise((resolve, reject) => {\n this.#pendingRequests.set(requestId, { resolve, reject });\n });\n };\n\n async disconnect() {\n try {\n this.#worker.send({ type: \"disconnect\" });\n this.#deferredServerAPI = new DeferredPromise<ServerAPI>();\n return \"disconnected\";\n } catch (err: unknown) {\n return \"rejected\";\n }\n }\n\n destroy() {\n this.#worker.terminate();\n }\n}\n\nexport default ConnectionManager.instance;\n"],"names":["EventEmitter","DeferredPromise","shouldMessageBeRoutedToDataSource","isWebSocketConnectionMessage","isConnectionQualityMetrics","isRequestResponse","messageHasResult","isTableSchemaMessage","uuid","DedicatedWorker"],"mappings":"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"}
1
+ {"version":3,"file":"ConnectionManager.js","sources":["../../../packages/vuu-data-remote/src/ConnectionManager.ts"],"sourcesContent":["import {\n ConnectOptions,\n DataSourceCallbackMessage,\n ServerAPI,\n ServerProxySubscribeMessage,\n TableSchema,\n VuuUIMessageIn,\n} from \"@vuu-ui/vuu-data-types\";\nimport {\n VuuCreateVisualLink,\n VuuLoginResponse,\n VuuRemoveVisualLink,\n VuuRpcMenuRequest,\n VuuRpcServiceRequest,\n VuuTableList,\n VuuTableListRequest,\n VuuTableMetaRequest,\n} from \"@vuu-ui/vuu-protocol-types\";\nimport {\n DeferredPromise,\n EventEmitter,\n isConnectionQualityMetrics,\n isLoginResponse,\n isRequestResponse,\n isTableSchemaMessage,\n messageHasResult,\n uuid,\n} from \"@vuu-ui/vuu-utils\";\nimport {\n WebSocketConnectionEvents,\n WebSocketConnectionState,\n isWebSocketConnectionMessage,\n} from \"./WebSocketConnection\";\nimport { DedicatedWorker } from \"./DedicatedWorker\";\nimport { shouldMessageBeRoutedToDataSource } from \"./data-source\";\n\nimport { ConnectionQualityMetrics } from \"@vuu-ui/vuu-data-types\";\n\nexport type PostMessageToClientCallback = (\n msg: DataSourceCallbackMessage,\n) => void;\n\nexport type ConnectionEvents = WebSocketConnectionEvents & {\n \"connection-metrics\": (message: ConnectionQualityMetrics) => void;\n \"session-status\": (loginResponse: VuuLoginResponse) => void;\n};\n\ntype RegisteredViewport = {\n postMessageToClientDataSource: PostMessageToClientCallback;\n request: ServerProxySubscribeMessage;\n status: \"subscribing\";\n};\n\nclass ConnectionManager extends EventEmitter<ConnectionEvents> {\n #connectionState: WebSocketConnectionState = {\n connectionPhase: \"connecting\",\n connectionStatus: \"closed\",\n retryAttemptsTotal: -1,\n retryAttemptsRemaining: -1,\n secondsToNextRetry: -1,\n };\n static #instance: ConnectionManager;\n #deferredServerAPI = new DeferredPromise<ServerAPI>();\n #pendingRequests = new Map();\n #viewports = new Map<string, RegisteredViewport>();\n // #worker?: Worker;\n #worker: DedicatedWorker;\n\n private constructor() {\n super();\n this.#worker = new DedicatedWorker(this.handleMessageFromWorker);\n }\n\n public static get instance(): ConnectionManager {\n if (!ConnectionManager.#instance) {\n ConnectionManager.#instance = new ConnectionManager();\n }\n return ConnectionManager.#instance;\n }\n\n /**\n * Open a connection to the VuuServer. This method opens the websocket connection\n * and logs in. It can be called from whichever client code has access to the auth\n * token (eg. the login page, or just a hardcoded login script in a sample).\n * This will unblock any DataSources which may have already tried to subscribe to data,\n * but lacked access to the auth token.\n *\n * @param serverUrl\n * @param token\n */\n async connect(options: ConnectOptions) {\n const result = await this.#worker.connect(options);\n if (result === \"connected\") {\n this.#deferredServerAPI.resolve(this.connectedServerAPI);\n }\n return result;\n }\n\n private handleMessageFromWorker = (\n message: VuuUIMessageIn | DataSourceCallbackMessage,\n ) => {\n if (shouldMessageBeRoutedToDataSource(message)) {\n const viewport = this.#viewports.get(message.clientViewportId);\n if (viewport) {\n viewport.postMessageToClientDataSource(message);\n } else {\n console.error(\n `[ConnectionManager] ${message.type} message received, viewport not found`,\n );\n }\n } else if (isLoginResponse(message)) {\n this.emit(\"session-status\", message);\n } else if (isWebSocketConnectionMessage(message)) {\n this.#connectionState = message;\n this.emit(\"connection-status\", message);\n } else if (isConnectionQualityMetrics(message)) {\n this.emit(\"connection-metrics\", message);\n } else if (isRequestResponse(message)) {\n const { requestId } = message;\n if (this.#pendingRequests.has(requestId)) {\n const { resolve } = this.#pendingRequests.get(requestId);\n this.#pendingRequests.delete(requestId);\n const { requestId: _, ...messageWithoutRequestId } = message;\n\n if (messageHasResult(message)) {\n resolve(message.result);\n } else if (\n message.type === \"VP_EDIT_RPC_RESPONSE\" ||\n message.type === \"VP_EDIT_RPC_REJECT\"\n ) {\n resolve(message);\n } else if (isTableSchemaMessage(message)) {\n resolve(message.tableSchema);\n } else {\n resolve(messageWithoutRequestId);\n }\n } else {\n console.warn(\n \"%cConnectionManager Unexpected message from the worker\",\n \"color:red;font-weight:bold;\",\n );\n }\n }\n };\n\n get connectionStatus() {\n return this.#connectionState.connectionStatus;\n }\n\n get serverAPI() {\n return this.#deferredServerAPI.promise;\n }\n\n private connectedServerAPI: ServerAPI = {\n subscribe: (message, callback) => {\n if (this.#viewports.get(message.viewport)) {\n throw Error(\n `ConnectionManager attempting to subscribe with an existing viewport id`,\n );\n }\n // TODO we never use this status\n this.#viewports.set(message.viewport, {\n status: \"subscribing\",\n request: message,\n postMessageToClientDataSource: callback,\n });\n this.#worker.send({ type: \"subscribe\", ...message });\n },\n\n unsubscribe: (viewport) => {\n this.#worker.send({ type: \"unsubscribe\", viewport });\n },\n\n send: (message) => {\n this.#worker.send(message);\n },\n\n destroy: (viewportId?: string) => {\n if (viewportId && this.#viewports.has(viewportId)) {\n this.#viewports.delete(viewportId);\n }\n },\n\n rpcCall: async <T = unknown>(\n message:\n | VuuRpcServiceRequest\n | VuuRpcMenuRequest\n | VuuCreateVisualLink\n | VuuRemoveVisualLink,\n ) => this.asyncRequest<T>(message),\n\n getTableList: async () =>\n this.asyncRequest<VuuTableList>({ type: \"GET_TABLE_LIST\" }),\n\n getTableSchema: async (table) =>\n this.asyncRequest<TableSchema>({\n type: \"GET_TABLE_META\",\n table,\n }),\n };\n\n private asyncRequest = <T = unknown>(\n msg:\n | VuuRpcServiceRequest\n | VuuRpcMenuRequest\n | VuuTableListRequest\n | VuuTableMetaRequest\n | VuuCreateVisualLink\n | VuuRemoveVisualLink,\n ): Promise<T> => {\n const requestId = uuid();\n this.#worker.send({\n requestId,\n ...msg,\n });\n return new Promise((resolve, reject) => {\n this.#pendingRequests.set(requestId, { resolve, reject });\n });\n };\n\n async disconnect() {\n try {\n this.#worker.send({ type: \"disconnect\" });\n this.#deferredServerAPI = new DeferredPromise<ServerAPI>();\n return \"disconnected\";\n } catch (err: unknown) {\n return \"rejected\";\n }\n }\n\n destroy() {\n this.#worker.terminate();\n }\n}\n\nexport default ConnectionManager.instance;\n"],"names":["EventEmitter","DeferredPromise","shouldMessageBeRoutedToDataSource","isLoginResponse","isWebSocketConnectionMessage","isConnectionQualityMetrics","isRequestResponse","messageHasResult","isTableSchemaMessage","uuid","DedicatedWorker"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"WebSocketConnection.js","sources":["../../../packages/vuu-data-remote/src/WebSocketConnection.ts"],"sourcesContent":["import { WebSocketProtocol } from \"@vuu-ui/vuu-data-types\";\nimport { VuuClientMessage, VuuServerMessage } from \"@vuu-ui/vuu-protocol-types\";\nimport { DeferredPromise, EventEmitter, logger } from \"@vuu-ui/vuu-utils\";\n\nexport type ConnectingStatus = \"connecting\" | \"reconnecting\";\nexport type ConnectedStatus = \"connected\" | \"reconnected\";\nexport type ConnectionStatus =\n | ConnectedStatus\n | \"closed\"\n | \"connection-open-awaiting-session\"\n | \"disconnected\"\n | \"failed\"\n | \"inactive\";\n\ntype InternalConnectionStatus = ConnectionStatus | ConnectingStatus;\n\ntype ReconnectAttempts = {\n retryAttemptsTotal: number;\n retryAttemptsRemaining: number;\n secondsToNextRetry: number;\n};\n\nexport interface WebSocketConnectionState<\n T extends InternalConnectionStatus = ConnectionStatus,\n> extends ReconnectAttempts {\n connectionPhase: ConnectingStatus;\n connectionStatus: T;\n}\n\nconst { info } = logger(\"WebSocketConnection\");\n\nconst isNotConnecting = (\n connectionState: WebSocketConnectionState<InternalConnectionStatus>,\n): connectionState is WebSocketConnectionState<ConnectionStatus> =>\n connectionState.connectionStatus !== \"connecting\" &&\n connectionState.connectionStatus !== \"reconnecting\";\n\nexport const isWebSocketConnectionMessage = (\n msg: object | WebSocketConnectionState,\n): msg is WebSocketConnectionState => {\n if (\"connectionStatus\" in msg) {\n return [\n \"connecting\",\n \"connected\",\n \"connection-open-awaiting-session\",\n \"reconnecting\",\n \"reconnected\",\n \"disconnected\",\n \"closed\",\n \"failed\",\n ].includes(msg.connectionStatus);\n } else {\n return false;\n }\n};\n\nexport type VuuServerMessageCallback = (msg: VuuServerMessage) => void;\n\nexport type RetryLimits = {\n connect: number;\n reconnect: number;\n};\n\nexport type WebSocketConnectionConfig = {\n url: string;\n protocols: WebSocketProtocol;\n callback: VuuServerMessageCallback;\n connectionTimeout?: number;\n retryLimits?: RetryLimits;\n};\n\nconst DEFAULT_RETRY_LIMITS: RetryLimits = {\n connect: 5,\n reconnect: 8,\n};\n\nconst DEFAULT_CONNECTION_TIMEOUT = 10000;\n\nconst ConnectingEndState: Record<ConnectingStatus, ConnectedStatus> = {\n connecting: \"connected\",\n reconnecting: \"reconnected\",\n} as const;\n\nconst parseWebSocketMessage = (message: string): VuuServerMessage => {\n try {\n return JSON.parse(message) as VuuServerMessage;\n } catch (e) {\n throw Error(`Error parsing JSON response from server ${message}`);\n }\n};\n\nexport type WebSocketConnectionCloseReason = \"failure\" | \"shutdown\";\nexport type WebSocketConnectionEvents = {\n closed: (reason: WebSocketConnectionCloseReason) => void;\n connected: () => void;\n \"connection-status\": (message: WebSocketConnectionState) => void;\n reconnected: () => void;\n};\n\nexport class WebSocketConnection extends EventEmitter<WebSocketConnectionEvents> {\n #callback;\n /**\n We are not confirmedOpen until we receive the first message from the\n server. If we get an unexpected close event before that, we consider\n the reconnect attempts as still within the connection phase, not true\n reconnection. This can happen e.g. when connecting to remote host via\n a proxy.\n */\n #confirmedOpen = false;\n #connectionState: WebSocketConnectionState<InternalConnectionStatus>;\n #connectionTimeout;\n #deferredConnection?: DeferredPromise;\n #protocols;\n #reconnectAttempts: ReconnectAttempts;\n #requiresLogin = true;\n #url;\n #ws?: WebSocket;\n\n constructor({\n callback,\n connectionTimeout = DEFAULT_CONNECTION_TIMEOUT,\n protocols,\n retryLimits = DEFAULT_RETRY_LIMITS,\n url,\n }: WebSocketConnectionConfig) {\n super();\n\n this.#callback = callback;\n this.#connectionTimeout = connectionTimeout;\n this.#url = url;\n this.#protocols = protocols;\n\n this.#reconnectAttempts = {\n retryAttemptsTotal: retryLimits.reconnect,\n retryAttemptsRemaining: retryLimits.reconnect,\n secondsToNextRetry: 1,\n };\n\n /**\n * Initial retryAttempts are for the 'connecting' phase. These will\n * be replaced with 'reconnecting' phase retry attempts only once\n * initial connection succeeds.\n */\n this.#connectionState = {\n connectionPhase: \"connecting\",\n connectionStatus: \"closed\",\n retryAttemptsTotal: retryLimits.connect,\n retryAttemptsRemaining: retryLimits.connect,\n secondsToNextRetry: 1,\n };\n }\n\n get connectionTimeout() {\n return this.#connectionTimeout;\n }\n\n get protocols() {\n return this.#protocols;\n }\n\n get requiresLogin() {\n return this.#requiresLogin;\n }\n\n get isClosed() {\n return this.status === \"closed\";\n }\n get isDisconnected() {\n return this.status === \"disconnected\";\n }\n\n get isConnecting() {\n return this.#connectionState.connectionPhase === \"connecting\";\n }\n\n get status() {\n return this.#connectionState.connectionStatus;\n }\n\n private set status(connectionStatus: InternalConnectionStatus) {\n this.#connectionState = {\n ...this.#connectionState,\n connectionStatus,\n };\n // we don't publish the connecting states. They have little meaning for clients\n // and are will generally be very short-lived.\n if (isNotConnecting(this.#connectionState)) {\n this.emit(\"connection-status\", this.#connectionState);\n }\n }\n\n get connectionState() {\n return this.#connectionState;\n }\n\n private get hasConnectionAttemptsRemaining() {\n return this.#connectionState.retryAttemptsRemaining > 0;\n }\n\n private get confirmedOpen() {\n return this.#confirmedOpen;\n }\n\n /**\n * We are 'confirmedOpen' when we see the first message transmitted\n * from the server. This ensures that even if we have one or more\n * proxies in our route to the endPoint, all connections have been\n * opened successfully.\n * First time in here (on our initial successful connection) we switch\n * from 'connect' phase to 'reconnect' phase. We may have different\n * retry configurations for these two phases.\n */\n private set confirmedOpen(confirmedOpen: boolean) {\n this.#confirmedOpen = confirmedOpen;\n\n if (confirmedOpen && this.isConnecting) {\n this.#connectionState = {\n ...this.#connectionState,\n connectionPhase: \"reconnecting\",\n ...this.#reconnectAttempts,\n };\n } else if (confirmedOpen) {\n // we have successfully reconnected after a failure.\n // Reset the retry attempts, ready for next failure\n // Note: this retry is shared with 'disconnected' status\n this.#connectionState = {\n ...this.#connectionState,\n ...this.#reconnectAttempts,\n };\n }\n }\n\n get url() {\n return this.#url;\n }\n\n async connect(clientCall = true) {\n const state = this.#connectionState;\n if (this.isConnecting && this.#deferredConnection === undefined) {\n // We block on the first connecting call, this will be the\n // initial connect call from app. Any other calls will be\n // reconnect attempts. The initial connecting call returns a promise.\n // This promise is resolved either on that initial call or on a\n // subsequent successful retry attempt within nthat same initial\n // connecting phase.\n this.#deferredConnection = new DeferredPromise();\n }\n const { connectionTimeout, protocols, url } = this;\n this.status = state.connectionPhase;\n const timer = setTimeout(() => {\n throw Error(\n `Failed to open WebSocket connection to ${url}, timed out after ${connectionTimeout}ms`,\n );\n }, connectionTimeout);\n\n const ws = (this.#ws = new WebSocket(url, protocols));\n\n ws.onopen = () => {\n const connectedStatus = ConnectingEndState[state.connectionPhase];\n this.status = connectedStatus;\n clearTimeout(timer);\n if (this.#deferredConnection) {\n this.#deferredConnection.resolve(undefined);\n this.#deferredConnection = undefined;\n }\n if (this.isConnecting) {\n this.emit(\"connected\");\n } else {\n this.emit(\"reconnected\");\n }\n };\n ws.onerror = () => {\n clearTimeout(timer);\n };\n\n ws.onclose = () => {\n if (!this.isClosed) {\n this.confirmedOpen = false;\n this.status = \"disconnected\";\n if (this.hasConnectionAttemptsRemaining) {\n this.reconnect();\n } else {\n this.close(\"failure\");\n }\n }\n };\n\n ws.onmessage = (evt) => {\n if (!this.confirmedOpen) {\n // Now that we are confirmedOpen any subsequent close events\n // will be treated as part of a reconnection phase.\n this.confirmedOpen = true;\n }\n this.receive(evt);\n };\n\n if (clientCall) {\n return this.#deferredConnection?.promise;\n }\n }\n\n private reconnect() {\n const { retryAttemptsRemaining, secondsToNextRetry } =\n this.#connectionState;\n setTimeout(() => {\n this.#connectionState = {\n ...this.#connectionState,\n retryAttemptsRemaining: retryAttemptsRemaining - 1,\n secondsToNextRetry: secondsToNextRetry * 2,\n };\n this.connect(false);\n }, secondsToNextRetry * 1000);\n }\n\n private receive = (evt: MessageEvent) => {\n const vuuMessageFromServer = parseWebSocketMessage(evt.data);\n if (vuuMessageFromServer.body.type === \"CHANGE_VP_RANGE_SUCCESS\") {\n info?.(`CHANGE_VP_RANGE_SUCCESS<#${vuuMessageFromServer.requestId}>`);\n }\n this.#callback(vuuMessageFromServer);\n };\n\n send = (msg: VuuClientMessage) => {\n if (msg.body.type === \"CHANGE_VP_RANGE\") {\n info?.(\n `CHANGE_VP_RANGE<#${msg.requestId}> ${msg.body.from}-${msg.body.to}`,\n );\n }\n this.#ws?.send(JSON.stringify(msg));\n };\n\n close(reason: WebSocketConnectionCloseReason = \"shutdown\") {\n this.status = \"closed\";\n if (reason === \"failure\") {\n if (this.#deferredConnection) {\n this.#deferredConnection.reject(Error(\"connection failed\"));\n this.#deferredConnection = undefined;\n }\n } else {\n this.#ws?.close();\n }\n this.emit(\"closed\", reason);\n this.#ws = undefined;\n }\n}\n"],"names":["logger"],"mappings":";;;;AA6BiBA,eAAA,CAAO,qBAAqB;AAQhC,MAAA,4BAAA,GAA+B,CAC1C,GACoC,KAAA;AACpC,EAAA,IAAI,sBAAsB,GAAK,EAAA;AAC7B,IAAO,OAAA;AAAA,MACL,YAAA;AAAA,MACA,WAAA;AAAA,MACA,kCAAA;AAAA,MACA,cAAA;AAAA,MACA,aAAA;AAAA,MACA,cAAA;AAAA,MACA,QAAA;AAAA,MACA;AAAA,KACF,CAAE,QAAS,CAAA,GAAA,CAAI,gBAAgB,CAAA;AAAA,GAC1B,MAAA;AACL,IAAO,OAAA,KAAA;AAAA;AAEX;;;;"}
1
+ {"version":3,"file":"WebSocketConnection.js","sources":["../../../packages/vuu-data-remote/src/WebSocketConnection.ts"],"sourcesContent":["import { WebSocketProtocol } from \"@vuu-ui/vuu-data-types\";\nimport { VuuClientMessage, VuuServerMessage } from \"@vuu-ui/vuu-protocol-types\";\nimport { DeferredPromise, EventEmitter, logger } from \"@vuu-ui/vuu-utils\";\n\nexport type ConnectingStatus = \"connecting\" | \"reconnecting\";\nexport type ConnectedStatus = \"connected\" | \"reconnected\";\nexport type ConnectionStatus =\n | ConnectedStatus\n | \"closed\"\n | \"connection-open-awaiting-session\"\n | \"disconnected\"\n | \"failed\"\n | \"inactive\";\n\ntype InternalConnectionStatus = ConnectionStatus | ConnectingStatus;\n\ntype ReconnectAttempts = {\n retryAttemptsTotal: number;\n retryAttemptsRemaining: number;\n secondsToNextRetry: number;\n};\n\nexport interface WebSocketConnectionState<\n T extends InternalConnectionStatus = ConnectionStatus,\n> extends ReconnectAttempts {\n connectionPhase: ConnectingStatus;\n connectionStatus: T;\n}\n\nconst { debug, debugEnabled, info } = logger(\"WebSocketConnection\");\n\nconst isNotConnecting = (\n connectionState: WebSocketConnectionState<InternalConnectionStatus>,\n): connectionState is WebSocketConnectionState<ConnectionStatus> =>\n connectionState.connectionStatus !== \"connecting\" &&\n connectionState.connectionStatus !== \"reconnecting\";\n\nexport const isWebSocketConnectionMessage = (\n msg: object | WebSocketConnectionState,\n): msg is WebSocketConnectionState => {\n if (\"connectionStatus\" in msg) {\n return [\n \"connecting\",\n \"connected\",\n \"connection-open-awaiting-session\",\n \"reconnecting\",\n \"reconnected\",\n \"disconnected\",\n \"closed\",\n \"failed\",\n ].includes(msg.connectionStatus);\n } else {\n return false;\n }\n};\n\nexport type VuuServerMessageCallback = (msg: VuuServerMessage) => void;\n\nexport type RetryLimits = {\n connect: number;\n reconnect: number;\n};\n\nexport type WebSocketConnectionConfig = {\n url: string;\n protocols: WebSocketProtocol;\n callback: VuuServerMessageCallback;\n connectionTimeout?: number;\n retryLimits?: RetryLimits;\n};\n\nconst DEFAULT_RETRY_LIMITS: RetryLimits = {\n connect: 5,\n reconnect: 8,\n};\n\nconst DEFAULT_CONNECTION_TIMEOUT = 10000;\n\nconst ConnectingEndState: Record<ConnectingStatus, ConnectedStatus> = {\n connecting: \"connected\",\n reconnecting: \"reconnected\",\n} as const;\n\nconst parseWebSocketMessage = (message: string): VuuServerMessage => {\n try {\n return JSON.parse(message) as VuuServerMessage;\n } catch (e) {\n throw Error(`Error parsing JSON response from server ${message}`);\n }\n};\n\nexport type WebSocketConnectionCloseReason = \"failure\" | \"shutdown\";\nexport type WebSocketConnectionEvents = {\n closed: (reason: WebSocketConnectionCloseReason) => void;\n connected: () => void;\n \"connection-status\": (message: WebSocketConnectionState) => void;\n reconnected: () => void;\n};\n\nexport class WebSocketConnection extends EventEmitter<WebSocketConnectionEvents> {\n #callback;\n /**\n We are not confirmedOpen until we receive the first message from the\n server. If we get an unexpected close event before that, we consider\n the reconnect attempts as still within the connection phase, not true\n reconnection. This can happen e.g. when connecting to remote host via\n a proxy.\n */\n #confirmedOpen = false;\n #connectionState: WebSocketConnectionState<InternalConnectionStatus>;\n #connectionTimeout;\n #deferredConnection?: DeferredPromise;\n #protocols;\n #reconnectAttempts: ReconnectAttempts;\n #requiresLogin = true;\n #url;\n #ws?: WebSocket;\n\n constructor({\n callback,\n connectionTimeout = DEFAULT_CONNECTION_TIMEOUT,\n protocols,\n retryLimits = DEFAULT_RETRY_LIMITS,\n url,\n }: WebSocketConnectionConfig) {\n super();\n\n this.#callback = callback;\n this.#connectionTimeout = connectionTimeout;\n this.#url = url;\n this.#protocols = protocols;\n\n this.#reconnectAttempts = {\n retryAttemptsTotal: retryLimits.reconnect,\n retryAttemptsRemaining: retryLimits.reconnect,\n secondsToNextRetry: 1,\n };\n\n /**\n * Initial retryAttempts are for the 'connecting' phase. These will\n * be replaced with 'reconnecting' phase retry attempts only once\n * initial connection succeeds.\n */\n this.#connectionState = {\n connectionPhase: \"connecting\",\n connectionStatus: \"closed\",\n retryAttemptsTotal: retryLimits.connect,\n retryAttemptsRemaining: retryLimits.connect,\n secondsToNextRetry: 1,\n };\n }\n\n get connectionTimeout() {\n return this.#connectionTimeout;\n }\n\n get protocols() {\n return this.#protocols;\n }\n\n get requiresLogin() {\n return this.#requiresLogin;\n }\n\n get isClosed() {\n return this.status === \"closed\";\n }\n get isDisconnected() {\n return this.status === \"disconnected\";\n }\n\n get isConnecting() {\n return this.#connectionState.connectionPhase === \"connecting\";\n }\n\n get status() {\n return this.#connectionState.connectionStatus;\n }\n\n private set status(connectionStatus: InternalConnectionStatus) {\n this.#connectionState = {\n ...this.#connectionState,\n connectionStatus,\n };\n // we don't publish the connecting states. They have little meaning for clients\n // and are will generally be very short-lived.\n if (isNotConnecting(this.#connectionState)) {\n this.emit(\"connection-status\", this.#connectionState);\n }\n }\n\n get connectionState() {\n return this.#connectionState;\n }\n\n private get hasConnectionAttemptsRemaining() {\n return this.#connectionState.retryAttemptsRemaining > 0;\n }\n\n private get confirmedOpen() {\n return this.#confirmedOpen;\n }\n\n /**\n * We are 'confirmedOpen' when we see the first message transmitted\n * from the server. This ensures that even if we have one or more\n * proxies in our route to the endPoint, all connections have been\n * opened successfully.\n * First time in here (on our initial successful connection) we switch\n * from 'connect' phase to 'reconnect' phase. We may have different\n * retry configurations for these two phases.\n */\n private set confirmedOpen(confirmedOpen: boolean) {\n this.#confirmedOpen = confirmedOpen;\n\n if (confirmedOpen && this.isConnecting) {\n this.#connectionState = {\n ...this.#connectionState,\n connectionPhase: \"reconnecting\",\n ...this.#reconnectAttempts,\n };\n } else if (confirmedOpen) {\n // we have successfully reconnected after a failure.\n // Reset the retry attempts, ready for next failure\n // Note: this retry is shared with 'disconnected' status\n this.#connectionState = {\n ...this.#connectionState,\n ...this.#reconnectAttempts,\n };\n }\n }\n\n get url() {\n return this.#url;\n }\n\n async connect(clientCall = true) {\n const state = this.#connectionState;\n if (this.isConnecting && this.#deferredConnection === undefined) {\n // We block on the first connecting call, this will be the\n // initial connect call from app. Any other calls will be\n // reconnect attempts. The initial connecting call returns a promise.\n // This promise is resolved either on that initial call or on a\n // subsequent successful retry attempt within nthat same initial\n // connecting phase.\n this.#deferredConnection = new DeferredPromise();\n }\n const { connectionTimeout, protocols, url } = this;\n this.status = state.connectionPhase;\n const timer = setTimeout(() => {\n throw Error(\n `Failed to open WebSocket connection to ${url}, timed out after ${connectionTimeout}ms`,\n );\n }, connectionTimeout);\n\n const ws = (this.#ws = new WebSocket(url, protocols));\n\n ws.onopen = () => {\n const connectedStatus = ConnectingEndState[state.connectionPhase];\n this.status = connectedStatus;\n clearTimeout(timer);\n if (this.#deferredConnection) {\n this.#deferredConnection.resolve(undefined);\n this.#deferredConnection = undefined;\n }\n if (this.isConnecting) {\n this.emit(\"connected\");\n } else {\n this.emit(\"reconnected\");\n }\n };\n ws.onerror = () => {\n clearTimeout(timer);\n };\n\n ws.onclose = () => {\n if (!this.isClosed) {\n this.confirmedOpen = false;\n this.status = \"disconnected\";\n if (this.hasConnectionAttemptsRemaining) {\n this.reconnect();\n } else {\n this.close(\"failure\");\n }\n }\n };\n\n ws.onmessage = (evt) => {\n if (!this.confirmedOpen) {\n // Now that we are confirmedOpen any subsequent close events\n // will be treated as part of a reconnection phase.\n this.confirmedOpen = true;\n }\n this.receive(evt);\n };\n\n if (clientCall) {\n return this.#deferredConnection?.promise;\n }\n }\n\n private reconnect() {\n const { retryAttemptsRemaining, secondsToNextRetry } =\n this.#connectionState;\n setTimeout(() => {\n this.#connectionState = {\n ...this.#connectionState,\n retryAttemptsRemaining: retryAttemptsRemaining - 1,\n secondsToNextRetry: secondsToNextRetry * 2,\n };\n this.connect(false);\n }, secondsToNextRetry * 1000);\n }\n\n private receive = (evt: MessageEvent) => {\n const vuuMessageFromServer = parseWebSocketMessage(evt.data);\n if (vuuMessageFromServer.body.type === \"CHANGE_VP_RANGE_SUCCESS\") {\n info?.(`CHANGE_VP_RANGE_SUCCESS<#${vuuMessageFromServer.requestId}>`);\n }\n if (debugEnabled) {\n if (vuuMessageFromServer.body.type !== \"HB\") {\n debug(`${vuuMessageFromServer.body.type}`);\n if (vuuMessageFromServer.body.type === \"CHANGE_VP_SUCCESS\") {\n debug(JSON.stringify(vuuMessageFromServer.body));\n }\n }\n }\n this.#callback(vuuMessageFromServer);\n };\n\n send = (msg: VuuClientMessage) => {\n if (msg.body.type === \"CHANGE_VP_RANGE\") {\n info?.(\n `CHANGE_VP_RANGE<#${msg.requestId}> ${msg.body.from}-${msg.body.to}`,\n );\n }\n this.#ws?.send(JSON.stringify(msg));\n };\n\n close(reason: WebSocketConnectionCloseReason = \"shutdown\") {\n this.status = \"closed\";\n if (reason === \"failure\") {\n if (this.#deferredConnection) {\n this.#deferredConnection.reject(Error(\"connection failed\"));\n this.#deferredConnection = undefined;\n }\n } else {\n this.#ws?.close();\n }\n this.emit(\"closed\", reason);\n this.#ws = undefined;\n }\n}\n"],"names":["logger"],"mappings":";;;;AA6BsCA,gBAAO,qBAAqB;AAQrD,MAAA,4BAAA,GAA+B,CAC1C,GACoC,KAAA;AACpC,EAAA,IAAI,sBAAsB,GAAK,EAAA;AAC7B,IAAO,OAAA;AAAA,MACL,YAAA;AAAA,MACA,WAAA;AAAA,MACA,kCAAA;AAAA,MACA,cAAA;AAAA,MACA,aAAA;AAAA,MACA,cAAA;AAAA,MACA,QAAA;AAAA,MACA;AAAA,KACF,CAAE,QAAS,CAAA,GAAA,CAAI,gBAAgB,CAAA;AAAA,GAC1B,MAAA;AACL,IAAO,OAAA,KAAA;AAAA;AAEX;;;;"}
@@ -296,13 +296,13 @@ var NO_OP = () => void 0;
296
296
  var DEFAULT_DEBUG_LEVEL = false ? "error" : "info";
297
297
  var { loggingLevel = DEFAULT_DEBUG_LEVEL } = getLoggingSettings();
298
298
  var logger = (category) => {
299
- const debugEnabled4 = loggingLevel === "debug";
300
- const infoEnabled4 = debugEnabled4 || loggingLevel === "info";
299
+ const debugEnabled5 = loggingLevel === "debug";
300
+ const infoEnabled4 = debugEnabled5 || loggingLevel === "info";
301
301
  const warnEnabled = infoEnabled4 || loggingLevel === "warn";
302
302
  const errorEnabled = warnEnabled || loggingLevel === "error";
303
303
  const info5 = infoEnabled4 ? (message) => console.info(\`\${Date.now()} [\${category}] \${message}\`) : NO_OP;
304
304
  const warn3 = warnEnabled ? (message) => console.warn(\`[\${category}] \${message}\`) : NO_OP;
305
- const debug4 = debugEnabled4 ? (message) => console.debug(\`\${Date.now()} [\${category}] \${message}\`) : NO_OP;
305
+ const debug5 = debugEnabled5 ? (message) => console.debug(\`\${Date.now()} [\${category}] \${message}\`) : NO_OP;
306
306
  const error3 = errorEnabled ? (message) => console.error(\`[\${category}] \${message}\`) : NO_OP;
307
307
  if (false) {
308
308
  return {
@@ -311,13 +311,13 @@ var logger = (category) => {
311
311
  };
312
312
  } else {
313
313
  return {
314
- debugEnabled: debugEnabled4,
314
+ debugEnabled: debugEnabled5,
315
315
  infoEnabled: infoEnabled4,
316
316
  warnEnabled,
317
317
  errorEnabled,
318
318
  info: info5,
319
319
  warn: warn3,
320
- debug: debug4,
320
+ debug: debug5,
321
321
  error: error3
322
322
  };
323
323
  }
@@ -1481,7 +1481,7 @@ var Viewport = class {
1481
1481
  if (row) {
1482
1482
  out.push(toClient(row, keys, selectedRows));
1483
1483
  } else {
1484
- throw Error("[Viewport] missing row not in data cache");
1484
+ console.warn("[Viewport] missing row not in data cache");
1485
1485
  }
1486
1486
  }
1487
1487
  for (const row of this.pendingUpdates) {
@@ -1626,6 +1626,7 @@ var ServerProxy = class {
1626
1626
  this.viewports.set(msg.viewPortId, viewport);
1627
1627
  viewport.status = "subscribed";
1628
1628
  viewport.serverViewportId = msg.viewPortId;
1629
+ } else {
1629
1630
  }
1630
1631
  });
1631
1632
  });
@@ -2151,11 +2152,14 @@ var ServerProxy = class {
2151
2152
  this.sessionId = sessionId;
2152
2153
  (_a = this.pendingLogin) == null ? void 0 : _a.resolve(sessionId);
2153
2154
  this.pendingLogin = void 0;
2155
+ this.postMessageToClient(body);
2154
2156
  } else {
2155
2157
  throw Error("LOGIN_SUCCESS did not provide sessionId");
2156
2158
  }
2157
2159
  break;
2158
- // TODO login rejected
2160
+ case "LOGIN_FAIL":
2161
+ this.postMessageToClient(body);
2162
+ break;
2159
2163
  case "REMOVE_VP_SUCCESS":
2160
2164
  {
2161
2165
  const viewport = viewports.get(body.viewPortId);
@@ -2484,7 +2488,7 @@ var ServerProxy = class {
2484
2488
  };
2485
2489
 
2486
2490
  // src/WebSocketConnection.ts
2487
- var { info: info3 } = logger("WebSocketConnection");
2491
+ var { debug: debug4, debugEnabled: debugEnabled4, info: info3 } = logger("WebSocketConnection");
2488
2492
  var isNotConnecting = (connectionState) => connectionState.connectionStatus !== "connecting" && connectionState.connectionStatus !== "reconnecting";
2489
2493
  var isWebSocketConnectionMessage = (msg) => {
2490
2494
  if ("connectionStatus" in msg) {
@@ -2550,6 +2554,14 @@ var WebSocketConnection = class extends EventEmitter {
2550
2554
  if (vuuMessageFromServer.body.type === "CHANGE_VP_RANGE_SUCCESS") {
2551
2555
  info3 == null ? void 0 : info3(\`CHANGE_VP_RANGE_SUCCESS<#\${vuuMessageFromServer.requestId}>\`);
2552
2556
  }
2557
+ if (debugEnabled4) {
2558
+ if (vuuMessageFromServer.body.type !== "HB") {
2559
+ debug4(\`\${vuuMessageFromServer.body.type}\`);
2560
+ if (vuuMessageFromServer.body.type === "CHANGE_VP_SUCCESS") {
2561
+ debug4(JSON.stringify(vuuMessageFromServer.body));
2562
+ }
2563
+ }
2564
+ }
2553
2565
  __privateGet(this, _callback).call(this, vuuMessageFromServer);
2554
2566
  });
2555
2567
  __publicField(this, "send", (msg) => {