Skip to content

Commit 261cf51

Browse files
committed
Go back to shared UDP sockets, expose unref(), make UDP sockets exclusive by default
1 parent d3e583b commit 261cf51

10 files changed

Lines changed: 263 additions & 173 deletions

File tree

.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
.idea/
22
node_modules/
3-
test.js
3+
example.js
44

README.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,10 +26,14 @@ let client = new QueryClient();
2626

2727
let basic = await client.queryBasic('localhost', 25565, AbortSignal.timeout(5000));
2828
let full = await client.queryFull('localhost', 25565, AbortSignal.timeout(5000));
29+
30+
await client.close();
2931
```
3032
Basic and full query requests will return a [`BasicStatResponse`](src/Packet/Query/BasicStatResponse.js)
3133
and [`FullStatResponse`](src/Packet/Query/FullStatResponse.js) object respectively.
3234

35+
Note that the query client needs to be closed manually, since it keeps its UDP socket open to reuse it for future queries.
36+
3337
### Java Edition Ping
3438
The [Server List Ping protocol](https://wiki.vg/Server_List_Ping) is what the Minecraft client uses to show the server status in the in-game server list.
3539
This protocol changed multiple times over the years, so you'd ideally want to know the version of the server you are pinging to use the correct protocol version.
@@ -94,5 +98,9 @@ import {BedrockPingClient} from 'craftping';
9498
let client = new BedrockPingClient();
9599

96100
let status = await client.ping('localhost', 19132, AbortSignal.timeout(5000));
101+
102+
await client.close();
97103
```
98104
Pinging a Bedrock server will return an [`UnconnectedPong`](src/Packet/BedrockPing/UnconnectedPong.js) object.
105+
106+
Note that the Bedrock ping client also needs to be closed manually, since it keeps its UDP socket open to reuse it for future requests.

index.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,4 +65,4 @@ export { default as TCPSocket } from "./src/TCPSocket/TCPSocket.js";
6565
// UDPSocket
6666
export { default as UDPClient } from "./src/UDPSocket/UDPClient.js";
6767
export { default as UDPMessage } from "./src/UDPSocket/UDPMessage.js";
68-
export { default as UDPSocket } from "./src/UDPSocket/UDPSocket.js";
68+
export { default as SharedUDPSocket } from "./src/UDPSocket/SharedUDPSocket.js";

package-lock.json

Lines changed: 39 additions & 39 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/BedrockPing/BedrockPing.js

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,19 @@ import * as crypto from "node:crypto";
66
export default class BedrockPing extends UDPClient {
77
/** @type {BigInt} */ sessionId;
88

9+
/**
10+
* @inheritDoc
11+
*/
12+
appliesTo(message) {
13+
let data = message.getData();
14+
if (data.byteLength < 9) {
15+
return false;
16+
}
17+
18+
let timestamp = data.readBigInt64BE(1);
19+
return timestamp === this.sessionId;
20+
}
21+
922
/**
1023
* @return {Promise<UnconnectedPong>}
1124
*/
@@ -14,24 +27,11 @@ export default class BedrockPing extends UDPClient {
1427
// to identify which reply belongs to which request.
1528
this.sessionId = crypto.randomBytes(8).readBigInt64BE();
1629
let startTime = BigInt(Date.now());
17-
await this.sendPacket(new UnconnectedPing().setTime(this.sessionId).generateClientGUID());
30+
await this.send(new UnconnectedPing().setTime(this.sessionId).generateClientGUID().write());
1831
this.signal?.throwIfAborted();
1932

2033
// The time field in the response contains the session ID, but we replace it with the start time
2134
// in case anyone relies on the time field containing an actual timestamp.
2235
return new UnconnectedPong().read(await this.readData()).setTime(startTime);
2336
}
24-
25-
/**
26-
* @inheritDoc
27-
*/
28-
shouldAcceptPacket(packet) {
29-
let data = packet.getData();
30-
if (data.byteLength < 9) {
31-
return false;
32-
}
33-
34-
let timestamp = data.readBigInt64BE(1);
35-
return timestamp === this.sessionId;
36-
}
3737
}
Lines changed: 6 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,24 @@
1-
import UDPSocket from "../UDPSocket/UDPSocket.js";
21
import BedrockPing from "./BedrockPing.js";
2+
import SharedUDPSocket from "../UDPSocket/SharedUDPSocket.js";
33

