@mcp-z/client 2.2.3 → 2.3.0
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 +12 -1
- package/dist/cjs/auth/capability-discovery.d.cts +3 -1
- package/dist/cjs/auth/capability-discovery.d.ts +3 -1
- package/dist/cjs/auth/capability-discovery.js +46 -32
- package/dist/cjs/auth/capability-discovery.js.map +1 -1
- package/dist/cjs/auth/discovery-fetch.d.cts +2 -0
- package/dist/cjs/auth/discovery-fetch.d.ts +2 -0
- package/dist/cjs/auth/discovery-fetch.js +85 -31
- package/dist/cjs/auth/discovery-fetch.js.map +1 -1
- package/dist/cjs/auth/interactive-oauth-flow.d.cts +1 -1
- package/dist/cjs/auth/interactive-oauth-flow.d.ts +1 -1
- package/dist/cjs/auth/interactive-oauth-flow.js +13 -6
- package/dist/cjs/auth/interactive-oauth-flow.js.map +1 -1
- package/dist/cjs/auth/oauth-callback-listener.d.cts +1 -1
- package/dist/cjs/auth/oauth-callback-listener.d.ts +1 -1
- package/dist/cjs/auth/oauth-callback-listener.js +40 -4
- package/dist/cjs/auth/oauth-callback-listener.js.map +1 -1
- package/dist/cjs/auth/rfc9728-discovery.d.cts +7 -2
- package/dist/cjs/auth/rfc9728-discovery.d.ts +7 -2
- package/dist/cjs/auth/rfc9728-discovery.js +120 -88
- package/dist/cjs/auth/rfc9728-discovery.js.map +1 -1
- package/dist/cjs/auth/types.d.cts +4 -0
- package/dist/cjs/auth/types.d.ts +4 -0
- package/dist/cjs/auth/types.js.map +1 -1
- package/dist/cjs/connection/connect-client.d.cts +9 -1
- package/dist/cjs/connection/connect-client.d.ts +9 -1
- package/dist/cjs/connection/connect-client.js +282 -75
- package/dist/cjs/connection/connect-client.js.map +1 -1
- package/dist/cjs/connection/existing-process-transport.d.cts +18 -18
- package/dist/cjs/connection/existing-process-transport.d.ts +18 -18
- package/dist/cjs/connection/existing-process-transport.js +187 -82
- package/dist/cjs/connection/existing-process-transport.js.map +1 -1
- package/dist/cjs/connection/wait-for-http-ready.d.cts +1 -1
- package/dist/cjs/connection/wait-for-http-ready.d.ts +1 -1
- package/dist/cjs/connection/wait-for-http-ready.js +30 -5
- package/dist/cjs/connection/wait-for-http-ready.js.map +1 -1
- package/dist/cjs/dcr/dcr-authenticator.d.cts +1 -1
- package/dist/cjs/dcr/dcr-authenticator.d.ts +1 -1
- package/dist/cjs/dcr/dcr-authenticator.js +29 -18
- package/dist/cjs/dcr/dcr-authenticator.js.map +1 -1
- package/dist/cjs/dcr/dynamic-client-registrar.js +2 -1
- package/dist/cjs/dcr/dynamic-client-registrar.js.map +1 -1
- package/dist/cjs/index.d.cts +1 -1
- package/dist/cjs/index.d.ts +1 -1
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/spawn/owned-process-tree.d.cts +12 -0
- package/dist/cjs/spawn/owned-process-tree.d.ts +12 -0
- package/dist/cjs/spawn/owned-process-tree.js +93 -0
- package/dist/cjs/spawn/owned-process-tree.js.map +1 -0
- package/dist/cjs/spawn/spawn-server.d.cts +8 -32
- package/dist/cjs/spawn/spawn-server.d.ts +8 -32
- package/dist/cjs/spawn/spawn-server.js +406 -97
- package/dist/cjs/spawn/spawn-server.js.map +1 -1
- package/dist/cjs/spawn/spawn-servers.d.cts +18 -8
- package/dist/cjs/spawn/spawn-servers.d.ts +18 -8
- package/dist/cjs/spawn/spawn-servers.js +342 -113
- package/dist/cjs/spawn/spawn-servers.js.map +1 -1
- package/dist/cjs/types.d.cts +1 -1
- package/dist/cjs/types.d.ts +1 -1
- package/dist/cjs/types.js.map +1 -1
- package/dist/cjs/utils/path-utils.js +7 -4
- package/dist/cjs/utils/path-utils.js.map +1 -1
- package/dist/esm/auth/capability-discovery.d.ts +3 -1
- package/dist/esm/auth/capability-discovery.js +21 -11
- package/dist/esm/auth/capability-discovery.js.map +1 -1
- package/dist/esm/auth/discovery-fetch.d.ts +2 -0
- package/dist/esm/auth/discovery-fetch.js +64 -23
- package/dist/esm/auth/discovery-fetch.js.map +1 -1
- package/dist/esm/auth/interactive-oauth-flow.d.ts +1 -1
- package/dist/esm/auth/interactive-oauth-flow.js +13 -6
- package/dist/esm/auth/interactive-oauth-flow.js.map +1 -1
- package/dist/esm/auth/oauth-callback-listener.d.ts +1 -1
- package/dist/esm/auth/oauth-callback-listener.js +34 -4
- package/dist/esm/auth/oauth-callback-listener.js.map +1 -1
- package/dist/esm/auth/rfc9728-discovery.d.ts +7 -2
- package/dist/esm/auth/rfc9728-discovery.js +40 -16
- package/dist/esm/auth/rfc9728-discovery.js.map +1 -1
- package/dist/esm/auth/types.d.ts +4 -0
- package/dist/esm/auth/types.js.map +1 -1
- package/dist/esm/connection/connect-client.d.ts +9 -1
- package/dist/esm/connection/connect-client.js +97 -35
- package/dist/esm/connection/connect-client.js.map +1 -1
- package/dist/esm/connection/existing-process-transport.d.ts +18 -18
- package/dist/esm/connection/existing-process-transport.js +132 -70
- package/dist/esm/connection/existing-process-transport.js.map +1 -1
- package/dist/esm/connection/wait-for-http-ready.d.ts +1 -1
- package/dist/esm/connection/wait-for-http-ready.js +19 -4
- package/dist/esm/connection/wait-for-http-ready.js.map +1 -1
- package/dist/esm/dcr/dcr-authenticator.d.ts +1 -1
- package/dist/esm/dcr/dcr-authenticator.js +26 -15
- package/dist/esm/dcr/dcr-authenticator.js.map +1 -1
- package/dist/esm/dcr/dynamic-client-registrar.js +2 -1
- package/dist/esm/dcr/dynamic-client-registrar.js.map +1 -1
- package/dist/esm/index.d.ts +1 -1
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/spawn/owned-process-tree.d.ts +12 -0
- package/dist/esm/spawn/owned-process-tree.js +39 -0
- package/dist/esm/spawn/owned-process-tree.js.map +1 -0
- package/dist/esm/spawn/spawn-server.d.ts +8 -32
- package/dist/esm/spawn/spawn-server.js +228 -99
- package/dist/esm/spawn/spawn-server.js.map +1 -1
- package/dist/esm/spawn/spawn-servers.d.ts +18 -8
- package/dist/esm/spawn/spawn-servers.js +180 -55
- package/dist/esm/spawn/spawn-servers.js.map +1 -1
- package/dist/esm/types.d.ts +1 -1
- package/dist/esm/types.js.map +1 -1
- package/dist/esm/utils/path-utils.js +7 -4
- package/dist/esm/utils/path-utils.js.map +1 -1
- package/package.json +8 -3
- package/schemas/servers.d.ts +24 -0
- package/schemas/servers.schema.json +31 -0
package/README.md
CHANGED
|
@@ -86,13 +86,24 @@ const registry = createServerRegistry(
|
|
|
86
86
|
api: {
|
|
87
87
|
type: 'http',
|
|
88
88
|
url: 'http://localhost:3000/mcp',
|
|
89
|
-
start: {
|
|
89
|
+
start: {
|
|
90
|
+
command: 'node',
|
|
91
|
+
args: ['server.js'],
|
|
92
|
+
stop: {
|
|
93
|
+
command: 'node',
|
|
94
|
+
args: ['request-server-stop.js', 'http://localhost:3000/admin/shutdown']
|
|
95
|
+
}
|
|
96
|
+
}
|
|
90
97
|
}
|
|
91
98
|
},
|
|
92
99
|
{ dialects: ['start'] }
|
|
93
100
|
);
|
|
94
101
|
```
|
|
95
102
|
|
|
103
|
+
`start.stop` is an optional application-specific cooperative shutdown command for a server started by this registry. It runs once during `registry.close()`, before the registry waits for the owned process to close. The stop command must use a mechanism implemented by that server; MCP HTTP has no server shutdown operation. Commands use argument arrays without a shell. `timeoutMs` bounds each cooperative wait for the stop command and owned process (five seconds by default); emergency termination has its own bounded wait. Without a stop command there is no portable HTTP shutdown protocol: POSIX requests the configured signal, while Windows waits for the direct child and then uses emergency termination. External HTTP servers and client lease closure do not run the stop command.
|
|
104
|
+
|
|
105
|
+
Stdio shutdown sends EOF to the owned process first. If it remains open, POSIX sends the requested signal and waits again; Windows proceeds to bounded termination of the direct child. POSIX shutdown tracks the detached process group. Windows descendant ownership is not verified, so a wrapper that exits while leaving descendants may leave those processes running. `timedOut` reports a missed cooperative window, and `killedCount` counts direct-child or process-group emergency terminations.
|
|
106
|
+
|
|
96
107
|
## API overview
|
|
97
108
|
|
|
98
109
|
### Registry
|
|
@@ -3,4 +3,6 @@
|
|
|
3
3
|
* Probes RFC 9728 (Protected Resource) and RFC 8414 (Authorization Server) metadata
|
|
4
4
|
*/
|
|
5
5
|
import type { AuthCapabilities } from './types.js';
|
|
6
|
-
export declare function probeAuthCapabilities(baseUrl: string
|
|
6
|
+
export declare function probeAuthCapabilities(baseUrl: string, options?: {
|
|
7
|
+
signal?: AbortSignal;
|
|
8
|
+
}): Promise<AuthCapabilities>;
|
|
@@ -3,4 +3,6 @@
|
|
|
3
3
|
* Probes RFC 9728 (Protected Resource) and RFC 8414 (Authorization Server) metadata
|
|
4
4
|
*/
|
|
5
5
|
import type { AuthCapabilities } from './types.js';
|
|
6
|
-
export declare function probeAuthCapabilities(baseUrl: string
|
|
6
|
+
export declare function probeAuthCapabilities(baseUrl: string, options?: {
|
|
7
|
+
signal?: AbortSignal;
|
|
8
|
+
}): Promise<AuthCapabilities>;
|
|
@@ -205,7 +205,7 @@ function _ts_generator(thisArg, body) {
|
|
|
205
205
|
}
|
|
206
206
|
return capabilities;
|
|
207
207
|
}
|
|
208
|
-
function resolveCapabilitiesFromAuthorizationServer(authServerUrl, scopes, allowLoopback, resource) {
|
|
208
|
+
function resolveCapabilitiesFromAuthorizationServer(authServerUrl, scopes, allowLoopback, resource, signal) {
|
|
209
209
|
return _async_to_generator(function() {
|
|
210
210
|
var metadata;
|
|
211
211
|
return _ts_generator(this, function(_state) {
|
|
@@ -214,7 +214,8 @@ function resolveCapabilitiesFromAuthorizationServer(authServerUrl, scopes, allow
|
|
|
214
214
|
return [
|
|
215
215
|
4,
|
|
216
216
|
(0, _rfc9728discoveryts.discoverAuthorizationServerMetadata)(authServerUrl, {
|
|
217
|
-
allowLoopback: allowLoopback
|
|
217
|
+
allowLoopback: allowLoopback,
|
|
218
|
+
signal: signal
|
|
218
219
|
})
|
|
219
220
|
];
|
|
220
221
|
case 1:
|
|
@@ -231,31 +232,39 @@ function resolveCapabilitiesFromAuthorizationServer(authServerUrl, scopes, allow
|
|
|
231
232
|
});
|
|
232
233
|
})();
|
|
233
234
|
}
|
|
234
|
-
function probeAuthCapabilities(
|
|
235
|
-
return _async_to_generator(function() {
|
|
236
|
-
var normalizedBaseUrl, allowLoopback, resourceMetadata, authServerUrl, capabilities, issuer, issuerCapabilities, issuer1, issuerCapabilities1, origin, originCapabilities, _error;
|
|
235
|
+
function probeAuthCapabilities(_0) {
|
|
236
|
+
return _async_to_generator(function(baseUrl) {
|
|
237
|
+
var options, signal, normalizedBaseUrl, allowLoopback, resourceMetadata, authServerUrl, capabilities, issuer, issuerCapabilities, issuer1, issuerCapabilities1, origin, originCapabilities, _error;
|
|
238
|
+
var _arguments = arguments;
|
|
237
239
|
return _ts_generator(this, function(_state) {
|
|
238
240
|
switch(_state.label){
|
|
239
241
|
case 0:
|
|
242
|
+
options = _arguments.length > 1 && _arguments[1] !== void 0 ? _arguments[1] : {};
|
|
243
|
+
signal = options.signal;
|
|
244
|
+
_state.label = 1;
|
|
245
|
+
case 1:
|
|
240
246
|
_state.trys.push([
|
|
241
|
-
|
|
242
|
-
|
|
247
|
+
1,
|
|
248
|
+
11,
|
|
243
249
|
,
|
|
244
|
-
|
|
250
|
+
12
|
|
245
251
|
]);
|
|
252
|
+
(0, _discoveryfetchts.throwIfAborted)(signal);
|
|
246
253
|
normalizedBaseUrl = (0, _urlutilsts.normalizeUrl)(baseUrl);
|
|
247
254
|
// Trust signal for every authorization-server fetch below: whether the
|
|
248
255
|
// configured MCP server is itself loopback, never a remote-supplied URL.
|
|
249
256
|
allowLoopback = (0, _discoveryfetchts.isLoopbackUrl)(normalizedBaseUrl);
|
|
250
257
|
return [
|
|
251
258
|
4,
|
|
252
|
-
(0, _rfc9728discoveryts.discoverProtectedResourceMetadata)(normalizedBaseUrl
|
|
259
|
+
(0, _rfc9728discoveryts.discoverProtectedResourceMetadata)(normalizedBaseUrl, {
|
|
260
|
+
signal: signal
|
|
261
|
+
})
|
|
253
262
|
];
|
|
254
|
-
case
|
|
263
|
+
case 2:
|
|
255
264
|
resourceMetadata = _state.sent();
|
|
256
265
|
if (!(resourceMetadata && resourceMetadata.authorization_servers.length > 0)) return [
|
|
257
266
|
3,
|
|
258
|
-
|
|
267
|
+
6
|
|
259
268
|
];
|
|
260
269
|
// Found protected resource metadata with authorization servers
|
|
261
270
|
// Discover the authorization server's metadata (RFC 8414)
|
|
@@ -271,9 +280,9 @@ function probeAuthCapabilities(baseUrl) {
|
|
|
271
280
|
}
|
|
272
281
|
return [
|
|
273
282
|
4,
|
|
274
|
-
resolveCapabilitiesFromAuthorizationServer(authServerUrl, resourceMetadata.scopes_supported, allowLoopback, resourceMetadata.resource)
|
|
283
|
+
resolveCapabilitiesFromAuthorizationServer(authServerUrl, resourceMetadata.scopes_supported, allowLoopback, resourceMetadata.resource, signal)
|
|
275
284
|
];
|
|
276
|
-
case
|
|
285
|
+
case 3:
|
|
277
286
|
capabilities = _state.sent();
|
|
278
287
|
if (capabilities) {
|
|
279
288
|
return [
|
|
@@ -283,56 +292,60 @@ function probeAuthCapabilities(baseUrl) {
|
|
|
283
292
|
}
|
|
284
293
|
return [
|
|
285
294
|
4,
|
|
286
|
-
(0, _rfc9728discoveryts.discoverAuthorizationServerIssuer)(baseUrl
|
|
295
|
+
(0, _rfc9728discoveryts.discoverAuthorizationServerIssuer)(baseUrl, {
|
|
296
|
+
signal: signal
|
|
297
|
+
})
|
|
287
298
|
];
|
|
288
|
-
case
|
|
299
|
+
case 4:
|
|
289
300
|
issuer = _state.sent();
|
|
290
301
|
if (!issuer) return [
|
|
291
302
|
3,
|
|
292
|
-
|
|
303
|
+
6
|
|
293
304
|
];
|
|
294
305
|
return [
|
|
295
306
|
4,
|
|
296
|
-
resolveCapabilitiesFromAuthorizationServer(issuer, resourceMetadata.scopes_supported, allowLoopback, resourceMetadata.resource)
|
|
307
|
+
resolveCapabilitiesFromAuthorizationServer(issuer, resourceMetadata.scopes_supported, allowLoopback, resourceMetadata.resource, signal)
|
|
297
308
|
];
|
|
298
|
-
case
|
|
309
|
+
case 5:
|
|
299
310
|
issuerCapabilities = _state.sent();
|
|
300
311
|
if (issuerCapabilities) return [
|
|
301
312
|
2,
|
|
302
313
|
issuerCapabilities
|
|
303
314
|
];
|
|
304
|
-
_state.label =
|
|
305
|
-
case
|
|
315
|
+
_state.label = 6;
|
|
316
|
+
case 6:
|
|
306
317
|
return [
|
|
307
318
|
4,
|
|
308
|
-
(0, _rfc9728discoveryts.discoverAuthorizationServerIssuer)(normalizedBaseUrl
|
|
319
|
+
(0, _rfc9728discoveryts.discoverAuthorizationServerIssuer)(normalizedBaseUrl, {
|
|
320
|
+
signal: signal
|
|
321
|
+
})
|
|
309
322
|
];
|
|
310
|
-
case
|
|
323
|
+
case 7:
|
|
311
324
|
issuer1 = _state.sent();
|
|
312
325
|
if (!issuer1) return [
|
|
313
326
|
3,
|
|
314
|
-
|
|
327
|
+
9
|
|
315
328
|
];
|
|
316
329
|
return [
|
|
317
330
|
4,
|
|
318
|
-
resolveCapabilitiesFromAuthorizationServer(issuer1, undefined, allowLoopback)
|
|
331
|
+
resolveCapabilitiesFromAuthorizationServer(issuer1, undefined, allowLoopback, undefined, signal)
|
|
319
332
|
];
|
|
320
|
-
case
|
|
333
|
+
case 8:
|
|
321
334
|
issuerCapabilities1 = _state.sent();
|
|
322
335
|
if (issuerCapabilities1) return [
|
|
323
336
|
2,
|
|
324
337
|
issuerCapabilities1
|
|
325
338
|
];
|
|
326
|
-
_state.label =
|
|
327
|
-
case
|
|
339
|
+
_state.label = 9;
|
|
340
|
+
case 9:
|
|
328
341
|
// Strategy 2: Fall back to direct RFC 8414 discovery at resource origin
|
|
329
342
|
// This handles same-domain OAuth (traditional setup)
|
|
330
343
|
origin = getOrigin(normalizedBaseUrl);
|
|
331
344
|
return [
|
|
332
345
|
4,
|
|
333
|
-
resolveCapabilitiesFromAuthorizationServer(origin, undefined, allowLoopback)
|
|
346
|
+
resolveCapabilitiesFromAuthorizationServer(origin, undefined, allowLoopback, undefined, signal)
|
|
334
347
|
];
|
|
335
|
-
case
|
|
348
|
+
case 10:
|
|
336
349
|
originCapabilities = _state.sent();
|
|
337
350
|
if (originCapabilities) return [
|
|
338
351
|
2,
|
|
@@ -345,8 +358,9 @@ function probeAuthCapabilities(baseUrl) {
|
|
|
345
358
|
supportsDcr: false
|
|
346
359
|
}
|
|
347
360
|
];
|
|
348
|
-
case
|
|
361
|
+
case 11:
|
|
349
362
|
_error = _state.sent();
|
|
363
|
+
(0, _discoveryfetchts.throwIfAborted)(signal);
|
|
350
364
|
// Network error, invalid JSON, or other fetch failure
|
|
351
365
|
// Gracefully degrade - assume no DCR support
|
|
352
366
|
return [
|
|
@@ -355,12 +369,12 @@ function probeAuthCapabilities(baseUrl) {
|
|
|
355
369
|
supportsDcr: false
|
|
356
370
|
}
|
|
357
371
|
];
|
|
358
|
-
case
|
|
372
|
+
case 12:
|
|
359
373
|
return [
|
|
360
374
|
2
|
|
361
375
|
];
|
|
362
376
|
}
|
|
363
377
|
});
|
|
364
|
-
})();
|
|
378
|
+
}).apply(this, arguments);
|
|
365
379
|
}
|
|
366
380
|
/* CJS INTEROP */ if (exports.__esModule && exports.default) { try { Object.defineProperty(exports.default, '__esModule', { value: true }); for (var key in exports) { exports.default[key] = exports[key]; } } catch (_) {}; module.exports = exports.default; }
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/mcp-z/client/src/auth/capability-discovery.ts"],"sourcesContent":["/**\n * OAuth Server Capability Discovery\n * Probes RFC 9728 (Protected Resource) and RFC 8414 (Authorization Server) metadata\n */\n\nimport { normalizeUrl } from '../lib/url-utils.ts';\nimport { isLoopbackUrl } from './discovery-fetch.ts';\nimport { discoverAuthorizationServerIssuer, discoverAuthorizationServerMetadata, discoverProtectedResourceMetadata } from './rfc9728-discovery.ts';\nimport type { AuthCapabilities, AuthorizationServerMetadata } from './types.ts';\n\n/**\n * Extract origin (protocol + host) from a URL\n * @param url - Full URL that may include a path\n * @returns Origin (e.g., \"https://example.com\") or original string if invalid URL\n *\n * @example\n * getOrigin('https://example.com/mcp') // → 'https://example.com'\n * getOrigin('http://localhost:9999/api/v1/mcp') // → 'http://localhost:9999'\n */\nfunction getOrigin(url: string): string {\n try {\n return new URL(url).origin;\n } catch {\n // Invalid URL - return as-is for graceful degradation\n return url;\n }\n}\n\n/**\n * Probe OAuth server capabilities using RFC 9728 → RFC 8414 discovery chain\n * Returns capabilities including DCR support detection\n *\n * Discovery Strategy:\n * 1. Try RFC 9728 Protected Resource Metadata (supports cross-domain OAuth)\n * 2. If found, use first authorization_server to discover RFC 8414 Authorization Server Metadata\n * 3. Fall back to direct RFC 8414 discovery at resource origin\n *\n * @param baseUrl - Base URL of the protected resource (e.g., https://ai.todoist.net/mcp)\n * @returns AuthCapabilities object with discovered endpoints and features\n *\n * @example\n * // Todoist case: MCP at ai.todoist.net/mcp, OAuth at todoist.com\n * const caps = await probeAuthCapabilities('https://ai.todoist.net/mcp');\n * if (caps.supportsDcr) {\n * console.log('Registration endpoint:', caps.registrationEndpoint);\n * }\n */\nfunction buildCapabilities(metadata: AuthorizationServerMetadata, scopes?: string[], resource?: string): AuthCapabilities {\n const supportsDcr = !!metadata.registration_endpoint;\n const capabilities: AuthCapabilities = { supportsDcr, authorizationResponseIssSupported: metadata.authorization_response_iss_parameter_supported === true };\n\n if (metadata.issuer) {\n capabilities.issuer = metadata.issuer;\n }\n\n // Carried from the RFC 9728 document, not from the URL we were given: the\n // resource server names itself, and that name is what RFC 8707 audience-binds\n // a token to.\n if (resource) {\n capabilities.resource = resource;\n }\n\n if (metadata.registration_endpoint) {\n capabilities.registrationEndpoint = metadata.registration_endpoint;\n }\n if (metadata.authorization_endpoint) {\n capabilities.authorizationEndpoint = metadata.authorization_endpoint;\n }\n if (metadata.token_endpoint) capabilities.tokenEndpoint = metadata.token_endpoint;\n if (metadata.introspection_endpoint) {\n capabilities.introspectionEndpoint = metadata.introspection_endpoint;\n }\n\n if (scopes && scopes.length > 0) {\n capabilities.scopes = scopes;\n } else if (metadata.scopes_supported) {\n capabilities.scopes = metadata.scopes_supported;\n }\n\n return capabilities;\n}\n\nasync function resolveCapabilitiesFromAuthorizationServer(authServerUrl: string, scopes: string[] | undefined, allowLoopback: boolean, resource?: string): Promise<AuthCapabilities | null> {\n const metadata = await discoverAuthorizationServerMetadata(authServerUrl, { allowLoopback });\n if (!metadata) return null;\n return buildCapabilities(metadata, scopes, resource);\n}\n\nexport async function probeAuthCapabilities(baseUrl: string): Promise<AuthCapabilities> {\n try {\n const normalizedBaseUrl = normalizeUrl(baseUrl);\n // Trust signal for every authorization-server fetch below: whether the\n // configured MCP server is itself loopback, never a remote-supplied URL.\n const allowLoopback = isLoopbackUrl(normalizedBaseUrl);\n // Strategy 1: Try RFC 9728 Protected Resource Metadata discovery\n // This handles cross-domain OAuth (e.g., Todoist: ai.todoist.net/mcp → todoist.com)\n const resourceMetadata = await discoverProtectedResourceMetadata(normalizedBaseUrl);\n\n if (resourceMetadata && resourceMetadata.authorization_servers.length > 0) {\n // Found protected resource metadata with authorization servers\n // Discover the authorization server's metadata (RFC 8414)\n const authServerUrl = resourceMetadata.authorization_servers[0];\n if (!authServerUrl) {\n // Array has length > 0 but first element is undefined/null - skip this path\n return { supportsDcr: false };\n }\n const capabilities = await resolveCapabilitiesFromAuthorizationServer(authServerUrl, resourceMetadata.scopes_supported, allowLoopback, resourceMetadata.resource);\n if (capabilities) {\n return capabilities;\n }\n\n const issuer = await discoverAuthorizationServerIssuer(baseUrl);\n if (issuer) {\n const issuerCapabilities = await resolveCapabilitiesFromAuthorizationServer(issuer, resourceMetadata.scopes_supported, allowLoopback, resourceMetadata.resource);\n if (issuerCapabilities) return issuerCapabilities;\n }\n }\n\n const issuer = await discoverAuthorizationServerIssuer(normalizedBaseUrl);\n if (issuer) {\n const issuerCapabilities = await resolveCapabilitiesFromAuthorizationServer(issuer, undefined, allowLoopback);\n if (issuerCapabilities) return issuerCapabilities;\n }\n\n // Strategy 2: Fall back to direct RFC 8414 discovery at resource origin\n // This handles same-domain OAuth (traditional setup)\n const origin = getOrigin(normalizedBaseUrl);\n const originCapabilities = await resolveCapabilitiesFromAuthorizationServer(origin, undefined, allowLoopback);\n if (originCapabilities) return originCapabilities;\n\n // No OAuth metadata found\n return { supportsDcr: false };\n } catch (_error) {\n // Network error, invalid JSON, or other fetch failure\n // Gracefully degrade - assume no DCR support\n return { supportsDcr: false };\n }\n}\n"],"names":["probeAuthCapabilities","getOrigin","url","URL","origin","buildCapabilities","metadata","scopes","resource","supportsDcr","registration_endpoint","capabilities","authorizationResponseIssSupported","authorization_response_iss_parameter_supported","issuer","registrationEndpoint","authorization_endpoint","authorizationEndpoint","token_endpoint","tokenEndpoint","introspection_endpoint","introspectionEndpoint","length","scopes_supported","resolveCapabilitiesFromAuthorizationServer","authServerUrl","allowLoopback","discoverAuthorizationServerMetadata","baseUrl","normalizedBaseUrl","resourceMetadata","issuerCapabilities","originCapabilities","_error","normalizeUrl","isLoopbackUrl","discoverProtectedResourceMetadata","authorization_servers","discoverAuthorizationServerIssuer","undefined"],"mappings":"AAAA;;;CAGC;;;;+BAqFqBA;;;eAAAA;;;0BAnFO;gCACC;kCAC4F;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAG1H;;;;;;;;CAQC,GACD,SAASC,UAAUC,GAAW;IAC5B,IAAI;QACF,OAAO,IAAIC,IAAID,KAAKE,MAAM;IAC5B,EAAE,eAAM;QACN,sDAAsD;QACtD,OAAOF;IACT;AACF;AAEA;;;;;;;;;;;;;;;;;;CAkBC,GACD,SAASG,kBAAkBC,QAAqC,EAAEC,MAAiB,EAAEC,QAAiB;IACpG,IAAMC,cAAc,CAAC,CAACH,SAASI,qBAAqB;IACpD,IAAMC,eAAiC;QAAEF,aAAAA;QAAaG,mCAAmCN,SAASO,8CAA8C,KAAK;IAAK;IAE1J,IAAIP,SAASQ,MAAM,EAAE;QACnBH,aAAaG,MAAM,GAAGR,SAASQ,MAAM;IACvC;IAEA,0EAA0E;IAC1E,8EAA8E;IAC9E,cAAc;IACd,IAAIN,UAAU;QACZG,aAAaH,QAAQ,GAAGA;IAC1B;IAEA,IAAIF,SAASI,qBAAqB,EAAE;QAClCC,aAAaI,oBAAoB,GAAGT,SAASI,qBAAqB;IACpE;IACA,IAAIJ,SAASU,sBAAsB,EAAE;QACnCL,aAAaM,qBAAqB,GAAGX,SAASU,sBAAsB;IACtE;IACA,IAAIV,SAASY,cAAc,EAAEP,aAAaQ,aAAa,GAAGb,SAASY,cAAc;IACjF,IAAIZ,SAASc,sBAAsB,EAAE;QACnCT,aAAaU,qBAAqB,GAAGf,SAASc,sBAAsB;IACtE;IAEA,IAAIb,UAAUA,OAAOe,MAAM,GAAG,GAAG;QAC/BX,aAAaJ,MAAM,GAAGA;IACxB,OAAO,IAAID,SAASiB,gBAAgB,EAAE;QACpCZ,aAAaJ,MAAM,GAAGD,SAASiB,gBAAgB;IACjD;IAEA,OAAOZ;AACT;AAEA,SAAea,2CAA2CC,aAAqB,EAAElB,MAA4B,EAAEmB,aAAsB,EAAElB,QAAiB;;YAChJF;;;;oBAAW;;wBAAMqB,IAAAA,uDAAmC,EAACF,eAAe;4BAAEC,eAAAA;wBAAc;;;oBAApFpB,WAAW;oBACjB,IAAI,CAACA,UAAU;;wBAAO;;oBACtB;;wBAAOD,kBAAkBC,UAAUC,QAAQC;;;;IAC7C;;AAEO,SAAeR,sBAAsB4B,OAAe;;YAEjDC,mBAGAH,eAGAI,kBAKEL,eAKAd,cAKAG,QAEEiB,oBAKJjB,SAEEiB,qBAMF3B,QACA4B,oBAKCC;;;;;;;;;;oBA1CDJ,oBAAoBK,IAAAA,wBAAY,EAACN;oBACvC,uEAAuE;oBACvE,yEAAyE;oBACnEF,gBAAgBS,IAAAA,+BAAa,EAACN;oBAGX;;wBAAMO,IAAAA,qDAAiC,EAACP;;;oBAA3DC,mBAAmB;yBAErBA,CAAAA,oBAAoBA,iBAAiBO,qBAAqB,CAACf,MAAM,GAAG,CAAA,GAApEQ;;;;oBACF,+DAA+D;oBAC/D,0DAA0D;oBACpDL,gBAAgBK,iBAAiBO,qBAAqB,CAAC,EAAE;oBAC/D,IAAI,CAACZ,eAAe;wBAClB,4EAA4E;wBAC5E;;4BAAO;gCAAEhB,aAAa;4BAAM;;oBAC9B;oBACqB;;wBAAMe,2CAA2CC,eAAeK,iBAAiBP,gBAAgB,EAAEG,eAAeI,iBAAiBtB,QAAQ;;;oBAA1JG,eAAe;oBACrB,IAAIA,cAAc;wBAChB;;4BAAOA;;oBACT;oBAEe;;wBAAM2B,IAAAA,qDAAiC,EAACV;;;oBAAjDd,SAAS;yBACXA,QAAAA;;;;oBACyB;;wBAAMU,2CAA2CV,QAAQgB,iBAAiBP,gBAAgB,EAAEG,eAAeI,iBAAiBtB,QAAQ;;;oBAAzJuB,qBAAqB;oBAC3B,IAAIA,oBAAoB;;wBAAOA;;;;oBAIpB;;wBAAMO,IAAAA,qDAAiC,EAACT;;;oBAAjDf,UAAS;yBACXA,SAAAA;;;;oBACyB;;wBAAMU,2CAA2CV,SAAQyB,WAAWb;;;oBAAzFK,sBAAqB;oBAC3B,IAAIA,qBAAoB;;wBAAOA;;;;oBAGjC,wEAAwE;oBACxE,qDAAqD;oBAC/C3B,SAASH,UAAU4B;oBACE;;wBAAML,2CAA2CpB,QAAQmC,WAAWb;;;oBAAzFM,qBAAqB;oBAC3B,IAAIA,oBAAoB;;wBAAOA;;oBAE/B,0BAA0B;oBAC1B;;wBAAO;4BAAEvB,aAAa;wBAAM;;;oBACrBwB;oBACP,sDAAsD;oBACtD,6CAA6C;oBAC7C;;wBAAO;4BAAExB,aAAa;wBAAM;;;;;;;;IAEhC"}
|
|
1
|
+
{"version":3,"sources":["/Users/kevin/Dev/OpenSource/ai/mcp-z/client/src/auth/capability-discovery.ts"],"sourcesContent":["/**\n * OAuth Server Capability Discovery\n * Probes RFC 9728 (Protected Resource) and RFC 8414 (Authorization Server) metadata\n */\n\nimport { normalizeUrl } from '../lib/url-utils.ts';\nimport { isLoopbackUrl, throwIfAborted } from './discovery-fetch.ts';\nimport { discoverAuthorizationServerIssuer, discoverAuthorizationServerMetadata, discoverProtectedResourceMetadata } from './rfc9728-discovery.ts';\nimport type { AuthCapabilities, AuthorizationServerMetadata } from './types.ts';\n\n/**\n * Extract origin (protocol + host) from a URL\n * @param url - Full URL that may include a path\n * @returns Origin (e.g., \"https://example.com\") or original string if invalid URL\n *\n * @example\n * getOrigin('https://example.com/mcp') // → 'https://example.com'\n * getOrigin('http://localhost:9999/api/v1/mcp') // → 'http://localhost:9999'\n */\nfunction getOrigin(url: string): string {\n try {\n return new URL(url).origin;\n } catch {\n // Invalid URL - return as-is for graceful degradation\n return url;\n }\n}\n\n/**\n * Probe OAuth server capabilities using RFC 9728 → RFC 8414 discovery chain\n * Returns capabilities including DCR support detection\n *\n * Discovery Strategy:\n * 1. Try RFC 9728 Protected Resource Metadata (supports cross-domain OAuth)\n * 2. If found, use first authorization_server to discover RFC 8414 Authorization Server Metadata\n * 3. Fall back to direct RFC 8414 discovery at resource origin\n *\n * @param baseUrl - Base URL of the protected resource (e.g., https://ai.todoist.net/mcp)\n * @returns AuthCapabilities object with discovered endpoints and features\n *\n * @example\n * // Todoist case: MCP at ai.todoist.net/mcp, OAuth at todoist.com\n * const caps = await probeAuthCapabilities('https://ai.todoist.net/mcp');\n * if (caps.supportsDcr) {\n * console.log('Registration endpoint:', caps.registrationEndpoint);\n * }\n */\nfunction buildCapabilities(metadata: AuthorizationServerMetadata, scopes?: string[], resource?: string): AuthCapabilities {\n const supportsDcr = !!metadata.registration_endpoint;\n const capabilities: AuthCapabilities = { supportsDcr, authorizationResponseIssSupported: metadata.authorization_response_iss_parameter_supported === true };\n\n if (metadata.issuer) {\n capabilities.issuer = metadata.issuer;\n }\n\n // Carried from the RFC 9728 document, not from the URL we were given: the\n // resource server names itself, and that name is what RFC 8707 audience-binds\n // a token to.\n if (resource) {\n capabilities.resource = resource;\n }\n\n if (metadata.registration_endpoint) {\n capabilities.registrationEndpoint = metadata.registration_endpoint;\n }\n if (metadata.authorization_endpoint) {\n capabilities.authorizationEndpoint = metadata.authorization_endpoint;\n }\n if (metadata.token_endpoint) capabilities.tokenEndpoint = metadata.token_endpoint;\n if (metadata.introspection_endpoint) {\n capabilities.introspectionEndpoint = metadata.introspection_endpoint;\n }\n\n if (scopes && scopes.length > 0) {\n capabilities.scopes = scopes;\n } else if (metadata.scopes_supported) {\n capabilities.scopes = metadata.scopes_supported;\n }\n\n return capabilities;\n}\n\nasync function resolveCapabilitiesFromAuthorizationServer(authServerUrl: string, scopes: string[] | undefined, allowLoopback: boolean, resource?: string, signal?: AbortSignal): Promise<AuthCapabilities | null> {\n const metadata = await discoverAuthorizationServerMetadata(authServerUrl, { allowLoopback, signal });\n if (!metadata) return null;\n return buildCapabilities(metadata, scopes, resource);\n}\n\nexport async function probeAuthCapabilities(baseUrl: string, options: { signal?: AbortSignal } = {}): Promise<AuthCapabilities> {\n const { signal } = options;\n try {\n throwIfAborted(signal);\n const normalizedBaseUrl = normalizeUrl(baseUrl);\n // Trust signal for every authorization-server fetch below: whether the\n // configured MCP server is itself loopback, never a remote-supplied URL.\n const allowLoopback = isLoopbackUrl(normalizedBaseUrl);\n // Strategy 1: Try RFC 9728 Protected Resource Metadata discovery\n // This handles cross-domain OAuth (e.g., Todoist: ai.todoist.net/mcp → todoist.com)\n const resourceMetadata = await discoverProtectedResourceMetadata(normalizedBaseUrl, { signal });\n\n if (resourceMetadata && resourceMetadata.authorization_servers.length > 0) {\n // Found protected resource metadata with authorization servers\n // Discover the authorization server's metadata (RFC 8414)\n const authServerUrl = resourceMetadata.authorization_servers[0];\n if (!authServerUrl) {\n // Array has length > 0 but first element is undefined/null - skip this path\n return { supportsDcr: false };\n }\n const capabilities = await resolveCapabilitiesFromAuthorizationServer(authServerUrl, resourceMetadata.scopes_supported, allowLoopback, resourceMetadata.resource, signal);\n if (capabilities) {\n return capabilities;\n }\n\n const issuer = await discoverAuthorizationServerIssuer(baseUrl, { signal });\n if (issuer) {\n const issuerCapabilities = await resolveCapabilitiesFromAuthorizationServer(issuer, resourceMetadata.scopes_supported, allowLoopback, resourceMetadata.resource, signal);\n if (issuerCapabilities) return issuerCapabilities;\n }\n }\n\n const issuer = await discoverAuthorizationServerIssuer(normalizedBaseUrl, { signal });\n if (issuer) {\n const issuerCapabilities = await resolveCapabilitiesFromAuthorizationServer(issuer, undefined, allowLoopback, undefined, signal);\n if (issuerCapabilities) return issuerCapabilities;\n }\n\n // Strategy 2: Fall back to direct RFC 8414 discovery at resource origin\n // This handles same-domain OAuth (traditional setup)\n const origin = getOrigin(normalizedBaseUrl);\n const originCapabilities = await resolveCapabilitiesFromAuthorizationServer(origin, undefined, allowLoopback, undefined, signal);\n if (originCapabilities) return originCapabilities;\n\n // No OAuth metadata found\n return { supportsDcr: false };\n } catch (_error) {\n throwIfAborted(signal);\n // Network error, invalid JSON, or other fetch failure\n // Gracefully degrade - assume no DCR support\n return { supportsDcr: false };\n }\n}\n"],"names":["probeAuthCapabilities","getOrigin","url","URL","origin","buildCapabilities","metadata","scopes","resource","supportsDcr","registration_endpoint","capabilities","authorizationResponseIssSupported","authorization_response_iss_parameter_supported","issuer","registrationEndpoint","authorization_endpoint","authorizationEndpoint","token_endpoint","tokenEndpoint","introspection_endpoint","introspectionEndpoint","length","scopes_supported","resolveCapabilitiesFromAuthorizationServer","authServerUrl","allowLoopback","signal","discoverAuthorizationServerMetadata","baseUrl","options","normalizedBaseUrl","resourceMetadata","issuerCapabilities","originCapabilities","_error","throwIfAborted","normalizeUrl","isLoopbackUrl","discoverProtectedResourceMetadata","authorization_servers","discoverAuthorizationServerIssuer","undefined"],"mappings":"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"}
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
export declare class DiscoveryFetchError extends Error {
|
|
2
2
|
constructor(message: string);
|
|
3
3
|
}
|
|
4
|
+
/** Preserve cancellation while discovery falls back across endpoints. */
|
|
5
|
+
export declare function throwIfAborted(signal?: AbortSignal | null): void;
|
|
4
6
|
type LookupRecord = {
|
|
5
7
|
address: string;
|
|
6
8
|
family: number;
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
export declare class DiscoveryFetchError extends Error {
|
|
2
2
|
constructor(message: string);
|
|
3
3
|
}
|
|
4
|
+
/** Preserve cancellation while discovery falls back across endpoints. */
|
|
5
|
+
export declare function throwIfAborted(signal?: AbortSignal | null): void;
|
|
4
6
|
type LookupRecord = {
|
|
5
7
|
address: string;
|
|
6
8
|
family: number;
|
|
@@ -23,6 +23,9 @@ _export(exports, {
|
|
|
23
23
|
},
|
|
24
24
|
get readDiscoveryJson () {
|
|
25
25
|
return readDiscoveryJson;
|
|
26
|
+
},
|
|
27
|
+
get throwIfAborted () {
|
|
28
|
+
return throwIfAborted;
|
|
26
29
|
}
|
|
27
30
|
});
|
|
28
31
|
var _nodedns = /*#__PURE__*/ _interop_require_default(require("node:dns"));
|
|
@@ -325,6 +328,9 @@ var DiscoveryFetchError = /*#__PURE__*/ function(Error1) {
|
|
|
325
328
|
var DEFAULT_TIMEOUT_MS = 10000;
|
|
326
329
|
var MAX_REDIRECTS = 5;
|
|
327
330
|
var MAX_BODY_BYTES = 1000000; // 1 MB - discovery documents are small JSON
|
|
331
|
+
function throwIfAborted(signal) {
|
|
332
|
+
signal === null || signal === void 0 ? void 0 : signal.throwIfAborted();
|
|
333
|
+
}
|
|
328
334
|
function stripBrackets(hostname) {
|
|
329
335
|
return hostname.startsWith('[') && hostname.endsWith(']') ? hostname.slice(1, -1) : hostname;
|
|
330
336
|
}
|
|
@@ -386,32 +392,47 @@ function isLoopbackAddress(address) {
|
|
|
386
392
|
}
|
|
387
393
|
return url;
|
|
388
394
|
}
|
|
389
|
-
function lookupAll(hostname, lookup, timeoutMs) {
|
|
395
|
+
function lookupAll(hostname, lookup, timeoutMs, signal) {
|
|
390
396
|
return new Promise(function(resolve, reject) {
|
|
391
397
|
var settled = false;
|
|
398
|
+
var finish = function finish(complete) {
|
|
399
|
+
if (settled) return;
|
|
400
|
+
settled = true;
|
|
401
|
+
clearTimeout(deadline);
|
|
402
|
+
signal === null || signal === void 0 ? void 0 : signal.removeEventListener('abort', onAbort);
|
|
403
|
+
complete();
|
|
404
|
+
};
|
|
405
|
+
var onAbort = function onAbort() {
|
|
406
|
+
return finish(function() {
|
|
407
|
+
return reject(signal === null || signal === void 0 ? void 0 : signal.reason);
|
|
408
|
+
});
|
|
409
|
+
};
|
|
392
410
|
var deadline = setTimeout(function() {
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
}
|
|
411
|
+
finish(function() {
|
|
412
|
+
return reject(new Error('DNS resolution timed out'));
|
|
413
|
+
});
|
|
397
414
|
}, timeoutMs);
|
|
415
|
+
signal === null || signal === void 0 ? void 0 : signal.addEventListener('abort', onAbort, {
|
|
416
|
+
once: true
|
|
417
|
+
});
|
|
418
|
+
if (signal === null || signal === void 0 ? void 0 : signal.aborted) {
|
|
419
|
+
onAbort();
|
|
420
|
+
return;
|
|
421
|
+
}
|
|
398
422
|
try {
|
|
399
423
|
lookup(hostname, {
|
|
400
424
|
all: true,
|
|
401
425
|
verbatim: true
|
|
402
426
|
}, function(error, addresses) {
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
else resolve(addresses);
|
|
427
|
+
finish(function() {
|
|
428
|
+
if (error || addresses.length === 0) reject(new Error('DNS resolution failed'));
|
|
429
|
+
else resolve(addresses);
|
|
430
|
+
});
|
|
408
431
|
});
|
|
409
432
|
} catch (unused) {
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
reject(new Error('DNS resolution failed'));
|
|
414
|
-
}
|
|
433
|
+
finish(function() {
|
|
434
|
+
return reject(new Error('DNS resolution failed'));
|
|
435
|
+
});
|
|
415
436
|
}
|
|
416
437
|
});
|
|
417
438
|
}
|
|
@@ -421,12 +442,13 @@ function lookupAll(hostname, lookup, timeoutMs) {
|
|
|
421
442
|
* request itself (see `requestOnce`), which is what closes the DNS-rebinding
|
|
422
443
|
* TOCTOU: the transport can only dial addresses that were already validated,
|
|
423
444
|
* and it never resolves the hostname a second time.
|
|
424
|
-
*/ function resolveSafeAddresses(url, context, allowLoopback, lookup, timeoutMs) {
|
|
445
|
+
*/ function resolveSafeAddresses(url, context, allowLoopback, lookup, timeoutMs, signal) {
|
|
425
446
|
return _async_to_generator(function() {
|
|
426
|
-
var host, loopbackGrant, addresses,
|
|
447
|
+
var host, loopbackGrant, addresses, _error;
|
|
427
448
|
return _ts_generator(this, function(_state) {
|
|
428
449
|
switch(_state.label){
|
|
429
450
|
case 0:
|
|
451
|
+
throwIfAborted(signal);
|
|
430
452
|
host = stripBrackets(url.hostname);
|
|
431
453
|
if ((0, _nodenet.isIP)(host)) return [
|
|
432
454
|
2,
|
|
@@ -451,7 +473,7 @@ function lookupAll(hostname, lookup, timeoutMs) {
|
|
|
451
473
|
]);
|
|
452
474
|
return [
|
|
453
475
|
4,
|
|
454
|
-
lookupAll(host, lookup, timeoutMs)
|
|
476
|
+
lookupAll(host, lookup, timeoutMs, signal)
|
|
455
477
|
];
|
|
456
478
|
case 2:
|
|
457
479
|
addresses = _state.sent();
|
|
@@ -460,7 +482,8 @@ function lookupAll(hostname, lookup, timeoutMs) {
|
|
|
460
482
|
4
|
|
461
483
|
];
|
|
462
484
|
case 3:
|
|
463
|
-
|
|
485
|
+
_error = _state.sent();
|
|
486
|
+
throwIfAborted(signal);
|
|
464
487
|
throw new DiscoveryFetchError("Refusing to fetch ".concat(context, ": host could not be resolved"));
|
|
465
488
|
case 4:
|
|
466
489
|
if (loopbackGrant) {
|
|
@@ -684,55 +707,84 @@ function toResponse(status, statusText, headers, body) {
|
|
|
684
707
|
}
|
|
685
708
|
function requestOnce(url, init, context, addresses, timeoutMs) {
|
|
686
709
|
return _async_to_generator(function() {
|
|
687
|
-
var _init_method, body, unused, transport, options;
|
|
710
|
+
var _init_method, _init_signal, body, unused, transport, options;
|
|
688
711
|
return _ts_generator(this, function(_state) {
|
|
689
712
|
switch(_state.label){
|
|
690
713
|
case 0:
|
|
714
|
+
throwIfAborted(init.signal);
|
|
715
|
+
_state.label = 1;
|
|
716
|
+
case 1:
|
|
691
717
|
_state.trys.push([
|
|
692
|
-
|
|
693
|
-
|
|
718
|
+
1,
|
|
719
|
+
3,
|
|
694
720
|
,
|
|
695
|
-
|
|
721
|
+
4
|
|
696
722
|
]);
|
|
697
723
|
return [
|
|
698
724
|
4,
|
|
699
725
|
toRequestBody(init.body)
|
|
700
726
|
];
|
|
701
|
-
case
|
|
727
|
+
case 2:
|
|
702
728
|
body = _state.sent();
|
|
703
729
|
return [
|
|
704
730
|
3,
|
|
705
|
-
|
|
731
|
+
4
|
|
706
732
|
];
|
|
707
|
-
case
|
|
733
|
+
case 3:
|
|
708
734
|
unused = _state.sent();
|
|
709
735
|
throw new DiscoveryFetchError("Failed to fetch ".concat(context));
|
|
710
|
-
case
|
|
736
|
+
case 4:
|
|
711
737
|
transport = url.protocol === 'https:' ? _nodehttps.default : _nodehttp.default;
|
|
712
738
|
options = {
|
|
713
739
|
method: ((_init_method = init.method) !== null && _init_method !== void 0 ? _init_method : 'GET').toUpperCase(),
|
|
714
740
|
headers: toRequestHeaders(init),
|
|
715
741
|
agent: false,
|
|
716
|
-
lookup: pinnedLookup(addresses)
|
|
742
|
+
lookup: pinnedLookup(addresses),
|
|
743
|
+
signal: (_init_signal = init.signal) !== null && _init_signal !== void 0 ? _init_signal : undefined
|
|
717
744
|
};
|
|
718
745
|
return [
|
|
719
746
|
2,
|
|
720
747
|
new Promise(function(resolve, reject) {
|
|
748
|
+
var _init_signal;
|
|
721
749
|
var settled = false;
|
|
722
750
|
var req;
|
|
723
751
|
var res;
|
|
752
|
+
var signal = (_init_signal = init.signal) !== null && _init_signal !== void 0 ? _init_signal : undefined;
|
|
753
|
+
var cleanup = function cleanup() {
|
|
754
|
+
clearTimeout(deadline);
|
|
755
|
+
signal === null || signal === void 0 ? void 0 : signal.removeEventListener('abort', onAbort);
|
|
756
|
+
};
|
|
724
757
|
var fail = function fail(message) {
|
|
725
758
|
if (!settled) {
|
|
726
759
|
settled = true;
|
|
727
|
-
|
|
760
|
+
cleanup();
|
|
728
761
|
reject(new DiscoveryFetchError(message));
|
|
729
762
|
}
|
|
730
763
|
};
|
|
764
|
+
var failWith = function failWith(error) {
|
|
765
|
+
if (!settled) {
|
|
766
|
+
settled = true;
|
|
767
|
+
cleanup();
|
|
768
|
+
reject(error);
|
|
769
|
+
}
|
|
770
|
+
};
|
|
731
771
|
var deadline = setTimeout(function() {
|
|
732
772
|
res === null || res === void 0 ? void 0 : res.destroy();
|
|
733
773
|
req === null || req === void 0 ? void 0 : req.destroy();
|
|
734
774
|
fail("Failed to fetch ".concat(context));
|
|
735
775
|
}, timeoutMs);
|
|
776
|
+
var onAbort = function onAbort() {
|
|
777
|
+
res === null || res === void 0 ? void 0 : res.destroy();
|
|
778
|
+
req === null || req === void 0 ? void 0 : req.destroy();
|
|
779
|
+
failWith(signal === null || signal === void 0 ? void 0 : signal.reason);
|
|
780
|
+
};
|
|
781
|
+
signal === null || signal === void 0 ? void 0 : signal.addEventListener('abort', onAbort, {
|
|
782
|
+
once: true
|
|
783
|
+
});
|
|
784
|
+
if (signal === null || signal === void 0 ? void 0 : signal.aborted) {
|
|
785
|
+
onAbort();
|
|
786
|
+
return;
|
|
787
|
+
}
|
|
736
788
|
try {
|
|
737
789
|
req = transport.request(url, options, function(response) {
|
|
738
790
|
var _response_statusMessage;
|
|
@@ -761,7 +813,7 @@ function requestOnce(url, init, context, addresses, timeoutMs) {
|
|
|
761
813
|
response.once('end', function() {
|
|
762
814
|
if (settled) return;
|
|
763
815
|
settled = true;
|
|
764
|
-
|
|
816
|
+
cleanup();
|
|
765
817
|
resolve(toResponse(status, statusText, response.headers, Buffer.concat(chunks)));
|
|
766
818
|
});
|
|
767
819
|
});
|
|
@@ -785,9 +837,10 @@ function fetchOnce(url, init, context, allowLoopback, timeoutMs, lookup) {
|
|
|
785
837
|
return _ts_generator(this, function(_state) {
|
|
786
838
|
switch(_state.label){
|
|
787
839
|
case 0:
|
|
840
|
+
throwIfAborted(init.signal);
|
|
788
841
|
return [
|
|
789
842
|
4,
|
|
790
|
-
resolveSafeAddresses(url, context, allowLoopback, lookup, timeoutMs)
|
|
843
|
+
resolveSafeAddresses(url, context, allowLoopback, lookup, timeoutMs, init.signal)
|
|
791
844
|
];
|
|
792
845
|
case 1:
|
|
793
846
|
addresses = _state.sent();
|
|
@@ -807,6 +860,7 @@ function discoveryFetch(_0) {
|
|
|
807
860
|
switch(_state.label){
|
|
808
861
|
case 0:
|
|
809
862
|
init = _arguments.length > 1 && _arguments[1] !== void 0 ? _arguments[1] : {}, context = _arguments.length > 2 && _arguments[2] !== void 0 ? _arguments[2] : 'discovery URL', options = _arguments.length > 3 && _arguments[3] !== void 0 ? _arguments[3] : {};
|
|
863
|
+
throwIfAborted(init.signal);
|
|
810
864
|
_options_allowLoopback = options.allowLoopback, allowLoopback = _options_allowLoopback === void 0 ? false : _options_allowLoopback, _options_timeoutMs = options.timeoutMs, timeoutMs = _options_timeoutMs === void 0 ? DEFAULT_TIMEOUT_MS : _options_timeoutMs, _options_lookup = options.lookup, lookup = _options_lookup === void 0 ? _nodedns.default.lookup : _options_lookup;
|
|
811
865
|
url = assertSafeUrl(rawUrl, context, allowLoopback);
|
|
812
866
|
return [
|