everydaycats commited on
Commit
a216373
·
verified ·
1 Parent(s): d798d35

Update app.js

Browse files
Files changed (1) hide show
  1. app.js +135 -217
app.js CHANGED
@@ -1,243 +1,161 @@
1
- const express = require('express');
2
- const { createClient } = require('@supabase/supabase-js');
3
- const jwt = require('jsonwebtoken');
4
- const { v4: uuidv4 } = require('uuid');
5
- const axios = require('axios');
6
- const bodyParser = require('body-parser');
7
- const cors = require('cors');
8
 
 
 
 
 
 
 
 
 
 
9
  const app = express();
 
 
 
 
 
 
 
10
  app.use(cors());
11
- app.use(bodyParser.json({ limit: '50mb' }));
12
-
13
- const tempKeys = new Map();
14
- const activeSessions = new Map();
15
-
16
- const {
17
- SUPABASE_URL,
18
- SUPABASE_SERVICE_ROLE_KEY,
19
- EXTERNAL_SERVER_URL = 'http://localhost:7860',
20
- STORAGE_BUCKET = 'project-assets',
21
- PORT = 7860
22
- } = process.env;
23
-
24
- let supabase = null;
25
-
26
- try {
27
- if (SUPABASE_URL && SUPABASE_SERVICE_ROLE_KEY) {
28
- supabase = createClient(SUPABASE_URL, SUPABASE_SERVICE_ROLE_KEY, {
29
- auth: {
30
- autoRefreshToken: false,
31
- persistSession: false
32
  }
33
  });
34
- console.log("⚡ Supabase Connected (Admin Context)");
35
- } else {
36
- console.warn("⚠️ Memory-Only mode (Supabase credentials missing).");
37
- }
38
- } catch (e) {
39
- console.error("Supabase Init Error:", e);
40
- }
41
-
42
- const verifySupabaseUser = async (req, res, next) => {
43
- const debugMode = process.env.DEBUG_NO_AUTH === 'true';
44
- if (debugMode) { req.user = { id: "user_dev_01" }; return next(); }
45
- const authHeader = req.headers.authorization;
46
- if (!authHeader || !authHeader.startsWith('Bearer ')) return res.status(401).json({ error: 'Missing Bearer token' });
47
- const idToken = authHeader.split('Bearer ')[1];
48
- try {
49
- if (supabase) {
50
- const { data: { user }, error } = await supabase.auth.getUser(idToken);
51
- if (error || !user) throw new Error("Invalid Token");
52
- req.user = user;
53
- next();
54
- } else { req.user = { id: "memory_user" }; next(); }
55
- } catch (error) { return res.status(403).json({ error: 'Unauthorized', details: error.message }); }
56
- };
57
-
58
- async function getSessionSecret(uid, projectId) {
59
- const cacheKey = `${uid}:${projectId}`;
60
- if (activeSessions.has(cacheKey)) {
61
- const session = activeSessions.get(cacheKey);
62
- session.lastAccessed = Date.now();
63
- return session.secret;
64
  }
65
- if (supabase) {
66
- try {
67
- const { data } = await supabase.from('projects').select('plugin_secret').eq('id', projectId).eq('user_id', uid).single();
68
- if (data && data.plugin_secret) {
69
- activeSessions.set(cacheKey, { secret: data.plugin_secret, lastAccessed: Date.now() });
70
- return data.plugin_secret;
71
- }
72
- } catch (err) { console.error("DB Read Error:", err); }
73
- }
74
- return null;
75
- }
76
-
77
- app.post('/key', verifySupabaseUser, (req, res) => {
78
- const { projectId } = req.body;
79
- if (!projectId) return res.status(400).json({ error: 'projectId required' });
80
- const key = `key_${uuidv4().replace(/-/g, '')}`;
81
- tempKeys.set(key, { uid: req.user.id, projectId: projectId, createdAt: Date.now() });
82
- console.log(`🔑 Generated Key for user ${req.user.id}: ${key}`);
83
- res.json({ key, expiresIn: 300 });
84
- });
85
-
86
- app.post('/redeem', async (req, res) => {
87
- const { key } = req.body;
88
- if (!key || !tempKeys.has(key)) return res.status(404).json({ error: 'Invalid or expired key' });
89
- const data = tempKeys.get(key);
90
- const sessionSecret = uuidv4();
91
- const token = jwt.sign({ uid: data.uid, projectId: data.projectId }, sessionSecret, { expiresIn: '3d' });
92
- const cacheKey = `${data.uid}:${data.projectId}`;
93
- activeSessions.set(cacheKey, { secret: sessionSecret, lastAccessed: Date.now() });
94
- if (supabase) await supabase.from('projects').update({ plugin_secret: sessionSecret }).eq('id', data.projectId).eq('user_id', data.uid);
95
- tempKeys.delete(key);
96
- console.log(`🚀 Redeemed JWT for ${cacheKey}`);
97
- res.json({ token });
98
  });