4-
export default class BedrockPingClient {
5-
/** @type {import("node:dgram").SocketOptions}} */ socketOptions;
6-
/** @type {import("node:dgram").BindOptions}} */ bindOptions;
7-
8-
/**
9-
* @param {import("node:dgram").SocketOptions} socketOptions
10-
* @param {import("node:dgram").BindOptions} bindOptions
11-
*/
12-
constructor(socketOptions = {type: "udp4"}, bindOptions = {}) {
13-
this.socketOptions = socketOptions;
14-
this.bindOptions = bindOptions;
15-
}
16-
4+
export default class BedrockPingClient extends SharedUDPSocket {
175
/**
186
* @param {string} address
197
* @param {number} port
208
* @param {?AbortSignal} signal
219
* @return {Promise<UnconnectedPong>}
2210
*/
2311
async ping(address, port, signal = null) {
24-
let ping = new BedrockPing(address, port, signal, this.socketOptions, this.bindOptions);
25-
await ping.bind(signal);
12+
let ping = new BedrockPing(address, port, this, signal);
13+
await ping.connect();
2614
let result;
2715
try {
2816
result = await ping.ping();
2917
} catch (e) {
30-
await ping.close();
18+
ping.close();
3119
throw e;
3220
}
33-
await ping.close();
21+
ping.close();
3422
return result;
3523
}
3624
}

src/Query/Query.js

Lines changed: 20 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,27 @@ export default class Query extends UDPClient {
1010
/** @type {number} */ sessionId;
1111
/** @type {number} */ challengeToken;
1212

13+
/**
14+
* @inheritDoc
15+
*/
16+
appliesTo(message) {
17+
let data = message.getData();
18+
if (data.byteLength < 5) {
19+
return false;
20+
}
21+
22+
let session = data.readUInt32BE(1);
23+
return session === this.sessionId;
24+
}
25+
1326
/**
1427
* @return {Promise<this>}
1528
*/
1629
async handshake() {
1730
let handshakeRequest = new HandshakeRequest().generateSessionId();
1831
this.sessionId = handshakeRequest.getSessionId();
1932

20-
await this.sendPacket(handshakeRequest);
33+
await this.send(handshakeRequest.write());
2134
this.signal?.throwIfAborted();
2235

2336
let handshakeResponse = new HandshakeResponse().read(await this.readData());
@@ -31,9 +44,10 @@ export default class Query extends UDPClient {
3144
async queryBasic() {
3245
await this.handshake();
3346

34-
await this.sendPacket(new BasicStatRequest()
47+
await this.send(new BasicStatRequest()
3548
.setSessionId(this.sessionId)
36-
.setChallengeToken(this.challengeToken));
49+
.setChallengeToken(this.challengeToken)
50+
.write());
3751

3852
this.signal?.throwIfAborted();
3953
return new BasicStatResponse().read(await this.readData());
@@ -45,24 +59,12 @@ export default class Query extends UDPClient {
4559
async queryFull() {
4660
await this.handshake();
4761

48-
await this.sendPacket(new FullStatRequest()
62+
await this.send(new FullStatRequest()
4963
.setSessionId(this.sessionId)
50-
.setChallengeToken(this.challengeToken));
64+
.setChallengeToken(this.challengeToken)
65+
.write());
5166

5267
this.signal?.throwIfAborted();
5368
return new FullStatResponse().read(await this.readData());
5469
}
55-
56-
/**
57-
* @inheritDoc
58-
*/
59-
shouldAcceptPacket(packet) {
60-
let data = packet.getData();
61-
if (data.byteLength < 5) {
62-
return false;
63-
}
64-
65-
let session = data.readUInt32BE(1);
66-
return session === this.sessionId;
67-
}
6870
}

0 commit comments

Comments
 (0)