Real-time messaging hub powered by FeathersJS channels and Socket.io — direct messages, broadcasts, private rooms, user lists, history, and optional MongoDB persistence.
Live Demo: https://waelio-messaging.onrender.com/
waelio-messaging gives you:
- a real-time Socket.io server built on FeathersJS channels (no REST)
- a ready-to-use chat UI (
public/index.html) - direct message + broadcast + private rooms + message history
- optional MongoDB persistence (falls back to in-memory automatically)
The original MessagingHub (raw ws) is kept as a standalone library export. The server now uses a Feathers app layered on top, replacing the hand-rolled broadcast loops with declarative channel routing.
If you just want to run and chat locally, use the 5-minute guide below.
npm installnpm run devOpen your browser at:
Open 2 browser tabs to the same URL and:
- send a direct message
- click Broadcast for all users
- click History to load previous messages
- Users Online (left): pick a user for direct messages
- Send: sends to selected user
- Broadcast: sends to everyone
- History: loads saved/in-memory message history
- Room Start/Leave: optional focused room messaging
- Install (when shown): install the PWA
When the tab is in background and a new message arrives, the app shows:
- unread count in the tab title
- a favicon badge
- pulsing activity dot + count in the header
The indicator clears when you return to the tab/window.
The bundled UI under public/ is a Progressive Web App:
- Installable on desktop and mobile (manifest + service worker)
- Basic offline support (core assets cached, navigation fallback)
- Install prompt via
beforeinstallprompt
- Ensure server is running (
npm run dev) - Ensure port
8080is free
- Make sure both tabs use the same server URL
- Hard refresh both tabs after restarting server
- Set
MONGO_URIto a valid Mongo connection string - Without
MONGO_URI, app uses in-memory storage by design
If you want to use this as a library in your own project:
npm install @waelio/messagingnpm run dev # start in dev (ts-node)
npm run build # compile TypeScript
npm start # run compiled server- Page Visibility API (
document.hidden,visibilitychange) - Window focus event (
window.addEventListener('focus', ...)) - Document title updates (
document.title) - Favicon updates via
<link rel="icon">+ Canvas API - PWA install events (
beforeinstallprompt,appinstalled)
import http from "http";
import express from "express";
import { createFeathersApp } from "./src/feathers/app.js";
const app = express();
const server = http.createServer(app);
// Socket.io + Feathers channels, no REST
// Optional: pass a MongoDB URI as second argument
await createFeathersApp(server, process.env.MONGO_URI);
server.listen(8080, () => console.log("ready"));If you prefer the original raw-ws hub as a library:
import http from "http";
import express from "express";
import { MessagingHub } from "@waelio/messaging";
const app = express();
const server = http.createServer(app);
const hub = new MessagingHub(server, { mongoURI: process.env.MONGO_URI });
await hub.ready;
server.listen(8080, () => console.log("ready"));Auto-send on connect:
<waelio-message target="USER_ID" message="hello"></waelio-message>Broadcast:
<waelio-message message="hello everyone" broadcast></waelio-message>Manual:
<waelio-message id="msg" send-on="manual" target="USER_ID"></waelio-message>
<script>
msg.addEventListener("connected", () => msg.send("hi again"));
</script>Attributes: target, message, broadcast, ws-url, send-on (connect|manual|click), reconnect. Events: connected, disconnected, sent, error.
src/
feathers/
app.ts ← Feathers app, Socket.io transport (no REST)
channels.ts ← Declarative channel routing rules
services/
messages.ts ← create (send) + find (history)
rooms.ts ← create (join private room)
MessagingHub.ts ← Original raw-ws hub (library export, unchanged)
server.ts ← HTTP server entry point
public/
index.html ← p5.js canvas UI using socket.io-client
| Scenario | Channel used |
|---|---|
| Direct message | direct/<recipientId> |
| Broadcast | all (filtered to exclude sender) |
| Room message | rooms/<roomId> (filtered to exclude sender) |
| Room join notification | direct/<userId> + direct/<partnerId> |
Each connected socket is placed in all and direct/<id> on connect. When two clients join a room their sockets are also added to rooms/<roomId>. Feathers service.publish() in channels.ts decides which channel receives each service event — no manual looping required.
Old ws message type |
New Feathers call |
|---|---|
route |
socket.emit('messages::create', { type:'route', to, payload }) |
broadcast |
socket.emit('messages::create', { type:'broadcast', payload }) |
get-history |
socket.emit('messages::find', {}, callback) |
join-room |
socket.emit('rooms::create', { with: partnerId }) |
room-message |
socket.emit('messages::create', { type:'room-message', payload }) |
start-typing |
socket.emit('start-typing') |
stop-typing |
socket.emit('stop-typing') |
register-success{ id }— your assigned client IDuser-list{ users[] }— full list of connected IDsuser-joined{ id, ts }/user-left{ id, ts }user-typing{ id }/user-stopped-typing{ id }messages created{ senderId, recipientId, payload, isBroadcast, roomId, timestamp }— Feathers service eventrooms created{ roomId, userId, partnerId }— Feathers service event
Pass a MongoDB URI to createFeathersApp (or MessagingHub) to persist messages across restarts. Without it, an in-memory store is used automatically (last 100 messages).
await createFeathersApp(server, process.env.MONGO_URI);Patch / Minor / Major then publish:
npm run release:patch && npm run release:publish
npm run release:minor && npm run release:publish
npm run release:major && npm run release:publishThis project is licensed under the MIT License - see the LICENSE file for details.