@assistant-ui/vscode 0.0.0-stage → 0.0.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.
Files changed (77) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +159 -2
  3. package/dist/external-url.d.ts +3 -0
  4. package/dist/external-url.d.ts.map +1 -0
  5. package/dist/external-url.js +19 -0
  6. package/dist/external-url.js.map +1 -0
  7. package/dist/host/html.d.ts +65 -0
  8. package/dist/host/html.d.ts.map +1 -0
  9. package/dist/host/html.js +126 -0
  10. package/dist/host/html.js.map +1 -0
  11. package/dist/host/router.d.ts +24 -0
  12. package/dist/host/router.d.ts.map +1 -0
  13. package/dist/host/router.js +166 -0
  14. package/dist/host/router.js.map +1 -0
  15. package/dist/host/serve.d.ts +34 -0
  16. package/dist/host/serve.d.ts.map +1 -0
  17. package/dist/host/serve.js +93 -0
  18. package/dist/host/serve.js.map +1 -0
  19. package/dist/host.d.ts +5 -0
  20. package/dist/host.d.ts.map +1 -0
  21. package/dist/host.js +4 -0
  22. package/dist/protocol.d.ts +46 -0
  23. package/dist/protocol.d.ts.map +1 -0
  24. package/dist/protocol.js +30 -0
  25. package/dist/protocol.js.map +1 -0
  26. package/dist/route-url.d.ts +3 -0
  27. package/dist/route-url.d.ts.map +1 -0
  28. package/dist/route-url.js +7 -0
  29. package/dist/route-url.js.map +1 -0
  30. package/dist/webview/csp-nonce.d.ts +6 -0
  31. package/dist/webview/csp-nonce.d.ts.map +1 -0
  32. package/dist/webview/csp-nonce.js +12 -0
  33. package/dist/webview/csp-nonce.js.map +1 -0
  34. package/dist/webview/fetch.d.ts +14 -0
  35. package/dist/webview/fetch.d.ts.map +1 -0
  36. package/dist/webview/fetch.js +164 -0
  37. package/dist/webview/fetch.js.map +1 -0
  38. package/dist/webview/id.d.ts +2 -0
  39. package/dist/webview/id.d.ts.map +1 -0
  40. package/dist/webview/id.js +5 -0
  41. package/dist/webview/id.js.map +1 -0
  42. package/dist/webview/links.d.ts +14 -0
  43. package/dist/webview/links.d.ts.map +1 -0
  44. package/dist/webview/links.js +44 -0
  45. package/dist/webview/links.js.map +1 -0
  46. package/dist/webview/rpc.d.ts +3 -0
  47. package/dist/webview/rpc.d.ts.map +1 -0
  48. package/dist/webview/rpc.js +32 -0
  49. package/dist/webview/rpc.js.map +1 -0
  50. package/dist/webview/storage.d.ts +8 -0
  51. package/dist/webview/storage.d.ts.map +1 -0
  52. package/dist/webview/storage.js +24 -0
  53. package/dist/webview/storage.js.map +1 -0
  54. package/dist/webview/vscode-api.d.ts +11 -0
  55. package/dist/webview/vscode-api.d.ts.map +1 -0
  56. package/dist/webview/vscode-api.js +22 -0
  57. package/dist/webview/vscode-api.js.map +1 -0
  58. package/dist/webview.d.ts +7 -0
  59. package/dist/webview.d.ts.map +1 -0
  60. package/dist/webview.js +7 -0
  61. package/package.json +87 -3
  62. package/src/external-url.ts +19 -0
  63. package/src/host/html.ts +224 -0
  64. package/src/host/router.ts +253 -0
  65. package/src/host/serve.ts +150 -0
  66. package/src/host.ts +27 -0
  67. package/src/protocol.ts +131 -0
  68. package/src/route-url.ts +6 -0
  69. package/src/theme.css +327 -0
  70. package/src/webview/csp-nonce.ts +10 -0
  71. package/src/webview/fetch.ts +210 -0
  72. package/src/webview/id.ts +4 -0
  73. package/src/webview/links.ts +56 -0
  74. package/src/webview/rpc.ts +35 -0
  75. package/src/webview/storage.ts +24 -0
  76. package/src/webview/vscode-api.ts +31 -0
  77. package/src/webview.ts +14 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"router.js","names":[],"sources":["../../src/host/router.ts"],"sourcesContent":["import {\n isWebviewToHostMessage,\n VSCODE_BRIDGE_CHANNEL,\n VSCODE_VIRTUAL_ORIGIN,\n type FetchRequestMessage,\n type HostToWebviewMessage,\n} from \"../protocol\";\nimport { isForeignHttpUrl } from \"../route-url\";\n\nexport type RouteHandler = (request: Request) => Response | Promise<Response>;\n\nexport type HttpMethod =\n | \"GET\"\n | \"HEAD\"\n | \"POST\"\n | \"PUT\"\n | \"PATCH\"\n | \"DELETE\"\n | \"OPTIONS\";\n\nexport type RouteHandlers = { [M in HttpMethod]?: RouteHandler };\n\nexport type WebviewRoutes = Record<string, RouteHandlers>;\n\nexport type WebviewLike = {\n postMessage(message: any): PromiseLike<boolean>;\n onDidReceiveMessage(listener: (message: any) => unknown): {\n dispose(): unknown;\n };\n};\n\nexport type ServeWebviewRoutesOptions = {\n /** Milliseconds to coalesce response chunks before posting them. */\n flushInterval?: number;\n /** Buffered byte count that posts a chunk without waiting. */\n flushSize?: number;\n onError?: (error: unknown) => void;\n};\n\nexport type Disposable = { dispose(): void };\n\nconst BODYLESS_METHODS = new Set([\"GET\", \"HEAD\"]);\n\nconst concat = (parts: Uint8Array[], size: number) => {\n const out = new Uint8Array(size);\n let offset = 0;\n for (const part of parts) {\n out.set(part, offset);\n offset += part.byteLength;\n }\n return out;\n};\n\nconst errorMessage = (error: unknown) =>\n error instanceof Error ? error.message : String(error);\n\nexport function serveRoutes(\n webview: WebviewLike,\n routes: WebviewRoutes,\n {\n flushInterval = 16,\n flushSize = 64 * 1024,\n onError = (error) => console.error(error),\n }: ServeWebviewRoutesOptions = {},\n): Disposable {\n const inflight = new Map<string, AbortController>();\n\n const post = (message: HostToWebviewMessage, controller: AbortController) => {\n Promise.resolve(webview.postMessage(message)).then(\n (delivered) => {\n if (!delivered) controller.abort();\n },\n () => controller.abort(),\n );\n };\n\n const dispatch = async (\n request: Request,\n pathname: string,\n ): Promise<Response> => {\n const route = Object.hasOwn(routes, pathname) ? routes[pathname] : null;\n if (!route) return new Response(\"Not Found\", { status: 404 });\n const handler = Object.hasOwn(route, request.method)\n ? route[request.method as HttpMethod]\n : null;\n if (!handler) {\n return new Response(\"Method Not Allowed\", {\n status: 405,\n headers: { Allow: Object.keys(route).join(\", \") },\n });\n }\n try {\n return await handler(request);\n } catch (error) {\n if (request.signal.aborted) throw error;\n onError(error);\n return new Response(\"Internal Server Error\", { status: 500 });\n }\n };\n\n const pump = async (\n id: string,\n body: ReadableStream<Uint8Array>,\n controller: AbortController,\n ) => {\n const { signal } = controller;\n const reader = body.getReader();\n const onAbort = () => {\n reader.cancel(signal.reason).catch(() => undefined);\n };\n signal.addEventListener(\"abort\", onAbort, { once: true });\n\n let buffered: Uint8Array[] = [];\n let size = 0;\n let timer: ReturnType<typeof setTimeout> | undefined;\n const flush = () => {\n if (timer !== undefined) clearTimeout(timer);\n timer = undefined;\n if (size === 0 || signal.aborted) return;\n const chunk = concat(buffered, size);\n buffered = [];\n size = 0;\n post(\n { channel: VSCODE_BRIDGE_CHANNEL, kind: \"fetch:chunk\", id, chunk },\n controller,\n );\n };\n\n try {\n while (true) {\n const { done, value } = await reader.read();\n if (done || signal.aborted) break;\n if (value.byteLength === 0) continue;\n buffered.push(value);\n size += value.byteLength;\n if (size >= flushSize || flushInterval <= 0) flush();\n else timer ??= setTimeout(flush, flushInterval);\n }\n flush();\n } catch (error) {\n flush();\n throw error;\n } finally {\n if (timer !== undefined) clearTimeout(timer);\n signal.removeEventListener(\"abort\", onAbort);\n reader.releaseLock();\n }\n };\n\n const handle = async (message: FetchRequestMessage) => {\n const { id } = message;\n if (inflight.has(id)) {\n onError(new Error(`Ignored a request reusing the in-flight id ${id}`));\n return;\n }\n const controller = new AbortController();\n inflight.set(id, controller);\n try {\n const url = new URL(message.url);\n if (isForeignHttpUrl(url)) {\n throw new TypeError(`Refused a request for ${url.origin}`);\n }\n // The webview chooses this URL, so a route sees only its path and query.\n const requestUrl = new URL(VSCODE_VIRTUAL_ORIGIN);\n requestUrl.pathname = url.pathname;\n requestUrl.search = url.search;\n const request = new Request(requestUrl, {\n method: message.method,\n headers: message.headers,\n body: BODYLESS_METHODS.has(message.method.toUpperCase())\n ? null\n : message.body,\n signal: controller.signal,\n });\n const response = await dispatch(request, url.pathname);\n if (controller.signal.aborted) {\n void response.body?.cancel().catch(() => undefined);\n return;\n }\n post(\n {\n channel: VSCODE_BRIDGE_CHANNEL,\n kind: \"fetch:head\",\n id,\n status: response.status,\n statusText: response.statusText,\n headers: [...response.headers],\n },\n controller,\n );\n if (request.method === \"HEAD\") {\n void response.body?.cancel().catch(() => undefined);\n } else if (response.body) {\n await pump(id, response.body, controller);\n }\n if (!controller.signal.aborted) {\n post(\n { channel: VSCODE_BRIDGE_CHANNEL, kind: \"fetch:end\", id },\n controller,\n );\n }\n } catch (error) {\n if (controller.signal.aborted) return;\n onError(error);\n post(\n {\n channel: VSCODE_BRIDGE_CHANNEL,\n kind: \"fetch:error\",\n id,\n message: errorMessage(error),\n },\n controller,\n );\n } finally {\n if (inflight.get(id) === controller) inflight.delete(id);\n }\n };\n\n const subscription = webview.onDidReceiveMessage((message: unknown) => {\n if (!isWebviewToHostMessage(message)) return;\n if (message.kind === \"fetch:request\") void handle(message);\n else if (message.kind === \"fetch:reset\") {\n const controllers = [...inflight.values()];\n inflight.clear();\n for (const controller of controllers) controller.abort();\n } else if (message.kind === \"fetch:abort\") {\n const controller = inflight.get(message.id);\n if (controller) {\n inflight.delete(message.id);\n controller.abort();\n }\n }\n });\n\n return {\n dispose: () => {\n subscription.dispose();\n for (const [id, controller] of inflight) {\n post(\n {\n channel: VSCODE_BRIDGE_CHANNEL,\n kind: \"fetch:error\",\n id,\n message: \"Webview routes disposed\",\n },\n controller,\n );\n controller.abort();\n }\n inflight.clear();\n },\n };\n}\n"],"mappings":"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"}
