| 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' })); |
| |
| 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' }); } |
| }); |
|
|
| |
| 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) { |
| |
| 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'; |
| |
| 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}`)); |
|
|