@redocly/realm 0.136.0-next.14 → 0.136.0-next.15
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/CHANGELOG.md +15 -0
- package/dist/server/plugins/mcp/codemode/capabilities/fetch/index.js +3 -3
- package/dist/server/plugins/mcp/codemode/prompts.js +1 -1
- package/dist/server/plugins/mcp/codemode/sandbox/create-sandbox-setup.js +1 -1
- package/dist/server/plugins/mcp/codemode/sandbox/polyfills/fetch-shim.d.ts +6 -0
- package/dist/server/plugins/mcp/codemode/sandbox/polyfills/fetch-shim.js +54 -0
- package/dist/server/plugins/mcp/codemode/sandbox/polyfills/form-data.d.ts +3 -0
- package/dist/server/plugins/mcp/codemode/sandbox/polyfills/form-data.js +17 -0
- package/dist/server/plugins/mcp/codemode/sandbox/polyfills/index.d.ts +3 -0
- package/dist/server/plugins/mcp/codemode/sandbox/polyfills/index.js +2 -0
- package/dist/server/plugins/mcp/codemode/sandbox/polyfills/url-search-params.d.ts +3 -0
- package/dist/server/plugins/mcp/codemode/sandbox/polyfills/url-search-params.js +64 -0
- package/dist/server/plugins/mcp/codemode/sandbox/polyfills/url.d.ts +3 -0
- package/dist/server/plugins/mcp/codemode/sandbox/polyfills/url.js +239 -0
- package/dist/server/plugins/mcp/gateway-mcp/elicitation.js +3 -3
- package/dist/server/plugins/mcp/handlers/mcp-credentials-handler.js +12 -8
- package/dist/server/plugins/mcp/servers/mcp-server.d.ts +1 -0
- package/dist/server/plugins/mcp/servers/mcp-server.js +2 -2
- package/package.json +8 -8
- package/dist/server/plugins/mcp/codemode/sandbox/polyfills.d.ts +0 -2
- package/dist/server/plugins/mcp/codemode/sandbox/polyfills.js +0 -99
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,20 @@
|
|
|
1
1
|
# @redocly/realm
|
|
2
2
|
|
|
3
|
+
## 0.136.0-next.15
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 1f5d5014e53: Fixed an issue where sidebar items configured with `separatorLine: true` drew their line above the item instead of below it.
|
|
8
|
+
- e0f58876734: Fixed credential handling in the gateway MCP for clients without URL-mode elicitation.
|
|
9
|
+
Instructions provide correct links, credential errors are flagged, and credential pages display link validity duration.
|
|
10
|
+
- Updated dependencies [1f5d5014e53]
|
|
11
|
+
- @redocly/theme@0.68.0-next.9
|
|
12
|
+
- @redocly/api-docs@0.1.0-next.7
|
|
13
|
+
- @redocly/asyncapi-docs@1.13.0-next.13
|
|
14
|
+
- @redocly/graphql-docs@1.13.0-next.13
|
|
15
|
+
- @redocly/openapi-docs@3.24.0-next.13
|
|
16
|
+
- @redocly/portal-plugin-mock-server@0.21.0-next.13
|
|
17
|
+
|
|
3
18
|
## 0.136.0-next.14
|
|
4
19
|
|
|
5
20
|
### Patch Changes
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{getCallableApis as
|
|
2
|
-
`),c=["Example -
|
|
3
|
-
`);function
|
|
1
|
+
import{getCallableApis as s,isFetchCapable as o}from"../../../gateway-mcp/fetch-capability.js";import{FETCH_TOOL_NAME as i}from"../../constants.js";const l=["```ts","/**"," * Call a documented API. Only the callable APIs' documented hosts are reachable."," * Responses cap at 1 MiB and redirects are not followed."," */","declare function fetch(url: string | URL, init?: {"," method?: string;"," /** `content-type` is set automatically for URLSearchParams/FormData unless you set one. */"," headers?: Record<string, string>;"," /** string \xB7 URLSearchParams (\u2192 application/x-www-form-urlencoded) \xB7 FormData (\u2192 multipart/form-data) */"," body?: string | URLSearchParams | FormData;","}): Promise<{"," status: number;"," statusText: string;"," ok: boolean;"," headers: { get(name: string): string | null };"," json(): Promise<unknown>;"," text(): Promise<string>;","}>;","```"].join(`
|
|
2
|
+
`),c=["Example - list the paid invoices of a billing API:","","```ts","const { items } = await tools.listApis({ filter: 'billing' });","const { endpoints } = await tools.getEndpoints({ name: items[0].name });","const ep = endpoints.find((e) => e.method === 'GET' && e.path.includes('invoices'));","const info = await tools.getEndpointInfo({ name: items[0].name, path: ep.path, method: ep.method });","const url = new URL((info.servers?.[0]?.url ?? '') + ep.path);","// Every name below comes from `info.endpoint.parameters` - read them first, never guess them.","url.searchParams.set('filter', 'status:paid');","url.searchParams.set('sort', '-createdTime');","url.searchParams.set('limit', '100');","try {"," const res = await fetch(url, { method: ep.method });"," return res.ok ? await res.json() : { status: res.status, error: await res.text() };","} catch (error) {"," return { error: error instanceof Error ? error.message : String(error), endpointId: info };","}","```"].join(`
|
|
3
|
+
`);function m(a){const t=new Map;for(const{name:e,version:r}of a){if(!e)continue;const n=t.get(e)??[];r&&n.push(r),t.set(e,n)}return[...t.entries()].map(([e,r])=>r.length>1?`${e} (${r.map(n=>`v${n}`).join(", ")})`:e).join(", ")}const h={id:i,purpose:"Call the documented APIs over HTTP",isActive:o,annotations:{readOnlyHint:!1,destructiveHint:!0,openWorldHint:!0},renderSection(a){const t=m(s(a));return["## `fetch` \u2014 call the documented APIs","",l,"","Always call `getEndpointInfo` before `fetch` for the exact URL, required parameters, and body shape. Put filtering, sorting, and paging in the query parameters, and keep sandbox JS for what the endpoint cannot express. Never guess.","",`Callable APIs (their documented hosts are reachable via \`fetch\`): ${t}`,"",c].join(`
|
|
4
4
|
`)}};export{l as FETCH_DECLARATION,h as fetchCapability};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import{buildToolList as i}from"./build-tool-list.js";const l=2048,c="Both `execute` arguments are required: `code` (the JavaScript to run; return a value to receive it) and `description` (a short human-readable summary of what the code does).",n="Available tools (exact signatures come from `describe-tools`):";function a(e,t){if(t.length===0)return e;const o=l-e.length-n.length-3,s=i(t,o);return s?[e,"",n,s].join(`
|
|
2
|
-
`):e}function h(e,t=[]){const o=["Run JavaScript in a sandbox against the documentation tools on the global `tools` object.",e?"The
|
|
2
|
+
`):e}function h(e,t=[]){const o=["Run JavaScript in a sandbox against the documentation tools on the global `tools` object.",e?"The extra globals are `tools`, `fetch`, `URL`, `URLSearchParams`, and `FormData` - there is no `console`, `setTimeout`, `require`, or Node.js API; all other I/O goes through `tools`.":"The only extra global is `tools` - there is no `fetch`, `console`, `setTimeout`, `require`, or Node.js API; all I/O goes through `tools`.","","Workflow - minimize round-trips; one `describe-tools` call plus one `execute` call covers most requests:","1. Analyze the request and decide which sandbox tools it needs.","2. Call `describe-tools` with those names to get their exact TypeScript signatures - inputs, result shapes, and a worked example. NEVER call `execute` with guessed method names, parameter names, enum values, or result field names: read fields off the declared return types, not from assumptions.","3. Batch everything into a SINGLE script: chain tool calls, pass one call's output into the next, process results in plain JavaScript, and `return` only what the request needs."].join(`
|
|
3
3
|
`);return a(o,t)}const d=["Get the exact TypeScript signatures of the sandbox tools available to `execute`.","Analyze the user request first, then pass `tools` with only the tool names the request needs (every name is listed in this input schema / description); omit `tools` only when you cannot tell which tools apply.","Always call this before your first `execute` and never guess signatures. Request every tool the task may need in ONE call instead of calling this repeatedly."].join(`
|
|
4
4
|
`);function u(e,t=[]){const o=["This server exposes documentation tools (API discovery, endpoint details, security schemes, full-text search) through two tools:","- `describe-tools` returns the exact TypeScript signatures of the sandbox tools.","- `execute` runs JavaScript that calls those tools on the global `tools` object.","Workflow: analyze the request, call `describe-tools` once with only the tool names it needs, then batch everything into a single `execute` script. Minimize round-trips and never guess tool signatures.",...e?["The sandbox also has a global `fetch` that can call the documented APIs (allow-listed hosts only). Include `fetch` in your `describe-tools` call to get its contract and the list of callable APIs."]:[]].join(`
|
|
5
5
|
`);return a(o,t)}const p=["Example - list the write endpoints of an API:","","```ts","const { items } = await tools.listApis({ filter: 'billing' });","const { endpoints } = await tools.getEndpoints({ name: items[0].name });","const writes = endpoints.filter((e) => e.method !== 'GET');","return { api: items[0].name, writes: writes.map((e) => `${e.method} ${e.path}`) };","```"].join(`
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{setupHostFunctions as a}from"../../../../sandbox/utils/setup-host-functions.js";import{evalGuestCode as p}from"../../../../sandbox/utils/eval-guest-code.js";import{SANDBOX_FETCH_SETUP as i}from"./polyfills.js";import{FETCH_BRIDGE_FUNCTION_NAME as u,FETCH_BRIDGE_GLOBAL_NAME as c,TOOLS_BRIDGE_GLOBAL_NAME as n}from"../constants.js";const m=100,f=50;function T(r,t){return(e,l)=>{const s=[a(e,r,{globalName:n,budget:l,maxCalls:m,messages:{outputTooLarge:o=>`Tool output is too large (${o} bytes); request narrower data instead`,callLimitExceeded:o=>`Exceeded the limit of ${o} tool calls`}})];return t&&s.push(a(e,{[u]:o=>t(o)},{globalName:c,budget:l,maxCalls:f,messages:{outputTooLarge:o=>`Response is too large (${o} bytes); request less data`,callLimitExceeded:o=>`Exceeded the limit of ${o} fetch calls`}})),p(e,E(!!t)),()=>{for(const o of s)o()}}}function E(r){const t=r?i:"",e=`globalThis[${JSON.stringify(n)}]`;return`(() => {${t}
|
|
1
|
+
import{setupHostFunctions as a}from"../../../../sandbox/utils/setup-host-functions.js";import{evalGuestCode as p}from"../../../../sandbox/utils/eval-guest-code.js";import{SANDBOX_FETCH_SETUP as i}from"./polyfills/index.js";import{FETCH_BRIDGE_FUNCTION_NAME as u,FETCH_BRIDGE_GLOBAL_NAME as c,TOOLS_BRIDGE_GLOBAL_NAME as n}from"../constants.js";const m=100,f=50;function T(r,t){return(e,l)=>{const s=[a(e,r,{globalName:n,budget:l,maxCalls:m,messages:{outputTooLarge:o=>`Tool output is too large (${o} bytes); request narrower data instead`,callLimitExceeded:o=>`Exceeded the limit of ${o} tool calls`}})];return t&&s.push(a(e,{[u]:o=>t(o)},{globalName:c,budget:l,maxCalls:f,messages:{outputTooLarge:o=>`Response is too large (${o} bytes); request less data`,callLimitExceeded:o=>`Exceeded the limit of ${o} fetch calls`}})),p(e,E(!!t)),()=>{for(const o of s)o()}}}function E(r){const t=r?i:"",e=`globalThis[${JSON.stringify(n)}]`;return`(() => {${t}
|
|
2
2
|
const tools = ${e};
|
|
3
3
|
${e} = new Proxy(tools, {
|
|
4
4
|
get(target, prop) {
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `fetch`: not a polyfill but a bridge. It takes the host rail off the global object so guest
|
|
3
|
+
* code cannot reach it directly, encodes the body, and hands the host a plain request.
|
|
4
|
+
*/
|
|
5
|
+
export declare const FETCH_SHIM = "\n const hostFetch = globalThis.__fetchBridge.fetch;\n delete globalThis.__fetchBridge;\n // RFC 7578: a field name travels with its quote and line breaks escaped, so a name cannot close its own header and open a part the caller did not write.\n const escapeFieldName = (name) =>\n String(name).replace(\n /[\"\\r\\n]/g,\n (char) => '%' + char.charCodeAt(0).toString(16).toUpperCase().padStart(2, '0'),\n );\n globalThis.fetch = (url, init = {}) => {\n let body;\n let autoContentType;\n if (init.body instanceof URLSearchParams) {\n body = init.body.toString();\n autoContentType = 'application/x-www-form-urlencoded';\n } else if (init.body instanceof FormData) {\n const boundary = '----FormBoundary' + Math.random().toString(36).slice(2);\n body = init.body._fields\n .map(([k, v]) =>\n '--' + boundary + '\\r\\nContent-Disposition: form-data; name=\"' + escapeFieldName(k) + '\"\\r\\n\\r\\n' + v,\n )\n .join('\\r\\n') + '\\r\\n--' + boundary + '--';\n autoContentType = 'multipart/form-data; boundary=' + boundary;\n } else if (init.body != null && typeof init.body !== 'string') {\n throw new TypeError('fetch body must be a string, URLSearchParams, or FormData');\n } else {\n body = init.body ?? undefined;\n }\n const callerHeaders = init.headers;\n const hasContentType = callerHeaders != null && Object.keys(callerHeaders).some(\n (k) => k.toLowerCase() === 'content-type',\n );\n const headers = autoContentType && !hasContentType\n ? Object.assign({ 'content-type': autoContentType }, callerHeaders)\n : callerHeaders;\n return hostFetch({ url: String(url), method: init.method, headers, body })\n .then((res) => {\n const resHeaders = res.headers || {};\n return {\n status: res.status,\n statusText: res.statusText,\n ok: res.ok,\n headers: {\n get: (name) => {\n const value = resHeaders[String(name).toLowerCase()];\n return value === undefined ? null : value;\n },\n },\n json: () => Promise.resolve(JSON.parse(res.bodyText)),\n text: () => Promise.resolve(res.bodyText),\n };\n });\n };\n";
|
|
6
|
+
//# sourceMappingURL=fetch-shim.d.ts.map
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import{FETCH_BRIDGE_FUNCTION_NAME as t,FETCH_BRIDGE_GLOBAL_NAME as e}from"../../constants.js";const n=`
|
|
2
|
+
const hostFetch = globalThis.${e}.${t};
|
|
3
|
+
delete globalThis.${e};
|
|
4
|
+
// RFC 7578: a field name travels with its quote and line breaks escaped, so a name cannot close its own header and open a part the caller did not write.
|
|
5
|
+
const escapeFieldName = (name) =>
|
|
6
|
+
String(name).replace(
|
|
7
|
+
/["\\r\\n]/g,
|
|
8
|
+
(char) => '%' + char.charCodeAt(0).toString(16).toUpperCase().padStart(2, '0'),
|
|
9
|
+
);
|
|
10
|
+
globalThis.fetch = (url, init = {}) => {
|
|
11
|
+
let body;
|
|
12
|
+
let autoContentType;
|
|
13
|
+
if (init.body instanceof URLSearchParams) {
|
|
14
|
+
body = init.body.toString();
|
|
15
|
+
autoContentType = 'application/x-www-form-urlencoded';
|
|
16
|
+
} else if (init.body instanceof FormData) {
|
|
17
|
+
const boundary = '----FormBoundary' + Math.random().toString(36).slice(2);
|
|
18
|
+
body = init.body._fields
|
|
19
|
+
.map(([k, v]) =>
|
|
20
|
+
'--' + boundary + '\\r\\nContent-Disposition: form-data; name="' + escapeFieldName(k) + '"\\r\\n\\r\\n' + v,
|
|
21
|
+
)
|
|
22
|
+
.join('\\r\\n') + '\\r\\n--' + boundary + '--';
|
|
23
|
+
autoContentType = 'multipart/form-data; boundary=' + boundary;
|
|
24
|
+
} else if (init.body != null && typeof init.body !== 'string') {
|
|
25
|
+
throw new TypeError('fetch body must be a string, URLSearchParams, or FormData');
|
|
26
|
+
} else {
|
|
27
|
+
body = init.body ?? undefined;
|
|
28
|
+
}
|
|
29
|
+
const callerHeaders = init.headers;
|
|
30
|
+
const hasContentType = callerHeaders != null && Object.keys(callerHeaders).some(
|
|
31
|
+
(k) => k.toLowerCase() === 'content-type',
|
|
32
|
+
);
|
|
33
|
+
const headers = autoContentType && !hasContentType
|
|
34
|
+
? Object.assign({ 'content-type': autoContentType }, callerHeaders)
|
|
35
|
+
: callerHeaders;
|
|
36
|
+
return hostFetch({ url: String(url), method: init.method, headers, body })
|
|
37
|
+
.then((res) => {
|
|
38
|
+
const resHeaders = res.headers || {};
|
|
39
|
+
return {
|
|
40
|
+
status: res.status,
|
|
41
|
+
statusText: res.statusText,
|
|
42
|
+
ok: res.ok,
|
|
43
|
+
headers: {
|
|
44
|
+
get: (name) => {
|
|
45
|
+
const value = resHeaders[String(name).toLowerCase()];
|
|
46
|
+
return value === undefined ? null : value;
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
json: () => Promise.resolve(JSON.parse(res.bodyText)),
|
|
50
|
+
text: () => Promise.resolve(res.bodyText),
|
|
51
|
+
};
|
|
52
|
+
});
|
|
53
|
+
};
|
|
54
|
+
`;export{n as FETCH_SHIM};
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
/** `FormData`: the multipart body the fetch shim serializes. */
|
|
2
|
+
export declare const FORM_DATA_POLYFILL = "\n const FormData = class FormData {\n constructor() { this._fields = []; }\n append(key, value) { this._fields.push([String(key), String(value)]); }\n set(key, value) {\n const k = String(key), v = String(value);\n this._fields = this._fields.filter(([fk]) => fk !== k);\n this._fields.push([k, v]);\n }\n get(key) { return (this._fields.find(([k]) => k === String(key)) ?? [undefined, null])[1]; }\n has(key) { return this._fields.some(([k]) => k === String(key)); }\n delete(key) { this._fields = this._fields.filter(([k]) => k !== String(key)); }\n entries() { return this._fields.values(); }\n [Symbol.iterator]() { return this._fields.values(); }\n };\n globalThis.FormData = FormData;\n";
|
|
3
|
+
//# sourceMappingURL=form-data.d.ts.map
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
const e=`
|
|
2
|
+
const FormData = class FormData {
|
|
3
|
+
constructor() { this._fields = []; }
|
|
4
|
+
append(key, value) { this._fields.push([String(key), String(value)]); }
|
|
5
|
+
set(key, value) {
|
|
6
|
+
const k = String(key), v = String(value);
|
|
7
|
+
this._fields = this._fields.filter(([fk]) => fk !== k);
|
|
8
|
+
this._fields.push([k, v]);
|
|
9
|
+
}
|
|
10
|
+
get(key) { return (this._fields.find(([k]) => k === String(key)) ?? [undefined, null])[1]; }
|
|
11
|
+
has(key) { return this._fields.some(([k]) => k === String(key)); }
|
|
12
|
+
delete(key) { this._fields = this._fields.filter(([k]) => k !== String(key)); }
|
|
13
|
+
entries() { return this._fields.values(); }
|
|
14
|
+
[Symbol.iterator]() { return this._fields.values(); }
|
|
15
|
+
};
|
|
16
|
+
globalThis.FormData = FormData;
|
|
17
|
+
`;export{e as FORM_DATA_POLYFILL};
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
/** `URLSearchParams`: query-string parsing and serialization, and the store behind `URL.searchParams`. */
|
|
2
|
+
export declare const URL_SEARCH_PARAMS_POLYFILL = "\n // application/x-www-form-urlencoded reads '+' as a space, and leaves an escape it cannot read alone.\n // Decoding one well-formed run at a time is what keeps a stray '%' \u2014 '?discount=50%', a half-encoded cursor \u2014 from throwing URIError and ending the run.\n const decodeFormPart = (value) =>\n value.replace(/\\+/g, ' ').replace(/(?:%[0-9A-Fa-f]{2})+/g, (escaped) => {\n try { return decodeURIComponent(escaped); } catch (error) { return escaped; }\n });\n\n // The same rules in reverse: everything but alphanumerics and * - . _ is percent-encoded, and a space is '+'. encodeURIComponent keeps !'()~ literal, so those are encoded after it runs.\n const encodeFormPart = (value) =>\n encodeURIComponent(String(value))\n .replace(/%20/g, '+')\n .replace(/[!'()~]/g, (char) => '%' + char.charCodeAt(0).toString(16).toUpperCase());\n\n const URLSearchParams = class URLSearchParams {\n constructor(init) {\n this._params = [];\n if (init == null) return;\n if (typeof init === 'string') {\n const str = init.startsWith('?') ? init.slice(1) : init;\n for (const pair of (str ? str.split('&') : [])) {\n if (pair === '') continue;\n const eq = pair.indexOf('=');\n const key = eq >= 0 ? pair.slice(0, eq) : pair;\n const value = eq >= 0 ? pair.slice(eq + 1) : '';\n this._params.push([decodeFormPart(key), decodeFormPart(value)]);\n }\n } else if (typeof init[Symbol.iterator] === 'function') {\n // [['a', '1'], ['b', '2']] and another URLSearchParams both arrive here.\n for (const [key, value] of init) this._params.push([String(key), String(value)]);\n } else if (typeof init === 'object') {\n for (const [key, value] of Object.entries(init)) this._params.push([key, String(value)]);\n }\n }\n get size() { return this._params.length; }\n append(key, value) { this._params.push([String(key), String(value)]); }\n set(key, value) {\n const k = String(key), v = String(value);\n const at = this._params.findIndex(([pk]) => pk === k);\n if (at === -1) { this._params.push([k, v]); return; }\n // Overwrite where the key already sat, so an edit does not reorder the query the server sent.\n this._params[at] = [k, v];\n this._params = this._params.filter(([pk], index) => pk !== k || index === at);\n }\n get(key) { return (this._params.find(([k]) => k === String(key)) ?? [undefined, null])[1]; }\n getAll(key) { return this._params.filter(([k]) => k === String(key)).map(([, v]) => v); }\n has(key) { return this._params.some(([k]) => k === String(key)); }\n delete(key) { this._params = this._params.filter(([k]) => k !== String(key)); }\n forEach(callback, thisArg) {\n for (const [k, v] of [...this._params]) callback.call(thisArg, v, k, this);\n }\n keys() { return this._params.map(([k]) => k).values(); }\n values() { return this._params.map(([, v]) => v).values(); }\n entries() { return this._params.map(([k, v]) => [k, v]).values(); }\n sort() { this._params.sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0)); }\n [Symbol.iterator]() { return this.entries(); }\n toString() {\n return this._params\n .map(([k, v]) => encodeFormPart(k) + '=' + encodeFormPart(v))\n .join('&');\n }\n };\n globalThis.URLSearchParams = URLSearchParams;\n";
|
|
3
|
+
//# sourceMappingURL=url-search-params.d.ts.map
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
const e=`
|
|
2
|
+
// application/x-www-form-urlencoded reads '+' as a space, and leaves an escape it cannot read alone.
|
|
3
|
+
// Decoding one well-formed run at a time is what keeps a stray '%' \u2014 '?discount=50%', a half-encoded cursor \u2014 from throwing URIError and ending the run.
|
|
4
|
+
const decodeFormPart = (value) =>
|
|
5
|
+
value.replace(/\\+/g, ' ').replace(/(?:%[0-9A-Fa-f]{2})+/g, (escaped) => {
|
|
6
|
+
try { return decodeURIComponent(escaped); } catch (error) { return escaped; }
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
// The same rules in reverse: everything but alphanumerics and * - . _ is percent-encoded, and a space is '+'. encodeURIComponent keeps !'()~ literal, so those are encoded after it runs.
|
|
10
|
+
const encodeFormPart = (value) =>
|
|
11
|
+
encodeURIComponent(String(value))
|
|
12
|
+
.replace(/%20/g, '+')
|
|
13
|
+
.replace(/[!'()~]/g, (char) => '%' + char.charCodeAt(0).toString(16).toUpperCase());
|
|
14
|
+
|
|
15
|
+
const URLSearchParams = class URLSearchParams {
|
|
16
|
+
constructor(init) {
|
|
17
|
+
this._params = [];
|
|
18
|
+
if (init == null) return;
|
|
19
|
+
if (typeof init === 'string') {
|
|
20
|
+
const str = init.startsWith('?') ? init.slice(1) : init;
|
|
21
|
+
for (const pair of (str ? str.split('&') : [])) {
|
|
22
|
+
if (pair === '') continue;
|
|
23
|
+
const eq = pair.indexOf('=');
|
|
24
|
+
const key = eq >= 0 ? pair.slice(0, eq) : pair;
|
|
25
|
+
const value = eq >= 0 ? pair.slice(eq + 1) : '';
|
|
26
|
+
this._params.push([decodeFormPart(key), decodeFormPart(value)]);
|
|
27
|
+
}
|
|
28
|
+
} else if (typeof init[Symbol.iterator] === 'function') {
|
|
29
|
+
// [['a', '1'], ['b', '2']] and another URLSearchParams both arrive here.
|
|
30
|
+
for (const [key, value] of init) this._params.push([String(key), String(value)]);
|
|
31
|
+
} else if (typeof init === 'object') {
|
|
32
|
+
for (const [key, value] of Object.entries(init)) this._params.push([key, String(value)]);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
get size() { return this._params.length; }
|
|
36
|
+
append(key, value) { this._params.push([String(key), String(value)]); }
|
|
37
|
+
set(key, value) {
|
|
38
|
+
const k = String(key), v = String(value);
|
|
39
|
+
const at = this._params.findIndex(([pk]) => pk === k);
|
|
40
|
+
if (at === -1) { this._params.push([k, v]); return; }
|
|
41
|
+
// Overwrite where the key already sat, so an edit does not reorder the query the server sent.
|
|
42
|
+
this._params[at] = [k, v];
|
|
43
|
+
this._params = this._params.filter(([pk], index) => pk !== k || index === at);
|
|
44
|
+
}
|
|
45
|
+
get(key) { return (this._params.find(([k]) => k === String(key)) ?? [undefined, null])[1]; }
|
|
46
|
+
getAll(key) { return this._params.filter(([k]) => k === String(key)).map(([, v]) => v); }
|
|
47
|
+
has(key) { return this._params.some(([k]) => k === String(key)); }
|
|
48
|
+
delete(key) { this._params = this._params.filter(([k]) => k !== String(key)); }
|
|
49
|
+
forEach(callback, thisArg) {
|
|
50
|
+
for (const [k, v] of [...this._params]) callback.call(thisArg, v, k, this);
|
|
51
|
+
}
|
|
52
|
+
keys() { return this._params.map(([k]) => k).values(); }
|
|
53
|
+
values() { return this._params.map(([, v]) => v).values(); }
|
|
54
|
+
entries() { return this._params.map(([k, v]) => [k, v]).values(); }
|
|
55
|
+
sort() { this._params.sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0)); }
|
|
56
|
+
[Symbol.iterator]() { return this.entries(); }
|
|
57
|
+
toString() {
|
|
58
|
+
return this._params
|
|
59
|
+
.map(([k, v]) => encodeFormPart(k) + '=' + encodeFormPart(v))
|
|
60
|
+
.join('&');
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
globalThis.URLSearchParams = URLSearchParams;
|
|
64
|
+
`;export{e as URL_SEARCH_PARAMS_POLYFILL};
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
/** `URL`: parses and resolves a url string, and exposes its parts. */
|
|
2
|
+
export declare const URL_POLYFILL = "\n // Collapses '.' and '..'. A backslash separates path segments, the way the host parser reads\n // one, so it is folded into '/' first \u2014 otherwise the two disagree about where the host name\n // ends in 'https://a.example\\@b.example/', and guest code reads a host the gateway never calls.\n const normalizePath = (rawPath) => {\n const path = rawPath.replace(/\\\\/g, '/');\n const out = [];\n for (const segment of path.split('/')) {\n if (segment === '.') continue;\n if (segment === '..') { if (out.length > 1) out.pop(); continue; }\n out.push(segment);\n }\n let joined = out.join('/');\n if ((path.endsWith('/.') || path.endsWith('/..')) && !joined.endsWith('/')) joined += '/';\n return joined;\n };\n\n const DEFAULT_PORTS = { 'http:': '80', 'https:': '443', 'ws:': '80', 'wss:': '443', 'ftp:': '21' };\n\n // Percent-encode what cannot travel literally in a url, so a hand-assembled path or query\n // reaches the host parser as the caller meant it. Non-ASCII goes out as UTF-8, in runs, so a\n // surrogate pair is encoded as the one character it stands for.\n const encodeLiterals = (value) =>\n value.replace(/[\\s\"<>\\`{}\\\\^]|[^\\x00-\\x7F]+/g, (chunk) => {\n try { return encodeURIComponent(chunk); } catch (error) { return chunk; }\n });\n\n // A non-ASCII host name is lower-cased but not punycoded \u2014 that needs an IDNA table this sandbox\n // does not carry. The host re-parses the url before it fetches, so the request still goes to the\n // punycoded name; only what guest code reads back differs.\n const parseHostPort = (hostport, scheme) => {\n let hostname = hostport;\n let port = '';\n if (hostport.startsWith('[')) {\n const close = hostport.indexOf(']');\n hostname = hostport.slice(0, close + 1);\n const after = hostport.slice(close + 1);\n if (after.startsWith(':')) port = after.slice(1);\n } else {\n const colon = hostport.lastIndexOf(':');\n if (colon !== -1) { hostname = hostport.slice(0, colon); port = hostport.slice(colon + 1); }\n }\n if (port !== '' && !/^[0-9]+$/.test(port)) return null;\n if (hostname === '') return null;\n return {\n hostname: hostname.toLowerCase(),\n port: DEFAULT_PORTS[scheme] === port ? '' : port,\n // A host written without a port leaves the port alone, rather than clearing it.\n hasPort: port !== '',\n };\n };\n\n const ABSOLUTE_RE = /^([A-Za-z][A-Za-z0-9+\\-.]*):(\\/\\/)?([\\s\\S]*)$/;\n\n const parseAuthorityUrl = (scheme, rest) => {\n const cut = rest.search(/[/\\\\?#]/);\n const authority = cut === -1 ? rest : rest.slice(0, cut);\n let tail = cut === -1 ? '' : rest.slice(cut);\n let username = '';\n let password = '';\n let hostport = authority;\n const at = authority.lastIndexOf('@');\n if (at !== -1) {\n const credentials = authority.slice(0, at);\n hostport = authority.slice(at + 1);\n const colon = credentials.indexOf(':');\n username = colon === -1 ? credentials : credentials.slice(0, colon);\n password = colon === -1 ? '' : credentials.slice(colon + 1);\n }\n const hostPort = parseHostPort(hostport, scheme);\n if (!hostPort) return null;\n let hash = '';\n const hashAt = tail.indexOf('#');\n if (hashAt !== -1) { hash = tail.slice(hashAt); tail = tail.slice(0, hashAt); }\n let search = '';\n const queryAt = tail.indexOf('?');\n if (queryAt !== -1) { search = tail.slice(queryAt); tail = tail.slice(0, queryAt); }\n let pathname = tail === '' ? '/' : tail;\n if (!/^[/\\\\]/.test(pathname)) pathname = '/' + pathname;\n return {\n protocol: scheme,\n username,\n password,\n hostname: hostPort.hostname,\n port: hostPort.port,\n pathname: encodeLiterals(normalizePath(pathname)),\n search: encodeLiterals(search === '?' ? '' : search),\n hash: encodeLiterals(hash === '#' ? '' : hash),\n };\n };\n\n const parseUrl = (input, base) => {\n // A tab or newline inside a url is dropped, not encoded \u2014 the host parser does the same, and\n // a url copied out of a wrapped response body is the usual way one gets in here.\n const str = String(input).trim().replace(/[\\t\\n\\r]/g, '');\n const matched = ABSOLUTE_RE.exec(str);\n // A scheme with no `//` (mailto:, data:) is not a fetch target, so it is not resolved.\n if (matched) return matched[2] ? parseAuthorityUrl(matched[1].toLowerCase() + ':', matched[3]) : null;\n if (base === undefined) return null;\n const resolved = parseUrl(base, undefined);\n if (!resolved) return null;\n if (/^[/\\\\]{2}/.test(str)) return parseAuthorityUrl(resolved.protocol, str.slice(2));\n const parts = { ...resolved };\n if (str.startsWith('#')) {\n parts.hash = encodeLiterals(str === '#' ? '' : str);\n return parts;\n }\n let rest = str;\n let hash = '';\n const hashAt = rest.indexOf('#');\n if (hashAt !== -1) { hash = rest.slice(hashAt); rest = rest.slice(0, hashAt); }\n let search = '';\n const queryAt = rest.indexOf('?');\n if (queryAt !== -1) { search = rest.slice(queryAt); rest = rest.slice(0, queryAt); }\n if (rest !== '') {\n const absolute = /^[/\\\\]/.test(rest)\n ? rest\n : resolved.pathname.slice(0, resolved.pathname.lastIndexOf('/') + 1) + rest;\n parts.pathname = encodeLiterals(normalizePath(absolute));\n }\n // An empty reference with no query of its own keeps the base query (RFC 3986 section 5.3).\n parts.search = rest === '' && search === '' ? resolved.search : encodeLiterals(search === '?' ? '' : search);\n parts.hash = encodeLiterals(hash === '#' ? '' : hash);\n return parts;\n };\n\n const URL = class URL {\n constructor(input, base) {\n const parsed = parseUrl(input, base === undefined ? undefined : String(base));\n if (!parsed) throw new TypeError('Invalid URL: ' + String(input));\n this._parts = parsed;\n this._params = undefined;\n this._edited = false;\n }\n // The query stays exactly as it arrived until searchParams edits it, so reading a url apart\n // and putting it back together does not re-encode what the server sent.\n _applyParams() {\n if (this._edited && this._params) {\n const query = this._params.toString();\n this._parts.search = query ? '?' + query : '';\n this._edited = false;\n }\n }\n // Every part a caller can read, it can also write. Guest code runs sloppy, so a missing\n // setter would swallow `url.host = \u2026` and fetch the address the caller meant to replace.\n get protocol() { return this._parts.protocol; }\n set protocol(value) {\n const next = String(value).toLowerCase().replace(/:$/, '') + ':';\n // Only a scheme that carries an authority can replace one, the way the host parser reads it.\n if (DEFAULT_PORTS[next] !== undefined) this._parts.protocol = next;\n }\n get username() { return this._parts.username; }\n set username(value) { this._parts.username = String(value); }\n get password() { return this._parts.password; }\n set password(value) { this._parts.password = String(value); }\n get hostname() { return this._parts.hostname; }\n set hostname(value) { this._parts.hostname = String(value).toLowerCase(); }\n get port() { return this._parts.port; }\n set port(value) {\n const port = String(value);\n if (port !== '' && !/^[0-9]+$/.test(port)) return;\n this._parts.port = DEFAULT_PORTS[this._parts.protocol] === port ? '' : port;\n }\n get host() {\n return this._parts.port ? this._parts.hostname + ':' + this._parts.port : this._parts.hostname;\n }\n set host(value) {\n const parsed = parseHostPort(String(value), this._parts.protocol);\n if (!parsed) return;\n this._parts.hostname = parsed.hostname;\n if (parsed.hasPort) this._parts.port = parsed.port;\n }\n get origin() { return this._parts.protocol + '//' + this.host; }\n get pathname() { return this._parts.pathname; }\n set pathname(value) {\n const path = String(value);\n this._parts.pathname = encodeLiterals(path.startsWith('/') ? path : '/' + path);\n }\n get search() {\n this._applyParams();\n return this._parts.search;\n }\n set search(value) {\n const query = String(value);\n const body = query.startsWith('?') ? query.slice(1) : query;\n this._parts.search = body ? encodeLiterals('?' + body) : '';\n this._params = undefined;\n this._edited = false;\n }\n get searchParams() {\n if (!this._params) {\n const params = new URLSearchParams(this._parts.search);\n const url = this;\n // Every method that can change the parameters has to mark the query for re-serialization.\n for (const method of ['append', 'set', 'delete', 'sort']) {\n const original = params[method];\n params[method] = function (...args) {\n url._edited = true;\n return original.apply(params, args);\n };\n }\n this._params = params;\n }\n return this._params;\n }\n get hash() { return this._parts.hash; }\n set hash(value) {\n const fragment = String(value);\n this._parts.hash = fragment === ''\n ? ''\n : encodeLiterals(fragment.startsWith('#') ? fragment : '#' + fragment);\n }\n get href() {\n this._applyParams();\n const credentials = this._parts.username\n ? this._parts.username + (this._parts.password ? ':' + this._parts.password : '') + '@'\n : '';\n return this._parts.protocol + '//' + credentials + this.host +\n this._parts.pathname + this._parts.search + this._parts.hash;\n }\n set href(value) {\n const parsed = parseUrl(value, undefined);\n if (!parsed) throw new TypeError('Invalid URL: ' + String(value));\n this._parts = parsed;\n this._params = undefined;\n this._edited = false;\n }\n toString() { return this.href; }\n toJSON() { return this.href; }\n static canParse(input, base) {\n return parseUrl(input, base === undefined ? undefined : String(base)) !== null;\n }\n static parse(input, base) {\n const parsed = parseUrl(input, base === undefined ? undefined : String(base));\n return parsed ? new URL(input, base) : null;\n }\n };\n globalThis.URL = URL;\n";
|
|
3
|
+
//# sourceMappingURL=url.d.ts.map
|
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
const t=`
|
|
2
|
+
// Collapses '.' and '..'. A backslash separates path segments, the way the host parser reads
|
|
3
|
+
// one, so it is folded into '/' first \u2014 otherwise the two disagree about where the host name
|
|
4
|
+
// ends in 'https://a.example\\@b.example/', and guest code reads a host the gateway never calls.
|
|
5
|
+
const normalizePath = (rawPath) => {
|
|
6
|
+
const path = rawPath.replace(/\\\\/g, '/');
|
|
7
|
+
const out = [];
|
|
8
|
+
for (const segment of path.split('/')) {
|
|
9
|
+
if (segment === '.') continue;
|
|
10
|
+
if (segment === '..') { if (out.length > 1) out.pop(); continue; }
|
|
11
|
+
out.push(segment);
|
|
12
|
+
}
|
|
13
|
+
let joined = out.join('/');
|
|
14
|
+
if ((path.endsWith('/.') || path.endsWith('/..')) && !joined.endsWith('/')) joined += '/';
|
|
15
|
+
return joined;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
const DEFAULT_PORTS = { 'http:': '80', 'https:': '443', 'ws:': '80', 'wss:': '443', 'ftp:': '21' };
|
|
19
|
+
|
|
20
|
+
// Percent-encode what cannot travel literally in a url, so a hand-assembled path or query
|
|
21
|
+
// reaches the host parser as the caller meant it. Non-ASCII goes out as UTF-8, in runs, so a
|
|
22
|
+
// surrogate pair is encoded as the one character it stands for.
|
|
23
|
+
const encodeLiterals = (value) =>
|
|
24
|
+
value.replace(/[\\s"<>\\\`{}\\\\^]|[^\\x00-\\x7F]+/g, (chunk) => {
|
|
25
|
+
try { return encodeURIComponent(chunk); } catch (error) { return chunk; }
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
// A non-ASCII host name is lower-cased but not punycoded \u2014 that needs an IDNA table this sandbox
|
|
29
|
+
// does not carry. The host re-parses the url before it fetches, so the request still goes to the
|
|
30
|
+
// punycoded name; only what guest code reads back differs.
|
|
31
|
+
const parseHostPort = (hostport, scheme) => {
|
|
32
|
+
let hostname = hostport;
|
|
33
|
+
let port = '';
|
|
34
|
+
if (hostport.startsWith('[')) {
|
|
35
|
+
const close = hostport.indexOf(']');
|
|
36
|
+
hostname = hostport.slice(0, close + 1);
|
|
37
|
+
const after = hostport.slice(close + 1);
|
|
38
|
+
if (after.startsWith(':')) port = after.slice(1);
|
|
39
|
+
} else {
|
|
40
|
+
const colon = hostport.lastIndexOf(':');
|
|
41
|
+
if (colon !== -1) { hostname = hostport.slice(0, colon); port = hostport.slice(colon + 1); }
|
|
42
|
+
}
|
|
43
|
+
if (port !== '' && !/^[0-9]+$/.test(port)) return null;
|
|
44
|
+
if (hostname === '') return null;
|
|
45
|
+
return {
|
|
46
|
+
hostname: hostname.toLowerCase(),
|
|
47
|
+
port: DEFAULT_PORTS[scheme] === port ? '' : port,
|
|
48
|
+
// A host written without a port leaves the port alone, rather than clearing it.
|
|
49
|
+
hasPort: port !== '',
|
|
50
|
+
};
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
const ABSOLUTE_RE = /^([A-Za-z][A-Za-z0-9+\\-.]*):(\\/\\/)?([\\s\\S]*)$/;
|
|
54
|
+
|
|
55
|
+
const parseAuthorityUrl = (scheme, rest) => {
|
|
56
|
+
const cut = rest.search(/[/\\\\?#]/);
|
|
57
|
+
const authority = cut === -1 ? rest : rest.slice(0, cut);
|
|
58
|
+
let tail = cut === -1 ? '' : rest.slice(cut);
|
|
59
|
+
let username = '';
|
|
60
|
+
let password = '';
|
|
61
|
+
let hostport = authority;
|
|
62
|
+
const at = authority.lastIndexOf('@');
|
|
63
|
+
if (at !== -1) {
|
|
64
|
+
const credentials = authority.slice(0, at);
|
|
65
|
+
hostport = authority.slice(at + 1);
|
|
66
|
+
const colon = credentials.indexOf(':');
|
|
67
|
+
username = colon === -1 ? credentials : credentials.slice(0, colon);
|
|
68
|
+
password = colon === -1 ? '' : credentials.slice(colon + 1);
|
|
69
|
+
}
|
|
70
|
+
const hostPort = parseHostPort(hostport, scheme);
|
|
71
|
+
if (!hostPort) return null;
|
|
72
|
+
let hash = '';
|
|
73
|
+
const hashAt = tail.indexOf('#');
|
|
74
|
+
if (hashAt !== -1) { hash = tail.slice(hashAt); tail = tail.slice(0, hashAt); }
|
|
75
|
+
let search = '';
|
|
76
|
+
const queryAt = tail.indexOf('?');
|
|
77
|
+
if (queryAt !== -1) { search = tail.slice(queryAt); tail = tail.slice(0, queryAt); }
|
|
78
|
+
let pathname = tail === '' ? '/' : tail;
|
|
79
|
+
if (!/^[/\\\\]/.test(pathname)) pathname = '/' + pathname;
|
|
80
|
+
return {
|
|
81
|
+
protocol: scheme,
|
|
82
|
+
username,
|
|
83
|
+
password,
|
|
84
|
+
hostname: hostPort.hostname,
|
|
85
|
+
port: hostPort.port,
|
|
86
|
+
pathname: encodeLiterals(normalizePath(pathname)),
|
|
87
|
+
search: encodeLiterals(search === '?' ? '' : search),
|
|
88
|
+
hash: encodeLiterals(hash === '#' ? '' : hash),
|
|
89
|
+
};
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
const parseUrl = (input, base) => {
|
|
93
|
+
// A tab or newline inside a url is dropped, not encoded \u2014 the host parser does the same, and
|
|
94
|
+
// a url copied out of a wrapped response body is the usual way one gets in here.
|
|
95
|
+
const str = String(input).trim().replace(/[\\t\\n\\r]/g, '');
|
|
96
|
+
const matched = ABSOLUTE_RE.exec(str);
|
|
97
|
+
// A scheme with no \`//\` (mailto:, data:) is not a fetch target, so it is not resolved.
|
|
98
|
+
if (matched) return matched[2] ? parseAuthorityUrl(matched[1].toLowerCase() + ':', matched[3]) : null;
|
|
99
|
+
if (base === undefined) return null;
|
|
100
|
+
const resolved = parseUrl(base, undefined);
|
|
101
|
+
if (!resolved) return null;
|
|
102
|
+
if (/^[/\\\\]{2}/.test(str)) return parseAuthorityUrl(resolved.protocol, str.slice(2));
|
|
103
|
+
const parts = { ...resolved };
|
|
104
|
+
if (str.startsWith('#')) {
|
|
105
|
+
parts.hash = encodeLiterals(str === '#' ? '' : str);
|
|
106
|
+
return parts;
|
|
107
|
+
}
|
|
108
|
+
let rest = str;
|
|
109
|
+
let hash = '';
|
|
110
|
+
const hashAt = rest.indexOf('#');
|
|
111
|
+
if (hashAt !== -1) { hash = rest.slice(hashAt); rest = rest.slice(0, hashAt); }
|
|
112
|
+
let search = '';
|
|
113
|
+
const queryAt = rest.indexOf('?');
|
|
114
|
+
if (queryAt !== -1) { search = rest.slice(queryAt); rest = rest.slice(0, queryAt); }
|
|
115
|
+
if (rest !== '') {
|
|
116
|
+
const absolute = /^[/\\\\]/.test(rest)
|
|
117
|
+
? rest
|
|
118
|
+
: resolved.pathname.slice(0, resolved.pathname.lastIndexOf('/') + 1) + rest;
|
|
119
|
+
parts.pathname = encodeLiterals(normalizePath(absolute));
|
|
120
|
+
}
|
|
121
|
+
// An empty reference with no query of its own keeps the base query (RFC 3986 section 5.3).
|
|
122
|
+
parts.search = rest === '' && search === '' ? resolved.search : encodeLiterals(search === '?' ? '' : search);
|
|
123
|
+
parts.hash = encodeLiterals(hash === '#' ? '' : hash);
|
|
124
|
+
return parts;
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
const URL = class URL {
|
|
128
|
+
constructor(input, base) {
|
|
129
|
+
const parsed = parseUrl(input, base === undefined ? undefined : String(base));
|
|
130
|
+
if (!parsed) throw new TypeError('Invalid URL: ' + String(input));
|
|
131
|
+
this._parts = parsed;
|
|
132
|
+
this._params = undefined;
|
|
133
|
+
this._edited = false;
|
|
134
|
+
}
|
|
135
|
+
// The query stays exactly as it arrived until searchParams edits it, so reading a url apart
|
|
136
|
+
// and putting it back together does not re-encode what the server sent.
|
|
137
|
+
_applyParams() {
|
|
138
|
+
if (this._edited && this._params) {
|
|
139
|
+
const query = this._params.toString();
|
|
140
|
+
this._parts.search = query ? '?' + query : '';
|
|
141
|
+
this._edited = false;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
// Every part a caller can read, it can also write. Guest code runs sloppy, so a missing
|
|
145
|
+
// setter would swallow \`url.host = \u2026\` and fetch the address the caller meant to replace.
|
|
146
|
+
get protocol() { return this._parts.protocol; }
|
|
147
|
+
set protocol(value) {
|
|
148
|
+
const next = String(value).toLowerCase().replace(/:$/, '') + ':';
|
|
149
|
+
// Only a scheme that carries an authority can replace one, the way the host parser reads it.
|
|
150
|
+
if (DEFAULT_PORTS[next] !== undefined) this._parts.protocol = next;
|
|
151
|
+
}
|
|
152
|
+
get username() { return this._parts.username; }
|
|
153
|
+
set username(value) { this._parts.username = String(value); }
|
|
154
|
+
get password() { return this._parts.password; }
|
|
155
|
+
set password(value) { this._parts.password = String(value); }
|
|
156
|
+
get hostname() { return this._parts.hostname; }
|
|
157
|
+
set hostname(value) { this._parts.hostname = String(value).toLowerCase(); }
|
|
158
|
+
get port() { return this._parts.port; }
|
|
159
|
+
set port(value) {
|
|
160
|
+
const port = String(value);
|
|
161
|
+
if (port !== '' && !/^[0-9]+$/.test(port)) return;
|
|
162
|
+
this._parts.port = DEFAULT_PORTS[this._parts.protocol] === port ? '' : port;
|
|
163
|
+
}
|
|
164
|
+
get host() {
|
|
165
|
+
return this._parts.port ? this._parts.hostname + ':' + this._parts.port : this._parts.hostname;
|
|
166
|
+
}
|
|
167
|
+
set host(value) {
|
|
168
|
+
const parsed = parseHostPort(String(value), this._parts.protocol);
|
|
169
|
+
if (!parsed) return;
|
|
170
|
+
this._parts.hostname = parsed.hostname;
|
|
171
|
+
if (parsed.hasPort) this._parts.port = parsed.port;
|
|
172
|
+
}
|
|
173
|
+
get origin() { return this._parts.protocol + '//' + this.host; }
|
|
174
|
+
get pathname() { return this._parts.pathname; }
|
|
175
|
+
set pathname(value) {
|
|
176
|
+
const path = String(value);
|
|
177
|
+
this._parts.pathname = encodeLiterals(path.startsWith('/') ? path : '/' + path);
|
|
178
|
+
}
|
|
179
|
+
get search() {
|
|
180
|
+
this._applyParams();
|
|
181
|
+
return this._parts.search;
|
|
182
|
+
}
|
|
183
|
+
set search(value) {
|
|
184
|
+
const query = String(value);
|
|
185
|
+
const body = query.startsWith('?') ? query.slice(1) : query;
|
|
186
|
+
this._parts.search = body ? encodeLiterals('?' + body) : '';
|
|
187
|
+
this._params = undefined;
|
|
188
|
+
this._edited = false;
|
|
189
|
+
}
|
|
190
|
+
get searchParams() {
|
|
191
|
+
if (!this._params) {
|
|
192
|
+
const params = new URLSearchParams(this._parts.search);
|
|
193
|
+
const url = this;
|
|
194
|
+
// Every method that can change the parameters has to mark the query for re-serialization.
|
|
195
|
+
for (const method of ['append', 'set', 'delete', 'sort']) {
|
|
196
|
+
const original = params[method];
|
|
197
|
+
params[method] = function (...args) {
|
|
198
|
+
url._edited = true;
|
|
199
|
+
return original.apply(params, args);
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
this._params = params;
|
|
203
|
+
}
|
|
204
|
+
return this._params;
|
|
205
|
+
}
|
|
206
|
+
get hash() { return this._parts.hash; }
|
|
207
|
+
set hash(value) {
|
|
208
|
+
const fragment = String(value);
|
|
209
|
+
this._parts.hash = fragment === ''
|
|
210
|
+
? ''
|
|
211
|
+
: encodeLiterals(fragment.startsWith('#') ? fragment : '#' + fragment);
|
|
212
|
+
}
|
|
213
|
+
get href() {
|
|
214
|
+
this._applyParams();
|
|
215
|
+
const credentials = this._parts.username
|
|
216
|
+
? this._parts.username + (this._parts.password ? ':' + this._parts.password : '') + '@'
|
|
217
|
+
: '';
|
|
218
|
+
return this._parts.protocol + '//' + credentials + this.host +
|
|
219
|
+
this._parts.pathname + this._parts.search + this._parts.hash;
|
|
220
|
+
}
|
|
221
|
+
set href(value) {
|
|
222
|
+
const parsed = parseUrl(value, undefined);
|
|
223
|
+
if (!parsed) throw new TypeError('Invalid URL: ' + String(value));
|
|
224
|
+
this._parts = parsed;
|
|
225
|
+
this._params = undefined;
|
|
226
|
+
this._edited = false;
|
|
227
|
+
}
|
|
228
|
+
toString() { return this.href; }
|
|
229
|
+
toJSON() { return this.href; }
|
|
230
|
+
static canParse(input, base) {
|
|
231
|
+
return parseUrl(input, base === undefined ? undefined : String(base)) !== null;
|
|
232
|
+
}
|
|
233
|
+
static parse(input, base) {
|
|
234
|
+
const parsed = parseUrl(input, base === undefined ? undefined : String(base));
|
|
235
|
+
return parsed ? new URL(input, base) : null;
|
|
236
|
+
}
|
|
237
|
+
};
|
|
238
|
+
globalThis.URL = URL;
|
|
239
|
+
`;export{t as URL_POLYFILL};
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import{inputRequired as
|
|
2
|
-
`)}async function d({kv:e,subject:t,baseUrl:n,target:i,auth:a,hasExistingSecret:o}){const r=await
|
|
3
|
-
`)}export{
|
|
1
|
+
import{inputRequired as c,inputResponse as f}from"@modelcontextprotocol/server";import{MANAGE_API_CREDENTIALS_TOOL_NAME as T}from"../constants.js";import{secretStore as $,ELICITATION_TTL_MS as A}from"./secret-store.js";import{ANONYMOUS_SUBJECT_PREFIX as l}from"./subject.js";const h="credentials";function C(e){const t=f(e,h);return t.kind==="elicit"&&(t.action==="decline"||t.action==="cancel")}function _(e){return{api:e.api,name:e.name}}async function b(e){const{url:t}=await d(e);return c({inputRequests:{[h]:c.elicitUrl({message:P(e.target.name,e.hasExistingSecret),url:t})}})}async function O(e){const{url:t}=await d(e);return y(e,t,I(e.subject))}function R(e){return[e.status===401?`The stored credentials for "${e.name}" were rejected (HTTP 401).`:`The "${e.name}" API rejected the request with HTTP ${e.status}.`,"",`Do not retry the same call. Call \`${T}\` with api: "${e.api}" to get a link the user has to open in a browser to replace or remove the stored credentials, then retry once they confirm they saved them.`].join(`
|
|
2
|
+
`)}async function d({kv:e,subject:t,baseUrl:n,target:i,auth:a,hasExistingSecret:o}){const r=await $.createElicitation(e,{subject:t,api:i.api,name:i.name,auth:a,hasExistingSecret:o});return{token:r,url:g(n,r)}}function I(e){return e.startsWith(l)?e.slice(l.length):void 0}function P(e,t){const n=`The link works for ${u()} minutes.`;return t?`Open this page in a browser to replace or remove the stored credentials for the "${e}" API. They go straight to the API and never enter the chat. ${n}`:`The "${e}" API needs credentials. Open this page in a browser to paste them - they go straight to the API and never enter the chat. ${n}`}function u(){return Math.round(A/6e4)}function g(e,t){if(!e)return`/mcp/credentials?token=${t}`;try{const n=new URL("/mcp/credentials",e);return n.searchParams.set("token",t),n.toString()}catch{return`${e}/mcp/credentials?token=${t}`}}function y({target:e,reason:t,hasExistingSecret:n},i,a){const r=[`**Stop and show the user this link. They have to open it in a browser to ${n?`replace or remove the stored credentials for the "${e.name}" API`:`add credentials for the "${e.name}" API`} - nothing continues until they do.**`,"",i,"",`The link is valid ${u()} minutes and opens a form on this site. Credentials go straight to the API from there: never ask for them in the conversation and never pass them as a tool argument.`,"",t.kind==="authFailure"?`The upstream API "${e.name}" returned ${t.status} and needs credentials.`:n?`Credentials are already stored for the "${e.name}" API; the link lets the user replace or remove them.`:`The "${e.name}" API needs credentials.`,a?`Once the user confirms they saved them, call this tool again with sessionHandle: "${a}" - the credentials are stored against that handle, applied server-side, and never appear in the conversation.`:"Once the user confirms they saved them, call this tool again - the credentials are applied server-side and never appear in the conversation."],s=t.kind==="authFailure"?t.otherApiNames??[]:[];if(s.length>0){const p=s.map(m=>`"${m}"`).join(", ");r.push("",`The remaining APIs (${p}) will prompt for credentials on subsequent calls.`)}return r.join(`
|
|
3
|
+
`)}export{h as CREDENTIALS_INPUT_REQUEST_KEY,R as buildCredentialsToolHint,O as buildUpstreamCredentialFallback,b as buildUpstreamCredentialInputRequired,C as isCredentialElicitationDeclined,_ as targetOf};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{secretStore as s}from"../gateway-mcp/secret-store.js";import{buildAllowedHostMatchers as
|
|
1
|
+
import{secretStore as s,ELICITATION_TTL_MS as x}from"../gateway-mcp/secret-store.js";import{buildAllowedHostMatchers as P}from"../gateway-mcp/fetch/allowed-hosts.js";import{assertUrlAllowed as _}from"../gateway-mcp/fetch/ssrf-guard.js";import{readCappedBody as E,getDefaultAgent as T}from"../gateway-mcp/fetch/fetch-bridge.js";const p={"Content-Type":"text/html; charset=utf-8","Cache-Control":"no-store","X-Frame-Options":"DENY","Referrer-Policy":"no-referrer","Content-Security-Policy":"default-src 'none'; style-src 'unsafe-inline'; form-action 'self'"},R=3e4,z=64*1024;let $=fetch;function Q(e){$=e}const F=async(e,t)=>{const n=await t.getKv();if(e.method==="GET"){const o=new URL(e.url).searchParams.get("token")??"",r=await s.peekElicitation(n,o);return r?new Response(j(r,o),{status:200,headers:p}):u()}if(e.method==="POST"){const o=await e.formData(),r=String(o.get("token")??""),m=String(o.get("mode")??"token");return m==="client-credentials"?M(n,o,r):m==="clear"?B(n,r):O(n,o,r)}return new Response("Method Not Allowed",{status:405,headers:{Allow:"GET, POST"}})};async function O(e,t,n){const o=String(t.get("secret")??"");if(!o)return u();const r=await s.consumeElicitation(e,n);return r?(await s.setSecret(e,r.subject,r.api,H(r.auth,o)),new Response(A(r.name),{status:200,headers:p})):u()}async function B(e,t){const n=await s.consumeElicitation(e,t);return n?(await s.deleteSecret(e,n.subject,n.api),new Response(D(n.name),{status:200,headers:p})):u()}async function M(e,t,n){const o=String(t.get("client_id")??"").trim(),r=String(t.get("client_secret")??"").trim();if(!o||!r)return u();const m=await s.peekElicitation(e,n);if(!m||m.auth.kind!=="oauth2ClientCredentials")return u();const i=await s.consumeElicitation(e,n);if(!i||i.auth.kind!=="oauth2ClientCredentials")return u();const b=i.auth;try{const S=P([{url:b.tokenUrl}]),C=_(b.tokenUrl,S),k=new AbortController,I=setTimeout(()=>k.abort(),R);let g;try{g=await $(C,{method:"POST",headers:{"content-type":"application/x-www-form-urlencoded"},body:new URLSearchParams({grant_type:"client_credentials",client_id:o,client_secret:r,...b.scopes.length>0?{scope:b.scopes.join(" ")}:{}}),redirect:"manual",signal:k.signal,dispatcher:T()})}finally{clearTimeout(I)}if(!g.ok)return f(i.name);let v;try{v=await E(g,z)}catch{return f(i.name)}let h;try{h=JSON.parse(v)}catch{return f(i.name)}const w=typeof h=="object"&&h!==null&&"access_token"in h?h.access_token:void 0;return typeof w!="string"||!w?f(i.name):(await s.setSecret(e,i.subject,i.api,{header:"Authorization",value:`Bearer ${w}`}),new Response(A(i.name),{status:200,headers:p}))}catch{return f(i.name)}}function H(e,t){return e.kind==="apiKey"?{header:e.header,value:t}:e.kind==="bearer"||e.kind==="oauth2ClientCredentials"?{header:"Authorization",value:N(t)}:{header:"Authorization",value:t}}function N(e){const t=e.trim();return t.toLowerCase().startsWith("bearer")?t:`Bearer ${t}`}function a(e){return e.replaceAll("&","&").replaceAll("<","<").replaceAll(">",">").replaceAll('"',""")}function c(e){return`<!doctype html>
|
|
2
2
|
<html lang="en">
|
|
3
3
|
<head>
|
|
4
4
|
<meta charset="utf-8">
|
|
@@ -37,7 +37,7 @@ footer{border-top-color:#1f2937;color:#6b7280}
|
|
|
37
37
|
</style>
|
|
38
38
|
</head>
|
|
39
39
|
<body><div class="card">${e}</div></body>
|
|
40
|
-
</html>`}function d(e){return`<h1>\u{1F512} ${a(e)}</h1>`}function l(){return"<footer>Secured by Redocly · credentials never enter the AI conversation</footer>"}function
|
|
40
|
+
</html>`}function d(e){return`<h1>\u{1F512} ${a(e)}</h1>`}function l(){return"<footer>Secured by Redocly · credentials never enter the AI conversation</footer>"}function y(){return`<p class="hint">This link expires ${Math.round(x/6e4)} minutes after it was issued. If it stops working, ask your AI client for a new one.</p>`}function j(e,t){const n=e.hasExistingSecret?q(t):"";switch(e.auth.kind){case"bearer":return L(e.name,t,n);case"apiKey":return U(e.name,t,e.auth.header,n);case"oauth2ClientCredentials":return G(e.name,t,e.auth.scopes,n);default:return K(e.name,t,n)}}function q(e){return`
|
|
41
41
|
<div class="section">
|
|
42
42
|
<h2>Remove stored credentials</h2>
|
|
43
43
|
<p class="hint">This API already has credentials stored. Save above to replace them, or remove them to make calls anonymous again.</p>
|
|
@@ -46,7 +46,7 @@ footer{border-top-color:#1f2937;color:#6b7280}
|
|
|
46
46
|
<input type="hidden" name="mode" value="clear">
|
|
47
47
|
<button class="danger" type="submit">Remove credentials</button>
|
|
48
48
|
</form>
|
|
49
|
-
</div>`}function
|
|
49
|
+
</div>`}function L(e,t,n){return c(`
|
|
50
50
|
${d(`Credentials for ${e}`)}
|
|
51
51
|
<p class="hint">Stored encrypted for you only (24 hours for anonymous callers, 30 days when signed in) and sent to the API as the <code>Authorization</code> header. It never passes through the AI conversation.</p>
|
|
52
52
|
<form method="post" action="/mcp/credentials" autocomplete="off">
|
|
@@ -58,6 +58,7 @@ ${d(`Credentials for ${e}`)}
|
|
|
58
58
|
<button type="submit">Save credentials</button>
|
|
59
59
|
</form>
|
|
60
60
|
${n}
|
|
61
|
+
${y()}
|
|
61
62
|
${l()}`)}function U(e,t,n,o){return c(`
|
|
62
63
|
${d(`Credentials for ${e}`)}
|
|
63
64
|
<p class="hint">Stored encrypted for you only (24 hours for anonymous callers, 30 days when signed in) and sent to the API as the <code>${a(n)}</code> header. It never passes through the AI conversation.</p>
|
|
@@ -69,7 +70,8 @@ ${d(`Credentials for ${e}`)}
|
|
|
69
70
|
<button type="submit">Save credentials</button>
|
|
70
71
|
</form>
|
|
71
72
|
${o}
|
|
72
|
-
${
|
|
73
|
+
${y()}
|
|
74
|
+
${l()}`)}function G(e,t,n,o){const r=n.length>0?n.map(a).join(", "):"default scopes";return c(`
|
|
73
75
|
${d(`Credentials for ${e}`)}
|
|
74
76
|
<p class="hint">Paste a token you already have, or let us exchange client credentials for one via OAuth2 client_credentials.</p>
|
|
75
77
|
<div class="section">
|
|
@@ -97,7 +99,8 @@ ${d(`Credentials for ${e}`)}
|
|
|
97
99
|
</form>
|
|
98
100
|
</div>
|
|
99
101
|
${o}
|
|
100
|
-
${
|
|
102
|
+
${y()}
|
|
103
|
+
${l()}`)}function K(e,t,n){return c(`
|
|
101
104
|
${d(`Credentials for ${e}`)}
|
|
102
105
|
<p class="hint">The value is stored encrypted for you only (24 hours for anonymous callers, 30 days when signed in) and is sent to the API as the <code>Authorization</code> header. It never passes through the AI conversation.</p>
|
|
103
106
|
<form method="post" action="/mcp/credentials" autocomplete="off">
|
|
@@ -108,13 +111,14 @@ ${d(`Credentials for ${e}`)}
|
|
|
108
111
|
<button type="submit">Save credentials</button>
|
|
109
112
|
</form>
|
|
110
113
|
${n}
|
|
114
|
+
${y()}
|
|
111
115
|
${l()}`)}function D(e){return c(`
|
|
112
116
|
${d("Credentials removed")}
|
|
113
117
|
<p>The stored credentials for ${a(e)} are gone. Calls to that API are anonymous again until you add new ones.</p>
|
|
114
|
-
${l()}`)}function
|
|
118
|
+
${l()}`)}function A(e){return c(`
|
|
115
119
|
${d("Credentials saved")}
|
|
116
|
-
<p>Credentials for ${a(e)} are
|
|
120
|
+
<p>Credentials for ${a(e)} are saved. Go back to your AI client and ask it to retry - or confirm the prompt it left open, if there is one. You can close this tab.</p>
|
|
117
121
|
${l()}`)}function f(e){return new Response(c(`
|
|
118
122
|
${d("Exchange failed")}
|
|
119
123
|
<p>We could not exchange your client credentials for a token for ${a(e)}. This link has been used up - go back to your AI client and request a new link, then try again.</p>
|
|
120
|
-
${l()}`),{status:502,headers:p})}function u(){return new Response(c(`${d("Link expired")}<p>This credentials link is invalid or
|
|
124
|
+
${l()}`),{status:502,headers:p})}function u(){return new Response(c(`${d("Link expired")}<p>This credentials link is invalid, already used, or older than ${Math.round(x/6e4)} minutes. Retry the request in your AI client to get a fresh link.</p>${l()}`),{status:404,headers:p})}var V=F;export{V as default,Q as setCredentialsFetchImpl};
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import{McpServer as T,WebStandardStreamableHTTPServerTransport as q}from"@modelcontextprotocol/server";import{logger as
|
|
1
|
+
import{McpServer as T,WebStandardStreamableHTTPServerTransport as q}from"@modelcontextprotocol/server";import{logger as g}from"../../../tools/notifiers/logger.js";import{mcpToolWorkers as v,MCP_TOOL_WORKER_KEY as I}from"../../../workers/mcp-tool-worker-pool.js";import{SSE_KEEPALIVE_INTERVAL_MS as R}from"../constants.js";import{secretStore as _}from"../gateway-mcp/secret-store.js";import{readAuthRequiredMeta as A,stripAuthRequiredMeta as h}from"../gateway-mcp/upstream-auth.js";import{buildCredentialsToolHint as O,buildUpstreamCredentialFallback as w,buildUpstreamCredentialInputRequired as U,isCredentialElicitationDeclined as C,targetOf as D}from"../gateway-mcp/elicitation.js";import{resolveUpstreamAuthScheme as j}from"../gateway-mcp/upstream-auth-scheme.js";import{MISSING_SESSION_HANDLE_MESSAGE as K,resolveSecretSubject as P}from"../gateway-mcp/subject.js";const L=6e4,S=new Map;function $(e,t){const r=S.get(e);if(r)return r;const n=t().finally(()=>{S.delete(e)});return S.set(e,n),n}function ee(e,t){const r=new T({name:e,version:new Date().toISOString().slice(0,10)},{capabilities:{logging:{}},...t?{instructions:t}:{}}),n=new q({keepAliveMs:R,sessionIdGenerator:void 0});return{server:r,transport:n}}async function te(e,t){return await e.connect(t),{server:e,transport:t,cleanup:async()=>{t.close()}}}function re(e,t){return r=>async(n,a)=>{g.info(`MCP tool called: ${r}`);const i=e.elicitationEnabled??!1,s=a.mcpReq.envelope!==void 0,c=i?P(e.user,n.sessionHandle):{kind:"anonymous"},o=c.kind==="resolved"?c.subject:void 0;if(c.kind==="invalid")return{content:[{type:"text",text:c.message}],isError:!0};const d=async()=>{if(!(!i||!o||!t?.getKv))try{const u=await t.getKv();return await _.getSecretsForSubject(u,o)}catch(u){g.warn(`gateway mcp secret store error, continuing unauthenticated: ${String(u)}`);return}};let m;const E=async()=>{m=await d();const u={toolName:r,args:n,context:{...e,upstreamSecrets:m},extra:N(a)};return await v.exec(I,[u],{timeout:L})};let p=await E();const l=A(p);if(l.length>0&&i&&!o)return h({...p,content:[{type:"text",text:K}],isError:!0});if(l.length>0&&i&&o&&t?.getKv){g.info(`gateway mcp credentials prompt: era=${s?"2026-07-28":"2025"} clientElicitation=${F(t)}`);const u=await t.getKv(),b=new Set(Object.keys(m??{})),f=s?await H(e,a,l,o,u,b,t):await B(e,l,o,u,b);switch(f.kind){case"inputRequired":return f.result;case"retry":p=await E();break;case"prependText":p={...p,content:[{type:"text",text:f.text},...p.content],isError:!0};break;case"none":break}}return h(p)}}function x(e){return!!e?.getClientCapabilities()?.elicitation?.url}function F(e){const t=e?.getClientCapabilities()?.elicitation;if(!t)return"none";const r=[t.form?"form":void 0,t.url?"url":void 0].filter(Boolean);return r.length>0?r.join("+"):"none"}function k(e,t){return e.status===401&&!t.has(e.api)}function y(e){return e.map(O).join(`
|
|
2
2
|
|
|
3
|
-
`)}async function M(e,t,r,n){const[a,...i]=t;return{subject:r,baseUrl:e.baseUrl??"",target:
|
|
3
|
+
`)}async function M(e,t,r,n){const[a,...i]=t;return{subject:r,baseUrl:e.baseUrl??"",target:D(a),auth:await j(e,a.api),kv:n,hasExistingSecret:!1,reason:{kind:"authFailure",status:a.status,otherApiNames:i.map(s=>s.name)}}}async function H(e,t,r,n,a,i,s){if(C(t.mcpReq.inputResponses))return{kind:"none"};const c=r.filter(d=>k(d,i));if(c.length===0)return{kind:"prependText",text:y(r)};const o=await M(e,c,n,a);return x(s)?{kind:"inputRequired",result:await U(o)}:{kind:"prependText",text:await w(o)}}async function B(e,t,r,n,a){const i=t.filter(o=>k(o,a));if(i.length===0)return{kind:"prependText",text:y(t)};const s=await M(e,i,r,n);return{kind:"prependText",text:await $(`${r}:${i[0].api}`,()=>w(s))}}function N(e){return{sessionId:e.sessionId,authInfo:e.http?.authInfo,requestId:e.mcpReq.id,_meta:e.mcpReq._meta}}function ne(e){return{user:e.user,config:e.config,outdir:e.outdir,baseUrl:e.baseUrl,params:e.params,query:e.query,cookies:e.cookies,apiDescriptionsMap:e.apiDescriptionsMap,graphqlDescriptions:e.graphqlDescriptions,products:e.products,accessToken:e.accessToken,metadata:e.metadata,upstreamAuthorization:e.upstreamAuthorization,mcpMode:e.mcpMode,elicitationEnabled:e.elicitationEnabled,gatewayFetchBudgetPerExec:e.gatewayFetchBudgetPerExec,clientIp:e.clientIp,upstreamSecrets:e.upstreamSecrets}}export{te as connectMcpServer,ee as createMcpServer,re as createToolDispatch,ne as extractSerializableContext};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@redocly/realm",
|
|
3
|
-
"version": "0.136.0-next.
|
|
3
|
+
"version": "0.136.0-next.15",
|
|
4
4
|
"description": "",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -53,7 +53,7 @@
|
|
|
53
53
|
"escape-carriage": "^1.3.1",
|
|
54
54
|
"fflate": "0.7.4",
|
|
55
55
|
"flexsearch": "0.7.43",
|
|
56
|
-
"graphql": "16.
|
|
56
|
+
"graphql": "16.14.2",
|
|
57
57
|
"hono": "4.13.1",
|
|
58
58
|
"htmlparser2": "8.0.2",
|
|
59
59
|
"i18next": "22.4.15",
|
|
@@ -93,15 +93,15 @@
|
|
|
93
93
|
"xpath": "0.0.34",
|
|
94
94
|
"yaml-ast-parser": "0.0.43",
|
|
95
95
|
"zod": "^3.25.76",
|
|
96
|
-
"@redocly/api-docs": "0.1.0-next.
|
|
97
|
-
"@redocly/asyncapi-docs": "1.13.0-next.
|
|
96
|
+
"@redocly/api-docs": "0.1.0-next.7",
|
|
97
|
+
"@redocly/asyncapi-docs": "1.13.0-next.13",
|
|
98
98
|
"@redocly/config": "0.53.1",
|
|
99
|
-
"@redocly/graphql-docs": "1.13.0-next.
|
|
100
|
-
"@redocly/openapi-docs": "3.24.0-next.
|
|
99
|
+
"@redocly/graphql-docs": "1.13.0-next.13",
|
|
100
|
+
"@redocly/openapi-docs": "3.24.0-next.13",
|
|
101
101
|
"@redocly/portal-legacy-ui": "0.19.0-next.0",
|
|
102
|
-
"@redocly/portal-plugin-mock-server": "0.21.0-next.
|
|
102
|
+
"@redocly/portal-plugin-mock-server": "0.21.0-next.13",
|
|
103
103
|
"@redocly/realm-asyncapi-sdk": "0.14.0-next.4",
|
|
104
|
-
"@redocly/theme": "0.68.0-next.
|
|
104
|
+
"@redocly/theme": "0.68.0-next.9"
|
|
105
105
|
},
|
|
106
106
|
"peerDependencies": {
|
|
107
107
|
"react": "^19.2.7",
|
|
@@ -1,2 +0,0 @@
|
|
|
1
|
-
export declare const SANDBOX_FETCH_SETUP = "\n const URLSearchParams = class URLSearchParams {\n constructor(init) {\n this._params = [];\n if (typeof init === 'string') {\n const str = init.startsWith('?') ? init.slice(1) : init;\n for (const pair of (str ? str.split('&') : [])) {\n const eq = pair.indexOf('=');\n const k = eq >= 0 ? pair.slice(0, eq) : pair;\n const v = eq >= 0 ? pair.slice(eq + 1) : '';\n if (k) this._params.push([decodeURIComponent(k), decodeURIComponent(v)]);\n }\n } else if (init != null && typeof init === 'object') {\n for (const [k, v] of Object.entries(init)) this._params.push([k, String(v)]);\n }\n }\n append(key, value) { this._params.push([String(key), String(value)]); }\n set(key, value) {\n const k = String(key), v = String(value);\n this._params = this._params.filter(([pk]) => pk !== k);\n this._params.push([k, v]);\n }\n get(key) { return (this._params.find(([k]) => k === String(key)) ?? [undefined, null])[1]; }\n getAll(key) { return this._params.filter(([k]) => k === String(key)).map(([, v]) => v); }\n has(key) { return this._params.some(([k]) => k === String(key)); }\n delete(key) { this._params = this._params.filter(([k]) => k !== String(key)); }\n entries() { return this._params.values(); }\n [Symbol.iterator]() { return this._params.values(); }\n toString() {\n return this._params\n .map(([k, v]) => encodeURIComponent(k) + '=' + encodeURIComponent(v))\n .join('&');\n }\n };\n globalThis.URLSearchParams = URLSearchParams;\n\n const FormData = class FormData {\n constructor() { this._fields = []; }\n append(key, value) { this._fields.push([String(key), String(value)]); }\n set(key, value) {\n const k = String(key), v = String(value);\n this._fields = this._fields.filter(([fk]) => fk !== k);\n this._fields.push([k, v]);\n }\n get(key) { return (this._fields.find(([k]) => k === String(key)) ?? [undefined, null])[1]; }\n has(key) { return this._fields.some(([k]) => k === String(key)); }\n delete(key) { this._fields = this._fields.filter(([k]) => k !== String(key)); }\n entries() { return this._fields.values(); }\n [Symbol.iterator]() { return this._fields.values(); }\n };\n globalThis.FormData = FormData;\n\n const hostFetch = globalThis.__fetchBridge.fetch;\n delete globalThis.__fetchBridge;\n globalThis.fetch = (url, init = {}) => {\n let body;\n let autoContentType;\n if (init.body instanceof URLSearchParams) {\n body = init.body.toString();\n autoContentType = 'application/x-www-form-urlencoded';\n } else if (init.body instanceof FormData) {\n const boundary = '----FormBoundary' + Math.random().toString(36).slice(2);\n body = init.body._fields\n .map(([k, v]) =>\n '--' + boundary + '\\r\\nContent-Disposition: form-data; name=\"' + k + '\"\\r\\n\\r\\n' + v,\n )\n .join('\\r\\n') + '\\r\\n--' + boundary + '--';\n autoContentType = 'multipart/form-data; boundary=' + boundary;\n } else if (init.body != null && typeof init.body !== 'string') {\n throw new TypeError('fetch body must be a string, URLSearchParams, or FormData');\n } else {\n body = init.body ?? undefined;\n }\n const callerHeaders = init.headers;\n const hasContentType = callerHeaders != null && Object.keys(callerHeaders).some(\n (k) => k.toLowerCase() === 'content-type',\n );\n const headers = autoContentType && !hasContentType\n ? Object.assign({ 'content-type': autoContentType }, callerHeaders)\n : callerHeaders;\n return hostFetch({ url: String(url), method: init.method, headers, body })\n .then((res) => {\n const resHeaders = res.headers || {};\n return {\n status: res.status,\n statusText: res.statusText,\n ok: res.ok,\n headers: {\n get: (name) => {\n const value = resHeaders[String(name).toLowerCase()];\n return value === undefined ? null : value;\n },\n },\n json: () => Promise.resolve(JSON.parse(res.bodyText)),\n text: () => Promise.resolve(res.bodyText),\n };\n });\n };\n";
|
|
2
|
-
//# sourceMappingURL=polyfills.d.ts.map
|
|
@@ -1,99 +0,0 @@
|
|
|
1
|
-
import{FETCH_BRIDGE_FUNCTION_NAME as t,FETCH_BRIDGE_GLOBAL_NAME as e}from"../constants.js";const s=`
|
|
2
|
-
const URLSearchParams = class URLSearchParams {
|
|
3
|
-
constructor(init) {
|
|
4
|
-
this._params = [];
|
|
5
|
-
if (typeof init === 'string') {
|
|
6
|
-
const str = init.startsWith('?') ? init.slice(1) : init;
|
|
7
|
-
for (const pair of (str ? str.split('&') : [])) {
|
|
8
|
-
const eq = pair.indexOf('=');
|
|
9
|
-
const k = eq >= 0 ? pair.slice(0, eq) : pair;
|
|
10
|
-
const v = eq >= 0 ? pair.slice(eq + 1) : '';
|
|
11
|
-
if (k) this._params.push([decodeURIComponent(k), decodeURIComponent(v)]);
|
|
12
|
-
}
|
|
13
|
-
} else if (init != null && typeof init === 'object') {
|
|
14
|
-
for (const [k, v] of Object.entries(init)) this._params.push([k, String(v)]);
|
|
15
|
-
}
|
|
16
|
-
}
|
|
17
|
-
append(key, value) { this._params.push([String(key), String(value)]); }
|
|
18
|
-
set(key, value) {
|
|
19
|
-
const k = String(key), v = String(value);
|
|
20
|
-
this._params = this._params.filter(([pk]) => pk !== k);
|
|
21
|
-
this._params.push([k, v]);
|
|
22
|
-
}
|
|
23
|
-
get(key) { return (this._params.find(([k]) => k === String(key)) ?? [undefined, null])[1]; }
|
|
24
|
-
getAll(key) { return this._params.filter(([k]) => k === String(key)).map(([, v]) => v); }
|
|
25
|
-
has(key) { return this._params.some(([k]) => k === String(key)); }
|
|
26
|
-
delete(key) { this._params = this._params.filter(([k]) => k !== String(key)); }
|
|
27
|
-
entries() { return this._params.values(); }
|
|
28
|
-
[Symbol.iterator]() { return this._params.values(); }
|
|
29
|
-
toString() {
|
|
30
|
-
return this._params
|
|
31
|
-
.map(([k, v]) => encodeURIComponent(k) + '=' + encodeURIComponent(v))
|
|
32
|
-
.join('&');
|
|
33
|
-
}
|
|
34
|
-
};
|
|
35
|
-
globalThis.URLSearchParams = URLSearchParams;
|
|
36
|
-
|
|
37
|
-
const FormData = class FormData {
|
|
38
|
-
constructor() { this._fields = []; }
|
|
39
|
-
append(key, value) { this._fields.push([String(key), String(value)]); }
|
|
40
|
-
set(key, value) {
|
|
41
|
-
const k = String(key), v = String(value);
|
|
42
|
-
this._fields = this._fields.filter(([fk]) => fk !== k);
|
|
43
|
-
this._fields.push([k, v]);
|
|
44
|
-
}
|
|
45
|
-
get(key) { return (this._fields.find(([k]) => k === String(key)) ?? [undefined, null])[1]; }
|
|
46
|
-
has(key) { return this._fields.some(([k]) => k === String(key)); }
|
|
47
|
-
delete(key) { this._fields = this._fields.filter(([k]) => k !== String(key)); }
|
|
48
|
-
entries() { return this._fields.values(); }
|
|
49
|
-
[Symbol.iterator]() { return this._fields.values(); }
|
|
50
|
-
};
|
|
51
|
-
globalThis.FormData = FormData;
|
|
52
|
-
`,r=`
|
|
53
|
-
const hostFetch = globalThis.${e}.${t};
|
|
54
|
-
delete globalThis.${e};
|
|
55
|
-
globalThis.fetch = (url, init = {}) => {
|
|
56
|
-
let body;
|
|
57
|
-
let autoContentType;
|
|
58
|
-
if (init.body instanceof URLSearchParams) {
|
|
59
|
-
body = init.body.toString();
|
|
60
|
-
autoContentType = 'application/x-www-form-urlencoded';
|
|
61
|
-
} else if (init.body instanceof FormData) {
|
|
62
|
-
const boundary = '----FormBoundary' + Math.random().toString(36).slice(2);
|
|
63
|
-
body = init.body._fields
|
|
64
|
-
.map(([k, v]) =>
|
|
65
|
-
'--' + boundary + '\\r\\nContent-Disposition: form-data; name="' + k + '"\\r\\n\\r\\n' + v,
|
|
66
|
-
)
|
|
67
|
-
.join('\\r\\n') + '\\r\\n--' + boundary + '--';
|
|
68
|
-
autoContentType = 'multipart/form-data; boundary=' + boundary;
|
|
69
|
-
} else if (init.body != null && typeof init.body !== 'string') {
|
|
70
|
-
throw new TypeError('fetch body must be a string, URLSearchParams, or FormData');
|
|
71
|
-
} else {
|
|
72
|
-
body = init.body ?? undefined;
|
|
73
|
-
}
|
|
74
|
-
const callerHeaders = init.headers;
|
|
75
|
-
const hasContentType = callerHeaders != null && Object.keys(callerHeaders).some(
|
|
76
|
-
(k) => k.toLowerCase() === 'content-type',
|
|
77
|
-
);
|
|
78
|
-
const headers = autoContentType && !hasContentType
|
|
79
|
-
? Object.assign({ 'content-type': autoContentType }, callerHeaders)
|
|
80
|
-
: callerHeaders;
|
|
81
|
-
return hostFetch({ url: String(url), method: init.method, headers, body })
|
|
82
|
-
.then((res) => {
|
|
83
|
-
const resHeaders = res.headers || {};
|
|
84
|
-
return {
|
|
85
|
-
status: res.status,
|
|
86
|
-
statusText: res.statusText,
|
|
87
|
-
ok: res.ok,
|
|
88
|
-
headers: {
|
|
89
|
-
get: (name) => {
|
|
90
|
-
const value = resHeaders[String(name).toLowerCase()];
|
|
91
|
-
return value === undefined ? null : value;
|
|
92
|
-
},
|
|
93
|
-
},
|
|
94
|
-
json: () => Promise.resolve(JSON.parse(res.bodyText)),
|
|
95
|
-
text: () => Promise.resolve(res.bodyText),
|
|
96
|
-
};
|
|
97
|
-
});
|
|
98
|
-
};
|
|
99
|
-
`,i=`${s}${r}`;export{i as SANDBOX_FETCH_SETUP};
|