Skip to content

Add a relay server support #442

Description

@fttx

Here’s a secure Node.js-based relay server that does the following:

  • Allows desktop servers (behind NAT) to register.
  • Allows mobile clients to connect via WebSocket using a QR code-based pairing system.
  • Forwards messages securely only between matched client-server pairs.
  • Supports LAN fallback (users can skip relay and connect directly in trusted networks).

🔐 Pairing Strategy (QR-based)

  • When the desktop app (server) starts, it generates a unique pairing code (UUID or short code).
  • It opens a WebSocket to the relay and registers itself with that code.
  • The mobile app (client) scans a QR code from the desktop and uses the same code to connect to the relay.
  • The relay pairs the two sockets and relays messages securely between them.

🧠 Tech Stack

  • Node.js
  • ws for WebSocket
  • uuid for secure unique IDs

📦 Dependencies

npm install ws uuid

🌐 relay-server.js

const WebSocket = require('ws');
const { v4: uuidv4 } = require('uuid');

const wss = new WebSocket.Server({ port: 8080 });
console.log("Relay server listening on port 8080");

const servers = new Map();  // pairingCode -> ws
const clients = new Map();  // clientWs -> serverWs

wss.on('connection', (ws) => {
  ws.isAlive = true;

  ws.on('pong', () => ws.isAlive = true);

  ws.on('message', (msg) => {
    try {
      const data = JSON.parse(msg);

      if (data.type === 'register-server') {
        // Register a server with a unique pairing code
        const code = data.code || uuidv4();  // can be random short string
        ws.role = 'server';
        ws.pairingCode = code;
        servers.set(code, ws);
        ws.send(JSON.stringify({ type: 'registered', code }));
        console.log(`Server registered with code: ${code}`);

      } else if (data.type === 'connect-client') {
        const serverWs = servers.get(data.code);
        if (!serverWs) {
          ws.send(JSON.stringify({ type: 'error', message: 'Invalid code' }));
          return;
        }
        ws.role = 'client';
        ws.serverWs = serverWs;
        serverWs.clientWs = ws;
        clients.set(ws, serverWs);
        clients.set(serverWs, ws);
        ws.send(JSON.stringify({ type: 'connected' }));
        serverWs.send(JSON.stringify({ type: 'client-connected' }));
        console.log(`Client connected to server with code: ${data.code}`);

      } else if (data.type === 'message') {
        // Relay message to paired client/server
        const target = clients.get(ws);
        if (target) {
          target.send(JSON.stringify({ type: 'message', payload: data.payload }));
        }
      }

    } catch (err) {
      console.error("Error parsing message", err);
    }
  });

  ws.on('close', () => {
    if (ws.role === 'server') {
      servers.delete(ws.pairingCode);
      if (ws.clientWs) {
        ws.clientWs.close();
        clients.delete(ws.clientWs);
      }
    } else if (ws.role === 'client' && ws.serverWs) {
      ws.serverWs.send(JSON.stringify({ type: 'client-disconnected' }));
      ws.serverWs.clientWs = null;
      clients.delete(ws.serverWs);
    }
    clients.delete(ws);
  });
});

// Optional: ping-pong to clean up dead sockets
setInterval(() => {
  wss.clients.forEach(ws => {
    if (!ws.isAlive) return ws.terminate();
    ws.isAlive = false;
    ws.ping();
  });
}, 10000);

🖥️ Desktop App (server) Example

const ws = new WebSocket('wss://yourrelayserver.com');

ws.onopen = () => {
  const code = 'ABCD1234'; // generate or reuse
  ws.send(JSON.stringify({ type: 'register-server', code }));
};

ws.onmessage = (msg) => {
  const data = JSON.parse(msg.data);
  if (data.type === 'client-connected') {
    console.log('Client connected!');
  } else if (data.type === 'message') {
    console.log('Received from client:', data.payload);
  }
};

// To send message:
function sendToClient(payload) {
  ws.send(JSON.stringify({ type: 'message', payload }));
}

📱 Mobile App (client) Example

const ws = new WebSocket('wss://yourrelayserver.com');

ws.onopen = () => {
  const code = scannedFromQRCode; // user scans QR
  ws.send(JSON.stringify({ type: 'connect-client', code }));
};

ws.onmessage = (msg) => {
  const data = JSON.parse(msg.data);
  if (data.type === 'message') {
    console.log('Received from server:', data.payload);
  }
};

// To send message:
function sendToServer(payload) {
  ws.send(JSON.stringify({ type: 'message', payload }));
}

🧾 Security Recommendations

  1. Use TLS (wss://) — Cloud relay must use HTTPS/WSS only.
  2. Expire pairing codes — Limit to 10 minutes or until first connection.
  3. Add JWT or shared secret — For more secure client-server handshakes.
  4. Rate limit and firewall — Protect against abuse and DDoS.

Would you like:

  • A QR code generator snippet for the desktop app?
  • A React Native or Ionic QR scanner example for the mobile app?
  • A production-ready Docker setup for the relay?

Let me know how far you want to take it and I’ll build it out for you.

https://chatgpt.com/c/67e13278-29f0-8012-a5ae-515186fcc004

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions