{"version":3,"sources":["src/common.browser/WebsocketMessageAdapter.ts"],"names":[],"mappings":"AAGA,OAAO,EAIH,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;AAexC,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;IAEhD,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;aAmB5B,KAAK,EAAI,eAAe;IAI5B,IAAI,wCAsJV;IAEM,IAAI,mDAuBV;IAEM,IAAI,mCAMV;IAEM,KAAK,wCAYX;aAEU,MAAM,EAAI,WAAW,CAAC,eAAe,CAAC;IAIjD,OAAO,CAAC,cAAc,CAarB;IAED,OAAO,CAAC,OAAO,CAad;IAED,OAAO,CAAC,gBAAgB,CAoBvB;IAED,OAAO,CAAC,OAAO,CAGd;CACJ","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    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 * as http from \"http\";\nimport * as HttpsProxyAgent from \"https-proxy-agent\";\nimport * as tls from \"tls\";\nimport * as ws from \"ws\";\nimport * as ocsp from \"../../external/ocsp/ocsp\";\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\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\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            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                if (this.proxyInfo !== undefined &&\n                    this.proxyInfo.HostName !== undefined &&\n                    this.proxyInfo.Port > 0) {\n                    const httpProxyOptions: HttpsProxyAgent.HttpsProxyAgentOptions = {\n                        host: this.proxyInfo.HostName,\n                        port: this.proxyInfo.Port,\n                    };\n\n                    if (undefined !== this.proxyInfo.UserName) {\n                        httpProxyOptions.headers = {\n                            \"Proxy-Authentication\": \"Basic \" + new Buffer(this.proxyInfo.UserName + \":\" + (this.proxyInfo.Password === undefined) ? \"\" : this.proxyInfo.Password).toString(\"base64\"),\n                            \"requestOCSP\": \"true\",\n                        };\n                    }\n\n                    const httpProxyAgent: HttpsProxyAgent = new HttpsProxyAgent(httpProxyOptions);\n                    const httpsOptions: http.RequestOptions = { agent: httpProxyAgent , headers: this.privHeaders };\n\n                    this.privWebsocketClient = new ws(this.privUri, httpsOptions as ws.ClientOptions);\n\n                    // Register to be notified when WebSocket upgrade happens so we can check the validity of the\n                    // Certificate.\n                    this.privWebsocketClient.addListener(\"upgrade\", (e: http.IncomingMessage): void => {\n                        const tlsSocket: tls.TLSSocket = e.socket as tls.TLSSocket;\n                        const peer: tls.DetailedPeerCertificate = tlsSocket.getPeerCertificate(true);\n\n                        // Cork the socket until we know if the cert is good.\n                        tlsSocket.cork();\n\n                        ocsp.check({\n                            cert: peer.raw,\n                            httpOptions: httpsOptions,\n                            issuer: peer.issuerCertificate.raw,\n                        }, (error: Error, res: any): void => {\n                            if (error) {\n                                this.privCertificateValidatedDeferral.reject(error.message);\n                                tlsSocket.destroy(error);\n                            } else {\n                                this.privCertificateValidatedDeferral.resolve(true);\n                                tlsSocket.uncork();\n                            }\n                        });\n                    });\n\n                } else {\n                    // The ocsp library will handle validation for us and fail the connection if needed.\n                    this.privCertificateValidatedDeferral.resolve(true);\n\n                    const ocspAgent: ocsp.Agent = new ocsp.Agent({});\n                    const options: ws.ClientOptions = { agent: ocspAgent, headers: this.privHeaders };\n                    this.privWebsocketClient = new ws(this.privUri, options);\n                }\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            // TODO: Understand what this is error is. Will we still get onClose ?\n            if (this.privConnectionState !== ConnectionState.Connecting) {\n                // TODO: Is this required ?\n                // this.onEvent(new ConnectionErrorEvent(errorMsg, connectionId));\n            }\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));\n            } else {\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            this.privWebsocketClient.send(sendItem.RawWebsocketMessage.payload);\n            return PromiseHelper.fromResult(true);\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"]}