/** * Addressing Modes Test * * Tests addressing modes implemented in @open-matrix/federation: * 1. Realm-only addressing (anonymous) * 2. Tab realm addressing (flowpad-demo.tab.xyz) * 3. ~tab URI normalization * 4. Principal-based addressing (anon, jwt, service) * 5. Cross-realm with principal context * * Note: Session directory tests removed (SessionDirectory/SessionsFacade deleted in cleanup) * * Run: * npx tsx --test tests/integration/federation/addressing-modes.spec.ts */ import { describe, it } from 'node:test'; import { strict as assert } from 'node:assert'; import { FederationPeer, ExactInstanceResolver, WIRE_PROFILES, InMemoryFilterBroker, InMemoryExactBroker, getLegacyIngressTopic, getMxIngressTopic, getInternalTopic, createLegacyRequest, createMxEnvV1, serializeLegacyEnvelope, serializeMxEnvV1, parseMatrixUri, CrossRealmClient, } from '@open-matrix/federation'; function sleep(ms: number): Promise { return new Promise(resolve => setTimeout(resolve, ms)); } function generateCorrelationId(): string { return `${Date.now()}-${Math.random().toString(36).slice(2, 10)}`; } // ============================================================================= // 1. REALM-ONLY ADDRESSING (Anonymous) // ============================================================================= describe('Realm-Only Addressing', () => { it('anonymous request from realm A to realm B', async () => { const REALM_A = 'client-realm'; const REALM_B = 'server-realm'; const backbone = new InMemoryExactBroker(); const localBusA = new InMemoryFilterBroker(); const localBusB = new InMemoryFilterBroker(); const gatewayA = new FederationPeer({ localRealm: REALM_A, profile: WIRE_PROFILES.matrix3, backbone, localBus: localBusA, }); const gatewayB = new FederationPeer({ localRealm: REALM_B, profile: WIRE_PROFILES.matrix3, backbone, localBus: localBusB, }); gatewayA.start(); gatewayB.start(); // Handler on B - receives internal format, replies to replyTo topic localBusB.subscribe(getInternalTopic(REALM_B, 'echo.accepts.ping'), (topic, payload) => { const req = JSON.parse(payload); // Reply to the internal replyTo topic - gateway will forward to backbone localBusB.publish(req.replyTo, JSON.stringify({ ok: true, correlationId: req.correlationId, result: { pong: true, from: REALM_B }, })); }); await sleep(100); // A listens on its backbone mailbox for replies const correlationId = generateCorrelationId(); let reply: unknown = null; backbone.subscribe(getLegacyIngressTopic(REALM_A), (topic, payload) => { const parsed = JSON.parse(payload); if (parsed.correlationId === correlationId) { reply = parsed; } }); await sleep(100); const request = createLegacyRequest({ toRealm: REALM_B, toPath: 'echo.accepts.ping', fromRealm: REALM_A, replyTo: '_client.inbox', correlationId, body: { msg: 'hello' }, }); backbone.publish(getLegacyIngressTopic(REALM_B), serializeLegacyEnvelope(request)); await sleep(500); assert.ok(reply, 'Should receive reply'); // Reply body is in 'body' field, not 'result' assert.strictEqual((reply as { body: { pong: boolean } }).body.pong, true); console.log(' ✓ Anonymous realm-only addressing works'); gatewayA.stop(); gatewayB.stop(); }); }); // ============================================================================= // 2. TAB REALM ADDRESSING // ============================================================================= describe('Tab Realm Addressing', () => { it('tab realm format: home.tab.xyz communicates with home realm', async () => { const HOME_REALM = 'flowpad-demo'; const TAB_REALM = 'flowpad-demo.tab.abc123'; const backbone = new InMemoryExactBroker(); const homeBus = new InMemoryFilterBroker(); const tabBus = new InMemoryFilterBroker(); // Home realm gateway const homeGateway = new FederationPeer({ localRealm: HOME_REALM, profile: WIRE_PROFILES.mxenv1, backbone, localBus: homeBus, }); // Tab realm gateway const tabGateway = new FederationPeer({ localRealm: TAB_REALM, profile: WIRE_PROFILES.mxenv1, backbone, localBus: tabBus, }); homeGateway.start(); tabGateway.start(); // Home realm handler - replies to internal replyTo topic (gateway forwards to backbone) homeBus.subscribe(getInternalTopic(HOME_REALM, 'service.accepts.getData'), (topic, payload) => { const req = JSON.parse(payload); // Reply to the internal replyTo topic - gateway handles forwarding homeBus.publish(req.replyTo, JSON.stringify({ ok: true, correlationId: req.correlationId, result: { data: 'from home realm', tabRealm: TAB_REALM }, })); }); await sleep(100); // Tab listens on its backbone mailbox for replies let tabReceived: unknown = null; backbone.subscribe(getMxIngressTopic(TAB_REALM, '00'), (topic, payload) => { const parsed = JSON.parse(payload); if (parsed.msgType === 'Result') { tabReceived = parsed; } }); await sleep(100); const correlationId = generateCorrelationId(); // NOTE: createMxEnvV1 uses replyToRealm/replyToPath, NOT replyTo object const request = createMxEnvV1({ msgType: 'Cmd', toRealm: HOME_REALM, toPath: 'service.accepts.getData', fromRealm: TAB_REALM, fromPath: '_client', replyToRealm: TAB_REALM, replyToPath: '_inbox.emits.reply', correlationId, ttl: 8, body: { query: 'test' }, }); backbone.publish(getMxIngressTopic(HOME_REALM, '00'), serializeMxEnvV1(request)); await sleep(500); assert.ok(tabReceived, 'Tab should receive reply from home'); assert.strictEqual((tabReceived as { body: { tabRealm: string } }).body.tabRealm, TAB_REALM); console.log(' ✓ Tab realm addressing (home.tab.xyz) works'); homeGateway.stop(); tabGateway.stop(); }); it('multiple tabs under same home realm are isolated', async () => { const HOME_REALM = 'app'; const TAB_1 = 'app.tab.tab1'; const TAB_2 = 'app.tab.tab2'; const TAB_3 = 'app.tab.tab3'; const backbone = new InMemoryExactBroker(); const tab1Bus = new InMemoryFilterBroker(); const tab2Bus = new InMemoryFilterBroker(); const tab3Bus = new InMemoryFilterBroker(); const tab1Gw = new FederationPeer({ localRealm: TAB_1, profile: WIRE_PROFILES.mxenv1, backbone, localBus: tab1Bus }); const tab2Gw = new FederationPeer({ localRealm: TAB_2, profile: WIRE_PROFILES.mxenv1, backbone, localBus: tab2Bus }); const tab3Gw = new FederationPeer({ localRealm: TAB_3, profile: WIRE_PROFILES.mxenv1, backbone, localBus: tab3Bus }); tab1Gw.start(); tab2Gw.start(); tab3Gw.start(); const received: Record = { tab1: 0, tab2: 0, tab3: 0 }; tab1Bus.subscribe(getInternalTopic(TAB_1, 'notify.emits.event'), () => { received.tab1++; }); tab2Bus.subscribe(getInternalTopic(TAB_2, 'notify.emits.event'), () => { received.tab2++; }); tab3Bus.subscribe(getInternalTopic(TAB_3, 'notify.emits.event'), () => { received.tab3++; }); await sleep(100); // Send event ONLY to tab2 const evt = createMxEnvV1({ msgType: 'Evt', toRealm: TAB_2, toPath: 'notify.emits.event', fromRealm: 'broadcaster', fromPath: '_sender', ttl: 8, body: { msg: 'only for tab2' }, }); backbone.publish(getMxIngressTopic(TAB_2, '00'), serializeMxEnvV1(evt)); await sleep(300); assert.strictEqual(received.tab1, 0, 'Tab1 should NOT receive'); assert.strictEqual(received.tab2, 1, 'Tab2 should receive'); assert.strictEqual(received.tab3, 0, 'Tab3 should NOT receive'); console.log(' ✓ Multiple tabs are isolated - only tab2 received'); tab1Gw.stop(); tab2Gw.stop(); tab3Gw.stop(); }); }); // ============================================================================= // 3. ~TAB URI NORMALIZATION // ============================================================================= describe('~tab URI Normalization', () => { it('parses ~tab in legacy compact form', () => { const uri = 'flowpad-demo/~tab/xyz/root.ui.app.accepts.ping'; const parsed = parseMatrixUri(uri); assert.ok(parsed, 'Should parse URI'); assert.strictEqual(parsed.realm, 'flowpad-demo.tab.xyz', 'Realm should be normalized'); assert.strictEqual(parsed.toPath, 'root.ui.app.accepts.ping'); console.log(' ✓ ~tab normalization in legacy compact form'); }); it('parses ~tab in segmented MXURI/1 form', () => { const uri = 'flowpad-demo/~tab/abc123/-/root/ui/app/accepts/ping'; const parsed = parseMatrixUri(uri); assert.ok(parsed, 'Should parse URI'); assert.strictEqual(parsed.realm, 'flowpad-demo.tab.abc123', 'Realm should be normalized'); assert.strictEqual(parsed.componentPath, 'root.ui.app'); assert.strictEqual(parsed.plane, 'accepts'); assert.strictEqual(parsed.messageName, 'ping'); console.log(' ✓ ~tab normalization in segmented MXURI/1 form'); }); it('handles query parameters with ~tab', () => { const uri = 'home/~tab/session-42/service.accepts.query?depth=3&format=json'; const parsed = parseMatrixUri(uri); assert.ok(parsed, 'Should parse URI'); assert.strictEqual(parsed.realm, 'home.tab.session-42'); assert.strictEqual(parsed.query.depth, '3'); assert.strictEqual(parsed.query.format, 'json'); console.log(' ✓ ~tab with query parameters'); }); }); // ============================================================================= // 4. PRINCIPAL-BASED ADDRESSING // ============================================================================= describe('Principal-Based Addressing', () => { it('MXENV/1 envelope with JWT principal', async () => { const REALM_A = 'client'; const REALM_B = 'server'; const backbone = new InMemoryExactBroker(); const busA = new InMemoryFilterBroker(); const busB = new InMemoryFilterBroker(); const gwA = new FederationPeer({ localRealm: REALM_A, profile: WIRE_PROFILES.mxenv1, backbone, localBus: busA }); const gwB = new FederationPeer({ localRealm: REALM_B, profile: WIRE_PROFILES.mxenv1, backbone, localBus: busB }); gwA.start(); gwB.start(); // EdgeGateway passes principalKey (string), not full principal object // JWT principal derives to: `${iss}:${sub}` = "https://auth.example.com:user-123" let receivedPrincipalKey: string | undefined = undefined; busB.subscribe(getInternalTopic(REALM_B, 'auth.accepts.check'), (topic, payload) => { const req = JSON.parse(payload); receivedPrincipalKey = req._mx?.principalKey; busB.publish(req.replyTo, JSON.stringify({ ok: true, correlationId: req.correlationId, result: { authenticated: true }, })); }); await sleep(100); // Send request with JWT principal // NOTE: createMxEnvV1 doesn't support principal directly - we add it manually const request = createMxEnvV1({ msgType: 'Cmd', toRealm: REALM_B, toPath: 'auth.accepts.check', fromRealm: REALM_A, fromPath: '_client', replyToRealm: REALM_A, replyToPath: '_inbox', correlationId: generateCorrelationId(), ttl: 8, body: { action: 'verify' }, }); // Add principal manually (createMxEnvV1 doesn't support it) request.principal = { kind: 'jwt', iss: 'https://auth.example.com', sub: 'user-123', aud: ['server'], scopes: ['read', 'write'], }; backbone.publish(getMxIngressTopic(REALM_B, '00'), serializeMxEnvV1(request)); await sleep(300); // Check principalKey is derived correctly: iss:sub = "https://auth.example.com:user-123" assert.ok(receivedPrincipalKey, 'Handler should receive principalKey'); assert.strictEqual(receivedPrincipalKey, 'https://auth.example.com:user-123'); console.log(' ✓ JWT principal in MXENV/1 envelope → principalKey: ' + receivedPrincipalKey); gwA.stop(); gwB.stop(); }); it('MXENV/1 envelope with service principal', async () => { const REALM = 'api-server'; const FROM_REALM = 'scheduler-service'; const backbone = new InMemoryExactBroker(); const localBus = new InMemoryFilterBroker(); const gw = new FederationPeer({ localRealm: REALM, profile: WIRE_PROFILES.mxenv1, backbone, localBus }); gw.start(); // Service principal derives to: `service:${sub}` = "service:cron-scheduler" let receivedPrincipalKey: string | undefined = undefined; localBus.subscribe(getInternalTopic(REALM, 'internal.accepts.sync'), (topic, payload) => { const req = JSON.parse(payload); receivedPrincipalKey = req._mx?.principalKey; // Reply so the gateway doesn't timeout localBus.publish(req.replyTo, JSON.stringify({ ok: true, correlationId: req.correlationId, result: { synced: true }, })); }); await sleep(100); // Send with service principal - MUST include replyTo and correlationId for Cmd messages const request = createMxEnvV1({ msgType: 'Cmd', toRealm: REALM, toPath: 'internal.accepts.sync', fromRealm: FROM_REALM, fromPath: '_scheduler', replyToRealm: FROM_REALM, replyToPath: '_inbox', correlationId: generateCorrelationId(), ttl: 8, body: { task: 'cleanup' }, }); // Add principal manually request.principal = { kind: 'service', sub: 'cron-scheduler', scopes: ['internal:sync'], }; backbone.publish(getMxIngressTopic(REALM, '00'), serializeMxEnvV1(request)); await sleep(300); // Check principalKey is derived correctly: service:sub = "service:cron-scheduler" assert.ok(receivedPrincipalKey, 'Handler should receive principalKey'); assert.strictEqual(receivedPrincipalKey, 'service:cron-scheduler'); console.log(' ✓ Service principal in MXENV/1 envelope → principalKey: ' + receivedPrincipalKey); gw.stop(); }); it('anonymous principal when none specified', async () => { const REALM = 'public-api'; const FROM_REALM = 'anonymous-client'; const backbone = new InMemoryExactBroker(); const localBus = new InMemoryFilterBroker(); const gw = new FederationPeer({ localRealm: REALM, profile: WIRE_PROFILES.mxenv1, backbone, localBus }); gw.start(); // Anonymous principal derives to: `anon:${fromRealm}` = "anon:anonymous-client" let receivedPrincipalKey: string | undefined = undefined; localBus.subscribe(getInternalTopic(REALM, 'public.accepts.query'), (topic, payload) => { const req = JSON.parse(payload); receivedPrincipalKey = req._mx?.principalKey; // Reply so gateway doesn't timeout localBus.publish(req.replyTo, JSON.stringify({ ok: true, correlationId: req.correlationId, result: { found: true }, })); }); await sleep(100); // Send without principal (anonymous) const request = createMxEnvV1({ msgType: 'Cmd', toRealm: REALM, toPath: 'public.accepts.query', fromRealm: FROM_REALM, fromPath: '_anon', // NO principal field - will derive to anon replyToRealm: FROM_REALM, replyToPath: '_inbox', correlationId: generateCorrelationId(), ttl: 8, body: { q: 'search' }, }); backbone.publish(getMxIngressTopic(REALM, '00'), serializeMxEnvV1(request)); await sleep(300); // Check principalKey is derived correctly: anon:fromRealm = "anon:anonymous-client" assert.ok(receivedPrincipalKey, 'Handler should receive principalKey'); assert.strictEqual(receivedPrincipalKey, 'anon:anonymous-client'); console.log(' ✓ Anonymous principal → principalKey: ' + receivedPrincipalKey); gw.stop(); }); }); // ============================================================================= // 5. CROSS-REALM CLIENT WITH DISCOVERY // ============================================================================= describe('Cross-Realm Client', () => { it('invoke with automatic reply handling', async () => { const SERVER_REALM = 'calculator-service'; const CLIENT_REALM = 'client-app'; const backbone = new InMemoryExactBroker(); const serverBus = new InMemoryFilterBroker(); const serverGw = new FederationPeer({ localRealm: SERVER_REALM, profile: WIRE_PROFILES.mxenv1, backbone, localBus: serverBus, }); serverGw.start(); // Server handler serverBus.subscribe(getInternalTopic(SERVER_REALM, 'math.accepts.add'), (topic, payload) => { const req = JSON.parse(payload); const { a, b } = req.params; const result = a + b; // Reply const reply = createMxEnvV1({ msgType: 'Result', toRealm: req._mx?.replyTo?.realm ?? CLIENT_REALM, toPath: req._mx?.replyTo?.path ?? '_inbox', fromRealm: SERVER_REALM, fromPath: 'math', correlationId: req.correlationId, body: { sum: result }, }); backbone.publish(getMxIngressTopic(req._mx?.replyTo?.realm ?? CLIENT_REALM, '00'), serializeMxEnvV1(reply)); }); await sleep(100); // Client uses CrossRealmClient const client = new CrossRealmClient({ localRealm: CLIENT_REALM, transport: backbone, profile: WIRE_PROFILES.mxenv1, defaultTimeoutMs: 5000, }); const result = await client.invoke<{ sum: number }>(SERVER_REALM, 'math.accepts.add', { a: 15, b: 27 }); assert.strictEqual(result.sum, 42); console.log(' ✓ CrossRealmClient invoke: 15 + 27 = 42'); serverGw.stop(); }); }); // ============================================================================= // SUMMARY // ============================================================================= describe('Addressing Modes Summary', () => { it('documents all tested addressing modes', () => { const modes = [ { name: 'Realm-only (anonymous)', tested: true }, { name: 'Tab realm (home.tab.xyz)', tested: true }, { name: '~tab URI normalization', tested: true }, { name: 'JWT principal', tested: true }, { name: 'Service principal', tested: true }, { name: 'Anonymous principal', tested: true }, { name: 'CrossRealmClient invoke', tested: true }, ]; console.log('\n ┌────────────────────────────────────────────┐'); console.log(' │ ADDRESSING MODES COVERAGE │'); console.log(' ├────────────────────────────────────────────┤'); for (const m of modes) { console.log(` │ ${m.tested ? '✓' : '✗'} ${m.name.padEnd(38)} │`); } console.log(' └────────────────────────────────────────────┘'); assert.ok(modes.every(m => m.tested), 'All addressing modes should be tested'); }); });