mirror of
https://github.com/DayuanJiang/next-ai-draw-io.git
synced 2026-10-12 12:39:52 +08:00
feat(mcp-server): serve the bundled draw.io same-origin behind an api token
GET /drawio/<path> serves dist/drawio with a MIME table, a day of caching and nosniff; paths are normalized and never reach WEB-INF or META-INF. The preview embeds /drawio/index.html when the copy exists and DRAWIO_BASE_URL is unset, else the external draw.io as before (and start_session says so). Every /api request must carry the per-process X-Drawio-Token the page gets in its HTML; pages send frame-ancestors 'self' and nosniff.
This commit is contained in:
@@ -40,7 +40,7 @@
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<iframe id="drawio" src="{{DRAWIO_URL}}/?embed=1&proto=json&spin=1&libraries=1&noSaveBtn=1&noExitBtn=1&saveAndExit=0&themes=1&{{DRAWIO_PARAMS}}" title="draw.io editor"></iframe>
|
||||
<iframe id="drawio" src="{{DRAWIO_URL}}?embed=1&proto=json&spin=1&libraries=1&noSaveBtn=1&noExitBtn=1&saveAndExit=0&themes=1&{{DRAWIO_PARAMS}}" title="draw.io editor"></iframe>
|
||||
</div>
|
||||
<div id="notice" role="status"></div>
|
||||
<div id="history-modal">
|
||||
@@ -85,7 +85,9 @@
|
||||
</div>
|
||||
<script>
|
||||
const sessionId = {{SESSION_JSON}};
|
||||
const DRAWIO_ORIGIN = {{ORIGIN_JSON}};
|
||||
// Empty when draw.io is served from this origin
|
||||
const DRAWIO_ORIGIN = {{ORIGIN_JSON}} || location.origin;
|
||||
const API_TOKEN = {{TOKEN_JSON}};
|
||||
</script>
|
||||
<script>
|
||||
{{SCRIPT}}
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
const iframe = document.getElementById('drawio');
|
||||
// Every API request carries the token the server wrote into this page
|
||||
const api = (url, init = {}) => fetch(url, { ...init, headers: { ...(init.headers || {}), 'X-Drawio-Token': API_TOKEN } });
|
||||
let currentVersion = 0, isReady = false, pendingXml = null, lastXml = null;
|
||||
// The server state this tab is in step with (see stateId in http-server.ts);
|
||||
// null until the first poll
|
||||
@@ -82,7 +84,7 @@ window.addEventListener('message', (e) => {
|
||||
// result: a poll answered before that still sees the
|
||||
// request and would start the same export again.
|
||||
const seq = msg.message.mcpExport;
|
||||
fetch('/api/state', {
|
||||
api('/api/state', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ sessionId, exportData: d, exportId: mcpExportId })
|
||||
@@ -126,7 +128,7 @@ window.addEventListener('message', (e) => {
|
||||
const t = thumbExport;
|
||||
if (!t || msg.message.thumbExport !== t.n || latestXml !== t.xml) return;
|
||||
thumbExport = null;
|
||||
fetch('/api/history-svg', {
|
||||
api('/api/history-svg', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ sessionId, svg, stateId: t.stateId, version: t.version })
|
||||
@@ -184,7 +186,7 @@ async function pushState(xml, svg = '', baseVersion = currentVersion, source = '
|
||||
if (!sessionId) return;
|
||||
pushesInFlight.push(xml);
|
||||
try {
|
||||
const r = await fetch('/api/state', {
|
||||
const r = await api('/api/state', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ sessionId, xml, svg, baseVersion, source, stateId: sid })
|
||||
@@ -282,7 +284,7 @@ async function poll() {
|
||||
if (!sessionId) return;
|
||||
const seq = ++pollSeq;
|
||||
try {
|
||||
const r = await fetch('/api/state?sessionId=' + encodeURIComponent(sessionId));
|
||||
const r = await api('/api/state?sessionId=' + encodeURIComponent(sessionId));
|
||||
if (!r.ok) return;
|
||||
const s = await r.json();
|
||||
// An older answer than one already handled (the interval, the 409
|
||||
@@ -469,7 +471,7 @@ historyBtn.onclick = async () => {
|
||||
// A list for a state the server recreated meanwhile has old ids
|
||||
const sid = stateId;
|
||||
try {
|
||||
const r = await fetch('/api/history?sessionId=' + encodeURIComponent(sessionId));
|
||||
const r = await api('/api/history?sessionId=' + encodeURIComponent(sessionId));
|
||||
if (r.ok) {
|
||||
const d = await r.json();
|
||||
if (sid !== stateId) return;
|
||||
@@ -529,7 +531,7 @@ restoreBtn.onclick = async () => {
|
||||
restoreBtn.disabled = true;
|
||||
restoreBtn.textContent = 'Restoring...';
|
||||
try {
|
||||
const r = await fetch('/api/restore', {
|
||||
const r = await api('/api/restore', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ sessionId, id: selectedId, stateId: historyStateId })
|
||||
|
||||
Reference in New Issue
Block a user