Outlook_Addin_LLM/node_modules/@microsoft/dev-tunnels-management/tunnelManagementHttpClient.js

787 lines
38 KiB
JavaScript

"use strict";
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
Object.defineProperty(exports, "__esModule", { value: true });
exports.TunnelManagementHttpClient = exports.ManagementApiVersions = void 0;
const dev_tunnels_contracts_1 = require("@microsoft/dev-tunnels-contracts");
const tunnelManagementClient_1 = require("./tunnelManagementClient");
const tunnelAccessTokenProperties_1 = require("./tunnelAccessTokenProperties");
const version_1 = require("./version");
const axios_1 = require("axios");
const tunnelPlanTokenProperties_1 = require("./tunnelPlanTokenProperties");
const idGeneration_1 = require("./idGeneration");
const vscode_jsonrpc_1 = require("vscode-jsonrpc");
const tunnelsApiPath = '/tunnels';
const limitsApiPath = '/userlimits';
const endpointsApiSubPath = '/endpoints';
const portsApiSubPath = '/ports';
const clustersApiPath = '/clusters';
const tunnelAuthentication = 'Authorization';
const checkAvailablePath = ':checkNameAvailability';
const createNameRetries = 3;
var ManagementApiVersions;
(function (ManagementApiVersions) {
ManagementApiVersions["Version20230927preview"] = "2023-09-27-preview";
})(ManagementApiVersions = exports.ManagementApiVersions || (exports.ManagementApiVersions = {}));
function comparePorts(a, b) {
var _a, _b;
return ((_a = a.portNumber) !== null && _a !== void 0 ? _a : Number.MAX_SAFE_INTEGER) - ((_b = b.portNumber) !== null && _b !== void 0 ? _b : Number.MAX_SAFE_INTEGER);
}
function parseDate(value) {
return typeof value === 'string' ? new Date(Date.parse(value)) : value;
}
/**
* Fixes Tunnel properties of type Date that were deserialized as strings.
*/
function parseTunnelDates(tunnel) {
if (!tunnel)
return;
tunnel.created = parseDate(tunnel.created);
if (tunnel.status) {
tunnel.status.lastHostConnectionTime = parseDate(tunnel.status.lastHostConnectionTime);
tunnel.status.lastClientConnectionTime = parseDate(tunnel.status.lastClientConnectionTime);
}
}
/**
* Fixes TunnelPort properties of type Date that were deserialized as strings.
*/
function parseTunnelPortDates(port) {
if (!port)
return;
if (port.status) {
port.status.lastClientConnectionTime = parseDate(port.status.lastClientConnectionTime);
}
}
/**
* Copy access tokens from the request object to the result object, except for any
* tokens that were refreshed by the request.
*/
function preserveAccessTokens(requestObject, resultObject) {
var _a;
// This intentionally does not check whether any existing tokens are expired. So
// expired tokens may be preserved also, if not refreshed. This allows for better
// diagnostics in that case.
if (requestObject.accessTokens && resultObject) {
(_a = resultObject.accessTokens) !== null && _a !== void 0 ? _a : (resultObject.accessTokens = {});
for (const scopeAndToken of Object.entries(requestObject.accessTokens)) {
if (!resultObject.accessTokens[scopeAndToken[0]]) {
resultObject.accessTokens[scopeAndToken[0]] = scopeAndToken[1];
}
}
}
}
const manageAccessTokenScope = [dev_tunnels_contracts_1.TunnelAccessScopes.Manage];
const hostAccessTokenScope = [dev_tunnels_contracts_1.TunnelAccessScopes.Host];
const managePortsAccessTokenScopes = [
dev_tunnels_contracts_1.TunnelAccessScopes.Manage,
dev_tunnels_contracts_1.TunnelAccessScopes.ManagePorts,
dev_tunnels_contracts_1.TunnelAccessScopes.Host,
];
const readAccessTokenScopes = [
dev_tunnels_contracts_1.TunnelAccessScopes.Manage,
dev_tunnels_contracts_1.TunnelAccessScopes.ManagePorts,
dev_tunnels_contracts_1.TunnelAccessScopes.Host,
dev_tunnels_contracts_1.TunnelAccessScopes.Connect,
];
const apiVersions = ["2023-09-27-preview"];
const defaultRequestTimeoutMS = 20000;
class TunnelManagementHttpClient {
/**
* Initializes a new instance of the `TunnelManagementHttpClient` class
* with a client authentication callback, service URI, and HTTP handler.
*
* @param userAgent { name, version } object or a comment string to use as the User-Agent header.
* @param apiVersion ApiVersion to be used for requests, value should be one of ManagementApiVersions enum.
* @param userTokenCallback Optional async callback for retrieving a client authentication
* header value with access token, for AAD or GitHub user authentication. This may be omitted
* for anonymous tunnel clients, or if tunnel access tokens will be specified via
* `TunnelRequestOptions.accessToken`.
* @param tunnelServiceUri Optional tunnel service URI (not including any path). Defaults to
* the global tunnel service URI.
* @param httpsAgent Optional agent that will be invoked for HTTPS requests to the tunnel
* service.
* @param adapter Optional axios adapter to use for HTTP requests.
*/
constructor(userAgents, apiVersion, userTokenCallback, tunnelServiceUri, httpsAgent, adapter) {
var _a;
this.httpsAgent = httpsAgent;
this.adapter = adapter;
this.reportProgressEmitter = new vscode_jsonrpc_1.Emitter();
/**
* Event that is raised to report tunnel management progress.
*
* See `Progress` for a description of the different progress events that can be reported.
*/
this.onReportProgress = this.reportProgressEmitter.event;
this.trace = (msg) => { };
if (apiVersions.indexOf(apiVersion) === -1) {
throw new TypeError(`Invalid API version: ${apiVersion}, must be one of ${apiVersions}`);
}
this.apiVersion = apiVersion;
if (!userAgents) {
throw new TypeError('User agent must be provided.');
}
if (Array.isArray(userAgents)) {
if (userAgents.length === 0) {
throw new TypeError('User agents cannot be empty.');
}
let combinedUserAgents = '';
userAgents.forEach((userAgent) => {
var _a;
if (typeof userAgent !== 'string') {
if (!userAgent.name) {
throw new TypeError('Invalid user agent. The name must be provided.');
}
if (typeof userAgent.name !== 'string') {
throw new TypeError('Invalid user agent. The name must be a string.');
}
if (userAgent.version && typeof userAgent.version !== 'string') {
throw new TypeError('Invalid user agent. The version must be a string.');
}
combinedUserAgents = `${combinedUserAgents}${userAgent.name}/${(_a = userAgent.version) !== null && _a !== void 0 ? _a : 'unknown'} `;
}
else {
combinedUserAgents = `${combinedUserAgents}${userAgent} `;
}
});
this.userAgents = combinedUserAgents.trim();
}
else if (typeof userAgents !== 'string') {
if (!userAgents.name) {
throw new TypeError('Invalid user agent. The name must be provided.');
}
if (typeof userAgents.name !== 'string') {
throw new TypeError('Invalid user agent. The name must be a string.');
}
if (userAgents.version && typeof userAgents.version !== 'string') {
throw new TypeError('Invalid user agent. The version must be a string.');
}
this.userAgents = `${userAgents.name}/${(_a = userAgents.version) !== null && _a !== void 0 ? _a : 'unknown'}`;
}
else {
this.userAgents = userAgents;
}
this.userTokenCallback = userTokenCallback !== null && userTokenCallback !== void 0 ? userTokenCallback : (() => Promise.resolve(null));
if (!tunnelServiceUri) {
tunnelServiceUri = dev_tunnels_contracts_1.TunnelServiceProperties.production.serviceUri;
}
const parsedUri = new URL(tunnelServiceUri);
if (!parsedUri || parsedUri.pathname !== '/') {
throw new TypeError(`Invalid tunnel service URI: ${tunnelServiceUri}`);
}
this.baseAddress = tunnelServiceUri;
}
async listTunnels(clusterId, domain, options, cancellation) {
const queryParams = [clusterId ? null : 'global=true', domain ? `domain=${domain}` : null];
const query = queryParams.filter((p) => !!p).join('&');
const results = (await this.sendRequest('GET', clusterId, tunnelsApiPath, query, options, undefined, undefined, cancellation));
let tunnels = new Array();
if (results.value) {
for (const region of results.value) {
if (region.value) {
tunnels = tunnels.concat(region.value);
}
}
}
tunnels.forEach(parseTunnelDates);
return tunnels;
}
async getTunnel(tunnel, options, cancellation) {
const result = await this.sendTunnelRequest('GET', tunnel, readAccessTokenScopes, undefined, undefined, options, undefined, undefined, cancellation);
preserveAccessTokens(tunnel, result);
parseTunnelDates(result);
return result;
}
async createTunnel(tunnel, options, cancellation) {
const tunnelId = tunnel.tunnelId;
const idGenerated = tunnelId === undefined || tunnelId === null || tunnelId === '';
options = options || {};
options.additionalHeaders = options.additionalHeaders || {};
options.additionalHeaders['If-Not-Match'] = "*";
if (idGenerated) {
tunnel.tunnelId = idGeneration_1.IdGeneration.generateTunnelId();
}
for (let i = 0; i <= createNameRetries; i++) {
try {
const result = (await this.sendTunnelRequest('PUT', tunnel, manageAccessTokenScope, undefined, undefined, options, this.convertTunnelForRequest(tunnel), undefined, cancellation, true));
preserveAccessTokens(tunnel, result);
parseTunnelDates(result);
return result;
}
catch (error) {
if (idGenerated) {
// The tunnel ID was generated and there was a conflict.
// Try again with a new ID.
tunnel.tunnelId = idGeneration_1.IdGeneration.generateTunnelId();
}
else {
throw error;
}
}
}
const result2 = (await this.sendTunnelRequest('PUT', tunnel, manageAccessTokenScope, undefined, undefined, options, this.convertTunnelForRequest(tunnel), undefined, cancellation, true));
preserveAccessTokens(tunnel, result2);
parseTunnelDates(result2);
return result2;
}
async createOrUpdateTunnel(tunnel, options, cancellation) {
const tunnelId = tunnel.tunnelId;
const idGenerated = tunnelId === undefined || tunnelId === null || tunnelId === '';
if (idGenerated) {
tunnel.tunnelId = idGeneration_1.IdGeneration.generateTunnelId();
}
for (let i = 0; i <= createNameRetries; i++) {
try {
const result = (await this.sendTunnelRequest('PUT', tunnel, manageAccessTokenScope, undefined, undefined, options, this.convertTunnelForRequest(tunnel), undefined, cancellation, true));
preserveAccessTokens(tunnel, result);
parseTunnelDates(result);
return result;
}
catch (error) {
if (idGenerated) {
// The tunnel ID was generated and there was a conflict.
// Try again with a new ID.
tunnel.tunnelId = idGeneration_1.IdGeneration.generateTunnelId();
}
else {
throw error;
}
}
}
const result2 = (await this.sendTunnelRequest('PUT', tunnel, manageAccessTokenScope, undefined, "forceCreate=true", options, this.convertTunnelForRequest(tunnel), undefined, cancellation, true));
preserveAccessTokens(tunnel, result2);
parseTunnelDates(result2);
return result2;
}
async updateTunnel(tunnel, options, cancellation) {
options = options || {};
options.additionalHeaders = options.additionalHeaders || {};
options.additionalHeaders['If-Match'] = "*";
const result = (await this.sendTunnelRequest('PUT', tunnel, manageAccessTokenScope, undefined, undefined, options, this.convertTunnelForRequest(tunnel), undefined, cancellation));
preserveAccessTokens(tunnel, result);
parseTunnelDates(result);
return result;
}
async deleteTunnel(tunnel, options, cancellation) {
return await this.sendTunnelRequest('DELETE', tunnel, manageAccessTokenScope, undefined, undefined, options, undefined, true, cancellation);
}
async updateTunnelEndpoint(tunnel, endpoint, options, cancellation) {
if (endpoint.id == null) {
throw new Error('Endpoint ID must be specified when updating an endpoint.');
}
const path = `${endpointsApiSubPath}/${endpoint.id}`;
const result = (await this.sendTunnelRequest('PUT', tunnel, hostAccessTokenScope, path, "connectionMode=" + endpoint.connectionMode, options, endpoint, undefined, cancellation));
if (tunnel.endpoints) {
// Also update the endpoint in the local tunnel object.
tunnel.endpoints = tunnel.endpoints
.filter((e) => e.hostId !== endpoint.hostId ||
e.connectionMode !== endpoint.connectionMode)
.concat(result);
}
return result;
}
async deleteTunnelEndpoints(tunnel, id, options, cancellation) {
const path = `${endpointsApiSubPath}/${id}`;
const result = await this.sendTunnelRequest('DELETE', tunnel, hostAccessTokenScope, path, undefined, options, undefined, true, cancellation);
if (result && tunnel.endpoints) {
// Also delete the endpoint in the local tunnel object.
tunnel.endpoints = tunnel.endpoints.filter((e) => e.id !== id);
}
return result;
}
async listUserLimits(cancellation) {
const results = await this.sendRequest('GET', undefined, limitsApiPath, undefined, undefined, undefined, undefined, cancellation);
return results || [];
}
async listTunnelPorts(tunnel, options, cancellation) {
const results = (await this.sendTunnelRequest('GET', tunnel, readAccessTokenScopes, portsApiSubPath, undefined, options, undefined, undefined, cancellation));
if (results.value) {
results.value.forEach(parseTunnelPortDates);
}
return results.value;
}
async getTunnelPort(tunnel, portNumber, options, cancellation) {
this.raiseReportProgress(dev_tunnels_contracts_1.TunnelProgress.StartingGetTunnelPort);
const path = `${portsApiSubPath}/${portNumber}`;
const result = await this.sendTunnelRequest('GET', tunnel, readAccessTokenScopes, path, undefined, options, undefined, undefined, cancellation);
parseTunnelPortDates(result);
this.raiseReportProgress(dev_tunnels_contracts_1.TunnelProgress.CompletedGetTunnelPort);
return result;
}
async createTunnelPort(tunnel, tunnelPort, options, cancellation) {
this.raiseReportProgress(dev_tunnels_contracts_1.TunnelProgress.StartingCreateTunnelPort);
tunnelPort = this.convertTunnelPortForRequest(tunnel, tunnelPort);
const path = `${portsApiSubPath}/${tunnelPort.portNumber}`;
options = options || {};
options.additionalHeaders = options.additionalHeaders || {};
options.additionalHeaders['If-Not-Match'] = "*";
const result = (await this.sendTunnelRequest('PUT', tunnel, managePortsAccessTokenScopes, path, undefined, options, tunnelPort, undefined, cancellation));
tunnel.ports = tunnel.ports || [];
// Also add the port to the local tunnel object.
tunnel.ports = tunnel.ports
.filter((p) => p.portNumber !== tunnelPort.portNumber)
.concat(result)
.sort(comparePorts);
parseTunnelPortDates(result);
this.raiseReportProgress(dev_tunnels_contracts_1.TunnelProgress.CompletedCreateTunnelPort);
return result;
}
async updateTunnelPort(tunnel, tunnelPort, options, cancellation) {
if (tunnelPort.clusterId && tunnel.clusterId && tunnelPort.clusterId !== tunnel.clusterId) {
throw new Error('Tunnel port cluster ID is not consistent.');
}
options = options || {};
options.additionalHeaders = options.additionalHeaders || {};
options.additionalHeaders['If-Match'] = "*";
const portNumber = tunnelPort.portNumber;
const path = `${portsApiSubPath}/${portNumber}`;
tunnelPort = this.convertTunnelPortForRequest(tunnel, tunnelPort);
const result = (await this.sendTunnelRequest('PUT', tunnel, managePortsAccessTokenScopes, path, undefined, options, tunnelPort, undefined, cancellation));
preserveAccessTokens(tunnelPort, result);
parseTunnelPortDates(result);
tunnel.ports = tunnel.ports || [];
// Also add the port to the local tunnel object.
tunnel.ports = tunnel.ports
.filter((p) => p.portNumber !== tunnelPort.portNumber)
.concat(result)
.sort(comparePorts);
return result;
}
async createOrUpdateTunnelPort(tunnel, tunnelPort, options, cancellation) {
tunnelPort = this.convertTunnelPortForRequest(tunnel, tunnelPort);
const path = `${portsApiSubPath}/${tunnelPort.portNumber}`;
const result = (await this.sendTunnelRequest('PUT', tunnel, managePortsAccessTokenScopes, path, undefined, options, tunnelPort, undefined, cancellation));
tunnel.ports = tunnel.ports || [];
// Also add the port to the local tunnel object.
tunnel.ports = tunnel.ports
.filter((p) => p.portNumber !== tunnelPort.portNumber)
.concat(result)
.sort(comparePorts);
parseTunnelPortDates(result);
return result;
}
async deleteTunnelPort(tunnel, portNumber, options, cancellation) {
const path = `${portsApiSubPath}/${portNumber}`;
const result = await this.sendTunnelRequest('DELETE', tunnel, managePortsAccessTokenScopes, path, undefined, options, undefined, true, cancellation);
if (result && tunnel.ports) {
// Also delete the port in the local tunnel object.
tunnel.ports = tunnel.ports
.filter((p) => p.portNumber !== portNumber)
.sort(comparePorts);
}
return result;
}
async listClusters(cancellation) {
return (await this.sendRequest('GET', undefined, clustersApiPath, undefined, undefined, undefined, false, cancellation));
}
/**
* Sends an HTTP request to the tunnel management API, targeting a specific tunnel.
* This protected method enables subclasses to support additional tunnel management APIs.
* @param method HTTP request method.
* @param tunnel Tunnel that the request is targeting.
* @param accessTokenScopes Required array of access scopes for tokens in `tunnel.accessTokens`
* that could be used to authorize the request.
* @param path Optional request sub-path relative to the tunnel.
* @param query Optional query string to append to the request.
* @param options Request options.
* @param body Optional request body object.
* @param allowNotFound If true, a 404 response is returned as a null or false result
* instead of an error.
* @param cancellationToken Optional cancellation token for the request.
* @param isCreate Set to true if this is a tunnel create request, default is false.
* @returns Result of the request.
*/
async sendTunnelRequest(method, tunnel, accessTokenScopes, path, query, options, body, allowNotFound, cancellation, isCreate = false) {
this.raiseReportProgress(dev_tunnels_contracts_1.TunnelProgress.StartingRequestUri);
const uri = await this.buildUriForTunnel(tunnel, path, query, options, isCreate);
this.raiseReportProgress(dev_tunnels_contracts_1.TunnelProgress.StartingRequestConfig);
const config = await this.getAxiosRequestConfig(tunnel, options, accessTokenScopes);
this.raiseReportProgress(dev_tunnels_contracts_1.TunnelProgress.StartingSendTunnelRequest);
const result = await this.request(method, uri, body, config, allowNotFound, cancellation);
this.raiseReportProgress(dev_tunnels_contracts_1.TunnelProgress.CompletedSendTunnelRequest);
return result;
}
/**
* Sends an HTTP request to the tunnel management API.
* This protected method enables subclasses to support additional tunnel management APIs.
* @param method HTTP request method.
* @param clusterId Optional tunnel service cluster ID to direct the request to. If unspecified,
* the request will use the global traffic-manager to find the nearest cluster.
* @param path Required request path.
* @param query Optional query string to append to the request.
* @param options Request options.
* @param body Optional request body object.
* @param allowNotFound If true, a 404 response is returned as a null or false result
* instead of an error.
* @param cancellationToken Optional cancellation token for the request.
* @returns Result of the request.
*/
async sendRequest(method, clusterId, path, query, options, body, allowNotFound, cancellation) {
this.raiseReportProgress(dev_tunnels_contracts_1.TunnelProgress.StartingSendTunnelRequest);
const uri = await this.buildUri(clusterId, path, query, options);
const config = await this.getAxiosRequestConfig(undefined, options);
const result = await this.request(method, uri, body, config, allowNotFound, cancellation);
this.raiseReportProgress(dev_tunnels_contracts_1.TunnelProgress.CompletedSendTunnelRequest);
return result;
}
async checkNameAvailablility(tunnelName, cancellation) {
tunnelName = encodeURI(tunnelName);
const uri = await this.buildUri(undefined, `${tunnelsApiPath}/${tunnelName}${checkAvailablePath}`);
const config = {
httpsAgent: this.httpsAgent,
adapter: this.adapter,
};
return await this.request('GET', uri, undefined, config, undefined, cancellation);
}
raiseReportProgress(progress) {
const args = {
progress: progress
};
this.reportProgressEmitter.fire(args);
}
getResponseErrorMessage(error, signal) {
var _a, _b, _c, _d;
let errorMessage = '';
if (error.code === 'ECONNABORTED') {
// server timeout
errorMessage = `Timeout reached: ${error.message}`;
}
if (signal.aborted) {
// connection timeout
errorMessage = `Signal aborted: ${error.message}`;
}
if ((_a = error.response) === null || _a === void 0 ? void 0 : _a.data) {
const problemDetails = error.response.data;
if (problemDetails.title || problemDetails.detail) {
errorMessage = `Tunnel service error: ${problemDetails.title}`;
if (problemDetails.detail) {
errorMessage += ' ' + problemDetails.detail;
}
if (problemDetails.errors) {
errorMessage += JSON.stringify(problemDetails.errors);
}
}
}
if (!errorMessage) {
if (error === null || error === void 0 ? void 0 : error.response) {
errorMessage =
'Tunnel service returned status code: ' +
`${error.response.status} ${error.response.statusText}`;
}
else {
errorMessage = (_c = (_b = error === null || error === void 0 ? void 0 : error.message) !== null && _b !== void 0 ? _b : error) !== null && _c !== void 0 ? _c : 'Unknown tunnel service request error.';
}
}
const requestIdHeaderName = 'VsSaaS-Request-Id';
if (((_d = error.response) === null || _d === void 0 ? void 0 : _d.headers) && error.response.headers[requestIdHeaderName]) {
errorMessage += `\nRequest ID: ${error.response.headers[requestIdHeaderName]}`;
}
return errorMessage;
}
// Helper functions
async buildUri(clusterId, path, query, options) {
if (clusterId === undefined && this.userTokenCallback) {
let token = await this.userTokenCallback();
if (token && token.startsWith("tunnelplan")) {
token = token.replace("tunnelplan ", "");
const parsedToken = tunnelPlanTokenProperties_1.TunnelPlanTokenProperties.tryParse(token);
if (parsedToken !== null && parsedToken.clusterId) {
clusterId = parsedToken.clusterId;
}
}
}
let baseAddress = this.baseAddress;
if (clusterId) {
const url = new URL(baseAddress);
const portNumber = parseInt(url.port, 10);
if (url.hostname !== 'localhost' && !url.hostname.startsWith(`${clusterId}.`)) {
// A specific cluster ID was specified (while not running on localhost).
// Prepend the cluster ID to the hostname, and optionally strip a global prefix.
url.hostname = `${clusterId}.${url.hostname}`.replace('global.', '');
baseAddress = url.toString();
}
else if (url.protocol === 'https:' &&
clusterId.startsWith('localhost') &&
portNumber % 10 > 0) {
// Local testing simulates clusters by running the service on multiple ports.
// Change the port number to match the cluster ID suffix.
const clusterNumber = parseInt(clusterId.substring('localhost'.length), 10);
if (clusterNumber > 0 && clusterNumber < 10) {
url.port = (portNumber - (portNumber % 10) + clusterNumber).toString();
baseAddress = url.toString();
}
}
}
baseAddress = `${baseAddress.replace(/\/$/, '')}${path}`;
const optionsQuery = this.tunnelRequestOptionsToQueryString(options, query);
if (optionsQuery) {
baseAddress += `?${optionsQuery}`;
}
return baseAddress;
}
buildUriForTunnel(tunnel, path, query, options, isCreate = false) {
let tunnelPath = '';
if ((tunnel.clusterId || isCreate) && tunnel.tunnelId) {
tunnelPath = `${tunnelsApiPath}/${tunnel.tunnelId}`;
}
else {
throw new Error('Tunnel object must include a tunnel ID always and cluster ID for non creates.');
}
if (options === null || options === void 0 ? void 0 : options.additionalQueryParameters) {
for (const [paramName, paramValue] of Object.entries(options.additionalQueryParameters)) {
if (query) {
query += `&${paramName}=${paramValue}`;
}
else {
query = `${paramName}=${paramValue}`;
}
}
}
return this.buildUri(tunnel.clusterId, tunnelPath + (path ? path : ''), query, options);
}
async getAxiosRequestConfig(tunnel, options, accessTokenScopes) {
// Get access token header
const headers = {};
if (options && options.accessToken) {
tunnelAccessTokenProperties_1.TunnelAccessTokenProperties.validateTokenExpiration(options.accessToken);
headers[tunnelAuthentication] = `${tunnelManagementClient_1.TunnelAuthenticationSchemes.tunnel} ${options.accessToken}`;
}
if (!(tunnelAuthentication in headers) && this.userTokenCallback) {
const token = await this.userTokenCallback();
if (token) {
headers[tunnelAuthentication] = token;
}
}
if (!(tunnelAuthentication in headers)) {
const accessToken = tunnelAccessTokenProperties_1.TunnelAccessTokenProperties.getTunnelAccessToken(tunnel, accessTokenScopes);
if (accessToken) {
headers[tunnelAuthentication] = `${tunnelManagementClient_1.TunnelAuthenticationSchemes.tunnel} ${accessToken}`;
}
}
const copyAdditionalHeaders = (additionalHeaders) => {
if (additionalHeaders) {
for (const [headerName, headerValue] of Object.entries(additionalHeaders)) {
headers[headerName] = headerValue;
}
}
};
copyAdditionalHeaders(this.additionalRequestHeaders);
copyAdditionalHeaders(options === null || options === void 0 ? void 0 : options.additionalHeaders);
const userAgentPrefix = headers['User-Agent'] ? headers['User-Agent'] + ' ' : '';
headers['User-Agent'] = `${userAgentPrefix}${this.userAgents} ${version_1.tunnelSdkUserAgent}`;
// Get axios config
const config = Object.assign(Object.assign({ headers }, (this.httpsAgent && { httpsAgent: this.httpsAgent })), (this.adapter && { adapter: this.adapter }));
if ((options === null || options === void 0 ? void 0 : options.followRedirects) === false) {
config.maxRedirects = 0;
}
return config;
}
convertTunnelForRequest(tunnel) {
var _a;
const convertedTunnel = {
tunnelId: tunnel.tunnelId,
name: tunnel.name,
domain: tunnel.domain,
description: tunnel.description,
labels: tunnel.labels,
options: tunnel.options,
customExpiration: tunnel.customExpiration,
accessControl: !tunnel.accessControl
? undefined
: { entries: tunnel.accessControl.entries.filter((ace) => !ace.isInherited) },
endpoints: tunnel.endpoints,
ports: (_a = tunnel.ports) === null || _a === void 0 ? void 0 : _a.map((p) => this.convertTunnelPortForRequest(tunnel, p)),
};
return convertedTunnel;
}
convertTunnelPortForRequest(tunnel, tunnelPort) {
if (tunnelPort.clusterId && tunnel.clusterId && tunnelPort.clusterId !== tunnel.clusterId) {
throw new Error('Tunnel port cluster ID does not match tunnel.');
}
if (tunnelPort.tunnelId && tunnel.tunnelId && tunnelPort.tunnelId !== tunnel.tunnelId) {
throw new Error('Tunnel port tunnel ID does not match tunnel.');
}
return {
portNumber: tunnelPort.portNumber,
protocol: tunnelPort.protocol,
isDefault: tunnelPort.isDefault,
description: tunnelPort.description,
labels: tunnelPort.labels,
sshUser: tunnelPort.sshUser,
options: tunnelPort.options,
accessControl: !tunnelPort.accessControl
? undefined
: { entries: tunnelPort.accessControl.entries.filter((ace) => !ace.isInherited) },
};
}
tunnelRequestOptionsToQueryString(options, additionalQuery) {
const queryOptions = {};
const queryItems = [];
if (options) {
if (options.includePorts) {
queryOptions.includePorts = ['true'];
}
if (options.includeAccessControl) {
queryOptions.includeAccessControl = ['true'];
}
if (options.tokenScopes) {
dev_tunnels_contracts_1.TunnelAccessControl.validateScopes(options.tokenScopes, undefined, true);
queryOptions.tokenScopes = options.tokenScopes;
}
if (options.forceRename) {
queryOptions.forceRename = ['true'];
}
if (options.labels) {
queryOptions.labels = options.labels;
if (options.requireAllLabels) {
queryOptions.allLabels = ['true'];
}
}
if (options.limit) {
queryOptions.limit = [options.limit.toString()];
}
queryItems.push(...Object.keys(queryOptions).map((key) => {
const value = queryOptions[key];
return `${key}=${value.map(encodeURIComponent).join(',')}`;
}));
}
if (additionalQuery) {
queryItems.push(additionalQuery);
}
queryItems.push(`api-version=${this.apiVersion}`);
const queryString = queryItems.join('&');
return queryString;
}
/**
* Axios request that can be overridden for unit tests purposes.
* @param config axios request config
* @param _cancellation the cancellation token for the request (used by unit tests to simulate timeouts).
*/
async axiosRequest(config, _cancellation) {
return await axios_1.default.request(config);
}
/**
* Makes an HTTP request using Axios, while tracing request and response details.
*/
async request(method, uri, data, config, allowNotFound, cancellation) {
var _a, _b;
this.trace(`${method} ${uri}`);
if (config.headers) {
this.traceHeaders(config.headers);
}
this.traceContent(data);
const traceResponse = (response) => {
this.trace(`${response.status} ${response.statusText}`);
this.traceHeaders(response.headers);
this.traceContent(response.data);
};
let disposable;
const abortController = new AbortController();
let timeout = undefined;
const newAbortSignal = () => {
if (cancellation === null || cancellation === void 0 ? void 0 : cancellation.isCancellationRequested) {
abortController.abort('Cancelled: CancellationToken cancel requested.');
}
else if (cancellation) {
disposable = cancellation.onCancellationRequested(() => abortController.abort('Cancelled: CancellationToken cancel requested.'));
}
else {
timeout = setTimeout(() => abortController.abort('Cancelled: default request timeout reached.'), defaultRequestTimeoutMS);
}
return abortController.signal;
};
try {
config.url = uri;
config.method = method;
config.data = data;
config.signal = newAbortSignal();
config.timeout = defaultRequestTimeoutMS;
const response = await this.axiosRequest(config, cancellation);
traceResponse(response);
// This assumes that TResult is always boolean for DELETE requests.
return (method === 'DELETE' ? true : response.data);
}
catch (e) {
if (!(e instanceof Error) || !e.isAxiosError)
throw e;
const requestError = e;
if (requestError.response) {
traceResponse(requestError.response);
if (allowNotFound && requestError.response.status === 404) {
return (method === 'DELETE' ? false : null);
}
}
requestError.message = this.getResponseErrorMessage(requestError, abortController.signal);
// Axios errors have too much redundant detail! Delete some of it.
delete requestError.request;
if (requestError.response) {
(_a = requestError.config) === null || _a === void 0 ? true : delete _a.httpAgent;
(_b = requestError.config) === null || _b === void 0 ? true : delete _b.httpsAgent;
delete requestError.response.request;
}
throw requestError;
}
finally {
if (timeout) {
clearTimeout(timeout);
}
disposable === null || disposable === void 0 ? void 0 : disposable.dispose();
}
}
traceHeaders(headers) {
for (const [headerName, headerValue] of Object.entries(headers)) {
if (headerName === 'Authorization') {
this.traceAuthorizationHeader(headerName, headerValue);
return;
}
this.trace(`${headerName}: ${headerValue !== null && headerValue !== void 0 ? headerValue : ''}`);
}
}
traceAuthorizationHeader(key, value) {
if (typeof value !== 'string')
return;
const spaceIndex = value.indexOf(' ');
if (spaceIndex < 0) {
this.trace(`${key}: [${value.length}]`);
return;
}
const scheme = value.substring(0, spaceIndex);
const token = value.substring(spaceIndex + 1);
if (scheme.toLowerCase() === tunnelManagementClient_1.TunnelAuthenticationSchemes.tunnel.toLowerCase()) {
const tokenProperties = tunnelAccessTokenProperties_1.TunnelAccessTokenProperties.tryParse(token);
if (tokenProperties) {
this.trace(`${key}: ${scheme} <${tokenProperties}>`);
return;
}
}
this.trace(`${key}: ${scheme} <token>`);
}
traceContent(data) {
if (typeof data === 'object') {
data = JSON.stringify(data, undefined, ' ');
}
if (typeof data === 'string') {
this.trace(TunnelManagementHttpClient.replaceTokensInContent(data));
}
}
static replaceTokensInContent(content) {
var _a;
const tokenRegex = /"(eyJ[a-zA-z0-9\-_]+\.[a-zA-z0-9\-_]+\.[a-zA-z0-9\-_]+)"/;
let match = tokenRegex.exec(content);
while (match) {
let token = match[1];
const tokenProperties = tunnelAccessTokenProperties_1.TunnelAccessTokenProperties.tryParse(token);
token = (_a = tokenProperties === null || tokenProperties === void 0 ? void 0 : tokenProperties.toString()) !== null && _a !== void 0 ? _a : 'token';
content =
content.substring(0, match.index + 1) +
'<' +
token +
'>' +
content.substring(match.index + match[0].length - 1);
match = tokenRegex.exec(content);
}
return content;
}
}
exports.TunnelManagementHttpClient = TunnelManagementHttpClient;
//# sourceMappingURL=tunnelManagementHttpClient.js.map