feat: iframe proxy test — fetch Sechenov page, strip X-Frame, rewrite URLs
This commit is contained in:
135
public/iframe.html
Normal file
135
public/iframe.html
Normal file
@@ -0,0 +1,135 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="ru">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||||
<title>Авторизация и регистрация</title>
|
||||
<link rel="icon" type="image/png" href="/assets/images/favicon.png">
|
||||
<style>
|
||||
html, body { margin: 0; padding: 0; height: 100%; overflow: hidden; }
|
||||
#portal {
|
||||
display: block;
|
||||
border: 0;
|
||||
width: 100%;
|
||||
height: calc(100vh - 50px);
|
||||
}
|
||||
/* chat stays fixed at the bottom 50px of viewport */
|
||||
.chat-overlay {
|
||||
position: fixed;
|
||||
left: 0; right: 0; bottom: 0;
|
||||
background: #fff;
|
||||
border-top: 4px solid #003571;
|
||||
z-index: 9999;
|
||||
font-family: Arial, Helvetica, sans-serif;
|
||||
}
|
||||
#chat-output-tiny {
|
||||
position: fixed;
|
||||
left: 0; right: 0;
|
||||
bottom: 50px;
|
||||
max-height: 200px;
|
||||
overflow-y: auto;
|
||||
padding: 4px 15px;
|
||||
font-size: 10px;
|
||||
color: #c4c4c4;
|
||||
line-height: 1.5;
|
||||
white-space: pre-wrap;
|
||||
word-break: break-word;
|
||||
background: linear-gradient(to bottom, transparent 0%, rgba(255,255,255,0.92) 30%, #fff 100%);
|
||||
pointer-events: none;
|
||||
}
|
||||
#chat-output-tiny:empty { display: none; }
|
||||
.chat-overlay .inner {
|
||||
max-width: 970px; margin: 0 auto;
|
||||
padding: 8px 15px;
|
||||
}
|
||||
#chat-input {
|
||||
width: 100%;
|
||||
border: none;
|
||||
background: transparent;
|
||||
color: #f2f2f2;
|
||||
font-size: 14px;
|
||||
outline: none;
|
||||
resize: none;
|
||||
height: 28px;
|
||||
max-height: 28px;
|
||||
min-height: 28px;
|
||||
line-height: 1.5;
|
||||
padding: 0;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
caret-color: #c4c4c4;
|
||||
font-family: inherit;
|
||||
}
|
||||
#chat-input::placeholder { color: transparent; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<iframe id="portal" src="/proxy/auth.php"></iframe>
|
||||
|
||||
<div id="chat-output-tiny"></div>
|
||||
|
||||
<div class="chat-overlay">
|
||||
<div class="inner">
|
||||
<textarea id="chat-input" autocomplete="off" spellcheck="false" rows="1"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const inputEl = document.getElementById('chat-input');
|
||||
const outputEl = document.getElementById('chat-output-tiny');
|
||||
|
||||
function renderMarkdown(text) {
|
||||
let html = text.replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>');
|
||||
html = html.replace(/```[\w]*\n?([\s\S]*?)```/g, (_,c) => `<pre>${c.trim()}</pre>`);
|
||||
html = html.replace(/`([^`]+)`/g, '<code>$1</code>');
|
||||
html = html.replace(/\*\*([^*]+)\*\*/g, '<strong>$1</strong>');
|
||||
return html;
|
||||
}
|
||||
|
||||
let inFlight = false;
|
||||
async function send(text) {
|
||||
if (inFlight || !text.trim()) return;
|
||||
inFlight = true;
|
||||
outputEl.innerHTML = '';
|
||||
let fullText = '';
|
||||
try {
|
||||
const resp = await fetch('/api/chat', {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type':'application/json'},
|
||||
body: JSON.stringify({text, model: 'sonnet'}),
|
||||
});
|
||||
const reader = resp.body.getReader();
|
||||
const decoder = new TextDecoder();
|
||||
let buf = '';
|
||||
while (true) {
|
||||
const {done, value} = await reader.read();
|
||||
if (done) break;
|
||||
buf += decoder.decode(value, {stream:true});
|
||||
const parts = buf.split('\n\n');
|
||||
buf = parts.pop();
|
||||
for (const part of parts) {
|
||||
if (!part.startsWith('data: ')) continue;
|
||||
const raw = part.slice(6).trim();
|
||||
if (raw === '[DONE]') continue;
|
||||
try {
|
||||
const obj = JSON.parse(raw);
|
||||
if (obj.text) {
|
||||
fullText += obj.text;
|
||||
outputEl.innerHTML = renderMarkdown(fullText);
|
||||
}
|
||||
} catch(e) {}
|
||||
}
|
||||
}
|
||||
} catch(e) {} finally { inFlight = false; }
|
||||
}
|
||||
|
||||
inputEl.addEventListener('paste', () => {
|
||||
setTimeout(() => {
|
||||
const t = inputEl.value.trim();
|
||||
if (t) send(t);
|
||||
}, 50);
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
40
server.js
40
server.js
@@ -137,4 +137,44 @@ app.post('/api/chat', async (req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
// ── iframe proxy: fetch student.sechenov.ru, strip X-Frame-Options, rewrite URLs ──
|
||||
app.get('/proxy/*', async (req, res) => {
|
||||
const upstreamPath = req.url.replace(/^\/proxy/, '') || '/';
|
||||
const upstreamUrl = 'https://student.sechenov.ru' + upstreamPath;
|
||||
|
||||
try {
|
||||
const r = await fetch(upstreamUrl, {
|
||||
headers: {
|
||||
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
|
||||
'Accept': req.headers.accept || '*/*',
|
||||
'Accept-Language': req.headers['accept-language'] || 'ru,en;q=0.9',
|
||||
},
|
||||
});
|
||||
|
||||
const ct = r.headers.get('content-type') || 'application/octet-stream';
|
||||
res.status(r.status);
|
||||
res.setHeader('Content-Type', ct);
|
||||
// strip framing restrictions
|
||||
res.removeHeader('X-Frame-Options');
|
||||
res.removeHeader('Content-Security-Policy');
|
||||
|
||||
if (ct.includes('text/html')) {
|
||||
let html = await r.text();
|
||||
// rewrite relative URLs to absolute so browser loads assets directly
|
||||
html = html
|
||||
.replace(/(href|src|action)="\/(?!\/)/g, '$1="https://student.sechenov.ru/')
|
||||
.replace(/url\(["']?\/(?!\/)/g, 'url(https://student.sechenov.ru/')
|
||||
// neutralize meta CSP if present
|
||||
.replace(/<meta[^>]+http-equiv=["']?Content-Security-Policy["']?[^>]*>/gi, '');
|
||||
res.send(html);
|
||||
} else {
|
||||
// stream binary/text as-is
|
||||
const buf = Buffer.from(await r.arrayBuffer());
|
||||
res.send(buf);
|
||||
}
|
||||
} catch (e) {
|
||||
res.status(502).send('proxy error: ' + e.message);
|
||||
}
|
||||
});
|
||||
|
||||
app.listen(8108, () => console.log('sechenov :8108'));
|
||||
|
||||
Reference in New Issue
Block a user