recker 1.0.5 → 1.0.6
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 +1 -1
- package/dist/ai/adaptive-timeout.d.ts +51 -0
- package/dist/ai/adaptive-timeout.d.ts.map +1 -0
- package/dist/ai/adaptive-timeout.js +208 -0
- package/dist/ai/client.d.ts +24 -0
- package/dist/ai/client.d.ts.map +1 -0
- package/dist/ai/client.js +289 -0
- package/dist/ai/index.d.ts +10 -0
- package/dist/ai/index.d.ts.map +1 -0
- package/dist/ai/index.js +6 -0
- package/dist/ai/providers/anthropic.d.ts +64 -0
- package/dist/ai/providers/anthropic.d.ts.map +1 -0
- package/dist/ai/providers/anthropic.js +367 -0
- package/dist/ai/providers/base.d.ts +49 -0
- package/dist/ai/providers/base.d.ts.map +1 -0
- package/dist/ai/providers/base.js +145 -0
- package/dist/ai/providers/index.d.ts +7 -0
- package/dist/ai/providers/index.d.ts.map +1 -0
- package/dist/ai/providers/index.js +3 -0
- package/dist/ai/providers/openai.d.ts +65 -0
- package/dist/ai/providers/openai.d.ts.map +1 -0
- package/dist/ai/providers/openai.js +298 -0
- package/dist/ai/rate-limiter.d.ts +44 -0
- package/dist/ai/rate-limiter.d.ts.map +1 -0
- package/dist/ai/rate-limiter.js +212 -0
- package/dist/bench/generator.d.ts +19 -0
- package/dist/bench/generator.d.ts.map +1 -0
- package/dist/bench/generator.js +86 -0
- package/dist/bench/stats.d.ts +35 -0
- package/dist/bench/stats.d.ts.map +1 -0
- package/dist/bench/stats.js +60 -0
- package/dist/cli/handler.d.ts +11 -0
- package/dist/cli/handler.d.ts.map +1 -0
- package/dist/cli/handler.js +92 -0
- package/dist/cli/index.d.ts +3 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +255 -0
- package/dist/cli/presets.d.ts +2 -0
- package/dist/cli/presets.d.ts.map +1 -0
- package/dist/cli/presets.js +67 -0
- package/dist/cli/tui/ai-chat.d.ts +3 -0
- package/dist/cli/tui/ai-chat.d.ts.map +1 -0
- package/dist/cli/tui/ai-chat.js +100 -0
- package/dist/cli/tui/load-dashboard.d.ts +3 -0
- package/dist/cli/tui/load-dashboard.d.ts.map +1 -0
- package/dist/cli/tui/load-dashboard.js +117 -0
- package/dist/cli/tui/shell.d.ts +27 -0
- package/dist/cli/tui/shell.d.ts.map +1 -0
- package/dist/cli/tui/shell.js +386 -0
- package/dist/cli/tui/websocket.d.ts +2 -0
- package/dist/cli/tui/websocket.d.ts.map +1 -0
- package/dist/cli/tui/websocket.js +87 -0
- package/dist/contract/index.d.ts +2 -2
- package/dist/contract/index.d.ts.map +1 -1
- package/dist/core/client.d.ts +1 -0
- package/dist/core/client.d.ts.map +1 -1
- package/dist/core/client.js +4 -2
- package/dist/core/request-promise.d.ts +1 -1
- package/dist/core/request-promise.d.ts.map +1 -1
- package/dist/mcp/contract.d.ts +1 -1
- package/dist/mcp/contract.d.ts.map +1 -1
- package/dist/protocols/ftp.d.ts +28 -5
- package/dist/protocols/ftp.d.ts.map +1 -1
- package/dist/protocols/ftp.js +549 -136
- package/dist/protocols/sftp.d.ts +4 -2
- package/dist/protocols/sftp.d.ts.map +1 -1
- package/dist/protocols/sftp.js +16 -2
- package/dist/protocols/telnet.d.ts +37 -5
- package/dist/protocols/telnet.d.ts.map +1 -1
- package/dist/protocols/telnet.js +434 -58
- package/dist/scrape/document.d.ts.map +1 -1
- package/dist/scrape/document.js +7 -12
- package/dist/testing/index.d.ts +2 -0
- package/dist/testing/index.d.ts.map +1 -1
- package/dist/testing/index.js +1 -0
- package/dist/testing/mock-udp-server.d.ts +44 -0
- package/dist/testing/mock-udp-server.d.ts.map +1 -0
- package/dist/testing/mock-udp-server.js +188 -0
- package/dist/transport/base-udp.d.ts +36 -0
- package/dist/transport/base-udp.d.ts.map +1 -0
- package/dist/transport/base-udp.js +188 -0
- package/dist/transport/udp-response.d.ts +65 -0
- package/dist/transport/udp-response.d.ts.map +1 -0
- package/dist/transport/udp-response.js +269 -0
- package/dist/transport/udp.d.ts +22 -0
- package/dist/transport/udp.d.ts.map +1 -0
- package/dist/transport/udp.js +260 -0
- package/dist/types/ai.d.ts +268 -0
- package/dist/types/ai.d.ts.map +1 -0
- package/dist/types/ai.js +1 -0
- package/dist/types/udp.d.ts +138 -0
- package/dist/types/udp.d.ts.map +1 -0
- package/dist/types/udp.js +1 -0
- package/dist/udp/index.d.ts +6 -0
- package/dist/udp/index.d.ts.map +1 -0
- package/dist/udp/index.js +3 -0
- package/dist/utils/chart.d.ts +15 -0
- package/dist/utils/chart.d.ts.map +1 -0
- package/dist/utils/chart.js +94 -0
- package/dist/utils/colors.d.ts +27 -0
- package/dist/utils/colors.d.ts.map +1 -0
- package/dist/utils/colors.js +50 -0
- package/dist/utils/optional-require.d.ts +20 -0
- package/dist/utils/optional-require.d.ts.map +1 -0
- package/dist/utils/optional-require.js +105 -0
- package/package.json +53 -12
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
export class UDPResponseImpl {
|
|
2
|
+
_buffer;
|
|
3
|
+
_timings;
|
|
4
|
+
_connection;
|
|
5
|
+
_url;
|
|
6
|
+
_bodyUsed = false;
|
|
7
|
+
status = 200;
|
|
8
|
+
statusText = 'OK';
|
|
9
|
+
headers;
|
|
10
|
+
ok = true;
|
|
11
|
+
raw;
|
|
12
|
+
constructor(data, options = {}) {
|
|
13
|
+
this._buffer = data;
|
|
14
|
+
this._url = options.url || '';
|
|
15
|
+
this._timings = options.timings ?? {
|
|
16
|
+
queued: 0,
|
|
17
|
+
send: 0,
|
|
18
|
+
receive: 0,
|
|
19
|
+
retransmissions: 0,
|
|
20
|
+
total: 0,
|
|
21
|
+
};
|
|
22
|
+
this._connection = options.connection ?? {
|
|
23
|
+
protocol: 'udp',
|
|
24
|
+
localAddress: '',
|
|
25
|
+
localPort: 0,
|
|
26
|
+
remoteAddress: '',
|
|
27
|
+
remotePort: 0,
|
|
28
|
+
};
|
|
29
|
+
this.headers = new Headers({
|
|
30
|
+
'content-type': 'application/octet-stream',
|
|
31
|
+
'content-length': String(data.length),
|
|
32
|
+
'x-protocol': 'udp',
|
|
33
|
+
});
|
|
34
|
+
this.raw = new Response(new Uint8Array(data), {
|
|
35
|
+
status: 200,
|
|
36
|
+
statusText: 'OK',
|
|
37
|
+
headers: this.headers,
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
get url() {
|
|
41
|
+
return this._url;
|
|
42
|
+
}
|
|
43
|
+
get timings() {
|
|
44
|
+
return this._timings;
|
|
45
|
+
}
|
|
46
|
+
get connection() {
|
|
47
|
+
return this._connection;
|
|
48
|
+
}
|
|
49
|
+
async buffer() {
|
|
50
|
+
this._bodyUsed = true;
|
|
51
|
+
return this._buffer;
|
|
52
|
+
}
|
|
53
|
+
async json() {
|
|
54
|
+
this._bodyUsed = true;
|
|
55
|
+
const text = this._buffer.toString('utf8');
|
|
56
|
+
return JSON.parse(text);
|
|
57
|
+
}
|
|
58
|
+
async text() {
|
|
59
|
+
this._bodyUsed = true;
|
|
60
|
+
return this._buffer.toString('utf8');
|
|
61
|
+
}
|
|
62
|
+
async cleanText() {
|
|
63
|
+
this._bodyUsed = true;
|
|
64
|
+
return this._buffer
|
|
65
|
+
.toString('utf8')
|
|
66
|
+
.replace(/[\x00-\x1F\x7F-\x9F]/g, ' ')
|
|
67
|
+
.replace(/\s+/g, ' ')
|
|
68
|
+
.trim();
|
|
69
|
+
}
|
|
70
|
+
async blob() {
|
|
71
|
+
this._bodyUsed = true;
|
|
72
|
+
return new Blob([new Uint8Array(this._buffer)]);
|
|
73
|
+
}
|
|
74
|
+
read() {
|
|
75
|
+
if (this._bodyUsed) {
|
|
76
|
+
return null;
|
|
77
|
+
}
|
|
78
|
+
this._bodyUsed = true;
|
|
79
|
+
const buffer = this._buffer;
|
|
80
|
+
return new ReadableStream({
|
|
81
|
+
start(controller) {
|
|
82
|
+
controller.enqueue(new Uint8Array(buffer));
|
|
83
|
+
controller.close();
|
|
84
|
+
},
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
clone() {
|
|
88
|
+
return new UDPResponseImpl(Buffer.from(this._buffer), {
|
|
89
|
+
timings: { ...this._timings },
|
|
90
|
+
connection: { ...this._connection },
|
|
91
|
+
url: this._url,
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
async *sse() {
|
|
95
|
+
throw new Error('SSE is not supported for UDP responses');
|
|
96
|
+
}
|
|
97
|
+
async *download() {
|
|
98
|
+
yield {
|
|
99
|
+
loaded: this._buffer.length,
|
|
100
|
+
transferred: this._buffer.length,
|
|
101
|
+
total: this._buffer.length,
|
|
102
|
+
percent: 100,
|
|
103
|
+
direction: 'download',
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
async *packets() {
|
|
107
|
+
yield this._buffer;
|
|
108
|
+
}
|
|
109
|
+
async *[Symbol.asyncIterator]() {
|
|
110
|
+
yield new Uint8Array(this._buffer);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
export class StreamingUDPResponse {
|
|
114
|
+
_packets = [];
|
|
115
|
+
_timings;
|
|
116
|
+
_connection;
|
|
117
|
+
_url;
|
|
118
|
+
_complete = false;
|
|
119
|
+
_waiters = [];
|
|
120
|
+
status = 200;
|
|
121
|
+
statusText = 'OK';
|
|
122
|
+
headers;
|
|
123
|
+
ok = true;
|
|
124
|
+
raw;
|
|
125
|
+
constructor(options = {}) {
|
|
126
|
+
this._url = options.url || '';
|
|
127
|
+
this._timings = options.timings ?? {
|
|
128
|
+
queued: 0,
|
|
129
|
+
send: 0,
|
|
130
|
+
receive: 0,
|
|
131
|
+
retransmissions: 0,
|
|
132
|
+
total: 0,
|
|
133
|
+
};
|
|
134
|
+
this._connection = options.connection ?? {
|
|
135
|
+
protocol: 'udp',
|
|
136
|
+
localAddress: '',
|
|
137
|
+
localPort: 0,
|
|
138
|
+
remoteAddress: '',
|
|
139
|
+
remotePort: 0,
|
|
140
|
+
};
|
|
141
|
+
this.headers = new Headers({
|
|
142
|
+
'content-type': 'application/octet-stream',
|
|
143
|
+
'x-protocol': 'udp',
|
|
144
|
+
'x-streaming': 'true',
|
|
145
|
+
});
|
|
146
|
+
this.raw = new Response(null, {
|
|
147
|
+
status: 200,
|
|
148
|
+
statusText: 'OK',
|
|
149
|
+
headers: this.headers,
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
get url() {
|
|
153
|
+
return this._url;
|
|
154
|
+
}
|
|
155
|
+
get timings() {
|
|
156
|
+
return this._timings;
|
|
157
|
+
}
|
|
158
|
+
get connection() {
|
|
159
|
+
return this._connection;
|
|
160
|
+
}
|
|
161
|
+
pushPacket(packet) {
|
|
162
|
+
if (this._complete) {
|
|
163
|
+
throw new Error('Cannot push to completed stream');
|
|
164
|
+
}
|
|
165
|
+
const waiter = this._waiters.shift();
|
|
166
|
+
if (waiter) {
|
|
167
|
+
waiter.resolve({ value: packet, done: false });
|
|
168
|
+
}
|
|
169
|
+
else {
|
|
170
|
+
this._packets.push(packet);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
complete() {
|
|
174
|
+
this._complete = true;
|
|
175
|
+
for (const waiter of this._waiters) {
|
|
176
|
+
waiter.resolve({ value: undefined, done: true });
|
|
177
|
+
}
|
|
178
|
+
this._waiters = [];
|
|
179
|
+
}
|
|
180
|
+
async buffer() {
|
|
181
|
+
while (!this._complete) {
|
|
182
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
183
|
+
}
|
|
184
|
+
return Buffer.concat(this._packets);
|
|
185
|
+
}
|
|
186
|
+
async json() {
|
|
187
|
+
const buf = await this.buffer();
|
|
188
|
+
return JSON.parse(buf.toString('utf8'));
|
|
189
|
+
}
|
|
190
|
+
async text() {
|
|
191
|
+
const buf = await this.buffer();
|
|
192
|
+
return buf.toString('utf8');
|
|
193
|
+
}
|
|
194
|
+
async cleanText() {
|
|
195
|
+
const text = await this.text();
|
|
196
|
+
return text
|
|
197
|
+
.replace(/[\x00-\x1F\x7F-\x9F]/g, ' ')
|
|
198
|
+
.replace(/\s+/g, ' ')
|
|
199
|
+
.trim();
|
|
200
|
+
}
|
|
201
|
+
async blob() {
|
|
202
|
+
const buf = await this.buffer();
|
|
203
|
+
return new Blob([new Uint8Array(buf)]);
|
|
204
|
+
}
|
|
205
|
+
read() {
|
|
206
|
+
const self = this;
|
|
207
|
+
return new ReadableStream({
|
|
208
|
+
async pull(controller) {
|
|
209
|
+
const result = await self.packets().next();
|
|
210
|
+
if (result.done) {
|
|
211
|
+
controller.close();
|
|
212
|
+
}
|
|
213
|
+
else {
|
|
214
|
+
controller.enqueue(new Uint8Array(result.value));
|
|
215
|
+
}
|
|
216
|
+
},
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
clone() {
|
|
220
|
+
const cloned = new StreamingUDPResponse({
|
|
221
|
+
timings: { ...this._timings },
|
|
222
|
+
connection: { ...this._connection },
|
|
223
|
+
url: this._url,
|
|
224
|
+
});
|
|
225
|
+
cloned._packets = this._packets.map((p) => Buffer.from(p));
|
|
226
|
+
cloned._complete = this._complete;
|
|
227
|
+
return cloned;
|
|
228
|
+
}
|
|
229
|
+
async *sse() {
|
|
230
|
+
throw new Error('SSE is not supported for UDP responses');
|
|
231
|
+
}
|
|
232
|
+
async *download() {
|
|
233
|
+
let loaded = 0;
|
|
234
|
+
for await (const packet of this.packets()) {
|
|
235
|
+
loaded += packet.length;
|
|
236
|
+
yield {
|
|
237
|
+
loaded,
|
|
238
|
+
transferred: loaded,
|
|
239
|
+
direction: 'download',
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
async *packets() {
|
|
244
|
+
while (true) {
|
|
245
|
+
if (this._packets.length > 0) {
|
|
246
|
+
yield this._packets.shift();
|
|
247
|
+
continue;
|
|
248
|
+
}
|
|
249
|
+
if (this._complete) {
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
252
|
+
const packet = await new Promise((resolve, reject) => {
|
|
253
|
+
this._waiters.push({
|
|
254
|
+
resolve: (result) => resolve(result.done ? null : result.value),
|
|
255
|
+
reject,
|
|
256
|
+
});
|
|
257
|
+
});
|
|
258
|
+
if (packet === null) {
|
|
259
|
+
return;
|
|
260
|
+
}
|
|
261
|
+
yield packet;
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
async *[Symbol.asyncIterator]() {
|
|
265
|
+
for await (const packet of this.packets()) {
|
|
266
|
+
yield new Uint8Array(packet);
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { ReckerRequest, ReckerResponse } from '../types/index.js';
|
|
2
|
+
import type { UDPTransportOptions, SimpleUDPAPI } from '../types/udp.js';
|
|
3
|
+
import { BaseUDPTransport } from './base-udp.js';
|
|
4
|
+
export declare class UDPTransportImpl extends BaseUDPTransport {
|
|
5
|
+
private socket;
|
|
6
|
+
private baseUrl;
|
|
7
|
+
private udpOptions;
|
|
8
|
+
private multicastGroups;
|
|
9
|
+
constructor(baseUrl?: string, options?: UDPTransportOptions);
|
|
10
|
+
private getSocket;
|
|
11
|
+
private _socketBound;
|
|
12
|
+
dispatch(req: ReckerRequest): Promise<ReckerResponse>;
|
|
13
|
+
send(host: string, port: number, data: Buffer): Promise<void>;
|
|
14
|
+
broadcast(port: number, data: Buffer): Promise<void>;
|
|
15
|
+
joinMulticast(group: string): void;
|
|
16
|
+
leaveMulticast(group: string): void;
|
|
17
|
+
close(): Promise<void>;
|
|
18
|
+
}
|
|
19
|
+
export declare function createUDPClient(options?: UDPTransportOptions): UDPTransportImpl;
|
|
20
|
+
export declare const udp: SimpleUDPAPI;
|
|
21
|
+
export { UDPTransportImpl as UDPTransport };
|
|
22
|
+
//# sourceMappingURL=udp.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"udp.d.ts","sourceRoot":"","sources":["../../src/transport/udp.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AACvE,OAAO,KAAK,EACV,mBAAmB,EAInB,YAAY,EACb,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAAE,gBAAgB,EAAwC,MAAM,eAAe,CAAC;AAqCvF,qBAAa,gBAAiB,SAAQ,gBAAgB;IACpD,OAAO,CAAC,MAAM,CAA6B;IAC3C,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,UAAU,CAAgC;IAClD,OAAO,CAAC,eAAe,CAA0B;gBAErC,OAAO,GAAE,MAAW,EAAE,OAAO,GAAE,mBAAwB;IAyBnE,OAAO,CAAC,SAAS;IA0CjB,OAAO,CAAC,YAAY,CAAkB;IAKhC,QAAQ,CAAC,GAAG,EAAE,aAAa,GAAG,OAAO,CAAC,cAAc,CAAC;IAoFrD,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAyB7D,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAiB1D,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAWlC,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAW7B,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;CAqB7B;AASD,wBAAgB,eAAe,CAAC,OAAO,GAAE,mBAAwB,GAAG,gBAAgB,CAEnF;AAKD,eAAO,MAAM,GAAG,EAAE,YAuGjB,CAAC;AAGF,OAAO,EAAE,gBAAgB,IAAI,YAAY,EAAE,CAAC"}
|
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
import dgram from 'node:dgram';
|
|
2
|
+
import { BaseUDPTransport, udpRequestStorage } from './base-udp.js';
|
|
3
|
+
import { UDPResponseImpl } from './udp-response.js';
|
|
4
|
+
const DEFAULT_OPTIONS = {
|
|
5
|
+
timeout: 5000,
|
|
6
|
+
retransmissions: 3,
|
|
7
|
+
maxPacketSize: 65507,
|
|
8
|
+
observability: true,
|
|
9
|
+
broadcast: false,
|
|
10
|
+
multicastTTL: 1,
|
|
11
|
+
multicastLoopback: true,
|
|
12
|
+
type: 'udp4',
|
|
13
|
+
};
|
|
14
|
+
export class UDPTransportImpl extends BaseUDPTransport {
|
|
15
|
+
socket = null;
|
|
16
|
+
baseUrl;
|
|
17
|
+
udpOptions;
|
|
18
|
+
multicastGroups = new Set();
|
|
19
|
+
constructor(baseUrl = '', options = {}) {
|
|
20
|
+
super(options);
|
|
21
|
+
this.baseUrl = baseUrl;
|
|
22
|
+
this.udpOptions = {
|
|
23
|
+
...DEFAULT_OPTIONS,
|
|
24
|
+
...options,
|
|
25
|
+
timeout: options.timeout ?? DEFAULT_OPTIONS.timeout,
|
|
26
|
+
retransmissions: options.retransmissions ?? DEFAULT_OPTIONS.retransmissions,
|
|
27
|
+
maxPacketSize: options.maxPacketSize ?? DEFAULT_OPTIONS.maxPacketSize,
|
|
28
|
+
observability: options.observability ?? DEFAULT_OPTIONS.observability,
|
|
29
|
+
localAddress: options.localAddress ?? '',
|
|
30
|
+
localPort: options.localPort ?? 0,
|
|
31
|
+
broadcast: options.broadcast ?? DEFAULT_OPTIONS.broadcast,
|
|
32
|
+
multicastTTL: options.multicastTTL ?? DEFAULT_OPTIONS.multicastTTL,
|
|
33
|
+
multicastGroups: options.multicastGroups ?? [],
|
|
34
|
+
multicastLoopback: options.multicastLoopback ?? DEFAULT_OPTIONS.multicastLoopback,
|
|
35
|
+
type: options.type ?? DEFAULT_OPTIONS.type,
|
|
36
|
+
recvBufferSize: options.recvBufferSize,
|
|
37
|
+
sendBufferSize: options.sendBufferSize,
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
getSocket() {
|
|
41
|
+
if (!this.socket) {
|
|
42
|
+
this.socket = this.createSocket(this.udpOptions.type);
|
|
43
|
+
this._socketBound = false;
|
|
44
|
+
this.socket.on('listening', () => {
|
|
45
|
+
this._socketBound = true;
|
|
46
|
+
if (this.udpOptions.broadcast) {
|
|
47
|
+
this.socket.setBroadcast(true);
|
|
48
|
+
}
|
|
49
|
+
if (this.udpOptions.multicastTTL !== 1) {
|
|
50
|
+
this.socket.setMulticastTTL(this.udpOptions.multicastTTL);
|
|
51
|
+
}
|
|
52
|
+
if (!this.udpOptions.multicastLoopback) {
|
|
53
|
+
this.socket.setMulticastLoopback(false);
|
|
54
|
+
}
|
|
55
|
+
if (this.udpOptions.recvBufferSize) {
|
|
56
|
+
this.socket.setRecvBufferSize(this.udpOptions.recvBufferSize);
|
|
57
|
+
}
|
|
58
|
+
if (this.udpOptions.sendBufferSize) {
|
|
59
|
+
this.socket.setSendBufferSize(this.udpOptions.sendBufferSize);
|
|
60
|
+
}
|
|
61
|
+
for (const group of this.udpOptions.multicastGroups ?? []) {
|
|
62
|
+
this.joinMulticast(group);
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
this.socket.bind(this.udpOptions.localPort, this.udpOptions.localAddress || undefined);
|
|
66
|
+
}
|
|
67
|
+
return this.socket;
|
|
68
|
+
}
|
|
69
|
+
_socketBound = false;
|
|
70
|
+
async dispatch(req) {
|
|
71
|
+
const context = {
|
|
72
|
+
startTime: performance.now(),
|
|
73
|
+
retransmissions: 0,
|
|
74
|
+
};
|
|
75
|
+
return udpRequestStorage.run(context, async () => {
|
|
76
|
+
const fullUrl = this.baseUrl ? `${this.baseUrl}${req.url}` : req.url;
|
|
77
|
+
const { host, port, path } = this.parseUrl(fullUrl);
|
|
78
|
+
if (!host || !port) {
|
|
79
|
+
throw new Error(`Invalid UDP URL: ${fullUrl}. Expected format: udp://host:port/path`);
|
|
80
|
+
}
|
|
81
|
+
let body;
|
|
82
|
+
if (req.body === null || req.body === undefined) {
|
|
83
|
+
body = Buffer.from(path);
|
|
84
|
+
}
|
|
85
|
+
else if (Buffer.isBuffer(req.body)) {
|
|
86
|
+
body = req.body;
|
|
87
|
+
}
|
|
88
|
+
else if (typeof req.body === 'string') {
|
|
89
|
+
body = Buffer.from(req.body);
|
|
90
|
+
}
|
|
91
|
+
else if (req.body instanceof ArrayBuffer) {
|
|
92
|
+
body = Buffer.from(req.body);
|
|
93
|
+
}
|
|
94
|
+
else if (ArrayBuffer.isView(req.body)) {
|
|
95
|
+
body = Buffer.from(req.body.buffer, req.body.byteOffset, req.body.byteLength);
|
|
96
|
+
}
|
|
97
|
+
else {
|
|
98
|
+
body = Buffer.from(JSON.stringify(req.body));
|
|
99
|
+
}
|
|
100
|
+
this.validatePacketSize(body);
|
|
101
|
+
const socket = this.getSocket();
|
|
102
|
+
if (!this._socketBound) {
|
|
103
|
+
await new Promise((resolve) => {
|
|
104
|
+
socket.once('listening', () => resolve());
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
const udpReq = req;
|
|
108
|
+
const isBroadcast = udpReq.udp?.broadcast ?? this.udpOptions.broadcast;
|
|
109
|
+
const targetPort = udpReq.udp?.port ?? port;
|
|
110
|
+
const targetAddress = isBroadcast ? '255.255.255.255' : (udpReq.udp?.multicast ?? host);
|
|
111
|
+
if (isBroadcast && !this.udpOptions.broadcast) {
|
|
112
|
+
socket.setBroadcast(true);
|
|
113
|
+
}
|
|
114
|
+
const responseBuffer = await this.sendWithRetry(socket, body, targetPort, targetAddress, req.signal);
|
|
115
|
+
const timings = this.collectTimings();
|
|
116
|
+
const connection = this.collectConnection(socket);
|
|
117
|
+
return new UDPResponseImpl(responseBuffer, {
|
|
118
|
+
timings,
|
|
119
|
+
connection,
|
|
120
|
+
url: fullUrl,
|
|
121
|
+
});
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
async send(host, port, data) {
|
|
125
|
+
this.validatePacketSize(data);
|
|
126
|
+
const socket = this.getSocket();
|
|
127
|
+
if (!this._socketBound) {
|
|
128
|
+
await new Promise((resolve) => {
|
|
129
|
+
socket.once('listening', () => resolve());
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
return new Promise((resolve, reject) => {
|
|
133
|
+
socket.send(data, 0, data.length, port, host, (err) => {
|
|
134
|
+
if (err) {
|
|
135
|
+
reject(err);
|
|
136
|
+
}
|
|
137
|
+
else {
|
|
138
|
+
resolve();
|
|
139
|
+
}
|
|
140
|
+
});
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
async broadcast(port, data) {
|
|
144
|
+
const socket = this.getSocket();
|
|
145
|
+
if (!this._socketBound) {
|
|
146
|
+
await new Promise((resolve) => {
|
|
147
|
+
socket.once('listening', () => resolve());
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
socket.setBroadcast(true);
|
|
151
|
+
await this.send('255.255.255.255', port, data);
|
|
152
|
+
}
|
|
153
|
+
joinMulticast(group) {
|
|
154
|
+
const socket = this.getSocket();
|
|
155
|
+
if (!this.multicastGroups.has(group)) {
|
|
156
|
+
socket.addMembership(group);
|
|
157
|
+
this.multicastGroups.add(group);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
leaveMulticast(group) {
|
|
161
|
+
const socket = this.getSocket();
|
|
162
|
+
if (this.multicastGroups.has(group)) {
|
|
163
|
+
socket.dropMembership(group);
|
|
164
|
+
this.multicastGroups.delete(group);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
async close() {
|
|
168
|
+
if (this.socket) {
|
|
169
|
+
for (const group of this.multicastGroups) {
|
|
170
|
+
try {
|
|
171
|
+
this.socket.dropMembership(group);
|
|
172
|
+
}
|
|
173
|
+
catch {
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
this.multicastGroups.clear();
|
|
177
|
+
return new Promise((resolve) => {
|
|
178
|
+
this.socket.close(() => {
|
|
179
|
+
this.socket = null;
|
|
180
|
+
resolve();
|
|
181
|
+
});
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
export function createUDPClient(options = {}) {
|
|
187
|
+
return new UDPTransportImpl('', options);
|
|
188
|
+
}
|
|
189
|
+
export const udp = {
|
|
190
|
+
async send(address, data, options = {}) {
|
|
191
|
+
const transport = new UDPTransportImpl('', options);
|
|
192
|
+
try {
|
|
193
|
+
const response = await transport.dispatch({
|
|
194
|
+
url: address.startsWith('udp://') ? address : `udp://${address}`,
|
|
195
|
+
method: 'GET',
|
|
196
|
+
headers: new Headers(),
|
|
197
|
+
body: new Uint8Array(data),
|
|
198
|
+
withHeader: () => ({ url: address }),
|
|
199
|
+
withBody: () => ({ url: address }),
|
|
200
|
+
});
|
|
201
|
+
return response;
|
|
202
|
+
}
|
|
203
|
+
finally {
|
|
204
|
+
await transport.close();
|
|
205
|
+
}
|
|
206
|
+
},
|
|
207
|
+
async broadcast(port, data, options = {}) {
|
|
208
|
+
const timeout = options.timeout ?? 3000;
|
|
209
|
+
const results = [];
|
|
210
|
+
const socket = dgram.createSocket({ type: options.type ?? 'udp4', reuseAddr: true });
|
|
211
|
+
return new Promise((resolve) => {
|
|
212
|
+
const startTime = performance.now();
|
|
213
|
+
socket.on('message', (msg, rinfo) => {
|
|
214
|
+
results.push({
|
|
215
|
+
address: rinfo.address,
|
|
216
|
+
port: rinfo.port,
|
|
217
|
+
data: msg,
|
|
218
|
+
latency: performance.now() - startTime,
|
|
219
|
+
});
|
|
220
|
+
});
|
|
221
|
+
socket.on('listening', () => {
|
|
222
|
+
socket.setBroadcast(true);
|
|
223
|
+
socket.send(data, 0, data.length, port, '255.255.255.255');
|
|
224
|
+
});
|
|
225
|
+
socket.bind();
|
|
226
|
+
setTimeout(() => {
|
|
227
|
+
socket.close();
|
|
228
|
+
resolve(results);
|
|
229
|
+
}, timeout);
|
|
230
|
+
});
|
|
231
|
+
},
|
|
232
|
+
async discover(group, port, data, options = {}) {
|
|
233
|
+
const timeout = options.timeout ?? 3000;
|
|
234
|
+
const results = [];
|
|
235
|
+
const socket = dgram.createSocket({ type: options.type ?? 'udp4', reuseAddr: true });
|
|
236
|
+
return new Promise((resolve) => {
|
|
237
|
+
const startTime = performance.now();
|
|
238
|
+
socket.on('message', (msg, rinfo) => {
|
|
239
|
+
results.push({
|
|
240
|
+
address: rinfo.address,
|
|
241
|
+
port: rinfo.port,
|
|
242
|
+
data: msg,
|
|
243
|
+
latency: performance.now() - startTime,
|
|
244
|
+
});
|
|
245
|
+
});
|
|
246
|
+
socket.on('listening', () => {
|
|
247
|
+
socket.addMembership(group);
|
|
248
|
+
socket.setMulticastTTL(options.multicastTTL ?? 1);
|
|
249
|
+
socket.send(data, 0, data.length, port, group);
|
|
250
|
+
});
|
|
251
|
+
socket.bind(port);
|
|
252
|
+
setTimeout(() => {
|
|
253
|
+
socket.dropMembership(group);
|
|
254
|
+
socket.close();
|
|
255
|
+
resolve(results);
|
|
256
|
+
}, timeout);
|
|
257
|
+
});
|
|
258
|
+
},
|
|
259
|
+
};
|
|
260
|
+
export { UDPTransportImpl as UDPTransport };
|