@volter/editor-core 0.5.58 → 0.5.60

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/BUNDLED_NOTICES CHANGED
@@ -31,7 +31,7 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
31
31
  SOFTWARE.
32
32
 
33
33
 
34
- @volter/editor-project@0.5.58
34
+ @volter/editor-project@0.5.60
35
35
  Declared license: Apache-2.0
36
36
 
37
37
  --- packages/editor-project/LICENSE ---
@@ -240,7 +240,7 @@ Declared license: Apache-2.0
240
240
  limitations under the License.
241
241
 
242
242
 
243
- @volter/editor-sdk@0.5.58
243
+ @volter/editor-sdk@0.5.60
244
244
  Declared license: Apache-2.0
245
245
 
246
246
  --- packages/editor-sdk/LICENSE ---
@@ -461,7 +461,7 @@ The project output-root policy is Apache-2.0, transferred from the original
461
461
  SDK; its exact source is recorded in provenance/editor-server.json.
462
462
 
463
463
 
464
- @volter/editor-threejs@0.5.58
464
+ @volter/editor-threejs@0.5.60
465
465
  Declared license: AGPL-3.0-only AND Apache-2.0
466
466
 
467
467
  --- packages/editor-threejs/LICENSE ---
@@ -7,6 +7,12 @@ import { connect as netConnect } from "node:net";
7
7
  import { basename } from "node:path";
8
8
  import { pipeline } from "node:stream/promises";
9
9
  import { createBrotliCompress, createGzip, constants as zlibConstants } from "node:zlib";
10
+
11
+ // server/js-profiling-policy.ts
12
+ var JS_PROFILING_POLICY_HEADER = "Document-Policy";
13
+ var JS_PROFILING_POLICY_VALUE = "js-profiling";
14
+
15
+ // server/frame-proxy.ts
10
16
  var VSCODE_PATH_BASE = "^\\/$|^\\/(static|favicon|manifest\\.json|code-192|code-512|callback|oauth|out|extensions|builtin|remote|vscode-remote|_static|web-extension|version|delay-shutdown)";
11
17
  var COMPRESSIBLE = /^(?:text\/|application\/(?:javascript|json|wasm|manifest\+json)|image\/svg\+xml)/;
12
18
  var decodeAttr = (value) => value.replaceAll("&quot;", '"').replaceAll("&#39;", "'").replaceAll("&lt;", "<").replaceAll("&gt;", ">").replaceAll("&amp;", "&");