99
 
100
- app.post('/verify', async (req, res) => {
101
- const { token } = req.body;
102
- if (!token) return res.status(400).json({ valid: false });
103
- const decoded = jwt.decode(token);
104
- if (!decoded || !decoded.uid || !decoded.projectId) return res.status(401).json({ valid: false });
105
- const secret = await getSessionSecret(decoded.uid, decoded.projectId);
106
- if (!secret) return res.status(401).json({ valid: false });
107
- try { jwt.verify(token, secret); return res.json({ valid: true }); } catch (err) { return res.status(403).json({ valid: false }); }
108
  });
109
 
110
- app.post('/feedback', async (req, res) => {
111
- const { token, ...pluginPayload } = req.body;
112
- if (!token) return res.status(400).json({ error: 'Token required' });
113
- const decoded = jwt.decode(token);
114
- if (!decoded) return res.status(401).json({ error: 'Malformed token' });
115
- const secret = await getSessionSecret(decoded.uid, decoded.projectId);
116
- if (!secret) return res.status(404).json({ error: 'Session revoked' });
117
- try {
118
- jwt.verify(token, secret);
119
- const targetUrl = EXTERNAL_SERVER_URL.replace(/\/$/, '') + '/project/feedback';
120
- const response = await axios.post(targetUrl, { userId: decoded.uid, projectId: decoded.projectId, ...pluginPayload });
121
- return res.json({ success: true, externalResponse: response.data });
122
- } catch (err) { return res.status(502).json({ error: 'Failed to forward' }); }
123
- });
124
 
125
- app.post('/feedback2', verifySupabaseUser, async (req, res) => {
126
- const { projectId, prompt, images, ...otherPayload } = req.body;
127
- const userId = req.user.id;
128
- if (!projectId || !prompt) return res.status(400).json({ error: 'Missing projectId or prompt' });
129
- const targetUrl = EXTERNAL_SERVER_URL.replace(/\/$/, '') + '/project/feedback';
130
- try {
131
- const response = await axios.post(targetUrl, { userId: userId, projectId: projectId, prompt: prompt, images: images || [], ...otherPayload });
132
- return res.json({ success: true, externalResponse: response.data });
133
- } catch (err) { return res.status(502).json({ error: 'Failed to forward' }); }
134
- });
135
 
136
- // --- STREAM FEED (Optimized headers) ---
137
- app.post('/stream-feed', verifySupabaseUser, async (req, res) => {
138
- const { projectId } = req.body;
139
- const userId = req.user.id;
 
140
 
141
- // Headers to disable caching for poller
142
- res.setHeader('Cache-Control', 'no-cache, no-store, must-revalidate');
143
- res.setHeader('Pragma', 'no-cache');
144
- res.setHeader('Expires', '0');
145
 
146
- if (!projectId) return res.status(400).json({ error: 'Missing projectId' });
147
-
148
- if (supabase) {
149
- const { data, error } = await supabase.from('projects').select('id, user_id, info').eq('id', projectId).single();
150
- if (error || !data || data.user_id !== userId) return res.status(403).json({ error: 'Unauthorized' });
151
-
152
- const targetUrl = EXTERNAL_SERVER_URL.replace(/\/$/, '') + '/project/ping';
153
- try {
154
- const response = await axios.post(targetUrl, { projectId, userId, isFrontend: true });
155
- return res.json({ ...response.data, dbStatus: data.info?.status || 'idle' });
156
- } catch (e) { return res.status(502).json({ error: "AI Server Unreachable" }); }
157
  }
158
- });
159
 
