From 460846a448f7912af264bfde531d23b0961ca85b Mon Sep 17 00:00:00 2001
From: UniDesk Codex
Date: Tue, 30 Jun 2026 22:07:22 +0800
Subject: [PATCH] fix: serve opencode project bootstrap as csp-safe script
---
internal/dev-entrypoint/cloud-web-runtime.mjs | 53 ++++++++++++++++---
.../dev-entrypoint/cloud-web-runtime.test.mjs | 26 +++++++--
2 files changed, 67 insertions(+), 12 deletions(-)
diff --git a/internal/dev-entrypoint/cloud-web-runtime.mjs b/internal/dev-entrypoint/cloud-web-runtime.mjs
index 7ea264c8..52e30f80 100644
--- a/internal/dev-entrypoint/cloud-web-runtime.mjs
+++ b/internal/dev-entrypoint/cloud-web-runtime.mjs
@@ -21,6 +21,7 @@ const CLIENT_ROUTE_FALLBACK_PATHS = new Set(["/workbench", "/workspace", "/openc
const STATIC_ASSET_EXTENSION_PATTERN = /\.[A-Za-z0-9][A-Za-z0-9_-]*$/u;
const OPENCODE_PROXY_PREFIX = "/_opencode";
const OPENCODE_FRAME_URL_PATH = "/opencode/frame-url";
+const OPENCODE_DEFAULT_PROJECT_BOOTSTRAP_SCRIPT_PATH = "/__hwlab/opencode-default-project.js";
const OPENCODE_TICKET_QUERY = "hwlab_opencode_ticket";
const OPENCODE_TICKET_COOKIE = "hwlab_opencode_ticket";
const OPENCODE_TICKET_TTL_MS = 5 * 60 * 1000;
@@ -822,9 +823,13 @@ async function proxyOpencodeRequest({ request, response, url, cloudApiBaseUrl, c
headers: opencodeUpstreamHeaders({ headers: { ...request.headers, ...cloudWebTraceHeaders(traceContext, serviceId) } }, body, authorization)
};
try {
- const proxyResult = shouldInjectOpencodeDefaultProject({ request, target, opencodeDefaultProject })
- ? await proxyOpencodeHtmlRequest({ target, request: upstreamRequest, response, timeoutMs: opencodeProxyTimeoutMs, extraResponseHeaders, defaultProject: opencodeDefaultProject })
- : await proxyCloudApiRequest({
+ let proxyResult;
+ if (shouldServeOpencodeDefaultProjectBootstrapScript({ request, target, opencodeDefaultProject })) {
+ proxyResult = sendOpencodeDefaultProjectBootstrapScript({ response, extraResponseHeaders, defaultProject: opencodeDefaultProject });
+ } else if (shouldInjectOpencodeDefaultProject({ request, target, opencodeDefaultProject })) {
+ proxyResult = await proxyOpencodeHtmlRequest({ target, request: upstreamRequest, response, timeoutMs: opencodeProxyTimeoutMs, extraResponseHeaders, defaultProject: opencodeDefaultProject });
+ } else {
+ proxyResult = await proxyCloudApiRequest({
target,
request: upstreamRequest,
response,
@@ -833,6 +838,7 @@ async function proxyOpencodeRequest({ request, response, url, cloudApiBaseUrl, c
extraResponseHeaders,
streamTransform
});
+ }
emitOpencodeProxySpanAsync({
traceContext,
serviceId,
@@ -903,6 +909,26 @@ function shouldInjectOpencodeDefaultProject({ request, target, opencodeDefaultPr
return true;
}
+function shouldServeOpencodeDefaultProjectBootstrapScript({ request, target, opencodeDefaultProject }) {
+ return Boolean(opencodeDefaultProject) && request.method === "GET" && target.pathname === OPENCODE_DEFAULT_PROJECT_BOOTSTRAP_SCRIPT_PATH;
+}
+
+function sendOpencodeDefaultProjectBootstrapScript({ response, extraResponseHeaders, defaultProject }) {
+ const outputBody = Buffer.from(opencodeDefaultProjectBootstrapJavaScript(defaultProject), "utf8");
+ response.writeHead(200, mergeOpencodeProxyResponseHeaders({
+ "content-type": "text/javascript; charset=utf-8",
+ "cache-control": "no-store",
+ "content-length": outputBody.length
+ }, extraResponseHeaders));
+ response.end(outputBody);
+ return {
+ statusCode: 200,
+ streaming: false,
+ bodyBytes: outputBody.length,
+ opencodeDefaultProjectBootstrapScript: true
+ };
+}
+
async function proxyOpencodeHtmlRequest({ target, request, response, timeoutMs, extraResponseHeaders, defaultProject }) {
const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), timeoutMs);
@@ -919,7 +945,7 @@ async function proxyOpencodeHtmlRequest({ target, request, response, timeoutMs,
? Buffer.from(opencodeInjectDefaultProjectHtml(inputBody.toString("utf8"), defaultProject), "utf8")
: inputBody;
response.writeHead(upstream.status, {
- ...mergeOpencodeProxyResponseHeaders(copyProxyResponseHeaders(upstreamHeaders), extraResponseHeaders),
+ ...mergeOpencodeProxyResponseHeaders(opencodeDecodedFetchResponseHeaders(upstreamHeaders), extraResponseHeaders),
"content-length": outputBody.length
});
response.end(outputBody);
@@ -939,14 +965,27 @@ async function proxyOpencodeHtmlRequest({ target, request, response, timeoutMs,
function opencodeInjectDefaultProjectHtml(html, defaultProject) {
if (!html || html.includes("data-hwlab-opencode-default-project")) return html;
- const script = opencodeDefaultProjectBootstrapScript(defaultProject);
+ const script = opencodeDefaultProjectBootstrapScriptTag();
if (html.includes("")) return html.replace("", `${script}`);
if (html.includes("
")) return html.replace("", `${script}`);
return `${script}${html}`;
}
-function opencodeDefaultProjectBootstrapScript(defaultProject) {
- return ``;
+function opencodeDefaultProjectBootstrapScriptTag() {
+ return ``;
+}
+
+function opencodeDefaultProjectBootstrapJavaScript(defaultProject) {
+ return `(function(){try{var project=${safeInlineJson(defaultProject)};if(!project)return;var serverKey="opencode.global.dat:server";var server=JSON.parse(localStorage.getItem(serverKey)||"{}");var projects=server.projects&&typeof server.projects==="object"?server.projects:{};var local=Array.isArray(projects.local)?projects.local.slice():[];var found=false;local=local.map(function(item){if(!item||item.worktree!==project)return item;found=true;return Object.assign({},item,{expanded:true});});if(!found)local.unshift({worktree:project,expanded:true});projects.local=local;server.projects=projects;server.lastProject=Object.assign({},server.lastProject||{},{local:project});localStorage.setItem(serverKey,JSON.stringify(server));var layoutKey="opencode.global.dat:layout.page";var layout=JSON.parse(localStorage.getItem(layoutKey)||"{}");layout.workspaceExpanded=Object.assign({},layout.workspaceExpanded||{});layout.workspaceExpanded[project]=true;localStorage.setItem(layoutKey,JSON.stringify(layout));}catch(error){console.warn("HWLAB OpenCode default project bootstrap failed",error);}})();\n`;
+}
+
+function opencodeDecodedFetchResponseHeaders(headers = {}) {
+ const result = copyProxyResponseHeaders(headers);
+ for (const key of Object.keys(result)) {
+ const lowerKey = key.toLowerCase();
+ if (lowerKey === "content-encoding" || lowerKey === "content-md5" || lowerKey === "etag") delete result[key];
+ }
+ return result;
}
function mergeOpencodeProxyResponseHeaders(baseHeaders = {}, extraHeaders = {}) {
diff --git a/internal/dev-entrypoint/cloud-web-runtime.test.mjs b/internal/dev-entrypoint/cloud-web-runtime.test.mjs
index 5d8cc549..21c76143 100644
--- a/internal/dev-entrypoint/cloud-web-runtime.test.mjs
+++ b/internal/dev-entrypoint/cloud-web-runtime.test.mjs
@@ -5,6 +5,7 @@ import { connect } from "node:net";
import os from "node:os";
import path from "node:path";
import test from "node:test";
+import { gzipSync } from "node:zlib";
import { createCloudWebServer, isClientDisconnectError } from "./cloud-web-runtime.mjs";
@@ -658,6 +659,8 @@ test("cloud web OpenCode proxy bootstraps the default project for iframe HTML",
response.end(JSON.stringify({ authenticated: request.headers.cookie === "hwlab_session=session-a" }));
});
const opencodeRequests = [];
+ const opencodeHtml = "