import { Hono } from 'hono'; import { ShadeSessionManager, ShadeEventEmitter, } from '../../packages/shade-core/src/index.js'; import { SubtleCryptoProvider, MemoryStorage, } from '../../packages/shade-crypto-web/src/index.js'; import { createPrekeyServer, MemoryPrekeyStore, PrekeyServerEvents, } from '../../packages/shade-server/src/index.js'; import { createObserver } from '../../packages/shade-observer/src/index.js'; const crypto = new SubtleCryptoProvider(); const TOKEN = 'demo-token-must-be-at-least-16-chars'; async function main() { console.log('━━━ Shade Observer Demo ━━━\n'); // ─── Wire up event emitters ────────────────────────── const clientEvents = new ShadeEventEmitter(); const serverEvents = new PrekeyServerEvents(); // ─── Two demo session managers ─────────────────────── const alice = new ShadeSessionManager(crypto, new MemoryStorage(), { events: clientEvents }); const bob = new ShadeSessionManager(crypto, new MemoryStorage(), { events: clientEvents }); await alice.initialize(); await bob.initialize(); // ─── Prekey server with events ─────────────────────── const prekeyApp = createPrekeyServer({ crypto, store: new MemoryPrekeyStore(), disableRateLimit: true, events: serverEvents, }); // ─── Observer ──────────────────────────────────────── const observer = createObserver({ token: TOKEN, clientEvents, serverEvents, }); // ─── Mount everything in one Hono app ──────────────── const app = new Hono(); app.route('/prekey', prekeyApp); app.route('/', observer); const PORT = 3901; Bun.serve({ port: PORT, fetch: app.fetch }); console.log(`✓ Server listening on http://localhost:${PORT}`); console.log(`✓ Dashboard: http://localhost:${PORT}/dashboard/`); console.log(`✓ Token: ${TOKEN}\n`); console.log('Open the dashboard, paste the token, and watch the live activity below…\n'); // ─── Establish Alice ↔ Bob session ─────────────────── await bob.generateOneTimePreKeys(20); const bundle = await bob.createPreKeyBundle(); // Inline a one-time prekey since we're not going through the prekey server const otpks = await bob.generateOneTimePreKeys(1); bundle.oneTimePreKey = { keyId: otpks[0].keyId, publicKey: otpks[0].keyPair.publicKey }; await alice.initSessionFromBundle('bob', bundle); // Initial exchange to establish bidirectional ratchet const initEnv = await alice.encrypt('bob', 'init'); await bob.decrypt('alice', initEnv); const initReply = await bob.encrypt('alice', 'init reply'); await alice.decrypt('bob', initReply); // ─── Run a loop of encrypted messages ──────────────── const messages = [ "Hey Bob, can you see this?", "Yes Alice, loud and clear.", "Cool — every message has a fresh key.", "And the dashboard shows it live.", "Ratchet steps every time we switch direction.", "Forward secrecy in action.", ]; let i = 0; setInterval(async () => { try { const fromAlice = i % 2 === 0; const sender = fromAlice ? alice : bob; const receiver = fromAlice ? bob : alice; const senderAddr = fromAlice ? 'bob' : 'alice'; const receiverAddr = fromAlice ? 'alice' : 'bob'; const text = messages[i % messages.length]; const env = await sender.encrypt(senderAddr, text); await receiver.decrypt(receiverAddr, env); console.log(` [${i + 1}] ${fromAlice ? 'Alice' : 'Bob '}: "${text}"`); i++; // Periodically replenish prekeys to show that activity in the dashboard if (i % 8 === 0) { await bob.ensurePreKeyStock(5, 20); } } catch (err) { console.error('Loop error:', err); } }, 2000); } main().catch(console.error);