Support panel hosts that serve the page from their own scheme

Codex serves panels from codex-sandbox:// pages, where origins are
opaque: the panel recognizes the app's own addresses by scheme and host,
the vessels feed resolves against the document address, and the dev
server answers Codex's sandbox origins with CORS.
This commit is contained in:
Sameh Khamis committed 2026-10-02 15:21:37 -07:00
1 parent 64c1b1951f
commit b11d54d8b8
5 files changed
+16 -10

No files matched your search

+1
View File
@@ -6,6 +6,7 @@ import { embedFramingPlugin } from './embed-framing.js';
export const PANEL_CORS_ORIGINS = Object.freeze([ export const PANEL_CORS_ORIGINS = Object.freeze([
/^https?:\/\/(?:(?:[^:]+\.)?localhost|127\.0\.0\.1|\[::1\])(?::\d+)?$/, /^https?:\/\/(?:(?:[^:]+\.)?localhost|127\.0\.0\.1|\[::1\])(?::\d+)?$/,
/^https:\/\/[a-z0-9]+\.claudemcpcontent\.com$/, /^https:\/\/[a-z0-9]+\.claudemcpcontent\.com$/,
/^(?:https|codex-sandbox):\/\/[a-z0-9-]+\.web-sandbox\.oaiusercontent\.com$/,
]); ]);
/** /**
+3 -3
View File
@@ -5,9 +5,9 @@ import { createAisStreamSource } from '../sources/live/standalone.js';
const aisLiveVesselsLayer = createApplicationVessels({ const aisLiveVesselsLayer = createApplicationVessels({
source: createAisStreamSource({ source: createAisStreamSource({
apiUrl: import.meta.env?.VITE_AIS_LIVE_API_URL || '/api/ais-live', apiUrl: import.meta.env?.VITE_AIS_LIVE_API_URL || '/api/ais-live',
// The app's server, also when a panel page loads the app under <base>. // Resolved against the document's address, which a panel host may serve
origin: () => // from its own scheme.
new URL(globalThis.document?.baseURI ?? 'http://localhost').origin, origin: () => globalThis.document?.baseURI ?? 'http://localhost',
}), }),
options: { options: {
maxRows: import.meta.env?.VITE_AIS_LIVE_MAX_ROWS, maxRows: import.meta.env?.VITE_AIS_LIVE_MAX_ROWS,
+3 -3
View File
@@ -29,9 +29,9 @@ export function createStandaloneLayerSources() {
military: createAdsbLolSource(), military: createAdsbLolSource(),
vessels: createAisStreamSource({ vessels: createAisStreamSource({
apiUrl: import.meta.env?.VITE_AIS_LIVE_API_URL || '/api/ais-live', apiUrl: import.meta.env?.VITE_AIS_LIVE_API_URL || '/api/ais-live',
// The app's server, also when a panel page loads the app under <base>. // Resolved against the document's address, which a panel host may
origin: () => // serve from its own scheme.
new URL(globalThis.document?.baseURI ?? 'http://localhost').origin, origin: () => globalThis.document?.baseURI ?? 'http://localhost',
}), }),
cctv: createCctvSource(), cctv: createCctvSource(),
radio: createRadioSource(), radio: createRadioSource(),
+5
View File
@@ -72,6 +72,11 @@ test('panel origins may read every dev server path, other sites may not', () =>
request('https://abc123.claudemcpcontent.com', 'OPTIONS').ended, request('https://abc123.claudemcpcontent.com', 'OPTIONS').ended,
true, true,
); );
assert.equal(
request('codex-sandbox://mcp-app-ab12.web-sandbox.oaiusercontent.com')
.headers['Access-Control-Allow-Origin'],
'codex-sandbox://mcp-app-ab12.web-sandbox.oaiusercontent.com',
);
const other = request('https://example.com'); const other = request('https://example.com');
assert.deepEqual(other.headers, {}); assert.deepEqual(other.headers, {});
assert.equal(other.passed, true); assert.equal(other.passed, true);
+4 -4
View File
@@ -99,8 +99,9 @@ function panelHtml(appOrigin) {
// The panel page lives on the host's site, which serves none of the app. // The panel page lives on the host's site, which serves none of the app.
// Hosts may refuse <base>, so the app's own addresses (relative ones, and // Hosts may refuse <base>, so the app's own addresses (relative ones, and
// absolute ones on this page's site) are sent to the app's server instead. // absolute ones on this page's site) are sent to the app's server instead.
// The page's own site; location.origin is "null" in a srcdoc document. // The page's own site, by scheme and host: location.origin is "null" in a
const PAGE_ORIGIN = new URL(document.baseURI).origin; // srcdoc document, and a host may serve the page from its own scheme.
const PAGE = new URL(document.baseURI);
const toApp = (value) => { const toApp = (value) => {
let url; let url;
try { try {
@@ -108,8 +109,7 @@ function panelHtml(appOrigin) {
} catch { } catch {
return value; return value;
} }
if (!/^https?:$/.test(url.protocol) || url.origin !== PAGE_ORIGIN) if (url.protocol !== PAGE.protocol || url.host !== PAGE.host) return value;
return value;
return APP_ORIGIN + url.pathname + url.search + url.hash; return APP_ORIGIN + url.pathname + url.search + url.hash;
}; };
const isRemote = (value) => { const isRemote = (value) => {