160
- /*
161
- app.post('/poll', async (req, res) => {
162
- const { token } = req.body;
163
- if (!token) return res.status(400).json({ error: 'Token required' });
164
- const decoded = jwt.decode(token);
165
- if (!decoded) return res.status(401).json({ error: 'Malformed token' });
166
- const secret = await getSessionSecret(decoded.uid, decoded.projectId);
167
- if (!secret) return res.status(404).json({ error: 'Session revoked' });
168
- try {
169
- jwt.verify(token, secret);
170
- const targetUrl = EXTERNAL_SERVER_URL.replace(/\/$/, '') + '/project/ping';
171
- const response = await axios.post(targetUrl, { projectId: decoded.projectId, userId: decoded.uid });
172
- return res.json(response.data);
173
- } catch (err) { return res.status(403).json({ error: 'Invalid Token' }); }
174
  });
175
- */
176
 
177
- app.post('/poll', async (req, res) => {
178
- const { token } = req.body;
179
- if (!token) return res.status(400).json({ error: 'Token required' });
180
-
181
- const decoded = jwt.decode(token);
182
- if (!decoded) return res.status(401).json({ error: 'Malformed token' });
183
-
184
- const secret = await getSessionSecret(decoded.uid, decoded.projectId);
185
- if (!secret) return res.status(404).json({ error: 'Session revoked' });
186
-
187
- try {
188
- jwt.verify(token, secret);
189
- const targetUrl = EXTERNAL_SERVER_URL.replace(/\/$/, '') + '/project/ping';
190
-
191
- // FIX: We do NOT send isFrontend: true.
192
- // We act as the Plugin (Executor), but the Main Server will still give us the snapshot now.
193
- const response = await axios.post(targetUrl, {
194
- projectId: decoded.projectId,
195
- userId: decoded.uid
196
- });
197
-
198
- return res.json(response.data);
199
- } catch (err) { return res.status(403).json({ error: 'Invalid Token' }); }
200
- });
201
 
 
 
 
202
 
203
- app.post('/project/delete', verifySupabaseUser, async (req, res) => {
204
- const { projectId } = req.body;
205
- const userId = req.user.id;
206
- if (!projectId) return res.status(400).json({ error: "Missing Project ID" });
207
- try {
208
- const { data: project, error: fetchError } = await supabase.from('projects').select('user_id').eq('id', projectId).single();
209
- if (fetchError || !project || project.user_id !== userId) return res.status(403).json({ error: "Unauthorized" });
210
- await supabase.from('message_chunks').delete().eq('project_id', projectId);
211
- await supabase.from('projects').delete().eq('id', projectId);
212
- if (STORAGE_BUCKET) {
213
- const { data: files } = await supabase.storage.from(STORAGE_BUCKET).list(projectId);
214
- if (files && files.length > 0) { const filesToRemove = files.map(f => `${projectId}/${f.name}`); await supabase.storage.from(STORAGE_BUCKET).remove(filesToRemove); }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
215
  }
216
- activeSessions.delete(`${userId}:${projectId}`);
217
- for (const [key, val] of tempKeys.entries()) { if (val.projectId === projectId) tempKeys.delete(key); }
218
- res.json({ success: true });
219
- } catch (err) { res.status(500).json({ error: "Delete failed" }); }
220
- });
221
 
