@microsoft/dev-tunnels-management 1.3.5 → 1.3.36
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/idGeneration.d.ts +5 -5
- package/idGeneration.js +18 -18
- package/index.d.ts +4 -4
- package/index.js +22 -22
- package/package.json +2 -2
- package/tsbuildinfo.json +1 -1
- package/tunnelAccessTokenProperties.d.ts +43 -43
- package/tunnelAccessTokenProperties.js +149 -149
- package/tunnelManagementClient.d.ts +186 -186
- package/tunnelManagementClient.js +14 -14
- package/tunnelManagementHttpClient.d.ts +139 -139
- package/tunnelManagementHttpClient.d.ts.map +1 -1
- package/tunnelManagementHttpClient.js +917 -936
- package/tunnelManagementHttpClient.js.map +1 -1
- package/tunnelPlanTokenProperties.d.ts +35 -35
- package/tunnelPlanTokenProperties.js +91 -91
- package/tunnelRequestOptions.d.ts +84 -84
- package/tunnelRequestOptions.js +4 -4
- package/version.d.ts +4 -4
- package/version.js +12 -12
|
@@ -1,937 +1,918 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
// Copyright (c) Microsoft Corporation.
|
|
3
|
-
// Licensed under the MIT license.
|
|
4
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
5
|
-
exports.TunnelManagementHttpClient = exports.ManagementApiVersions = void 0;
|
|
6
|
-
const vscode_jsonrpc_1 = require("vscode-jsonrpc");
|
|
7
|
-
const dev_tunnels_ssh_1 = require("@microsoft/dev-tunnels-ssh");
|
|
8
|
-
const dev_tunnels_contracts_1 = require("@microsoft/dev-tunnels-contracts");
|
|
9
|
-
const tunnelManagementClient_1 = require("./tunnelManagementClient");
|
|
10
|
-
const tunnelAccessTokenProperties_1 = require("./tunnelAccessTokenProperties");
|
|
11
|
-
const version_1 = require("./version");
|
|
12
|
-
const axios_1 = require("axios");
|
|
13
|
-
const tunnelPlanTokenProperties_1 = require("./tunnelPlanTokenProperties");
|
|
14
|
-
const idGeneration_1 = require("./idGeneration");
|
|
15
|
-
const tunnelsApiPath = '/tunnels';
|
|
16
|
-
const limitsApiPath = '/userlimits';
|
|
17
|
-
const endpointsApiSubPath = '/endpoints';
|
|
18
|
-
const portsApiSubPath = '/ports';
|
|
19
|
-
const eventsApiSubPath = '/events';
|
|
20
|
-
const clustersApiPath = '/clusters';
|
|
21
|
-
const tunnelAuthentication = 'Authorization';
|
|
22
|
-
const checkAvailablePath = ':checkNameAvailability';
|
|
23
|
-
const createNameRetries = 3;
|
|
24
|
-
var ManagementApiVersions;
|
|
25
|
-
(function (ManagementApiVersions) {
|
|
26
|
-
ManagementApiVersions["Version20230927preview"] = "2023-09-27-preview";
|
|
27
|
-
})(ManagementApiVersions = exports.ManagementApiVersions || (exports.ManagementApiVersions = {}));
|
|
28
|
-
function comparePorts(a, b) {
|
|
29
|
-
var _a, _b;
|
|
30
|
-
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);
|
|
31
|
-
}
|
|
32
|
-
function parseDate(value) {
|
|
33
|
-
return typeof value === 'string' ? new Date(Date.parse(value)) : value;
|
|
34
|
-
}
|
|
35
|
-
/**
|
|
36
|
-
* Fixes Tunnel properties of type Date that were deserialized as strings.
|
|
37
|
-
*/
|
|
38
|
-
function parseTunnelDates(tunnel) {
|
|
39
|
-
if (!tunnel)
|
|
40
|
-
return;
|
|
41
|
-
tunnel.created = parseDate(tunnel.created);
|
|
42
|
-
if (tunnel.status) {
|
|
43
|
-
tunnel.status.lastHostConnectionTime = parseDate(tunnel.status.lastHostConnectionTime);
|
|
44
|
-
tunnel.status.lastClientConnectionTime = parseDate(tunnel.status.lastClientConnectionTime);
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
/**
|
|
48
|
-
* Fixes TunnelPort properties of type Date that were deserialized as strings.
|
|
49
|
-
*/
|
|
50
|
-
function parseTunnelPortDates(port) {
|
|
51
|
-
if (!port)
|
|
52
|
-
return;
|
|
53
|
-
if (port.status) {
|
|
54
|
-
port.status.lastClientConnectionTime = parseDate(port.status.lastClientConnectionTime);
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
/**
|
|
58
|
-
* Copy access tokens from the request object to the result object, except for any
|
|
59
|
-
* tokens that were refreshed by the request.
|
|
60
|
-
*/
|
|
61
|
-
function preserveAccessTokens(requestObject, resultObject) {
|
|
62
|
-
var _a;
|
|
63
|
-
// This intentionally does not check whether any existing tokens are expired. So
|
|
64
|
-
// expired tokens may be preserved also, if not refreshed. This allows for better
|
|
65
|
-
// diagnostics in that case.
|
|
66
|
-
if (requestObject.accessTokens && resultObject) {
|
|
67
|
-
(_a = resultObject.accessTokens) !== null && _a !== void 0 ? _a : (resultObject.accessTokens = {});
|
|
68
|
-
for (const scopeAndToken of Object.entries(requestObject.accessTokens)) {
|
|
69
|
-
if (!resultObject.accessTokens[scopeAndToken[0]]) {
|
|
70
|
-
resultObject.accessTokens[scopeAndToken[0]] = scopeAndToken[1];
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
const manageAccessTokenScope = [dev_tunnels_contracts_1.TunnelAccessScopes.Manage];
|
|
76
|
-
const hostAccessTokenScope = [dev_tunnels_contracts_1.TunnelAccessScopes.Host];
|
|
77
|
-
const managePortsAccessTokenScopes = [
|
|
78
|
-
dev_tunnels_contracts_1.TunnelAccessScopes.Manage,
|
|
79
|
-
dev_tunnels_contracts_1.TunnelAccessScopes.ManagePorts,
|
|
80
|
-
dev_tunnels_contracts_1.TunnelAccessScopes.Host,
|
|
81
|
-
];
|
|
82
|
-
const readAccessTokenScopes = [
|
|
83
|
-
dev_tunnels_contracts_1.TunnelAccessScopes.Manage,
|
|
84
|
-
dev_tunnels_contracts_1.TunnelAccessScopes.ManagePorts,
|
|
85
|
-
dev_tunnels_contracts_1.TunnelAccessScopes.Host,
|
|
86
|
-
dev_tunnels_contracts_1.TunnelAccessScopes.Connect,
|
|
87
|
-
];
|
|
88
|
-
const apiVersions = ["2023-09-27-preview"];
|
|
89
|
-
const defaultRequestTimeoutMS = 20000;
|
|
90
|
-
class TunnelManagementHttpClient {
|
|
91
|
-
/**
|
|
92
|
-
* Initializes a new instance of the `TunnelManagementHttpClient` class
|
|
93
|
-
* with a client authentication callback, service URI, and HTTP handler.
|
|
94
|
-
*
|
|
95
|
-
* @param userAgent { name, version } object or a comment string to use as the User-Agent header.
|
|
96
|
-
* @param apiVersion ApiVersion to be used for requests, value should be one of ManagementApiVersions enum.
|
|
97
|
-
* @param userTokenCallback Optional async callback for retrieving a client authentication
|
|
98
|
-
* header value with access token, for AAD or GitHub user authentication. This may be omitted
|
|
99
|
-
* for anonymous tunnel clients, or if tunnel access tokens will be specified via
|
|
100
|
-
* `TunnelRequestOptions.accessToken`.
|
|
101
|
-
* @param tunnelServiceUri Optional tunnel service URI (not including any path). Defaults to
|
|
102
|
-
* the global tunnel service URI.
|
|
103
|
-
* @param httpsAgent Optional agent that will be invoked for HTTPS requests to the tunnel
|
|
104
|
-
* service.
|
|
105
|
-
* @param adapter Optional axios adapter to use for HTTP requests.
|
|
106
|
-
*/
|
|
107
|
-
constructor(userAgents, apiVersion, userTokenCallback, tunnelServiceUri, httpsAgent, adapter) {
|
|
108
|
-
var _a;
|
|
109
|
-
this.httpsAgent = httpsAgent;
|
|
110
|
-
this.adapter = adapter;
|
|
111
|
-
this.reportProgressEmitter = new vscode_jsonrpc_1.Emitter();
|
|
112
|
-
/**
|
|
113
|
-
* Event that is raised to report tunnel management progress.
|
|
114
|
-
*
|
|
115
|
-
* See `Progress` for a description of the different progress events that can be reported.
|
|
116
|
-
*/
|
|
117
|
-
this.onReportProgress = this.reportProgressEmitter.event;
|
|
118
|
-
this.trace = (msg) => { };
|
|
119
|
-
/**
|
|
120
|
-
* Gets or sets a value indicating whether events reporting is enabled.
|
|
121
|
-
*
|
|
122
|
-
* When not enabled, any events reported via {@link reportEvent}
|
|
123
|
-
* (either by the tunnel SDK or the application) will be ignored.
|
|
124
|
-
*/
|
|
125
|
-
this.enableEventsReporting = false;
|
|
126
|
-
this.eventsQueue = [];
|
|
127
|
-
this.eventsPromise = null;
|
|
128
|
-
this.isDisposed = false;
|
|
129
|
-
this.eventsAvailableCompletion = new dev_tunnels_ssh_1.PromiseCompletionSource();
|
|
130
|
-
if (apiVersions.indexOf(apiVersion) === -1) {
|
|
131
|
-
throw new TypeError(`Invalid API version: ${apiVersion}, must be one of ${apiVersions}`);
|
|
132
|
-
}
|
|
133
|
-
this.apiVersion = apiVersion;
|
|
134
|
-
if (!userAgents) {
|
|
135
|
-
throw new TypeError('User agent must be provided.');
|
|
136
|
-
}
|
|
137
|
-
if (Array.isArray(userAgents)) {
|
|
138
|
-
if (userAgents.length === 0) {
|
|
139
|
-
throw new TypeError('User agents cannot be empty.');
|
|
140
|
-
}
|
|
141
|
-
let combinedUserAgents = '';
|
|
142
|
-
userAgents.forEach((userAgent) => {
|
|
143
|
-
var _a;
|
|
144
|
-
if (typeof userAgent !== 'string') {
|
|
145
|
-
if (!userAgent.name) {
|
|
146
|
-
throw new TypeError('Invalid user agent. The name must be provided.');
|
|
147
|
-
}
|
|
148
|
-
if (typeof userAgent.name !== 'string') {
|
|
149
|
-
throw new TypeError('Invalid user agent. The name must be a string.');
|
|
150
|
-
}
|
|
151
|
-
if (userAgent.version && typeof userAgent.version !== 'string') {
|
|
152
|
-
throw new TypeError('Invalid user agent. The version must be a string.');
|
|
153
|
-
}
|
|
154
|
-
combinedUserAgents = `${combinedUserAgents}${userAgent.name}/${(_a = userAgent.version) !== null && _a !== void 0 ? _a : 'unknown'} `;
|
|
155
|
-
}
|
|
156
|
-
else {
|
|
157
|
-
combinedUserAgents = `${combinedUserAgents}${userAgent} `;
|
|
158
|
-
}
|
|
159
|
-
});
|
|
160
|
-
this.userAgents = combinedUserAgents.trim();
|
|
161
|
-
}
|
|
162
|
-
else if (typeof userAgents !== 'string') {
|
|
163
|
-
if (!userAgents.name) {
|
|
164
|
-
throw new TypeError('Invalid user agent. The name must be provided.');
|
|
165
|
-
}
|
|
166
|
-
if (typeof userAgents.name !== 'string') {
|
|
167
|
-
throw new TypeError('Invalid user agent. The name must be a string.');
|
|
168
|
-
}
|
|
169
|
-
if (userAgents.version && typeof userAgents.version !== 'string') {
|
|
170
|
-
throw new TypeError('Invalid user agent. The version must be a string.');
|
|
171
|
-
}
|
|
172
|
-
this.userAgents = `${userAgents.name}/${(_a = userAgents.version) !== null && _a !== void 0 ? _a : 'unknown'}`;
|
|
173
|
-
}
|
|
174
|
-
else {
|
|
175
|
-
this.userAgents = userAgents;
|
|
176
|
-
}
|
|
177
|
-
this.userTokenCallback = userTokenCallback !== null && userTokenCallback !== void 0 ? userTokenCallback : (() => Promise.resolve(null));
|
|
178
|
-
if (!tunnelServiceUri) {
|
|
179
|
-
tunnelServiceUri = dev_tunnels_contracts_1.TunnelServiceProperties.production.serviceUri;
|
|
180
|
-
}
|
|
181
|
-
const parsedUri = new URL(tunnelServiceUri);
|
|
182
|
-
if (!parsedUri || parsedUri.pathname !== '/') {
|
|
183
|
-
throw new TypeError(`Invalid tunnel service URI: ${tunnelServiceUri}`);
|
|
184
|
-
}
|
|
185
|
-
this.baseAddress = tunnelServiceUri;
|
|
186
|
-
}
|
|
187
|
-
async listTunnels(clusterId, domain, options, cancellation) {
|
|
188
|
-
const queryParams = [clusterId ? null : 'global=true', domain ? `domain=${domain}` : null];
|
|
189
|
-
const query = queryParams.filter((p) => !!p).join('&');
|
|
190
|
-
const results = (await this.sendRequest('GET', clusterId, tunnelsApiPath, query, options, undefined, undefined, cancellation));
|
|
191
|
-
let tunnels = new Array();
|
|
192
|
-
if (results.value) {
|
|
193
|
-
for (const region of results.value) {
|
|
194
|
-
if (region.value) {
|
|
195
|
-
tunnels = tunnels.concat(region.value);
|
|
196
|
-
}
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
|
-
tunnels.forEach(parseTunnelDates);
|
|
200
|
-
return tunnels;
|
|
201
|
-
}
|
|
202
|
-
async getTunnel(tunnel, options, cancellation) {
|
|
203
|
-
const result = await this.sendTunnelRequest('GET', tunnel, readAccessTokenScopes, undefined, undefined, options, undefined, undefined, cancellation);
|
|
204
|
-
preserveAccessTokens(tunnel, result);
|
|
205
|
-
parseTunnelDates(result);
|
|
206
|
-
return result;
|
|
207
|
-
}
|
|
208
|
-
async createTunnel(tunnel, options, cancellation) {
|
|
209
|
-
|
|
210
|
-
const
|
|
211
|
-
|
|
212
|
-
options
|
|
213
|
-
options.additionalHeaders
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
tunnel
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
return result;
|
|
304
|
-
}
|
|
305
|
-
async
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
options = options || {};
|
|
329
|
-
options.additionalHeaders =
|
|
330
|
-
|
|
331
|
-
const
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
parseTunnelPortDates(result);
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
}
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
}
|
|
480
|
-
if (
|
|
481
|
-
|
|
482
|
-
}
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
//
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
}
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
}
|
|
542
|
-
}
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
errorMessage =
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
if (
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
}
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
}
|
|
649
|
-
if (
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
}
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
}
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
}
|
|
692
|
-
if (
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
if (
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
if (options.
|
|
759
|
-
queryOptions.
|
|
760
|
-
}
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
if (
|
|
871
|
-
this.
|
|
872
|
-
return;
|
|
873
|
-
}
|
|
874
|
-
|
|
875
|
-
}
|
|
876
|
-
}
|
|
877
|
-
|
|
878
|
-
if (typeof
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
if (
|
|
882
|
-
this.trace(
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
match = tokenRegex.exec(content);
|
|
919
|
-
}
|
|
920
|
-
return content;
|
|
921
|
-
}
|
|
922
|
-
/**
|
|
923
|
-
* Disposes the client and any background tasks.
|
|
924
|
-
*/
|
|
925
|
-
async dispose() {
|
|
926
|
-
this.isDisposed = true;
|
|
927
|
-
// Resolving the events-available completion will cause the events processing task
|
|
928
|
-
// to exit after processing any remaining already-queued events.
|
|
929
|
-
this.eventsAvailableCompletion.resolve();
|
|
930
|
-
if (this.eventsPromise) {
|
|
931
|
-
await this.eventsPromise;
|
|
932
|
-
this.eventsPromise = null;
|
|
933
|
-
}
|
|
934
|
-
}
|
|
935
|
-
}
|
|
936
|
-
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 = exports.ManagementApiVersions = void 0;
|
|
6
|
+
const vscode_jsonrpc_1 = require("vscode-jsonrpc");
|
|
7
|
+
const dev_tunnels_ssh_1 = require("@microsoft/dev-tunnels-ssh");
|
|
8
|
+
const dev_tunnels_contracts_1 = require("@microsoft/dev-tunnels-contracts");
|
|
9
|
+
const tunnelManagementClient_1 = require("./tunnelManagementClient");
|
|
10
|
+
const tunnelAccessTokenProperties_1 = require("./tunnelAccessTokenProperties");
|
|
11
|
+
const version_1 = require("./version");
|
|
12
|
+
const axios_1 = require("axios");
|
|
13
|
+
const tunnelPlanTokenProperties_1 = require("./tunnelPlanTokenProperties");
|
|
14
|
+
const idGeneration_1 = require("./idGeneration");
|
|
15
|
+
const tunnelsApiPath = '/tunnels';
|
|
16
|
+
const limitsApiPath = '/userlimits';
|
|
17
|
+
const endpointsApiSubPath = '/endpoints';
|
|
18
|
+
const portsApiSubPath = '/ports';
|
|
19
|
+
const eventsApiSubPath = '/events';
|
|
20
|
+
const clustersApiPath = '/clusters';
|
|
21
|
+
const tunnelAuthentication = 'Authorization';
|
|
22
|
+
const checkAvailablePath = ':checkNameAvailability';
|
|
23
|
+
const createNameRetries = 3;
|
|
24
|
+
var ManagementApiVersions;
|
|
25
|
+
(function (ManagementApiVersions) {
|
|
26
|
+
ManagementApiVersions["Version20230927preview"] = "2023-09-27-preview";
|
|
27
|
+
})(ManagementApiVersions = exports.ManagementApiVersions || (exports.ManagementApiVersions = {}));
|
|
28
|
+
function comparePorts(a, b) {
|
|
29
|
+
var _a, _b;
|
|
30
|
+
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);
|
|
31
|
+
}
|
|
32
|
+
function parseDate(value) {
|
|
33
|
+
return typeof value === 'string' ? new Date(Date.parse(value)) : value;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Fixes Tunnel properties of type Date that were deserialized as strings.
|
|
37
|
+
*/
|
|
38
|
+
function parseTunnelDates(tunnel) {
|
|
39
|
+
if (!tunnel)
|
|
40
|
+
return;
|
|
41
|
+
tunnel.created = parseDate(tunnel.created);
|
|
42
|
+
if (tunnel.status) {
|
|
43
|
+
tunnel.status.lastHostConnectionTime = parseDate(tunnel.status.lastHostConnectionTime);
|
|
44
|
+
tunnel.status.lastClientConnectionTime = parseDate(tunnel.status.lastClientConnectionTime);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Fixes TunnelPort properties of type Date that were deserialized as strings.
|
|
49
|
+
*/
|
|
50
|
+
function parseTunnelPortDates(port) {
|
|
51
|
+
if (!port)
|
|
52
|
+
return;
|
|
53
|
+
if (port.status) {
|
|
54
|
+
port.status.lastClientConnectionTime = parseDate(port.status.lastClientConnectionTime);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Copy access tokens from the request object to the result object, except for any
|
|
59
|
+
* tokens that were refreshed by the request.
|
|
60
|
+
*/
|
|
61
|
+
function preserveAccessTokens(requestObject, resultObject) {
|
|
62
|
+
var _a;
|
|
63
|
+
// This intentionally does not check whether any existing tokens are expired. So
|
|
64
|
+
// expired tokens may be preserved also, if not refreshed. This allows for better
|
|
65
|
+
// diagnostics in that case.
|
|
66
|
+
if (requestObject.accessTokens && resultObject) {
|
|
67
|
+
(_a = resultObject.accessTokens) !== null && _a !== void 0 ? _a : (resultObject.accessTokens = {});
|
|
68
|
+
for (const scopeAndToken of Object.entries(requestObject.accessTokens)) {
|
|
69
|
+
if (!resultObject.accessTokens[scopeAndToken[0]]) {
|
|
70
|
+
resultObject.accessTokens[scopeAndToken[0]] = scopeAndToken[1];
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
const manageAccessTokenScope = [dev_tunnels_contracts_1.TunnelAccessScopes.Manage];
|
|
76
|
+
const hostAccessTokenScope = [dev_tunnels_contracts_1.TunnelAccessScopes.Host];
|
|
77
|
+
const managePortsAccessTokenScopes = [
|
|
78
|
+
dev_tunnels_contracts_1.TunnelAccessScopes.Manage,
|
|
79
|
+
dev_tunnels_contracts_1.TunnelAccessScopes.ManagePorts,
|
|
80
|
+
dev_tunnels_contracts_1.TunnelAccessScopes.Host,
|
|
81
|
+
];
|
|
82
|
+
const readAccessTokenScopes = [
|
|
83
|
+
dev_tunnels_contracts_1.TunnelAccessScopes.Manage,
|
|
84
|
+
dev_tunnels_contracts_1.TunnelAccessScopes.ManagePorts,
|
|
85
|
+
dev_tunnels_contracts_1.TunnelAccessScopes.Host,
|
|
86
|
+
dev_tunnels_contracts_1.TunnelAccessScopes.Connect,
|
|
87
|
+
];
|
|
88
|
+
const apiVersions = ["2023-09-27-preview"];
|
|
89
|
+
const defaultRequestTimeoutMS = 20000;
|
|
90
|
+
class TunnelManagementHttpClient {
|
|
91
|
+
/**
|
|
92
|
+
* Initializes a new instance of the `TunnelManagementHttpClient` class
|
|
93
|
+
* with a client authentication callback, service URI, and HTTP handler.
|
|
94
|
+
*
|
|
95
|
+
* @param userAgent { name, version } object or a comment string to use as the User-Agent header.
|
|
96
|
+
* @param apiVersion ApiVersion to be used for requests, value should be one of ManagementApiVersions enum.
|
|
97
|
+
* @param userTokenCallback Optional async callback for retrieving a client authentication
|
|
98
|
+
* header value with access token, for AAD or GitHub user authentication. This may be omitted
|
|
99
|
+
* for anonymous tunnel clients, or if tunnel access tokens will be specified via
|
|
100
|
+
* `TunnelRequestOptions.accessToken`.
|
|
101
|
+
* @param tunnelServiceUri Optional tunnel service URI (not including any path). Defaults to
|
|
102
|
+
* the global tunnel service URI.
|
|
103
|
+
* @param httpsAgent Optional agent that will be invoked for HTTPS requests to the tunnel
|
|
104
|
+
* service.
|
|
105
|
+
* @param adapter Optional axios adapter to use for HTTP requests.
|
|
106
|
+
*/
|
|
107
|
+
constructor(userAgents, apiVersion, userTokenCallback, tunnelServiceUri, httpsAgent, adapter) {
|
|
108
|
+
var _a;
|
|
109
|
+
this.httpsAgent = httpsAgent;
|
|
110
|
+
this.adapter = adapter;
|
|
111
|
+
this.reportProgressEmitter = new vscode_jsonrpc_1.Emitter();
|
|
112
|
+
/**
|
|
113
|
+
* Event that is raised to report tunnel management progress.
|
|
114
|
+
*
|
|
115
|
+
* See `Progress` for a description of the different progress events that can be reported.
|
|
116
|
+
*/
|
|
117
|
+
this.onReportProgress = this.reportProgressEmitter.event;
|
|
118
|
+
this.trace = (msg) => { };
|
|
119
|
+
/**
|
|
120
|
+
* Gets or sets a value indicating whether events reporting is enabled.
|
|
121
|
+
*
|
|
122
|
+
* When not enabled, any events reported via {@link reportEvent}
|
|
123
|
+
* (either by the tunnel SDK or the application) will be ignored.
|
|
124
|
+
*/
|
|
125
|
+
this.enableEventsReporting = false;
|
|
126
|
+
this.eventsQueue = [];
|
|
127
|
+
this.eventsPromise = null;
|
|
128
|
+
this.isDisposed = false;
|
|
129
|
+
this.eventsAvailableCompletion = new dev_tunnels_ssh_1.PromiseCompletionSource();
|
|
130
|
+
if (apiVersions.indexOf(apiVersion) === -1) {
|
|
131
|
+
throw new TypeError(`Invalid API version: ${apiVersion}, must be one of ${apiVersions}`);
|
|
132
|
+
}
|
|
133
|
+
this.apiVersion = apiVersion;
|
|
134
|
+
if (!userAgents) {
|
|
135
|
+
throw new TypeError('User agent must be provided.');
|
|
136
|
+
}
|
|
137
|
+
if (Array.isArray(userAgents)) {
|
|
138
|
+
if (userAgents.length === 0) {
|
|
139
|
+
throw new TypeError('User agents cannot be empty.');
|
|
140
|
+
}
|
|
141
|
+
let combinedUserAgents = '';
|
|
142
|
+
userAgents.forEach((userAgent) => {
|
|
143
|
+
var _a;
|
|
144
|
+
if (typeof userAgent !== 'string') {
|
|
145
|
+
if (!userAgent.name) {
|
|
146
|
+
throw new TypeError('Invalid user agent. The name must be provided.');
|
|
147
|
+
}
|
|
148
|
+
if (typeof userAgent.name !== 'string') {
|
|
149
|
+
throw new TypeError('Invalid user agent. The name must be a string.');
|
|
150
|
+
}
|
|
151
|
+
if (userAgent.version && typeof userAgent.version !== 'string') {
|
|
152
|
+
throw new TypeError('Invalid user agent. The version must be a string.');
|
|
153
|
+
}
|
|
154
|
+
combinedUserAgents = `${combinedUserAgents}${userAgent.name}/${(_a = userAgent.version) !== null && _a !== void 0 ? _a : 'unknown'} `;
|
|
155
|
+
}
|
|
156
|
+
else {
|
|
157
|
+
combinedUserAgents = `${combinedUserAgents}${userAgent} `;
|
|
158
|
+
}
|
|
159
|
+
});
|
|
160
|
+
this.userAgents = combinedUserAgents.trim();
|
|
161
|
+
}
|
|
162
|
+
else if (typeof userAgents !== 'string') {
|
|
163
|
+
if (!userAgents.name) {
|
|
164
|
+
throw new TypeError('Invalid user agent. The name must be provided.');
|
|
165
|
+
}
|
|
166
|
+
if (typeof userAgents.name !== 'string') {
|
|
167
|
+
throw new TypeError('Invalid user agent. The name must be a string.');
|
|
168
|
+
}
|
|
169
|
+
if (userAgents.version && typeof userAgents.version !== 'string') {
|
|
170
|
+
throw new TypeError('Invalid user agent. The version must be a string.');
|
|
171
|
+
}
|
|
172
|
+
this.userAgents = `${userAgents.name}/${(_a = userAgents.version) !== null && _a !== void 0 ? _a : 'unknown'}`;
|
|
173
|
+
}
|
|
174
|
+
else {
|
|
175
|
+
this.userAgents = userAgents;
|
|
176
|
+
}
|
|
177
|
+
this.userTokenCallback = userTokenCallback !== null && userTokenCallback !== void 0 ? userTokenCallback : (() => Promise.resolve(null));
|
|
178
|
+
if (!tunnelServiceUri) {
|
|
179
|
+
tunnelServiceUri = dev_tunnels_contracts_1.TunnelServiceProperties.production.serviceUri;
|
|
180
|
+
}
|
|
181
|
+
const parsedUri = new URL(tunnelServiceUri);
|
|
182
|
+
if (!parsedUri || parsedUri.pathname !== '/') {
|
|
183
|
+
throw new TypeError(`Invalid tunnel service URI: ${tunnelServiceUri}`);
|
|
184
|
+
}
|
|
185
|
+
this.baseAddress = tunnelServiceUri;
|
|
186
|
+
}
|
|
187
|
+
async listTunnels(clusterId, domain, options, cancellation) {
|
|
188
|
+
const queryParams = [clusterId ? null : 'global=true', domain ? `domain=${domain}` : null];
|
|
189
|
+
const query = queryParams.filter((p) => !!p).join('&');
|
|
190
|
+
const results = (await this.sendRequest('GET', clusterId, tunnelsApiPath, query, options, undefined, undefined, cancellation));
|
|
191
|
+
let tunnels = new Array();
|
|
192
|
+
if (results.value) {
|
|
193
|
+
for (const region of results.value) {
|
|
194
|
+
if (region.value) {
|
|
195
|
+
tunnels = tunnels.concat(region.value);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
tunnels.forEach(parseTunnelDates);
|
|
200
|
+
return tunnels;
|
|
201
|
+
}
|
|
202
|
+
async getTunnel(tunnel, options, cancellation) {
|
|
203
|
+
const result = await this.sendTunnelRequest('GET', tunnel, readAccessTokenScopes, undefined, undefined, options, undefined, undefined, cancellation);
|
|
204
|
+
preserveAccessTokens(tunnel, result);
|
|
205
|
+
parseTunnelDates(result);
|
|
206
|
+
return result;
|
|
207
|
+
}
|
|
208
|
+
async createTunnel(tunnel, options, cancellation) {
|
|
209
|
+
var _a;
|
|
210
|
+
const tunnelId = tunnel.tunnelId;
|
|
211
|
+
const idGenerated = tunnelId === undefined || tunnelId === null || tunnelId === '';
|
|
212
|
+
options = options || {};
|
|
213
|
+
options.additionalHeaders = options.additionalHeaders || {};
|
|
214
|
+
options.additionalHeaders['If-Not-Match'] = "*";
|
|
215
|
+
if (idGenerated) {
|
|
216
|
+
tunnel.tunnelId = idGeneration_1.IdGeneration.generateTunnelId();
|
|
217
|
+
}
|
|
218
|
+
for (let retries = 0;; retries++) {
|
|
219
|
+
try {
|
|
220
|
+
const result = (await this.sendTunnelRequest('PUT', tunnel, manageAccessTokenScope, undefined, undefined, options, this.convertTunnelForRequest(tunnel), undefined, cancellation, true));
|
|
221
|
+
preserveAccessTokens(tunnel, result);
|
|
222
|
+
parseTunnelDates(result);
|
|
223
|
+
return result;
|
|
224
|
+
}
|
|
225
|
+
catch (error) {
|
|
226
|
+
if (((_a = error === null || error === void 0 ? void 0 : error.response) === null || _a === void 0 ? void 0 : _a.status) === 409 &&
|
|
227
|
+
idGenerated && retries < createNameRetries) {
|
|
228
|
+
// The tunnel ID was generated and there was a conflict.
|
|
229
|
+
// Try again with a new ID.
|
|
230
|
+
tunnel.tunnelId = idGeneration_1.IdGeneration.generateTunnelId();
|
|
231
|
+
}
|
|
232
|
+
else {
|
|
233
|
+
throw error;
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
async createOrUpdateTunnel(tunnel, options, cancellation) {
|
|
239
|
+
const tunnelId = tunnel.tunnelId;
|
|
240
|
+
if (!tunnelId) {
|
|
241
|
+
// When no tunnel ID is specified, a randomly-generated ID should not unintionally
|
|
242
|
+
// update an existing tunnel.
|
|
243
|
+
return this.createTunnel(tunnel, options, cancellation);
|
|
244
|
+
}
|
|
245
|
+
const result = (await this.sendTunnelRequest('PUT', tunnel, manageAccessTokenScope, undefined, undefined, options, this.convertTunnelForRequest(tunnel), undefined, cancellation, true));
|
|
246
|
+
preserveAccessTokens(tunnel, result);
|
|
247
|
+
parseTunnelDates(result);
|
|
248
|
+
return result;
|
|
249
|
+
}
|
|
250
|
+
async updateTunnel(tunnel, options, cancellation) {
|
|
251
|
+
options = options || {};
|
|
252
|
+
options.additionalHeaders = options.additionalHeaders || {};
|
|
253
|
+
options.additionalHeaders['If-Match'] = "*";
|
|
254
|
+
const result = (await this.sendTunnelRequest('PUT', tunnel, manageAccessTokenScope, undefined, undefined, options, this.convertTunnelForRequest(tunnel), undefined, cancellation));
|
|
255
|
+
preserveAccessTokens(tunnel, result);
|
|
256
|
+
parseTunnelDates(result);
|
|
257
|
+
return result;
|
|
258
|
+
}
|
|
259
|
+
async deleteTunnel(tunnel, options, cancellation) {
|
|
260
|
+
return await this.sendTunnelRequest('DELETE', tunnel, manageAccessTokenScope, undefined, undefined, options, undefined, true, cancellation);
|
|
261
|
+
}
|
|
262
|
+
async updateTunnelEndpoint(tunnel, endpoint, options, cancellation) {
|
|
263
|
+
if (endpoint.id == null) {
|
|
264
|
+
throw new Error('Endpoint ID must be specified when updating an endpoint.');
|
|
265
|
+
}
|
|
266
|
+
const path = `${endpointsApiSubPath}/${endpoint.id}`;
|
|
267
|
+
const result = (await this.sendTunnelRequest('PUT', tunnel, hostAccessTokenScope, path, "connectionMode=" + endpoint.connectionMode, options, endpoint, undefined, cancellation));
|
|
268
|
+
if (tunnel.endpoints) {
|
|
269
|
+
// Also update the endpoint in the local tunnel object.
|
|
270
|
+
tunnel.endpoints = tunnel.endpoints
|
|
271
|
+
.filter((e) => e.hostId !== endpoint.hostId ||
|
|
272
|
+
e.connectionMode !== endpoint.connectionMode)
|
|
273
|
+
.concat(result);
|
|
274
|
+
}
|
|
275
|
+
return result;
|
|
276
|
+
}
|
|
277
|
+
async deleteTunnelEndpoints(tunnel, id, options, cancellation) {
|
|
278
|
+
const path = `${endpointsApiSubPath}/${id}`;
|
|
279
|
+
const result = await this.sendTunnelRequest('DELETE', tunnel, hostAccessTokenScope, path, undefined, options, undefined, true, cancellation);
|
|
280
|
+
if (result && tunnel.endpoints) {
|
|
281
|
+
// Also delete the endpoint in the local tunnel object.
|
|
282
|
+
tunnel.endpoints = tunnel.endpoints.filter((e) => e.id !== id);
|
|
283
|
+
}
|
|
284
|
+
return result;
|
|
285
|
+
}
|
|
286
|
+
async listUserLimits(cancellation) {
|
|
287
|
+
const results = await this.sendRequest('GET', undefined, limitsApiPath, undefined, undefined, undefined, undefined, cancellation);
|
|
288
|
+
return results || [];
|
|
289
|
+
}
|
|
290
|
+
async listTunnelPorts(tunnel, options, cancellation) {
|
|
291
|
+
const results = (await this.sendTunnelRequest('GET', tunnel, readAccessTokenScopes, portsApiSubPath, undefined, options, undefined, undefined, cancellation));
|
|
292
|
+
if (results.value) {
|
|
293
|
+
results.value.forEach(parseTunnelPortDates);
|
|
294
|
+
}
|
|
295
|
+
return results.value;
|
|
296
|
+
}
|
|
297
|
+
async getTunnelPort(tunnel, portNumber, options, cancellation) {
|
|
298
|
+
this.raiseReportProgress(dev_tunnels_contracts_1.TunnelProgress.StartingGetTunnelPort);
|
|
299
|
+
const path = `${portsApiSubPath}/${portNumber}`;
|
|
300
|
+
const result = await this.sendTunnelRequest('GET', tunnel, readAccessTokenScopes, path, undefined, options, undefined, undefined, cancellation);
|
|
301
|
+
parseTunnelPortDates(result);
|
|
302
|
+
this.raiseReportProgress(dev_tunnels_contracts_1.TunnelProgress.CompletedGetTunnelPort);
|
|
303
|
+
return result;
|
|
304
|
+
}
|
|
305
|
+
async createTunnelPort(tunnel, tunnelPort, options, cancellation) {
|
|
306
|
+
this.raiseReportProgress(dev_tunnels_contracts_1.TunnelProgress.StartingCreateTunnelPort);
|
|
307
|
+
tunnelPort = this.convertTunnelPortForRequest(tunnel, tunnelPort);
|
|
308
|
+
const path = `${portsApiSubPath}/${tunnelPort.portNumber}`;
|
|
309
|
+
options = options || {};
|
|
310
|
+
options.additionalHeaders = options.additionalHeaders || {};
|
|
311
|
+
options.additionalHeaders['If-Not-Match'] = "*";
|
|
312
|
+
const result = (await this.sendTunnelRequest('PUT', tunnel, managePortsAccessTokenScopes, path, undefined, options, tunnelPort, undefined, cancellation));
|
|
313
|
+
tunnel.ports = tunnel.ports || [];
|
|
314
|
+
// Also add the port to the local tunnel object.
|
|
315
|
+
tunnel.ports = tunnel.ports
|
|
316
|
+
.filter((p) => p.portNumber !== tunnelPort.portNumber)
|
|
317
|
+
.concat(result)
|
|
318
|
+
.sort(comparePorts);
|
|
319
|
+
parseTunnelPortDates(result);
|
|
320
|
+
this.raiseReportProgress(dev_tunnels_contracts_1.TunnelProgress.CompletedCreateTunnelPort);
|
|
321
|
+
return result;
|
|
322
|
+
}
|
|
323
|
+
async updateTunnelPort(tunnel, tunnelPort, options, cancellation) {
|
|
324
|
+
if (tunnelPort.clusterId && tunnel.clusterId && tunnelPort.clusterId !== tunnel.clusterId) {
|
|
325
|
+
throw new Error('Tunnel port cluster ID is not consistent.');
|
|
326
|
+
}
|
|
327
|
+
options = options || {};
|
|
328
|
+
options.additionalHeaders = options.additionalHeaders || {};
|
|
329
|
+
options.additionalHeaders['If-Match'] = "*";
|
|
330
|
+
const portNumber = tunnelPort.portNumber;
|
|
331
|
+
const path = `${portsApiSubPath}/${portNumber}`;
|
|
332
|
+
tunnelPort = this.convertTunnelPortForRequest(tunnel, tunnelPort);
|
|
333
|
+
const result = (await this.sendTunnelRequest('PUT', tunnel, managePortsAccessTokenScopes, path, undefined, options, tunnelPort, undefined, cancellation));
|
|
334
|
+
preserveAccessTokens(tunnelPort, result);
|
|
335
|
+
parseTunnelPortDates(result);
|
|
336
|
+
tunnel.ports = tunnel.ports || [];
|
|
337
|
+
// Also add the port to the local tunnel object.
|
|
338
|
+
tunnel.ports = tunnel.ports
|
|
339
|
+
.filter((p) => p.portNumber !== tunnelPort.portNumber)
|
|
340
|
+
.concat(result)
|
|
341
|
+
.sort(comparePorts);
|
|
342
|
+
return result;
|
|
343
|
+
}
|
|
344
|
+
async createOrUpdateTunnelPort(tunnel, tunnelPort, options, cancellation) {
|
|
345
|
+
tunnelPort = this.convertTunnelPortForRequest(tunnel, tunnelPort);
|
|
346
|
+
const path = `${portsApiSubPath}/${tunnelPort.portNumber}`;
|
|
347
|
+
const result = (await this.sendTunnelRequest('PUT', tunnel, managePortsAccessTokenScopes, path, undefined, options, tunnelPort, undefined, cancellation));
|
|
348
|
+
tunnel.ports = tunnel.ports || [];
|
|
349
|
+
// Also add the port to the local tunnel object.
|
|
350
|
+
tunnel.ports = tunnel.ports
|
|
351
|
+
.filter((p) => p.portNumber !== tunnelPort.portNumber)
|
|
352
|
+
.concat(result)
|
|
353
|
+
.sort(comparePorts);
|
|
354
|
+
parseTunnelPortDates(result);
|
|
355
|
+
return result;
|
|
356
|
+
}
|
|
357
|
+
async deleteTunnelPort(tunnel, portNumber, options, cancellation) {
|
|
358
|
+
const path = `${portsApiSubPath}/${portNumber}`;
|
|
359
|
+
const result = await this.sendTunnelRequest('DELETE', tunnel, managePortsAccessTokenScopes, path, undefined, options, undefined, true, cancellation);
|
|
360
|
+
if (result && tunnel.ports) {
|
|
361
|
+
// Also delete the port in the local tunnel object.
|
|
362
|
+
tunnel.ports = tunnel.ports
|
|
363
|
+
.filter((p) => p.portNumber !== portNumber)
|
|
364
|
+
.sort(comparePorts);
|
|
365
|
+
}
|
|
366
|
+
return result;
|
|
367
|
+
}
|
|
368
|
+
async listClusters(cancellation) {
|
|
369
|
+
return (await this.sendRequest('GET', undefined, clustersApiPath, undefined, undefined, undefined, false, cancellation));
|
|
370
|
+
}
|
|
371
|
+
/**
|
|
372
|
+
* Sends an HTTP request to the tunnel management API, targeting a specific tunnel.
|
|
373
|
+
* This protected method enables subclasses to support additional tunnel management APIs.
|
|
374
|
+
* @param method HTTP request method.
|
|
375
|
+
* @param tunnel Tunnel that the request is targeting.
|
|
376
|
+
* @param accessTokenScopes Required array of access scopes for tokens in `tunnel.accessTokens`
|
|
377
|
+
* that could be used to authorize the request.
|
|
378
|
+
* @param path Optional request sub-path relative to the tunnel.
|
|
379
|
+
* @param query Optional query string to append to the request.
|
|
380
|
+
* @param options Request options.
|
|
381
|
+
* @param body Optional request body object.
|
|
382
|
+
* @param allowNotFound If true, a 404 response is returned as a null or false result
|
|
383
|
+
* instead of an error.
|
|
384
|
+
* @param cancellationToken Optional cancellation token for the request.
|
|
385
|
+
* @param isCreate Set to true if this is a tunnel create request, default is false.
|
|
386
|
+
* @returns Result of the request.
|
|
387
|
+
*/
|
|
388
|
+
async sendTunnelRequest(method, tunnel, accessTokenScopes, path, query, options, body, allowNotFound, cancellation, isCreate = false) {
|
|
389
|
+
this.raiseReportProgress(dev_tunnels_contracts_1.TunnelProgress.StartingRequestUri);
|
|
390
|
+
const uri = await this.buildUriForTunnel(tunnel, path, query, options, isCreate);
|
|
391
|
+
this.raiseReportProgress(dev_tunnels_contracts_1.TunnelProgress.StartingRequestConfig);
|
|
392
|
+
const config = await this.getAxiosRequestConfig(tunnel, options, accessTokenScopes);
|
|
393
|
+
this.raiseReportProgress(dev_tunnels_contracts_1.TunnelProgress.StartingSendTunnelRequest);
|
|
394
|
+
try {
|
|
395
|
+
const result = await this.request(method, uri, body, config, allowNotFound, cancellation);
|
|
396
|
+
this.raiseReportProgress(dev_tunnels_contracts_1.TunnelProgress.CompletedSendTunnelRequest);
|
|
397
|
+
return result;
|
|
398
|
+
}
|
|
399
|
+
catch (error) {
|
|
400
|
+
if (/certificate/i.test(error.message)) {
|
|
401
|
+
const originalErrorMessage = error.message;
|
|
402
|
+
throw new Error("Tunnel service HTTPS certificate is invalid. This may be caused by the use of a " +
|
|
403
|
+
"self-signed certificate or a firewall intercepting the connection. " + originalErrorMessage + ". ");
|
|
404
|
+
}
|
|
405
|
+
throw error;
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
/**
|
|
409
|
+
* Sends an HTTP request to the tunnel management API.
|
|
410
|
+
* This protected method enables subclasses to support additional tunnel management APIs.
|
|
411
|
+
* @param method HTTP request method.
|
|
412
|
+
* @param clusterId Optional tunnel service cluster ID to direct the request to. If unspecified,
|
|
413
|
+
* the request will use the global traffic-manager to find the nearest cluster.
|
|
414
|
+
* @param path Required request path.
|
|
415
|
+
* @param query Optional query string to append to the request.
|
|
416
|
+
* @param options Request options.
|
|
417
|
+
* @param body Optional request body object.
|
|
418
|
+
* @param allowNotFound If true, a 404 response is returned as a null or false result
|
|
419
|
+
* instead of an error.
|
|
420
|
+
* @param cancellationToken Optional cancellation token for the request.
|
|
421
|
+
* @returns Result of the request.
|
|
422
|
+
*/
|
|
423
|
+
async sendRequest(method, clusterId, path, query, options, body, allowNotFound, cancellation) {
|
|
424
|
+
this.raiseReportProgress(dev_tunnels_contracts_1.TunnelProgress.StartingSendTunnelRequest);
|
|
425
|
+
const uri = await this.buildUri(clusterId, path, query, options);
|
|
426
|
+
const config = await this.getAxiosRequestConfig(undefined, options);
|
|
427
|
+
try {
|
|
428
|
+
const result = await this.request(method, uri, body, config, allowNotFound, cancellation);
|
|
429
|
+
this.raiseReportProgress(dev_tunnels_contracts_1.TunnelProgress.CompletedSendTunnelRequest);
|
|
430
|
+
return result;
|
|
431
|
+
}
|
|
432
|
+
catch (error) {
|
|
433
|
+
if (/certificate/i.test(error.message)) {
|
|
434
|
+
throw new Error("Tunnel service HTTPS certificate is invalid. This may be caused by the use of a " +
|
|
435
|
+
"self signed certificate or a firewall intercepting the connection.");
|
|
436
|
+
}
|
|
437
|
+
throw error;
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
async checkNameAvailablility(tunnelName, cancellation) {
|
|
441
|
+
tunnelName = encodeURI(tunnelName);
|
|
442
|
+
const uri = await this.buildUri(undefined, `${tunnelsApiPath}/${tunnelName}${checkAvailablePath}`);
|
|
443
|
+
const config = {
|
|
444
|
+
httpsAgent: this.httpsAgent,
|
|
445
|
+
adapter: this.adapter,
|
|
446
|
+
};
|
|
447
|
+
return await this.request('GET', uri, undefined, config, undefined, cancellation);
|
|
448
|
+
}
|
|
449
|
+
reportEvent(tunnel, tunnelEvent, options) {
|
|
450
|
+
var _a;
|
|
451
|
+
if (!tunnel) {
|
|
452
|
+
throw new TypeError('A tunnel is required.');
|
|
453
|
+
}
|
|
454
|
+
if (!tunnelEvent) {
|
|
455
|
+
throw new TypeError('A tunnelEvent is required.');
|
|
456
|
+
}
|
|
457
|
+
if (!this.apiVersion) {
|
|
458
|
+
// Events are not supported by the V1 API.
|
|
459
|
+
return;
|
|
460
|
+
}
|
|
461
|
+
if (!this.enableEventsReporting) {
|
|
462
|
+
return;
|
|
463
|
+
}
|
|
464
|
+
if (this.isDisposed) {
|
|
465
|
+
// Do not queue any more events after the client is disposed.
|
|
466
|
+
return;
|
|
467
|
+
}
|
|
468
|
+
// Set the client timestamp if it wasn't already initialized.
|
|
469
|
+
(_a = tunnelEvent.timestamp) !== null && _a !== void 0 ? _a : (tunnelEvent.timestamp = new Date());
|
|
470
|
+
const wasEmpty = this.eventsQueue.length === 0;
|
|
471
|
+
this.eventsQueue.push({
|
|
472
|
+
tunnel: tunnel,
|
|
473
|
+
event: tunnelEvent,
|
|
474
|
+
requestOptions: options
|
|
475
|
+
});
|
|
476
|
+
// Signal that events are available
|
|
477
|
+
if (wasEmpty) {
|
|
478
|
+
this.eventsAvailableCompletion.resolve();
|
|
479
|
+
}
|
|
480
|
+
if (this.eventsPromise === null) {
|
|
481
|
+
this.eventsPromise = this.processPendingEventsAsync();
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
async processPendingEventsAsync() {
|
|
485
|
+
const eventsToSend = [];
|
|
486
|
+
while (!this.isDisposed) {
|
|
487
|
+
await this.eventsAvailableCompletion.promise;
|
|
488
|
+
this.eventsAvailableCompletion = new dev_tunnels_ssh_1.PromiseCompletionSource();
|
|
489
|
+
// Get the first event
|
|
490
|
+
const nextEventInfo = this.eventsQueue.shift();
|
|
491
|
+
if (!nextEventInfo) {
|
|
492
|
+
// The completion was resolved, but no events were queued.
|
|
493
|
+
// This indicates the client is being disposed.
|
|
494
|
+
break;
|
|
495
|
+
}
|
|
496
|
+
const tunnel = nextEventInfo.tunnel;
|
|
497
|
+
const requestOptions = nextEventInfo.requestOptions;
|
|
498
|
+
eventsToSend.length = 0;
|
|
499
|
+
eventsToSend.push(nextEventInfo.event);
|
|
500
|
+
// Batch events for the same tunnel with the same request options
|
|
501
|
+
while (this.eventsQueue.length > 0) {
|
|
502
|
+
const peekEventInfo = this.eventsQueue[0];
|
|
503
|
+
// Check if next event is for the same tunnel and has same request options
|
|
504
|
+
if (peekEventInfo.tunnel !== tunnel || peekEventInfo.requestOptions !== requestOptions) {
|
|
505
|
+
// Different tunnel or options, process as separate batch
|
|
506
|
+
break;
|
|
507
|
+
}
|
|
508
|
+
eventsToSend.push(this.eventsQueue.shift().event);
|
|
509
|
+
}
|
|
510
|
+
// Upload a batch of events for the same tunnel
|
|
511
|
+
try {
|
|
512
|
+
// Do not use sendTunnelRequest() here, to avoid reporting progress
|
|
513
|
+
// for these requests.
|
|
514
|
+
const uri = await this.buildUriForTunnel(tunnel, eventsApiSubPath, this.tunnelRequestOptionsToQueryString(requestOptions), requestOptions);
|
|
515
|
+
const config = await this.getAxiosRequestConfig(tunnel, requestOptions, readAccessTokenScopes);
|
|
516
|
+
await this.request('POST', uri, [...eventsToSend], // Create a copy to avoid mutation issues
|
|
517
|
+
config, undefined, undefined);
|
|
518
|
+
}
|
|
519
|
+
catch (error) {
|
|
520
|
+
// Errors uploading events are ignored.
|
|
521
|
+
this.trace(`Error uploading events: ${error}`);
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
raiseReportProgress(progress) {
|
|
526
|
+
const args = {
|
|
527
|
+
progress: progress
|
|
528
|
+
};
|
|
529
|
+
this.reportProgressEmitter.fire(args);
|
|
530
|
+
}
|
|
531
|
+
getResponseErrorMessage(error, signal) {
|
|
532
|
+
var _a, _b, _c, _d, _e, _f;
|
|
533
|
+
let errorMessage = '';
|
|
534
|
+
if (signal.aborted) {
|
|
535
|
+
// connection timeout
|
|
536
|
+
error.code = 'ECONNABORTED';
|
|
537
|
+
errorMessage = `ECONNABORTED: (signal aborted) ${error.message}`;
|
|
538
|
+
}
|
|
539
|
+
else if (error.code === 'ECONNABORTED') {
|
|
540
|
+
// server timeout
|
|
541
|
+
errorMessage = `ECONNABORTED: (timeout) ${error.message}`;
|
|
542
|
+
}
|
|
543
|
+
if ((_a = error.response) === null || _a === void 0 ? void 0 : _a.data) {
|
|
544
|
+
const problemDetails = error.response.data;
|
|
545
|
+
if (problemDetails.title || problemDetails.detail) {
|
|
546
|
+
errorMessage = `Tunnel service error: ${problemDetails.title}`;
|
|
547
|
+
if (problemDetails.detail) {
|
|
548
|
+
errorMessage += ' ' + problemDetails.detail;
|
|
549
|
+
}
|
|
550
|
+
if (problemDetails.errors) {
|
|
551
|
+
errorMessage += JSON.stringify(problemDetails.errors);
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
if (!errorMessage && error.response && error.response.status &&
|
|
556
|
+
error.response.status >= 400 && error.response.status < 500 &&
|
|
557
|
+
error.response.headers) {
|
|
558
|
+
const headers = error.response.headers;
|
|
559
|
+
const servedBy = headers['X-Served-By'] || headers['x-served-by'];
|
|
560
|
+
if (!/tunnels-/.test(servedBy)) {
|
|
561
|
+
// The response did not include either a ProblemDetails body object or a header
|
|
562
|
+
// confirming it was served by the tunnel service. This check excludes 5xx status
|
|
563
|
+
// responses which may include non-firwall network infrastructure issues.
|
|
564
|
+
const requestDomain = new URL((_c = (_b = error.config) === null || _b === void 0 ? void 0 : _b.url) !== null && _c !== void 0 ? _c : dev_tunnels_contracts_1.TunnelServiceProperties.production.serviceUri).host;
|
|
565
|
+
errorMessage = 'The tunnel request resulted in ' +
|
|
566
|
+
`${error.response.status} status, but the request ` +
|
|
567
|
+
'did not reach the tunnel service. This may indicate the domain ' +
|
|
568
|
+
`'${requestDomain}' is blocked by a firewall.`;
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
if (!errorMessage) {
|
|
572
|
+
if (error.response) {
|
|
573
|
+
errorMessage =
|
|
574
|
+
'Tunnel service returned status code: ' +
|
|
575
|
+
`${error.response.status} ${error.response.statusText}`;
|
|
576
|
+
}
|
|
577
|
+
else {
|
|
578
|
+
errorMessage = (_e = (_d = error === null || error === void 0 ? void 0 : error.message) !== null && _d !== void 0 ? _d : error) !== null && _e !== void 0 ? _e : 'Unknown tunnel service request error.';
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
const requestIdHeaderName = 'VsSaaS-Request-Id';
|
|
582
|
+
if (((_f = error.response) === null || _f === void 0 ? void 0 : _f.headers) && error.response.headers[requestIdHeaderName]) {
|
|
583
|
+
errorMessage += `\nRequest ID: ${error.response.headers[requestIdHeaderName]}`;
|
|
584
|
+
}
|
|
585
|
+
return errorMessage;
|
|
586
|
+
}
|
|
587
|
+
// Helper functions
|
|
588
|
+
async buildUri(clusterId, path, query, options) {
|
|
589
|
+
if (clusterId === undefined && this.userTokenCallback) {
|
|
590
|
+
let token = await this.userTokenCallback();
|
|
591
|
+
if (token && token.startsWith("tunnelplan")) {
|
|
592
|
+
token = token.replace("tunnelplan ", "");
|
|
593
|
+
const parsedToken = tunnelPlanTokenProperties_1.TunnelPlanTokenProperties.tryParse(token);
|
|
594
|
+
if (parsedToken !== null && parsedToken.clusterId) {
|
|
595
|
+
clusterId = parsedToken.clusterId;
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
let baseAddress = this.baseAddress;
|
|
600
|
+
if (clusterId) {
|
|
601
|
+
const url = new URL(baseAddress);
|
|
602
|
+
const portNumber = parseInt(url.port, 10);
|
|
603
|
+
url.hostname = TunnelManagementHttpClient.replaceTunnelServiceHostnameClusterId(url.hostname, clusterId);
|
|
604
|
+
if (url.protocol === 'https:' &&
|
|
605
|
+
clusterId.startsWith('localhost') &&
|
|
606
|
+
portNumber % 10 > 0) {
|
|
607
|
+
// Local testing simulates clusters by running the service on multiple ports.
|
|
608
|
+
// Change the port number to match the cluster ID suffix.
|
|
609
|
+
const clusterNumber = parseInt(clusterId.substring('localhost'.length), 10);
|
|
610
|
+
if (clusterNumber > 0 && clusterNumber < 10) {
|
|
611
|
+
url.port = (portNumber - (portNumber % 10) + clusterNumber).toString();
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
baseAddress = url.toString();
|
|
615
|
+
}
|
|
616
|
+
baseAddress = `${baseAddress.replace(/\/$/, '')}${path}`;
|
|
617
|
+
const optionsQuery = this.tunnelRequestOptionsToQueryString(options, query);
|
|
618
|
+
if (optionsQuery) {
|
|
619
|
+
baseAddress += `?${optionsQuery}`;
|
|
620
|
+
}
|
|
621
|
+
return baseAddress;
|
|
622
|
+
}
|
|
623
|
+
static replaceTunnelServiceHostnameClusterId(hostname, clusterId) {
|
|
624
|
+
// tunnels.local.api.visualstudio.com resolves to localhost (for local development).
|
|
625
|
+
if (!clusterId ||
|
|
626
|
+
hostname === 'localhost' ||
|
|
627
|
+
hostname === 'tunnels.local.api.visualstudio.com') {
|
|
628
|
+
return hostname;
|
|
629
|
+
}
|
|
630
|
+
if (hostname.startsWith('global.') ||
|
|
631
|
+
dev_tunnels_contracts_1.TunnelConstraints.clusterIdPrefixRegex.test(hostname)) {
|
|
632
|
+
// Hostname is in the form "global.rel.tunnels..." or "<clusterId>.rel.tunnels..."
|
|
633
|
+
// Replace the first part of the hostname with the specified cluster ID.
|
|
634
|
+
return clusterId + hostname.substring(hostname.indexOf('.'));
|
|
635
|
+
}
|
|
636
|
+
else {
|
|
637
|
+
// Hostname does not have a recognized cluster prefix. Prepend the cluster ID.
|
|
638
|
+
return `${clusterId}.${hostname}`;
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
buildUriForTunnel(tunnel, path, query, options, isCreate = false) {
|
|
642
|
+
let tunnelPath = '';
|
|
643
|
+
if ((tunnel.clusterId || isCreate) && tunnel.tunnelId) {
|
|
644
|
+
tunnelPath = `${tunnelsApiPath}/${tunnel.tunnelId}`;
|
|
645
|
+
}
|
|
646
|
+
else {
|
|
647
|
+
throw new Error('Tunnel object must include a tunnel ID always and cluster ID for non creates.');
|
|
648
|
+
}
|
|
649
|
+
if (options === null || options === void 0 ? void 0 : options.additionalQueryParameters) {
|
|
650
|
+
for (const [paramName, paramValue] of Object.entries(options.additionalQueryParameters)) {
|
|
651
|
+
if (query) {
|
|
652
|
+
query += `&${paramName}=${paramValue}`;
|
|
653
|
+
}
|
|
654
|
+
else {
|
|
655
|
+
query = `${paramName}=${paramValue}`;
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
return this.buildUri(tunnel.clusterId, tunnelPath + (path ? path : ''), query, options);
|
|
660
|
+
}
|
|
661
|
+
async getAxiosRequestConfig(tunnel, options, accessTokenScopes) {
|
|
662
|
+
// Get access token header
|
|
663
|
+
const headers = {};
|
|
664
|
+
if (options && options.accessToken) {
|
|
665
|
+
headers[tunnelAuthentication] = `${tunnelManagementClient_1.TunnelAuthenticationSchemes.tunnel} ${options.accessToken}`;
|
|
666
|
+
}
|
|
667
|
+
if (!(tunnelAuthentication in headers) && this.userTokenCallback) {
|
|
668
|
+
const token = await this.userTokenCallback();
|
|
669
|
+
if (token) {
|
|
670
|
+
headers[tunnelAuthentication] = token;
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
if (!(tunnelAuthentication in headers)) {
|
|
674
|
+
const accessToken = tunnelAccessTokenProperties_1.TunnelAccessTokenProperties.getTunnelAccessToken(tunnel, accessTokenScopes);
|
|
675
|
+
if (accessToken) {
|
|
676
|
+
headers[tunnelAuthentication] = `${tunnelManagementClient_1.TunnelAuthenticationSchemes.tunnel} ${accessToken}`;
|
|
677
|
+
}
|
|
678
|
+
}
|
|
679
|
+
const copyAdditionalHeaders = (additionalHeaders) => {
|
|
680
|
+
if (additionalHeaders) {
|
|
681
|
+
for (const [headerName, headerValue] of Object.entries(additionalHeaders)) {
|
|
682
|
+
headers[headerName] = headerValue;
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
};
|
|
686
|
+
copyAdditionalHeaders(this.additionalRequestHeaders);
|
|
687
|
+
copyAdditionalHeaders(options === null || options === void 0 ? void 0 : options.additionalHeaders);
|
|
688
|
+
const userAgentPrefix = headers['User-Agent'] ? headers['User-Agent'] + ' ' : '';
|
|
689
|
+
headers['User-Agent'] = `${userAgentPrefix}${this.userAgents} ${version_1.tunnelSdkUserAgent}`;
|
|
690
|
+
// Get axios config
|
|
691
|
+
const config = Object.assign(Object.assign({ headers }, (this.httpsAgent && { httpsAgent: this.httpsAgent })), (this.adapter && { adapter: this.adapter }));
|
|
692
|
+
if ((options === null || options === void 0 ? void 0 : options.followRedirects) === false) {
|
|
693
|
+
config.maxRedirects = 0;
|
|
694
|
+
}
|
|
695
|
+
return config;
|
|
696
|
+
}
|
|
697
|
+
convertTunnelForRequest(tunnel) {
|
|
698
|
+
var _a;
|
|
699
|
+
const convertedTunnel = {
|
|
700
|
+
tunnelId: tunnel.tunnelId,
|
|
701
|
+
name: tunnel.name,
|
|
702
|
+
domain: tunnel.domain,
|
|
703
|
+
description: tunnel.description,
|
|
704
|
+
labels: tunnel.labels,
|
|
705
|
+
options: tunnel.options,
|
|
706
|
+
customExpiration: tunnel.customExpiration,
|
|
707
|
+
accessControl: !tunnel.accessControl
|
|
708
|
+
? undefined
|
|
709
|
+
: { entries: tunnel.accessControl.entries.filter((ace) => !ace.isInherited) },
|
|
710
|
+
endpoints: tunnel.endpoints,
|
|
711
|
+
ports: (_a = tunnel.ports) === null || _a === void 0 ? void 0 : _a.map((p) => this.convertTunnelPortForRequest(tunnel, p)),
|
|
712
|
+
};
|
|
713
|
+
return convertedTunnel;
|
|
714
|
+
}
|
|
715
|
+
convertTunnelPortForRequest(tunnel, tunnelPort) {
|
|
716
|
+
if (tunnelPort.clusterId && tunnel.clusterId && tunnelPort.clusterId !== tunnel.clusterId) {
|
|
717
|
+
throw new Error('Tunnel port cluster ID does not match tunnel.');
|
|
718
|
+
}
|
|
719
|
+
if (tunnelPort.tunnelId && tunnel.tunnelId && tunnelPort.tunnelId !== tunnel.tunnelId) {
|
|
720
|
+
throw new Error('Tunnel port tunnel ID does not match tunnel.');
|
|
721
|
+
}
|
|
722
|
+
return {
|
|
723
|
+
portNumber: tunnelPort.portNumber,
|
|
724
|
+
protocol: tunnelPort.protocol,
|
|
725
|
+
isDefault: tunnelPort.isDefault,
|
|
726
|
+
description: tunnelPort.description,
|
|
727
|
+
labels: tunnelPort.labels,
|
|
728
|
+
sshUser: tunnelPort.sshUser,
|
|
729
|
+
options: tunnelPort.options,
|
|
730
|
+
accessControl: !tunnelPort.accessControl
|
|
731
|
+
? undefined
|
|
732
|
+
: { entries: tunnelPort.accessControl.entries.filter((ace) => !ace.isInherited) },
|
|
733
|
+
};
|
|
734
|
+
}
|
|
735
|
+
tunnelRequestOptionsToQueryString(options, additionalQuery) {
|
|
736
|
+
const queryOptions = {};
|
|
737
|
+
const queryItems = [];
|
|
738
|
+
if (options) {
|
|
739
|
+
if (options.includePorts) {
|
|
740
|
+
queryOptions.includePorts = ['true'];
|
|
741
|
+
}
|
|
742
|
+
if (options.includeAccessControl) {
|
|
743
|
+
queryOptions.includeAccessControl = ['true'];
|
|
744
|
+
}
|
|
745
|
+
if (options.tokenScopes) {
|
|
746
|
+
dev_tunnels_contracts_1.TunnelAccessControl.validateScopes(options.tokenScopes, undefined, true);
|
|
747
|
+
queryOptions.tokenScopes = options.tokenScopes;
|
|
748
|
+
}
|
|
749
|
+
if (options.forceRename) {
|
|
750
|
+
queryOptions.forceRename = ['true'];
|
|
751
|
+
}
|
|
752
|
+
if (options.labels) {
|
|
753
|
+
queryOptions.labels = options.labels;
|
|
754
|
+
if (options.requireAllLabels) {
|
|
755
|
+
queryOptions.allLabels = ['true'];
|
|
756
|
+
}
|
|
757
|
+
}
|
|
758
|
+
if (options.limit) {
|
|
759
|
+
queryOptions.limit = [options.limit.toString()];
|
|
760
|
+
}
|
|
761
|
+
queryItems.push(...Object.keys(queryOptions).map((key) => {
|
|
762
|
+
const value = queryOptions[key];
|
|
763
|
+
return `${key}=${value.map(encodeURIComponent).join(',')}`;
|
|
764
|
+
}));
|
|
765
|
+
}
|
|
766
|
+
if (additionalQuery) {
|
|
767
|
+
queryItems.push(additionalQuery);
|
|
768
|
+
}
|
|
769
|
+
queryItems.push(`api-version=${this.apiVersion}`);
|
|
770
|
+
const queryString = queryItems.join('&');
|
|
771
|
+
return queryString;
|
|
772
|
+
}
|
|
773
|
+
/**
|
|
774
|
+
* Axios request that can be overridden for unit tests purposes.
|
|
775
|
+
* @param config axios request config
|
|
776
|
+
* @param _cancellation the cancellation token for the request (used by unit tests to simulate timeouts).
|
|
777
|
+
*/
|
|
778
|
+
async axiosRequest(config, _cancellation) {
|
|
779
|
+
return await axios_1.default.request(config);
|
|
780
|
+
}
|
|
781
|
+
/**
|
|
782
|
+
* Makes an HTTP request using Axios, while tracing request and response details.
|
|
783
|
+
*/
|
|
784
|
+
async request(method, uri, data, config, allowNotFound, cancellation) {
|
|
785
|
+
var _a, _b;
|
|
786
|
+
this.trace(`${method} ${uri}`);
|
|
787
|
+
if (config.headers) {
|
|
788
|
+
this.traceHeaders(config.headers);
|
|
789
|
+
}
|
|
790
|
+
this.traceContent(data);
|
|
791
|
+
const traceResponse = (response) => {
|
|
792
|
+
this.trace(`${response.status} ${response.statusText}`);
|
|
793
|
+
this.traceHeaders(response.headers);
|
|
794
|
+
this.traceContent(response.data);
|
|
795
|
+
};
|
|
796
|
+
let disposable;
|
|
797
|
+
const abortController = new AbortController();
|
|
798
|
+
let timeout = undefined;
|
|
799
|
+
const newAbortSignal = () => {
|
|
800
|
+
if (cancellation === null || cancellation === void 0 ? void 0 : cancellation.isCancellationRequested) {
|
|
801
|
+
abortController.abort('Cancelled: CancellationToken cancel requested.');
|
|
802
|
+
}
|
|
803
|
+
else if (cancellation) {
|
|
804
|
+
disposable = cancellation.onCancellationRequested(() => abortController.abort('Cancelled: CancellationToken cancel requested.'));
|
|
805
|
+
}
|
|
806
|
+
else {
|
|
807
|
+
timeout = setTimeout(() => abortController.abort('Cancelled: default request timeout reached.'), defaultRequestTimeoutMS);
|
|
808
|
+
}
|
|
809
|
+
return abortController.signal;
|
|
810
|
+
};
|
|
811
|
+
try {
|
|
812
|
+
config.url = uri;
|
|
813
|
+
config.method = method;
|
|
814
|
+
config.data = data;
|
|
815
|
+
config.signal = newAbortSignal();
|
|
816
|
+
config.timeout = defaultRequestTimeoutMS;
|
|
817
|
+
const response = await this.axiosRequest(config, cancellation);
|
|
818
|
+
traceResponse(response);
|
|
819
|
+
// This assumes that TResult is always boolean for DELETE requests.
|
|
820
|
+
return (method === 'DELETE' ? true : response.data);
|
|
821
|
+
}
|
|
822
|
+
catch (e) {
|
|
823
|
+
if (!(e instanceof Error) || !e.isAxiosError)
|
|
824
|
+
throw e;
|
|
825
|
+
const requestError = e;
|
|
826
|
+
if (requestError.response) {
|
|
827
|
+
traceResponse(requestError.response);
|
|
828
|
+
if (allowNotFound && requestError.response.status === 404) {
|
|
829
|
+
return (method === 'DELETE' ? false : null);
|
|
830
|
+
}
|
|
831
|
+
}
|
|
832
|
+
requestError.message = this.getResponseErrorMessage(requestError, abortController.signal);
|
|
833
|
+
// Axios errors have too much redundant detail! Delete some of it.
|
|
834
|
+
delete requestError.request;
|
|
835
|
+
if (requestError.response) {
|
|
836
|
+
(_a = requestError.config) === null || _a === void 0 ? true : delete _a.httpAgent;
|
|
837
|
+
(_b = requestError.config) === null || _b === void 0 ? true : delete _b.httpsAgent;
|
|
838
|
+
delete requestError.response.request;
|
|
839
|
+
}
|
|
840
|
+
throw requestError;
|
|
841
|
+
}
|
|
842
|
+
finally {
|
|
843
|
+
if (timeout) {
|
|
844
|
+
clearTimeout(timeout);
|
|
845
|
+
}
|
|
846
|
+
disposable === null || disposable === void 0 ? void 0 : disposable.dispose();
|
|
847
|
+
}
|
|
848
|
+
}
|
|
849
|
+
traceHeaders(headers) {
|
|
850
|
+
for (const [headerName, headerValue] of Object.entries(headers)) {
|
|
851
|
+
if (headerName === 'Authorization') {
|
|
852
|
+
this.traceAuthorizationHeader(headerName, headerValue);
|
|
853
|
+
return;
|
|
854
|
+
}
|
|
855
|
+
this.trace(`${headerName}: ${headerValue !== null && headerValue !== void 0 ? headerValue : ''}`);
|
|
856
|
+
}
|
|
857
|
+
}
|
|
858
|
+
traceAuthorizationHeader(key, value) {
|
|
859
|
+
if (typeof value !== 'string')
|
|
860
|
+
return;
|
|
861
|
+
const spaceIndex = value.indexOf(' ');
|
|
862
|
+
if (spaceIndex < 0) {
|
|
863
|
+
this.trace(`${key}: [${value.length}]`);
|
|
864
|
+
return;
|
|
865
|
+
}
|
|
866
|
+
const scheme = value.substring(0, spaceIndex);
|
|
867
|
+
const token = value.substring(spaceIndex + 1);
|
|
868
|
+
if (scheme.toLowerCase() === tunnelManagementClient_1.TunnelAuthenticationSchemes.tunnel.toLowerCase()) {
|
|
869
|
+
const tokenProperties = tunnelAccessTokenProperties_1.TunnelAccessTokenProperties.tryParse(token);
|
|
870
|
+
if (tokenProperties) {
|
|
871
|
+
this.trace(`${key}: ${scheme} <${tokenProperties}>`);
|
|
872
|
+
return;
|
|
873
|
+
}
|
|
874
|
+
}
|
|
875
|
+
this.trace(`${key}: ${scheme} <token>`);
|
|
876
|
+
}
|
|
877
|
+
traceContent(data) {
|
|
878
|
+
if (typeof data === 'object') {
|
|
879
|
+
data = JSON.stringify(data, undefined, ' ');
|
|
880
|
+
}
|
|
881
|
+
if (typeof data === 'string') {
|
|
882
|
+
this.trace(TunnelManagementHttpClient.replaceTokensInContent(data));
|
|
883
|
+
}
|
|
884
|
+
}
|
|
885
|
+
static replaceTokensInContent(content) {
|
|
886
|
+
var _a;
|
|
887
|
+
const tokenRegex = /"(eyJ[a-zA-z0-9\-_]+\.[a-zA-z0-9\-_]+\.[a-zA-z0-9\-_]+)"/;
|
|
888
|
+
let match = tokenRegex.exec(content);
|
|
889
|
+
while (match) {
|
|
890
|
+
let token = match[1];
|
|
891
|
+
const tokenProperties = tunnelAccessTokenProperties_1.TunnelAccessTokenProperties.tryParse(token);
|
|
892
|
+
token = (_a = tokenProperties === null || tokenProperties === void 0 ? void 0 : tokenProperties.toString()) !== null && _a !== void 0 ? _a : 'token';
|
|
893
|
+
content =
|
|
894
|
+
content.substring(0, match.index + 1) +
|
|
895
|
+
'<' +
|
|
896
|
+
token +
|
|
897
|
+
'>' +
|
|
898
|
+
content.substring(match.index + match[0].length - 1);
|
|
899
|
+
match = tokenRegex.exec(content);
|
|
900
|
+
}
|
|
901
|
+
return content;
|
|
902
|
+
}
|
|
903
|
+
/**
|
|
904
|
+
* Disposes the client and any background tasks.
|
|
905
|
+
*/
|
|
906
|
+
async dispose() {
|
|
907
|
+
this.isDisposed = true;
|
|
908
|
+
// Resolving the events-available completion will cause the events processing task
|
|
909
|
+
// to exit after processing any remaining already-queued events.
|
|
910
|
+
this.eventsAvailableCompletion.resolve();
|
|
911
|
+
if (this.eventsPromise) {
|
|
912
|
+
await this.eventsPromise;
|
|
913
|
+
this.eventsPromise = null;
|
|
914
|
+
}
|
|
915
|
+
}
|
|
916
|
+
}
|
|
917
|
+
exports.TunnelManagementHttpClient = TunnelManagementHttpClient;
|
|
937
918
|
//# sourceMappingURL=tunnelManagementHttpClient.js.map
|