{"version":3,"sources":["src/common.browser/WebsocketMessageAdapter.ts"],"names":[],"mappings":"AAGA,OAAO,EAKH,eAAe,EACf,iBAAiB,EAGjB,sBAAsB,EAEtB,eAAe,EAGf,WAAW,EACX,0BAA0B,EAE1B,OAAO,EAIV,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAYxC,qBAAa,uBAAuB;IAChC,OAAO,CAAC,mBAAmB,CAAkB;IAC7C,OAAO,CAAC,oBAAoB,CAA6B;IACzD,OAAO,CAAC,mBAAmB,CAAiB;IAE5C,OAAO,CAAC,oBAAoB,CAAmB;IAC/C,OAAO,CAAC,yBAAyB,CAA2B;IAC5D,OAAO,CAAC,+BAA+B,CAAmC;IAC1E,OAAO,CAAC,gCAAgC,CAAoB;IAC5D,OAAO,CAAC,sBAAsB,CAAoB;IAClD,OAAO,CAAC,oBAAoB,CAA+B;IAC3D,OAAO,CAAC,gBAAgB,CAAS;IACjC,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,SAAS,CAAY;IAC7B,OAAO,CAAC,WAAW,CAA6B;IAChD,OAAO,CAAC,qBAAqB,CAAS;IAEtC,OAAc,iBAAiB,EAAE,OAAO,CAAS;gBAG7C,GAAG,EAAE,MAAM,EACX,YAAY,EAAE,MAAM,EACpB,gBAAgB,EAAE,0BAA0B,EAC5C,SAAS,EAAE,SAAS,EACpB,OAAO,EAAE;QAAE,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAAC;KAAE;IAwBvC,IAAW,KAAK,IAAI,eAAe,CAElC;IAEM,IAAI,wCAwGV;IAEM,IAAI,mDAuBV;IAEM,IAAI,mCAMV;IAEM,KAAK,wCAYX;IAED,IAAW,MAAM,IAAI,WAAW,CAAC,eAAe,CAAC,CAEhD;IAED,OAAO,CAAC,cAAc,CAoBrB;IAED,OAAO,CAAC,OAAO,CAad;IAED,OAAO,CAAC,gBAAgB,CAoBvB;IAED,OAAO,CAAC,OAAO,CAGd;IAED,OAAO,KAAK,eAAe,GAE1B;CAEJ","file":"WebsocketMessageAdapter.d.ts","sourcesContent":["// Copyright (c) Microsoft Corporation. All rights reserved.\n// Licensed under the MIT license.\n\nimport {\n    ArgumentNullError,\n    ConnectionClosedEvent,\n    ConnectionErrorEvent,\n    ConnectionEstablishedEvent,\n    ConnectionEvent,\n    ConnectionMessage,\n    ConnectionMessageReceivedEvent,\n    ConnectionMessageSentEvent,\n    ConnectionOpenResponse,\n    ConnectionStartEvent,\n    ConnectionState,\n    Deferred,\n    Events,\n    EventSource,\n    IWebsocketMessageFormatter,\n    MessageType,\n    Promise,\n    PromiseHelper,\n    Queue,\n    RawWebsocketMessage,\n} from \"../common/Exports\";\nimport { ProxyInfo } from \"./ProxyInfo\";\n\n// Node.JS specific web socket / browser support.\nimport ws from \"ws\";\nimport { CertCheckAgent } from \"./CertChecks\";\n\ninterface ISendItem {\n    Message: ConnectionMessage;\n    RawWebsocketMessage: RawWebsocketMessage;\n    sendStatusDeferral: Deferred<boolean>;\n}\n\nexport class WebsocketMessageAdapter {\n    private privConnectionState: ConnectionState;\n    private privMessageFormatter: IWebsocketMessageFormatter;\n    private privWebsocketClient: WebSocket | ws;\n\n    private privSendMessageQueue: Queue<ISendItem>;\n    private privReceivingMessageQueue: Queue<ConnectionMessage>;\n    private privConnectionEstablishDeferral: Deferred<ConnectionOpenResponse>;\n    private privCertificateValidatedDeferral: Deferred<boolean>;\n    private privDisconnectDeferral: Deferred<boolean>;\n    private privConnectionEvents: EventSource<ConnectionEvent>;\n    private privConnectionId: string;\n    private privUri: string;\n    private proxyInfo: ProxyInfo;\n    private privHeaders: { [key: string]: string; };\n    private privLastErrorReceived: string;\n\n    public static forceNpmWebSocket: boolean = false;\n\n    public constructor(\n        uri: string,\n        connectionId: string,\n        messageFormatter: IWebsocketMessageFormatter,\n        proxyInfo: ProxyInfo,\n        headers: { [key: string]: string; }) {\n\n        if (!uri) {\n            throw new ArgumentNullError(\"uri\");\n        }\n\n        if (!messageFormatter) {\n            throw new ArgumentNullError(\"messageFormatter\");\n        }\n\n        this.proxyInfo = proxyInfo;\n        this.privConnectionEvents = new EventSource<ConnectionEvent>();\n        this.privConnectionId = connectionId;\n        this.privMessageFormatter = messageFormatter;\n        this.privConnectionState = ConnectionState.None;\n        this.privUri = uri;\n        this.privHeaders = headers;\n\n        // Add the connection ID to the headers\n        this.privHeaders[\"X-ConnectionId\"] = this.privConnectionId;\n\n        this.privLastErrorReceived = \"\";\n    }\n\n    public get state(): ConnectionState {\n        return this.privConnectionState;\n    }\n\n    public open = (): Promise<ConnectionOpenResponse> => {\n        if (this.privConnectionState === ConnectionState.Disconnected) {\n            return PromiseHelper.fromError<ConnectionOpenResponse>(`Cannot open a connection that is in ${this.privConnectionState} state`);\n        }\n\n        if (this.privConnectionEstablishDeferral) {\n            return this.privConnectionEstablishDeferral.promise();\n        }\n\n        this.privConnectionEstablishDeferral = new Deferred<ConnectionOpenResponse>();\n        this.privCertificateValidatedDeferral = new Deferred<boolean>();\n\n        this.privConnectionState = ConnectionState.Connecting;\n\n        try {\n\n            if (typeof WebSocket !== \"undefined\" && !WebsocketMessageAdapter.forceNpmWebSocket) {\n                // Browser handles cert checks.\n                this.privCertificateValidatedDeferral.resolve(true);\n\n                this.privWebsocketClient = new WebSocket(this.privUri);\n            } else {\n                const options: ws.ClientOptions = { headers: this.privHeaders };\n                // The ocsp library will handle validation for us and fail the connection if needed.\n                this.privCertificateValidatedDeferral.resolve(true);\n                const checkAgent: CertCheckAgent = new CertCheckAgent(this.proxyInfo);\n\n                options.agent = checkAgent.GetAgent();\n                this.privWebsocketClient = new ws(this.privUri, options);\n            }\n\n            this.privWebsocketClient.binaryType = \"arraybuffer\";\n            this.privReceivingMessageQueue = new Queue<ConnectionMessage>();\n            this.privDisconnectDeferral = new Deferred<boolean>();\n            this.privSendMessageQueue = new Queue<ISendItem>();\n            this.processSendQueue();\n        } catch (error) {\n            this.privConnectionEstablishDeferral.resolve(new ConnectionOpenResponse(500, error));\n            return this.privConnectionEstablishDeferral.promise();\n        }\n\n        this.onEvent(new ConnectionStartEvent(this.privConnectionId, this.privUri));\n\n        this.privWebsocketClient.onopen = (e: { target: WebSocket | ws }) => {\n            this.privCertificateValidatedDeferral.promise().on((): void => {\n                this.privConnectionState = ConnectionState.Connected;\n                this.onEvent(new ConnectionEstablishedEvent(this.privConnectionId));\n                this.privConnectionEstablishDeferral.resolve(new ConnectionOpenResponse(200, \"\"));\n            }, (error: string): void => {\n                this.privConnectionEstablishDeferral.reject(error);\n            });\n        };\n\n        this.privWebsocketClient.onerror = (e: { error: any; message: string; type: string; target: WebSocket | ws }) => {\n            this.onEvent(new ConnectionErrorEvent(this.privConnectionId, e.message, e.type));\n            this.privLastErrorReceived = e.message;\n        };\n\n        this.privWebsocketClient.onclose = (e: { wasClean: boolean; code: number; reason: string; target: WebSocket | ws }) => {\n            if (this.privConnectionState === ConnectionState.Connecting) {\n                this.privConnectionState = ConnectionState.Disconnected;\n                // this.onEvent(new ConnectionEstablishErrorEvent(this.connectionId, e.code, e.reason));\n                this.privConnectionEstablishDeferral.resolve(new ConnectionOpenResponse(e.code, e.reason + \" \" + this.privLastErrorReceived));\n            } else {\n                this.privConnectionState = ConnectionState.Disconnected;\n                this.onEvent(new ConnectionClosedEvent(this.privConnectionId, e.code, e.reason));\n            }\n\n            this.onClose(e.code, e.reason);\n        };\n\n        this.privWebsocketClient.onmessage = (e: { data: ws.Data; type: string; target: WebSocket | ws }) => {\n            const networkReceivedTime = new Date().toISOString();\n            if (this.privConnectionState === ConnectionState.Connected) {\n                const deferred = new Deferred<ConnectionMessage>();\n                // let id = ++this.idCounter;\n                this.privReceivingMessageQueue.enqueueFromPromise(deferred.promise());\n                if (e.data instanceof ArrayBuffer) {\n                    const rawMessage = new RawWebsocketMessage(MessageType.Binary, e.data);\n                    this.privMessageFormatter\n                        .toConnectionMessage(rawMessage)\n                        .on((connectionMessage: ConnectionMessage) => {\n                            this.onEvent(new ConnectionMessageReceivedEvent(this.privConnectionId, networkReceivedTime, connectionMessage));\n                            deferred.resolve(connectionMessage);\n                        }, (error: string) => {\n                            // TODO: Events for these ?\n                            deferred.reject(`Invalid binary message format. Error: ${error}`);\n                        });\n                } else {\n                    const rawMessage = new RawWebsocketMessage(MessageType.Text, e.data);\n                    this.privMessageFormatter\n                        .toConnectionMessage(rawMessage)\n                        .on((connectionMessage: ConnectionMessage) => {\n                            this.onEvent(new ConnectionMessageReceivedEvent(this.privConnectionId, networkReceivedTime, connectionMessage));\n                            deferred.resolve(connectionMessage);\n                        }, (error: string) => {\n                            // TODO: Events for these ?\n                            deferred.reject(`Invalid text message format. Error: ${error}`);\n                        });\n                }\n            }\n        };\n\n        return this.privConnectionEstablishDeferral.promise();\n    }\n\n    public send = (message: ConnectionMessage): Promise<boolean> => {\n        if (this.privConnectionState !== ConnectionState.Connected) {\n            return PromiseHelper.fromError<boolean>(`Cannot send on connection that is in ${this.privConnectionState} state`);\n        }\n\n        const messageSendStatusDeferral = new Deferred<boolean>();\n        const messageSendDeferral = new Deferred<ISendItem>();\n\n        this.privSendMessageQueue.enqueueFromPromise(messageSendDeferral.promise());\n\n        this.privMessageFormatter\n            .fromConnectionMessage(message)\n            .on((rawMessage: RawWebsocketMessage) => {\n                messageSendDeferral.resolve({\n                    Message: message,\n                    RawWebsocketMessage: rawMessage,\n                    sendStatusDeferral: messageSendStatusDeferral,\n                });\n            }, (error: string) => {\n                messageSendDeferral.reject(`Error formatting the message. ${error}`);\n            });\n\n        return messageSendStatusDeferral.promise();\n    }\n\n    public read = (): Promise<ConnectionMessage> => {\n        if (this.privConnectionState !== ConnectionState.Connected) {\n            return PromiseHelper.fromError<ConnectionMessage>(`Cannot read on connection that is in ${this.privConnectionState} state`);\n        }\n\n        return this.privReceivingMessageQueue.dequeue();\n    }\n\n    public close = (reason?: string): Promise<boolean> => {\n        if (this.privWebsocketClient) {\n            if (this.privConnectionState !== ConnectionState.Disconnected) {\n                this.privWebsocketClient.close(1000, reason ? reason : \"Normal closure by client\");\n            }\n        } else {\n            const deferral = new Deferred<boolean>();\n            deferral.resolve(true);\n            return deferral.promise();\n        }\n\n        return this.privDisconnectDeferral.promise();\n    }\n\n    public get events(): EventSource<ConnectionEvent> {\n        return this.privConnectionEvents;\n    }\n\n    private sendRawMessage = (sendItem: ISendItem): Promise<boolean> => {\n        try {\n            // indicates we are draining the queue and it came with no message;\n            if (!sendItem) {\n                return PromiseHelper.fromResult(true);\n            }\n\n            this.onEvent(new ConnectionMessageSentEvent(this.privConnectionId, new Date().toISOString(), sendItem.Message));\n\n            // add a check for the ws readystate in order to stop the red console error 'WebSocket is already in CLOSING or CLOSED state' appearing\n            if (this.isWebsocketOpen) {\n                this.privWebsocketClient.send(sendItem.RawWebsocketMessage.payload);\n            } else {\n                return PromiseHelper.fromError<boolean>(\"websocket send error: Websocket not ready\");\n            }\n            return PromiseHelper.fromResult(true);\n\n        } catch (e) {\n            return PromiseHelper.fromError<boolean>(`websocket send error: ${e}`);\n        }\n    }\n\n    private onClose = (code: number, reason: string): void => {\n        const closeReason = `Connection closed. ${code}: ${reason}`;\n        this.privConnectionState = ConnectionState.Disconnected;\n        this.privDisconnectDeferral.resolve(true);\n        this.privReceivingMessageQueue.dispose(reason);\n        this.privReceivingMessageQueue.drainAndDispose((pendingReceiveItem: ConnectionMessage) => {\n            // TODO: Events for these ?\n            // Logger.instance.onEvent(new LoggingEvent(LogType.Warning, null, `Failed to process received message. Reason: ${closeReason}, Message: ${JSON.stringify(pendingReceiveItem)}`));\n        }, closeReason);\n\n        this.privSendMessageQueue.drainAndDispose((pendingSendItem: ISendItem) => {\n            pendingSendItem.sendStatusDeferral.reject(closeReason);\n        }, closeReason);\n    }\n\n    private processSendQueue = (): void => {\n        this.privSendMessageQueue\n            .dequeue()\n            .on((sendItem: ISendItem) => {\n                // indicates we are draining the queue and it came with no message;\n                if (!sendItem) {\n                    return;\n                }\n\n                this.sendRawMessage(sendItem)\n                    .on((result: boolean) => {\n                        sendItem.sendStatusDeferral.resolve(result);\n                        this.processSendQueue();\n                    }, (sendError: string) => {\n                        sendItem.sendStatusDeferral.reject(sendError);\n                        this.processSendQueue();\n                    });\n            }, (error: string) => {\n                // do nothing\n            });\n    }\n\n    private onEvent = (event: ConnectionEvent): void => {\n        this.privConnectionEvents.onEvent(event);\n        Events.instance.onEvent(event);\n    }\n\n    private get isWebsocketOpen(): boolean {\n        return this.privWebsocketClient && this.privWebsocketClient.readyState === this.privWebsocketClient.OPEN;\n    }\n\n}\n"]}