222
- app.get('/cleanup', (req, res) => {
223
- // ... (Standard cleanup) ...
224
- const THRESHOLD = 1000 * 60 * 60;
225
- const now = Date.now();
226
- let cleanedCount = 0;
227
- for (const [key, value] of activeSessions.entries()) { if (now - value.lastAccessed > THRESHOLD) { activeSessions.delete(key); cleanedCount++; } }
228
- for (const [key, value] of tempKeys.entries()) { if (now - value.createdAt > (1000 * 60 * 4)) { tempKeys.delete(key); } }
229
- res.json({ message: `Cleaned ${cleanedCount} cached sessions from memory.` });
230
  });
231
 
232
- app.post('/nullify', verifySupabaseUser, async (req, res) => {
233
- const { projectId } = req.body;
234
- if (!projectId) return res.status(400).json({ error: 'projectId required' });
235
- const cacheKey = `${req.user.id}:${projectId}`;
236
- activeSessions.delete(cacheKey);
237
- if (supabase) await supabase.from('projects').update({ plugin_secret: null }).eq('id', projectId).eq('user_id', req.user.id);
238
- res.json({ success: true });
239
- });
240
 
241
- app.get('/', (req, res) => { res.send('Plugin Auth Proxy Running (Supabase Edition)'); });
242
 
243
- app.listen(PORT, () => { console.log(`🚀 Auth Proxy running on port ${PORT}`); });
 
 
 
1
+ import express from 'express';
2
+ import { createServer } from 'http';
3
+ import { WebSocketServer, WebSocket } from 'ws';
4
+ import cors from 'cors';
5
+ import { createClient } from '@supabase/supabase-js';
 
 
6
 
7
+ // --- CONFIG ---
8
+ const PORT = 7860;
9
+ const SUPABASE_URL = process.env.SUPABASE_URL;
10
+ const SUPABASE_KEY = process.env.SUPABASE_SERVICE_KEY;
11
+ const CORE_URL = process.env.CORE_URL; // e.g. https://my-core-server.hf.space
12
+
13
+ if (!SUPABASE_URL) { console.error("❌ Missing Config"); process.exit(1); }
14
+
15
+ // --- STATE ---
16
  const app = express();
17
+ const server = createServer(app);
18
+ const wss = new WebSocketServer({ noServer: true });
19
+ const supabase = createClient(SUPABASE_URL, SUPABASE_KEY);
20
+
21
+ // Active Connections: Map<UserId, Set<WebSocket>>
22
+ const clients = new Map();
23
+
24
  app.use(cors());
25
+ app.use(express.json());
26
+
27
+ // --- HTTP ENDPOINTS (Webhooks/Uploads) ---
28
+
29
+ app.get('/', (req, res) => res.send('Front Gateway Active'));
30
+
31
+ // Internal Endpoint for Core to push notifications to desktop
32
+ app.post('/internal/notify', (req, res) => {
33
+ const { user_id, type, message, payload } = req.body;
34
+
35
+ if (clients.has(user_id)) {
36
+ const userSockets = clients.get(user_id);
37
+ let count = 0;
38
+ userSockets.forEach(ws => {
39
+ if (ws.readyState === WebSocket.OPEN) {
40
+ ws.send(JSON.stringify({ type, message, payload }));
41
+ count++;
 
 
 
 
42
  }
43
  });
44
+ return res.json({ success: true, delivered_to: count });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
45
  }
46
+
47
+ res.json({ success: false, reason: "User offline" });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
48
  });
49
 
50
+ // Endpoint for File Uploads (Base64 is easier for single script than Multer)
51
+ app.post('/upload', async (req, res) => {
52
+ // In a real app, handle streams. Here we assume small/medium files.
53
+ // Client sends this to avoid clogging WS.
54
+ res.json({ status: "ok", note: "File received (Placeholder logic)" });
 
 
 
55
  });
56
 
57
+ // --- WEBSOCKET AUTH & UPGRADE ---
 
 
 
 
 
 
 
 
 
 
 
 
 
