Alpha

Sockethub 5.0.0-alpha.12 is the current active-use release. See the changelog.

Get started

Get started

Pick whichever install path fits how you work. The client code is the same either way — one Socket.IO connection, one ActivityStreams 2.0 envelope per action.

Install

Repository →
npm fastest way to try it

Requires Node.js 20+ and a Redis instance reachable at localhost:6379:

# install the current release channel
npm install -g sockethub@alpha

# start the gateway
sockethub

# or explore the bundled examples
sockethub --examples

Running version: 5.0.0-alpha.12

Docker for deployment

Bring up Sockethub + Redis with a single compose file:

# docker-compose.yml
services:
  redis:
    image: redis:7-alpine
  sockethub:
    image: sockethub/sockethub:alpha
    ports: ["10550:10550"]
    environment:
      REDIS_URL: redis://redis:6379
    depends_on: [redis]

docker compose up — Sockethub is live on http://localhost:10550.

Source hack on it

Clone the monorepo and run with Bun:

git clone https://github.com/sockethub/sockethub.git
cd sockethub
bun install
bun run dev

This runs the full workspace with live reload. Changes in any package are picked up by the running gateway.

Talk to it from the browser

All platforms →

The client is a thin wrapper over Socket.IO. Point it at your gateway, wait for the schema registry, then credentials → connect → join → send. Every call is the same JSON-LD ActivityStreams 2.0 shape.

This example sends a single message to #sockethub on Libera.Chat. Swap sc.contextFor('irc') for sc.contextFor('xmpp') and the same code reaches an XMPP contact (for 1:1 you can skip the join).

Every emit returns an ack. We wrap that as a promise so each step either resolves on success or throws — no silent failures, no guessing at state.

// Load @sockethub/client (ESM or bundled UMD)
import SockethubClient from '@sockethub/client';

const sc = new SockethubClient({
  host: 'http://localhost:10550',
});

// wait for the schema registry to load
await sc.ready();

const ircContext = sc.contextFor('irc');
const actor = { id: 'alice@irc.libera.chat', type: 'person' };
const room  = { id: '#sockethub@irc.libera.chat', type: 'room' };

// turn emit+ack into an awaitable step
const step = (event, activity) => new Promise((resolve, reject) => {
  sc.socket.emit(event, activity, (ack) => {
    if (ack?.error) reject(new Error(ack.error));
    else            resolve(ack);
  });
});

try {
  // 1. credentials (stored encrypted per session)
  await step('credentials', {
    '@context': ircContext,
    type:   'credentials',
    actor,
    object: {
      type:   'credentials',
      nick:   'alice',
      server: 'irc.libera.chat',
      port:   6697,
      secure: true,
      token:  process.env.IRC_TOKEN,
    },
  });
  console.log('creds stored');

  // 2. connect
  await step('message', { '@context': ircContext, type: 'connect', actor });
  console.log('connected');

  // 3. join (required on IRC + XMPP MUC; skip for XMPP 1:1)
  await step('message', { '@context': ircContext, type: 'join', actor, target: room });
  console.log('joined', room.id);

  // 4. send
  const ack = await step('message', {
    '@context': ircContext,
    type:   'send',
    actor,
    target: room,
    object: { type: 'message', content: 'hello from the browser' },
  });
  console.log('sent', ack);
} catch (err) {
  console.error('sockethub:', err.message);
}

Looking for the deeper architecture? How it works Source on GitHub