chat / server /index.js
incognitolm
Versions
8a206ae
Raw
History Blame
4.65 kB
import express from 'express';
import { createServer } from 'http';
import { WebSocketServer } from 'ws';
import { createClient } from '@supabase/supabase-js';
import crypto from 'crypto';
import path from 'path';
import { fileURLToPath } from 'url';
import { handleWsMessage } from './wsHandler.js';
import { sessionStore, initStoreConfig } from './sessionStore.js';
import { SUPABASE_URL, SUPABASE_ANON_KEY } from './config.js';
import { safeSend } from './helpers.js';
export { SUPABASE_URL, SUPABASE_ANON_KEY };
export { LIGHTNING_BASE, PUBLIC_URL } from './config.js';
const __dirname = path.dirname(fileURLToPath(import.meta.url));
initStoreConfig(SUPABASE_URL, SUPABASE_ANON_KEY);
export const supabaseAnon = createClient(SUPABASE_URL, SUPABASE_ANON_KEY);
const PORT = process.env.PORT || 7860;
const app = express();
app.use(express.static(path.join(__dirname, '..', 'public')));
app.use(express.json({ limit: '10mb' }));
// Require turnstile cookie for API routes (except the verification endpoint itself)
app.use('/api', (req, res, next) => {
const exempt = ['/turnstile', '/health'];
if (exempt.includes(req.path)) return next();
const cookieHeader = req.headers.cookie || '';
if (cookieHeader.includes('turnstile=1')) return next();
return res.status(403).json({ error: 'turnstile:required' });
});
app.get('/health', (_req, res) => res.json({ ok: true }));
app.get('/api/share/:token', async (req, res) => {
try {
const shared = await sessionStore.resolveShareToken(req.params.token);
if (!shared) return res.status(404).json({ error: 'Not found' });
const snap = shared.session_snapshot;
res.json({
name: snap.name,
preview: (snap.history || []).slice(0, 6).map(m => ({
role: m.role,
content: (typeof m.content === 'string' ? m.content : JSON.stringify(m.content)).slice(0, 400),
})),
});
} catch { res.status(500).json({ error: 'Server error' }); }
});
// Turnstile verification endpoint - accepts token and verifies with Cloudflare
app.post('/api/turnstile', async (req, res) => {
try {
const token = req.body?.token;
const secret = process.env.TURNSTILE_SECRET_KEY;
if (!token || !secret) return res.status(400).json({ error: 'Missing token or server not configured' });
const params = new URLSearchParams();
params.append('secret', secret);
params.append('response', token);
if (req.ip) params.append('remoteip', req.ip);
const r = await fetch('https://challenges.cloudflare.com/turnstile/v0/siteverify', {
method: 'POST', body: params,
});
const j = await r.json();
if (j?.success) {
// Set a short-lived cookie to allow access to other endpoints
res.cookie('turnstile', '1', { maxAge: 24 * 3600 * 1000, path: '/', sameSite: 'lax' });
return res.json({ success: true });
}
return res.status(403).json({ error: 'Verification failed' });
} catch (e) { console.error('turnstile verify', e); return res.status(500).json({ error: 'Server error' }); }
});
app.get('*', (req, res) => {
if (!req.path.startsWith('/api/'))
res.sendFile(path.join(__dirname, '..', 'public', 'index.html'));
});
const httpServer = createServer(app);
const wss = new WebSocketServer({ server: httpServer, path: '/ws' });
export const wsClients = new Map();
wss.on('connection', (ws, req) => {
const ip = (req.headers['x-forwarded-for'] || '').split(',')[0].trim()
|| req.socket.remoteAddress || 'unknown';
const userAgent = req.headers['user-agent'] || 'unknown';
// Mark verified if request included a turnstile cookie
const cookies = (req.headers.cookie || '').split(';').map(s => s.trim()).filter(Boolean);
const cookieMap = Object.fromEntries(cookies.map(c => { const i = c.indexOf('='); return [c.slice(0, i), c.slice(i+1)]; }));
const verified = cookieMap.turnstile === '1';
wsClients.set(ws, { tempId: crypto.randomUUID(), ip, userAgent, userId: null, authenticated: false, verified });
ws.on('message', async raw => {
try { await handleWsMessage(ws, JSON.parse(raw.toString()), wsClients); }
catch (ex) {
console.error("Invalid message error:", ex.message, "\nStack:", ex.stack);
safeSend(ws, { type: 'error', message: 'Invalid message: ' + ex.message });
}
});
ws.on('close', () => {
const c = wsClients.get(ws);
if (c?.userId) sessionStore.markOffline(c.userId, ws);
wsClients.delete(ws);
});
ws.on('error', () => wsClients.delete(ws));
safeSend(ws, { type: 'connected', tempId: wsClients.get(ws)?.tempId });
});
httpServer.listen(PORT, '0.0.0.0', () => console.log(`InferencePort Web on port ${PORT}`));