Skip to content

sourcey/symple-server

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

44 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Symple Server

Realtime messaging and presence server over native WebSocket.

Part of the Symple ecosystem:

Features

  • Presence - peer online/offline status broadcasting
  • Scoped messaging - direct (peer-to-peer), user-level, room-level, or global broadcast
  • Team/group permissions - peers only see and message others in their assigned rooms
  • Dynamic rooms - clients can optionally join and leave rooms at runtime
  • Token authentication - session validation via Redis (or anonymous mode)
  • Server-side emission - push messages from Ruby/Rails via Redis pub/sub
  • SSL/TLS - optional HTTPS/WSS support

Quick Start

git clone https://github.com/sourcey/symple-server.git
cd symple-server
npm install
npm start

The server listens on port 4500 by default. No Redis required - it runs in single-instance mode out of the box.

Configuration

All configuration is via environment variables (loaded from .env via dotenv). Copy .env.example to .env to get started.

Variable Default Description
PORT 4500 Port to listen on (also SYMPLE_PORT)
SYMPLE_SESSION_TTL -1 Session TTL in minutes (-1 = no expiry)
SYMPLE_AUTHENTICATION false Require token auth (needs Redis)
SYMPLE_DYNAMIC_ROOMS true Allow clients to join/leave rooms
SYMPLE_REDIS_URL - Redis connection URL (enables Redis features)
SYMPLE_REDIS_HOST - Redis host (alternative to URL)
SYMPLE_REDIS_PORT - Redis port (alternative to URL)
SYMPLE_SSL_ENABLED false Enable HTTPS/WSS
SYMPLE_SSL_KEY - Path to SSL key file
SYMPLE_SSL_CERT - Path to SSL certificate file

Redis

Redis is optional. Without it, the server runs in single-instance mode with in-memory state. Set SYMPLE_REDIS_URL to enable:

  • Token authentication - session lookup at symple:session:<token>
  • Server-side emission - push messages from Ruby/Rails via symple-client-ruby

Authentication

When SYMPLE_AUTHENTICATION=true, clients must provide user and token in the auth message (the first WebSocket message after connection). The server looks up the session in Redis at symple:session:<token> and merges it with the auth data.

When SYMPLE_AUTHENTICATION=false (default), clients only need to provide user.

Teams and Permissions

Rooms are the permission boundary. A peer can only see presence from and send direct messages to peers that share at least one room. Every peer is auto-joined to their own user room on authentication.

There are three ways to assign rooms:

1. Via Redis session (recommended for web apps)

When your backend creates a session, include a rooms array. The server auto-joins the peer on authentication:

# Rails: on login, create a Symple session with team memberships
Symple.session.set(api_token.token, {
  user: user.username,
  rooms: user.teams.pluck(:slug)  # ["team-a", "design", "project-42"]
}, ttl: 1.week.to_i)

The client connects with the token; the server looks up the session, finds the rooms, and auto-joins:

const client = new SympleClient({
  url: 'wss://your-server.com',
  token: 'the-api-token',
  peer: { user: 'alice', name: 'Alice' }
})

2. Via auth message (for simple setups)

The client can include rooms directly in the auth message. This only works when SYMPLE_AUTHENTICATION=false (no token validation), so the client is trusted:

// Client sends: { type: "auth", user: "alice", rooms: ["lobby", "vip"] }

3. Via dynamic rooms (for open systems)

With SYMPLE_DYNAMIC_ROOMS=true (default), clients can join and leave rooms freely after authentication. This gives no permission scoping; any peer can join any room. Set SYMPLE_DYNAMIC_ROOMS=false to lock rooms to server-assigned only.

Permission model

Scenario Behavior
Alice in ["team-a"], Bob in ["team-a"] Can see each other's presence, can DM
Alice in ["team-a"], Bob in ["team-b"] Invisible to each other, DMs blocked
Alice in ["team-a", "design"], Bob in ["design"] Can see each other via design room
Broadcast to room "team-a" Only reaches peers in team-a

The welcome message includes the full room list so the client knows its memberships:

{ "type": "welcome", "protocol": "symple/4", "status": 200, "peer": {...}, "rooms": ["alice", "team-a", "design"] }

Message Routing

Messages are routed based on the to field:

to value Behavior
Undefined Broadcast to all sender's rooms (excluding sender)
"user|id" Direct message to a specific peer (must share a room)
"user" Broadcast to the user's room
["room1", "room2"] Broadcast to multiple rooms

Programmatic Usage

const Symple = require('./lib/symple');
const { createConfig } = require('./config');

const config = createConfig();
const server = new Symple(config);

// Custom authentication with room assignment (supports async)
server.authenticate = async (peer, auth) => {
  const user = await db.users.findByToken(auth.token);
  if (!user) return { allowed: false };
  return {
    allowed: true,
    rooms: user.teams  // ["team-a", "design"]
  };
};

// Post-auth hook (peer is already registered)
server.onAuthorize = function(ws, peer) {
  console.log('Peer connected:', peer.name);
};

server.init();

C++ Server

A C++ implementation with the same protocol is available in libsourcey.

Debug Logging

Enable debug output with:

DEBUG=symple:* npm start

More Information

For more details, visit sourcey.com/code/symple.

License

MIT

About

Symple real time messaging and presence server using Node.js, Socket.IO and Redis

Resources

Stars

Watchers

Forks

Packages

 
 
 

Contributors