Remote connection
This commit is contained in:
BIN
server/data/metoyou.sqlite
Normal file
BIN
server/data/metoyou.sqlite
Normal file
Binary file not shown.
@@ -23,5 +23,17 @@
|
||||
"tags": [],
|
||||
"createdAt": 1766902260144,
|
||||
"lastSeen": 1766902260144
|
||||
},
|
||||
{
|
||||
"id": "337ad599-736e-49c6-bf01-fb94c1b82a6d",
|
||||
"name": "ASDASD",
|
||||
"ownerId": "54c0953a-1e54-4c07-8da9-06c143d9354f",
|
||||
"ownerPublicKey": "54c0953a-1e54-4c07-8da9-06c143d9354f",
|
||||
"isPrivate": false,
|
||||
"maxUsers": 50,
|
||||
"currentUsers": 0,
|
||||
"tags": [],
|
||||
"createdAt": 1767240654523,
|
||||
"lastSeen": 1767240654523
|
||||
}
|
||||
]
|
||||
@@ -19,6 +19,7 @@
|
||||
"@types/cors": "^2.8.14",
|
||||
"@types/express": "^4.17.18",
|
||||
"@types/node": "^20.8.0",
|
||||
"@types/sql.js": "^1.4.9",
|
||||
"@types/uuid": "^9.0.4",
|
||||
"@types/ws": "^8.5.8",
|
||||
"ts-node-dev": "^2.0.0",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import initSqlJs, { Database, Statement } from 'sql.js';
|
||||
import initSqlJs from 'sql.js';
|
||||
|
||||
// Simple SQLite via sql.js persisted to a single file
|
||||
const DATA_DIR = path.join(process.cwd(), 'data');
|
||||
@@ -11,7 +11,7 @@ function ensureDataDir() {
|
||||
}
|
||||
|
||||
let SQL: any = null;
|
||||
let db: Database | null = null;
|
||||
let db: any | null = null;
|
||||
|
||||
export async function initDB(): Promise<void> {
|
||||
if (db) return;
|
||||
@@ -56,7 +56,7 @@ export interface AuthUser {
|
||||
|
||||
export async function getUserByUsername(username: string): Promise<AuthUser | null> {
|
||||
if (!db) await initDB();
|
||||
const stmt: Statement = db!.prepare('SELECT id, username, passwordHash, displayName, createdAt FROM users WHERE username = ? LIMIT 1');
|
||||
const stmt: any = db!.prepare('SELECT id, username, passwordHash, displayName, createdAt FROM users WHERE username = ? LIMIT 1');
|
||||
stmt.bind([username]);
|
||||
let row: AuthUser | null = null;
|
||||
if (stmt.step()) {
|
||||
@@ -75,7 +75,7 @@ export async function getUserByUsername(username: string): Promise<AuthUser | nu
|
||||
|
||||
export async function getUserById(id: string): Promise<AuthUser | null> {
|
||||
if (!db) await initDB();
|
||||
const stmt: Statement = db!.prepare('SELECT id, username, passwordHash, displayName, createdAt FROM users WHERE id = ? LIMIT 1');
|
||||
const stmt: any = db!.prepare('SELECT id, username, passwordHash, displayName, createdAt FROM users WHERE id = ? LIMIT 1');
|
||||
stmt.bind([id]);
|
||||
let row: AuthUser | null = null;
|
||||
if (stmt.step()) {
|
||||
|
||||
@@ -88,6 +88,46 @@ app.get('/api/time', (req, res) => {
|
||||
res.json({ now: Date.now() });
|
||||
});
|
||||
|
||||
// Image proxy to allow rendering external images within CSP (img-src 'self' data: blob:)
|
||||
app.get('/api/image-proxy', async (req, res) => {
|
||||
try {
|
||||
const url = String(req.query.url || '');
|
||||
if (!/^https?:\/\//i.test(url)) {
|
||||
return res.status(400).json({ error: 'Invalid URL' });
|
||||
}
|
||||
|
||||
const controller = new AbortController();
|
||||
const timeout = setTimeout(() => controller.abort(), 8000);
|
||||
const response = await fetch(url, { redirect: 'follow', signal: controller.signal });
|
||||
clearTimeout(timeout);
|
||||
|
||||
if (!response.ok) {
|
||||
return res.status(response.status).end();
|
||||
}
|
||||
|
||||
const contentType = response.headers.get('content-type') || '';
|
||||
if (!contentType.toLowerCase().startsWith('image/')) {
|
||||
return res.status(415).json({ error: 'Unsupported content type' });
|
||||
}
|
||||
|
||||
const arrayBuffer = await response.arrayBuffer();
|
||||
const MAX_BYTES = 8 * 1024 * 1024; // 8MB limit
|
||||
if (arrayBuffer.byteLength > MAX_BYTES) {
|
||||
return res.status(413).json({ error: 'Image too large' });
|
||||
}
|
||||
|
||||
res.setHeader('Content-Type', contentType);
|
||||
res.setHeader('Cache-Control', 'public, max-age=3600');
|
||||
res.send(Buffer.from(arrayBuffer));
|
||||
} catch (err) {
|
||||
if ((err as any)?.name === 'AbortError') {
|
||||
return res.status(504).json({ error: 'Timeout fetching image' });
|
||||
}
|
||||
console.error('Image proxy error:', err);
|
||||
res.status(502).json({ error: 'Failed to fetch image' });
|
||||
}
|
||||
});
|
||||
|
||||
// Basic auth (demo - file-based)
|
||||
interface AuthUser { id: string; username: string; passwordHash: string; displayName: string; createdAt: number; }
|
||||
let authUsers: AuthUser[] = [];
|
||||
@@ -320,33 +360,33 @@ const server = createServer(app);
|
||||
const wss = new WebSocketServer({ server });
|
||||
|
||||
wss.on('connection', (ws: WebSocket) => {
|
||||
const oderId = uuidv4();
|
||||
connectedUsers.set(oderId, { oderId, ws });
|
||||
const connectionId = uuidv4();
|
||||
connectedUsers.set(connectionId, { oderId: connectionId, ws });
|
||||
|
||||
ws.on('message', (data) => {
|
||||
try {
|
||||
const message = JSON.parse(data.toString());
|
||||
handleWebSocketMessage(oderId, message);
|
||||
handleWebSocketMessage(connectionId, message);
|
||||
} catch (err) {
|
||||
console.error('Invalid WebSocket message:', err);
|
||||
}
|
||||
});
|
||||
|
||||
ws.on('close', () => {
|
||||
const user = connectedUsers.get(oderId);
|
||||
const user = connectedUsers.get(connectionId);
|
||||
if (user?.serverId) {
|
||||
// Notify others in the room
|
||||
// Notify others in the room - use user.oderId (the actual user ID), not connectionId
|
||||
broadcastToServer(user.serverId, {
|
||||
type: 'user_left',
|
||||
oderId,
|
||||
oderId: user.oderId,
|
||||
displayName: user.displayName,
|
||||
}, oderId);
|
||||
}, user.oderId);
|
||||
}
|
||||
connectedUsers.delete(oderId);
|
||||
connectedUsers.delete(connectionId);
|
||||
});
|
||||
|
||||
// Send connection acknowledgment
|
||||
ws.send(JSON.stringify({ type: 'connected', oderId, serverTime: Date.now() }));
|
||||
// Send connection acknowledgment with the connectionId (client will identify with their actual oderId)
|
||||
ws.send(JSON.stringify({ type: 'connected', connectionId, serverTime: Date.now() }));
|
||||
});
|
||||
|
||||
function handleWebSocketMessage(connectionId: string, message: any): void {
|
||||
@@ -366,14 +406,15 @@ function handleWebSocketMessage(connectionId: string, message: any): void {
|
||||
case 'join_server':
|
||||
user.serverId = message.serverId;
|
||||
connectedUsers.set(connectionId, user);
|
||||
console.log(`User ${user.displayName} (${user.oderId}) joined server ${message.serverId}`);
|
||||
console.log(`User ${user.displayName || 'Anonymous'} (${user.oderId}) joined server ${message.serverId}`);
|
||||
|
||||
// Get list of current users in server (exclude this user by oderId)
|
||||
// Only include users that have been identified (have displayName)
|
||||
const usersInServer = Array.from(connectedUsers.values())
|
||||
.filter(u => u.serverId === message.serverId && u.oderId !== user.oderId)
|
||||
.map(u => ({ oderId: u.oderId, displayName: u.displayName }));
|
||||
.filter(u => u.serverId === message.serverId && u.oderId !== user.oderId && u.displayName)
|
||||
.map(u => ({ oderId: u.oderId, displayName: u.displayName || 'Anonymous' }));
|
||||
|
||||
console.log(`Sending server_users to ${user.displayName}:`, usersInServer);
|
||||
console.log(`Sending server_users to ${user.displayName || 'Anonymous'}:`, usersInServer);
|
||||
user.ws.send(JSON.stringify({
|
||||
type: 'server_users',
|
||||
users: usersInServer,
|
||||
@@ -383,7 +424,7 @@ function handleWebSocketMessage(connectionId: string, message: any): void {
|
||||
broadcastToServer(message.serverId, {
|
||||
type: 'user_joined',
|
||||
oderId: user.oderId,
|
||||
displayName: user.displayName,
|
||||
displayName: user.displayName || 'Anonymous',
|
||||
}, user.oderId);
|
||||
break;
|
||||
|
||||
@@ -396,7 +437,7 @@ function handleWebSocketMessage(connectionId: string, message: any): void {
|
||||
broadcastToServer(oldServerId, {
|
||||
type: 'user_left',
|
||||
oderId: user.oderId,
|
||||
displayName: user.displayName,
|
||||
displayName: user.displayName || 'Anonymous',
|
||||
}, user.oderId);
|
||||
}
|
||||
break;
|
||||
|
||||
1
server/src/types/sqljs.d.ts
vendored
1
server/src/types/sqljs.d.ts
vendored
@@ -1,3 +1,4 @@
|
||||
declare module 'sql.js';
|
||||
declare module 'sql.js' {
|
||||
export default function initSqlJs(config?: { locateFile?: (file: string) => string }): Promise<any>;
|
||||
export type Database = any;
|
||||
|
||||
Reference in New Issue
Block a user