{"version":3,"sources":["../../../packages/core/data/batch-connection.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,MAAM,CAAC;AAClC,OAAO,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AAOzC,OAAO,EAAE,oBAAoB,EAAE,MAAM,mCAAmC,CAAC;AACzE,OAAO,EAAE,iBAAiB,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAIzE,OAAO,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AAkBvD,MAAM,WAAW,iBAAiB;IAE9B;;OAEG;IACH,QAAQ,EAAE,MAAM,CAAC;IAEjB;;OAEG;IACH,cAAc,EAAE,MAAM,CAAC;IAEvB;;OAEG;IACH,QAAQ,EAAE,YAAY,CAAC,GAAG,CAAC,CAAC;CAC/B;AAED;;GAEG;AACH,MAAM,WAAW,YAAa,SAAQ,cAAc,EAAE,kBAAkB;CACvE;AAED;;GAEG;AACH,qBAAa,eAAe;IAUZ,OAAO,CAAC,OAAO;IAAqB,OAAO,CAAC,oBAAoB;IAR5E,OAAO,CAAC,UAAU,CAAQ;IAE1B;;;;;OAKG;gBACiB,OAAO,EAAE,iBAAiB,EAAU,oBAAoB,EAAE,oBAAoB;IAElG;;;;;;;;;;;;;;;;;;;OAmBG;IACI,KAAK,CAAC,SAAS,EAAE,MAAM,EAAE,EAAE,eAAe,EAAE,MAAM,EAAE,EAAE,eAAe,CAAC,EAAE,MAAM,EAAE,EAAE,WAAW,CAAC,EAAE,MAAM,EAAE,EAAE,OAAO,CAAC,EAAE,YAAY,GAAG,UAAU,CAAC,iBAAiB,EAAE,CAAC;IAyBzK;;;;;;;;;;;OAWG;IACI,IAAI,CAAC,SAAS,EAAE,MAAM,EAAE,EAAE,eAAe,EAAE,MAAM,EAAE,EAAE,eAAe,EAAE,MAAM,EAAE,EAAE,OAAO,CAAC,EAAE,YAAY,GAAG,UAAU,CAAC,iBAAiB,EAAE,CAAC;IAyB/I;;;;;;OAMG;IACI,GAAG,CAAC,SAAS,EAAE,MAAM,EAAE,EAAE,eAAe,EAAE,MAAM,EAAE,EAAE,OAAO,CAAC,EAAE,YAAY,GAAG,UAAU,CAAC,iBAAiB,EAAE,CAAC;IAyBnH;;;;;;;;;;;;;;OAcG;IACI,GAAG,CAAC,SAAS,EAAE,MAAM,EAAE,EAAE,eAAe,EAAE,MAAM,EAAE,EAAE,eAAe,CAAC,EAAE,MAAM,EAAE,EAAE,OAAO,CAAC,EAAE,YAAY,GAAG,UAAU,CAAC,iBAAiB,EAAE,CAAC;IAyB/I;;;;;;OAMG;IACI,MAAM,CAAC,SAAS,EAAE,MAAM,EAAE,EAAE,eAAe,EAAE,MAAM,EAAE,EAAE,OAAO,CAAC,EAAE,YAAY,GAAG,UAAU,CAAC,iBAAiB,EAAE,CAAC;IAyBtH;;;;;;OAMG;IACH,OAAO,CAAC,kBAAkB;IAa1B;;;;OAIG;IACH,OAAO,CAAC,iBAAiB;IAWzB;;;;;;;;;;OAUG;IACH,OAAO,CAAC,UAAU;IA6BlB;;;;;;;;;;OAUG;IACH,OAAO,CAAC,sBAAsB;IA4B9B;;;;;;;;;;;;OAYG;IACH,OAAO,CAAC,sBAAsB;IAgC9B;;;;;;;;;OASG;IACH,OAAO,CAAC,gBAAgB;IAUxB;;;;;;;;;OASG;IACH,OAAO,CAAC,aAAa;IAWrB;;;;;;;;;;OAUG;IACH,OAAO,CAAC,cAAc;IAoBtB;;;;;;;;;;OAUG;IACH,OAAO,CAAC,aAAa;IAwBrB;;;;;;;OAOG;IACH,OAAO,CAAC,kBAAkB;IA2B1B;;;;;;;OAOG;IACH,OAAO,CAAC,UAAU;IAYlB;;;;;OAKG;IACH,OAAO,CAAC,iBAAiB;IAUzB;;;;;;OAMG;IACH,OAAO,CAAC,+BAA+B;IAkBvC;;;;;;OAMG;IACH,OAAO,CAAC,sBAAsB;CAwFjC","file":"batch-connection.d.ts","sourcesContent":["import { Observable } from 'rxjs';\r\nimport { AjaxResponse } from 'rxjs/ajax';\r\nimport { map } from 'rxjs/operators';\r\nimport { LogLevel } from '../diagnostics/log-level';\r\nimport { LogRecord } from '../diagnostics/log-record';\r\nimport { Logging } from '../diagnostics/logging';\r\nimport { Strings } from '../generated/strings';\r\nimport { EnvironmentModule } from '../manifest/environment-modules';\r\nimport { AuthorizationManager } from '../security/authorization-manager';\r\nimport { GatewayConnection, GatewayRequest } from './gateway-connection';\r\nimport { HttpMethod } from './http';\r\nimport { headerConstants } from './http-constants';\r\nimport { Net } from './net';\r\nimport { NodeRequestOptions } from './node-connection';\r\n\r\n/**\r\n * Batch request context.\r\n */\r\ninterface BatchRequestContext {\r\n\r\n    /**\r\n     * The node name.\r\n     */\r\n    nodeName: string;\r\n\r\n    /**\r\n     * The sequence number of request in batch.\r\n     */\r\n    sequenceNumber: number;\r\n}\r\n\r\nexport interface BatchResponseItem {\r\n\r\n    /**\r\n     * The node name.\r\n     */\r\n    nodeName: string;\r\n\r\n    /**\r\n     * The sequence number of request in batch.\r\n     */\r\n    sequenceNumber: number;\r\n\r\n    /**\r\n     * The Ajax response.\r\n     */\r\n    response: AjaxResponse<any>;\r\n}\r\n\r\n/**\r\n * Extension of GatewayRequest interface for calling the Gateway Http API\r\n */\r\nexport interface BatchRequest extends GatewayRequest, NodeRequestOptions {\r\n}\r\n\r\n/**\r\n * The Batch Connection class for creating requests and calling the Gateway's Http API\r\n */\r\nexport class BatchConnection {\r\n\r\n    private moduleName = null;\r\n\r\n    /**\r\n     * Initializes a new instance of the BatchConnection class.\r\n     *\r\n     * @param gateway the gateway Connection\r\n     * @param authorizationManager the authorization manager.\r\n     */\r\n    constructor(private gateway: GatewayConnection, private authorizationManager: AuthorizationManager) { }\r\n\r\n    /**\r\n     * Makes a batch call to the gateway api, by using provided methods for each node.\r\n     *\r\n     * Auth handling: createBody() handles the Auth for individual calls in the batch. It gets the needed tokens\r\n     *                for each node and adds to the batch body, along with rest of node call.\r\n     *                See authorizationManager.addAuthorizationTokensToMultiPartBody() for details.\r\n     * For the outer batch call, we use the auth token from the first node in list.\r\n     * @param nodesList : list of nodes we will be running the batch against.\r\n     * @param relativeUrlList : list of relative Urls of nodes after \"/api/nodes/<nodeName>/\"\r\n     * @param bodyCommandList : list of body commands, that will be present in body of a typical Post call.\r\n     *                          This is essentially a json request that we keep as a string to put it in the body.\r\n     * Ex: {\r\n     *      \"properties\": {\r\n     *          \"command\": \"##GetVirtualMachines##\\n\\nSet-StrictMode -Version 5.0\\nget-vm | Select-Object name, id, CPUUsage\"\r\n     *          }\r\n     *      }\r\n     *\r\n     * @param methodsList : list of Http methods, with each item corresponding to the node in nodeList and relativeUrl in relativeUrlList.\r\n     * @param request : optional request Properties.\r\n     */\r\n    public mixed(nodesList: string[], relativeUrlList: string[], bodyCommandList?: string[], methodsList?: string[], request?: BatchRequest): Observable<BatchResponseItem[]> {\r\n\r\n        const batchCallRelativeUrl = Net.updateApiVersion20190201(Net.batch);\r\n        const boundary = MsftSme.newGuid();\r\n        const guidsList = this.generateGuidsList(nodesList.length);\r\n        const guidsToRequestCtxMap = this.generateGuidToRequestContextMap(nodesList, guidsList);\r\n\r\n        // populate request properties.\r\n        request = this.createBatchRequest(request || <BatchRequest>{});\r\n\r\n        // populate batch request headers.\r\n        this.setRequestHeaders(request, boundary);\r\n\r\n        // Create batch command body.\r\n        const body = this.createBody(\r\n            nodesList, relativeUrlList, guidsList, methodsList, boundary, bodyCommandList, request);\r\n\r\n        return this.gateway.post(batchCallRelativeUrl, body, request)\r\n            .pipe(\r\n                map((responseData: any) => {\r\n                    const parsedResponse = this.parseMultiPartResponse(responseData, guidsToRequestCtxMap);\r\n                    return parsedResponse;\r\n                }));\r\n    }\r\n\r\n    /**\r\n     * Makes a batch POST calls to the gateway api\r\n     *\r\n     * Auth handling: createBody() handles the Auth for individual calls in the batch. It gets the needed tokens\r\n     *                for each node and adds to the batch body, along with rest of node call.\r\n     *                See authorizationManager.addAuthorizationTokensToMultiPartBody() for details.\r\n     * For the outer batch call, we use the auth token from the first node in list.\r\n     * @param nodesList : list of nodes we will be running the batch against.\r\n     * @param relativeUrlList : list of relative urls after \"/api/nodes/<nodeName>/\",  for each node.\r\n     * @param bodyCommandList : list of body commands, that will be present in body of a typical Post call.\r\n     * @param request : optional request Properties.\r\n     */\r\n    public post(nodesList: string[], relativeUrlList: string[], bodyCommandList: string[], request?: BatchRequest): Observable<BatchResponseItem[]> {\r\n\r\n        const batchCallRelativeUrl = Net.updateApiVersion20190201(Net.batch);\r\n        const boundary = MsftSme.newGuid();\r\n        const guidsList = this.generateGuidsList(nodesList.length);\r\n        const guidsToRequestCtxMap = this.generateGuidToRequestContextMap(nodesList, guidsList);\r\n\r\n        // populate request properties.\r\n        request = this.createBatchRequest(request || <BatchRequest>{});\r\n\r\n        // populate batch request headers.\r\n        this.setRequestHeaders(request, boundary);\r\n\r\n        // Create batch command body.\r\n        const body = this.createBodySingleMethod(\r\n            nodesList, relativeUrlList, guidsList, HttpMethod.Post, boundary, bodyCommandList, request);\r\n\r\n        return this.gateway.post(batchCallRelativeUrl, body, request)\r\n            .pipe(\r\n                map((responseData: any) => {\r\n                    const parsedResponse = this.parseMultiPartResponse(responseData, guidsToRequestCtxMap);\r\n                    return parsedResponse;\r\n                }));\r\n    }\r\n\r\n    /**\r\n     * Makes a batch GET call to the gateway api\r\n     *\r\n     * @param nodeList: the list of names of the node to call the API for.\r\n     * @param relativeUrlList: the list of relative Url after \"/api/nodes/<nodeName>/\"\r\n     * @param request: the batch request object.\r\n     */\r\n    public get(nodesList: string[], relativeUrlList: string[], request?: BatchRequest): Observable<BatchResponseItem[]> {\r\n\r\n        const batchCallRelativeUrl = Net.updateApiVersion20190201(Net.batch);\r\n        const boundary = MsftSme.newGuid();\r\n        const guidsList = this.generateGuidsList(nodesList.length);\r\n        const guidsToRequestCtxMap = this.generateGuidToRequestContextMap(nodesList, guidsList);\r\n\r\n        // populate request properties.\r\n        request = this.createBatchRequest(request || <BatchRequest>{});\r\n\r\n        // populate batch request headers.\r\n        this.setRequestHeaders(request, boundary);\r\n\r\n        // Create batch command body.\r\n        const body = this.createBodySingleMethod(\r\n            nodesList, relativeUrlList, guidsList, HttpMethod.Get, boundary, null, request);\r\n\r\n        return this.gateway.post(batchCallRelativeUrl, body, request)\r\n            .pipe(\r\n                map((responseData: AjaxResponse<any>[]) => {\r\n                    const parsedResponse = this.parseMultiPartResponse(responseData, guidsToRequestCtxMap);\r\n                    return parsedResponse;\r\n                }));\r\n    }\r\n\r\n    /**\r\n     * Makes a batch PUT call to the gateway api\r\n     *\r\n     * @param nodesList : list of nodes we will be running the batch against.\r\n     * @param relativeUrlList : list of relative Urls of nodes after \"/api/nodes/<nodeName>/\"\r\n     * @param bodyCommandList : list of body commands, that will be present in body of a typical Post call.\r\n     *                          This is essentially a json request that we keep as a string to put it in the body.\r\n     * Ex: {\r\n     *      \"properties\": {\r\n     *          \"command\": \"##GetVirtualMachines##\\n\\nSet-StrictMode -Version 5.0\\nget-vm | Select-Object name, id, CPUUsage\"\r\n     *          }\r\n     *      }\r\n     *\r\n     * @param request : optional request Properties.\r\n     */\r\n    public put(nodesList: string[], relativeUrlList: string[], bodyCommandList?: string[], request?: BatchRequest): Observable<BatchResponseItem[]> {\r\n\r\n        const batchCallRelativeUrl = Net.updateApiVersion20190201(Net.batch);\r\n        const boundary = MsftSme.newGuid();\r\n        const guidsList = this.generateGuidsList(nodesList.length);\r\n        const guidsToRequestCtxMap = this.generateGuidToRequestContextMap(nodesList, guidsList);\r\n\r\n        // populate request properties.\r\n        request = this.createBatchRequest(request || <BatchRequest>{});\r\n\r\n        // populate batch request headers.\r\n        this.setRequestHeaders(request, boundary);\r\n\r\n        // Create batch command body.\r\n        const body = this.createBodySingleMethod(\r\n            nodesList, relativeUrlList, guidsList, HttpMethod.Put, boundary, bodyCommandList, request);\r\n\r\n        return this.gateway.post(batchCallRelativeUrl, body, request)\r\n            .pipe(\r\n                map((responseData: any) => {\r\n                    const parsedResponse = this.parseMultiPartResponse(responseData, guidsToRequestCtxMap);\r\n                    return parsedResponse;\r\n                }));\r\n    }\r\n\r\n    /**\r\n     * Makes a batch DELETE call to the gateway api\r\n     *\r\n     * @param nodeList: the list of names of the nodes to call the API for.\r\n     * @param relativeUrlList: the list of relative Urls of nodes after \"/api/nodes/<nodeName>/\"\r\n     * @param request: the batch request object.\r\n     */\r\n    public delete(nodesList: string[], relativeUrlList: string[], request?: BatchRequest): Observable<BatchResponseItem[]> {\r\n\r\n        const batchCallRelativeUrl = Net.updateApiVersion20190201(Net.batch);\r\n        const boundary = MsftSme.newGuid();\r\n        const guidsList = this.generateGuidsList(nodesList.length);\r\n        const guidsToRequestCtxMap = this.generateGuidToRequestContextMap(nodesList, guidsList);\r\n\r\n        // populate request properties.\r\n        request = this.createBatchRequest(request || <BatchRequest>{});\r\n\r\n        // populate batch request headers.\r\n        this.setRequestHeaders(request, boundary);\r\n\r\n        // Create batch command body.\r\n        const body = this.createBodySingleMethod(\r\n            nodesList, relativeUrlList, guidsList, HttpMethod.Delete, boundary, null, request);\r\n\r\n        return this.gateway.post(batchCallRelativeUrl, body, request)\r\n            .pipe(\r\n                map((responseData: any) => {\r\n                    const parsedResponse = this.parseMultiPartResponse(responseData, guidsToRequestCtxMap);\r\n                    return parsedResponse;\r\n                }));\r\n    }\r\n\r\n    /**\r\n     * Adds default parameters to Batch Request. For the outside batch call, we just use the Auth for first node in batch request.\r\n     * No need to append any tokens for hitting Gateway, as browser handles that. For the nodes being managed,\r\n     * the tokens are already part of body.\r\n     *\r\n     * @param request The batch request object.\r\n     */\r\n    private createBatchRequest(request: BatchRequest): GatewayRequest {\r\n        if (!request.noAuth) {\r\n            // Add Node specific authorization handlers\r\n            request.retryHandlers = (request.retryHandlers || []).concat([{\r\n                canHandle: (code, error) => this.authorizationManager.canHandleAjaxFailure(code, error),\r\n                handle: (code, originalRequest, error) =>\r\n                    this.authorizationManager.handleAjaxFailure(code, originalRequest, error)\r\n            }]);\r\n        }\r\n\r\n        return request;\r\n    }\r\n\r\n    /**\r\n     * Set the request headers for the batch call.\r\n     * @param request : batch request object.\r\n     * @param boundary : boundary string used to separate multi part request.\r\n     */\r\n    private setRequestHeaders(request: BatchRequest, boundary: string): void {\r\n        // Set Batch request headers.\r\n        const batchRequest: any = request;\r\n\r\n        batchRequest.headers = batchRequest.headers || {};\r\n        batchRequest.headers[headerConstants.ACCEPT] = 'multipart/mixed';\r\n        batchRequest.headers[headerConstants.CONTENT_TYPE] = 'multipart/mixed; boundary={0}'.format(boundary);\r\n\r\n        batchRequest.responseType = 'text';\r\n    }\r\n\r\n    /**\r\n     * Creates http multi-part request body, with each individual request being different Http request type.\r\n     *\r\n     * @param nodesList The list of target nodes.\r\n     * @param relativeUrlList The relative url corresponding to each node\r\n     * @param requestIdsList The guids list to be used for batch request, corresponding to each node.\r\n     * @param methodList The Http method list, corresponding to each relative url in relativeUrlList.\r\n     * @param boundary The boundary string to be used in multipart call\r\n     * @param commandList The list of command body for each node.\r\n     * @param request : optional node request options.\r\n     */\r\n    private createBody(\r\n        nodesList: string[],\r\n        relativeUrlList: string[],\r\n        requestIdsList: string[],\r\n        methodList: string[],\r\n        boundary: string,\r\n        commandList?: string[],\r\n        request?: BatchRequest): string {\r\n\r\n        const host = this.gateway.gatewayUrl.replace('http://', '').replace('https://', '');\r\n        const body: string[] = [];\r\n\r\n        for (let index = 0; index < nodesList.length; index++) {\r\n            const nodeName = nodesList[index];\r\n            const relativeUrl = relativeUrlList[index];\r\n            const method = methodList[index];\r\n            const requestId = requestIdsList[index];\r\n\r\n            if (commandList && commandList.length === nodesList.length) {\r\n                this.createAndAddSinglePart(nodeName, relativeUrl, body, host, method, boundary, requestId, commandList[index], request);\r\n            } else {\r\n                this.createAndAddSinglePart(nodeName, relativeUrl, body, host, method, boundary, requestId, null, request);\r\n            }\r\n        }\r\n\r\n        body.push('--' + boundary + '--');\r\n        return body.join('\\r\\n');\r\n    }\r\n\r\n    /**\r\n     * Creates http multi-part request body using same Http method for all parts.\r\n     *\r\n     * @param nodesList The list of target nodes.\r\n     * @param relativeUrlList The relative url corresponding to each node\r\n     * @param requestIdsList The guids list to be used for batch request, corresponding to each node.\r\n     * @param method The Http method to be used for the call.\r\n     * @param boundary The boundary string to be used in multipart call.\r\n     * @param commandList The list of command body for each node.\r\n     * @param request : optional node request options.\r\n     */\r\n    private createBodySingleMethod(\r\n        nodesList: string[],\r\n        relativeUrlList: string[],\r\n        requestIdsList: string[],\r\n        method: string,\r\n        boundary: string,\r\n        commandList?: string[],\r\n        request?: BatchRequest): string {\r\n\r\n        const host = this.gateway.gatewayUrl.replace('http://', '').replace('https://', '');\r\n        const body: string[] = [];\r\n\r\n        for (let index = 0; index < nodesList.length; index++) {\r\n            const nodeName = nodesList[index];\r\n            const relativeUrl = relativeUrlList[index];\r\n            const requestId = requestIdsList[index];\r\n\r\n            if (commandList && commandList.length === nodesList.length) {\r\n                this.createAndAddSinglePart(nodeName, relativeUrl, body, host, method, boundary, requestId, commandList[index], request);\r\n            } else {\r\n                this.createAndAddSinglePart(nodeName, relativeUrl, body, host, method, boundary, requestId, null, request);\r\n            }\r\n        }\r\n\r\n        body.push('--' + boundary + '--');\r\n        return body.join('\\r\\n');\r\n    }\r\n\r\n    /**\r\n     * Create the part for a single request and add it to to the multi-Part body.\r\n     *\r\n     * @param nodeName : node being targeted with the request. Used for Auth headers.\r\n     * @param relativeUrl : the relative url of node for Delete request.\r\n     * @param body : the HTTP request body to populate.\r\n     * @param host : Host to run request against.\r\n     * @param method The Http method to be used for the part.\r\n     * @param boundary The boundary string to be used in multipart call.\r\n     * @param requestId The request Id to be used for the part call.\r\n     * @param commandBody : the command body to use for this part/node.\r\n     * @param request : optional node request options.\r\n     */\r\n    private createAndAddSinglePart(\r\n        nodeName: string,\r\n        relativeUrl: string,\r\n        body: string[],\r\n        host: string,\r\n        method: string,\r\n        boundary: string,\r\n        requestId: string,\r\n        commandBody?: string,\r\n        request?: BatchRequest): void {\r\n\r\n        body.push('--' + boundary);\r\n        body.push(`${headerConstants.CONTENT_TYPE}: application/http; msgtype=request`);\r\n        body.push('Content-Transfer-Encoding: binary\\r\\n');\r\n\r\n        if (method === HttpMethod.Get) {\r\n            this.addGetCommand(nodeName, relativeUrl, body, host, requestId, request);\r\n\r\n        } else if (method === HttpMethod.Put) {\r\n            this.addPutCommand(nodeName, relativeUrl, body, host, requestId, commandBody, request);\r\n\r\n        } else if (method === HttpMethod.Post) {\r\n            this.addPostCommand(nodeName, relativeUrl, body, host, requestId, commandBody, request);\r\n\r\n        } else if (method === HttpMethod.Delete) {\r\n            this.addDeleteCommand(nodeName, relativeUrl, body, host, requestId, request);\r\n\r\n        } else {\r\n            throw new Error(MsftSme.getStrings<Strings>().MsftSmeShell.Core.Error.BatchUnSupportedInvocation.message.format(method));\r\n        }\r\n    }\r\n\r\n    /**\r\n     * Create a HTTP Delete request and add it to to the multi-Part body.\r\n     *\r\n     * @param nodeName : node being targeted with the request. Used for Auth headers.\r\n     * @param relativeUrl : the relative url of node for Delete request.\r\n     * @param body : the HTTP request body to populate with Delete command.\r\n     * @param host : Host to run request against.\r\n     * @param requestId The request Id to be used for the part call.\r\n     * @param request : optional node request options.\r\n     */\r\n    private addDeleteCommand(\r\n        nodeName: string, relativeUrl: string, body: string[], host: string, requestId: string, request?: BatchRequest): void {\r\n        const multiPartItemUrl = this.getNodeUrl(relativeUrl, nodeName, HttpMethod.Delete);\r\n        body.push(multiPartItemUrl);\r\n\r\n        this.writeCommonSection(nodeName, body, host, requestId, request);\r\n\r\n        body.push('\\r\\n');\r\n    }\r\n\r\n    /**\r\n     * Create a HTTP Get request and add it to to the multi-Part body.\r\n     *\r\n     * @param nodeName : node being targeted with the request. Used for Auth headers.\r\n     * @param relativeUrl : the relative url of node for Get request.\r\n     * @param body : the HTTP request body to populate with Get command.\r\n     * @param host : Host to run request against.\r\n     * @param requestId The request Id to be used for the part call.\r\n     * @param request : optional node request options.\r\n     */\r\n    private addGetCommand(\r\n        nodeName: string, relativeUrl: string, body: string[], host: string, requestId: string, request?: BatchRequest): void {\r\n\r\n        const multiPartItemUrl = this.getNodeUrl(relativeUrl, nodeName, HttpMethod.Get);\r\n        body.push(multiPartItemUrl);\r\n\r\n        this.writeCommonSection(nodeName, body, host, requestId, request);\r\n\r\n        body.push('\\r\\n');\r\n    }\r\n\r\n    /**\r\n     * Create a HTTP Post request and add it to to the multi-Part body.\r\n     *\r\n     * @param nodeName : node being targeted with the request. Used for Auth headers.\r\n     * @param relativeUrl : the relative url of node for Post request.\r\n     * @param body : the HTTP request body to populate with Post command.\r\n     * @param host : Host to run request against.\r\n     * @param requestId The request Id to be used for the part call.\r\n     * @param data : optional data for the Post request.\r\n     * @param request : optional node request options.\r\n     */\r\n    private addPostCommand(\r\n        nodeName: string,\r\n        relativeUrl: string,\r\n        body: string[],\r\n        host: string,\r\n        requestId: string,\r\n        data: string,\r\n        request?: BatchRequest): void {\r\n\r\n        const multiPartItemUrl = this.getNodeUrl(relativeUrl, nodeName, HttpMethod.Post);\r\n        body.push(multiPartItemUrl);\r\n\r\n        this.writeCommonSection(nodeName, body, host, requestId, request);\r\n\r\n        body.push(`${headerConstants.CONTENT_TYPE}: application/json; charset=utf-8`);\r\n        body.push(`${headerConstants.ACCEPT}: application/json, text/plain, */*`);\r\n        body.push('\\r\\n');\r\n        body.push(data);\r\n    }\r\n\r\n    /**\r\n     * Create a HTTP Put request and add it to to the multi-Part body.\r\n     *\r\n     * @param nodeName : node being targeted with the request. Used for Auth headers.\r\n     * @param relativeUrl : the relative url of node for Put request.\r\n     * @param body : the HTTP request body to populate with PUT command.\r\n     * @param host : Host to run request against.\r\n     * @param requestId The request Id to be used for the part call.\r\n     * @param data : optional data for the Put request.\r\n     * @param request : optional node request options.\r\n     */\r\n    private addPutCommand(\r\n        nodeName: string,\r\n        relativeUrl: string,\r\n        body: string[],\r\n        host: string,\r\n        requestId: string,\r\n        data?: string,\r\n        request?: BatchRequest): void {\r\n\r\n        const multiPartItemUrl = this.getNodeUrl(relativeUrl, nodeName, HttpMethod.Put);\r\n        body.push(multiPartItemUrl);\r\n\r\n        this.writeCommonSection(nodeName, body, host, requestId, request);\r\n\r\n        if (!data) {\r\n            body.push('\\r\\n');\r\n        } else {\r\n            body.push(`${headerConstants.CONTENT_TYPE}: application/json; charset=utf-8`);\r\n            body.push(`${headerConstants.ACCEPT}: application/json, text/plain, */*`);\r\n            body.push('\\r\\n');\r\n            body.push(data);\r\n        }\r\n    }\r\n\r\n    /**\r\n     * Write common session to body for all HTTP request types.\r\n     * @param nodeName : node being targeted with the request.\r\n     * @param body : The body string of the multi-part request being formed.\r\n     * @param host : The host node(gateway node) for the batch call.\r\n     * @param requestId The request Id to be used for the part call.\r\n     * @param request : optional. The node request options.\r\n     */\r\n    private writeCommonSection(nodeName: string, body: string[], host: string, requestId: string, request?: BatchRequest): void {\r\n\r\n        body.push('Host: ' + host);\r\n        body.push('request-id: ' + requestId);\r\n        body.push(headerConstants.MODULE_NAME + ': ' + EnvironmentModule.getModuleName());\r\n        body.push(headerConstants.MODULE_VERSION + ': ' + EnvironmentModule.getModuleVersion());\r\n\r\n        if (request) {\r\n            if (request.logAudit === true || request.logAudit === false) {\r\n                body.push(headerConstants.LOG_AUDIT + (request.logAudit ? ': true' : ': false'));\r\n            }\r\n\r\n            if (request.logTelemetry === true || request.logTelemetry === false) {\r\n                body.push(headerConstants.LOG_TELEMETRY + (request.logTelemetry ? ': true' : ': false'));\r\n            }\r\n\r\n            const endpoint = this.authorizationManager.getJeaEndpoint(nodeName);\r\n            if (request.powerShellEndpoint) {\r\n                body.push(headerConstants.POWERSHELL_ENDPOINT + ': ' + request.powerShellEndpoint);\r\n            } else if (endpoint) {\r\n                body.push(headerConstants.POWERSHELL_ENDPOINT + ': ' + endpoint);\r\n            }\r\n        }\r\n\r\n        this.authorizationManager.addAuthorizationTokensToMultiPartBody(body, nodeName, (request && request.authToken));\r\n    }\r\n\r\n    /**\r\n     * Creates a full Node url for multiPart call\r\n     *  Ex: PUT /api/nodes/<nodeName>/<relativeUrl> HTTP/1.1\r\n     *\r\n     * @param relativeUrl the relative Url after \"/nodes\"\r\n     * @param nodeName the name of the node to make a call against\r\n     * @param actionName the Http call type: Put/Post/Delete/Get.\r\n     */\r\n    private getNodeUrl(relativeUrl: string, nodeName: string, actionName: string): string {\r\n\r\n        if (!relativeUrl.startsWith('/')) {\r\n            relativeUrl = `/${relativeUrl}`;\r\n        }\r\n\r\n        const nodesUrl = Net.updateApiVersion20190201(`nodes/${nodeName}${relativeUrl}`);\r\n        const fullRelativeUrl = Net.apiRoot.format(nodesUrl);\r\n\r\n        return Net.multiPartCallBodyUrl.format(actionName, fullRelativeUrl);\r\n    }\r\n\r\n    /**\r\n     * Creates a list of guids to be used as request-ids in batch request.\r\n     *\r\n     * @param count the count of guids to be produced\r\n     * @return the array of generated guids\r\n     */\r\n    private generateGuidsList(count: number): string[] {\r\n\r\n        const guidsList: string[] = [];\r\n        for (let index = 0; index < count; index++) {\r\n            guidsList.push(MsftSme.newGuid());\r\n        }\r\n\r\n        return guidsList;\r\n    }\r\n\r\n    /**\r\n     * Creates a map of guids to NodeNames and sequence number, to be used to parse responses.\r\n     *\r\n     * @param orderedNodesList the list of Nodes to run batch against.\r\n     * @param guidsList the list of guids to be used for requests.\r\n     * @return the map of generated guids to BatchRequestContext\r\n     */\r\n    private generateGuidToRequestContextMap(orderedNodesList: string[], guidsList: string[]): MsftSme.StringMap<BatchRequestContext> {\r\n\r\n        if (orderedNodesList.length !== guidsList.length) {\r\n            throw new Error();\r\n        }\r\n\r\n        const guidToRequestCtxMap: MsftSme.StringMap<BatchRequestContext> = {};\r\n\r\n        for (let index = 0; index < orderedNodesList.length; index++) {\r\n\r\n            const sequenceNumber = index + 1;\r\n            const nodeName = orderedNodesList[index];\r\n            guidToRequestCtxMap[guidsList[index]] = <BatchRequestContext>{ sequenceNumber, nodeName };\r\n        }\r\n\r\n        return guidToRequestCtxMap;\r\n    }\r\n\r\n    /**\r\n     * Parses http response.\r\n     * See http://stackoverflow.com/questions/21229418/how-to-process-parse-read-a-multipart-mixed-boundary-batch-response\r\n     * for sample response.\r\n     * @param responseData: multipart response as received from the Batch call.\r\n     * @param guidToRequestCtxMap: the map of request-id guids to BatchRequestContext.\r\n     */\r\n    private parseMultiPartResponse(responseData: any, guidToRequestCtxMap: MsftSme.StringMap<BatchRequestContext>): BatchResponseItem[] {\r\n\r\n        // ToDo: Check if we can update Gateway connection to get handle of response header, so we can extract the boundary from there.\r\n\r\n        // Try to get boundary string from the response.\r\n        const indexBoundaryStart = responseData.indexOf('--');\r\n        const indexBoundaryEnd = responseData.indexOf('\\r\\n');\r\n\r\n        if (indexBoundaryStart < 0 || indexBoundaryEnd < 0 || indexBoundaryStart > indexBoundaryEnd) {\r\n            Logging.log(<LogRecord>{\r\n                source: 'Batch PowerShell',\r\n                level: LogLevel.Error,\r\n                message: MsftSme.getStrings<Strings>().MsftSmeShell.Core.Error.BatchResponseParsing.message\r\n                    .format(indexBoundaryStart, indexBoundaryEnd)\r\n            });\r\n            return [];\r\n        }\r\n\r\n        const boundary = responseData.slice(indexBoundaryStart, indexBoundaryEnd);\r\n\r\n        const items = responseData.split(boundary);\r\n        const results: BatchResponseItem[] = [];\r\n\r\n        for (const item of items) {\r\n            if (item === '' || item === '--\\r\\n') {\r\n                continue;\r\n            }\r\n\r\n            const rows = item.split('\\r\\n');\r\n\r\n            let status: number;\r\n            let data: any;\r\n            let requestId: string;\r\n\r\n            for (const row of rows) {\r\n                if (row.startsWith('HTTP/')) {\r\n                    const values = row.split(' ');\r\n                    status = +values[1];\r\n\r\n                } else if (row.toLowerCase().startsWith('request-id')) {\r\n                    const values = row.split(' ');\r\n                    requestId = values[1];\r\n                    if (!requestId) {\r\n                        Logging.log({\r\n                            source: 'Batch PowerShell',\r\n                            level: LogLevel.Warning,\r\n                            message: `Couldn't parse request-id from the response: ${item}`\r\n                        });\r\n                    }\r\n\r\n                } else if (row.startsWith('{') && row.endsWith('}')) {\r\n                    try {\r\n                        // try parse only when we have a valid return code.\r\n                        if (!!status && status < 400) {\r\n                            data = JSON.parse(row);\r\n                        } else {\r\n                            // error response also JSON format mostly.\r\n                            data = row;\r\n                            if (typeof row === 'string') {\r\n                                try {\r\n                                    data = JSON.parse(row);\r\n                                } catch {\r\n                                    // ignore\r\n                                }\r\n                            }\r\n                        }\r\n                    } catch (exception) {\r\n                        // Log Exception on JSON parse fail.\r\n                        Logging.log({\r\n                            source: 'Batch PowerShell',\r\n                            level: LogLevel.Error,\r\n                            message: exception.message\r\n                        });\r\n\r\n                        // re throw.\r\n                        throw exception;\r\n                    }\r\n                }\r\n            }\r\n\r\n            const response = <AjaxResponse<any>>{ status: status, response: data };\r\n            const sequenceNumber = guidToRequestCtxMap[requestId]?.sequenceNumber;\r\n            const nodeName = guidToRequestCtxMap[requestId]?.nodeName;\r\n            results.push(<BatchResponseItem>{ response, nodeName, sequenceNumber });\r\n        }\r\n\r\n        return results;\r\n    }\r\n}\r\n"]}