@microsoft/dev-tunnels-management 1.0.8 → 1.0.7302
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/index.d.ts +4 -4
- package/index.js +18 -18
- package/package.json +1 -1
- package/tsbuildinfo.json +1 -1
- package/tunnelAccessTokenProperties.d.ts +43 -43
- package/tunnelAccessTokenProperties.js +138 -138
- package/tunnelManagementClient.d.ts +132 -132
- package/tunnelManagementClient.js +14 -14
- package/tunnelManagementHttpClient.d.ts +92 -92
- package/tunnelManagementHttpClient.js +573 -573
- package/tunnelRequestOptions.d.ts +68 -68
- package/tunnelRequestOptions.js +4 -4
- package/version.d.ts +4 -4
- package/version.js +12 -12
|
@@ -1,574 +1,574 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
// Copyright (c) Microsoft Corporation.
|
|
3
|
-
// Licensed under the MIT license.
|
|
4
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
5
|
-
exports.TunnelManagementHttpClient = void 0;
|
|
6
|
-
const dev_tunnels_contracts_1 = require("@microsoft/dev-tunnels-contracts");
|
|
7
|
-
const tunnelManagementClient_1 = require("./tunnelManagementClient");
|
|
8
|
-
const tunnelAccessTokenProperties_1 = require("./tunnelAccessTokenProperties");
|
|
9
|
-
const version_1 = require("./version");
|
|
10
|
-
const axios_1 = require("axios");
|
|
11
|
-
const apiV1Path = `/api/v1`;
|
|
12
|
-
const tunnelsApiPath = apiV1Path + '/tunnels';
|
|
13
|
-
const endpointsApiSubPath = '/endpoints';
|
|
14
|
-
const portsApiSubPath = '/ports';
|
|
15
|
-
const clustersApiPath = apiV1Path + '/clusters';
|
|
16
|
-
const tunnelAuthentication = 'Authorization';
|
|
17
|
-
function comparePorts(a, b) {
|
|
18
|
-
var _a, _b;
|
|
19
|
-
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);
|
|
20
|
-
}
|
|
21
|
-
function parseDate(value) {
|
|
22
|
-
return typeof value === 'string' ? new Date(Date.parse(value)) : value;
|
|
23
|
-
}
|
|
24
|
-
/**
|
|
25
|
-
* Fixes Tunnel properties of type Date that were deserialized as strings.
|
|
26
|
-
*/
|
|
27
|
-
function parseTunnelDates(tunnel) {
|
|
28
|
-
if (!tunnel)
|
|
29
|
-
return;
|
|
30
|
-
tunnel.created = parseDate(tunnel.created);
|
|
31
|
-
if (tunnel.status) {
|
|
32
|
-
tunnel.status.lastHostConnectionTime = parseDate(tunnel.status.lastHostConnectionTime);
|
|
33
|
-
tunnel.status.lastClientConnectionTime = parseDate(tunnel.status.lastClientConnectionTime);
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
/**
|
|
37
|
-
* Fixes TunnelPort properties of type Date that were deserialized as strings.
|
|
38
|
-
*/
|
|
39
|
-
function parseTunnelPortDates(port) {
|
|
40
|
-
if (!port)
|
|
41
|
-
return;
|
|
42
|
-
if (port.status) {
|
|
43
|
-
port.status.lastClientConnectionTime = parseDate(port.status.lastClientConnectionTime);
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
const manageAccessTokenScope = [dev_tunnels_contracts_1.TunnelAccessScopes.Manage];
|
|
47
|
-
const hostAccessTokenScope = [dev_tunnels_contracts_1.TunnelAccessScopes.Host];
|
|
48
|
-
const managePortsAccessTokenScopes = [
|
|
49
|
-
dev_tunnels_contracts_1.TunnelAccessScopes.Manage,
|
|
50
|
-
dev_tunnels_contracts_1.TunnelAccessScopes.ManagePorts,
|
|
51
|
-
dev_tunnels_contracts_1.TunnelAccessScopes.Host,
|
|
52
|
-
];
|
|
53
|
-
const readAccessTokenScopes = [
|
|
54
|
-
dev_tunnels_contracts_1.TunnelAccessScopes.Manage,
|
|
55
|
-
dev_tunnels_contracts_1.TunnelAccessScopes.ManagePorts,
|
|
56
|
-
dev_tunnels_contracts_1.TunnelAccessScopes.Host,
|
|
57
|
-
dev_tunnels_contracts_1.TunnelAccessScopes.Connect,
|
|
58
|
-
];
|
|
59
|
-
class TunnelManagementHttpClient {
|
|
60
|
-
/**
|
|
61
|
-
* Initializes a new instance of the `TunnelManagementHttpClient` class
|
|
62
|
-
* with a client authentication callback, service URI, and HTTP handler.
|
|
63
|
-
*
|
|
64
|
-
* @param userAgent { name, version } object or a comment string to use as the User-Agent header.
|
|
65
|
-
* @param userTokenCallback Optional async callback for retrieving a client authentication
|
|
66
|
-
* header value with access token, for AAD or GitHub user authentication. This may be omitted
|
|
67
|
-
* for anonymous tunnel clients, or if tunnel access tokens will be specified via
|
|
68
|
-
* `TunnelRequestOptions.accessToken`.
|
|
69
|
-
* @param tunnelServiceUri Optional tunnel service URI (not including any path). Defaults to
|
|
70
|
-
* the global tunnel service URI.
|
|
71
|
-
* @param httpsAgent Optional agent that will be invoked for HTTPS requests to the tunnel
|
|
72
|
-
* service.
|
|
73
|
-
*/
|
|
74
|
-
constructor(userAgents, userTokenCallback, tunnelServiceUri, httpsAgent) {
|
|
75
|
-
var _a;
|
|
76
|
-
this.httpsAgent = httpsAgent;
|
|
77
|
-
this.trace = (msg) => { };
|
|
78
|
-
if (!userAgents) {
|
|
79
|
-
throw new TypeError('User agent must be provided.');
|
|
80
|
-
}
|
|
81
|
-
if (Array.isArray(userAgents)) {
|
|
82
|
-
if (userAgents.length === 0) {
|
|
83
|
-
throw new TypeError('User agents cannot be empty.');
|
|
84
|
-
}
|
|
85
|
-
let combinedUserAgents = '';
|
|
86
|
-
userAgents.forEach((userAgent) => {
|
|
87
|
-
var _a;
|
|
88
|
-
if (typeof userAgent !== 'string') {
|
|
89
|
-
if (!userAgent.name) {
|
|
90
|
-
throw new TypeError('Invalid user agent. The name must be provided.');
|
|
91
|
-
}
|
|
92
|
-
if (typeof userAgent.name !== 'string') {
|
|
93
|
-
throw new TypeError('Invalid user agent. The name must be a string.');
|
|
94
|
-
}
|
|
95
|
-
if (userAgent.version && typeof userAgent.version !== 'string') {
|
|
96
|
-
throw new TypeError('Invalid user agent. The version must be a string.');
|
|
97
|
-
}
|
|
98
|
-
combinedUserAgents = `${combinedUserAgents}${userAgent.name}/${(_a = userAgent.version) !== null && _a !== void 0 ? _a : 'unknown'} `;
|
|
99
|
-
}
|
|
100
|
-
else {
|
|
101
|
-
combinedUserAgents = `${combinedUserAgents}${userAgent} `;
|
|
102
|
-
}
|
|
103
|
-
});
|
|
104
|
-
this.userAgents = combinedUserAgents.trim();
|
|
105
|
-
}
|
|
106
|
-
else if (typeof userAgents !== 'string') {
|
|
107
|
-
if (!userAgents.name) {
|
|
108
|
-
throw new TypeError('Invalid user agent. The name must be provided.');
|
|
109
|
-
}
|
|
110
|
-
if (typeof userAgents.name !== 'string') {
|
|
111
|
-
throw new TypeError('Invalid user agent. The name must be a string.');
|
|
112
|
-
}
|
|
113
|
-
if (userAgents.version && typeof userAgents.version !== 'string') {
|
|
114
|
-
throw new TypeError('Invalid user agent. The version must be a string.');
|
|
115
|
-
}
|
|
116
|
-
this.userAgents = `${userAgents.name}/${(_a = userAgents.version) !== null && _a !== void 0 ? _a : 'unknown'}`;
|
|
117
|
-
}
|
|
118
|
-
else {
|
|
119
|
-
this.userAgents = userAgents;
|
|
120
|
-
}
|
|
121
|
-
this.userTokenCallback = userTokenCallback !== null && userTokenCallback !== void 0 ? userTokenCallback : (() => Promise.resolve(null));
|
|
122
|
-
if (!tunnelServiceUri) {
|
|
123
|
-
tunnelServiceUri = dev_tunnels_contracts_1.TunnelServiceProperties.production.serviceUri;
|
|
124
|
-
}
|
|
125
|
-
const parsedUri = new URL(tunnelServiceUri);
|
|
126
|
-
if (!parsedUri || parsedUri.pathname !== '/') {
|
|
127
|
-
throw new TypeError(`Invalid tunnel service URI: ${tunnelServiceUri}`);
|
|
128
|
-
}
|
|
129
|
-
this.baseAddress = tunnelServiceUri;
|
|
130
|
-
}
|
|
131
|
-
async listTunnels(clusterId, domain, options) {
|
|
132
|
-
const queryParams = [clusterId ? null : 'global=true', domain ? `domain=${domain}` : null];
|
|
133
|
-
const query = queryParams.filter((p) => !!p).join('&');
|
|
134
|
-
const results = (await this.sendRequest('GET', clusterId, tunnelsApiPath, query, options));
|
|
135
|
-
results.forEach(parseTunnelDates);
|
|
136
|
-
return results;
|
|
137
|
-
}
|
|
138
|
-
async getTunnel(tunnel, options) {
|
|
139
|
-
const result = await this.sendTunnelRequest('GET', tunnel, readAccessTokenScopes, undefined, undefined, options);
|
|
140
|
-
parseTunnelDates(result);
|
|
141
|
-
return result;
|
|
142
|
-
}
|
|
143
|
-
async createTunnel(tunnel, options) {
|
|
144
|
-
let tunnelId = tunnel.tunnelId;
|
|
145
|
-
if (tunnelId) {
|
|
146
|
-
throw new Error('An ID may not be specified when creating a tunnel.');
|
|
147
|
-
}
|
|
148
|
-
tunnel = this.convertTunnelForRequest(tunnel);
|
|
149
|
-
const result = (await this.sendRequest('POST', tunnel.clusterId, tunnelsApiPath, undefined, options, tunnel));
|
|
150
|
-
parseTunnelDates(result);
|
|
151
|
-
return result;
|
|
152
|
-
}
|
|
153
|
-
async updateTunnel(tunnel, options) {
|
|
154
|
-
const result = (await this.sendTunnelRequest('PUT', tunnel, manageAccessTokenScope, undefined, undefined, options, this.convertTunnelForRequest(tunnel)));
|
|
155
|
-
if (!(options === null || options === void 0 ? void 0 : options.tokenScopes)) {
|
|
156
|
-
// If no new tokens were requested in the update, preserve any existing
|
|
157
|
-
// access tokens in the resulting tunnel object.
|
|
158
|
-
result.accessTokens = tunnel.accessTokens;
|
|
159
|
-
}
|
|
160
|
-
parseTunnelDates(result);
|
|
161
|
-
return result;
|
|
162
|
-
}
|
|
163
|
-
async deleteTunnel(tunnel, options) {
|
|
164
|
-
return await this.sendTunnelRequest('DELETE', tunnel, manageAccessTokenScope, undefined, undefined, options, undefined, true);
|
|
165
|
-
}
|
|
166
|
-
async updateTunnelEndpoint(tunnel, endpoint, options) {
|
|
167
|
-
const path = `${endpointsApiSubPath}/${endpoint.hostId}/${endpoint.connectionMode}`;
|
|
168
|
-
const result = (await this.sendTunnelRequest('PUT', tunnel, hostAccessTokenScope, path, undefined, options, endpoint));
|
|
169
|
-
if (tunnel.endpoints) {
|
|
170
|
-
// Also update the endpoint in the local tunnel object.
|
|
171
|
-
tunnel.endpoints = tunnel.endpoints
|
|
172
|
-
.filter((e) => e.hostId !== endpoint.hostId ||
|
|
173
|
-
e.connectionMode !== endpoint.connectionMode)
|
|
174
|
-
.concat(result);
|
|
175
|
-
}
|
|
176
|
-
return result;
|
|
177
|
-
}
|
|
178
|
-
async deleteTunnelEndpoints(tunnel, hostId, connectionMode, options) {
|
|
179
|
-
const path = connectionMode == null
|
|
180
|
-
? `${endpointsApiSubPath}/${hostId}`
|
|
181
|
-
: `${endpointsApiSubPath}/${hostId}/${connectionMode}`;
|
|
182
|
-
const result = await this.sendTunnelRequest('DELETE', tunnel, hostAccessTokenScope, path, undefined, options, undefined, true);
|
|
183
|
-
if (result && tunnel.endpoints) {
|
|
184
|
-
// Also delete the endpoint in the local tunnel object.
|
|
185
|
-
tunnel.endpoints = tunnel.endpoints.filter((e) => e.hostId !== hostId || e.connectionMode !== connectionMode);
|
|
186
|
-
}
|
|
187
|
-
return result;
|
|
188
|
-
}
|
|
189
|
-
async listTunnelPorts(tunnel, options) {
|
|
190
|
-
const results = (await this.sendTunnelRequest('GET', tunnel, readAccessTokenScopes, portsApiSubPath, undefined, options));
|
|
191
|
-
results.forEach(parseTunnelPortDates);
|
|
192
|
-
return results;
|
|
193
|
-
}
|
|
194
|
-
async getTunnelPort(tunnel, portNumber, options) {
|
|
195
|
-
const path = `${portsApiSubPath}/${portNumber}`;
|
|
196
|
-
const result = await this.sendTunnelRequest('GET', tunnel, readAccessTokenScopes, path, undefined, options);
|
|
197
|
-
parseTunnelPortDates(result);
|
|
198
|
-
return result;
|
|
199
|
-
}
|
|
200
|
-
async createTunnelPort(tunnel, tunnelPort, options) {
|
|
201
|
-
tunnelPort = this.convertTunnelPortForRequest(tunnel, tunnelPort);
|
|
202
|
-
const result = (await this.sendTunnelRequest('POST', tunnel, managePortsAccessTokenScopes, portsApiSubPath, undefined, options, tunnelPort));
|
|
203
|
-
if (tunnel.ports) {
|
|
204
|
-
// Also add the port to the local tunnel object.
|
|
205
|
-
tunnel.ports = tunnel.ports
|
|
206
|
-
.filter((p) => p.portNumber !== tunnelPort.portNumber)
|
|
207
|
-
.concat(result)
|
|
208
|
-
.sort(comparePorts);
|
|
209
|
-
}
|
|
210
|
-
parseTunnelPortDates(result);
|
|
211
|
-
return result;
|
|
212
|
-
}
|
|
213
|
-
async updateTunnelPort(tunnel, tunnelPort, options) {
|
|
214
|
-
if (tunnelPort.clusterId && tunnel.clusterId && tunnelPort.clusterId !== tunnel.clusterId) {
|
|
215
|
-
throw new Error('Tunnel port cluster ID is not consistent.');
|
|
216
|
-
}
|
|
217
|
-
const portNumber = tunnelPort.portNumber;
|
|
218
|
-
const path = `${portsApiSubPath}/${portNumber}`;
|
|
219
|
-
tunnelPort = this.convertTunnelPortForRequest(tunnel, tunnelPort);
|
|
220
|
-
const result = (await this.sendTunnelRequest('PUT', tunnel, managePortsAccessTokenScopes, path, undefined, options, tunnelPort));
|
|
221
|
-
if (tunnel.ports) {
|
|
222
|
-
// Also update the port in the local tunnel object.
|
|
223
|
-
tunnel.ports = tunnel.ports
|
|
224
|
-
.filter((p) => p.portNumber !== tunnelPort.portNumber)
|
|
225
|
-
.concat(result)
|
|
226
|
-
.sort(comparePorts);
|
|
227
|
-
}
|
|
228
|
-
if (!(options === null || options === void 0 ? void 0 : options.tokenScopes)) {
|
|
229
|
-
// If no new tokens were requested in the update, preserve any existing
|
|
230
|
-
// access tokens in the resulting port object.
|
|
231
|
-
result.accessTokens = tunnelPort.accessTokens;
|
|
232
|
-
}
|
|
233
|
-
parseTunnelPortDates(result);
|
|
234
|
-
return result;
|
|
235
|
-
}
|
|
236
|
-
async deleteTunnelPort(tunnel, portNumber, options) {
|
|
237
|
-
const path = `${portsApiSubPath}/${portNumber}`;
|
|
238
|
-
const result = await this.sendTunnelRequest('DELETE', tunnel, managePortsAccessTokenScopes, path, undefined, options, undefined, true);
|
|
239
|
-
if (result && tunnel.ports) {
|
|
240
|
-
// Also delete the port in the local tunnel object.
|
|
241
|
-
tunnel.ports = tunnel.ports
|
|
242
|
-
.filter((p) => p.portNumber !== portNumber)
|
|
243
|
-
.sort(comparePorts);
|
|
244
|
-
}
|
|
245
|
-
return result;
|
|
246
|
-
}
|
|
247
|
-
async listClusters() {
|
|
248
|
-
return (await this.sendRequest('GET', undefined, clustersApiPath, undefined, undefined, undefined, false));
|
|
249
|
-
}
|
|
250
|
-
/**
|
|
251
|
-
* Sends an HTTP request to the tunnel management API, targeting a specific tunnel.
|
|
252
|
-
* This protected method enables subclasses to support additional tunnel management APIs.
|
|
253
|
-
* @param method HTTP request method.
|
|
254
|
-
* @param tunnel Tunnel that the request is targeting.
|
|
255
|
-
* @param accessTokenScopes Required array of access scopes for tokens in `tunnel.accessTokens`
|
|
256
|
-
* that could be used to authorize the request.
|
|
257
|
-
* @param path Optional request sub-path relative to the tunnel.
|
|
258
|
-
* @param query Optional query string to append to the request.
|
|
259
|
-
* @param options Request options.
|
|
260
|
-
* @param body Optional request body object.
|
|
261
|
-
* @param allowNotFound If true, a 404 response is returned as a null or false result
|
|
262
|
-
* instead of an error.
|
|
263
|
-
* @returns Result of the request.
|
|
264
|
-
*/
|
|
265
|
-
async sendTunnelRequest(method, tunnel, accessTokenScopes, path, query, options, body, allowNotFound) {
|
|
266
|
-
const uri = this.buildUriForTunnel(tunnel, path, query, options);
|
|
267
|
-
const config = await this.getAxiosRequestConfig(tunnel, options, accessTokenScopes);
|
|
268
|
-
const result = await this.request(method, uri, body, config, allowNotFound);
|
|
269
|
-
return result;
|
|
270
|
-
}
|
|
271
|
-
/**
|
|
272
|
-
* Sends an HTTP request to the tunnel management API.
|
|
273
|
-
* This protected method enables subclasses to support additional tunnel management APIs.
|
|
274
|
-
* @param method HTTP request method.
|
|
275
|
-
* @param clusterId Optional tunnel service cluster ID to direct the request to. If unspecified,
|
|
276
|
-
* the request will use the global traffic-manager to find the nearest cluster.
|
|
277
|
-
* @param path Required request path.
|
|
278
|
-
* @param query Optional query string to append to the request.
|
|
279
|
-
* @param options Request options.
|
|
280
|
-
* @param body Optional request body object.
|
|
281
|
-
* @param allowNotFound If true, a 404 response is returned as a null or false result
|
|
282
|
-
* instead of an error.
|
|
283
|
-
* @returns Result of the request.
|
|
284
|
-
*/
|
|
285
|
-
async sendRequest(method, clusterId, path, query, options, body, allowNotFound) {
|
|
286
|
-
const uri = this.buildUri(clusterId, path, query, options);
|
|
287
|
-
const config = await this.getAxiosRequestConfig(undefined, options);
|
|
288
|
-
const result = await this.request(method, uri, body, config, allowNotFound);
|
|
289
|
-
return result;
|
|
290
|
-
}
|
|
291
|
-
getResponseErrorMessage(error) {
|
|
292
|
-
var _a, _b, _c, _d;
|
|
293
|
-
let errorMessage = '';
|
|
294
|
-
if ((_a = error.response) === null || _a === void 0 ? void 0 : _a.data) {
|
|
295
|
-
let problemDetails = error.response.data;
|
|
296
|
-
if (problemDetails.title || problemDetails.detail) {
|
|
297
|
-
errorMessage = `Tunnel service error: ${problemDetails.title}`;
|
|
298
|
-
if (problemDetails.detail) {
|
|
299
|
-
errorMessage += ' ' + problemDetails.detail;
|
|
300
|
-
}
|
|
301
|
-
if (problemDetails.errors) {
|
|
302
|
-
errorMessage += JSON.stringify(problemDetails.errors);
|
|
303
|
-
}
|
|
304
|
-
}
|
|
305
|
-
}
|
|
306
|
-
if (!errorMessage) {
|
|
307
|
-
if (error === null || error === void 0 ? void 0 : error.response) {
|
|
308
|
-
errorMessage =
|
|
309
|
-
'Tunnel service returned status code: ' +
|
|
310
|
-
`${error.response.status} ${error.response.statusText}`;
|
|
311
|
-
}
|
|
312
|
-
else {
|
|
313
|
-
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.';
|
|
314
|
-
}
|
|
315
|
-
}
|
|
316
|
-
const requestIdHeaderName = 'VsSaaS-Request-Id';
|
|
317
|
-
if (((_d = error.response) === null || _d === void 0 ? void 0 : _d.headers) && error.response.headers[requestIdHeaderName]) {
|
|
318
|
-
errorMessage += `\nRequest ID: ${error.response.headers[requestIdHeaderName]}`;
|
|
319
|
-
}
|
|
320
|
-
return errorMessage;
|
|
321
|
-
}
|
|
322
|
-
// Helper functions
|
|
323
|
-
buildUri(clusterId, path, query, options) {
|
|
324
|
-
let baseAddress = this.baseAddress;
|
|
325
|
-
if (clusterId) {
|
|
326
|
-
const url = new URL(baseAddress);
|
|
327
|
-
const portNumber = parseInt(url.port, 10);
|
|
328
|
-
if (url.hostname !== 'localhost' && !url.hostname.startsWith(`${clusterId}.`)) {
|
|
329
|
-
// A specific cluster ID was specified (while not running on localhost).
|
|
330
|
-
// Prepend the cluster ID to the hostname, and optionally strip a global prefix.
|
|
331
|
-
url.hostname = `${clusterId}.${url.hostname}`.replace('global.', '');
|
|
332
|
-
baseAddress = url.toString();
|
|
333
|
-
}
|
|
334
|
-
else if (url.protocol === 'https:' &&
|
|
335
|
-
clusterId.startsWith('localhost') &&
|
|
336
|
-
portNumber % 10 > 0) {
|
|
337
|
-
// Local testing simulates clusters by running the service on multiple ports.
|
|
338
|
-
// Change the port number to match the cluster ID suffix.
|
|
339
|
-
const clusterNumber = parseInt(clusterId.substring('localhost'.length), 10);
|
|
340
|
-
if (clusterNumber > 0 && clusterNumber < 10) {
|
|
341
|
-
url.port = (portNumber - (portNumber % 10) + clusterNumber).toString();
|
|
342
|
-
baseAddress = url.toString();
|
|
343
|
-
}
|
|
344
|
-
}
|
|
345
|
-
}
|
|
346
|
-
baseAddress = `${baseAddress.replace(/\/$/, '')}${path}`;
|
|
347
|
-
let optionsQuery = this.tunnelRequestOptionsToQueryString(options, query);
|
|
348
|
-
if (optionsQuery) {
|
|
349
|
-
baseAddress += `?${optionsQuery}`;
|
|
350
|
-
}
|
|
351
|
-
return baseAddress;
|
|
352
|
-
}
|
|
353
|
-
buildUriForTunnel(tunnel, path, query, options) {
|
|
354
|
-
let tunnelPath = '';
|
|
355
|
-
if (tunnel.clusterId && tunnel.tunnelId) {
|
|
356
|
-
tunnelPath = `${tunnelsApiPath}/${tunnel.tunnelId}`;
|
|
357
|
-
}
|
|
358
|
-
else {
|
|
359
|
-
if (!tunnel.name) {
|
|
360
|
-
throw new Error('Tunnel object must include either a name or tunnel ID and cluster ID.');
|
|
361
|
-
}
|
|
362
|
-
tunnelPath = `${tunnelsApiPath}/${tunnel.name}`;
|
|
363
|
-
}
|
|
364
|
-
if (options === null || options === void 0 ? void 0 : options.additionalQueryParameters) {
|
|
365
|
-
for (let [paramName, paramValue] of Object.entries(options.additionalQueryParameters)) {
|
|
366
|
-
if (query) {
|
|
367
|
-
query += `&${paramName}=${paramValue}`;
|
|
368
|
-
}
|
|
369
|
-
else {
|
|
370
|
-
query = `${paramName}=${paramValue}`;
|
|
371
|
-
}
|
|
372
|
-
}
|
|
373
|
-
}
|
|
374
|
-
return this.buildUri(tunnel.clusterId, tunnelPath + (path ? path : ''), query, options);
|
|
375
|
-
}
|
|
376
|
-
async getAxiosRequestConfig(tunnel, options, accessTokenScopes) {
|
|
377
|
-
// Get access token header
|
|
378
|
-
let headers = {};
|
|
379
|
-
if (options && options.accessToken) {
|
|
380
|
-
tunnelAccessTokenProperties_1.TunnelAccessTokenProperties.validateTokenExpiration(options.accessToken);
|
|
381
|
-
headers[tunnelAuthentication] = `${tunnelManagementClient_1.TunnelAuthenticationSchemes.tunnel} ${options.accessToken}`;
|
|
382
|
-
}
|
|
383
|
-
if (!(tunnelAuthentication in headers) && this.userTokenCallback) {
|
|
384
|
-
let token = await this.userTokenCallback();
|
|
385
|
-
if (token) {
|
|
386
|
-
headers[tunnelAuthentication] = token;
|
|
387
|
-
}
|
|
388
|
-
}
|
|
389
|
-
if (!(tunnelAuthentication in headers)) {
|
|
390
|
-
const accessToken = tunnelAccessTokenProperties_1.TunnelAccessTokenProperties.getTunnelAccessToken(tunnel, accessTokenScopes);
|
|
391
|
-
if (accessToken) {
|
|
392
|
-
headers[tunnelAuthentication] = `${tunnelManagementClient_1.TunnelAuthenticationSchemes.tunnel} ${accessToken}`;
|
|
393
|
-
}
|
|
394
|
-
}
|
|
395
|
-
const copyAdditionalHeaders = (additionalHeaders) => {
|
|
396
|
-
if (additionalHeaders) {
|
|
397
|
-
for (let [headerName, headerValue] of Object.entries(additionalHeaders)) {
|
|
398
|
-
headers[headerName] = headerValue;
|
|
399
|
-
}
|
|
400
|
-
}
|
|
401
|
-
};
|
|
402
|
-
copyAdditionalHeaders(this.additionalRequestHeaders);
|
|
403
|
-
copyAdditionalHeaders(options === null || options === void 0 ? void 0 : options.additionalHeaders);
|
|
404
|
-
const userAgentPrefix = headers['User-Agent'] ? headers['User-Agent'] + ' ' : '';
|
|
405
|
-
headers['User-Agent'] = `${userAgentPrefix}${this.userAgents} ${version_1.tunnelSdkUserAgent}`;
|
|
406
|
-
// Get axios config
|
|
407
|
-
let config = Object.assign({ headers }, (this.httpsAgent && { httpsAgent: this.httpsAgent }));
|
|
408
|
-
if ((options === null || options === void 0 ? void 0 : options.followRedirects) === false) {
|
|
409
|
-
config.maxRedirects = 0;
|
|
410
|
-
}
|
|
411
|
-
return config;
|
|
412
|
-
}
|
|
413
|
-
convertTunnelForRequest(tunnel) {
|
|
414
|
-
var _a;
|
|
415
|
-
const convertedTunnel = {
|
|
416
|
-
name: tunnel.name,
|
|
417
|
-
domain: tunnel.domain,
|
|
418
|
-
description: tunnel.description,
|
|
419
|
-
tags: tunnel.tags,
|
|
420
|
-
options: tunnel.options,
|
|
421
|
-
accessControl: !tunnel.accessControl
|
|
422
|
-
? undefined
|
|
423
|
-
: { entries: tunnel.accessControl.entries.filter((ace) => !ace.isInherited) },
|
|
424
|
-
endpoints: tunnel.endpoints,
|
|
425
|
-
ports: (_a = tunnel.ports) === null || _a === void 0 ? void 0 : _a.map((p) => this.convertTunnelPortForRequest(tunnel, p)),
|
|
426
|
-
};
|
|
427
|
-
return convertedTunnel;
|
|
428
|
-
}
|
|
429
|
-
convertTunnelPortForRequest(tunnel, tunnelPort) {
|
|
430
|
-
if (tunnelPort.clusterId && tunnel.clusterId && tunnelPort.clusterId !== tunnel.clusterId) {
|
|
431
|
-
throw new Error('Tunnel port cluster ID does not match tunnel.');
|
|
432
|
-
}
|
|
433
|
-
if (tunnelPort.tunnelId && tunnel.tunnelId && tunnelPort.tunnelId !== tunnel.tunnelId) {
|
|
434
|
-
throw new Error('Tunnel port tunnel ID does not match tunnel.');
|
|
435
|
-
}
|
|
436
|
-
return {
|
|
437
|
-
portNumber: tunnelPort.portNumber,
|
|
438
|
-
protocol: tunnelPort.protocol,
|
|
439
|
-
options: tunnelPort.options,
|
|
440
|
-
accessControl: !tunnelPort.accessControl
|
|
441
|
-
? undefined
|
|
442
|
-
: { entries: tunnelPort.accessControl.entries.filter((ace) => !ace.isInherited) },
|
|
443
|
-
};
|
|
444
|
-
}
|
|
445
|
-
tunnelRequestOptionsToQueryString(options, additionalQuery) {
|
|
446
|
-
let queryOptions = {};
|
|
447
|
-
const queryItems = [];
|
|
448
|
-
if (options) {
|
|
449
|
-
if (options.includePorts) {
|
|
450
|
-
queryOptions['includePorts'] = ['true'];
|
|
451
|
-
}
|
|
452
|
-
if (options.tokenScopes) {
|
|
453
|
-
dev_tunnels_contracts_1.TunnelAccessControl.validateScopes(options.tokenScopes, undefined, true);
|
|
454
|
-
queryOptions['tokenScopes'] = options.tokenScopes;
|
|
455
|
-
}
|
|
456
|
-
if (options.forceRename) {
|
|
457
|
-
queryOptions['forceRename'] = ['true'];
|
|
458
|
-
}
|
|
459
|
-
if (options.tags) {
|
|
460
|
-
queryOptions['tags'] = options.tags;
|
|
461
|
-
if (options.requireAllTags) {
|
|
462
|
-
queryOptions['allTags'] = ['true'];
|
|
463
|
-
}
|
|
464
|
-
}
|
|
465
|
-
queryItems.push(...Object.keys(queryOptions).map((key) => {
|
|
466
|
-
const value = queryOptions[key];
|
|
467
|
-
return `${key}=${value.map(encodeURIComponent).join(',')}`;
|
|
468
|
-
}));
|
|
469
|
-
}
|
|
470
|
-
if (additionalQuery) {
|
|
471
|
-
queryItems.push(additionalQuery);
|
|
472
|
-
}
|
|
473
|
-
const queryString = queryItems.join('&');
|
|
474
|
-
return queryString;
|
|
475
|
-
}
|
|
476
|
-
/**
|
|
477
|
-
* Makes an HTTP request using Axios, while tracing request and response details.
|
|
478
|
-
*/
|
|
479
|
-
async request(method, uri, data, config, allowNotFound) {
|
|
480
|
-
this.trace(`${method} ${uri}`);
|
|
481
|
-
this.traceHeaders(config.headers);
|
|
482
|
-
this.traceContent(data);
|
|
483
|
-
const traceResponse = (response) => {
|
|
484
|
-
this.trace(`${response.status} ${response.statusText}`);
|
|
485
|
-
this.traceHeaders(response.headers);
|
|
486
|
-
this.traceContent(response.data);
|
|
487
|
-
};
|
|
488
|
-
try {
|
|
489
|
-
config.url = uri;
|
|
490
|
-
config.method = method;
|
|
491
|
-
config.data = data;
|
|
492
|
-
let response = await axios_1.default.request(config);
|
|
493
|
-
traceResponse(response);
|
|
494
|
-
// This assumes that TResult is always boolean for DELETE requests.
|
|
495
|
-
return (method === 'DELETE' ? true : response.data);
|
|
496
|
-
}
|
|
497
|
-
catch (e) {
|
|
498
|
-
if (!(e instanceof Error) || !e.isAxiosError)
|
|
499
|
-
throw e;
|
|
500
|
-
const requestError = e;
|
|
501
|
-
if (requestError.response) {
|
|
502
|
-
traceResponse(requestError.response);
|
|
503
|
-
if (allowNotFound && requestError.response.status === 404) {
|
|
504
|
-
return (method === 'DELETE' ? false : null);
|
|
505
|
-
}
|
|
506
|
-
}
|
|
507
|
-
requestError.message = this.getResponseErrorMessage(requestError);
|
|
508
|
-
// Axios errors have too much redundant detail! Delete some of it.
|
|
509
|
-
delete requestError.request;
|
|
510
|
-
if (requestError.response) {
|
|
511
|
-
delete requestError.config.httpAgent;
|
|
512
|
-
delete requestError.config.httpsAgent;
|
|
513
|
-
delete requestError.response.request;
|
|
514
|
-
}
|
|
515
|
-
throw requestError;
|
|
516
|
-
}
|
|
517
|
-
}
|
|
518
|
-
traceHeaders(headers) {
|
|
519
|
-
for (let [headerName, headerValue] of Object.entries(headers)) {
|
|
520
|
-
if (headerName === 'Authorization') {
|
|
521
|
-
this.traceAuthorizationHeader(headerName, headerValue);
|
|
522
|
-
return;
|
|
523
|
-
}
|
|
524
|
-
this.trace(`${headerName}: ${headerValue !== null && headerValue !== void 0 ? headerValue : ''}`);
|
|
525
|
-
}
|
|
526
|
-
}
|
|
527
|
-
traceAuthorizationHeader(key, value) {
|
|
528
|
-
if (typeof value !== 'string')
|
|
529
|
-
return;
|
|
530
|
-
const spaceIndex = value.indexOf(' ');
|
|
531
|
-
if (spaceIndex < 0) {
|
|
532
|
-
this.trace(`${key}: [${value.length}]`);
|
|
533
|
-
return;
|
|
534
|
-
}
|
|
535
|
-
const scheme = value.substring(0, spaceIndex);
|
|
536
|
-
const token = value.substring(spaceIndex + 1);
|
|
537
|
-
if (scheme.toLowerCase() === tunnelManagementClient_1.TunnelAuthenticationSchemes.tunnel.toLowerCase()) {
|
|
538
|
-
const tokenProperties = tunnelAccessTokenProperties_1.TunnelAccessTokenProperties.tryParse(token);
|
|
539
|
-
if (tokenProperties) {
|
|
540
|
-
this.trace(`${key}: ${scheme} <${tokenProperties}>`);
|
|
541
|
-
return;
|
|
542
|
-
}
|
|
543
|
-
}
|
|
544
|
-
this.trace(`${key}: ${scheme} <token>`);
|
|
545
|
-
}
|
|
546
|
-
traceContent(data) {
|
|
547
|
-
if (typeof data === 'object') {
|
|
548
|
-
data = JSON.stringify(data, undefined, ' ');
|
|
549
|
-
}
|
|
550
|
-
if (typeof data === 'string') {
|
|
551
|
-
this.trace(TunnelManagementHttpClient.replaceTokensInContent(data));
|
|
552
|
-
}
|
|
553
|
-
}
|
|
554
|
-
static replaceTokensInContent(content) {
|
|
555
|
-
var _a;
|
|
556
|
-
const tokenRegex = /"(eyJ[a-zA-z0-9\-_]+\.[a-zA-z0-9\-_]+\.[a-zA-z0-9\-_]+)"/;
|
|
557
|
-
let match = tokenRegex.exec(content);
|
|
558
|
-
while (match) {
|
|
559
|
-
let token = match[1];
|
|
560
|
-
const tokenProperties = tunnelAccessTokenProperties_1.TunnelAccessTokenProperties.tryParse(token);
|
|
561
|
-
token = (_a = tokenProperties === null || tokenProperties === void 0 ? void 0 : tokenProperties.toString()) !== null && _a !== void 0 ? _a : 'token';
|
|
562
|
-
content =
|
|
563
|
-
content.substring(0, match.index + 1) +
|
|
564
|
-
'<' +
|
|
565
|
-
token +
|
|
566
|
-
'>' +
|
|
567
|
-
content.substring(match.index + match[0].length - 1);
|
|
568
|
-
match = tokenRegex.exec(content);
|
|
569
|
-
}
|
|
570
|
-
return content;
|
|
571
|
-
}
|
|
572
|
-
}
|
|
573
|
-
exports.TunnelManagementHttpClient = TunnelManagementHttpClient;
|
|
1
|
+
"use strict";
|
|
2
|
+
// Copyright (c) Microsoft Corporation.
|
|
3
|
+
// Licensed under the MIT license.
|
|
4
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
5
|
+
exports.TunnelManagementHttpClient = void 0;
|
|
6
|
+
const dev_tunnels_contracts_1 = require("@microsoft/dev-tunnels-contracts");
|
|
7
|
+
const tunnelManagementClient_1 = require("./tunnelManagementClient");
|
|
8
|
+
const tunnelAccessTokenProperties_1 = require("./tunnelAccessTokenProperties");
|
|
9
|
+
const version_1 = require("./version");
|
|
10
|
+
const axios_1 = require("axios");
|
|
11
|
+
const apiV1Path = `/api/v1`;
|
|
12
|
+
const tunnelsApiPath = apiV1Path + '/tunnels';
|
|
13
|
+
const endpointsApiSubPath = '/endpoints';
|
|
14
|
+
const portsApiSubPath = '/ports';
|
|
15
|
+
const clustersApiPath = apiV1Path + '/clusters';
|
|
16
|
+
const tunnelAuthentication = 'Authorization';
|
|
17
|
+
function comparePorts(a, b) {
|
|
18
|
+
var _a, _b;
|
|
19
|
+
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);
|
|
20
|
+
}
|
|
21
|
+
function parseDate(value) {
|
|
22
|
+
return typeof value === 'string' ? new Date(Date.parse(value)) : value;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Fixes Tunnel properties of type Date that were deserialized as strings.
|
|
26
|
+
*/
|
|
27
|
+
function parseTunnelDates(tunnel) {
|
|
28
|
+
if (!tunnel)
|
|
29
|
+
return;
|
|
30
|
+
tunnel.created = parseDate(tunnel.created);
|
|
31
|
+
if (tunnel.status) {
|
|
32
|
+
tunnel.status.lastHostConnectionTime = parseDate(tunnel.status.lastHostConnectionTime);
|
|
33
|
+
tunnel.status.lastClientConnectionTime = parseDate(tunnel.status.lastClientConnectionTime);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Fixes TunnelPort properties of type Date that were deserialized as strings.
|
|
38
|
+
*/
|
|
39
|
+
function parseTunnelPortDates(port) {
|
|
40
|
+
if (!port)
|
|
41
|
+
return;
|
|
42
|
+
if (port.status) {
|
|
43
|
+
port.status.lastClientConnectionTime = parseDate(port.status.lastClientConnectionTime);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
const manageAccessTokenScope = [dev_tunnels_contracts_1.TunnelAccessScopes.Manage];
|
|
47
|
+
const hostAccessTokenScope = [dev_tunnels_contracts_1.TunnelAccessScopes.Host];
|
|
48
|
+
const managePortsAccessTokenScopes = [
|
|
49
|
+
dev_tunnels_contracts_1.TunnelAccessScopes.Manage,
|
|
50
|
+
dev_tunnels_contracts_1.TunnelAccessScopes.ManagePorts,
|
|
51
|
+
dev_tunnels_contracts_1.TunnelAccessScopes.Host,
|
|
52
|
+
];
|
|
53
|
+
const readAccessTokenScopes = [
|
|
54
|
+
dev_tunnels_contracts_1.TunnelAccessScopes.Manage,
|
|
55
|
+
dev_tunnels_contracts_1.TunnelAccessScopes.ManagePorts,
|
|
56
|
+
dev_tunnels_contracts_1.TunnelAccessScopes.Host,
|
|
57
|
+
dev_tunnels_contracts_1.TunnelAccessScopes.Connect,
|
|
58
|
+
];
|
|
59
|
+
class TunnelManagementHttpClient {
|
|
60
|
+
/**
|
|
61
|
+
* Initializes a new instance of the `TunnelManagementHttpClient` class
|
|
62
|
+
* with a client authentication callback, service URI, and HTTP handler.
|
|
63
|
+
*
|
|
64
|
+
* @param userAgent { name, version } object or a comment string to use as the User-Agent header.
|
|
65
|
+
* @param userTokenCallback Optional async callback for retrieving a client authentication
|
|
66
|
+
* header value with access token, for AAD or GitHub user authentication. This may be omitted
|
|
67
|
+
* for anonymous tunnel clients, or if tunnel access tokens will be specified via
|
|
68
|
+
* `TunnelRequestOptions.accessToken`.
|
|
69
|
+
* @param tunnelServiceUri Optional tunnel service URI (not including any path). Defaults to
|
|
70
|
+
* the global tunnel service URI.
|
|
71
|
+
* @param httpsAgent Optional agent that will be invoked for HTTPS requests to the tunnel
|
|
72
|
+
* service.
|
|
73
|
+
*/
|
|
74
|
+
constructor(userAgents, userTokenCallback, tunnelServiceUri, httpsAgent) {
|
|
75
|
+
var _a;
|
|
76
|
+
this.httpsAgent = httpsAgent;
|
|
77
|
+
this.trace = (msg) => { };
|
|
78
|
+
if (!userAgents) {
|
|
79
|
+
throw new TypeError('User agent must be provided.');
|
|
80
|
+
}
|
|
81
|
+
if (Array.isArray(userAgents)) {
|
|
82
|
+
if (userAgents.length === 0) {
|
|
83
|
+
throw new TypeError('User agents cannot be empty.');
|
|
84
|
+
}
|
|
85
|
+
let combinedUserAgents = '';
|
|
86
|
+
userAgents.forEach((userAgent) => {
|
|
87
|
+
var _a;
|
|
88
|
+
if (typeof userAgent !== 'string') {
|
|
89
|
+
if (!userAgent.name) {
|
|
90
|
+
throw new TypeError('Invalid user agent. The name must be provided.');
|
|
91
|
+
}
|
|
92
|
+
if (typeof userAgent.name !== 'string') {
|
|
93
|
+
throw new TypeError('Invalid user agent. The name must be a string.');
|
|
94
|
+
}
|
|
95
|
+
if (userAgent.version && typeof userAgent.version !== 'string') {
|
|
96
|
+
throw new TypeError('Invalid user agent. The version must be a string.');
|
|
97
|
+
}
|
|
98
|
+
combinedUserAgents = `${combinedUserAgents}${userAgent.name}/${(_a = userAgent.version) !== null && _a !== void 0 ? _a : 'unknown'} `;
|
|
99
|
+
}
|
|
100
|
+
else {
|
|
101
|
+
combinedUserAgents = `${combinedUserAgents}${userAgent} `;
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
this.userAgents = combinedUserAgents.trim();
|
|
105
|
+
}
|
|
106
|
+
else if (typeof userAgents !== 'string') {
|
|
107
|
+
if (!userAgents.name) {
|
|
108
|
+
throw new TypeError('Invalid user agent. The name must be provided.');
|
|
109
|
+
}
|
|
110
|
+
if (typeof userAgents.name !== 'string') {
|
|
111
|
+
throw new TypeError('Invalid user agent. The name must be a string.');
|
|
112
|
+
}
|
|
113
|
+
if (userAgents.version && typeof userAgents.version !== 'string') {
|
|
114
|
+
throw new TypeError('Invalid user agent. The version must be a string.');
|
|
115
|
+
}
|
|
116
|
+
this.userAgents = `${userAgents.name}/${(_a = userAgents.version) !== null && _a !== void 0 ? _a : 'unknown'}`;
|
|
117
|
+
}
|
|
118
|
+
else {
|
|
119
|
+
this.userAgents = userAgents;
|
|
120
|
+
}
|
|
121
|
+
this.userTokenCallback = userTokenCallback !== null && userTokenCallback !== void 0 ? userTokenCallback : (() => Promise.resolve(null));
|
|
122
|
+
if (!tunnelServiceUri) {
|
|
123
|
+
tunnelServiceUri = dev_tunnels_contracts_1.TunnelServiceProperties.production.serviceUri;
|
|
124
|
+
}
|
|
125
|
+
const parsedUri = new URL(tunnelServiceUri);
|
|
126
|
+
if (!parsedUri || parsedUri.pathname !== '/') {
|
|
127
|
+
throw new TypeError(`Invalid tunnel service URI: ${tunnelServiceUri}`);
|
|
128
|
+
}
|
|
129
|
+
this.baseAddress = tunnelServiceUri;
|
|
130
|
+
}
|
|
131
|
+
async listTunnels(clusterId, domain, options) {
|
|
132
|
+
const queryParams = [clusterId ? null : 'global=true', domain ? `domain=${domain}` : null];
|
|
133
|
+
const query = queryParams.filter((p) => !!p).join('&');
|
|
134
|
+
const results = (await this.sendRequest('GET', clusterId, tunnelsApiPath, query, options));
|
|
135
|
+
results.forEach(parseTunnelDates);
|
|
136
|
+
return results;
|
|
137
|
+
}
|
|
138
|
+
async getTunnel(tunnel, options) {
|
|
139
|
+
const result = await this.sendTunnelRequest('GET', tunnel, readAccessTokenScopes, undefined, undefined, options);
|
|
140
|
+
parseTunnelDates(result);
|
|
141
|
+
return result;
|
|
142
|
+
}
|
|
143
|
+
async createTunnel(tunnel, options) {
|
|
144
|
+
let tunnelId = tunnel.tunnelId;
|
|
145
|
+
if (tunnelId) {
|
|
146
|
+
throw new Error('An ID may not be specified when creating a tunnel.');
|
|
147
|
+
}
|
|
148
|
+
tunnel = this.convertTunnelForRequest(tunnel);
|
|
149
|
+
const result = (await this.sendRequest('POST', tunnel.clusterId, tunnelsApiPath, undefined, options, tunnel));
|
|
150
|
+
parseTunnelDates(result);
|
|
151
|
+
return result;
|
|
152
|
+
}
|
|
153
|
+
async updateTunnel(tunnel, options) {
|
|
154
|
+
const result = (await this.sendTunnelRequest('PUT', tunnel, manageAccessTokenScope, undefined, undefined, options, this.convertTunnelForRequest(tunnel)));
|
|
155
|
+
if (!(options === null || options === void 0 ? void 0 : options.tokenScopes)) {
|
|
156
|
+
// If no new tokens were requested in the update, preserve any existing
|
|
157
|
+
// access tokens in the resulting tunnel object.
|
|
158
|
+
result.accessTokens = tunnel.accessTokens;
|
|
159
|
+
}
|
|
160
|
+
parseTunnelDates(result);
|
|
161
|
+
return result;
|
|
162
|
+
}
|
|
163
|
+
async deleteTunnel(tunnel, options) {
|
|
164
|
+
return await this.sendTunnelRequest('DELETE', tunnel, manageAccessTokenScope, undefined, undefined, options, undefined, true);
|
|
165
|
+
}
|
|
166
|
+
async updateTunnelEndpoint(tunnel, endpoint, options) {
|
|
167
|
+
const path = `${endpointsApiSubPath}/${endpoint.hostId}/${endpoint.connectionMode}`;
|
|
168
|
+
const result = (await this.sendTunnelRequest('PUT', tunnel, hostAccessTokenScope, path, undefined, options, endpoint));
|
|
169
|
+
if (tunnel.endpoints) {
|
|
170
|
+
// Also update the endpoint in the local tunnel object.
|
|
171
|
+
tunnel.endpoints = tunnel.endpoints
|
|
172
|
+
.filter((e) => e.hostId !== endpoint.hostId ||
|
|
173
|
+
e.connectionMode !== endpoint.connectionMode)
|
|
174
|
+
.concat(result);
|
|
175
|
+
}
|
|
176
|
+
return result;
|
|
177
|
+
}
|
|
178
|
+
async deleteTunnelEndpoints(tunnel, hostId, connectionMode, options) {
|
|
179
|
+
const path = connectionMode == null
|
|
180
|
+
? `${endpointsApiSubPath}/${hostId}`
|
|
181
|
+
: `${endpointsApiSubPath}/${hostId}/${connectionMode}`;
|
|
182
|
+
const result = await this.sendTunnelRequest('DELETE', tunnel, hostAccessTokenScope, path, undefined, options, undefined, true);
|
|
183
|
+
if (result && tunnel.endpoints) {
|
|
184
|
+
// Also delete the endpoint in the local tunnel object.
|
|
185
|
+
tunnel.endpoints = tunnel.endpoints.filter((e) => e.hostId !== hostId || e.connectionMode !== connectionMode);
|
|
186
|
+
}
|
|
187
|
+
return result;
|
|
188
|
+
}
|
|
189
|
+
async listTunnelPorts(tunnel, options) {
|
|
190
|
+
const results = (await this.sendTunnelRequest('GET', tunnel, readAccessTokenScopes, portsApiSubPath, undefined, options));
|
|
191
|
+
results.forEach(parseTunnelPortDates);
|
|
192
|
+
return results;
|
|
193
|
+
}
|
|
194
|
+
async getTunnelPort(tunnel, portNumber, options) {
|
|
195
|
+
const path = `${portsApiSubPath}/${portNumber}`;
|
|
196
|
+
const result = await this.sendTunnelRequest('GET', tunnel, readAccessTokenScopes, path, undefined, options);
|
|
197
|
+
parseTunnelPortDates(result);
|
|
198
|
+
return result;
|
|
199
|
+
}
|
|
200
|
+
async createTunnelPort(tunnel, tunnelPort, options) {
|
|
201
|
+
tunnelPort = this.convertTunnelPortForRequest(tunnel, tunnelPort);
|
|
202
|
+
const result = (await this.sendTunnelRequest('POST', tunnel, managePortsAccessTokenScopes, portsApiSubPath, undefined, options, tunnelPort));
|
|
203
|
+
if (tunnel.ports) {
|
|
204
|
+
// Also add the port to the local tunnel object.
|
|
205
|
+
tunnel.ports = tunnel.ports
|
|
206
|
+
.filter((p) => p.portNumber !== tunnelPort.portNumber)
|
|
207
|
+
.concat(result)
|
|
208
|
+
.sort(comparePorts);
|
|
209
|
+
}
|
|
210
|
+
parseTunnelPortDates(result);
|
|
211
|
+
return result;
|
|
212
|
+
}
|
|
213
|
+
async updateTunnelPort(tunnel, tunnelPort, options) {
|
|
214
|
+
if (tunnelPort.clusterId && tunnel.clusterId && tunnelPort.clusterId !== tunnel.clusterId) {
|
|
215
|
+
throw new Error('Tunnel port cluster ID is not consistent.');
|
|
216
|
+
}
|
|
217
|
+
const portNumber = tunnelPort.portNumber;
|
|
218
|
+
const path = `${portsApiSubPath}/${portNumber}`;
|
|
219
|
+
tunnelPort = this.convertTunnelPortForRequest(tunnel, tunnelPort);
|
|
220
|
+
const result = (await this.sendTunnelRequest('PUT', tunnel, managePortsAccessTokenScopes, path, undefined, options, tunnelPort));
|
|
221
|
+
if (tunnel.ports) {
|
|
222
|
+
// Also update the port in the local tunnel object.
|
|
223
|
+
tunnel.ports = tunnel.ports
|
|
224
|
+
.filter((p) => p.portNumber !== tunnelPort.portNumber)
|
|
225
|
+
.concat(result)
|
|
226
|
+
.sort(comparePorts);
|
|
227
|
+
}
|
|
228
|
+
if (!(options === null || options === void 0 ? void 0 : options.tokenScopes)) {
|
|
229
|
+
// If no new tokens were requested in the update, preserve any existing
|
|
230
|
+
// access tokens in the resulting port object.
|
|
231
|
+
result.accessTokens = tunnelPort.accessTokens;
|
|
232
|
+
}
|
|
233
|
+
parseTunnelPortDates(result);
|
|
234
|
+
return result;
|
|
235
|
+
}
|
|
236
|
+
async deleteTunnelPort(tunnel, portNumber, options) {
|
|
237
|
+
const path = `${portsApiSubPath}/${portNumber}`;
|
|
238
|
+
const result = await this.sendTunnelRequest('DELETE', tunnel, managePortsAccessTokenScopes, path, undefined, options, undefined, true);
|
|
239
|
+
if (result && tunnel.ports) {
|
|
240
|
+
// Also delete the port in the local tunnel object.
|
|
241
|
+
tunnel.ports = tunnel.ports
|
|
242
|
+
.filter((p) => p.portNumber !== portNumber)
|
|
243
|
+
.sort(comparePorts);
|
|
244
|
+
}
|
|
245
|
+
return result;
|
|
246
|
+
}
|
|
247
|
+
async listClusters() {
|
|
248
|
+
return (await this.sendRequest('GET', undefined, clustersApiPath, undefined, undefined, undefined, false));
|
|
249
|
+
}
|
|
250
|
+
/**
|
|
251
|
+
* Sends an HTTP request to the tunnel management API, targeting a specific tunnel.
|
|
252
|
+
* This protected method enables subclasses to support additional tunnel management APIs.
|
|
253
|
+
* @param method HTTP request method.
|
|
254
|
+
* @param tunnel Tunnel that the request is targeting.
|
|
255
|
+
* @param accessTokenScopes Required array of access scopes for tokens in `tunnel.accessTokens`
|
|
256
|
+
* that could be used to authorize the request.
|
|
257
|
+
* @param path Optional request sub-path relative to the tunnel.
|
|
258
|
+
* @param query Optional query string to append to the request.
|
|
259
|
+
* @param options Request options.
|
|
260
|
+
* @param body Optional request body object.
|
|
261
|
+
* @param allowNotFound If true, a 404 response is returned as a null or false result
|
|
262
|
+
* instead of an error.
|
|
263
|
+
* @returns Result of the request.
|
|
264
|
+
*/
|
|
265
|
+
async sendTunnelRequest(method, tunnel, accessTokenScopes, path, query, options, body, allowNotFound) {
|
|
266
|
+
const uri = this.buildUriForTunnel(tunnel, path, query, options);
|
|
267
|
+
const config = await this.getAxiosRequestConfig(tunnel, options, accessTokenScopes);
|
|
268
|
+
const result = await this.request(method, uri, body, config, allowNotFound);
|
|
269
|
+
return result;
|
|
270
|
+
}
|
|
271
|
+
/**
|
|
272
|
+
* Sends an HTTP request to the tunnel management API.
|
|
273
|
+
* This protected method enables subclasses to support additional tunnel management APIs.
|
|
274
|
+
* @param method HTTP request method.
|
|
275
|
+
* @param clusterId Optional tunnel service cluster ID to direct the request to. If unspecified,
|
|
276
|
+
* the request will use the global traffic-manager to find the nearest cluster.
|
|
277
|
+
* @param path Required request path.
|
|
278
|
+
* @param query Optional query string to append to the request.
|
|
279
|
+
* @param options Request options.
|
|
280
|
+
* @param body Optional request body object.
|
|
281
|
+
* @param allowNotFound If true, a 404 response is returned as a null or false result
|
|
282
|
+
* instead of an error.
|
|
283
|
+
* @returns Result of the request.
|
|
284
|
+
*/
|
|
285
|
+
async sendRequest(method, clusterId, path, query, options, body, allowNotFound) {
|
|
286
|
+
const uri = this.buildUri(clusterId, path, query, options);
|
|
287
|
+
const config = await this.getAxiosRequestConfig(undefined, options);
|
|
288
|
+
const result = await this.request(method, uri, body, config, allowNotFound);
|
|
289
|
+
return result;
|
|
290
|
+
}
|
|
291
|
+
getResponseErrorMessage(error) {
|
|
292
|
+
var _a, _b, _c, _d;
|
|
293
|
+
let errorMessage = '';
|
|
294
|
+
if ((_a = error.response) === null || _a === void 0 ? void 0 : _a.data) {
|
|
295
|
+
let problemDetails = error.response.data;
|
|
296
|
+
if (problemDetails.title || problemDetails.detail) {
|
|
297
|
+
errorMessage = `Tunnel service error: ${problemDetails.title}`;
|
|
298
|
+
if (problemDetails.detail) {
|
|
299
|
+
errorMessage += ' ' + problemDetails.detail;
|
|
300
|
+
}
|
|
301
|
+
if (problemDetails.errors) {
|
|
302
|
+
errorMessage += JSON.stringify(problemDetails.errors);
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
if (!errorMessage) {
|
|
307
|
+
if (error === null || error === void 0 ? void 0 : error.response) {
|
|
308
|
+
errorMessage =
|
|
309
|
+
'Tunnel service returned status code: ' +
|
|
310
|
+
`${error.response.status} ${error.response.statusText}`;
|
|
311
|
+
}
|
|
312
|
+
else {
|
|
313
|
+
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.';
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
const requestIdHeaderName = 'VsSaaS-Request-Id';
|
|
317
|
+
if (((_d = error.response) === null || _d === void 0 ? void 0 : _d.headers) && error.response.headers[requestIdHeaderName]) {
|
|
318
|
+
errorMessage += `\nRequest ID: ${error.response.headers[requestIdHeaderName]}`;
|
|
319
|
+
}
|
|
320
|
+
return errorMessage;
|
|
321
|
+
}
|
|
322
|
+
// Helper functions
|
|
323
|
+
buildUri(clusterId, path, query, options) {
|
|
324
|
+
let baseAddress = this.baseAddress;
|
|
325
|
+
if (clusterId) {
|
|
326
|
+
const url = new URL(baseAddress);
|
|
327
|
+
const portNumber = parseInt(url.port, 10);
|
|
328
|
+
if (url.hostname !== 'localhost' && !url.hostname.startsWith(`${clusterId}.`)) {
|
|
329
|
+
// A specific cluster ID was specified (while not running on localhost).
|
|
330
|
+
// Prepend the cluster ID to the hostname, and optionally strip a global prefix.
|
|
331
|
+
url.hostname = `${clusterId}.${url.hostname}`.replace('global.', '');
|
|
332
|
+
baseAddress = url.toString();
|
|
333
|
+
}
|
|
334
|
+
else if (url.protocol === 'https:' &&
|
|
335
|
+
clusterId.startsWith('localhost') &&
|
|
336
|
+
portNumber % 10 > 0) {
|
|
337
|
+
// Local testing simulates clusters by running the service on multiple ports.
|
|
338
|
+
// Change the port number to match the cluster ID suffix.
|
|
339
|
+
const clusterNumber = parseInt(clusterId.substring('localhost'.length), 10);
|
|
340
|
+
if (clusterNumber > 0 && clusterNumber < 10) {
|
|
341
|
+
url.port = (portNumber - (portNumber % 10) + clusterNumber).toString();
|
|
342
|
+
baseAddress = url.toString();
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
baseAddress = `${baseAddress.replace(/\/$/, '')}${path}`;
|
|
347
|
+
let optionsQuery = this.tunnelRequestOptionsToQueryString(options, query);
|
|
348
|
+
if (optionsQuery) {
|
|
349
|
+
baseAddress += `?${optionsQuery}`;
|
|
350
|
+
}
|
|
351
|
+
return baseAddress;
|
|
352
|
+
}
|
|
353
|
+
buildUriForTunnel(tunnel, path, query, options) {
|
|
354
|
+
let tunnelPath = '';
|
|
355
|
+
if (tunnel.clusterId && tunnel.tunnelId) {
|
|
356
|
+
tunnelPath = `${tunnelsApiPath}/${tunnel.tunnelId}`;
|
|
357
|
+
}
|
|
358
|
+
else {
|
|
359
|
+
if (!tunnel.name) {
|
|
360
|
+
throw new Error('Tunnel object must include either a name or tunnel ID and cluster ID.');
|
|
361
|
+
}
|
|
362
|
+
tunnelPath = `${tunnelsApiPath}/${tunnel.name}`;
|
|
363
|
+
}
|
|
364
|
+
if (options === null || options === void 0 ? void 0 : options.additionalQueryParameters) {
|
|
365
|
+
for (let [paramName, paramValue] of Object.entries(options.additionalQueryParameters)) {
|
|
366
|
+
if (query) {
|
|
367
|
+
query += `&${paramName}=${paramValue}`;
|
|
368
|
+
}
|
|
369
|
+
else {
|
|
370
|
+
query = `${paramName}=${paramValue}`;
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
return this.buildUri(tunnel.clusterId, tunnelPath + (path ? path : ''), query, options);
|
|
375
|
+
}
|
|
376
|
+
async getAxiosRequestConfig(tunnel, options, accessTokenScopes) {
|
|
377
|
+
// Get access token header
|
|
378
|
+
let headers = {};
|
|
379
|
+
if (options && options.accessToken) {
|
|
380
|
+
tunnelAccessTokenProperties_1.TunnelAccessTokenProperties.validateTokenExpiration(options.accessToken);
|
|
381
|
+
headers[tunnelAuthentication] = `${tunnelManagementClient_1.TunnelAuthenticationSchemes.tunnel} ${options.accessToken}`;
|
|
382
|
+
}
|
|
383
|
+
if (!(tunnelAuthentication in headers) && this.userTokenCallback) {
|
|
384
|
+
let token = await this.userTokenCallback();
|
|
385
|
+
if (token) {
|
|
386
|
+
headers[tunnelAuthentication] = token;
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
if (!(tunnelAuthentication in headers)) {
|
|
390
|
+
const accessToken = tunnelAccessTokenProperties_1.TunnelAccessTokenProperties.getTunnelAccessToken(tunnel, accessTokenScopes);
|
|
391
|
+
if (accessToken) {
|
|
392
|
+
headers[tunnelAuthentication] = `${tunnelManagementClient_1.TunnelAuthenticationSchemes.tunnel} ${accessToken}`;
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
const copyAdditionalHeaders = (additionalHeaders) => {
|
|
396
|
+
if (additionalHeaders) {
|
|
397
|
+
for (let [headerName, headerValue] of Object.entries(additionalHeaders)) {
|
|
398
|
+
headers[headerName] = headerValue;
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
};
|
|
402
|
+
copyAdditionalHeaders(this.additionalRequestHeaders);
|
|
403
|
+
copyAdditionalHeaders(options === null || options === void 0 ? void 0 : options.additionalHeaders);
|
|
404
|
+
const userAgentPrefix = headers['User-Agent'] ? headers['User-Agent'] + ' ' : '';
|
|
405
|
+
headers['User-Agent'] = `${userAgentPrefix}${this.userAgents} ${version_1.tunnelSdkUserAgent}`;
|
|
406
|
+
// Get axios config
|
|
407
|
+
let config = Object.assign({ headers }, (this.httpsAgent && { httpsAgent: this.httpsAgent }));
|
|
408
|
+
if ((options === null || options === void 0 ? void 0 : options.followRedirects) === false) {
|
|
409
|
+
config.maxRedirects = 0;
|
|
410
|
+
}
|
|
411
|
+
return config;
|
|
412
|
+
}
|
|
413
|
+
convertTunnelForRequest(tunnel) {
|
|
414
|
+
var _a;
|
|
415
|
+
const convertedTunnel = {
|
|
416
|
+
name: tunnel.name,
|
|
417
|
+
domain: tunnel.domain,
|
|
418
|
+
description: tunnel.description,
|
|
419
|
+
tags: tunnel.tags,
|
|
420
|
+
options: tunnel.options,
|
|
421
|
+
accessControl: !tunnel.accessControl
|
|
422
|
+
? undefined
|
|
423
|
+
: { entries: tunnel.accessControl.entries.filter((ace) => !ace.isInherited) },
|
|
424
|
+
endpoints: tunnel.endpoints,
|
|
425
|
+
ports: (_a = tunnel.ports) === null || _a === void 0 ? void 0 : _a.map((p) => this.convertTunnelPortForRequest(tunnel, p)),
|
|
426
|
+
};
|
|
427
|
+
return convertedTunnel;
|
|
428
|
+
}
|
|
429
|
+
convertTunnelPortForRequest(tunnel, tunnelPort) {
|
|
430
|
+
if (tunnelPort.clusterId && tunnel.clusterId && tunnelPort.clusterId !== tunnel.clusterId) {
|
|
431
|
+
throw new Error('Tunnel port cluster ID does not match tunnel.');
|
|
432
|
+
}
|
|
433
|
+
if (tunnelPort.tunnelId && tunnel.tunnelId && tunnelPort.tunnelId !== tunnel.tunnelId) {
|
|
434
|
+
throw new Error('Tunnel port tunnel ID does not match tunnel.');
|
|
435
|
+
}
|
|
436
|
+
return {
|
|
437
|
+
portNumber: tunnelPort.portNumber,
|
|
438
|
+
protocol: tunnelPort.protocol,
|
|
439
|
+
options: tunnelPort.options,
|
|
440
|
+
accessControl: !tunnelPort.accessControl
|
|
441
|
+
? undefined
|
|
442
|
+
: { entries: tunnelPort.accessControl.entries.filter((ace) => !ace.isInherited) },
|
|
443
|
+
};
|
|
444
|
+
}
|
|
445
|
+
tunnelRequestOptionsToQueryString(options, additionalQuery) {
|
|
446
|
+
let queryOptions = {};
|
|
447
|
+
const queryItems = [];
|
|
448
|
+
if (options) {
|
|
449
|
+
if (options.includePorts) {
|
|
450
|
+
queryOptions['includePorts'] = ['true'];
|
|
451
|
+
}
|
|
452
|
+
if (options.tokenScopes) {
|
|
453
|
+
dev_tunnels_contracts_1.TunnelAccessControl.validateScopes(options.tokenScopes, undefined, true);
|
|
454
|
+
queryOptions['tokenScopes'] = options.tokenScopes;
|
|
455
|
+
}
|
|
456
|
+
if (options.forceRename) {
|
|
457
|
+
queryOptions['forceRename'] = ['true'];
|
|
458
|
+
}
|
|
459
|
+
if (options.tags) {
|
|
460
|
+
queryOptions['tags'] = options.tags;
|
|
461
|
+
if (options.requireAllTags) {
|
|
462
|
+
queryOptions['allTags'] = ['true'];
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
queryItems.push(...Object.keys(queryOptions).map((key) => {
|
|
466
|
+
const value = queryOptions[key];
|
|
467
|
+
return `${key}=${value.map(encodeURIComponent).join(',')}`;
|
|
468
|
+
}));
|
|
469
|
+
}
|
|
470
|
+
if (additionalQuery) {
|
|
471
|
+
queryItems.push(additionalQuery);
|
|
472
|
+
}
|
|
473
|
+
const queryString = queryItems.join('&');
|
|
474
|
+
return queryString;
|
|
475
|
+
}
|
|
476
|
+
/**
|
|
477
|
+
* Makes an HTTP request using Axios, while tracing request and response details.
|
|
478
|
+
*/
|
|
479
|
+
async request(method, uri, data, config, allowNotFound) {
|
|
480
|
+
this.trace(`${method} ${uri}`);
|
|
481
|
+
this.traceHeaders(config.headers);
|
|
482
|
+
this.traceContent(data);
|
|
483
|
+
const traceResponse = (response) => {
|
|
484
|
+
this.trace(`${response.status} ${response.statusText}`);
|
|
485
|
+
this.traceHeaders(response.headers);
|
|
486
|
+
this.traceContent(response.data);
|
|
487
|
+
};
|
|
488
|
+
try {
|
|
489
|
+
config.url = uri;
|
|
490
|
+
config.method = method;
|
|
491
|
+
config.data = data;
|
|
492
|
+
let response = await axios_1.default.request(config);
|
|
493
|
+
traceResponse(response);
|
|
494
|
+
// This assumes that TResult is always boolean for DELETE requests.
|
|
495
|
+
return (method === 'DELETE' ? true : response.data);
|
|
496
|
+
}
|
|
497
|
+
catch (e) {
|
|
498
|
+
if (!(e instanceof Error) || !e.isAxiosError)
|
|
499
|
+
throw e;
|
|
500
|
+
const requestError = e;
|
|
501
|
+
if (requestError.response) {
|
|
502
|
+
traceResponse(requestError.response);
|
|
503
|
+
if (allowNotFound && requestError.response.status === 404) {
|
|
504
|
+
return (method === 'DELETE' ? false : null);
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
requestError.message = this.getResponseErrorMessage(requestError);
|
|
508
|
+
// Axios errors have too much redundant detail! Delete some of it.
|
|
509
|
+
delete requestError.request;
|
|
510
|
+
if (requestError.response) {
|
|
511
|
+
delete requestError.config.httpAgent;
|
|
512
|
+
delete requestError.config.httpsAgent;
|
|
513
|
+
delete requestError.response.request;
|
|
514
|
+
}
|
|
515
|
+
throw requestError;
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
traceHeaders(headers) {
|
|
519
|
+
for (let [headerName, headerValue] of Object.entries(headers)) {
|
|
520
|
+
if (headerName === 'Authorization') {
|
|
521
|
+
this.traceAuthorizationHeader(headerName, headerValue);
|
|
522
|
+
return;
|
|
523
|
+
}
|
|
524
|
+
this.trace(`${headerName}: ${headerValue !== null && headerValue !== void 0 ? headerValue : ''}`);
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
traceAuthorizationHeader(key, value) {
|
|
528
|
+
if (typeof value !== 'string')
|
|
529
|
+
return;
|
|
530
|
+
const spaceIndex = value.indexOf(' ');
|
|
531
|
+
if (spaceIndex < 0) {
|
|
532
|
+
this.trace(`${key}: [${value.length}]`);
|
|
533
|
+
return;
|
|
534
|
+
}
|
|
535
|
+
const scheme = value.substring(0, spaceIndex);
|
|
536
|
+
const token = value.substring(spaceIndex + 1);
|
|
537
|
+
if (scheme.toLowerCase() === tunnelManagementClient_1.TunnelAuthenticationSchemes.tunnel.toLowerCase()) {
|
|
538
|
+
const tokenProperties = tunnelAccessTokenProperties_1.TunnelAccessTokenProperties.tryParse(token);
|
|
539
|
+
if (tokenProperties) {
|
|
540
|
+
this.trace(`${key}: ${scheme} <${tokenProperties}>`);
|
|
541
|
+
return;
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
this.trace(`${key}: ${scheme} <token>`);
|
|
545
|
+
}
|
|
546
|
+
traceContent(data) {
|
|
547
|
+
if (typeof data === 'object') {
|
|
548
|
+
data = JSON.stringify(data, undefined, ' ');
|
|
549
|
+
}
|
|
550
|
+
if (typeof data === 'string') {
|
|
551
|
+
this.trace(TunnelManagementHttpClient.replaceTokensInContent(data));
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
static replaceTokensInContent(content) {
|
|
555
|
+
var _a;
|
|
556
|
+
const tokenRegex = /"(eyJ[a-zA-z0-9\-_]+\.[a-zA-z0-9\-_]+\.[a-zA-z0-9\-_]+)"/;
|
|
557
|
+
let match = tokenRegex.exec(content);
|
|
558
|
+
while (match) {
|
|
559
|
+
let token = match[1];
|
|
560
|
+
const tokenProperties = tunnelAccessTokenProperties_1.TunnelAccessTokenProperties.tryParse(token);
|
|
561
|
+
token = (_a = tokenProperties === null || tokenProperties === void 0 ? void 0 : tokenProperties.toString()) !== null && _a !== void 0 ? _a : 'token';
|
|
562
|
+
content =
|
|
563
|
+
content.substring(0, match.index + 1) +
|
|
564
|
+
'<' +
|
|
565
|
+
token +
|
|
566
|
+
'>' +
|
|
567
|
+
content.substring(match.index + match[0].length - 1);
|
|
568
|
+
match = tokenRegex.exec(content);
|
|
569
|
+
}
|
|
570
|
+
return content;
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
exports.TunnelManagementHttpClient = TunnelManagementHttpClient;
|
|
574
574
|
//# sourceMappingURL=tunnelManagementHttpClient.js.map
|