import { createServer } from 'node:http' import { mkdir, readFile, writeFile } from 'node:fs/promises' import { createReadStream } from 'node:fs' import { existsSync } from 'node:fs' import { resolve, extname, join, normalize } from 'node:path' import { spawn } from 'node:child_process' import { createChromeProfile, removeChromeProfile } from './chrome-profile.mjs' const root = resolve(new URL('..', import.meta.url).pathname) const reportDir = resolve(root, '.cache/freecad/browser-occt') await mkdir(reportDir, { recursive: true }) const contentTypes = { '.html': 'text/html', '.js': 'text/javascript', '.wasm': 'application/wasm', '.json': 'application/json' } const reportPromises = new Map() const reportResolvers = new Map() const waitForReport = (browserId) => { if (reportPromises.has(browserId)) return reportPromises.get(browserId) const promise = new Promise((resolveReport, reject) => { reportResolvers.set(browserId, { resolve: resolveReport, reject }) }) reportPromises.set(browserId, promise) return promise } const server = createServer(async (request, response) => { response.setHeader('Cross-Origin-Opener-Policy', 'same-origin') response.setHeader('Cross-Origin-Embedder-Policy', 'require-corp') response.setHeader('Cross-Origin-Resource-Policy', 'same-origin') if (request.method === 'POST' && request.url?.startsWith('/__occt-report')) { let body = '' request.setEncoding('utf8') request.on('data', (chunk) => { body += chunk }) request.on('end', async () => { try { const report = JSON.parse(body) const browserId = report.browserId || new URL(request.url, 'http://127.0.0.1').searchParams.get('browser') || 'unknown' await writeFile(resolve(reportDir, `${browserId}.json`), `${JSON.stringify(report, null, 2)}\n`) reportResolvers.get(browserId)?.resolve(report) response.writeHead(204) response.end() } catch (error) { response.writeHead(400) response.end(String(error)) } }) return } const pathname = decodeURIComponent((request.url || '/').split('?')[0]) const relative = pathname === '/occt-browser-harness.html' ? 'public/occt-browser-harness.html' : pathname.startsWith('/native/') ? `public${pathname}` : null if (!relative) { response.writeHead(404); response.end('not found'); return } const file = normalize(join(root, relative)) if (!file.startsWith(resolve(root, 'public')) || !existsSync(file)) { response.writeHead(404); response.end('not found'); return } response.setHeader('content-type', contentTypes[extname(file)] || 'application/octet-stream') createReadStream(file).pipe(response) }) await new Promise((resolveServer) => server.listen(0, '127.0.0.1', resolveServer)) const port = server.address().port const runBrowser = (browserId, executable, args) => new Promise((resolveRun) => { if (!executable || !existsSync(executable)) { resolveRun({ browserId, status: 'unavailable', executable }); return } const reportPromise = waitForReport(browserId) const child = spawn(executable, [...args, `http://127.0.0.1:${port}/occt-browser-harness.html?browser=${browserId}`], { cwd: root, stdio: ['ignore', 'pipe', 'pipe'] }) let stdout = '' child.stdout.on('data', (chunk) => { stdout += String(chunk) }) let stderr = '' child.stderr.on('data', (chunk) => { stderr += String(chunk) }) let settled = false const finish = (result) => { if (settled) return settled = true clearTimeout(timer) resolveRun({ ...result, stdout: stdout.slice(-4000), stderr: stderr.slice(-2000) }) } const timer = setTimeout(() => { child.kill('SIGTERM'); finish({ browserId, status: 'timeout' }) }, 240000) reportPromise.then((report) => { if (settled) return child.kill('SIGTERM') finish({ browserId, status: report.status || 'pass', report, exitCode: child.exitCode, signal: null }) }).catch(() => {}) child.on('close', async (code, signal) => { if (!settled) { try { const report = await Promise.race([reportPromise, new Promise((_, reject) => setTimeout(() => reject(new Error('report timeout')), 5000))]) finish({ browserId, status: report.status || 'pass', report, exitCode: code, signal }) } catch { finish({ browserId, status: 'failed', exitCode: code, signal }) } } }) }) const results = [] const chromeProfile = await createChromeProfile('occt') try { results.push(await runBrowser('chrome', process.env.CHROME_BIN || '/home/mes123456/.local/bin/google-chrome', ['--headless=new', '--no-sandbox', '--disable-gpu', '--disable-dev-shm-usage', '--enable-logging=stderr', '--v=1', `--user-data-dir=${chromeProfile}`])) } finally { await removeChromeProfile(chromeProfile) } await new Promise((resolveServer) => server.close(resolveServer)) // Keep committed evidence stable: Chrome's temporary profile paths, timestamps // and verbose stderr are diagnostics, not compatibility claims. const verification = { schemaVersion: 1, headers: { coop: 'same-origin', coep: 'require-corp' }, browsers: results.map(({ stdout, stderr, ...result }) => result), status: results.every((result) => result.status === 'pass') ? 'pass' : 'partial', } await writeFile(resolve(root, 'config/occt-browser-verification.json'), `${JSON.stringify(verification, null, 2)}\n`) console.log(JSON.stringify(verification, null, 2)) if (results.some((result) => result.status !== 'pass')) process.exit(1)