STVOR SDK

Signal-style E2EE for your app — minimal API

STVOR provides end-to-end encryption using the Signal Protocol (X3DH + Double Ratchet). Messages are encrypted on the client — the server never sees plaintext.

✅ Good for:
  • Startups & MVPs
  • Internal tools
  • Pilot projects
❌ Not for:
  • Healthcare/Finance
  • Mobile apps
  • Multi-instance prod

Installation

1

Install the SDK

npm install @stvor/sdk
⚠️ Node.js Only (v2.2.x)
This version requires ws for WebSocket transport. Browser support coming in v2.3.
npm install ws

Quick Start (v2.3.1)

The simplest example — connect two users and send an encrypted message:

✨ New in v2.3.1

The SDK now auto-waits for recipients. No more setTimeout hacks!

import { createApp } from '@stvor/sdk';

// Initialize
const app = await createApp({
  appToken: 'stvor_demo_token',
  relayUrl: 'ws://localhost:8080'  // Your relay server
});

// Connect users
const alice = await app.connect('alice@example.com');
const bob = await app.connect('bob@example.com');

// Setup message handler
bob.onMessage((from, msg) => {
  console.log(`📩 ${from}: ${msg}`);
});

// send() now auto-waits for recipient (up to 10s timeout)
await alice.send('bob@example.com', 'Hello Bob! 🔐');
console.log('✅ Message sent!');

Explicit Readiness Control

For more control, use waitForUser() and onUserAvailable():

// Option 1: Explicit wait before sending
await alice.waitForUser('bob@example.com');
await alice.send('bob@example.com', 'Hello!');

// Option 2: React to user availability
alice.onUserAvailable((userId) => {
  console.log(`${userId} is now online and ready for messages`);
});

// Option 3: Check synchronously
if (alice.isUserAvailable('bob@example.com')) {
  await alice.send('bob@example.com', 'Hello!');
}

// Option 4: Skip auto-wait (throws immediately if not available)
await alice.send('bob@example.com', 'Hello!', { waitForRecipient: false });

// Option 5: Custom timeout
await alice.send('bob@example.com', 'Hello!', { timeout: 30000 });

Full Working Example

Copy these files to get E2EE working in 2 minutes:

Step 1: Create relay-server.mjs

⚠️ Demo Transport Only
This relay server is a minimal example for local development. It is NOT the STVOR API — it's a simple WebSocket forwarder to help you test encryption. For production, you'll need proper auth, persistence, and scaling.

The relay server forwards encrypted messages between clients. It cannot decrypt anything.

// relay-server.mjs
import WebSocket, { WebSocketServer } from 'ws';

const PORT = 8080;
const wss = new WebSocketServer({ port: PORT });
console.log(`🔌 Relay running on ws://localhost:${PORT}`);

const clients = new Map();
const pubkeys = new Map();

wss.on('connection', (ws) => {
  // Send all known keys to new client
  for (const [user, pub] of pubkeys.entries()) {
    ws.send(JSON.stringify({ type: 'announce', user, pub }));
  }

  ws.on('message', (data) => {
    let msg;
    try { msg = JSON.parse(data.toString()); } catch { return; }

    if (msg.type === 'announce' && msg.user) {
      clients.set(msg.user, ws);
      pubkeys.set(msg.user, msg.pub);
      // Broadcast to all
      for (const [_, client] of clients) {
        if (client.readyState === WebSocket.OPEN) {
          client.send(JSON.stringify({ 
            type: 'announce', 
            user: msg.user, 
            pub: msg.pub 
          }));
        }
      }
    }

    if (msg.type === 'message' && msg.to) {
      const target = clients.get(msg.to);
      if (target && target.readyState === WebSocket.OPEN) {
        target.send(JSON.stringify(msg));
      }
    }
  });
});

Step 2: Create app.mjs

Your application code with E2EE messaging:

// app.mjs
import { createApp } from '@stvor/sdk';

async function main() {
  const app = await createApp({
    appToken: 'stvor_demo_token',
    relayUrl: 'ws://localhost:8080'
  });

  // Connect Bob (receiver)
  const bob = await app.connect('bob@example.com');
  bob.onMessage((from, msg) => {
    console.log(`📩 [Bob] from ${from}: ${msg}`);
  });

  // Connect Alice (sender)
  const alice = await app.connect('alice@example.com');
  alice.onMessage((from, msg) => {
    console.log(`📩 [Alice] from ${from}: ${msg}`);
  });

  // SDK auto-waits for recipient keys (no setTimeout needed!)
  await alice.send('bob@example.com', 'Hello Bob! This is E2EE! 🔐');
  console.log('✅ Alice sent message');

  // Bob replies
  await bob.send('alice@example.com', 'Hi Alice! Got it! 🎉');
  console.log('✅ Bob sent message');

  // Keep alive to see messages
  await new Promise(r => setTimeout(r, 1000));
}

main().catch(console.error);

Step 3: Create package.json

{
  "name": "my-e2ee-app",
  "type": "module",
  "dependencies": {
    "@stvor/sdk": "^2.3.1",
    "ws": "^8.13.0"
  }
}

Step 4: Run it!

# Terminal 1: Start relay
node relay-server.mjs

# Terminal 2: Run app
npm install
node app.mjs

✅ Expected Output:

✅ Alice sent message
✅ Bob sent message
📩 [Bob] from alice@example.com: Hello Bob! This is E2EE! 🔐
📩 [Alice] from bob@example.com: Hi Alice! Got it! 🎉

Security Model

✅ What STVOR Guarantees

  • End-to-end encryption — plaintext never leaves the device
  • Forward Secrecy — compromise of current keys doesn't expose past messages
  • Post-Compromise Security — after key rotation, attacker loses access
  • Zero-knowledge relay — server cannot decrypt messages
⚠️ TOFU Model: First contact requires user verification. After first contact, strong MITM protection is active. For high-security scenarios, verify fingerprints out-of-band.

Limitations

Known Limitations (v2.2.2)

  • In-memory keys: Lost on app restart
  • Single-instance only: No distributed state
  • No mobile support: Node.js only
  • Demo-level replay protection: In-memory nonce cache
  • No multi-device: Each device = separate identity

Honest Positioning

STVOR is a practical reference implementation. Production-grade crypto (Signal Protocol), but demo-level infrastructure. Perfect for learning, prototypes, and low-risk applications.