@@ -30,6 +36,9 @@ async function startFrameProxy(options) {
30
36
  const webviewOriginPattern = `http://*.localhost:${port}`;
31
37
  const projectId = basename(projectRoot);
32
38
  const isolation = {
39
+ // The Code-OSS HTML comes from the upstream, not Express. Keep the
40
+ // editor's profiling permission on the actual top-level document too.
41
+ [JS_PROFILING_POLICY_HEADER]: JS_PROFILING_POLICY_VALUE,
33
42
  "Cross-Origin-Opener-Policy": "same-origin",
34
43
  "Cross-Origin-Embedder-Policy": "credentialless",
35
44
  "Cross-Origin-Resource-Policy": "cross-origin"
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
- "sources": ["../../server/frame-proxy.ts"],
4
- "sourcesContent": ["/**\n * THE ONE ORIGIN in front of two servers: the Code-OSS workbench and this\n * session. It listens IN THE SESSION PROCESS \u2014 no spawn, no second program \u2014\n * exactly as the loopback Colyseus server does.\n *\n * /$product-segment/*, /static, /vscode-remote, \u2026 the workbench (the REH)\n * everything else this session\n *\n * WHY A PROXY AND NOT TWO ORIGINS (measured 2026-09-19 against the REH). The\n * REH stamps its own CSP on the workbench page\n * (`createWorkbenchContentSecurityPolicy`, src/vs/server/node/webClientServer.ts):\n *\n * script-src 'self' 'unsafe-eval' blob: 'nonce-\u2026' <sha> http://<the REH's own authority>\n * connect-src 'self' ws: wss: https:\n *\n * The ONLY http origin on either list is the server's own. The editor's own\n * entry module and every project module are imported as module scripts from\n * this session, and every `/__editor/*` call is a fetch to it \u2014 so a session on\n * a second port is refused by `script-src` before CORS is ever consulted, and\n * its fetches by `connect-src` (which carries no `http:` at all). Relaxing that\n * means editing a core service; the proxy costs none.\n *\n * CROSS-ORIGIN ISOLATION. Every response carries COOP `same-origin` and COEP\n * `credentialless`, because the Blender build is `-sPROXY_TO_PTHREAD` and needs\n * `SharedArrayBuffer`. The REH has no cross-origin-isolation flag or header\n * hook of its own \u2014 `--enable-coi` is an ELECTRON-MAIN argument \u2014 so these\n * headers are this proxy's. `credentialless` rather than `require-corp` so the\n * workbench's own cross-origin loads keep working, the same choice `dev.ts`\n * makes.\n *\n * THE WEBVIEW'S SECOND ORIGIN. VS Code loads every webview from\n * `webviewExternalEndpoint`, which defaults to `https://{{uuid}}.vscode-cdn.net/\u2026`\n * \u2014 a host that does not exist for this fork and that a cross-origin-isolated\n * page could not embed anyway. `IWorkbenchConstructionOptions.webviewEndpoint`\n * is the supported door and the REH leaves it unset, so this rewrites the page\n * config to `http://{{uuid}}.localhost:<port>`. It has to be a SUBDOMAIN and\n * not this origin itself: `pre/index.html` refuses to start unless\n * `location.hostname` IS the hash of `{parentOrigin, salt}` or begins with it,\n * and `*.localhost` is the one wildcard host a browser resolves to loopback\n * with no DNS. Two consequences handled here: the webview is genuinely\n * cross-origin, so its responses need CORP `cross-origin` (without it the frame\n * dies as `corp-not-same-origin-after-defaulted-to-same-origin-by-coep`, which\n * Chrome renders as \"refused to connect\" and which cost an hour); and the REH's\n * own `frame-src 'self' https://*.vscode-cdn.net` is widened on the way through.\n *\n * COMPRESSION IS THIS PROXY'S JOB, and it is the reason the frame's page weight\n * is not the workbench's disk size. U3 measured `transferSize ===\n * decodedBodySize` on every entry of a 41.75 MB page: the REH writes no\n * `content-encoding` at all, and neither did this proxy. It negotiates\n * `accept-encoding` now \u2014 brotli, then gzip \u2014 over text, JavaScript, CSS, JSON\n * and wasm only; everything else is byte-for-byte.\n */\n\nimport {\n createServer,\n request as httpRequest,\n type IncomingMessage,\n type Server,\n type ServerResponse,\n} from 'node:http';\nimport { connect as netConnect, type Socket } from 'node:net';\nimport { basename } from 'node:path';\nimport type { Transform } from 'node:stream';\nimport { pipeline } from 'node:stream/promises';\nimport { createBrotliCompress, createGzip, constants as zlibConstants } from 'node:zlib';\n\nexport interface FrameProxyOptions {\n /** The one port a person opens: this proxy's. */\n readonly port: number;\n /** The Code-OSS workbench (REH) behind it. */\n readonly upstreamPort: number;\n /** This session's own port. */\n readonly sessionPort: number;\n /** The project the workbench opens; its folder name is the `?project=` id. */\n readonly projectRoot: string;\n /**\n * Which way round the PRODUCT paints, from its own declaration\n * (`package.json#vgai.product.colorTheme`, `@volter/editor-sdk/session/\n * product-locator`), or `null` when this session could not resolve a product\n * \u2014 in which case the page keeps the workbench's own web default and the\n * `served-modules` door is what says why there is no product.\n */\n readonly colorTheme: 'dark' | 'light' | null;\n readonly log: (line: string) => void;\n}\n\nexport interface FrameProxy {\n /** The url to open: this origin, on this project. */\n readonly url: string;\n close(): Promise<void>;\n}\n\n/**\n * The workbench's own paths. `<product segment>` (`/oss-dev` out of a source\n * checkout, the quality segment out of a build \u2014 `getServerProductSegment`) is\n * DISCOVERED at boot rather than spelled here, because the page states it.\n */\nconst VSCODE_PATH_BASE =\n '^\\\\/$|^\\\\/(static|favicon|manifest\\\\.json|code-192|code-512|callback|oauth|out|extensions|builtin|remote|vscode-remote|_static|web-extension|version|delay-shutdown)';\n\n/** What is worth compressing. Everything else goes through byte for byte. */\nconst COMPRESSIBLE =\n /^(?:text\\/|application\\/(?:javascript|json|wasm|manifest\\+json)|image\\/svg\\+xml)/;\n\n/** `&quot;`-encoded HTML attribute value \u2194 JSON, the way webClientServer writes it. */\nconst decodeAttr = (value: string): string =>\n value\n .replaceAll('&quot;', '\"')\n .replaceAll('&#39;', \"'\")\n .replaceAll('&lt;', '<')\n .replaceAll('&gt;', '>')\n .replaceAll('&amp;', '&');\nconst encodeAttr = (value: string): string =>\n value\n .replaceAll('&', '&amp;')\n .replaceAll('\"', '&quot;')\n .replaceAll(\"'\", '&#39;')\n .replaceAll('<', '&lt;')\n .replaceAll('>', '&gt;');\n\n/**\n * Which encoding the browser asked for, in this proxy's order of preference.\n * `null` means \"send it as it is\" \u2014 no `accept-encoding`, or none we write.\n */\nfunction negotiateEncoding(header: string | string[] | undefined): 'br' | 'gzip' | null {\n const accepted = (Array.isArray(header) ? header.join(',') : (header ?? '')).toLowerCase();\n if (accepted.includes('br')) return 'br';\n if (accepted.includes('gzip')) return 'gzip';\n return null;\n}\n\n/**\n * Quality 5 rather than brotli's default 11. The default spends seconds on a\n * multi-megabyte workbench chunk and this proxy compresses on the request path,\n * with the person waiting; 5 is the knee where the ratio is nearly the same and\n * the CPU is not.\n */\nfunction compressor(encoding: 'br' | 'gzip'): Transform {\n return encoding === 'br'\n ? createBrotliCompress({ params: { [zlibConstants.BROTLI_PARAM_QUALITY]: 5 } })\n : createGzip({ level: 6 });\n}\n\nexport async function startFrameProxy(options: FrameProxyOptions): Promise<FrameProxy> {\n const { port, upstreamPort, sessionPort, projectRoot, colorTheme, log } = options;\n const upstream = { host: '127.0.0.1', port: upstreamPort };\n const session = { host: '127.0.0.1', port: sessionPort };\n const upstreamOrigin = `http://${upstream.host}:${upstream.port}`;\n const selfOrigin = `http://127.0.0.1:${port}`;\n const webviewOriginTemplate = `http://{{uuid}}.localhost:${port}`;\n const webviewOriginPattern = `http://*.localhost:${port}`;\n const projectId = basename(projectRoot);\n\n const isolation: Record<string, string> = {\n 'Cross-Origin-Opener-Policy': 'same-origin',\n 'Cross-Origin-Embedder-Policy': 'credentialless',\n 'Cross-Origin-Resource-Policy': 'cross-origin',\n };\n\n let vscodePath = new RegExp(VSCODE_PATH_BASE);\n\n /**\n * THE DOCUMENT'S OWN COLOUR, before it has any styles at all.\n *\n * `initialColorTheme` below fixes the WORKBENCH's first paint, and that is not the page's\n * first paint: measured 2026-09-21 on the live release, `html` and `body` compute\n * `rgba(0, 0, 0, 0)` \u2014 upstream's `workbench.css` styles `.monaco-workbench` and nothing\n * above it \u2014 so from the document's first frame until `Workbench.renderWorkbench` builds that\n * element, the canvas is the BROWSER's default, which is white. One frame of it survived F1's\n * first reading (frame 0 at 0.0 s white, frame 1 at 0.5 s already the product's splash).\n *\n * `color-scheme` is the platform's own answer to exactly that question, and it is why this is\n * a meta tag and not a background rule: it hands the browser the product's `dark`/`light`\n * declaration UNCHANGED and lets it paint its own dark canvas, so nothing here becomes a\n * second author of a palette value. The workbench sets `color-scheme` itself once its theme\n * loads; this only fills the window before that.\n */\n const colorSchemeMeta = (html: string): string =>\n colorTheme === null\n ? html\n : html.replace('<head>', `<head>\\n\\t\\t<meta name=\"color-scheme\" content=\"${colorTheme}\">`);\n\n /** Rewrite the workbench page's own configuration for THIS origin.\n *\n * - `remoteAuthority` needs NOTHING here, and that is worth stating: the REH\n * derives it from the request's HOST header (measured \u2014 a direct fetch to\n * the REH reports its own authority, the same fetch through this proxy\n * reports this one), so forwarding the original Host is already what makes\n * the management websocket, the `vscode-remote://` file system, every\n * `vscode-remote-resource` url AND the page's own CSP address this origin.\n * - `webviewEndpoint` is set (see the header).\n * - `initialColorTheme` is set (see below). */\n const patchWorkbenchConfig = (html: string): string => {\n const webBaseUrl =\n html.match(/id=\"vscode-workbench-web-base-url\" data-settings=\"([^\"]*)\"/)?.[1] ?? '/static';\n return html.replace(\n /(id=\"vscode-workbench-web-configuration\" data-settings=\")([^\"]*)(\")/,\n (_all, head: string, value: string, tail: string) => {\n const config = JSON.parse(decodeAttr(value)) as Record<string, unknown>;\n config['webviewEndpoint'] =\n `${webviewOriginTemplate}${decodeAttr(webBaseUrl)}/out/vs/workbench/contrib/webview/browser/pre/`;\n // THE FIRST PAINT IS THE PRODUCT'S, NOT THE WEB DEFAULT'S. Measured\n // 2026-09-21 against both releases: with nothing here the page's first\n // frame is LIGHT \u2014 `workbenchThemeService` falls back to\n // `isWeb ? ColorScheme.LIGHT : ColorScheme.DARK` when it has no stored\n // theme \u2014 so the product's opening cover, every colour of which is a\n // `--vscode-*` variable by design, was WHITE from the first frame until\n // the real theme loaded seconds later, and then flipped.\n //\n // `IWorkbenchConstructionOptions.initialColorTheme` is the supported\n // door and the REH leaves it unset, the same shape as `webviewEndpoint`\n // above. It has to be HERE and not in a setting or an extension: the\n // theme service reads it in its constructor, before any extension is\n // registered, which is exactly the window the cover lives in. The\n // themeType strings ARE `ColorScheme`'s values (`dark`, `light`).\n //\n // The colour comes from the PRODUCT's own declaration and nothing else\n // (`package.json#vgai.product.colorTheme`), and it is a FALLBACK in the\n // workbench's own order: a person who has already picked a theme has it\n // in storage, and `ColorThemeData.fromStorageData` is preferred over\n // this on every open after the first.\n if (colorTheme !== null) config['initialColorTheme'] = { themeType: colorTheme };\n return head + encodeAttr(JSON.stringify(config)) + tail;\n },\n );\n };\n\n const sendText = (res: ServerResponse, status: number, body: string, type: string): void => {\n res.writeHead(status, { ...isolation, 'content-type': type, 'cache-control': 'no-store' });\n res.end(body);\n };\n\n /** An unknown `?project=` says so by name rather than opening an empty workbench. */\n const refuseProject = (res: ServerResponse, id: string): void => {\n sendText(\n res,\n 404,\n `<!doctype html><meta charset=\"utf-8\"><title>no such project</title>` +\n `<body style=\"font:14px system-ui;padding:2rem\"><h1>No project \"${encodeAttr(id)}\" is open here.</h1>` +\n `<p>This workbench is the session for <code>${encodeAttr(projectRoot)}</code>, whose id is ` +\n `<code>${encodeAttr(projectId)}</code>.</p>`,\n 'text/html; charset=utf-8',\n );\n };\n\n const handleRequest = (req: IncomingMessage, res: ServerResponse): void => {\n const url = new URL(req.url ?? '/', selfOrigin);\n const pathname = url.pathname;\n // THE ONE HOSTED BOOT PARAM, ONTO THE WORKBENCH'S OWN. `?project=<id>` is\n // what a link to the vgai editor carries; the workbench's own boot contract\n // is `?folder=<path>` (`WorkspaceProvider.create`, which reads a bare path\n // against `remoteAuthority`). This is a REDIRECT and not an injected\n // `folderUri`, and the reason was measured (2026-09-19): under this shape\n // the workbench page is ALSO the vgai editor's page, and the editor's boot\n // refuses `?project=` on a session-backed surface by name \u2014 \"project\n // identity does NOT live in the URL on a local editor\"\n // (`assertNoRemovedBootParams`, packages/editor/src/boot-routing.ts). The\n // redirect hands the page a url both halves accept.\n if (pathname === '/') {\n const id = url.searchParams.get('project');\n if (id && !url.searchParams.has('folder') && !url.searchParams.has('workspace')) {\n if (id !== projectId) {\n refuseProject(res, id);\n return;\n }\n const next = new URL(url);\n next.searchParams.delete('project');\n next.searchParams.set('folder', projectRoot);\n res.writeHead(302, {\n ...isolation,\n location: `${next.pathname}${next.search}`,\n 'cache-control': 'no-store',\n });\n res.end();\n return;\n }\n }\n\n const target = vscodePath.test(pathname) ? upstream : session;\n const encoding = negotiateEncoding(req.headers['accept-encoding']);\n const forward = httpRequest(\n { ...target, method: req.method, path: req.url, headers: req.headers },\n (answer) => {\n const headers: Record<string, string | string[]> = {};\n for (const [key, value] of Object.entries(answer.headers)) {\n if (value !== undefined) headers[key] = value;\n }\n delete headers['cross-origin-opener-policy'];\n delete headers['cross-origin-embedder-policy'];\n delete headers['cross-origin-resource-policy'];\n // The webview's second origin, onto the REH's own `frame-src`.\n const csp = headers['content-security-policy'];\n if (typeof csp === 'string' && csp.includes('frame-src')) {\n // GLTFLoader fetches local blob textures; img-src alone cannot\n // authorize that fetch. Media previews use local blobs as well.\n headers['content-security-policy'] = csp\n .replace(/frame-src([^;]*)/, `frame-src$1 ${webviewOriginPattern}`)\n .replace(/connect-src([^;]*)/, 'connect-src$1 blob:')\n .replace(/media-src([^;]*)/, 'media-src$1 blob:');\n }\n const type = String(headers['content-type'] ?? '');\n const alreadyEncoded = headers['content-encoding'] !== undefined;\n // The workbench HTML and its config spell the upstream's OWN origin\n // into absolute script and endpoint URLs; through this origin those are\n // cross-origin and CORS refuses them. Text bodies are re-spelled here,\n // which needs the whole body \u2014 so this branch buffers and the streaming\n // one below does not.\n if (/text\\/html|javascript|json/.test(type) && !alreadyEncoded) {\n const chunks: Buffer[] = [];\n answer.on('data', (chunk: Buffer) => chunks.push(chunk));\n answer.on('end', () => {\n let body = Buffer.concat(chunks)\n .toString('utf8')\n .replaceAll(upstreamOrigin, selfOrigin);\n if (pathname === '/' && target === upstream && /text\\/html/.test(type)) {\n body = colorSchemeMeta(patchWorkbenchConfig(body));\n }\n delete headers['content-length'];\n const raw = Buffer.from(body, 'utf8');\n if (encoding === null) {\n res.writeHead(answer.statusCode ?? 502, {\n ...headers,\n ...isolation,\n 'content-length': raw.byteLength,\n });\n res.end(raw);\n return;\n }\n res.writeHead(answer.statusCode ?? 502, {\n ...headers,\n ...isolation,\n 'content-encoding': encoding,\n vary: 'accept-encoding',\n });\n const stream = compressor(encoding);\n stream.pipe(res);\n stream.end(raw);\n });\n return;\n }\n if (encoding !== null && !alreadyEncoded && COMPRESSIBLE.test(type)) {\n delete headers['content-length'];\n res.writeHead(answer.statusCode ?? 502, {\n ...headers,\n ...isolation,\n 'content-encoding': encoding,\n vary: 'accept-encoding',\n });\n void pipeline(answer, compressor(encoding), res).catch(() => res.destroy());\n return;\n }\n res.writeHead(answer.statusCode ?? 502, { ...headers, ...isolation });\n answer.pipe(res);\n },\n );\n forward.on('error', (error) => {\n if (!res.headersSent) {\n sendText(\n res,\n 502,\n `upstream ${target.port}: ${error.message}`,\n 'text/plain; charset=utf-8',\n );\n } else res.destroy();\n });\n // A browser that goes away takes its upstream request with it \u2014 otherwise a\n // dead tab's event stream stays open, this session keeps that tab blessed,\n // and EVERY later tab lands on `/__editor/tab-yielded` (measured\n // 2026-09-19 in the session journal).\n res.on('close', () => forward.destroy());\n req.pipe(forward);\n };\n\n const handleUpgrade = (req: IncomingMessage, socket: Socket, head: Buffer): void => {\n const target = vscodePath.test(new URL(req.url ?? '/', selfOrigin).pathname)\n ? upstream\n : session;\n const proxied = netConnect(target.port, target.host, () => {\n proxied.write(\n `${req.method} ${req.url} HTTP/${req.httpVersion}\\r\\n` +\n Object.entries(req.headers)\n .map(([key, value]) => `${key}: ${Array.isArray(value) ? value.join(', ') : value}`)\n .join('\\r\\n') +\n '\\r\\n\\r\\n',\n );\n if (head.length) proxied.write(head);\n socket.pipe(proxied).pipe(socket);\n });\n proxied.on('error', () => socket.destroy());\n socket.on('error', () => proxied.destroy());\n };\n\n /**\n * Wait for the upstream to report its shape before accepting requests. The REH serves its static\n * assets under a PRODUCT SEGMENT (`/oss-dev/static` out of a source checkout,\n * `/<quality>/static` out of a build), and this cannot route without knowing\n * it. The page itself states it in `vscode-workbench-web-base-url`, so it is\n * read rather than spelled.\n */\n const discoverUpstream = (): Promise<string | null> =>\n new Promise((done) => {\n const probe = httpRequest({ ...upstream, path: '/' }, (answer) => {\n const chunks: Buffer[] = [];\n answer.on('data', (chunk: Buffer) => chunks.push(chunk));\n answer.on('end', () => {\n const html = Buffer.concat(chunks).toString('utf8');\n const base = html.match(\n /id=\"vscode-workbench-web-base-url\" data-settings=\"([^\"]*)\"/,\n )?.[1];\n done(answer.statusCode === 200 && base ? decodeAttr(base).replace(/\\/static\\/?$/, '') : null);\n });\n });\n probe.on('error', () => done(null));\n probe.setTimeout(3_000, () => {\n probe.destroy();\n done(null);\n });\n probe.end();\n });\n\n // A listening socket is not proof that Code-OSS can serve its HTML yet.\n // Never expose a half-configured proxy: unknown prefixes route native JS to\n // the session server and produce a blank workbench with script 404s.\n const discoveryDeadline = Date.now() + 30_000;\n let segment: string | null = null;\n do {\n segment = await discoverUpstream();\n if (segment !== null) break;\n await new Promise(resolve => setTimeout(resolve, 100));\n } while (Date.now() < discoveryDeadline);\n if (segment === null) throw new Error(\n `Code-OSS on port ${upstream.port} did not report its workbench asset base within 30s; refusing to start an incorrectly routed proxy.`,\n );\n if (segment) {\n vscodePath = new RegExp(\n `${VSCODE_PATH_BASE}|^${segment.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&')}(\\\\/|$)`,\n );\n log(`frame: workbench product segment ${segment}`);\n }\n\n // ONE handler, TWO loopback listeners \u2014 127.0.0.1 and ::1. The webview's\n // origin is a `*.localhost` subdomain (see the header), and macOS answers\n // `<anything>.localhost` with the IPv6 `::1` as well as 127.0.0.1\n // (`dscacheutil -q host -a name x.localhost`); a browser that picks the IPv6\n // address finds nothing listening and the webview iframe fails as\n // `chrome-error://chromewebdata` with no message at all \u2014 measured\n // 2026-09-19 against a proxy bound to 127.0.0.1 alone.\n const servers: Server[] = [];\n await new Promise<void>((ready, failed) => {\n let settled = false;\n for (const host of ['127.0.0.1', '::1']) {\n const one = createServer(handleRequest);\n one.on('upgrade', handleUpgrade);\n one.on('error', (error) => {\n if (host === '::1') {\n log(\n `frame: no IPv6 loopback listener (${error.message}); webviews need one if the browser resolves *.localhost to ::1`,\n );\n return;\n }\n if (!settled) {\n settled = true;\n failed(error);\n }\n });\n servers.push(one);\n one.listen(port, host, () => {\n if (!settled) {\n settled = true;\n ready();\n }\n });\n }\n });\n\n return {\n url: `${selfOrigin}/?project=${encodeURIComponent(projectId)}`,\n close: async () => {\n await Promise.all(\n servers.map(\n (one) =>\n new Promise<void>((done) => {\n one.close(() => done());\n one.closeAllConnections?.();\n }),\n ),\n );\n },\n };\n}\n"],
5
- "mappings": "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",
3
+ "sources": ["../../server/frame-proxy.ts", "../../server/js-profiling-policy.ts"],
4
+ "sourcesContent": ["/**\n * THE ONE ORIGIN in front of two servers: the Code-OSS workbench and this\n * session. It listens IN THE SESSION PROCESS \u2014 no spawn, no second program \u2014\n * exactly as the loopback Colyseus server does.\n *\n * /$product-segment/*, /static, /vscode-remote, \u2026 the workbench (the REH)\n * everything else this session\n *\n * WHY A PROXY AND NOT TWO ORIGINS (measured 2026-09-19 against the REH). The\n * REH stamps its own CSP on the workbench page\n * (`createWorkbenchContentSecurityPolicy`, src/vs/server/node/webClientServer.ts):\n *\n * script-src 'self' 'unsafe-eval' blob: 'nonce-\u2026' <sha> http://<the REH's own authority>\n * connect-src 'self' ws: wss: https:\n *\n * The ONLY http origin on either list is the server's own. The editor's own\n * entry module and every project module are imported as module scripts from\n * this session, and every `/__editor/*` call is a fetch to it \u2014 so a session on\n * a second port is refused by `script-src` before CORS is ever consulted, and\n * its fetches by `connect-src` (which carries no `http:` at all). Relaxing that\n * means editing a core service; the proxy costs none.\n *\n * CROSS-ORIGIN ISOLATION. Every response carries COOP `same-origin` and COEP\n * `credentialless`, because the Blender build is `-sPROXY_TO_PTHREAD` and needs\n * `SharedArrayBuffer`. The REH has no cross-origin-isolation flag or header\n * hook of its own \u2014 `--enable-coi` is an ELECTRON-MAIN argument \u2014 so these\n * headers are this proxy's. `credentialless` rather than `require-corp` so the\n * workbench's own cross-origin loads keep working, the same choice `dev.ts`\n * makes.\n *\n * THE WEBVIEW'S SECOND ORIGIN. VS Code loads every webview from\n * `webviewExternalEndpoint`, which defaults to `https://{{uuid}}.vscode-cdn.net/\u2026`\n * \u2014 a host that does not exist for this fork and that a cross-origin-isolated\n * page could not embed anyway. `IWorkbenchConstructionOptions.webviewEndpoint`\n * is the supported door and the REH leaves it unset, so this rewrites the page\n * config to `http://{{uuid}}.localhost:<port>`. It has to be a SUBDOMAIN and\n * not this origin itself: `pre/index.html` refuses to start unless\n * `location.hostname` IS the hash of `{parentOrigin, salt}` or begins with it,\n * and `*.localhost` is the one wildcard host a browser resolves to loopback\n * with no DNS. Two consequences handled here: the webview is genuinely\n * cross-origin, so its responses need CORP `cross-origin` (without it the frame\n * dies as `corp-not-same-origin-after-defaulted-to-same-origin-by-coep`, which\n * Chrome renders as \"refused to connect\" and which cost an hour); and the REH's\n * own `frame-src 'self' https://*.vscode-cdn.net` is widened on the way through.\n *\n * COMPRESSION IS THIS PROXY'S JOB, and it is the reason the frame's page weight\n * is not the workbench's disk size. U3 measured `transferSize ===\n * decodedBodySize` on every entry of a 41.75 MB page: the REH writes no\n * `content-encoding` at all, and neither did this proxy. It negotiates\n * `accept-encoding` now \u2014 brotli, then gzip \u2014 over text, JavaScript, CSS, JSON\n * and wasm only; everything else is byte-for-byte.\n */\n\nimport {\n createServer,\n request as httpRequest,\n type IncomingMessage,\n type Server,\n type ServerResponse,\n} from 'node:http';\nimport { connect as netConnect, type Socket } from 'node:net';\nimport { basename } from 'node:path';\nimport type { Transform } from 'node:stream';\nimport { pipeline } from 'node:stream/promises';\nimport { createBrotliCompress, createGzip, constants as zlibConstants } from 'node:zlib';\nimport { JS_PROFILING_POLICY_HEADER, JS_PROFILING_POLICY_VALUE } from './js-profiling-policy';\n\nexport interface FrameProxyOptions {\n /** The one port a person opens: this proxy's. */\n readonly port: number;\n /** The Code-OSS workbench (REH) behind it. */\n readonly upstreamPort: number;\n /** This session's own port. */\n readonly sessionPort: number;\n /** The project the workbench opens; its folder name is the `?project=` id. */\n readonly projectRoot: string;\n /**\n * Which way round the PRODUCT paints, from its own declaration\n * (`package.json#vgai.product.colorTheme`, `@volter/editor-sdk/session/\n * product-locator`), or `null` when this session could not resolve a product\n * \u2014 in which case the page keeps the workbench's own web default and the\n * `served-modules` door is what says why there is no product.\n */\n readonly colorTheme: 'dark' | 'light' | null;\n readonly log: (line: string) => void;\n}\n\nexport interface FrameProxy {\n /** The url to open: this origin, on this project. */\n readonly url: string;\n close(): Promise<void>;\n}\n\n/**\n * The workbench's own paths. `<product segment>` (`/oss-dev` out of a source\n * checkout, the quality segment out of a build \u2014 `getServerProductSegment`) is\n * DISCOVERED at boot rather than spelled here, because the page states it.\n */\nconst VSCODE_PATH_BASE =\n '^\\\\/$|^\\\\/(static|favicon|manifest\\\\.json|code-192|code-512|callback|oauth|out|extensions|builtin|remote|vscode-remote|_static|web-extension|version|delay-shutdown)';\n\n/** What is worth compressing. Everything else goes through byte for byte. */\nconst COMPRESSIBLE =\n /^(?:text\\/|application\\/(?:javascript|json|wasm|manifest\\+json)|image\\/svg\\+xml)/;\n\n/** `&quot;`-encoded HTML attribute value \u2194 JSON, the way webClientServer writes it. */\nconst decodeAttr = (value: string): string =>\n value\n .replaceAll('&quot;', '\"')\n .replaceAll('&#39;', \"'\")\n .replaceAll('&lt;', '<')\n .replaceAll('&gt;', '>')\n .replaceAll('&amp;', '&');\nconst encodeAttr = (value: string): string =>\n value\n .replaceAll('&', '&amp;')\n .replaceAll('\"', '&quot;')\n .replaceAll(\"'\", '&#39;')\n .replaceAll('<', '&lt;')\n .replaceAll('>', '&gt;');\n\n/**\n * Which encoding the browser asked for, in this proxy's order of preference.\n * `null` means \"send it as it is\" \u2014 no `accept-encoding`, or none we write.\n */\nfunction negotiateEncoding(header: string | string[] | undefined): 'br' | 'gzip' | null {\n const accepted = (Array.isArray(header) ? header.join(',') : (header ?? '')).toLowerCase();\n if (accepted.includes('br')) return 'br';\n if (accepted.includes('gzip')) return 'gzip';\n return null;\n}\n\n/**\n * Quality 5 rather than brotli's default 11. The default spends seconds on a\n * multi-megabyte workbench chunk and this proxy compresses on the request path,\n * with the person waiting; 5 is the knee where the ratio is nearly the same and\n * the CPU is not.\n */\nfunction compressor(encoding: 'br' | 'gzip'): Transform {\n return encoding === 'br'\n ? createBrotliCompress({ params: { [zlibConstants.BROTLI_PARAM_QUALITY]: 5 } })\n : createGzip({ level: 6 });\n}\n\nexport async function startFrameProxy(options: FrameProxyOptions): Promise<FrameProxy> {\n const { port, upstreamPort, sessionPort, projectRoot, colorTheme, log } = options;\n const upstream = { host: '127.0.0.1', port: upstreamPort };\n const session = { host: '127.0.0.1', port: sessionPort };\n const upstreamOrigin = `http://${upstream.host}:${upstream.port}`;\n const selfOrigin = `http://127.0.0.1:${port}`;\n const webviewOriginTemplate = `http://{{uuid}}.localhost:${port}`;\n const webviewOriginPattern = `http://*.localhost:${port}`;\n const projectId = basename(projectRoot);\n\n const isolation: Record<string, string> = {\n // The Code-OSS HTML comes from the upstream, not Express. Keep the\n // editor's profiling permission on the actual top-level document too.\n [JS_PROFILING_POLICY_HEADER]: JS_PROFILING_POLICY_VALUE,\n 'Cross-Origin-Opener-Policy': 'same-origin',\n 'Cross-Origin-Embedder-Policy': 'credentialless',\n 'Cross-Origin-Resource-Policy': 'cross-origin',\n };\n\n let vscodePath = new RegExp(VSCODE_PATH_BASE);\n\n /**\n * THE DOCUMENT'S OWN COLOUR, before it has any styles at all.\n *\n * `initialColorTheme` below fixes the WORKBENCH's first paint, and that is not the page's\n * first paint: measured 2026-09-21 on the live release, `html` and `body` compute\n * `rgba(0, 0, 0, 0)` \u2014 upstream's `workbench.css` styles `.monaco-workbench` and nothing\n * above it \u2014 so from the document's first frame until `Workbench.renderWorkbench` builds that\n * element, the canvas is the BROWSER's default, which is white. One frame of it survived F1's\n * first reading (frame 0 at 0.0 s white, frame 1 at 0.5 s already the product's splash).\n *\n * `color-scheme` is the platform's own answer to exactly that question, and it is why this is\n * a meta tag and not a background rule: it hands the browser the product's `dark`/`light`\n * declaration UNCHANGED and lets it paint its own dark canvas, so nothing here becomes a\n * second author of a palette value. The workbench sets `color-scheme` itself once its theme\n * loads; this only fills the window before that.\n */\n const colorSchemeMeta = (html: string): string =>\n colorTheme === null\n ? html\n : html.replace('<head>', `<head>\\n\\t\\t<meta name=\"color-scheme\" content=\"${colorTheme}\">`);\n\n /** Rewrite the workbench page's own configuration for THIS origin.\n *\n * - `remoteAuthority` needs NOTHING here, and that is worth stating: the REH\n * derives it from the request's HOST header (measured \u2014 a direct fetch to\n * the REH reports its own authority, the same fetch through this proxy\n * reports this one), so forwarding the original Host is already what makes\n * the management websocket, the `vscode-remote://` file system, every\n * `vscode-remote-resource` url AND the page's own CSP address this origin.\n * - `webviewEndpoint` is set (see the header).\n * - `initialColorTheme` is set (see below). */\n const patchWorkbenchConfig = (html: string): string => {\n const webBaseUrl =\n html.match(/id=\"vscode-workbench-web-base-url\" data-settings=\"([^\"]*)\"/)?.[1] ?? '/static';\n return html.replace(\n /(id=\"vscode-workbench-web-configuration\" data-settings=\")([^\"]*)(\")/,\n (_all, head: string, value: string, tail: string) => {\n const config = JSON.parse(decodeAttr(value)) as Record<string, unknown>;\n config['webviewEndpoint'] =\n `${webviewOriginTemplate}${decodeAttr(webBaseUrl)}/out/vs/workbench/contrib/webview/browser/pre/`;\n // THE FIRST PAINT IS THE PRODUCT'S, NOT THE WEB DEFAULT'S. Measured\n // 2026-09-21 against both releases: with nothing here the page's first\n // frame is LIGHT \u2014 `workbenchThemeService` falls back to\n // `isWeb ? ColorScheme.LIGHT : ColorScheme.DARK` when it has no stored\n // theme \u2014 so the product's opening cover, every colour of which is a\n // `--vscode-*` variable by design, was WHITE from the first frame until\n // the real theme loaded seconds later, and then flipped.\n //\n // `IWorkbenchConstructionOptions.initialColorTheme` is the supported\n // door and the REH leaves it unset, the same shape as `webviewEndpoint`\n // above. It has to be HERE and not in a setting or an extension: the\n // theme service reads it in its constructor, before any extension is\n // registered, which is exactly the window the cover lives in. The\n // themeType strings ARE `ColorScheme`'s values (`dark`, `light`).\n //\n // The colour comes from the PRODUCT's own declaration and nothing else\n // (`package.json#vgai.product.colorTheme`), and it is a FALLBACK in the\n // workbench's own order: a person who has already picked a theme has it\n // in storage, and `ColorThemeData.fromStorageData` is preferred over\n // this on every open after the first.\n if (colorTheme !== null) config['initialColorTheme'] = { themeType: colorTheme };\n return head + encodeAttr(JSON.stringify(config)) + tail;\n },\n );\n };\n\n const sendText = (res: ServerResponse, status: number, body: string, type: string): void => {\n res.writeHead(status, { ...isolation, 'content-type': type, 'cache-control': 'no-store' });\n res.end(body);\n };\n\n /** An unknown `?project=` says so by name rather than opening an empty workbench. */\n const refuseProject = (res: ServerResponse, id: string): void => {\n sendText(\n res,\n 404,\n `<!doctype html><meta charset=\"utf-8\"><title>no such project</title>` +\n `<body style=\"font:14px system-ui;padding:2rem\"><h1>No project \"${encodeAttr(id)}\" is open here.</h1>` +\n `<p>This workbench is the session for <code>${encodeAttr(projectRoot)}</code>, whose id is ` +\n `<code>${encodeAttr(projectId)}</code>.</p>`,\n 'text/html; charset=utf-8',\n );\n };\n\n const handleRequest = (req: IncomingMessage, res: ServerResponse): void => {\n const url = new URL(req.url ?? '/', selfOrigin);\n const pathname = url.pathname;\n // THE ONE HOSTED BOOT PARAM, ONTO THE WORKBENCH'S OWN. `?project=<id>` is\n // what a link to the vgai editor carries; the workbench's own boot contract\n // is `?folder=<path>` (`WorkspaceProvider.create`, which reads a bare path\n // against `remoteAuthority`). This is a REDIRECT and not an injected\n // `folderUri`, and the reason was measured (2026-09-19): under this shape\n // the workbench page is ALSO the vgai editor's page, and the editor's boot\n // refuses `?project=` on a session-backed surface by name \u2014 \"project\n // identity does NOT live in the URL on a local editor\"\n // (`assertNoRemovedBootParams`, packages/editor/src/boot-routing.ts). The\n // redirect hands the page a url both halves accept.\n if (pathname === '/') {\n const id = url.searchParams.get('project');\n if (id && !url.searchParams.has('folder') && !url.searchParams.has('workspace')) {\n if (id !== projectId) {\n refuseProject(res, id);\n return;\n }\n const next = new URL(url);\n next.searchParams.delete('project');\n next.searchParams.set('folder', projectRoot);\n res.writeHead(302, {\n ...isolation,\n location: `${next.pathname}${next.search}`,\n 'cache-control': 'no-store',\n });\n res.end();\n return;\n }\n }\n\n const target = vscodePath.test(pathname) ? upstream : session;\n const encoding = negotiateEncoding(req.headers['accept-encoding']);\n const forward = httpRequest(\n { ...target, method: req.method, path: req.url, headers: req.headers },\n (answer) => {\n const headers: Record<string, string | string[]> = {};\n for (const [key, value] of Object.entries(answer.headers)) {\n if (value !== undefined) headers[key] = value;\n }\n delete headers['cross-origin-opener-policy'];\n delete headers['cross-origin-embedder-policy'];\n delete headers['cross-origin-resource-policy'];\n // The webview's second origin, onto the REH's own `frame-src`.\n const csp = headers['content-security-policy'];\n if (typeof csp === 'string' && csp.includes('frame-src')) {\n // GLTFLoader fetches local blob textures; img-src alone cannot\n // authorize that fetch. Media previews use local blobs as well.\n headers['content-security-policy'] = csp\n .replace(/frame-src([^;]*)/, `frame-src$1 ${webviewOriginPattern}`)\n .replace(/connect-src([^;]*)/, 'connect-src$1 blob:')\n .replace(/media-src([^;]*)/, 'media-src$1 blob:');\n }\n const type = String(headers['content-type'] ?? '');\n const alreadyEncoded = headers['content-encoding'] !== undefined;\n // The workbench HTML and its config spell the upstream's OWN origin\n // into absolute script and endpoint URLs; through this origin those are\n // cross-origin and CORS refuses them. Text bodies are re-spelled here,\n // which needs the whole body \u2014 so this branch buffers and the streaming\n // one below does not.\n if (/text\\/html|javascript|json/.test(type) && !alreadyEncoded) {\n const chunks: Buffer[] = [];\n answer.on('data', (chunk: Buffer) => chunks.push(chunk));\n answer.on('end', () => {\n let body = Buffer.concat(chunks)\n .toString('utf8')\n .replaceAll(upstreamOrigin, selfOrigin);\n if (pathname === '/' && target === upstream && /text\\/html/.test(type)) {\n body = colorSchemeMeta(patchWorkbenchConfig(body));\n }\n delete headers['content-length'];\n const raw = Buffer.from(body, 'utf8');\n if (encoding === null) {\n res.writeHead(answer.statusCode ?? 502, {\n ...headers,\n ...isolation,\n 'content-length': raw.byteLength,\n });\n res.end(raw);\n return;\n }\n res.writeHead(answer.statusCode ?? 502, {\n ...headers,\n ...isolation,\n 'content-encoding': encoding,\n vary: 'accept-encoding',\n });\n const stream = compressor(encoding);\n stream.pipe(res);\n stream.end(raw);\n });\n return;\n }\n if (encoding !== null && !alreadyEncoded && COMPRESSIBLE.test(type)) {\n delete headers['content-length'];\n res.writeHead(answer.statusCode ?? 502, {\n ...headers,\n ...isolation,\n 'content-encoding': encoding,\n vary: 'accept-encoding',\n });\n void pipeline(answer, compressor(encoding), res).catch(() => res.destroy());\n return;\n }\n res.writeHead(answer.statusCode ?? 502, { ...headers, ...isolation });\n answer.pipe(res);\n },\n );\n forward.on('error', (error) => {\n if (!res.headersSent) {\n sendText(\n res,\n 502,\n `upstream ${target.port}: ${error.message}`,\n 'text/plain; charset=utf-8',\n );\n } else res.destroy();\n });\n // A browser that goes away takes its upstream request with it \u2014 otherwise a\n // dead tab's event stream stays open, this session keeps that tab blessed,\n // and EVERY later tab lands on `/__editor/tab-yielded` (measured\n // 2026-09-19 in the session journal).\n res.on('close', () => forward.destroy());\n req.pipe(forward);\n };\n\n const handleUpgrade = (req: IncomingMessage, socket: Socket, head: Buffer): void => {\n const target = vscodePath.test(new URL(req.url ?? '/', selfOrigin).pathname)\n ? upstream\n : session;\n const proxied = netConnect(target.port, target.host, () => {\n proxied.write(\n `${req.method} ${req.url} HTTP/${req.httpVersion}\\r\\n` +\n Object.entries(req.headers)\n .map(([key, value]) => `${key}: ${Array.isArray(value) ? value.join(', ') : value}`)\n .join('\\r\\n') +\n '\\r\\n\\r\\n',\n );\n if (head.length) proxied.write(head);\n socket.pipe(proxied).pipe(socket);\n });\n proxied.on('error', () => socket.destroy());\n socket.on('error', () => proxied.destroy());\n };\n\n /**\n * Wait for the upstream to report its shape before accepting requests. The REH serves its static\n * assets under a PRODUCT SEGMENT (`/oss-dev/static` out of a source checkout,\n * `/<quality>/static` out of a build), and this cannot route without knowing\n * it. The page itself states it in `vscode-workbench-web-base-url`, so it is\n * read rather than spelled.\n */\n const discoverUpstream = (): Promise<string | null> =>\n new Promise((done) => {\n const probe = httpRequest({ ...upstream, path: '/' }, (answer) => {\n const chunks: Buffer[] = [];\n answer.on('data', (chunk: Buffer) => chunks.push(chunk));\n answer.on('end', () => {\n const html = Buffer.concat(chunks).toString('utf8');\n const base = html.match(\n /id=\"vscode-workbench-web-base-url\" data-settings=\"([^\"]*)\"/,\n )?.[1];\n done(answer.statusCode === 200 && base ? decodeAttr(base).replace(/\\/static\\/?$/, '') : null);\n });\n });\n probe.on('error', () => done(null));\n probe.setTimeout(3_000, () => {\n probe.destroy();\n done(null);\n });\n probe.end();\n });\n\n // A listening socket is not proof that Code-OSS can serve its HTML yet.\n // Never expose a half-configured proxy: unknown prefixes route native JS to\n // the session server and produce a blank workbench with script 404s.\n const discoveryDeadline = Date.now() + 30_000;\n let segment: string | null = null;\n do {\n segment = await discoverUpstream();\n if (segment !== null) break;\n await new Promise(resolve => setTimeout(resolve, 100));\n } while (Date.now() < discoveryDeadline);\n if (segment === null) throw new Error(\n `Code-OSS on port ${upstream.port} did not report its workbench asset base within 30s; refusing to start an incorrectly routed proxy.`,\n );\n if (segment) {\n vscodePath = new RegExp(\n `${VSCODE_PATH_BASE}|^${segment.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&')}(\\\\/|$)`,\n );\n log(`frame: workbench product segment ${segment}`);\n }\n\n // ONE handler, TWO loopback listeners \u2014 127.0.0.1 and ::1. The webview's\n // origin is a `*.localhost` subdomain (see the header), and macOS answers\n // `<anything>.localhost` with the IPv6 `::1` as well as 127.0.0.1\n // (`dscacheutil -q host -a name x.localhost`); a browser that picks the IPv6\n // address finds nothing listening and the webview iframe fails as\n // `chrome-error://chromewebdata` with no message at all \u2014 measured\n // 2026-09-19 against a proxy bound to 127.0.0.1 alone.\n const servers: Server[] = [];\n await new Promise<void>((ready, failed) => {\n let settled = false;\n for (const host of ['127.0.0.1', '::1']) {\n const one = createServer(handleRequest);\n one.on('upgrade', handleUpgrade);\n one.on('error', (error) => {\n if (host === '::1') {\n log(\n `frame: no IPv6 loopback listener (${error.message}); webviews need one if the browser resolves *.localhost to ::1`,\n );\n return;\n }\n if (!settled) {\n settled = true;\n failed(error);\n }\n });\n servers.push(one);\n one.listen(port, host, () => {\n if (!settled) {\n settled = true;\n ready();\n }\n });\n }\n });\n\n return {\n url: `${selfOrigin}/?project=${encodeURIComponent(projectId)}`,\n close: async () => {\n await Promise.all(\n servers.map(\n (one) =>\n new Promise<void>((done) => {\n one.close(() => done());\n one.closeAllConnections?.();\n }),\n ),\n );\n },\n };\n}\n", "/**\n * `Document-Policy: js-profiling` \u2014 the permission the editor's own diagnostics\n * assume it already has.\n *\n * The `visible-tab-fps-floor` invariant tells the reader, by name, to \"find the\n * cost with the Profiler's Overview (long-animation-frame attribution)\"\n * (`coverage/ontology-invariants.ts`). But `new Profiler(...)` in the editor\n * page throws `NotAllowedError: JS profiling is disabled by Document Policy`:\n * the JS Self-Profiling API is gated on a policy the DOCUMENT's own response\n * has to grant, and no editor server was sending it. The product printed an\n * instruction its own page forbade.\n *\n * A document policy is granted by the response that created the document, so\n * this is a response header or it is nothing \u2014 there is no client-side way to\n * turn it on. It is set on EVERY response rather than on a sniffed subset:\n * the header is meaningless on a subresource (browsers read it only when\n * creating a document), the editor serves its index through three different\n * paths (`express.static`, the SPA `sendFile` fallback, and Vite's own HTML\n * middleware in dev), and a heuristic that tried to name \"the document one\"\n * would silently miss whichever path a future change routed through. Blanket\n * is the version that cannot be quietly wrong.\n *\n * It also reaches the game's own frame this way, which is the point: a game\n * mounted in a child document is exactly what the reader is profiling.\n */\n\n/** The header name and value, together, so a test names them once. */\nexport const JS_PROFILING_POLICY_HEADER = 'Document-Policy';\nexport const JS_PROFILING_POLICY_VALUE = 'js-profiling';\n\ninterface HeaderSink {\n setHeader(name: string, value: string): unknown;\n}\n\n/**\n * Express middleware granting {@link JS_PROFILING_POLICY_VALUE} to every\n * document this server creates.\n *\n * Typed against the narrowest shape it uses (`setHeader`) so the unit test can\n * drive it without an HTTP server, and so both editor hosts can mount it\n * without agreeing on an express version.\n */\nexport function jsProfilingPolicy(): (\n request: unknown,\n response: HeaderSink,\n next: () => void,\n) => void {\n return (_request, response, next) => {\n response.setHeader(JS_PROFILING_POLICY_HEADER, JS_PROFILING_POLICY_VALUE);\n next();\n };\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -8390,6 +8390,7 @@ var alwaysRefresh = (timeoutMs = DEFAULT_RELAY_COMMAND_TIMEOUT_MS) => ({
8390
8390
  derivedRefresh: "always"
8391
8391
  });
8392
8392
  var RELAY_COMMANDS = {
8393
+ "session-prepare-close": noDerivedRefresh(12e4),
8393
8394
  // ---- Selection and framing ---------------------------------------------
8394
8395
  select: noDerivedRefresh(3e4),
8395
8396
  "select-multiple": noDerivedRefresh(),
@@ -86497,6 +86498,18 @@ import { connect as netConnect } from "node:net";
86497
86498
  import { basename as basename13 } from "node:path";
86498
86499
  import { pipeline as pipeline2 } from "node:stream/promises";
86499
86500
  import { createBrotliCompress, createGzip, constants as zlibConstants } from "node:zlib";
86501
+
86502
+ // server/js-profiling-policy.ts
86503
+ var JS_PROFILING_POLICY_HEADER = "Document-Policy";
86504
+ var JS_PROFILING_POLICY_VALUE = "js-profiling";
86505
+ function jsProfilingPolicy() {
86506
+ return (_request, response, next) => {
86507
+ response.setHeader(JS_PROFILING_POLICY_HEADER, JS_PROFILING_POLICY_VALUE);
86508
+ next();
86509
+ };
86510
+ }
86511
+
86512
+ // server/frame-proxy.ts
86500
86513
  var VSCODE_PATH_BASE = "^\\/$|^\\/(static|favicon|manifest\\.json|code-192|code-512|callback|oauth|out|extensions|builtin|remote|vscode-remote|_static|web-extension|version|delay-shutdown)";
86501
86514
  var COMPRESSIBLE = /^(?:text\/|application\/(?:javascript|json|wasm|manifest\+json)|image\/svg\+xml)/;
86502
86515
  var decodeAttr = (value) => value.replaceAll("&quot;", '"').replaceAll("&#39;", "'").replaceAll("&lt;", "<").replaceAll("&gt;", ">").replaceAll("&amp;", "&");
@@ -86520,6 +86533,9 @@ async function startFrameProxy(options) {
86520
86533
  const webviewOriginPattern = `http://*.localhost:${port}`;
86521
86534
  const projectId = basename13(projectRoot);
86522
86535
  const isolation = {
86536
+ // The Code-OSS HTML comes from the upstream, not Express. Keep the
86537
+ // editor's profiling permission on the actual top-level document too.
86538
+ [JS_PROFILING_POLICY_HEADER]: JS_PROFILING_POLICY_VALUE,
86523
86539
  "Cross-Origin-Opener-Policy": "same-origin",
86524
86540
  "Cross-Origin-Embedder-Policy": "credentialless",
86525
86541
  "Cross-Origin-Resource-Policy": "cross-origin"
@@ -87045,16 +87061,6 @@ function createIdleShutdown(options) {
87045
87061
  };
87046
87062
  }
87047
87063
 
87048
- // server/js-profiling-policy.ts
87049
- var JS_PROFILING_POLICY_HEADER = "Document-Policy";
87050
- var JS_PROFILING_POLICY_VALUE = "js-profiling";
87051
- function jsProfilingPolicy() {
87052
- return (_request, response, next) => {
87053
- response.setHeader(JS_PROFILING_POLICY_HEADER, JS_PROFILING_POLICY_VALUE);
87054
- next();
87055
- };
87056
- }
87057
-
87058
87064
  // server/managed-account-defaults.ts
87059
87065
  var DEFAULT_VGAI_ACCOUNT_URL = "https://auth.videogame.ai";
87060
87066
  function configureManagedAccountDefaults() {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@volter/editor-core",
3
3
  "author": "Volter AI, Inc.",
4
- "version": "0.5.58",
4
+ "version": "0.5.60",
5
5
  "publishConfig": {
6
6
  "access": "public"
7
7
  },
@@ -82,9 +82,9 @@
82
82
  "three.quarks": "0.16.0",
83
83
  "typescript": "5.9.3",
84
84
  "zod": "4.3.6",
85
- "@volter/editor-project": "0.5.58",
86
- "@volter/editor-sdk": "0.5.58",
87
- "@volter/editor-threejs": "0.5.58",
85
+ "@volter/editor-project": "0.5.60",
86
+ "@volter/editor-sdk": "0.5.60",
87
+ "@volter/editor-threejs": "0.5.60",
88
88
  "vite": "^6.4.3",
89
89
  "express": "^5.2.1",
90
90
  "chokidar": "^5.0.0",
@@ -63,6 +63,7 @@ import { basename } from 'node:path';
63
63
  import type { Transform } from 'node:stream';
64
64
  import { pipeline } from 'node:stream/promises';
65
65
  import { createBrotliCompress, createGzip, constants as zlibConstants } from 'node:zlib';
66
+ import { JS_PROFILING_POLICY_HEADER, JS_PROFILING_POLICY_VALUE } from './js-profiling-policy';
66
67
 
67
68
  export interface FrameProxyOptions {
68
69
  /** The one port a person opens: this proxy's. */
@@ -152,6 +153,9 @@ export async function startFrameProxy(options: FrameProxyOptions): Promise<Frame
152
153
  const projectId = basename(projectRoot);
153
154
 
154
155
  const isolation: Record<string, string> = {
156
+ // The Code-OSS HTML comes from the upstream, not Express. Keep the
157
+ // editor's profiling permission on the actual top-level document too.
158
+ [JS_PROFILING_POLICY_HEADER]: JS_PROFILING_POLICY_VALUE,
155
159
  'Cross-Origin-Opener-Policy': 'same-origin',
156
160
  'Cross-Origin-Embedder-Policy': 'credentialless',
157
161
  'Cross-Origin-Resource-Policy': 'cross-origin',
@@ -99,6 +99,7 @@ import { setPlayBootPhaseReporter } from './play-boot-phase';
99
99
  import { projectAdapterFacet, subscribeProjectAdapter } from './project-adapter';
100
100
  import { getProjectModuleSplitReports } from './project-module-split';
101
101
  import { onSessionEndedChange, sessionEndedRefusal, sessionEndedState } from './session-tombstone';
102
+ import { prepareSessionClose } from './session-close';
102
103
  import { focusedStageStore } from './stage-context';
103
104
  import { scheduleDeferredFullReport } from './state-report-deferral';
104
105
  import { interactiveViewportRendererCounts } from './three-viewport/interactive-renderer';
@@ -1260,6 +1261,9 @@ export async function handleCommand(
1260
1261
  }
1261
1262
  const commandType: RelayCommandType = cmd['type'];
1262
1263
  switch (commandType) {
1264
+ case 'session-prepare-close':
1265
+ await prepareSessionClose();
1266
+ return { ok: true };
1263
1267
  // Selection
1264
1268
  case 'select': {
1265
1269
  const id = (cmd['id'] as string | null) ?? null;
@@ -102,6 +102,7 @@ import { getCurrentProject } from './project-manager';
102
102
  import { onProjectReady } from './project-ready';
103
103
  import { projectMounts } from './project-shape';
104
104
  import { onSessionEndedChange } from './session-tombstone';
105
+ import { onBeforeSessionClose } from './session-close';
105
106
  import { setSettingsProvider, subscribeSettingsProvider } from './settings/settings-provider';
106
107
  import { getSetting, inspectSetting, setSetting, subscribeSettings } from './settings-store';
107
108
  import { onShellStoreChange, shellStoreForHost } from './shell-store-door';
@@ -254,6 +255,7 @@ export function installEditorHostDoor(): void {
254
255
  onSessionEndedChange((state) => {
255
256
  if (state !== null) fn();
256
257
  }),
258
+ onBeforeClose: onBeforeSessionClose,
257
259
  reportWorkerCallMeter: setBlenderCallMeter,
258
260
  },
259
261
  project: {
@@ -0,0 +1,17 @@
1
+ /** Save barriers run while the server and command relay are still live.
2
+ * Never put them in onEnded: that signal is forced resource teardown. */
3
+ const handlers = new Set<() => Promise<void>>();
4
+ let preparing: Promise<void> | null = null;
5
+
6
+ export function onBeforeSessionClose(handler: () => Promise<void>): () => void {
7
+ handlers.add(handler);
8
+ return () => { handlers.delete(handler); };
9
+ }
10
+
11
+ export function prepareSessionClose(): Promise<void> {
12
+ if (preparing) return preparing;
13
+ preparing = Promise.all([...handlers].map(handler => Promise.resolve().then(handler)))
14
+ .then(() => undefined)
15
+ .finally(() => { preparing = null; });
16
+ return preparing;
17
+ }