recker 1.0.19 → 1.0.20-next.595cc59
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/dist/cli/index.d.ts +0 -1
- package/dist/cli/index.js +495 -2
- package/dist/cli/tui/shell.js +1 -1
- package/dist/core/client.d.ts +2 -0
- package/dist/core/client.d.ts.map +1 -1
- package/dist/core/client.js +6 -7
- package/dist/plugins/cache.js +1 -1
- package/dist/plugins/hls.d.ts +90 -17
- package/dist/plugins/hls.d.ts.map +1 -1
- package/dist/plugins/hls.js +343 -173
- package/dist/plugins/retry.js +2 -2
- package/dist/testing/index.d.ts +16 -0
- package/dist/testing/index.d.ts.map +1 -1
- package/dist/testing/index.js +8 -0
- package/dist/testing/mock-dns-server.d.ts +70 -0
- package/dist/testing/mock-dns-server.d.ts.map +1 -0
- package/dist/testing/mock-dns-server.js +269 -0
- package/dist/testing/mock-ftp-server.d.ts +90 -0
- package/dist/testing/mock-ftp-server.d.ts.map +1 -0
- package/dist/testing/mock-ftp-server.js +562 -0
- package/dist/testing/mock-hls-server.d.ts +81 -0
- package/dist/testing/mock-hls-server.d.ts.map +1 -0
- package/dist/testing/mock-hls-server.js +381 -0
- package/dist/testing/mock-http-server.d.ts +100 -0
- package/dist/testing/mock-http-server.d.ts.map +1 -0
- package/dist/testing/mock-http-server.js +298 -0
- package/dist/testing/mock-sse-server.d.ts +77 -0
- package/dist/testing/mock-sse-server.d.ts.map +1 -0
- package/dist/testing/mock-sse-server.js +291 -0
- package/dist/testing/mock-telnet-server.d.ts +60 -0
- package/dist/testing/mock-telnet-server.d.ts.map +1 -0
- package/dist/testing/mock-telnet-server.js +273 -0
- package/dist/testing/mock-websocket-server.d.ts +78 -0
- package/dist/testing/mock-websocket-server.d.ts.map +1 -0
- package/dist/testing/mock-websocket-server.js +316 -0
- package/dist/testing/mock-whois-server.d.ts +57 -0
- package/dist/testing/mock-whois-server.d.ts.map +1 -0
- package/dist/testing/mock-whois-server.js +234 -0
- package/dist/transport/undici.d.ts +1 -1
- package/dist/transport/undici.d.ts.map +1 -1
- package/dist/transport/undici.js +11 -5
- package/dist/utils/dns-toolkit.js +1 -1
- package/dist/utils/dns.js +2 -2
- package/dist/utils/optional-require.js +1 -1
- package/dist/webrtc/index.js +1 -1
- package/package.json +3 -1
|
@@ -0,0 +1,316 @@
|
|
|
1
|
+
import { EventEmitter } from 'node:events';
|
|
2
|
+
import { WebSocketServer, WebSocket } from 'ws';
|
|
3
|
+
import { createServer } from 'node:http';
|
|
4
|
+
export class MockWebSocketServer extends EventEmitter {
|
|
5
|
+
options;
|
|
6
|
+
httpServer = null;
|
|
7
|
+
wss = null;
|
|
8
|
+
clients = new Map();
|
|
9
|
+
responses = new Map();
|
|
10
|
+
_port = 0;
|
|
11
|
+
_started = false;
|
|
12
|
+
clientIdCounter = 0;
|
|
13
|
+
stats = {
|
|
14
|
+
totalConnections: 0,
|
|
15
|
+
currentConnections: 0,
|
|
16
|
+
totalMessages: 0,
|
|
17
|
+
totalBytesSent: 0,
|
|
18
|
+
totalBytesReceived: 0,
|
|
19
|
+
messageLog: [],
|
|
20
|
+
};
|
|
21
|
+
constructor(options = {}) {
|
|
22
|
+
super();
|
|
23
|
+
this.options = {
|
|
24
|
+
port: 0,
|
|
25
|
+
host: '127.0.0.1',
|
|
26
|
+
path: '/',
|
|
27
|
+
echo: true,
|
|
28
|
+
delay: 0,
|
|
29
|
+
protocols: [],
|
|
30
|
+
autoCloseAfter: 0,
|
|
31
|
+
dropRate: 0,
|
|
32
|
+
maxConnections: 0,
|
|
33
|
+
...options,
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
get port() {
|
|
37
|
+
return this._port;
|
|
38
|
+
}
|
|
39
|
+
get address() {
|
|
40
|
+
return this.options.host;
|
|
41
|
+
}
|
|
42
|
+
get url() {
|
|
43
|
+
return `ws://${this.options.host}:${this._port}${this.options.path}`;
|
|
44
|
+
}
|
|
45
|
+
get isRunning() {
|
|
46
|
+
return this._started;
|
|
47
|
+
}
|
|
48
|
+
get connectionCount() {
|
|
49
|
+
return this.clients.size;
|
|
50
|
+
}
|
|
51
|
+
get statistics() {
|
|
52
|
+
return { ...this.stats };
|
|
53
|
+
}
|
|
54
|
+
get allClients() {
|
|
55
|
+
return [...this.clients.values()];
|
|
56
|
+
}
|
|
57
|
+
async start() {
|
|
58
|
+
if (this._started) {
|
|
59
|
+
throw new Error('Server already started');
|
|
60
|
+
}
|
|
61
|
+
return new Promise((resolve, reject) => {
|
|
62
|
+
this.httpServer = createServer();
|
|
63
|
+
this.wss = new WebSocketServer({
|
|
64
|
+
server: this.httpServer,
|
|
65
|
+
path: this.options.path,
|
|
66
|
+
handleProtocols: this.options.protocols.length > 0
|
|
67
|
+
? (protocols) => {
|
|
68
|
+
for (const p of protocols) {
|
|
69
|
+
if (this.options.protocols.includes(p)) {
|
|
70
|
+
return p;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return false;
|
|
74
|
+
}
|
|
75
|
+
: undefined,
|
|
76
|
+
});
|
|
77
|
+
this.wss.on('connection', (socket, req) => this.handleConnection(socket, req));
|
|
78
|
+
this.wss.on('error', (err) => this.emit('error', err));
|
|
79
|
+
this.httpServer.on('error', reject);
|
|
80
|
+
this.httpServer.listen(this.options.port, this.options.host, () => {
|
|
81
|
+
const addr = this.httpServer.address();
|
|
82
|
+
this._port = typeof addr === 'string' ? 0 : addr?.port ?? 0;
|
|
83
|
+
this._started = true;
|
|
84
|
+
this.emit('listening', this._port);
|
|
85
|
+
resolve();
|
|
86
|
+
});
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
async stop() {
|
|
90
|
+
if (!this._started)
|
|
91
|
+
return;
|
|
92
|
+
for (const client of this.clients.values()) {
|
|
93
|
+
client.socket.close(1001, 'Server shutting down');
|
|
94
|
+
}
|
|
95
|
+
this.clients.clear();
|
|
96
|
+
return new Promise((resolve) => {
|
|
97
|
+
this.wss?.close(() => {
|
|
98
|
+
this.httpServer?.close(() => {
|
|
99
|
+
this._started = false;
|
|
100
|
+
this.wss = null;
|
|
101
|
+
this.httpServer = null;
|
|
102
|
+
this.emit('close');
|
|
103
|
+
resolve();
|
|
104
|
+
});
|
|
105
|
+
});
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
reset() {
|
|
109
|
+
this.responses.clear();
|
|
110
|
+
this.stats = {
|
|
111
|
+
totalConnections: 0,
|
|
112
|
+
currentConnections: 0,
|
|
113
|
+
totalMessages: 0,
|
|
114
|
+
totalBytesSent: 0,
|
|
115
|
+
totalBytesReceived: 0,
|
|
116
|
+
messageLog: [],
|
|
117
|
+
};
|
|
118
|
+
this.options.echo = true;
|
|
119
|
+
this.options.delay = 0;
|
|
120
|
+
this.options.dropRate = 0;
|
|
121
|
+
this.emit('reset');
|
|
122
|
+
}
|
|
123
|
+
setResponse(pattern, response) {
|
|
124
|
+
const key = pattern instanceof RegExp ? pattern.source : pattern;
|
|
125
|
+
this.responses.set(key, response);
|
|
126
|
+
}
|
|
127
|
+
clearResponses() {
|
|
128
|
+
this.responses.clear();
|
|
129
|
+
}
|
|
130
|
+
setEcho(enabled) {
|
|
131
|
+
this.options.echo = enabled;
|
|
132
|
+
}
|
|
133
|
+
setDelay(delay) {
|
|
134
|
+
this.options.delay = delay;
|
|
135
|
+
}
|
|
136
|
+
setDropRate(rate) {
|
|
137
|
+
this.options.dropRate = Math.max(0, Math.min(1, rate));
|
|
138
|
+
}
|
|
139
|
+
getClient(id) {
|
|
140
|
+
return this.clients.get(id);
|
|
141
|
+
}
|
|
142
|
+
disconnectClient(id, code = 1000, reason = 'Disconnected by server') {
|
|
143
|
+
const client = this.clients.get(id);
|
|
144
|
+
if (client) {
|
|
145
|
+
client.socket.close(code, reason);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
disconnectAll(code = 1000, reason = 'Disconnected by server') {
|
|
149
|
+
for (const client of this.clients.values()) {
|
|
150
|
+
client.socket.close(code, reason);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
send(clientId, data) {
|
|
154
|
+
const client = this.clients.get(clientId);
|
|
155
|
+
if (!client || client.socket.readyState !== WebSocket.OPEN) {
|
|
156
|
+
return false;
|
|
157
|
+
}
|
|
158
|
+
const message = typeof data === 'object' && !Buffer.isBuffer(data)
|
|
159
|
+
? JSON.stringify(data)
|
|
160
|
+
: data;
|
|
161
|
+
client.socket.send(message);
|
|
162
|
+
this.stats.totalBytesSent += Buffer.byteLength(message);
|
|
163
|
+
return true;
|
|
164
|
+
}
|
|
165
|
+
broadcast(data) {
|
|
166
|
+
let sent = 0;
|
|
167
|
+
for (const client of this.clients.values()) {
|
|
168
|
+
if (this.send(client.id, data)) {
|
|
169
|
+
sent++;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
return sent;
|
|
173
|
+
}
|
|
174
|
+
sendToAll(data) {
|
|
175
|
+
return this.broadcast(data);
|
|
176
|
+
}
|
|
177
|
+
simulateMessage(clientId, data) {
|
|
178
|
+
const client = this.clients.get(clientId);
|
|
179
|
+
if (client) {
|
|
180
|
+
const rawData = Buffer.isBuffer(data) ? data : Buffer.from(data);
|
|
181
|
+
this.handleMessage(client, rawData, Buffer.isBuffer(data));
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
ping(clientId) {
|
|
185
|
+
if (clientId) {
|
|
186
|
+
const client = this.clients.get(clientId);
|
|
187
|
+
client?.socket.ping();
|
|
188
|
+
}
|
|
189
|
+
else {
|
|
190
|
+
for (const client of this.clients.values()) {
|
|
191
|
+
client.socket.ping();
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
async waitForConnections(count, timeout = 5000) {
|
|
196
|
+
const start = Date.now();
|
|
197
|
+
while (this.clients.size < count) {
|
|
198
|
+
if (Date.now() - start > timeout) {
|
|
199
|
+
throw new Error(`Timeout waiting for ${count} connections (have ${this.clients.size})`);
|
|
200
|
+
}
|
|
201
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
202
|
+
}
|
|
203
|
+
return [...this.clients.values()].slice(0, count);
|
|
204
|
+
}
|
|
205
|
+
async waitForMessages(count, timeout = 5000) {
|
|
206
|
+
const start = Date.now();
|
|
207
|
+
while (this.stats.messageLog.length < count) {
|
|
208
|
+
if (Date.now() - start > timeout) {
|
|
209
|
+
throw new Error(`Timeout waiting for ${count} messages (have ${this.stats.messageLog.length})`);
|
|
210
|
+
}
|
|
211
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
212
|
+
}
|
|
213
|
+
return this.stats.messageLog.slice(0, count);
|
|
214
|
+
}
|
|
215
|
+
handleConnection(socket, _req) {
|
|
216
|
+
if (this.options.maxConnections > 0 && this.clients.size >= this.options.maxConnections) {
|
|
217
|
+
socket.close(1013, 'Max connections reached');
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
if (this.options.dropRate > 0 && Math.random() < this.options.dropRate) {
|
|
221
|
+
socket.close(1006, 'Connection dropped');
|
|
222
|
+
this.emit('dropped');
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
const clientId = `client-${++this.clientIdCounter}`;
|
|
226
|
+
const client = {
|
|
227
|
+
id: clientId,
|
|
228
|
+
socket,
|
|
229
|
+
connectedAt: Date.now(),
|
|
230
|
+
messageCount: 0,
|
|
231
|
+
metadata: {},
|
|
232
|
+
};
|
|
233
|
+
this.clients.set(clientId, client);
|
|
234
|
+
this.stats.totalConnections++;
|
|
235
|
+
this.stats.currentConnections++;
|
|
236
|
+
this.emit('connection', client);
|
|
237
|
+
if (this.options.autoCloseAfter > 0) {
|
|
238
|
+
setTimeout(() => {
|
|
239
|
+
if (socket.readyState === WebSocket.OPEN) {
|
|
240
|
+
socket.close(1000, 'Auto-close timeout');
|
|
241
|
+
}
|
|
242
|
+
}, this.options.autoCloseAfter);
|
|
243
|
+
}
|
|
244
|
+
socket.on('message', (data, isBinary) => {
|
|
245
|
+
this.handleMessage(client, data, isBinary);
|
|
246
|
+
});
|
|
247
|
+
socket.on('close', (code, reason) => {
|
|
248
|
+
this.clients.delete(clientId);
|
|
249
|
+
this.stats.currentConnections--;
|
|
250
|
+
this.emit('disconnect', client, code, reason.toString());
|
|
251
|
+
});
|
|
252
|
+
socket.on('error', (err) => {
|
|
253
|
+
this.emit('clientError', client, err);
|
|
254
|
+
});
|
|
255
|
+
socket.on('ping', () => {
|
|
256
|
+
this.emit('ping', client);
|
|
257
|
+
});
|
|
258
|
+
socket.on('pong', () => {
|
|
259
|
+
this.emit('pong', client);
|
|
260
|
+
});
|
|
261
|
+
}
|
|
262
|
+
async handleMessage(client, data, isBinary) {
|
|
263
|
+
const bytes = Buffer.isBuffer(data) ? data.length : Buffer.byteLength(data.toString());
|
|
264
|
+
this.stats.totalBytesReceived += bytes;
|
|
265
|
+
this.stats.totalMessages++;
|
|
266
|
+
client.messageCount++;
|
|
267
|
+
client.lastMessage = data;
|
|
268
|
+
const message = {
|
|
269
|
+
clientId: client.id,
|
|
270
|
+
data,
|
|
271
|
+
timestamp: Date.now(),
|
|
272
|
+
isBinary,
|
|
273
|
+
};
|
|
274
|
+
this.stats.messageLog.push(message);
|
|
275
|
+
this.emit('message', message, client);
|
|
276
|
+
if (this.options.delay > 0) {
|
|
277
|
+
await new Promise((resolve) => setTimeout(resolve, this.options.delay));
|
|
278
|
+
}
|
|
279
|
+
const response = this.getResponse(data.toString(), client);
|
|
280
|
+
if (response !== null && client.socket.readyState === WebSocket.OPEN) {
|
|
281
|
+
client.socket.send(response);
|
|
282
|
+
this.stats.totalBytesSent += Buffer.byteLength(response);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
getResponse(msg, client) {
|
|
286
|
+
for (const [pattern, response] of this.responses) {
|
|
287
|
+
const regex = new RegExp(pattern);
|
|
288
|
+
if (regex.test(msg)) {
|
|
289
|
+
if (typeof response === 'function') {
|
|
290
|
+
return response(msg, client);
|
|
291
|
+
}
|
|
292
|
+
return response;
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
if (this.options.echo) {
|
|
296
|
+
return msg;
|
|
297
|
+
}
|
|
298
|
+
return null;
|
|
299
|
+
}
|
|
300
|
+
static async create(options = {}) {
|
|
301
|
+
const server = new MockWebSocketServer(options);
|
|
302
|
+
await server.start();
|
|
303
|
+
return server;
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
export async function createMockWebSocketServer(responses, options) {
|
|
307
|
+
const server = new MockWebSocketServer(options);
|
|
308
|
+
if (responses) {
|
|
309
|
+
for (const [pattern, response] of Object.entries(responses)) {
|
|
310
|
+
server.setResponse(pattern, response);
|
|
311
|
+
}
|
|
312
|
+
server.setEcho(false);
|
|
313
|
+
}
|
|
314
|
+
await server.start();
|
|
315
|
+
return server;
|
|
316
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { EventEmitter } from 'node:events';
|
|
2
|
+
export interface MockWhoisServerOptions {
|
|
3
|
+
port?: number;
|
|
4
|
+
host?: string;
|
|
5
|
+
delay?: number;
|
|
6
|
+
}
|
|
7
|
+
export interface WhoisDomainData {
|
|
8
|
+
registrar?: string;
|
|
9
|
+
registrarUrl?: string;
|
|
10
|
+
createdDate?: string;
|
|
11
|
+
updatedDate?: string;
|
|
12
|
+
expiryDate?: string;
|
|
13
|
+
status?: string[];
|
|
14
|
+
nameservers?: string[];
|
|
15
|
+
registrantName?: string;
|
|
16
|
+
registrantOrg?: string;
|
|
17
|
+
registrantEmail?: string;
|
|
18
|
+
adminEmail?: string;
|
|
19
|
+
techEmail?: string;
|
|
20
|
+
dnssec?: string;
|
|
21
|
+
}
|
|
22
|
+
export interface MockWhoisStats {
|
|
23
|
+
queriesReceived: number;
|
|
24
|
+
responseSent: number;
|
|
25
|
+
queryLog: Array<{
|
|
26
|
+
query: string;
|
|
27
|
+
timestamp: number;
|
|
28
|
+
}>;
|
|
29
|
+
}
|
|
30
|
+
export declare class MockWhoisServer extends EventEmitter {
|
|
31
|
+
private options;
|
|
32
|
+
private server;
|
|
33
|
+
private domains;
|
|
34
|
+
private started;
|
|
35
|
+
private stats;
|
|
36
|
+
constructor(options?: MockWhoisServerOptions);
|
|
37
|
+
get isRunning(): boolean;
|
|
38
|
+
get port(): number;
|
|
39
|
+
get host(): string;
|
|
40
|
+
get url(): string;
|
|
41
|
+
get statistics(): MockWhoisStats;
|
|
42
|
+
addDomain(domain: string, data: WhoisDomainData): void;
|
|
43
|
+
removeDomain(domain: string): void;
|
|
44
|
+
getDomain(domain: string): WhoisDomainData | undefined;
|
|
45
|
+
clearDomains(): void;
|
|
46
|
+
private addDefaultDomains;
|
|
47
|
+
start(): Promise<void>;
|
|
48
|
+
stop(): Promise<void>;
|
|
49
|
+
reset(): void;
|
|
50
|
+
private handleConnection;
|
|
51
|
+
private handleQuery;
|
|
52
|
+
private buildResponse;
|
|
53
|
+
private buildNotFoundResponse;
|
|
54
|
+
private buildDomainResponse;
|
|
55
|
+
static create(options?: MockWhoisServerOptions): Promise<MockWhoisServer>;
|
|
56
|
+
}
|
|
57
|
+
//# sourceMappingURL=mock-whois-server.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mock-whois-server.d.ts","sourceRoot":"","sources":["../../src/testing/mock-whois-server.ts"],"names":[],"mappings":"AAyBA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAO3C,MAAM,WAAW,sBAAsB;IAKrC,IAAI,CAAC,EAAE,MAAM,CAAC;IAMd,IAAI,CAAC,EAAE,MAAM,CAAC;IAMd,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,eAAe;IAC9B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;IACvB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,cAAc;IAC7B,eAAe,EAAE,MAAM,CAAC;IACxB,YAAY,EAAE,MAAM,CAAC;IACrB,QAAQ,EAAE,KAAK,CAAC;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CACvD;AAMD,qBAAa,eAAgB,SAAQ,YAAY;IAC/C,OAAO,CAAC,OAAO,CAAmC;IAClD,OAAO,CAAC,MAAM,CAA2B;IACzC,OAAO,CAAC,OAAO,CAA2C;IAC1D,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,KAAK,CAIX;gBAEU,OAAO,GAAE,sBAA2B;IAkBhD,IAAI,SAAS,IAAI,OAAO,CAEvB;IAED,IAAI,IAAI,IAAI,MAAM,CAEjB;IAED,IAAI,IAAI,IAAI,MAAM,CAEjB;IAED,IAAI,GAAG,IAAI,MAAM,CAEhB;IAED,IAAI,UAAU,IAAI,cAAc,CAE/B;IASD,SAAS,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,eAAe,GAAG,IAAI;IAOtD,YAAY,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI;IAOlC,SAAS,CAAC,MAAM,EAAE,MAAM,GAAG,eAAe,GAAG,SAAS;IAOtD,YAAY,IAAI,IAAI;IAKpB,OAAO,CAAC,iBAAiB;IA4CnB,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAyBtB,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAa3B,KAAK,IAAI,IAAI;IAcb,OAAO,CAAC,gBAAgB;YAiBV,WAAW;IAoBzB,OAAO,CAAC,aAAa;IAWrB,OAAO,CAAC,qBAAqB;IAY7B,OAAO,CAAC,mBAAmB;WA+Dd,MAAM,CAAC,OAAO,GAAE,sBAA2B,GAAG,OAAO,CAAC,eAAe,CAAC;CAKpF"}
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
import { EventEmitter } from 'node:events';
|
|
2
|
+
import * as net from 'node:net';
|
|
3
|
+
export class MockWhoisServer extends EventEmitter {
|
|
4
|
+
options;
|
|
5
|
+
server = null;
|
|
6
|
+
domains = new Map();
|
|
7
|
+
started = false;
|
|
8
|
+
stats = {
|
|
9
|
+
queriesReceived: 0,
|
|
10
|
+
responseSent: 0,
|
|
11
|
+
queryLog: [],
|
|
12
|
+
};
|
|
13
|
+
constructor(options = {}) {
|
|
14
|
+
super();
|
|
15
|
+
this.options = {
|
|
16
|
+
port: 4343,
|
|
17
|
+
host: '127.0.0.1',
|
|
18
|
+
delay: 0,
|
|
19
|
+
...options,
|
|
20
|
+
};
|
|
21
|
+
this.addDefaultDomains();
|
|
22
|
+
}
|
|
23
|
+
get isRunning() {
|
|
24
|
+
return this.started;
|
|
25
|
+
}
|
|
26
|
+
get port() {
|
|
27
|
+
return this.options.port;
|
|
28
|
+
}
|
|
29
|
+
get host() {
|
|
30
|
+
return this.options.host;
|
|
31
|
+
}
|
|
32
|
+
get url() {
|
|
33
|
+
return `${this.options.host}:${this.options.port}`;
|
|
34
|
+
}
|
|
35
|
+
get statistics() {
|
|
36
|
+
return { ...this.stats };
|
|
37
|
+
}
|
|
38
|
+
addDomain(domain, data) {
|
|
39
|
+
this.domains.set(domain.toLowerCase(), data);
|
|
40
|
+
}
|
|
41
|
+
removeDomain(domain) {
|
|
42
|
+
this.domains.delete(domain.toLowerCase());
|
|
43
|
+
}
|
|
44
|
+
getDomain(domain) {
|
|
45
|
+
return this.domains.get(domain.toLowerCase());
|
|
46
|
+
}
|
|
47
|
+
clearDomains() {
|
|
48
|
+
this.domains.clear();
|
|
49
|
+
this.addDefaultDomains();
|
|
50
|
+
}
|
|
51
|
+
addDefaultDomains() {
|
|
52
|
+
this.addDomain('example.com', {
|
|
53
|
+
registrar: 'RESERVED-Internet Assigned Numbers Authority',
|
|
54
|
+
registrarUrl: 'http://www.iana.org',
|
|
55
|
+
createdDate: '1995-08-14T04:00:00Z',
|
|
56
|
+
updatedDate: '2023-08-14T07:01:38Z',
|
|
57
|
+
expiryDate: '2024-08-13T04:00:00Z',
|
|
58
|
+
status: ['client delete prohibited', 'client transfer prohibited', 'client update prohibited'],
|
|
59
|
+
nameservers: ['a.iana-servers.net', 'b.iana-servers.net'],
|
|
60
|
+
dnssec: 'signedDelegation',
|
|
61
|
+
});
|
|
62
|
+
this.addDomain('google.com', {
|
|
63
|
+
registrar: 'MarkMonitor Inc.',
|
|
64
|
+
registrarUrl: 'http://www.markmonitor.com',
|
|
65
|
+
createdDate: '1997-09-15T04:00:00Z',
|
|
66
|
+
updatedDate: '2019-09-09T15:39:04Z',
|
|
67
|
+
expiryDate: '2028-09-14T04:00:00Z',
|
|
68
|
+
status: ['client delete prohibited', 'client transfer prohibited', 'client update prohibited', 'server delete prohibited', 'server transfer prohibited', 'server update prohibited'],
|
|
69
|
+
nameservers: ['ns1.google.com', 'ns2.google.com', 'ns3.google.com', 'ns4.google.com'],
|
|
70
|
+
registrantOrg: 'Google LLC',
|
|
71
|
+
registrantEmail: 'Select Request Email Form at https://domains.markmonitor.com/whois/google.com',
|
|
72
|
+
dnssec: 'unsigned',
|
|
73
|
+
});
|
|
74
|
+
this.addDomain('test.local', {
|
|
75
|
+
registrar: 'Mock Registrar',
|
|
76
|
+
registrarUrl: 'http://localhost',
|
|
77
|
+
createdDate: '2020-01-01T00:00:00Z',
|
|
78
|
+
updatedDate: '2024-01-01T00:00:00Z',
|
|
79
|
+
expiryDate: '2030-01-01T00:00:00Z',
|
|
80
|
+
status: ['ok'],
|
|
81
|
+
nameservers: ['ns1.test.local', 'ns2.test.local'],
|
|
82
|
+
registrantName: 'Test User',
|
|
83
|
+
registrantOrg: 'Test Organization',
|
|
84
|
+
registrantEmail: 'admin@test.local',
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
async start() {
|
|
88
|
+
if (this.started) {
|
|
89
|
+
throw new Error('Server already started');
|
|
90
|
+
}
|
|
91
|
+
return new Promise((resolve, reject) => {
|
|
92
|
+
this.server = net.createServer((socket) => {
|
|
93
|
+
this.handleConnection(socket);
|
|
94
|
+
});
|
|
95
|
+
this.server.on('error', (err) => {
|
|
96
|
+
this.emit('error', err);
|
|
97
|
+
if (!this.started) {
|
|
98
|
+
reject(err);
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
this.server.listen(this.options.port, this.options.host, () => {
|
|
102
|
+
this.started = true;
|
|
103
|
+
this.emit('start');
|
|
104
|
+
resolve();
|
|
105
|
+
});
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
async stop() {
|
|
109
|
+
if (!this.started || !this.server)
|
|
110
|
+
return;
|
|
111
|
+
return new Promise((resolve) => {
|
|
112
|
+
this.server.close(() => {
|
|
113
|
+
this.server = null;
|
|
114
|
+
this.started = false;
|
|
115
|
+
this.emit('stop');
|
|
116
|
+
resolve();
|
|
117
|
+
});
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
reset() {
|
|
121
|
+
this.stats = {
|
|
122
|
+
queriesReceived: 0,
|
|
123
|
+
responseSent: 0,
|
|
124
|
+
queryLog: [],
|
|
125
|
+
};
|
|
126
|
+
this.clearDomains();
|
|
127
|
+
this.emit('reset');
|
|
128
|
+
}
|
|
129
|
+
handleConnection(socket) {
|
|
130
|
+
let data = '';
|
|
131
|
+
socket.on('data', (chunk) => {
|
|
132
|
+
data += chunk.toString();
|
|
133
|
+
if (data.includes('\n') || data.includes('\r')) {
|
|
134
|
+
this.handleQuery(data.trim(), socket);
|
|
135
|
+
}
|
|
136
|
+
});
|
|
137
|
+
socket.on('error', (err) => {
|
|
138
|
+
this.emit('error', err);
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
async handleQuery(query, socket) {
|
|
142
|
+
this.stats.queriesReceived++;
|
|
143
|
+
this.stats.queryLog.push({ query, timestamp: Date.now() });
|
|
144
|
+
this.emit('query', query);
|
|
145
|
+
if (this.options.delay > 0) {
|
|
146
|
+
await new Promise((resolve) => setTimeout(resolve, this.options.delay));
|
|
147
|
+
}
|
|
148
|
+
const response = this.buildResponse(query);
|
|
149
|
+
socket.write(response, () => {
|
|
150
|
+
this.stats.responseSent++;
|
|
151
|
+
this.emit('response', query);
|
|
152
|
+
socket.end();
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
buildResponse(query) {
|
|
156
|
+
const domain = query.toLowerCase().trim();
|
|
157
|
+
const data = this.domains.get(domain);
|
|
158
|
+
if (!data) {
|
|
159
|
+
return this.buildNotFoundResponse(domain);
|
|
160
|
+
}
|
|
161
|
+
return this.buildDomainResponse(domain, data);
|
|
162
|
+
}
|
|
163
|
+
buildNotFoundResponse(domain) {
|
|
164
|
+
return `
|
|
165
|
+
% WHOIS Mock Server
|
|
166
|
+
|
|
167
|
+
No match for domain "${domain}".
|
|
168
|
+
|
|
169
|
+
>>> Last update of WHOIS database: ${new Date().toISOString()} <<<
|
|
170
|
+
|
|
171
|
+
NOTICE: This is a mock WHOIS server for testing purposes.
|
|
172
|
+
`.trim() + '\n';
|
|
173
|
+
}
|
|
174
|
+
buildDomainResponse(domain, data) {
|
|
175
|
+
const lines = [
|
|
176
|
+
'% WHOIS Mock Server',
|
|
177
|
+
'',
|
|
178
|
+
`Domain Name: ${domain.toUpperCase()}`,
|
|
179
|
+
];
|
|
180
|
+
if (data.registrar) {
|
|
181
|
+
lines.push(`Registrar: ${data.registrar}`);
|
|
182
|
+
}
|
|
183
|
+
if (data.registrarUrl) {
|
|
184
|
+
lines.push(`Registrar URL: ${data.registrarUrl}`);
|
|
185
|
+
}
|
|
186
|
+
if (data.createdDate) {
|
|
187
|
+
lines.push(`Creation Date: ${data.createdDate}`);
|
|
188
|
+
}
|
|
189
|
+
if (data.updatedDate) {
|
|
190
|
+
lines.push(`Updated Date: ${data.updatedDate}`);
|
|
191
|
+
}
|
|
192
|
+
if (data.expiryDate) {
|
|
193
|
+
lines.push(`Registry Expiry Date: ${data.expiryDate}`);
|
|
194
|
+
}
|
|
195
|
+
if (data.status && data.status.length > 0) {
|
|
196
|
+
for (const status of data.status) {
|
|
197
|
+
lines.push(`Domain Status: ${status}`);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
if (data.nameservers && data.nameservers.length > 0) {
|
|
201
|
+
for (const ns of data.nameservers) {
|
|
202
|
+
lines.push(`Name Server: ${ns}`);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
if (data.registrantName) {
|
|
206
|
+
lines.push(`Registrant Name: ${data.registrantName}`);
|
|
207
|
+
}
|
|
208
|
+
if (data.registrantOrg) {
|
|
209
|
+
lines.push(`Registrant Organization: ${data.registrantOrg}`);
|
|
210
|
+
}
|
|
211
|
+
if (data.registrantEmail) {
|
|
212
|
+
lines.push(`Registrant Email: ${data.registrantEmail}`);
|
|
213
|
+
}
|
|
214
|
+
if (data.adminEmail) {
|
|
215
|
+
lines.push(`Admin Email: ${data.adminEmail}`);
|
|
216
|
+
}
|
|
217
|
+
if (data.techEmail) {
|
|
218
|
+
lines.push(`Tech Email: ${data.techEmail}`);
|
|
219
|
+
}
|
|
220
|
+
if (data.dnssec) {
|
|
221
|
+
lines.push(`DNSSEC: ${data.dnssec}`);
|
|
222
|
+
}
|
|
223
|
+
lines.push('');
|
|
224
|
+
lines.push(`>>> Last update of WHOIS database: ${new Date().toISOString()} <<<`);
|
|
225
|
+
lines.push('');
|
|
226
|
+
lines.push('NOTICE: This is a mock WHOIS server for testing purposes.');
|
|
227
|
+
return lines.join('\n') + '\n';
|
|
228
|
+
}
|
|
229
|
+
static async create(options = {}) {
|
|
230
|
+
const server = new MockWhoisServer(options);
|
|
231
|
+
await server.start();
|
|
232
|
+
return server;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
@@ -31,7 +31,7 @@ export declare class UndiciTransport implements Transport {
|
|
|
31
31
|
private tlsOptions?;
|
|
32
32
|
private socketClient?;
|
|
33
33
|
private observability;
|
|
34
|
-
constructor(baseUrl
|
|
34
|
+
constructor(baseUrl?: string, options?: UndiciTransportOptions);
|
|
35
35
|
dispatch(req: ReckerRequest): Promise<ReckerResponse>;
|
|
36
36
|
private dispatchFast;
|
|
37
37
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"undici.d.ts","sourceRoot":"","sources":["../../src/transport/undici.ts"],"names":[],"mappings":"AACA,OAAO,EAAkB,aAAa,EAAE,cAAc,EAAW,SAAS,EAAE,YAAY,EAAE,YAAY,EAAE,UAAU,EAAgB,UAAU,EAAkD,MAAM,mBAAmB,CAAC;AAOxN,OAAO,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAC;AAwNzD,UAAU,sBAAsB;IAC9B,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,KAAK,CAAC,EAAE,YAAY,GAAG,MAAM,CAAC;IAC9B,KAAK,CAAC,EAAE,YAAY,CAAC;IACrB,GAAG,CAAC,EAAE,UAAU,CAAC;IACjB,KAAK,CAAC,EAAE,YAAY,CAAC;IACrB,GAAG,CAAC,EAAE,UAAU,CAAC;IACjB,UAAU,CAAC,EAAE,MAAM,CAAC;IAEpB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,YAAY,CAAC,EAAE,MAAM,CAAC;IAMtB,aAAa,CAAC,EAAE,OAAO,CAAC;CACzB;AAqDD,qBAAa,eAAgB,YAAW,SAAS;IAC/C,OAAO,CAAC,MAAM,CAAC,cAAc,CAAK;IAElC,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,OAAO,CAAyB;IACxC,OAAO,CAAC,UAAU,CAAC,CAAa;IAChC,OAAO,CAAC,QAAQ,CAAC,CAAQ;IACzB,OAAO,CAAC,YAAY,CAAC,CAAe;IACpC,OAAO,CAAC,eAAe,CAAC,CAAW;IACnC,OAAO,CAAC,UAAU,CAAC,CAAa;IAChC,OAAO,CAAC,YAAY,CAAC,CAAS;IAC9B,OAAO,CAAC,aAAa,CAAU;gBAEnB,OAAO,EAAE,MAAM,EAAE,OAAO,GAAE,sBAA2B;
|
|
1
|
+
{"version":3,"file":"undici.d.ts","sourceRoot":"","sources":["../../src/transport/undici.ts"],"names":[],"mappings":"AACA,OAAO,EAAkB,aAAa,EAAE,cAAc,EAAW,SAAS,EAAE,YAAY,EAAE,YAAY,EAAE,UAAU,EAAgB,UAAU,EAAkD,MAAM,mBAAmB,CAAC;AAOxN,OAAO,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAC;AAwNzD,UAAU,sBAAsB;IAC9B,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,KAAK,CAAC,EAAE,YAAY,GAAG,MAAM,CAAC;IAC9B,KAAK,CAAC,EAAE,YAAY,CAAC;IACrB,GAAG,CAAC,EAAE,UAAU,CAAC;IACjB,KAAK,CAAC,EAAE,YAAY,CAAC;IACrB,GAAG,CAAC,EAAE,UAAU,CAAC;IACjB,UAAU,CAAC,EAAE,MAAM,CAAC;IAEpB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,YAAY,CAAC,EAAE,MAAM,CAAC;IAMtB,aAAa,CAAC,EAAE,OAAO,CAAC;CACzB;AAqDD,qBAAa,eAAgB,YAAW,SAAS;IAC/C,OAAO,CAAC,MAAM,CAAC,cAAc,CAAK;IAElC,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,OAAO,CAAyB;IACxC,OAAO,CAAC,UAAU,CAAC,CAAa;IAChC,OAAO,CAAC,QAAQ,CAAC,CAAQ;IACzB,OAAO,CAAC,YAAY,CAAC,CAAe;IACpC,OAAO,CAAC,eAAe,CAAC,CAAW;IACnC,OAAO,CAAC,UAAU,CAAC,CAAa;IAChC,OAAO,CAAC,YAAY,CAAC,CAAS;IAC9B,OAAO,CAAC,aAAa,CAAU;gBAEnB,OAAO,CAAC,EAAE,MAAM,EAAE,OAAO,GAAE,sBAA2B;IAmG5D,QAAQ,CAAC,GAAG,EAAE,aAAa,GAAG,OAAO,CAAC,cAAc,CAAC;YAsV7C,YAAY;CAoQ3B"}
|
package/dist/transport/undici.js
CHANGED
|
@@ -23,7 +23,7 @@ undiciRequestChannel.subscribe((message) => {
|
|
|
23
23
|
});
|
|
24
24
|
undiciBodySentChannel.subscribe((message) => {
|
|
25
25
|
const store = requestStorage.getStore();
|
|
26
|
-
if (store
|
|
26
|
+
if (store?.hooks && store.hooks.onRequestSent) {
|
|
27
27
|
store.hooks.onRequestSent();
|
|
28
28
|
}
|
|
29
29
|
});
|
|
@@ -143,7 +143,7 @@ export class UndiciTransport {
|
|
|
143
143
|
socketClient;
|
|
144
144
|
observability;
|
|
145
145
|
constructor(baseUrl, options = {}) {
|
|
146
|
-
this.baseUrl = baseUrl;
|
|
146
|
+
this.baseUrl = baseUrl || '';
|
|
147
147
|
this.options = options;
|
|
148
148
|
this.tlsOptions = options.tls;
|
|
149
149
|
this.observability = options.observability !== false;
|
|
@@ -203,7 +203,7 @@ export class UndiciTransport {
|
|
|
203
203
|
localAddress: options.localAddress,
|
|
204
204
|
});
|
|
205
205
|
}
|
|
206
|
-
if (options.socketPath) {
|
|
206
|
+
if (options.socketPath && baseUrl) {
|
|
207
207
|
this.socketClient = new Client(baseUrl, {
|
|
208
208
|
socketPath: options.socketPath
|
|
209
209
|
});
|
|
@@ -213,8 +213,14 @@ export class UndiciTransport {
|
|
|
213
213
|
const headers = Object.fromEntries(req.headers);
|
|
214
214
|
const contentLengthHeader = headers['content-length'];
|
|
215
215
|
const uploadTotal = contentLengthHeader ? parseInt(contentLengthHeader, 10) : undefined;
|
|
216
|
-
|
|
217
|
-
|
|
216
|
+
let currentUrl;
|
|
217
|
+
if (this.baseUrl) {
|
|
218
|
+
const path = req.url.startsWith(this.baseUrl) ? req.url.substring(this.baseUrl.length) : req.url;
|
|
219
|
+
currentUrl = new URL(path, this.baseUrl).toString();
|
|
220
|
+
}
|
|
221
|
+
else {
|
|
222
|
+
currentUrl = req.url;
|
|
223
|
+
}
|
|
218
224
|
const handleRedirectsManually = Boolean(req.beforeRedirect);
|
|
219
225
|
const maxRedirects = req.maxRedirects ?? 20;
|
|
220
226
|
const followRedirects = req.followRedirects !== false;
|
|
@@ -88,7 +88,7 @@ export async function getSecurityRecords(domain) {
|
|
|
88
88
|
try {
|
|
89
89
|
const dmarcRecords = await dns.resolveTxt(`_dmarc.${domain}`);
|
|
90
90
|
const dmarcTxt = dmarcRecords.map(chunks => chunks.join(''))[0];
|
|
91
|
-
if (dmarcTxt
|
|
91
|
+
if (dmarcTxt?.startsWith('v=DMARC1')) {
|
|
92
92
|
results.dmarc = dmarcTxt;
|
|
93
93
|
}
|
|
94
94
|
}
|