mirror of
https://github.com/DayuanJiang/next-ai-draw-io.git
synced 2026-01-03 06:42:27 +08:00
fix(mcp): sync browser state before get_diagram to prevent data loss (#342)
* fix(mcp): sync browser state before get_diagram to prevent data loss - Add syncRequested flag to SessionState for browser sync coordination - Add requestSync() and waitForSync() functions to http-server - Browser polls for syncRequested flag and immediately pushes current state - get_diagram now syncs fresh state from browser before returning - edit_diagram requires get_diagram to be called within 30s to prevent stale edits - Updated edit_diagram description to enforce workflow * fix(mcp): make lastGetDiagramTime session-scoped and handle missing session in requestSync - Move lastGetDiagramTime into currentSession object to prevent cross-session issues - requestSync now returns boolean indicating if request was made - Only wait for sync if session exists (avoids false-positive from undefined state)
This commit is contained in:
@@ -18,6 +18,7 @@ interface SessionState {
|
||||
version: number
|
||||
lastUpdated: Date
|
||||
svg?: string // Cached SVG from last browser save
|
||||
syncRequested?: number // Timestamp when sync requested, cleared when browser responds
|
||||
}
|
||||
|
||||
export const stateStore = new Map<string, SessionState>()
|
||||
@@ -39,11 +40,37 @@ export function setState(sessionId: string, xml: string, svg?: string): number {
|
||||
version: newVersion,
|
||||
lastUpdated: new Date(),
|
||||
svg: svg || existing?.svg, // Preserve cached SVG if not provided
|
||||
syncRequested: undefined, // Clear sync request when browser pushes state
|
||||
})
|
||||
log.debug(`State updated: session=${sessionId}, version=${newVersion}`)
|
||||
return newVersion
|
||||
}
|
||||
|
||||
export function requestSync(sessionId: string): boolean {
|
||||
const state = stateStore.get(sessionId)
|
||||
if (state) {
|
||||
state.syncRequested = Date.now()
|
||||
log.debug(`Sync requested for session=${sessionId}`)
|
||||
return true
|
||||
}
|
||||
log.debug(`Sync requested for non-existent session=${sessionId}`)
|
||||
return false
|
||||
}
|
||||
|
||||
export async function waitForSync(
|
||||
sessionId: string,
|
||||
timeoutMs = 3000,
|
||||
): Promise<boolean> {
|
||||
const start = Date.now()
|
||||
while (Date.now() - start < timeoutMs) {
|
||||
const state = stateStore.get(sessionId)
|
||||
if (!state?.syncRequested) return true // Sync completed
|
||||
await new Promise((r) => setTimeout(r, 100))
|
||||
}
|
||||
log.warn(`Sync timeout for session=${sessionId}`)
|
||||
return false // Timeout
|
||||
}
|
||||
|
||||
export function startHttpServer(port = 6002): Promise<number> {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (server) {
|
||||
@@ -157,6 +184,7 @@ function handleStateApi(
|
||||
JSON.stringify({
|
||||
xml: state?.xml || null,
|
||||
version: state?.version || 0,
|
||||
syncRequested: !!state?.syncRequested,
|
||||
}),
|
||||
)
|
||||
} else if (req.method === "POST") {
|
||||
@@ -415,6 +443,13 @@ function getHtmlPage(sessionId: string): string {
|
||||
// Fallback if export doesn't respond
|
||||
setTimeout(() => { if (pendingSvgExport === msg.xml) { pushState(msg.xml, ''); pendingSvgExport = null; } }, 2000);
|
||||
} else if (msg.event === 'export' && msg.data) {
|
||||
// Handle sync export (XML format) - server requested fresh state
|
||||
if (pendingSyncExport && !msg.data.startsWith('data:') && !msg.data.startsWith('<svg')) {
|
||||
pendingSyncExport = false;
|
||||
pushState(msg.data, '');
|
||||
return;
|
||||
}
|
||||
// Handle SVG export
|
||||
let svg = msg.data;
|
||||
if (!svg.startsWith('data:')) svg = 'data:image/svg+xml;base64,' + btoa(unescape(encodeURIComponent(svg)));
|
||||
if (pendingSvgExport) {
|
||||
@@ -457,12 +492,20 @@ function getHtmlPage(sessionId: string): string {
|
||||
} catch (e) { console.error('Push failed:', e); }
|
||||
}
|
||||
|
||||
let pendingSyncExport = false;
|
||||
|
||||
async function poll() {
|
||||
if (!sessionId) return;
|
||||
try {
|
||||
const r = await fetch('/api/state?sessionId=' + encodeURIComponent(sessionId));
|
||||
if (!r.ok) return;
|
||||
const s = await r.json();
|
||||
// Handle sync request - server needs fresh state
|
||||
if (s.syncRequested && !pendingSyncExport) {
|
||||
pendingSyncExport = true;
|
||||
iframe.contentWindow.postMessage(JSON.stringify({ action: 'export', format: 'xml' }), '*');
|
||||
}
|
||||
// Load new diagram from server
|
||||
if (s.version > currentVersion && s.xml) {
|
||||
currentVersion = s.version;
|
||||
loadDiagram(s.xml, true);
|
||||
|
||||
Reference in New Issue
Block a user