@@ -0,0 +1,34 @@
1
+ import { type Disposable, type ServeWebviewRoutesOptions, type WebviewLike, type WebviewRoutes } from "./router.js";
2
+ /** Matches `vscode.Memento`, whose `update(key, undefined)` removes the key. */
3
+ export type MementoLike = {
4
+ get(key: string): unknown;
5
+ update(key: string, value: unknown): PromiseLike<void>;
6
+ };
7
+ export type ServeWebviewHostOptions = ServeWebviewRoutesOptions & {
8
+ routes?: WebviewRoutes;
9
+ /**
10
+ * Opens a URL forwarded by `installLinkInterceptor`, after the host has
11
+ * checked its scheme against `externalSchemes`.
12
+ */
13
+ openExternal?: (url: string) => PromiseLike<boolean> | boolean | void;
14
+ /** URL schemes `openExternal` may receive, such as `"https:"`. */
15
+ externalSchemes?: readonly string[];
16
+ /** Backs `createVSCodeStorage`, such as `context.globalState`. */
17
+ storage?: MementoLike;
18
+ /** Namespaces the webview's keys inside `storage`. */
19
+ storagePrefix?: string;
20
+ };
21
+ /**
22
+ * Serves `vscodeFetch` routes and the webview's host calls, such as
23
+ * `installLinkInterceptor` opening links and `createVSCodeStorage`, for one
24
+ * webview. Host calls without a handler reject in the webview.
25
+ */
26
+ export declare function serveWebviewHost(webview: WebviewLike, { routes, openExternal, externalSchemes, storage, storagePrefix, ...routeOptions }?: ServeWebviewHostOptions): Disposable;
27
+ /**
28
+ * Serves `routes` to `createVSCodeFetch` / `vscodeFetch` calls made inside
29
+ * `webview`, streaming each response back as it is produced. Host calls such
30
+ * as `installLinkInterceptor` or `createVSCodeStorage` reject in the webview;
31
+ * use `serveWebviewHost` to serve them.
32
+ */
33
+ export declare function serveWebviewRoutes(webview: WebviewLike, routes: WebviewRoutes, options?: ServeWebviewRoutesOptions): Disposable;
34
+ //# sourceMappingURL=serve.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"serve.d.ts","sourceRoot":"","sources":["../../src/host/serve.ts"],"names":[],"mappings":"AAOA,OAAO,EAEL,KAAK,UAAU,EACf,KAAK,yBAAyB,EAC9B,KAAK,WAAW,EAChB,KAAK,aAAa,EACnB,oBAAiB;AAElB,gFAAgF;AAChF,MAAM,MAAM,WAAW,GAAG;IACxB,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;IAC1B,MAAM,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,WAAW,CAAC,IAAI,CAAC,CAAC;CACxD,CAAC;AAEF,MAAM,MAAM,uBAAuB,GAAG,yBAAyB,GAAG;IAChE,MAAM,CAAC,EAAE,aAAa,CAAC;IACvB;;;OAGG;IACH,YAAY,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,WAAW,CAAC,OAAO,CAAC,GAAG,OAAO,GAAG,IAAI,CAAC;IACtE,kEAAkE;IAClE,eAAe,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACpC,kEAAkE;IAClE,OAAO,CAAC,EAAE,WAAW,CAAC;IACtB,sDAAsD;IACtD,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB,CAAC;AAaF;;;;GAIG;AACH,wBAAgB,gBAAgB,CAC9B,OAAO,EAAE,WAAW,EACpB,EACE,MAAW,EACX,YAAY,EACZ,eAA0C,EAC1C,OAAO,EACP,aAAuC,EACvC,GAAG,YAAY,EAChB,GAAE,uBAA4B,GAC9B,UAAU,CAyEZ;AAED;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAChC,OAAO,EAAE,WAAW,EACpB,MAAM,EAAE,aAAa,EACrB,OAAO,CAAC,EAAE,yBAAyB,GAClC,UAAU,CAEZ"}
@@ -0,0 +1,93 @@
1
+ import { DEFAULT_EXTERNAL_SCHEMES, parseExternalUrl } from "../external-url.js";
2
+ import { VSCODE_BRIDGE_CHANNEL, isWebviewToHostMessage } from "../protocol.js";
3
+ import { serveRoutes } from "./router.js";
4
+ // src/host/serve.ts
5
+ const errorMessage = (error) => error instanceof Error ? error.message : String(error);
6
+ const stringParam = (value, name) => {
7
+ if (typeof value !== "string") throw new TypeError(`${name} must be a string`);
8
+ return value;
9
+ };
10
+ /**
11
+ * Serves `vscodeFetch` routes and the webview's host calls, such as
12
+ * `installLinkInterceptor` opening links and `createVSCodeStorage`, for one
13
+ * webview. Host calls without a handler reject in the webview.
14
+ */
15
+ function serveWebviewHost(webview, { routes = {}, openExternal, externalSchemes = DEFAULT_EXTERNAL_SCHEMES, storage, storagePrefix = "@assistant-ui/vscode:", ...routeOptions } = {}) {
16
+ const handlers = {};
17
+ if (openExternal) handlers.openExternal = async (value) => {
18
+ const url = parseExternalUrl(value, externalSchemes);
19
+ if (!url) throw new Error(`Refused to open ${String(value)}`);
20
+ return await openExternal(url) !== false;
21
+ };
22
+ if (storage) {
23
+ const storageKey = (key) => storagePrefix + stringParam(key, "key");
24
+ handlers["storage.getItem"] = (key) => {
25
+ const value = storage.get(storageKey(key));
26
+ return typeof value === "string" ? value : null;
27
+ };
28
+ handlers["storage.setItem"] = async (key, value) => {
29
+ await storage.update(storageKey(key), stringParam(value, "value"));
30
+ };
31
+ handlers["storage.removeItem"] = async (key) => {
32
+ await storage.update(storageKey(key), void 0);
33
+ };
34
+ }
35
+ const respond = (response) => {
36
+ const message = {
37
+ channel: VSCODE_BRIDGE_CHANNEL,
38
+ kind: "rpc:response",
39
+ ...response
40
+ };
41
+ Promise.resolve(webview.postMessage(message)).catch(() => void 0);
42
+ };
43
+ const pending = /* @__PURE__ */ new Set();
44
+ const handle = async ({ id, method, params }) => {
45
+ if (pending.has(id)) return;
46
+ pending.add(id);
47
+ try {
48
+ const handler = Object.hasOwn(handlers, method) ? handlers[method] : null;
49
+ if (!handler) throw new Error(`${method} is not served by this host`);
50
+ const result = await handler(...params);
51
+ if (pending.delete(id)) respond({
52
+ id,
53
+ ok: true,
54
+ result
55
+ });
56
+ } catch (error) {
57
+ if (pending.delete(id)) respond({
58
+ id,
59
+ ok: false,
60
+ message: errorMessage(error)
61
+ });
62
+ }
63
+ };
64
+ const server = serveRoutes(webview, routes, routeOptions);
65
+ const subscription = webview.onDidReceiveMessage((message) => {
66
+ if (isWebviewToHostMessage(message) && message.kind === "rpc:request") handle(message);
67
+ });
68
+ return { dispose: () => {
69
+ subscription.dispose();
70
+ for (const id of pending) respond({
71
+ id,
72
+ ok: false,
73
+ message: "Webview host disposed"
74
+ });
75
+ pending.clear();
76
+ server.dispose();
77
+ } };
78
+ }
79
+ /**
80
+ * Serves `routes` to `createVSCodeFetch` / `vscodeFetch` calls made inside
81
+ * `webview`, streaming each response back as it is produced. Host calls such
82
+ * as `installLinkInterceptor` or `createVSCodeStorage` reject in the webview;
83
+ * use `serveWebviewHost` to serve them.
84
+ */
85
+ function serveWebviewRoutes(webview, routes, options) {
86
+ return serveWebviewHost(webview, {
87
+ ...options,
88
+ routes
89
+ });
90
+ }
91
+ export { serveWebviewHost, serveWebviewRoutes };
92
+
93
+ //# sourceMappingURL=serve.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"serve.js","names":[],"sources":["../../src/host/serve.ts"],"sourcesContent":["import { DEFAULT_EXTERNAL_SCHEMES, parseExternalUrl } from \"../external-url\";\nimport {\n isWebviewToHostMessage,\n VSCODE_BRIDGE_CHANNEL,\n type RpcRequestMessage,\n type RpcResponseMessage,\n} from \"../protocol\";\nimport {\n serveRoutes,\n type Disposable,\n type ServeWebviewRoutesOptions,\n type WebviewLike,\n type WebviewRoutes,\n} from \"./router\";\n\n/** Matches `vscode.Memento`, whose `update(key, undefined)` removes the key. */\nexport type MementoLike = {\n get(key: string): unknown;\n update(key: string, value: unknown): PromiseLike<void>;\n};\n\nexport type ServeWebviewHostOptions = ServeWebviewRoutesOptions & {\n routes?: WebviewRoutes;\n /**\n * Opens a URL forwarded by `installLinkInterceptor`, after the host has\n * checked its scheme against `externalSchemes`.\n */\n openExternal?: (url: string) => PromiseLike<boolean> | boolean | void;\n /** URL schemes `openExternal` may receive, such as `\"https:\"`. */\n externalSchemes?: readonly string[];\n /** Backs `createVSCodeStorage`, such as `context.globalState`. */\n storage?: MementoLike;\n /** Namespaces the webview's keys inside `storage`. */\n storagePrefix?: string;\n};\n\ntype RpcHandler = (...params: unknown[]) => unknown;\n\nconst errorMessage = (error: unknown) =>\n error instanceof Error ? error.message : String(error);\n\nconst stringParam = (value: unknown, name: string): string => {\n if (typeof value !== \"string\")\n throw new TypeError(`${name} must be a string`);\n return value;\n};\n\n/**\n * Serves `vscodeFetch` routes and the webview's host calls, such as\n * `installLinkInterceptor` opening links and `createVSCodeStorage`, for one\n * webview. Host calls without a handler reject in the webview.\n */\nexport function serveWebviewHost(\n webview: WebviewLike,\n {\n routes = {},\n openExternal,\n externalSchemes = DEFAULT_EXTERNAL_SCHEMES,\n storage,\n storagePrefix = \"@assistant-ui/vscode:\",\n ...routeOptions\n }: ServeWebviewHostOptions = {},\n): Disposable {\n const handlers: Record<string, RpcHandler> = {};\n\n if (openExternal) {\n handlers.openExternal = async (value) => {\n const url = parseExternalUrl(value, externalSchemes);\n if (!url) throw new Error(`Refused to open ${String(value)}`);\n return (await openExternal(url)) !== false;\n };\n }\n\n if (storage) {\n const storageKey = (key: unknown) =>\n storagePrefix + stringParam(key, \"key\");\n handlers[\"storage.getItem\"] = (key) => {\n const value = storage.get(storageKey(key));\n return typeof value === \"string\" ? value : null;\n };\n handlers[\"storage.setItem\"] = async (key, value) => {\n await storage.update(storageKey(key), stringParam(value, \"value\"));\n };\n handlers[\"storage.removeItem\"] = async (key) => {\n await storage.update(storageKey(key), undefined);\n };\n }\n\n const respond = (\n response:\n | { id: string; ok: true; result: unknown }\n | { id: string; ok: false; message: string },\n ) => {\n const message: RpcResponseMessage = {\n channel: VSCODE_BRIDGE_CHANNEL,\n kind: \"rpc:response\",\n ...response,\n };\n Promise.resolve(webview.postMessage(message)).catch(() => undefined);\n };\n\n const pending = new Set<string>();\n\n const handle = async ({ id, method, params }: RpcRequestMessage) => {\n if (pending.has(id)) return;\n pending.add(id);\n try {\n const handler = Object.hasOwn(handlers, method) ? handlers[method] : null;\n if (!handler) throw new Error(`${method} is not served by this host`);\n const result = await handler(...params);\n if (pending.delete(id)) respond({ id, ok: true, result });\n } catch (error) {\n if (pending.delete(id)) {\n respond({ id, ok: false, message: errorMessage(error) });\n }\n }\n };\n\n const server = serveRoutes(webview, routes, routeOptions);\n const subscription = webview.onDidReceiveMessage((message: unknown) => {\n if (isWebviewToHostMessage(message) && message.kind === \"rpc:request\") {\n void handle(message);\n }\n });\n\n return {\n dispose: () => {\n subscription.dispose();\n for (const id of pending) {\n respond({ id, ok: false, message: \"Webview host disposed\" });\n }\n pending.clear();\n server.dispose();\n },\n };\n}\n\n/**\n * Serves `routes` to `createVSCodeFetch` / `vscodeFetch` calls made inside\n * `webview`, streaming each response back as it is produced. Host calls such\n * as `installLinkInterceptor` or `createVSCodeStorage` reject in the webview;\n * use `serveWebviewHost` to serve them.\n */\nexport function serveWebviewRoutes(\n webview: WebviewLike,\n routes: WebviewRoutes,\n options?: ServeWebviewRoutesOptions,\n): Disposable {\n return serveWebviewHost(webview, { ...options, routes });\n}\n"],"mappings":";;;;AAsCA,MAAM,gBAAgB,UACpB,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AAEvD,MAAM,eAAe,OAAgB,SAAyB;CAC5D,IAAI,OAAO,UAAU,UACnB,MAAM,IAAI,UAAU,GAAG,KAAK,kBAAkB;CAChD,OAAO;AACT;;;;;;AAOA,SAAgB,iBACd,SACA,EACE,SAAS,CAAC,GACV,cACA,kBAAkB,0BAClB,SACA,gBAAgB,yBAChB,GAAG,iBACwB,CAAC,GAClB;CACZ,MAAM,WAAuC,CAAC;CAE9C,IAAI,cACF,SAAS,eAAe,OAAO,UAAU;EACvC,MAAM,MAAM,iBAAiB,OAAO,eAAe;EACnD,IAAI,CAAC,KAAK,MAAM,IAAI,MAAM,mBAAmB,OAAO,KAAK,GAAG;EAC5D,OAAQ,MAAM,aAAa,GAAG,MAAO;CACvC;CAGF,IAAI,SAAS;EACX,MAAM,cAAc,QAClB,gBAAgB,YAAY,KAAK,KAAK;EACxC,SAAS,sBAAsB,QAAQ;GACrC,MAAM,QAAQ,QAAQ,IAAI,WAAW,GAAG,CAAC;GACzC,OAAO,OAAO,UAAU,WAAW,QAAQ;EAC7C;EACA,SAAS,qBAAqB,OAAO,KAAK,UAAU;GAClD,MAAM,QAAQ,OAAO,WAAW,GAAG,GAAG,YAAY,OAAO,OAAO,CAAC;EACnE;EACA,SAAS,wBAAwB,OAAO,QAAQ;GAC9C,MAAM,QAAQ,OAAO,WAAW,GAAG,GAAG,KAAA,CAAS;EACjD;CACF;CAEA,MAAM,WACJ,aAGG;EACH,MAAM,UAA8B;GAClC,SAAS;GACT,MAAM;GACN,GAAG;EACL;EACA,QAAQ,QAAQ,QAAQ,YAAY,OAAO,CAAC,CAAC,CAAC,YAAY,KAAA,CAAS;CACrE;CAEA,MAAM,0BAAU,IAAI,IAAY;CAEhC,MAAM,SAAS,OAAO,EAAE,IAAI,QAAQ,aAAgC;EAClE,IAAI,QAAQ,IAAI,EAAE,GAAG;EACrB,QAAQ,IAAI,EAAE;EACd,IAAI;GACF,MAAM,UAAU,OAAO,OAAO,UAAU,MAAM,IAAI,SAAS,UAAU;GACrE,IAAI,CAAC,SAAS,MAAM,IAAI,MAAM,GAAG,OAAO,4BAA4B;GACpE,MAAM,SAAS,MAAM,QAAQ,GAAG,MAAM;GACtC,IAAI,QAAQ,OAAO,EAAE,GAAG,QAAQ;IAAE;IAAI,IAAI;IAAM;GAAO,CAAC;EAC1D,SAAS,OAAO;GACd,IAAI,QAAQ,OAAO,EAAE,GACnB,QAAQ;IAAE;IAAI,IAAI;IAAO,SAAS,aAAa,KAAK;GAAE,CAAC;EAE3D;CACF;CAEA,MAAM,SAAS,YAAY,SAAS,QAAQ,YAAY;CACxD,MAAM,eAAe,QAAQ,qBAAqB,YAAqB;EACrE,IAAI,uBAAuB,OAAO,KAAK,QAAQ,SAAS,eACtD,OAAY,OAAO;CAEvB,CAAC;CAED,OAAO,EACL,eAAe;EACb,aAAa,QAAQ;EACrB,KAAK,MAAM,MAAM,SACf,QAAQ;GAAE;GAAI,IAAI;GAAO,SAAS;EAAwB,CAAC;EAE7D,QAAQ,MAAM;EACd,OAAO,QAAQ;CACjB,EACF;AACF;;;;;;;AAQA,SAAgB,mBACd,SACA,QACA,SACY;CACZ,OAAO,iBAAiB,SAAS;EAAE,GAAG;EAAS;CAAO,CAAC;AACzD"}
package/dist/host.d.ts ADDED
@@ -0,0 +1,5 @@
1
+ export { createCspNonce, createWebviewCsp, renderWebviewHtml, type RenderWebviewHtmlOptions, type WebviewCspMode, type WebviewCspOptions, type WebviewCspSource, type WebviewResourceLike, type WebviewSurface, } from "./host/html.js";
2
+ export { type Disposable, type HttpMethod, type RouteHandler, type RouteHandlers, type ServeWebviewRoutesOptions, type WebviewLike, type WebviewRoutes, } from "./host/router.js";
3
+ export { serveWebviewHost, serveWebviewRoutes, type MementoLike, type ServeWebviewHostOptions, } from "./host/serve.js";
4
+ export * from "./protocol.js";
5
+ //# sourceMappingURL=host.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"host.d.ts","sourceRoot":"","sources":["../src/host.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,cAAc,EACd,gBAAgB,EAChB,iBAAiB,EACjB,KAAK,wBAAwB,EAC7B,KAAK,cAAc,EACnB,KAAK,iBAAiB,EACtB,KAAK,gBAAgB,EACrB,KAAK,mBAAmB,EACxB,KAAK,cAAc,GACpB,uBAAoB;AACrB,OAAO,EACL,KAAK,UAAU,EACf,KAAK,UAAU,EACf,KAAK,YAAY,EACjB,KAAK,aAAa,EAClB,KAAK,yBAAyB,EAC9B,KAAK,WAAW,EAChB,KAAK,aAAa,GACnB,yBAAsB;AACvB,OAAO,EACL,gBAAgB,EAChB,kBAAkB,EAClB,KAAK,WAAW,EAChB,KAAK,uBAAuB,GAC7B,wBAAqB;AACtB,8BAA2B"}
package/dist/host.js ADDED
@@ -0,0 +1,4 @@
1
+ import { createCspNonce, createWebviewCsp, renderWebviewHtml } from "./host/html.js";
2
+ import { VSCODE_BRIDGE_CHANNEL, VSCODE_VIRTUAL_ORIGIN, isHostToWebviewMessage, isWebviewToHostMessage } from "./protocol.js";
3
+ import { serveWebviewHost, serveWebviewRoutes } from "./host/serve.js";
4
+ export { VSCODE_BRIDGE_CHANNEL, VSCODE_VIRTUAL_ORIGIN, createCspNonce, createWebviewCsp, isHostToWebviewMessage, isWebviewToHostMessage, renderWebviewHtml, serveWebviewHost, serveWebviewRoutes };
@@ -0,0 +1,46 @@
1
+ export declare const VSCODE_BRIDGE_CHANNEL = "aui-vscode";
2
+ export declare const VSCODE_VIRTUAL_ORIGIN = "https://webview.vscode.invalid";
3
+ export type BridgeHeaders = [name: string, value: string][];
4
+ type BridgeEnvelope<TKind extends string> = {
5
+ channel: typeof VSCODE_BRIDGE_CHANNEL;
6
+ kind: TKind;
7
+ id: string;
8
+ };
9
+ export type FetchRequestMessage = BridgeEnvelope<"fetch:request"> & {
10
+ url: string;
11
+ method: string;
12
+ headers: BridgeHeaders;
13
+ body: Uint8Array<ArrayBuffer> | null;
14
+ };
15
+ export type FetchAbortMessage = BridgeEnvelope<"fetch:abort">;
16
+ export type FetchResetMessage = BridgeEnvelope<"fetch:reset">;
17
+ export type FetchHeadMessage = BridgeEnvelope<"fetch:head"> & {
18
+ status: number;
19
+ statusText: string;
20
+ headers: BridgeHeaders;
21
+ };
22
+ export type FetchChunkMessage = BridgeEnvelope<"fetch:chunk"> & {
23
+ chunk: Uint8Array<ArrayBuffer>;
24
+ };
25
+ export type FetchEndMessage = BridgeEnvelope<"fetch:end">;
26
+ export type FetchErrorMessage = BridgeEnvelope<"fetch:error"> & {
27
+ message: string;
28
+ };
29
+ export type RpcRequestMessage = BridgeEnvelope<"rpc:request"> & {
30
+ method: string;
31
+ params: unknown[];
32
+ };
33
+ export type RpcResponseMessage = BridgeEnvelope<"rpc:response"> & ({
34
+ ok: true;
35
+ result: unknown;
36
+ } | {
37
+ ok: false;
38
+ message: string;
39
+ });
40
+ export type WebviewToHostMessage = FetchRequestMessage | FetchAbortMessage | FetchResetMessage | RpcRequestMessage;
41
+ export type HostToWebviewMessage = FetchHeadMessage | FetchChunkMessage | FetchEndMessage | FetchErrorMessage | RpcResponseMessage;
42
+ export type BridgeMessage = WebviewToHostMessage | HostToWebviewMessage;
43
+ export declare const isWebviewToHostMessage: (value: unknown) => value is WebviewToHostMessage;
44
+ export declare const isHostToWebviewMessage: (value: unknown) => value is HostToWebviewMessage;
45
+ export {};
46
+ //# sourceMappingURL=protocol.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"protocol.d.ts","sourceRoot":"","sources":["../src/protocol.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,qBAAqB,eAAe,CAAC;AAElD,eAAO,MAAM,qBAAqB,mCAAmC,CAAC;AAEtE,MAAM,MAAM,aAAa,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,CAAC,EAAE,CAAC;AAE5D,KAAK,cAAc,CAAC,KAAK,SAAS,MAAM,IAAI;IAC1C,OAAO,EAAE,OAAO,qBAAqB,CAAC;IACtC,IAAI,EAAE,KAAK,CAAC;IACZ,EAAE,EAAE,MAAM,CAAC;CACZ,CAAC;AAEF,MAAM,MAAM,mBAAmB,GAAG,cAAc,CAAC,eAAe,CAAC,GAAG;IAClE,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,aAAa,CAAC;IACvB,IAAI,EAAE,UAAU,CAAC,WAAW,CAAC,GAAG,IAAI,CAAC;CACtC,CAAC;AAEF,MAAM,MAAM,iBAAiB,GAAG,cAAc,CAAC,aAAa,CAAC,CAAC;AAE9D,MAAM,MAAM,iBAAiB,GAAG,cAAc,CAAC,aAAa,CAAC,CAAC;AAE9D,MAAM,MAAM,gBAAgB,GAAG,cAAc,CAAC,YAAY,CAAC,GAAG;IAC5D,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,aAAa,CAAC;CACxB,CAAC;AAEF,MAAM,MAAM,iBAAiB,GAAG,cAAc,CAAC,aAAa,CAAC,GAAG;IAC9D,KAAK,EAAE,UAAU,CAAC,WAAW,CAAC,CAAC;CAChC,CAAC;AAEF,MAAM,MAAM,eAAe,GAAG,cAAc,CAAC,WAAW,CAAC,CAAC;AAE1D,MAAM,MAAM,iBAAiB,GAAG,cAAc,CAAC,aAAa,CAAC,GAAG;IAC9D,OAAO,EAAE,MAAM,CAAC;CACjB,CAAC;AAEF,MAAM,MAAM,iBAAiB,GAAG,cAAc,CAAC,aAAa,CAAC,GAAG;IAC9D,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,OAAO,EAAE,CAAC;CACnB,CAAC;AAEF,MAAM,MAAM,kBAAkB,GAAG,cAAc,CAAC,cAAc,CAAC,GAC7D,CAAC;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,MAAM,EAAE,OAAO,CAAA;CAAE,GAAG;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC,CAAC;AAEnE,MAAM,MAAM,oBAAoB,GAC5B,mBAAmB,GACnB,iBAAiB,GACjB,iBAAiB,GACjB,iBAAiB,CAAC;AAEtB,MAAM,MAAM,oBAAoB,GAC5B,gBAAgB,GAChB,iBAAiB,GACjB,eAAe,GACf,iBAAiB,GACjB,kBAAkB,CAAC;AAEvB,MAAM,MAAM,aAAa,GAAG,oBAAoB,GAAG,oBAAoB,CAAC;AAgExE,eAAO,MAAM,sBAAsB,GACjC,OAAO,OAAO,KACb,KAAK,IAAI,oBAAuD,CAAC;AAEpE,eAAO,MAAM,sBAAsB,GACjC,OAAO,OAAO,KACb,KAAK,IAAI,oBAAuD,CAAC"}
@@ -0,0 +1,30 @@
1
+ // src/protocol.ts
2
+ const VSCODE_BRIDGE_CHANNEL = "aui-vscode";
3
+ const VSCODE_VIRTUAL_ORIGIN = "https://webview.vscode.invalid";
4
+ const isString = (value) => typeof value === "string";
5
+ const isBytes = (value) => value instanceof Uint8Array;
6
+ const isHeaders = (value) => Array.isArray(value) && value.every((entry) => Array.isArray(entry) && entry.length === 2 && isString(entry[0]) && isString(entry[1]));
7
+ const hasNoPayload = () => true;
8
+ const WEBVIEW_TO_HOST = {
9
+ "fetch:request": (m) => isString(m.url) && isString(m.method) && isHeaders(m.headers) && (m.body === null || isBytes(m.body)),
10
+ "fetch:abort": hasNoPayload,
11
+ "fetch:reset": hasNoPayload,
12
+ "rpc:request": (m) => isString(m.method) && Array.isArray(m.params)
13
+ };
14
+ const HOST_TO_WEBVIEW = {
15
+ "fetch:head": (m) => typeof m.status === "number" && isString(m.statusText) && isHeaders(m.headers),
16
+ "fetch:chunk": (m) => isBytes(m.chunk),
17
+ "fetch:end": hasNoPayload,
18
+ "fetch:error": (m) => isString(m.message),
19
+ "rpc:response": (m) => m.ok === true || m.ok === false && isString(m.message)
20
+ };
21
+ const matches = (validators, value) => {
22
+ if (typeof value !== "object" || value === null) return false;
23
+ const message = value;
24
+ return message.channel === "aui-vscode" && isString(message.id) && isString(message.kind) && Object.hasOwn(validators, message.kind) && validators[message.kind](message);
25
+ };
26
+ const isWebviewToHostMessage = (value) => matches(WEBVIEW_TO_HOST, value);
27
+ const isHostToWebviewMessage = (value) => matches(HOST_TO_WEBVIEW, value);
28
+ export { VSCODE_BRIDGE_CHANNEL, VSCODE_VIRTUAL_ORIGIN, isHostToWebviewMessage, isWebviewToHostMessage };
29
+
30
+ //# sourceMappingURL=protocol.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"protocol.js","names":[],"sources":["../src/protocol.ts"],"sourcesContent":["export const VSCODE_BRIDGE_CHANNEL = \"aui-vscode\";\n\nexport const VSCODE_VIRTUAL_ORIGIN = \"https://webview.vscode.invalid\";\n\nexport type BridgeHeaders = [name: string, value: string][];\n\ntype BridgeEnvelope<TKind extends string> = {\n channel: typeof VSCODE_BRIDGE_CHANNEL;\n kind: TKind;\n id: string;\n};\n\nexport type FetchRequestMessage = BridgeEnvelope<\"fetch:request\"> & {\n url: string;\n method: string;\n headers: BridgeHeaders;\n body: Uint8Array<ArrayBuffer> | null;\n};\n\nexport type FetchAbortMessage = BridgeEnvelope<\"fetch:abort\">;\n\nexport type FetchResetMessage = BridgeEnvelope<\"fetch:reset\">;\n\nexport type FetchHeadMessage = BridgeEnvelope<\"fetch:head\"> & {\n status: number;\n statusText: string;\n headers: BridgeHeaders;\n};\n\nexport type FetchChunkMessage = BridgeEnvelope<\"fetch:chunk\"> & {\n chunk: Uint8Array<ArrayBuffer>;\n};\n\nexport type FetchEndMessage = BridgeEnvelope<\"fetch:end\">;\n\nexport type FetchErrorMessage = BridgeEnvelope<\"fetch:error\"> & {\n message: string;\n};\n\nexport type RpcRequestMessage = BridgeEnvelope<\"rpc:request\"> & {\n method: string;\n params: unknown[];\n};\n\nexport type RpcResponseMessage = BridgeEnvelope<\"rpc:response\"> &\n ({ ok: true; result: unknown } | { ok: false; message: string });\n\nexport type WebviewToHostMessage =\n | FetchRequestMessage\n | FetchAbortMessage\n | FetchResetMessage\n | RpcRequestMessage;\n\nexport type HostToWebviewMessage =\n | FetchHeadMessage\n | FetchChunkMessage\n | FetchEndMessage\n | FetchErrorMessage\n | RpcResponseMessage;\n\nexport type BridgeMessage = WebviewToHostMessage | HostToWebviewMessage;\n\ntype Fields = Record<string, unknown>;\n\nconst isString = (value: unknown): value is string => typeof value === \"string\";\n\nconst isBytes = (value: unknown) => value instanceof Uint8Array;\n\nconst isHeaders = (value: unknown) =>\n Array.isArray(value) &&\n value.every(\n (entry) =>\n Array.isArray(entry) &&\n entry.length === 2 &&\n isString(entry[0]) &&\n isString(entry[1]),\n );\n\nconst hasNoPayload = () => true;\n\nconst WEBVIEW_TO_HOST: Record<\n WebviewToHostMessage[\"kind\"],\n (message: Fields) => boolean\n> = {\n \"fetch:request\": (m) =>\n isString(m.url) &&\n isString(m.method) &&\n isHeaders(m.headers) &&\n (m.body === null || isBytes(m.body)),\n \"fetch:abort\": hasNoPayload,\n \"fetch:reset\": hasNoPayload,\n \"rpc:request\": (m) => isString(m.method) && Array.isArray(m.params),\n};\n\nconst HOST_TO_WEBVIEW: Record<\n HostToWebviewMessage[\"kind\"],\n (message: Fields) => boolean\n> = {\n \"fetch:head\": (m) =>\n typeof m.status === \"number\" &&\n isString(m.statusText) &&\n isHeaders(m.headers),\n \"fetch:chunk\": (m) => isBytes(m.chunk),\n \"fetch:end\": hasNoPayload,\n \"fetch:error\": (m) => isString(m.message),\n \"rpc:response\": (m) =>\n m.ok === true || (m.ok === false && isString(m.message)),\n};\n\nconst matches = (\n validators: Record<string, (message: Fields) => boolean>,\n value: unknown,\n) => {\n if (typeof value !== \"object\" || value === null) return false;\n const message = value as Fields;\n return (\n message.channel === VSCODE_BRIDGE_CHANNEL &&\n isString(message.id) &&\n isString(message.kind) &&\n Object.hasOwn(validators, message.kind) &&\n validators[message.kind]!(message)\n );\n};\n\nexport const isWebviewToHostMessage = (\n value: unknown,\n): value is WebviewToHostMessage => matches(WEBVIEW_TO_HOST, value);\n\nexport const isHostToWebviewMessage = (\n value: unknown,\n): value is HostToWebviewMessage => matches(HOST_TO_WEBVIEW, value);\n"],"mappings":";AAAA,MAAa,wBAAwB;AAErC,MAAa,wBAAwB;AA8DrC,MAAM,YAAY,UAAoC,OAAO,UAAU;AAEvE,MAAM,WAAW,UAAmB,iBAAiB;AAErD,MAAM,aAAa,UACjB,MAAM,QAAQ,KAAK,KACnB,MAAM,OACH,UACC,MAAM,QAAQ,KAAK,KACnB,MAAM,WAAW,KACjB,SAAS,MAAM,EAAE,KACjB,SAAS,MAAM,EAAE,CACrB;AAEF,MAAM,qBAAqB;AAE3B,MAAM,kBAGF;CACF,kBAAkB,MAChB,SAAS,EAAE,GAAG,KACd,SAAS,EAAE,MAAM,KACjB,UAAU,EAAE,OAAO,MAClB,EAAE,SAAS,QAAQ,QAAQ,EAAE,IAAI;CACpC,eAAe;CACf,eAAe;CACf,gBAAgB,MAAM,SAAS,EAAE,MAAM,KAAK,MAAM,QAAQ,EAAE,MAAM;AACpE;AAEA,MAAM,kBAGF;CACF,eAAe,MACb,OAAO,EAAE,WAAW,YACpB,SAAS,EAAE,UAAU,KACrB,UAAU,EAAE,OAAO;CACrB,gBAAgB,MAAM,QAAQ,EAAE,KAAK;CACrC,aAAa;CACb,gBAAgB,MAAM,SAAS,EAAE,OAAO;CACxC,iBAAiB,MACf,EAAE,OAAO,QAAS,EAAE,OAAO,SAAS,SAAS,EAAE,OAAO;AAC1D;AAEA,MAAM,WACJ,YACA,UACG;CACH,IAAI,OAAO,UAAU,YAAY,UAAU,MAAM,OAAO;CACxD,MAAM,UAAU;CAChB,OACE,QAAQ,YAAA,gBACR,SAAS,QAAQ,EAAE,KACnB,SAAS,QAAQ,IAAI,KACrB,OAAO,OAAO,YAAY,QAAQ,IAAI,KACtC,WAAW,QAAQ,KAAK,CAAE,OAAO;AAErC;AAEA,MAAa,0BACX,UACkC,QAAQ,iBAAiB,KAAK;AAElE,MAAa,0BACX,UACkC,QAAQ,iBAAiB,KAAK"}
@@ -0,0 +1,3 @@
1
+ /** Routes are served on the virtual origin, so an `http:` or `https:` URL on any other origin reaches none of them. */
2
+ export declare const isForeignHttpUrl: (url: URL) => boolean;
3
+ //# sourceMappingURL=route-url.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"route-url.d.ts","sourceRoot":"","sources":["../src/route-url.ts"],"names":[],"mappings":"AAEA,uHAAuH;AACvH,eAAO,MAAM,gBAAgB,GAAI,KAAK,GAAG,YAEH,CAAC"}
@@ -0,0 +1,7 @@
1
+ import "./protocol.js";
2
+ // src/route-url.ts
3
+ /** Routes are served on the virtual origin, so an `http:` or `https:` URL on any other origin reaches none of them. */
4
+ const isForeignHttpUrl = (url) => (url.protocol === "http:" || url.protocol === "https:") && url.origin !== "https://webview.vscode.invalid";
5
+ export { isForeignHttpUrl };
6
+
7
+ //# sourceMappingURL=route-url.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"route-url.js","names":[],"sources":["../src/route-url.ts"],"sourcesContent":["import { VSCODE_VIRTUAL_ORIGIN } from \"./protocol\";\n\n/** Routes are served on the virtual origin, so an `http:` or `https:` URL on any other origin reaches none of them. */\nexport const isForeignHttpUrl = (url: URL) =>\n (url.protocol === \"http:\" || url.protocol === \"https:\") &&\n url.origin !== VSCODE_VIRTUAL_ORIGIN;\n"],"mappings":";;;AAGA,MAAa,oBAAoB,SAC9B,IAAI,aAAa,WAAW,IAAI,aAAa,aAC9C,IAAI,WAAA"}
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Returns the nonce `renderWebviewHtml` wrote to `<meta property="csp-nonce">`,
3
+ * for libraries that inject `<style>` or `<script>` tags at runtime.
4
+ */
5
+ export declare function getCspNonce(): string | undefined;
6
+ //# sourceMappingURL=csp-nonce.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"csp-nonce.d.ts","sourceRoot":"","sources":["../../src/webview/csp-nonce.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,wBAAgB,WAAW,IAAI,MAAM,GAAG,SAAS,CAKhD"}
@@ -0,0 +1,12 @@
1
+ // src/webview/csp-nonce.ts
2
+ /**
3
+ * Returns the nonce `renderWebviewHtml` wrote to `<meta property="csp-nonce">`,
4
+ * for libraries that inject `<style>` or `<script>` tags at runtime.
5
+ */
6
+ function getCspNonce() {
7
+ const meta = globalThis.document?.querySelector("meta[property=\"csp-nonce\"]");
8
+ return meta?.nonce || meta?.getAttribute("nonce") || void 0;
9
+ }
10
+ export { getCspNonce };
11
+
12
+ //# sourceMappingURL=csp-nonce.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"csp-nonce.js","names":[],"sources":["../../src/webview/csp-nonce.ts"],"sourcesContent":["/**\n * Returns the nonce `renderWebviewHtml` wrote to `<meta property=\"csp-nonce\">`,\n * for libraries that inject `<style>` or `<script>` tags at runtime.\n */\nexport function getCspNonce(): string | undefined {\n const meta = globalThis.document?.querySelector<HTMLMetaElement>(\n 'meta[property=\"csp-nonce\"]',\n );\n return meta?.nonce || meta?.getAttribute(\"nonce\") || undefined;\n}\n"],"mappings":";;;;;AAIA,SAAgB,cAAkC;CAChD,MAAM,OAAO,WAAW,UAAU,cAChC,8BACF;CACA,OAAO,MAAM,SAAS,MAAM,aAAa,OAAO,KAAK,KAAA;AACvD"}
@@ -0,0 +1,14 @@
1
+ import { type WebviewToHostMessage } from "../protocol.js";
2
+ export type VSCodeBridgePort = {
3
+ postMessage(message: WebviewToHostMessage): void;
4
+ onMessage(listener: (message: unknown) => void): () => void;
5
+ };
6
+ export type VSCodeFetch = (input: RequestInfo | URL, init?: RequestInit) => Promise<Response>;
7
+ export declare const webviewPort: VSCodeBridgePort;
8
+ /**
9
+ * Creates a `fetch` that sends each request to `serveWebviewRoutes` in the
10
+ * extension host and streams the response back over `port`.
11
+ */
12
+ export declare function createVSCodeFetch(port?: VSCodeBridgePort): VSCodeFetch;
13
+ export declare const vscodeFetch: VSCodeFetch;
14
+ //# sourceMappingURL=fetch.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fetch.d.ts","sourceRoot":"","sources":["../../src/webview/fetch.ts"],"names":[],"mappings":"AAAA,OAAO,EAKL,KAAK,oBAAoB,EAC1B,uBAAoB;AAKrB,MAAM,MAAM,gBAAgB,GAAG;IAC7B,WAAW,CAAC,OAAO,EAAE,oBAAoB,GAAG,IAAI,CAAC;IACjD,SAAS,CAAC,QAAQ,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,IAAI,GAAG,MAAM,IAAI,CAAC;CAC7D,CAAC;AAEF,MAAM,MAAM,WAAW,GAAG,CACxB,KAAK,EAAE,WAAW,GAAG,GAAG,EACxB,IAAI,CAAC,EAAE,WAAW,KACf,OAAO,CAAC,QAAQ,CAAC,CAAC;AAOvB,eAAO,MAAM,WAAW,EAAE,gBAOzB,CAAC;AAgDF;;;GAGG;AACH,wBAAgB,iBAAiB,CAC/B,IAAI,GAAE,gBAA8B,GACnC,WAAW,CAwHb;AAED,eAAO,MAAM,WAAW,EAAE,WAAiC,CAAC"}
@@ -0,0 +1,164 @@
1
+ import { VSCODE_BRIDGE_CHANNEL, VSCODE_VIRTUAL_ORIGIN, isHostToWebviewMessage } from "../protocol.js";
2
+ import { isForeignHttpUrl } from "../route-url.js";
3
+ import { getVSCodeApi } from "./vscode-api.js";
4
+ import { randomIdPrefix } from "./id.js";
5
+ // src/webview/fetch.ts
6
+ const NULL_BODY_STATUSES = /* @__PURE__ */ new Set([
7
+ 101,
8
+ 204,
9
+ 205,
10
+ 304
11
+ ]);
12
+ const idPrefix = randomIdPrefix();
13
+ let nextId = 0;
14
+ const webviewPort = {
15
+ postMessage: (message) => getVSCodeApi().postMessage(message),
16
+ onMessage: (listener) => {
17
+ const handler = (event) => listener(event.data);
18
+ globalThis.addEventListener("message", handler);
19
+ return () => globalThis.removeEventListener("message", handler);
20
+ }
21
+ };
22
+ const abortReason = (signal) => signal.reason ?? new DOMException("This operation was aborted", "AbortError");
23
+ const readBody = async (body, signal) => {
24
+ const reader = body.getReader();
25
+ const onAbort = () => {
26
+ reader.cancel(signal.reason).catch(() => void 0);
27
+ };
28
+ signal.addEventListener("abort", onAbort, { once: true });
29
+ try {
30
+ const parts = [];
31
+ let size = 0;
32
+ while (true) {
33
+ const { done, value } = await reader.read();
34
+ if (signal.aborted) throw abortReason(signal);
35
+ if (done) break;
36
+ parts.push(value);
37
+ size += value.byteLength;
38
+ }
39
+ const out = new Uint8Array(size);
40
+ let offset = 0;
41
+ for (const part of parts) {
42
+ out.set(part, offset);
43
+ offset += part.byteLength;
44
+ }
45
+ return out;
46
+ } finally {
47
+ signal.removeEventListener("abort", onAbort);
48
+ reader.releaseLock();
49
+ }
50
+ };
51
+ const toRequest = (input, init) => {
52
+ const request = input instanceof Request ? new Request(input, init) : new Request(new URL(String(input), VSCODE_VIRTUAL_ORIGIN), init);
53
+ if (isForeignHttpUrl(new URL(request.url))) throw new TypeError("vscodeFetch only reaches the extension host's routes");
54
+ return request;
55
+ };
56
+ /**
57
+ * Creates a `fetch` that sends each request to `serveWebviewRoutes` in the
58
+ * extension host and streams the response back over `port`.
59
+ */
60
+ function createVSCodeFetch(port = webviewPort) {
61
+ return async (input, init) => {
62
+ const request = toRequest(input, init);
63
+ const { signal } = request;
64
+ if (signal.aborted) throw abortReason(signal);
65
+ const body = request.body ? await readBody(request.body, signal) : null;
66
+ if (signal.aborted) throw abortReason(signal);
67
+ const id = `${idPrefix}-${++nextId}`;
68
+ return new Promise((resolve, reject) => {
69
+ let controller;
70
+ let responded = false;
71
+ let finished = false;
72
+ const finish = () => {
73
+ finished = true;
74
+ unsubscribe();
75
+ signal.removeEventListener("abort", onAbort);
76
+ };
77
+ const fail = (error) => {
78
+ if (finished) return;
79
+ finish();
80
+ if (controller) controller.error(error);
81
+ else reject(error);
82
+ };
83
+ const sendAbort = () => {
84
+ try {
85
+ port.postMessage({
86
+ channel: VSCODE_BRIDGE_CHANNEL,
87
+ kind: "fetch:abort",
88
+ id
89
+ });
90
+ } catch {
91
+ return;
92
+ }
93
+ };
94
+ const onAbort = () => {
95
+ if (finished) return;
96
+ fail(abortReason(signal));
97
+ sendAbort();
98
+ };
99
+ const onHead = (message) => {
100
+ const stream = request.method === "HEAD" || NULL_BODY_STATUSES.has(message.status) ? null : new ReadableStream({
101
+ start: (c) => {
102
+ controller = c;
103
+ },
104
+ cancel: () => {
105
+ if (finished) return;
106
+ finish();
107
+ sendAbort();
108
+ }
109
+ });
110
+ let response;
111
+ try {
112
+ response = new Response(stream, {
113
+ status: message.status,
114
+ statusText: message.statusText,
115
+ headers: message.headers
116
+ });
117
+ } catch (error) {
118
+ finish();
119
+ reject(new TypeError("Failed to fetch", { cause: error }));
120
+ sendAbort();
121
+ return;
122
+ }
123
+ responded = true;
124
+ resolve(response);
125
+ };
126
+ const unsubscribe = port.onMessage((message) => {
127
+ if (finished) return;
128
+ if (!isHostToWebviewMessage(message) || message.id !== id) return;
129
+ switch (message.kind) {
130
+ case "fetch:head":
131
+ onHead(message);
132
+ break;
133
+ case "fetch:chunk":
134
+ controller?.enqueue(message.chunk);
135
+ break;
136
+ case "fetch:end":
137
+ finish();
138
+ if (controller) controller.close();
139
+ else if (!responded) reject(/* @__PURE__ */ new TypeError("Response ended before its head"));
140
+ break;
141
+ case "fetch:error": fail(new TypeError(message.message));
142
+ }
143
+ });
144
+ signal.addEventListener("abort", onAbort, { once: true });
145
+ try {
146
+ port.postMessage({
147
+ channel: VSCODE_BRIDGE_CHANNEL,
148
+ kind: "fetch:request",
149
+ id,
150
+ url: request.url,
151
+ method: request.method,
152
+ headers: [...request.headers],
153
+ body
154
+ });
155
+ } catch (error) {
156
+ fail(error);
157
+ }
158
+ });
159
+ };
160
+ }
161
+ const vscodeFetch = createVSCodeFetch();
162
+ export { createVSCodeFetch, vscodeFetch, webviewPort };
163
+
164
+ //# sourceMappingURL=fetch.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fetch.js","names":[],"sources":["../../src/webview/fetch.ts"],"sourcesContent":["import {\n isHostToWebviewMessage,\n VSCODE_BRIDGE_CHANNEL,\n VSCODE_VIRTUAL_ORIGIN,\n type HostToWebviewMessage,\n type WebviewToHostMessage,\n} from \"../protocol\";\nimport { isForeignHttpUrl } from \"../route-url\";\nimport { getVSCodeApi } from \"./vscode-api\";\nimport { randomIdPrefix } from \"./id\";\n\nexport type VSCodeBridgePort = {\n postMessage(message: WebviewToHostMessage): void;\n onMessage(listener: (message: unknown) => void): () => void;\n};\n\nexport type VSCodeFetch = (\n input: RequestInfo | URL,\n init?: RequestInit,\n) => Promise<Response>;\n\nconst NULL_BODY_STATUSES = new Set([101, 204, 205, 304]);\n\nconst idPrefix = randomIdPrefix();\nlet nextId = 0;\n\nexport const webviewPort: VSCodeBridgePort = {\n postMessage: (message) => getVSCodeApi().postMessage(message),\n onMessage: (listener) => {\n const handler = (event: MessageEvent) => listener(event.data);\n globalThis.addEventListener(\"message\", handler);\n return () => globalThis.removeEventListener(\"message\", handler);\n },\n};\n\nconst abortReason = (signal: AbortSignal): unknown =>\n signal.reason ?? new DOMException(\"This operation was aborted\", \"AbortError\");\n\nconst readBody = async (\n body: ReadableStream<Uint8Array>,\n signal: AbortSignal,\n) => {\n const reader = body.getReader();\n const onAbort = () => {\n reader.cancel(signal.reason).catch(() => undefined);\n };\n signal.addEventListener(\"abort\", onAbort, { once: true });\n try {\n const parts: Uint8Array[] = [];\n let size = 0;\n while (true) {\n const { done, value } = await reader.read();\n if (signal.aborted) throw abortReason(signal);\n if (done) break;\n parts.push(value);\n size += value.byteLength;\n }\n const out = new Uint8Array(size);\n let offset = 0;\n for (const part of parts) {\n out.set(part, offset);\n offset += part.byteLength;\n }\n return out;\n } finally {\n signal.removeEventListener(\"abort\", onAbort);\n reader.releaseLock();\n }\n};\n\nconst toRequest = (input: RequestInfo | URL, init?: RequestInit) => {\n const request =\n input instanceof Request\n ? new Request(input, init)\n : new Request(new URL(String(input), VSCODE_VIRTUAL_ORIGIN), init);\n if (isForeignHttpUrl(new URL(request.url))) {\n throw new TypeError(\"vscodeFetch only reaches the extension host's routes\");\n }\n return request;\n};\n\n/**\n * Creates a `fetch` that sends each request to `serveWebviewRoutes` in the\n * extension host and streams the response back over `port`.\n */\nexport function createVSCodeFetch(\n port: VSCodeBridgePort = webviewPort,\n): VSCodeFetch {\n return async (input, init) => {\n const request = toRequest(input, init);\n const { signal } = request;\n if (signal.aborted) throw abortReason(signal);\n\n const body = request.body ? await readBody(request.body, signal) : null;\n if (signal.aborted) throw abortReason(signal);\n\n const id = `${idPrefix}-${++nextId}`;\n\n return new Promise<Response>((resolve, reject) => {\n let controller: ReadableStreamDefaultController<Uint8Array> | undefined;\n let responded = false;\n let finished = false;\n\n const finish = () => {\n finished = true;\n unsubscribe();\n signal.removeEventListener(\"abort\", onAbort);\n };\n\n const fail = (error: unknown) => {\n if (finished) return;\n finish();\n if (controller) controller.error(error);\n else reject(error);\n };\n\n const sendAbort = () => {\n try {\n port.postMessage({\n channel: VSCODE_BRIDGE_CHANNEL,\n kind: \"fetch:abort\",\n id,\n });\n } catch {\n return;\n }\n };\n\n const onAbort = () => {\n if (finished) return;\n fail(abortReason(signal));\n sendAbort();\n };\n\n const onHead = (\n message: Extract<HostToWebviewMessage, { kind: \"fetch:head\" }>,\n ) => {\n const stream =\n request.method === \"HEAD\" || NULL_BODY_STATUSES.has(message.status)\n ? null\n : new ReadableStream<Uint8Array>({\n start: (c) => {\n controller = c;\n },\n cancel: () => {\n if (finished) return;\n finish();\n sendAbort();\n },\n });\n let response: Response;\n try {\n response = new Response(stream, {\n status: message.status,\n statusText: message.statusText,\n headers: message.headers,\n });\n } catch (error) {\n finish();\n reject(new TypeError(\"Failed to fetch\", { cause: error }));\n sendAbort();\n return;\n }\n responded = true;\n resolve(response);\n };\n\n const unsubscribe = port.onMessage((message) => {\n if (finished) return;\n if (!isHostToWebviewMessage(message) || message.id !== id) return;\n switch (message.kind) {\n case \"fetch:head\":\n onHead(message);\n break;\n case \"fetch:chunk\":\n controller?.enqueue(message.chunk);\n break;\n case \"fetch:end\":\n finish();\n if (controller) controller.close();\n else if (!responded) {\n reject(new TypeError(\"Response ended before its head\"));\n }\n break;\n case \"fetch:error\":\n fail(new TypeError(message.message));\n break;\n }\n });\n\n signal.addEventListener(\"abort\", onAbort, { once: true });\n\n try {\n port.postMessage({\n channel: VSCODE_BRIDGE_CHANNEL,\n kind: \"fetch:request\",\n id,\n url: request.url,\n method: request.method,\n headers: [...request.headers],\n body,\n });\n } catch (error) {\n fail(error);\n }\n });\n };\n}\n\nexport const vscodeFetch: VSCodeFetch = createVSCodeFetch();\n"],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export declare const randomIdPrefix: () => string;
2
+ //# sourceMappingURL=id.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"id.d.ts","sourceRoot":"","sources":["../../src/webview/id.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,cAAc,cAGf,CAAC"}
@@ -0,0 +1,5 @@
1
+ // src/webview/id.ts
2
+ const randomIdPrefix = () => Array.from(globalThis.crypto.getRandomValues(/* @__PURE__ */ new Uint8Array(8)), (byte) => byte.toString(16).padStart(2, "0")).join("");
3
+ export { randomIdPrefix };
4
+
5
+ //# sourceMappingURL=id.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"id.js","names":[],"sources":["../../src/webview/id.ts"],"sourcesContent":["export const randomIdPrefix = () =>\n Array.from(globalThis.crypto.getRandomValues(new Uint8Array(8)), (byte) =>\n byte.toString(16).padStart(2, \"0\"),\n ).join(\"\");\n"],"mappings":";AAAA,MAAa,uBACX,MAAM,KAAK,WAAW,OAAO,gCAAgB,IAAI,WAAW,CAAC,CAAC,IAAI,SAChE,KAAK,SAAS,EAAE,CAAC,CAAC,SAAS,GAAG,GAAG,CACnC,CAAC,CAAC,KAAK,EAAE"}
@@ -0,0 +1,14 @@
1
+ import { type VSCodeBridgePort } from "./fetch.js";
2
+ export type LinkInterceptorOptions = {
3
+ /** URL schemes to open outside the webview, such as `"https:"`. */
4
+ schemes?: readonly string[];
5
+ onError?: (error: unknown) => void;
6
+ port?: VSCodeBridgePort;
7
+ };
8
+ /**
9
+ * Opens absolute links and `window.open` URLs through the extension host's
10
+ * `openExternal`, since a webview cannot navigate away or open windows.
11
+ * Returns a function that removes the listeners and restores `window.open`.
12
+ */
13
+ export declare function installLinkInterceptor({ schemes, onError, port, }?: LinkInterceptorOptions): () => void;
14
+ //# sourceMappingURL=links.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"links.d.ts","sourceRoot":"","sources":["../../src/webview/links.ts"],"names":[],"mappings":"AACA,OAAO,EAAe,KAAK,gBAAgB,EAAE,mBAAgB;AAG7D,MAAM,MAAM,sBAAsB,GAAG;IACnC,mEAAmE;IACnE,OAAO,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAC5B,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,CAAC;IACnC,IAAI,CAAC,EAAE,gBAAgB,CAAC;CACzB,CAAC;AAEF;;;;GAIG;AACH,wBAAgB,sBAAsB,CAAC,EACrC,OAAkC,EAClC,OAAyC,EACzC,IAAkB,GACnB,GAAE,sBAA2B,GAAG,MAAM,IAAI,CAmC1C"}
@@ -0,0 +1,44 @@
1
+ import { DEFAULT_EXTERNAL_SCHEMES, parseExternalUrl } from "../external-url.js";
2
+ import { webviewPort } from "./fetch.js";
3
+ import { callHost } from "./rpc.js";
4
+ // src/webview/links.ts
5
+ /**
6
+ * Opens absolute links and `window.open` URLs through the extension host's
7
+ * `openExternal`, since a webview cannot navigate away or open windows.
8
+ * Returns a function that removes the listeners and restores `window.open`.
9
+ */
10
+ function installLinkInterceptor({ schemes = DEFAULT_EXTERNAL_SCHEMES, onError = (error) => console.error(error), port = webviewPort } = {}) {
11
+ const openExternal = (url) => {
12
+ callHost(port, "openExternal", [url]).catch(onError);
13
+ };
14
+ const onClick = (event) => {
15
+ if (event.defaultPrevented) return;
16
+ if (event.button !== (event.type === "auxclick" ? 1 : 0)) return;
17
+ if (!(event.target instanceof Element)) return;
18
+ const anchor = event.target.closest("a[href]");
19
+ const url = parseExternalUrl(anchor?.getAttribute("href"), schemes);
20
+ if (!url) return;
21
+ event.preventDefault();
22
+ openExternal(url);
23
+ };
24
+ let installed = true;
25
+ const originalOpen = window.open;
26
+ const patchedOpen = function(url, ...rest) {
27
+ const external = installed ? parseExternalUrl(url, schemes) : null;
28
+ if (!external) return originalOpen.call(window, url, ...rest);
29
+ openExternal(external);
30
+ return null;
31
+ };
32
+ document.addEventListener("click", onClick);
33
+ document.addEventListener("auxclick", onClick);
34
+ window.open = patchedOpen;
35
+ return () => {
36
+ installed = false;
37
+ document.removeEventListener("click", onClick);
38
+ document.removeEventListener("auxclick", onClick);
39
+ if (window.open === patchedOpen) window.open = originalOpen;
40
+ };
41
+ }
42
+ export { installLinkInterceptor };
43
+
44
+ //# sourceMappingURL=links.js.map