/**
* Same-origin Mock HTTP Server for the Confluence Research Web UI.
* Implements exact HTTP contracts, security headers, cookie sessions, and deterministic scenarios.
* Strictly binds loopback (default 5173). Zero external dependencies.
*/
import http from 'node:http';
import fs from 'node:fs';
import path from 'node:path';
import crypto from 'node:crypto';
import { fileURLToPath } from 'node:url';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const FRONTEND_ROOT = path.resolve(__dirname, '..');
const DEFAULT_PORT = 5173;
const HOST = '127.0.0.1';
// Active state
let currentGlobalScenario = 'normal';
let isQueryBusy = false;
// Predefined available scenarios
export const SCENARIOS = [
'normal',
'empty_search',
'no_artifacts',
'repeated_cached_view',
'failed_tool',
'warning_truncated_history',
'403_verify',
'409_busy',
'504_timeout',
'delayed_cancellation',
'unknown_expired_download',
'malicious_content',
'large_output'
];
/**
* Standard Security Headers
*/
const SECURITY_HEADERS = {
'Content-Security-Policy': "default-src 'none'; script-src 'self'; style-src 'self'; connect-src 'self'; img-src 'self'; media-src 'none'; font-src 'self'; object-src 'none'; frame-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'",
'Referrer-Policy': 'no-referrer',
'X-Content-Type-Options': 'nosniff'
};
const MIME_TYPES = {
'.html': 'text/html; charset=utf-8',
'.css': 'text/css; charset=utf-8',
'.js': 'text/javascript; charset=utf-8',
'.mjs': 'text/javascript; charset=utf-8',
'.json': 'application/json; charset=utf-8',
'.svg': 'image/svg+xml',
'.md': 'text/markdown; charset=utf-8',
'.map': 'application/json'
};
/**
* Parses cookies from request.
*/
function parseCookies(cookieHeader) {
const list = {};
if (!cookieHeader) return list;
cookieHeader.split(';').forEach((cookie) => {
const parts = cookie.split('=');
if (parts.length >= 2) {
list[parts[0].trim()] = decodeURIComponent(parts.slice(1).join('=').trim());
}
});
return list;
}
/**
* Determines the active scenario for a request.
*/
function getScenarioForRequest(req, urlObj) {
// 1. Query parameter
const qScenario = urlObj.searchParams.get('scenario');
if (qScenario && SCENARIOS.includes(qScenario)) return qScenario;
// 2. Custom header
const hScenario = req.headers['x-mock-scenario'];
if (hScenario && SCENARIOS.includes(hScenario)) return hScenario;
// 3. Cookie
const cookies = parseCookies(req.headers.cookie);
if (cookies.mock_scenario && SCENARIOS.includes(cookies.mock_scenario)) {
return cookies.mock_scenario;
}
// 4. Global fallback
return currentGlobalScenario;
}
/**
* Generates fixture data for the requested scenario.
*/
function buildScenarioResponse(scenario, prompt) {
const now = new Date();
const nowIso = now.toISOString();
const expiresAtIso = new Date(now.getTime() + 15 * 60 * 1000).toISOString();
switch (scenario) {
case 'empty_search':
return {
session_id: 'mock-session-empty',
markdown: '# Research Results\n\nNo Confluence documentation matched your query.',
pages_accessed: [],
tool_history: [
{
tool_call_id: 'b_search_01',
tool: 'confluence_search',
parameters: { query: prompt || 'empty query', limit: 10 },
parameters_truncated: false,
started_at: nowIso,
completed_at: nowIso,
status: 'success',
cache_hit: false,
result: {
pages: [],
pagination: { offset: 0, limit: 10, has_more: false }
},
error: null,
result_truncated: false
}
],
artifacts: [],
warnings: [],
duration_seconds: 1.1
};
case 'no_artifacts':
return {
session_id: 'mock-session-no-art',
markdown: '# Research Summary\n\nInformation gathered from [Deployment Guide](https://approved.example.com/pages/viewpage.action?pageId=847291).\n\nNo artifacts produced.',
pages_accessed: [
{
page_id: '847291',
title: 'Deployment Guide',
space: 'OPS',
url: 'https://approved.example.com/pages/viewpage.action?pageId=847291',
accessed_at: nowIso
}
],
tool_history: [
{
tool_call_id: 'b_view_01',
tool: 'confluence_view',
parameters: { page_id: '847291' },
parameters_truncated: false,
started_at: nowIso,
completed_at: nowIso,
status: 'success',
cache_hit: false,
result: {
page_id: '847291',
title: 'Deployment Guide',
space: 'OPS',
url: 'https://approved.example.com/pages/viewpage.action?pageId=847291',
markdown: 'Deploy service X.',
truncated: false
},
error: null,
result_truncated: false
}
],
artifacts: [],
warnings: [],
duration_seconds: 2.3
};
case 'repeated_cached_view':
return {
session_id: 'mock-session-cached',
markdown: '# Deployment Summary\n\nReferenced [Deployment Guide](https://approved.example.com/pages/viewpage.action?pageId=847291) across multiple steps.',
pages_accessed: [
{
page_id: '847291',
title: 'Deployment Guide',
space: 'OPS',
url: 'https://approved.example.com/pages/viewpage.action?pageId=847291',
accessed_at: nowIso
}
],
tool_history: [
{
tool_call_id: 'b_call_01',
tool: 'confluence_view',
parameters: { page_id: '847291' },
parameters_truncated: false,
started_at: nowIso,
completed_at: nowIso,
status: 'success',
cache_hit: false,
result: {
page_id: '847291',
title: 'Deployment Guide',
space: 'OPS',
url: 'https://approved.example.com/pages/viewpage.action?pageId=847291',
markdown: 'Initial view.',
truncated: false
},
error: null,
result_truncated: false
},
{
tool_call_id: 'b_call_02',
tool: 'confluence_view',
parameters: { page_id: '847291' },
parameters_truncated: false,
started_at: nowIso,
completed_at: nowIso,
status: 'success',
cache_hit: true,
result: {
page_id: '847291',
title: 'Deployment Guide',
space: 'OPS',
url: 'https://approved.example.com/pages/viewpage.action?pageId=847291',
markdown: 'Initial view (from cache).',
truncated: false
},
error: null,
result_truncated: false
}
],
artifacts: [],
warnings: [],
duration_seconds: 1.8
};
case 'failed_tool':
return {
session_id: 'mock-session-failed-tool',
markdown: '# Partial Summary\n\nSearch succeeded, but page 999999 could not be accessed due to an upstream error.',
pages_accessed: [],
tool_history: [
{
tool_call_id: 'b_search_01',
tool: 'confluence_search',
parameters: { query: 'archived docs', limit: 5 },
parameters_truncated: false,
started_at: nowIso,
completed_at: nowIso,
status: 'success',
cache_hit: false,
result: {
pages: [{ page_id: '999999', title: 'Archived Page', space: 'ARCH', url: 'https://approved.example.com/pages/viewpage.action?pageId=999999', snippet: 'Missing page' }],
pagination: { offset: 0, limit: 5, has_more: false }
},
error: null,
result_truncated: false
},
{
tool_call_id: 'b_view_02',
tool: 'confluence_view',
parameters: { page_id: '999999' },
parameters_truncated: false,
started_at: nowIso,
completed_at: nowIso,
status: 'error',
cache_hit: false,
result: null,
error: {
code: 'page_not_found',
message: 'Confluence page 999999 was not found or has been deleted.'
},
result_truncated: false
}
],
artifacts: [],
warnings: [
{ code: 'page_not_found', message: 'Page 999999 access failed', tool_call_id: 'b_view_02' }
],
duration_seconds: 2.7
};
case 'warning_truncated_history':
return {
session_id: 'mock-session-warn-trunc',
markdown: '# Bounded Results\n\nExtensive data retrieved with truncated history logs.',
pages_accessed: [
{
page_id: '12345',
title: 'Large Architecture Document',
space: 'ARCH',
url: 'https://approved.example.com/pages/viewpage.action?pageId=12345',
accessed_at: nowIso
}
],
tool_history: [
{
tool_call_id: 'b_call_trunc_01',
tool: 'confluence_view',
parameters: { page_id: '12345', details: 'A'.repeat(500) },
parameters_truncated: true,
started_at: nowIso,
completed_at: nowIso,
status: 'success',
cache_hit: false,
result: {
page_id: '12345',
summary: 'Summary preserved while large raw content was truncated.'
},
error: null,
result_truncated: true
}
],
artifacts: [],
warnings: [
{
code: 'history_truncated',
message: 'History result budget exceeded; 1 entry truncated.',
tool_call_id: 'b_call_trunc_01'
},
{
code: 'unknown_custom_warning',
message: 'Custom backend warning code test.'
}
],
duration_seconds: 4.1
};
case 'malicious_content':
return {
session_id: 'mock-session-malicious',
markdown: [
'# Malicious Input Test',
'',
'Attempting XSS and unsafe content:',
'',
'
',
'',
'',
'