58
 
59
+ server.on('upgrade', async (request, socket, head) => {
60
+ const url = new URL(request.url, `http://${request.headers.host}`);
61
+ const token = url.searchParams.get('token');
 
 
 
 
 
 
 
62
 
63
+ if (!token) {
64
+ socket.write('HTTP/1.1 401 Unauthorized\r\n\r\n');
65
+ socket.destroy();
66
+ return;
67
+ }
68
 
69
+ // Validate with Supabase
70
+ const { data: { user }, error } = await supabase.auth.getUser(token);
 
 
71
 
72
+ if (error || !user) {
73
+ console.log("❌ WS Auth Failed");
74
+ socket.write('HTTP/1.1 403 Forbidden\r\n\r\n');
75
+ socket.destroy();
76
+ return;
 
 
 
 
 
 
77
  }
 
78
 
79
+ wss.handleUpgrade(request, socket, head, (ws) => {
80
+ wss.emit('connection', ws, request, user);
81
+ });
 
 
 
 
 
 
 
 
 
 
 
82
  });
 
83
 
84
+ // --- WEBSOCKET LOGIC ---
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
85
 
86
+ wss.on('connection', (ws, req, user) => {
87
+ const userId = user.id;
88
+ console.log(`🔌 Connected: ${userId}`);
89
 
90
+ // Register
91
+ if (!clients.has(userId)) clients.set(userId, new Set());
92
+ clients.get(userId).add(ws);
93
+
94
+ // Heartbeat (Keep-Alive for HF Spaces)
95
+ ws.isAlive = true;
96
+ ws.on('pong', () => { ws.isAlive = true; });
97
+
98
+ ws.on('message', async (message) => {
99
+ try {
100
+ const data = JSON.parse(message.toString());
101
+
102
+ // 1. ROUTING: PROMPT -> CORE
103
+ if (data.type === 'prompt') {
104
+ ws.send(JSON.stringify({ type: 'status', status: 'thinking' }));
105
+
106
+ if (!CORE_URL) {
107
+ ws.send(JSON.stringify({ type: 'error', message: "Core Server Config Missing" }));
108
+ return;
109
+ }
110
+
111
+ // Call Core
112
+ const response = await fetch(`${CORE_URL}/process`, {
113
+ method: 'POST',
114
+ headers: { 'Content-Type': 'application/json' },
115
+ body: JSON.stringify({
116
+ userId: userId,
117
+ projectId: data.projectId,
118
+ prompt: data.content,
119
+ context: data.context
120
+ })
121
+ });
122
+
123
+ const result = await response.json();
124
+
125
+ // Send Result to Client
126
+ ws.send(JSON.stringify({
127
+ type: 'response',
128
+ text: result.text,
129
+ should_reload: result.should_reload
130
+ }));
131
+ }
132
+
133
+ } catch (e) {
134
+ console.error("WS Message Error", e);
135
+ ws.send(JSON.stringify({ type: 'error', message: "Server Error" }));
136
  }
137
+ });
 
 
 
 
138
 
139
+ ws.on('close', () => {
140
+ if (clients.has(userId)) {
141
+ clients.get(userId).delete(ws);
142
+ if (clients.get(userId).size === 0) clients.delete(userId);
143
+ }
144
+ console.log(`❌ Disconnected: ${userId}`);
145
+ });
 
146
  });
147
 
148
+ // Heartbeat Interval (30s)
149
+ const interval = setInterval(() => {
150
+ wss.clients.forEach((ws) => {
151
+ if (ws.isAlive === false) return ws.terminate();
152
+ ws.isAlive = false;
153
+ ws.ping();
154
+ });
155
+ }, 30000);
156
 
157
+ wss.on('close', () => clearInterval(interval));
158
 
159
+ server.listen(PORT, () => {
160
+ console.log(`🚀 Front Gateway running on port ${PORT}`);
161
+ });