@gjsify/net 0.0.4 → 0.1.1
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/README.md +29 -1
- package/lib/esm/index.js +34 -3
- package/lib/esm/server.js +133 -0
- package/lib/esm/socket.js +332 -0
- package/lib/types/index.d.ts +30 -0
- package/lib/types/server.d.ts +40 -0
- package/lib/types/socket.d.ts +78 -0
- package/package.json +23 -22
- package/src/error.spec.ts +169 -0
- package/src/extended.spec.ts +413 -0
- package/src/index.spec.ts +1072 -80
- package/src/index.ts +47 -12
- package/src/server.spec.ts +303 -0
- package/src/server.ts +186 -0
- package/src/socket.ts +404 -0
- package/src/test.mts +6 -2
- package/src/timeout.spec.ts +464 -0
- package/tsconfig.json +22 -9
- package/tsconfig.tsbuildinfo +1 -0
- package/lib/cjs/index.js +0 -26
- package/test.gjs.mjs +0 -34766
- package/test.node.mjs +0 -366
- package/tsconfig.types.json +0 -8
package/src/index.ts
CHANGED
|
@@ -1,26 +1,61 @@
|
|
|
1
|
+
// Node.js net module for GJS
|
|
2
|
+
// Reference: Node.js lib/net.js
|
|
3
|
+
|
|
1
4
|
import Gio from '@girs/gio-2.0';
|
|
2
5
|
|
|
3
|
-
export
|
|
4
|
-
|
|
6
|
+
export { Socket, SocketConnectOptions } from './socket.js';
|
|
7
|
+
export { Server, ListenOptions } from './server.js';
|
|
8
|
+
import { Socket, SocketConnectOptions } from './socket.js';
|
|
9
|
+
import { Server, ListenOptions } from './server.js';
|
|
5
10
|
|
|
11
|
+
/** Check if input is a valid IP address. Returns 0, 4, or 6. */
|
|
12
|
+
export function isIP(input: string): 0 | 4 | 6 {
|
|
13
|
+
if (typeof input !== 'string') return 0;
|
|
14
|
+
// Strip IPv6 zone ID (e.g. '%eth0') — Gio.InetAddress doesn't handle it
|
|
15
|
+
const stripped = input.includes('%') ? input.split('%')[0] : input;
|
|
16
|
+
const addr = Gio.InetAddress.new_from_string(stripped);
|
|
6
17
|
if (!addr) return 0;
|
|
7
|
-
|
|
8
18
|
const family = addr.get_family();
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
case Gio.SocketFamily.
|
|
12
|
-
|
|
13
|
-
case Gio.SocketFamily.IPV4:
|
|
14
|
-
return 4;
|
|
15
|
-
case Gio.SocketFamily.IPV6:
|
|
16
|
-
return 6;
|
|
19
|
+
switch (family) {
|
|
20
|
+
case Gio.SocketFamily.INVALID: return 0;
|
|
21
|
+
case Gio.SocketFamily.IPV4: return 4;
|
|
22
|
+
case Gio.SocketFamily.IPV6: return 6;
|
|
17
23
|
}
|
|
18
24
|
}
|
|
19
25
|
|
|
26
|
+
/** Check if input is a valid IPv4 address. */
|
|
20
27
|
export function isIPv4(input: string): boolean {
|
|
21
28
|
return isIP(input) === 4;
|
|
22
29
|
}
|
|
23
30
|
|
|
31
|
+
/** Check if input is a valid IPv6 address. */
|
|
24
32
|
export function isIPv6(input: string): boolean {
|
|
25
33
|
return isIP(input) === 6;
|
|
26
|
-
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** Create a new TCP connection. */
|
|
37
|
+
export function createConnection(options: SocketConnectOptions | number, host?: string | (() => void), connectionListener?: () => void): Socket {
|
|
38
|
+
const socket = new Socket();
|
|
39
|
+
return socket.connect(options as any, host as any, connectionListener);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** Alias for createConnection. */
|
|
43
|
+
export const connect = createConnection;
|
|
44
|
+
|
|
45
|
+
/** Create a new TCP server. */
|
|
46
|
+
export function createServer(connectionListener?: (socket: Socket) => void): Server;
|
|
47
|
+
export function createServer(options?: { allowHalfOpen?: boolean }, connectionListener?: (socket: Socket) => void): Server;
|
|
48
|
+
export function createServer(optionsOrListener?: any, connectionListener?: (socket: Socket) => void): Server {
|
|
49
|
+
return new Server(optionsOrListener, connectionListener);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export default {
|
|
53
|
+
Socket,
|
|
54
|
+
Server,
|
|
55
|
+
isIP,
|
|
56
|
+
isIPv4,
|
|
57
|
+
isIPv6,
|
|
58
|
+
createConnection,
|
|
59
|
+
connect,
|
|
60
|
+
createServer,
|
|
61
|
+
};
|
|
@@ -0,0 +1,303 @@
|
|
|
1
|
+
// TCP server/client integration tests — connection lifecycle, data transfer, error handling
|
|
2
|
+
// Reference: refs/node-test/parallel/test-net-*.js
|
|
3
|
+
|
|
4
|
+
import { describe, it, expect } from '@gjsify/unit';
|
|
5
|
+
import { createServer, createConnection, Socket, Server } from 'node:net';
|
|
6
|
+
import { Buffer } from 'node:buffer';
|
|
7
|
+
|
|
8
|
+
/** Helper: create server, run test, cleanup */
|
|
9
|
+
function withServer(handler: (server: Server, port: number) => Promise<void>): Promise<void> {
|
|
10
|
+
return new Promise((resolve, reject) => {
|
|
11
|
+
const server = createServer();
|
|
12
|
+
server.listen(0, '127.0.0.1', () => {
|
|
13
|
+
const addr = server.address() as { port: number };
|
|
14
|
+
handler(server, addr.port)
|
|
15
|
+
.then(() => {
|
|
16
|
+
server.close();
|
|
17
|
+
resolve();
|
|
18
|
+
})
|
|
19
|
+
.catch((err) => {
|
|
20
|
+
server.close();
|
|
21
|
+
reject(err);
|
|
22
|
+
});
|
|
23
|
+
});
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export default async () => {
|
|
28
|
+
// ---- TCP Echo ----
|
|
29
|
+
|
|
30
|
+
await describe('net TCP: echo server', async () => {
|
|
31
|
+
await it('should echo data back to client', async () => {
|
|
32
|
+
await withServer(async (server, port) => {
|
|
33
|
+
server.on('connection', (socket) => {
|
|
34
|
+
socket.on('data', (data) => socket.write(data));
|
|
35
|
+
});
|
|
36
|
+
const result = await new Promise<string>((resolve, reject) => {
|
|
37
|
+
const client = createConnection({ port, host: '127.0.0.1' }, () => {
|
|
38
|
+
client.write('hello echo');
|
|
39
|
+
});
|
|
40
|
+
client.setEncoding('utf8');
|
|
41
|
+
client.on('data', (data) => {
|
|
42
|
+
client.end();
|
|
43
|
+
resolve(data);
|
|
44
|
+
});
|
|
45
|
+
client.on('error', reject);
|
|
46
|
+
});
|
|
47
|
+
expect(result).toBe('hello echo');
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
await it('should echo a second message after first completes', async () => {
|
|
52
|
+
await withServer(async (server, port) => {
|
|
53
|
+
server.on('connection', (socket) => {
|
|
54
|
+
socket.on('data', (data) => socket.write(data));
|
|
55
|
+
});
|
|
56
|
+
// Send one message, wait for echo, then send another
|
|
57
|
+
const result = await new Promise<string>((resolve, reject) => {
|
|
58
|
+
const client = createConnection({ port, host: '127.0.0.1' }, () => {
|
|
59
|
+
client.write('first');
|
|
60
|
+
});
|
|
61
|
+
client.setEncoding('utf8');
|
|
62
|
+
let received = '';
|
|
63
|
+
client.on('data', (data) => {
|
|
64
|
+
received += data;
|
|
65
|
+
if (received === 'first') {
|
|
66
|
+
// Send second message after first echo completes
|
|
67
|
+
client.write('second');
|
|
68
|
+
} else if (received.includes('second')) {
|
|
69
|
+
client.end();
|
|
70
|
+
resolve(received);
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
client.on('error', reject);
|
|
74
|
+
});
|
|
75
|
+
expect(result).toBe('firstsecond');
|
|
76
|
+
});
|
|
77
|
+
});
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
// ---- Large data transfer ----
|
|
81
|
+
|
|
82
|
+
await describe('net TCP: large data transfer', async () => {
|
|
83
|
+
await it('should transfer 64KB of data', async () => {
|
|
84
|
+
const size = 64 * 1024;
|
|
85
|
+
const data = Buffer.alloc(size, 0x41); // 'A'
|
|
86
|
+
await withServer(async (server, port) => {
|
|
87
|
+
server.on('connection', (socket) => {
|
|
88
|
+
socket.write(data);
|
|
89
|
+
socket.end();
|
|
90
|
+
});
|
|
91
|
+
const received = await new Promise<Buffer>((resolve, reject) => {
|
|
92
|
+
const chunks: Buffer[] = [];
|
|
93
|
+
const client = createConnection({ port, host: '127.0.0.1' });
|
|
94
|
+
client.on('data', (chunk) => chunks.push(Buffer.from(chunk)));
|
|
95
|
+
client.on('end', () => resolve(Buffer.concat(chunks)));
|
|
96
|
+
client.on('error', reject);
|
|
97
|
+
});
|
|
98
|
+
expect(received.length).toBe(size);
|
|
99
|
+
});
|
|
100
|
+
});
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
// ---- Connection events ----
|
|
104
|
+
|
|
105
|
+
await describe('net TCP: connection events', async () => {
|
|
106
|
+
await it('should emit connect event on client', async () => {
|
|
107
|
+
await withServer(async (server, port) => {
|
|
108
|
+
server.on('connection', (socket) => socket.end());
|
|
109
|
+
const connected = await new Promise<boolean>((resolve) => {
|
|
110
|
+
const client = createConnection({ port, host: '127.0.0.1' });
|
|
111
|
+
client.on('connect', () => {
|
|
112
|
+
client.end();
|
|
113
|
+
resolve(true);
|
|
114
|
+
});
|
|
115
|
+
});
|
|
116
|
+
expect(connected).toBe(true);
|
|
117
|
+
});
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
await it('should emit close event after end', async () => {
|
|
121
|
+
await withServer(async (server, port) => {
|
|
122
|
+
server.on('connection', (socket) => socket.end('bye'));
|
|
123
|
+
const closed = await new Promise<boolean>((resolve) => {
|
|
124
|
+
const client = createConnection({ port, host: '127.0.0.1' });
|
|
125
|
+
client.on('close', () => resolve(true));
|
|
126
|
+
client.resume(); // Consume data to allow close
|
|
127
|
+
});
|
|
128
|
+
expect(closed).toBe(true);
|
|
129
|
+
});
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
await it('should emit end event when server closes', async () => {
|
|
133
|
+
await withServer(async (server, port) => {
|
|
134
|
+
server.on('connection', (socket) => socket.end());
|
|
135
|
+
const ended = await new Promise<boolean>((resolve) => {
|
|
136
|
+
const client = createConnection({ port, host: '127.0.0.1' });
|
|
137
|
+
client.on('end', () => {
|
|
138
|
+
client.end();
|
|
139
|
+
resolve(true);
|
|
140
|
+
});
|
|
141
|
+
});
|
|
142
|
+
expect(ended).toBe(true);
|
|
143
|
+
});
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
await it('should track connection count via server event', async () => {
|
|
147
|
+
let connectionCount = 0;
|
|
148
|
+
await withServer(async (server, port) => {
|
|
149
|
+
server.on('connection', (socket) => {
|
|
150
|
+
connectionCount++;
|
|
151
|
+
socket.end();
|
|
152
|
+
});
|
|
153
|
+
for (let i = 0; i < 3; i++) {
|
|
154
|
+
await new Promise<void>((resolve) => {
|
|
155
|
+
const client = createConnection({ port, host: '127.0.0.1' });
|
|
156
|
+
client.on('close', () => resolve());
|
|
157
|
+
client.resume();
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
});
|
|
161
|
+
expect(connectionCount).toBe(3);
|
|
162
|
+
});
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
// ---- Server properties ----
|
|
166
|
+
|
|
167
|
+
await describe('net TCP: server properties', async () => {
|
|
168
|
+
await it('should return address with port and family', async () => {
|
|
169
|
+
await withServer(async (server, port) => {
|
|
170
|
+
const addr = server.address() as { port: number; family: string; address: string };
|
|
171
|
+
expect(addr.port).toBe(port);
|
|
172
|
+
expect(addr.port).toBeGreaterThan(0);
|
|
173
|
+
expect(typeof addr.address).toBe('string');
|
|
174
|
+
});
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
await it('should set listening to true after listen', async () => {
|
|
178
|
+
await withServer(async (server) => {
|
|
179
|
+
expect(server.listening).toBe(true);
|
|
180
|
+
});
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
await it('should allocate random port when 0 specified', async () => {
|
|
184
|
+
const server = createServer();
|
|
185
|
+
await new Promise<void>((resolve) => server.listen(0, '127.0.0.1', () => resolve()));
|
|
186
|
+
const addr = server.address() as { port: number };
|
|
187
|
+
expect(addr.port).toBeGreaterThan(0);
|
|
188
|
+
expect(addr.port).toBeLessThan(65536);
|
|
189
|
+
await new Promise<void>((resolve) => server.close(() => resolve()));
|
|
190
|
+
});
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
// ---- Socket properties ----
|
|
194
|
+
|
|
195
|
+
await describe('net TCP: socket properties', async () => {
|
|
196
|
+
await it('should expose remoteAddress and remotePort after connect', async () => {
|
|
197
|
+
await withServer(async (server, port) => {
|
|
198
|
+
server.on('connection', (socket) => socket.end());
|
|
199
|
+
const { remoteAddress, remotePort } = await new Promise<{ remoteAddress: string; remotePort: number }>((resolve) => {
|
|
200
|
+
const client = createConnection({ port, host: '127.0.0.1' }, () => {
|
|
201
|
+
resolve({
|
|
202
|
+
remoteAddress: client.remoteAddress!,
|
|
203
|
+
remotePort: client.remotePort!,
|
|
204
|
+
});
|
|
205
|
+
client.end();
|
|
206
|
+
});
|
|
207
|
+
});
|
|
208
|
+
expect(remoteAddress).toBe('127.0.0.1');
|
|
209
|
+
expect(remotePort).toBe(port);
|
|
210
|
+
});
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
await it('should expose localPort after connect', async () => {
|
|
214
|
+
await withServer(async (server, port) => {
|
|
215
|
+
server.on('connection', (socket) => socket.end());
|
|
216
|
+
const localPort = await new Promise<number>((resolve) => {
|
|
217
|
+
const client = createConnection({ port, host: '127.0.0.1' }, () => {
|
|
218
|
+
resolve(client.localPort!);
|
|
219
|
+
client.end();
|
|
220
|
+
});
|
|
221
|
+
});
|
|
222
|
+
expect(localPort).toBeGreaterThan(0);
|
|
223
|
+
});
|
|
224
|
+
});
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
// ---- Data encoding ----
|
|
228
|
+
|
|
229
|
+
await describe('net TCP: data encoding', async () => {
|
|
230
|
+
await it('should transfer UTF-8 strings', async () => {
|
|
231
|
+
const testStr = 'Hallo Welt! 你好世界';
|
|
232
|
+
await withServer(async (server, port) => {
|
|
233
|
+
server.on('connection', (socket) => {
|
|
234
|
+
socket.write(testStr, 'utf8');
|
|
235
|
+
socket.end();
|
|
236
|
+
});
|
|
237
|
+
const received = await new Promise<string>((resolve, reject) => {
|
|
238
|
+
const chunks: string[] = [];
|
|
239
|
+
const client = createConnection({ port, host: '127.0.0.1' });
|
|
240
|
+
client.setEncoding('utf8');
|
|
241
|
+
client.on('data', (data) => chunks.push(data));
|
|
242
|
+
client.on('end', () => resolve(chunks.join('')));
|
|
243
|
+
client.on('error', reject);
|
|
244
|
+
});
|
|
245
|
+
expect(received).toBe(testStr);
|
|
246
|
+
});
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
await it('should transfer binary data', async () => {
|
|
250
|
+
const binary = Buffer.from([0x00, 0x01, 0x80, 0xff, 0xfe]);
|
|
251
|
+
await withServer(async (server, port) => {
|
|
252
|
+
server.on('connection', (socket) => {
|
|
253
|
+
socket.write(binary);
|
|
254
|
+
socket.end();
|
|
255
|
+
});
|
|
256
|
+
const received = await new Promise<Buffer>((resolve, reject) => {
|
|
257
|
+
const chunks: Buffer[] = [];
|
|
258
|
+
const client = createConnection({ port, host: '127.0.0.1' });
|
|
259
|
+
client.on('data', (chunk) => chunks.push(Buffer.from(chunk)));
|
|
260
|
+
client.on('end', () => resolve(Buffer.concat(chunks)));
|
|
261
|
+
client.on('error', reject);
|
|
262
|
+
});
|
|
263
|
+
expect(received.length).toBe(5);
|
|
264
|
+
expect(received[0]).toBe(0x00);
|
|
265
|
+
expect(received[2]).toBe(0x80);
|
|
266
|
+
expect(received[4]).toBe(0xfe);
|
|
267
|
+
});
|
|
268
|
+
});
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
// ---- Error handling ----
|
|
272
|
+
|
|
273
|
+
await describe('net TCP: error handling', async () => {
|
|
274
|
+
await it('should emit error for connection refused', async () => {
|
|
275
|
+
const err = await new Promise<Error>((resolve) => {
|
|
276
|
+
// Port 1 is almost certainly not listening
|
|
277
|
+
const client = createConnection({ port: 1, host: '127.0.0.1' });
|
|
278
|
+
client.on('error', (e) => resolve(e));
|
|
279
|
+
});
|
|
280
|
+
expect(err).toBeDefined();
|
|
281
|
+
expect((err as NodeJS.ErrnoException).code).toBeDefined();
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
await it('should handle server close while client connected', async () => {
|
|
285
|
+
const server = createServer((socket) => {
|
|
286
|
+
// Close server immediately
|
|
287
|
+
server.close();
|
|
288
|
+
socket.end('goodbye');
|
|
289
|
+
});
|
|
290
|
+
await new Promise<void>((resolve) => server.listen(0, '127.0.0.1', () => resolve()));
|
|
291
|
+
const port = (server.address() as { port: number }).port;
|
|
292
|
+
|
|
293
|
+
const data = await new Promise<string>((resolve) => {
|
|
294
|
+
const client = createConnection({ port, host: '127.0.0.1' });
|
|
295
|
+
client.setEncoding('utf8');
|
|
296
|
+
const chunks: string[] = [];
|
|
297
|
+
client.on('data', (chunk) => chunks.push(chunk));
|
|
298
|
+
client.on('end', () => resolve(chunks.join('')));
|
|
299
|
+
});
|
|
300
|
+
expect(data).toBe('goodbye');
|
|
301
|
+
});
|
|
302
|
+
});
|
|
303
|
+
};
|
package/src/server.ts
ADDED
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
// Reference: Node.js lib/net.js
|
|
2
|
+
// Reimplemented for GJS using Gio.SocketService
|
|
3
|
+
|
|
4
|
+
import Gio from '@girs/gio-2.0';
|
|
5
|
+
import GLib from '@girs/glib-2.0';
|
|
6
|
+
import { EventEmitter } from 'node:events';
|
|
7
|
+
import { createNodeError, deferEmit, ensureMainLoop } from '@gjsify/utils';
|
|
8
|
+
import type { ErrnoException } from '@gjsify/utils';
|
|
9
|
+
import { Socket } from './socket.js';
|
|
10
|
+
|
|
11
|
+
export interface ListenOptions {
|
|
12
|
+
port?: number;
|
|
13
|
+
host?: string;
|
|
14
|
+
backlog?: number;
|
|
15
|
+
exclusive?: boolean;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// GC guard — GJS garbage-collects objects with no JS references.
|
|
19
|
+
// Keep strong references to all listening servers to prevent their
|
|
20
|
+
// Gio.SocketService from being collected while still active.
|
|
21
|
+
const _activeServers = new Set<Server>();
|
|
22
|
+
|
|
23
|
+
export class Server extends EventEmitter {
|
|
24
|
+
listening = false;
|
|
25
|
+
maxConnections?: number;
|
|
26
|
+
allowHalfOpen: boolean;
|
|
27
|
+
|
|
28
|
+
private _service: Gio.SocketService | null = null;
|
|
29
|
+
private _connections = new Set<Socket>();
|
|
30
|
+
private _address: { port: number; family: string; address: string } | null = null;
|
|
31
|
+
|
|
32
|
+
constructor(connectionListener?: (socket: Socket) => void);
|
|
33
|
+
constructor(options?: { allowHalfOpen?: boolean }, connectionListener?: (socket: Socket) => void);
|
|
34
|
+
constructor(
|
|
35
|
+
optionsOrListener?: { allowHalfOpen?: boolean } | ((socket: Socket) => void),
|
|
36
|
+
connectionListener?: (socket: Socket) => void,
|
|
37
|
+
) {
|
|
38
|
+
super();
|
|
39
|
+
|
|
40
|
+
if (typeof optionsOrListener === 'function') {
|
|
41
|
+
connectionListener = optionsOrListener;
|
|
42
|
+
this.allowHalfOpen = false;
|
|
43
|
+
} else {
|
|
44
|
+
this.allowHalfOpen = optionsOrListener?.allowHalfOpen ?? false;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if (connectionListener) {
|
|
48
|
+
this.on('connection', connectionListener);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Start listening for connections.
|
|
54
|
+
*/
|
|
55
|
+
listen(port?: number, host?: string, backlog?: number, callback?: () => void): this;
|
|
56
|
+
listen(port?: number, host?: string, callback?: () => void): this;
|
|
57
|
+
listen(port?: number, callback?: () => void): this;
|
|
58
|
+
listen(options?: ListenOptions, callback?: () => void): this;
|
|
59
|
+
listen(...args: unknown[]): this {
|
|
60
|
+
let port = 0;
|
|
61
|
+
let host = '0.0.0.0';
|
|
62
|
+
let backlog = 511;
|
|
63
|
+
let callback: (() => void) | undefined;
|
|
64
|
+
|
|
65
|
+
// Parse overloaded arguments
|
|
66
|
+
if (typeof args[0] === 'object' && args[0] !== null && !Array.isArray(args[0])) {
|
|
67
|
+
const opts = args[0] as ListenOptions;
|
|
68
|
+
port = opts.port ?? 0;
|
|
69
|
+
host = opts.host ?? '0.0.0.0';
|
|
70
|
+
backlog = opts.backlog ?? 511;
|
|
71
|
+
callback = args[1] as (() => void) | undefined;
|
|
72
|
+
} else {
|
|
73
|
+
if (typeof args[0] === 'number') port = args[0];
|
|
74
|
+
for (let i = 1; i < args.length; i++) {
|
|
75
|
+
if (typeof args[i] === 'string') host = args[i] as string;
|
|
76
|
+
else if (typeof args[i] === 'number') backlog = args[i] as number;
|
|
77
|
+
else if (typeof args[i] === 'function') callback = args[i] as () => void;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if (callback) {
|
|
82
|
+
this.once('listening', callback);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
try {
|
|
86
|
+
this._service = new Gio.SocketService();
|
|
87
|
+
this._service.set_backlog(backlog);
|
|
88
|
+
|
|
89
|
+
let actualPort: number;
|
|
90
|
+
if (port === 0) {
|
|
91
|
+
actualPort = this._service.add_any_inet_port(null);
|
|
92
|
+
} else {
|
|
93
|
+
this._service.add_inet_port(port, null);
|
|
94
|
+
actualPort = port;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Connect the incoming signal
|
|
98
|
+
this._service.connect('incoming', (_service: Gio.SocketService, connection: Gio.SocketConnection) => {
|
|
99
|
+
this._handleConnection(connection);
|
|
100
|
+
return true;
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
this._service.start();
|
|
104
|
+
ensureMainLoop();
|
|
105
|
+
this.listening = true;
|
|
106
|
+
_activeServers.add(this);
|
|
107
|
+
|
|
108
|
+
// Determine address info
|
|
109
|
+
const family = host.includes(':') ? 'IPv6' : 'IPv4';
|
|
110
|
+
this._address = { port: actualPort, family, address: host };
|
|
111
|
+
|
|
112
|
+
// Emit listening asynchronously (matching Node.js behavior)
|
|
113
|
+
deferEmit(this, 'listening');
|
|
114
|
+
} catch (err: unknown) {
|
|
115
|
+
const nodeErr = createNodeError(err, 'listen', { address: host, port });
|
|
116
|
+
deferEmit(this, 'error', nodeErr);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
return this;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
private _handleConnection(connection: Gio.SocketConnection): void {
|
|
123
|
+
if (this.maxConnections && this._connections.size >= this.maxConnections) {
|
|
124
|
+
try { connection.close(null); } catch { /* ignore */ }
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Create a Socket wrapping this connection
|
|
129
|
+
const socket = new Socket({ allowHalfOpen: this.allowHalfOpen });
|
|
130
|
+
|
|
131
|
+
// Inject the connection directly (bypass connect())
|
|
132
|
+
socket._setConnection(connection);
|
|
133
|
+
socket._setupConnection({});
|
|
134
|
+
|
|
135
|
+
this._connections.add(socket);
|
|
136
|
+
socket.on('close', () => {
|
|
137
|
+
this._connections.delete(socket);
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
this.emit('connection', socket);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/** Get the address the server is listening on. */
|
|
144
|
+
address(): { port: number; family: string; address: string } | null {
|
|
145
|
+
return this._address;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/** Close the server, stop accepting new connections. */
|
|
149
|
+
close(callback?: (err?: Error) => void): this {
|
|
150
|
+
if (callback) {
|
|
151
|
+
this.once('close', callback);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
if (!this._service || !this.listening) {
|
|
155
|
+
setTimeout(() => {
|
|
156
|
+
const err = new Error('Server is not running') as ErrnoException;
|
|
157
|
+
err.code = 'ERR_SERVER_NOT_RUNNING';
|
|
158
|
+
this.emit('error', err);
|
|
159
|
+
}, 0);
|
|
160
|
+
return this;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
this._service.stop();
|
|
164
|
+
this._service.close();
|
|
165
|
+
this._service = null;
|
|
166
|
+
this.listening = false;
|
|
167
|
+
_activeServers.delete(this);
|
|
168
|
+
|
|
169
|
+
// Close all existing connections
|
|
170
|
+
for (const socket of this._connections) {
|
|
171
|
+
socket.destroy();
|
|
172
|
+
}
|
|
173
|
+
this._connections.clear();
|
|
174
|
+
|
|
175
|
+
deferEmit(this, 'close');
|
|
176
|
+
return this;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/** Get the number of concurrent connections. */
|
|
180
|
+
getConnections(callback: (err: Error | null, count: number) => void): void {
|
|
181
|
+
callback(null, this._connections.size);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
ref(): this { return this; }
|
|
185
|
+
unref(): this { return this; }
|
|
186
|
+
}
|