hsync 0.30.1 → 0.31.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/ci.yml +32 -0
- package/.husky/pre-commit +1 -0
- package/.prettierrc +7 -0
- package/Readme.md +1 -0
- package/cli.js +103 -56
- package/config.js +6 -6
- package/connection.js +44 -48
- package/dist/hsync.js +1082 -831
- package/dist/hsync.min.js +28862 -1
- package/dist/hsync.min.js.LICENSE.txt +11 -1
- package/eslint.config.js +26 -0
- package/hsync-web.js +18 -17
- package/hsync.js +14 -18
- package/index.js +3 -3
- package/lib/fetch.js +2 -4
- package/lib/peers.js +69 -68
- package/lib/rtc-node.js +35 -34
- package/lib/rtc-web.js +60 -58
- package/lib/socket-listeners.js +16 -22
- package/lib/socket-map.js +5 -5
- package/lib/socket-relays.js +12 -17
- package/lib/web-handler.js +8 -14
- package/package.json +61 -18
- package/shell.js +4 -8
- package/test/mocks/mqtt.mock.js +25 -0
- package/test/mocks/net.mock.js +34 -0
- package/test/mocks/rtc.mock.js +9 -0
- package/test/setup.js +10 -0
- package/test/unit/config.test.js +36 -0
- package/test/unit/fetch.test.js +189 -0
- package/test/unit/peers.test.js +275 -0
- package/test/unit/socket-listeners.test.js +319 -0
- package/test/unit/socket-map.test.js +64 -0
- package/test/unit/socket-relays.test.js +315 -0
- package/test/unit/web-handler.test.js +223 -0
- package/todo.md +324 -0
- package/vitest.config.js +15 -0
- package/webpack.config.js +24 -8
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
2
|
+
import { initPeers, setRTC } from '../../lib/peers.js';
|
|
3
|
+
|
|
4
|
+
describe('peers', () => {
|
|
5
|
+
let mockHsyncClient;
|
|
6
|
+
let mockMqConn;
|
|
7
|
+
|
|
8
|
+
beforeEach(() => {
|
|
9
|
+
mockMqConn = {
|
|
10
|
+
publish: vi.fn(),
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
mockHsyncClient = {
|
|
14
|
+
myHostName: 'myhost.example.com',
|
|
15
|
+
webUrl: 'https://myhost.example.com',
|
|
16
|
+
peerMethods: {
|
|
17
|
+
testMethod: vi.fn(),
|
|
18
|
+
},
|
|
19
|
+
mqConn: mockMqConn,
|
|
20
|
+
};
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
describe('setRTC', () => {
|
|
24
|
+
it('should accept an RTC implementation', () => {
|
|
25
|
+
const mockRtc = {
|
|
26
|
+
offerPeer: vi.fn(),
|
|
27
|
+
answerPeer: vi.fn(),
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
// Should not throw
|
|
31
|
+
expect(() => setRTC(mockRtc)).not.toThrow();
|
|
32
|
+
});
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
describe('initPeers', () => {
|
|
36
|
+
it('should return peer library with required methods', () => {
|
|
37
|
+
const peerLib = initPeers(mockHsyncClient);
|
|
38
|
+
|
|
39
|
+
expect(peerLib.cachedPeers).toBeTypeOf('object');
|
|
40
|
+
expect(peerLib.getRPCPeer).toBeTypeOf('function');
|
|
41
|
+
expect(peerLib.createRPCPeer).toBeTypeOf('function');
|
|
42
|
+
expect(peerLib.createServerPeer).toBeTypeOf('function');
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it('should attach methods to hsyncClient', () => {
|
|
46
|
+
initPeers(mockHsyncClient);
|
|
47
|
+
|
|
48
|
+
expect(mockHsyncClient.cachedPeers).toBeTypeOf('object');
|
|
49
|
+
expect(mockHsyncClient.getRPCPeer).toBeTypeOf('function');
|
|
50
|
+
expect(mockHsyncClient.createRPCPeer).toBeTypeOf('function');
|
|
51
|
+
expect(mockHsyncClient.createServerPeer).toBeTypeOf('function');
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it('should return an EventEmitter', () => {
|
|
55
|
+
const peerLib = initPeers(mockHsyncClient);
|
|
56
|
+
|
|
57
|
+
expect(peerLib.on).toBeTypeOf('function');
|
|
58
|
+
expect(peerLib.emit).toBeTypeOf('function');
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
describe('createRPCPeer', () => {
|
|
63
|
+
let peerLib;
|
|
64
|
+
|
|
65
|
+
beforeEach(() => {
|
|
66
|
+
peerLib = initPeers(mockHsyncClient);
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it('should throw if no hostname specified', () => {
|
|
70
|
+
expect(() => peerLib.createRPCPeer({})).toThrow('No hostname specified');
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it('should throw if hostname is same as client', () => {
|
|
74
|
+
expect(() => peerLib.createRPCPeer({ hostName: 'myhost.example.com' })).toThrow(
|
|
75
|
+
'Peer must be a different host'
|
|
76
|
+
);
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it('should create peer with correct hostname', () => {
|
|
80
|
+
const peer = peerLib.createRPCPeer({ hostName: 'other.example.com' });
|
|
81
|
+
|
|
82
|
+
expect(peer.hostName).toBe('other.example.com');
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it('should create peer with myAuth', () => {
|
|
86
|
+
const peer = peerLib.createRPCPeer({ hostName: 'other.example.com' });
|
|
87
|
+
|
|
88
|
+
expect(peer.myAuth).toBeTypeOf('string');
|
|
89
|
+
expect(peer.myAuth.length).toBeGreaterThan(0);
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
it('should create peer with rtcEvents emitter', () => {
|
|
93
|
+
const peer = peerLib.createRPCPeer({ hostName: 'other.example.com' });
|
|
94
|
+
|
|
95
|
+
expect(peer.rtcEvents).toBeDefined();
|
|
96
|
+
expect(peer.rtcEvents.on).toBeTypeOf('function');
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
it('should create peer with empty sockets map', () => {
|
|
100
|
+
const peer = peerLib.createRPCPeer({ hostName: 'other.example.com' });
|
|
101
|
+
|
|
102
|
+
expect(peer.sockets).toEqual({});
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
it('should create peer with localMethods', () => {
|
|
106
|
+
const peer = peerLib.createRPCPeer({ hostName: 'other.example.com' });
|
|
107
|
+
|
|
108
|
+
expect(peer.localMethods).toBeDefined();
|
|
109
|
+
expect(peer.localMethods.rtcSignal).toBeTypeOf('function');
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
it('should create peer with sendJSONMsg method', () => {
|
|
113
|
+
const peer = peerLib.createRPCPeer({ hostName: 'other.example.com' });
|
|
114
|
+
|
|
115
|
+
expect(peer.sendJSONMsg).toBeTypeOf('function');
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
it('should throw when sendJSONMsg called without object', () => {
|
|
119
|
+
const peer = peerLib.createRPCPeer({ hostName: 'other.example.com' });
|
|
120
|
+
|
|
121
|
+
expect(() => peer.sendJSONMsg('not an object')).toThrow('sendJSONMsg requires an object');
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
it('should throw when sendJSONMsg called without connection', () => {
|
|
125
|
+
const peer = peerLib.createRPCPeer({ hostName: 'other.example.com' });
|
|
126
|
+
|
|
127
|
+
expect(() => peer.sendJSONMsg({ test: 'data' })).toThrow('peer not connected');
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
it('should create peer with connectRTC method', () => {
|
|
131
|
+
const peer = peerLib.createRPCPeer({ hostName: 'other.example.com' });
|
|
132
|
+
|
|
133
|
+
expect(peer.connectRTC).toBeTypeOf('function');
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
it('should use default timeout of 10000', () => {
|
|
137
|
+
const peer = peerLib.createRPCPeer({ hostName: 'other.example.com' });
|
|
138
|
+
|
|
139
|
+
// Timeout is passed to rawr internally, we can verify peer was created
|
|
140
|
+
expect(peer).toBeDefined();
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
it('should accept custom timeout', () => {
|
|
144
|
+
const peer = peerLib.createRPCPeer({
|
|
145
|
+
hostName: 'other.example.com',
|
|
146
|
+
timeout: 5000,
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
expect(peer).toBeDefined();
|
|
150
|
+
});
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
describe('getRPCPeer', () => {
|
|
154
|
+
let peerLib;
|
|
155
|
+
|
|
156
|
+
beforeEach(() => {
|
|
157
|
+
peerLib = initPeers(mockHsyncClient);
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
it('should create new peer if not cached', () => {
|
|
161
|
+
const peer = peerLib.getRPCPeer({ hostName: 'new.example.com' });
|
|
162
|
+
|
|
163
|
+
expect(peer).toBeDefined();
|
|
164
|
+
expect(peer.hostName).toBe('new.example.com');
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
it('should return cached peer on second call', () => {
|
|
168
|
+
const peer1 = peerLib.getRPCPeer({ hostName: 'cached.example.com' });
|
|
169
|
+
const peer2 = peerLib.getRPCPeer({ hostName: 'cached.example.com' });
|
|
170
|
+
|
|
171
|
+
expect(peer1).toBe(peer2);
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
it('should emit peerCreated event for new peer', () => {
|
|
175
|
+
const createdHandler = vi.fn();
|
|
176
|
+
peerLib.on('peerCreated', createdHandler);
|
|
177
|
+
|
|
178
|
+
peerLib.getRPCPeer({ hostName: 'new.example.com' });
|
|
179
|
+
|
|
180
|
+
expect(createdHandler).toHaveBeenCalledTimes(1);
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
it('should not emit peerCreated for cached peer', () => {
|
|
184
|
+
const createdHandler = vi.fn();
|
|
185
|
+
peerLib.on('peerCreated', createdHandler);
|
|
186
|
+
|
|
187
|
+
peerLib.getRPCPeer({ hostName: 'cached.example.com' });
|
|
188
|
+
peerLib.getRPCPeer({ hostName: 'cached.example.com' });
|
|
189
|
+
|
|
190
|
+
expect(createdHandler).toHaveBeenCalledTimes(1);
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
it('should mark peer as temporary if specified', () => {
|
|
194
|
+
const peer = peerLib.getRPCPeer({
|
|
195
|
+
hostName: 'temp.example.com',
|
|
196
|
+
temporary: true,
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
expect(peer.rpcTemporary).toBe(true);
|
|
200
|
+
});
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
describe('createServerPeer', () => {
|
|
204
|
+
let peerLib;
|
|
205
|
+
|
|
206
|
+
beforeEach(() => {
|
|
207
|
+
peerLib = initPeers(mockHsyncClient);
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
it('should create server peer with methods', () => {
|
|
211
|
+
const methods = {
|
|
212
|
+
testMethod: vi.fn(),
|
|
213
|
+
};
|
|
214
|
+
|
|
215
|
+
const peer = peerLib.createServerPeer(mockHsyncClient, methods);
|
|
216
|
+
|
|
217
|
+
expect(peer).toBeDefined();
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
it('should publish to srpc topic on send', () => {
|
|
221
|
+
const methods = {};
|
|
222
|
+
const peer = peerLib.createServerPeer(mockHsyncClient, methods);
|
|
223
|
+
|
|
224
|
+
// Access the transport to test send
|
|
225
|
+
// The peer has internal transport that calls mqConn.publish
|
|
226
|
+
// We need to trigger a method call or notification
|
|
227
|
+
|
|
228
|
+
expect(peer).toBeDefined();
|
|
229
|
+
});
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
describe('rtcEvents handlers', () => {
|
|
233
|
+
let peerLib;
|
|
234
|
+
|
|
235
|
+
beforeEach(() => {
|
|
236
|
+
peerLib = initPeers(mockHsyncClient);
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
it('should handle dcOpen event', () => {
|
|
240
|
+
const peer = peerLib.createRPCPeer({ hostName: 'other.example.com' });
|
|
241
|
+
peer.rtcSend = vi.fn();
|
|
242
|
+
|
|
243
|
+
peer.rtcEvents.emit('dcOpen');
|
|
244
|
+
|
|
245
|
+
expect(peer.packAndSend).toBeTypeOf('function');
|
|
246
|
+
// Should send test packet
|
|
247
|
+
expect(peer.rtcSend).toHaveBeenCalled();
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
it('should handle closed event', () => {
|
|
251
|
+
const peer = peerLib.createRPCPeer({ hostName: 'other.example.com' });
|
|
252
|
+
peer.dcOpen = true;
|
|
253
|
+
peer.packAndSend = vi.fn();
|
|
254
|
+
peer.sockets = {
|
|
255
|
+
'socket-1': { destroy: vi.fn() },
|
|
256
|
+
};
|
|
257
|
+
|
|
258
|
+
peer.rtcEvents.emit('closed');
|
|
259
|
+
|
|
260
|
+
expect(peer.dcOpen).toBe(false);
|
|
261
|
+
expect(peer.packAndSend).toBeUndefined();
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
it('should handle disconnected event', () => {
|
|
265
|
+
const peer = peerLib.createRPCPeer({ hostName: 'other.example.com' });
|
|
266
|
+
peer.dcOpen = true;
|
|
267
|
+
peer.packAndSend = vi.fn();
|
|
268
|
+
|
|
269
|
+
peer.rtcEvents.emit('disconnected');
|
|
270
|
+
|
|
271
|
+
expect(peer.dcOpen).toBe(false);
|
|
272
|
+
expect(peer.packAndSend).toBeUndefined();
|
|
273
|
+
});
|
|
274
|
+
});
|
|
275
|
+
});
|
|
@@ -0,0 +1,319 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
2
|
+
import { initListeners, setNet } from '../../lib/socket-listeners.js';
|
|
3
|
+
|
|
4
|
+
describe('socket-listeners', () => {
|
|
5
|
+
let mockNet;
|
|
6
|
+
let mockSocket;
|
|
7
|
+
let mockServer;
|
|
8
|
+
let mockHsyncClient;
|
|
9
|
+
let mockRpcPeer;
|
|
10
|
+
|
|
11
|
+
beforeEach(() => {
|
|
12
|
+
mockSocket = {
|
|
13
|
+
socketId: null,
|
|
14
|
+
write: vi.fn(),
|
|
15
|
+
end: vi.fn(),
|
|
16
|
+
on: vi.fn(),
|
|
17
|
+
connect: vi.fn((port, host, cb) => cb && cb()),
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
mockServer = {
|
|
21
|
+
listen: vi.fn(),
|
|
22
|
+
close: vi.fn(),
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
mockNet = {
|
|
26
|
+
Socket: vi.fn(() => mockSocket),
|
|
27
|
+
createServer: vi.fn((handler) => {
|
|
28
|
+
mockServer.connectionHandler = handler;
|
|
29
|
+
return mockServer;
|
|
30
|
+
}),
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
mockRpcPeer = {
|
|
34
|
+
hostName: 'remote.example.com',
|
|
35
|
+
rtcCon: null,
|
|
36
|
+
connectRTC: vi.fn().mockResolvedValue({}),
|
|
37
|
+
notifications: {
|
|
38
|
+
oncloseListenerSocket: vi.fn(),
|
|
39
|
+
},
|
|
40
|
+
notifiers: {
|
|
41
|
+
closeRelaySocket: vi.fn(),
|
|
42
|
+
},
|
|
43
|
+
methods: {
|
|
44
|
+
connectSocket: vi.fn().mockResolvedValue({ socketId: 'test-id' }),
|
|
45
|
+
},
|
|
46
|
+
sockets: {},
|
|
47
|
+
packAndSend: vi.fn(),
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
mockHsyncClient = {
|
|
51
|
+
myHostName: 'local.example.com',
|
|
52
|
+
getRPCPeer: vi.fn(() => mockRpcPeer),
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
setNet(mockNet);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
describe('setNet', () => {
|
|
59
|
+
it('should set the net implementation', () => {
|
|
60
|
+
const customNet = { Socket: vi.fn(), createServer: vi.fn() };
|
|
61
|
+
setNet(customNet);
|
|
62
|
+
// No error means success
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
describe('initListeners', () => {
|
|
67
|
+
it('should return object with required methods', () => {
|
|
68
|
+
const listeners = initListeners(mockHsyncClient);
|
|
69
|
+
|
|
70
|
+
expect(listeners.addSocketListener).toBeTypeOf('function');
|
|
71
|
+
expect(listeners.getSocketListeners).toBeTypeOf('function');
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it('should attach methods to hsyncClient', () => {
|
|
75
|
+
initListeners(mockHsyncClient);
|
|
76
|
+
|
|
77
|
+
expect(mockHsyncClient.socketListeners).toBeTypeOf('object');
|
|
78
|
+
expect(mockHsyncClient.addSocketListener).toBeTypeOf('function');
|
|
79
|
+
expect(mockHsyncClient.getSocketListeners).toBeTypeOf('function');
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
describe('addSocketListener', () => {
|
|
84
|
+
let listeners;
|
|
85
|
+
|
|
86
|
+
beforeEach(() => {
|
|
87
|
+
listeners = initListeners(mockHsyncClient);
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
it('should throw if no targetHost', () => {
|
|
91
|
+
expect(() => listeners.addSocketListener({ port: 3000 })).toThrow('no targetHost');
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it('should throw if targetHost is same as client', () => {
|
|
95
|
+
expect(() =>
|
|
96
|
+
listeners.addSocketListener({
|
|
97
|
+
port: 3000,
|
|
98
|
+
targetHost: 'https://local.example.com',
|
|
99
|
+
})
|
|
100
|
+
).toThrow('targetHost must be a different host');
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it('should clean trailing slash from targetHost', () => {
|
|
104
|
+
const listener = listeners.addSocketListener({
|
|
105
|
+
port: 3000,
|
|
106
|
+
targetHost: 'https://remote.example.com/',
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
expect(listener.targetHost).toBe('https://remote.example.com');
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
it('should create socket server', () => {
|
|
113
|
+
listeners.addSocketListener({
|
|
114
|
+
port: 3000,
|
|
115
|
+
targetHost: 'https://remote.example.com',
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
expect(mockNet.createServer).toHaveBeenCalled();
|
|
119
|
+
expect(mockServer.listen).toHaveBeenCalledWith(3000);
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
it('should return listener object with properties', () => {
|
|
123
|
+
const listener = listeners.addSocketListener({
|
|
124
|
+
port: 3000,
|
|
125
|
+
targetPort: 4000,
|
|
126
|
+
targetHost: 'https://remote.example.com',
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
expect(listener.port).toBe(3000);
|
|
130
|
+
expect(listener.targetPort).toBe(4000);
|
|
131
|
+
expect(listener.targetHost).toBe('https://remote.example.com');
|
|
132
|
+
expect(listener.socketServer).toBe(mockServer);
|
|
133
|
+
expect(listener.end).toBeTypeOf('function');
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
it('should use port as targetPort if not specified', () => {
|
|
137
|
+
const listener = listeners.addSocketListener({
|
|
138
|
+
port: 3000,
|
|
139
|
+
targetHost: 'https://remote.example.com',
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
expect(listener.targetPort).toBe(3000);
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
it('should get RPC peer for targetHost', () => {
|
|
146
|
+
listeners.addSocketListener({
|
|
147
|
+
port: 3000,
|
|
148
|
+
targetHost: 'https://remote.example.com',
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
expect(mockHsyncClient.getRPCPeer).toHaveBeenCalledWith({
|
|
152
|
+
hostName: 'https://remote.example.com',
|
|
153
|
+
});
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
it('should store listener by port key', () => {
|
|
157
|
+
listeners.addSocketListener({
|
|
158
|
+
port: 3000,
|
|
159
|
+
targetHost: 'https://remote.example.com',
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
expect(mockHsyncClient.socketListeners['p3000']).toBeDefined();
|
|
163
|
+
});
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
describe('getSocketListeners', () => {
|
|
167
|
+
let listeners;
|
|
168
|
+
|
|
169
|
+
beforeEach(() => {
|
|
170
|
+
listeners = initListeners(mockHsyncClient);
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
it('should return empty array when no listeners', () => {
|
|
174
|
+
const result = listeners.getSocketListeners();
|
|
175
|
+
|
|
176
|
+
expect(result).toEqual([]);
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
it('should return listener info', () => {
|
|
180
|
+
listeners.addSocketListener({
|
|
181
|
+
port: 3000,
|
|
182
|
+
targetPort: 4000,
|
|
183
|
+
targetHost: 'https://remote.example.com',
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
const result = listeners.getSocketListeners();
|
|
187
|
+
|
|
188
|
+
expect(result).toHaveLength(1);
|
|
189
|
+
expect(result[0]).toEqual({
|
|
190
|
+
port: 3000,
|
|
191
|
+
targetHost: 'https://remote.example.com',
|
|
192
|
+
targetPort: 4000,
|
|
193
|
+
});
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
it('should return multiple listeners', () => {
|
|
197
|
+
listeners.addSocketListener({
|
|
198
|
+
port: 3000,
|
|
199
|
+
targetHost: 'https://remote1.example.com',
|
|
200
|
+
});
|
|
201
|
+
listeners.addSocketListener({
|
|
202
|
+
port: 4000,
|
|
203
|
+
targetHost: 'https://remote2.example.com',
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
const result = listeners.getSocketListeners();
|
|
207
|
+
|
|
208
|
+
expect(result).toHaveLength(2);
|
|
209
|
+
});
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
describe('listener connection handler', () => {
|
|
213
|
+
let listeners;
|
|
214
|
+
|
|
215
|
+
beforeEach(() => {
|
|
216
|
+
listeners = initListeners(mockHsyncClient);
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
it('should initiate RTC connection if not connected', async () => {
|
|
220
|
+
listeners.addSocketListener({
|
|
221
|
+
port: 3000,
|
|
222
|
+
targetHost: 'https://remote.example.com',
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
// Simulate incoming connection
|
|
226
|
+
const incomingSocket = {
|
|
227
|
+
socketId: null,
|
|
228
|
+
on: vi.fn(),
|
|
229
|
+
end: vi.fn(),
|
|
230
|
+
};
|
|
231
|
+
|
|
232
|
+
await mockServer.connectionHandler(incomingSocket);
|
|
233
|
+
|
|
234
|
+
expect(mockRpcPeer.connectRTC).toHaveBeenCalled();
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
it('should not initiate RTC if already connected', async () => {
|
|
238
|
+
mockRpcPeer.rtcCon = {}; // Already connected
|
|
239
|
+
|
|
240
|
+
listeners.addSocketListener({
|
|
241
|
+
port: 3000,
|
|
242
|
+
targetHost: 'https://remote.example.com',
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
const incomingSocket = {
|
|
246
|
+
socketId: null,
|
|
247
|
+
on: vi.fn(),
|
|
248
|
+
end: vi.fn(),
|
|
249
|
+
};
|
|
250
|
+
|
|
251
|
+
await mockServer.connectionHandler(incomingSocket);
|
|
252
|
+
|
|
253
|
+
expect(mockRpcPeer.connectRTC).not.toHaveBeenCalled();
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
it('should end socket if RTC connection fails', async () => {
|
|
257
|
+
mockRpcPeer.connectRTC.mockRejectedValue(new Error('RTC failed'));
|
|
258
|
+
|
|
259
|
+
listeners.addSocketListener({
|
|
260
|
+
port: 3000,
|
|
261
|
+
targetHost: 'https://remote.example.com',
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
const incomingSocket = {
|
|
265
|
+
socketId: null,
|
|
266
|
+
on: vi.fn(),
|
|
267
|
+
end: vi.fn(),
|
|
268
|
+
};
|
|
269
|
+
|
|
270
|
+
await mockServer.connectionHandler(incomingSocket);
|
|
271
|
+
|
|
272
|
+
expect(incomingSocket.end).toHaveBeenCalled();
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
it('should assign socketId to incoming socket', async () => {
|
|
276
|
+
mockRpcPeer.rtcCon = {};
|
|
277
|
+
|
|
278
|
+
listeners.addSocketListener({
|
|
279
|
+
port: 3000,
|
|
280
|
+
targetHost: 'https://remote.example.com',
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
const incomingSocket = {
|
|
284
|
+
socketId: null,
|
|
285
|
+
on: vi.fn(),
|
|
286
|
+
end: vi.fn(),
|
|
287
|
+
};
|
|
288
|
+
|
|
289
|
+
await mockServer.connectionHandler(incomingSocket);
|
|
290
|
+
|
|
291
|
+
expect(incomingSocket.socketId).toBeTypeOf('string');
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
it('should call connectSocket on peer', async () => {
|
|
295
|
+
mockRpcPeer.rtcCon = {};
|
|
296
|
+
|
|
297
|
+
listeners.addSocketListener({
|
|
298
|
+
port: 3000,
|
|
299
|
+
targetPort: 4000,
|
|
300
|
+
targetHost: 'https://remote.example.com',
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
const incomingSocket = {
|
|
304
|
+
socketId: null,
|
|
305
|
+
on: vi.fn(),
|
|
306
|
+
end: vi.fn(),
|
|
307
|
+
};
|
|
308
|
+
|
|
309
|
+
await mockServer.connectionHandler(incomingSocket);
|
|
310
|
+
|
|
311
|
+
expect(mockRpcPeer.methods.connectSocket).toHaveBeenCalledWith(
|
|
312
|
+
expect.objectContaining({
|
|
313
|
+
port: 4000,
|
|
314
|
+
hostName: 'remote.example.com',
|
|
315
|
+
})
|
|
316
|
+
);
|
|
317
|
+
});
|
|
318
|
+
});
|
|
319
|
+
});
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, vi } from 'vitest';
|
|
2
|
+
import { sockets, handleSocketPacket } from '../../lib/socket-map.js';
|
|
3
|
+
|
|
4
|
+
describe('socket-map', () => {
|
|
5
|
+
beforeEach(() => {
|
|
6
|
+
// Clear sockets before each test
|
|
7
|
+
Object.keys(sockets).forEach((key) => delete sockets[key]);
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
describe('sockets', () => {
|
|
11
|
+
it('should be an empty object initially', () => {
|
|
12
|
+
expect(sockets).toBeDefined();
|
|
13
|
+
expect(typeof sockets).toBe('object');
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
it('should allow adding sockets', () => {
|
|
17
|
+
const mockSocket = { write: vi.fn() };
|
|
18
|
+
sockets['test-id'] = mockSocket;
|
|
19
|
+
|
|
20
|
+
expect(sockets['test-id']).toBe(mockSocket);
|
|
21
|
+
});
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
describe('handleSocketPacket', () => {
|
|
25
|
+
it('should do nothing if socket is not found', () => {
|
|
26
|
+
const packet = {
|
|
27
|
+
topic: 'socketData/unknown-id',
|
|
28
|
+
payload: Buffer.from('test'),
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
// Should not throw
|
|
32
|
+
expect(() => handleSocketPacket(packet)).not.toThrow();
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it('should write data to socket when socketData topic is received', () => {
|
|
36
|
+
const mockSocket = { write: vi.fn() };
|
|
37
|
+
sockets['socket-123'] = mockSocket;
|
|
38
|
+
|
|
39
|
+
const payload = Buffer.from('test data');
|
|
40
|
+
const packet = {
|
|
41
|
+
topic: 'socketData/socket-123',
|
|
42
|
+
payload,
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
handleSocketPacket(packet);
|
|
46
|
+
|
|
47
|
+
expect(mockSocket.write).toHaveBeenCalledWith(payload);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it('should not write for non-socketData topics', () => {
|
|
51
|
+
const mockSocket = { write: vi.fn() };
|
|
52
|
+
sockets['socket-123'] = mockSocket;
|
|
53
|
+
|
|
54
|
+
const packet = {
|
|
55
|
+
topic: 'otherTopic/socket-123',
|
|
56
|
+
payload: Buffer.from('test'),
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
handleSocketPacket(packet);
|
|
60
|
+
|
|
61
|
+
expect(mockSocket.write).not.toHaveBeenCalled();
|
|
62
|
+
});
|
|
63
|
+
});
|
|
64
|
+
});
|