diff --git a/connection.js b/connection.js index ffef33a..2e916b0 100644 --- a/connection.js +++ b/connection.js @@ -67,6 +67,11 @@ export async function createHsync(config) { hsyncClient.listeners = initListeners(hsyncClient); hsyncClient.relays = initRelays(hsyncClient); + // Enable auto-relay for web port (issue #15) + if (port) { + hsyncClient.relays.setWebPort(port); + } + const events = new EventEmitter(); hsyncClient.on = events.on.bind(events); diff --git a/lib/socket-relays.js b/lib/socket-relays.js index a41448a..de59d3f 100644 --- a/lib/socket-relays.js +++ b/lib/socket-relays.js @@ -14,6 +14,12 @@ export function setNet(netImpl) { export function initRelays(hsyncClient) { const cachedRelays = {}; + let webPort = null; + + function setWebPort(port) { + webPort = port; + debug('web port set for auto-relay:', port); + } function getSocketRelays() { const hKeys = Object.keys(cachedRelays); @@ -45,8 +51,15 @@ export function initRelays(hsyncClient) { return `closeRelaySocket no matching socket for ${socketId}`; }); - const relay = cachedRelays['p' + port]; + let relay = cachedRelays['p' + port]; debug('connect relay', port, socketId, peer.hostName); + + // Auto-create relay if port matches web port (issue #15) + if (!relay && webPort && port === webPort) { + debug('auto-creating relay for web port:', port); + relay = addSocketRelay({ port, targetHost: 'localhost', targetPort: port }); + } + if (!relay) { throw new Error('no relay found for port: ' + port); } @@ -112,11 +125,13 @@ export function initRelays(hsyncClient) { hsyncClient.addSocketRelay = addSocketRelay; hsyncClient.getSocketRelays = getSocketRelays; hsyncClient.connectSocket = connectSocket; + hsyncClient.setWebPort = setWebPort; return { // receiveListenerData, getSocketRelays, connectSocket, addSocketRelay, + setWebPort, }; } diff --git a/test/unit/socket-relays.test.js b/test/unit/socket-relays.test.js index 6d10dd6..efd3223 100644 --- a/test/unit/socket-relays.test.js +++ b/test/unit/socket-relays.test.js @@ -68,6 +68,27 @@ describe('socket-relays', () => { expect(mockHsyncClient.addSocketRelay).toBeTypeOf('function'); expect(mockHsyncClient.getSocketRelays).toBeTypeOf('function'); expect(mockHsyncClient.connectSocket).toBeTypeOf('function'); + expect(mockHsyncClient.setWebPort).toBeTypeOf('function'); + }); + + it('should return setWebPort in returned object', () => { + const relays = initRelays(mockHsyncClient); + + expect(relays.setWebPort).toBeTypeOf('function'); + }); + }); + + describe('setWebPort', () => { + let relays; + + beforeEach(() => { + relays = initRelays(mockHsyncClient); + }); + + it('should store web port for auto-relay', () => { + // setWebPort stores the port internally - no return value + relays.setWebPort(3000); + // No error means success }); }); @@ -201,6 +222,55 @@ describe('socket-relays', () => { ).toThrow('no relay found for port: 9999'); }); + it('should auto-create relay for web port (issue #15)', async () => { + // Set web port without explicit relay + relays.setWebPort(3000); + + // Should NOT throw - should auto-create relay for web port + const result = await relays.connectSocket(mockPeer, { + port: 3000, + socketId: 'auto-relay-socket', + hostName: 'remote.example.com', + }); + + expect(result.socketId).toBe('auto-relay-socket'); + expect(result.targetHost).toBe('localhost'); + expect(result.targetPort).toBe(3000); + expect(mockSocket.connect).toHaveBeenCalledWith(3000, 'localhost', expect.any(Function)); + }); + + it('should still throw for non-web ports when web port is set', () => { + relays.setWebPort(3000); + + // Different port should still throw + expect(() => + relays.connectSocket(mockPeer, { + port: 9999, + socketId: 'test-socket', + hostName: 'remote.example.com', + }) + ).toThrow('no relay found for port: 9999'); + }); + + it('should prefer explicit relay over auto-relay', async () => { + relays.setWebPort(3000); + relays.addSocketRelay({ + port: 3000, + targetPort: 4000, + targetHost: 'custom-host.local', + }); + + const result = await relays.connectSocket(mockPeer, { + port: 3000, + socketId: 'explicit-relay-socket', + hostName: 'remote.example.com', + }); + + // Should use explicit relay config, not auto-relay + expect(result.targetHost).toBe('custom-host.local'); + expect(result.targetPort).toBe(4000); + }); + it('should create socket and connect to relay target', async () => { relays.addSocketRelay({ port: 3000,