workers-rcon 1.0.1 → 1.0.2
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/package.json +1 -1
- package/src/index.ts +57 -26
- package/dist/index.d.ts +0 -15
- package/dist/index.js +0 -67
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { connect } from 'cloudflare:sockets';
|
|
2
2
|
|
|
3
|
-
// Типы пакетов RCON
|
|
4
3
|
const SERVERDATA_AUTH = 3;
|
|
5
4
|
const SERVERDATA_EXECCOMMAND = 2;
|
|
6
5
|
const SERVERDATA_RESPONSE_VALUE = 0;
|
|
@@ -12,11 +11,18 @@ export interface RCONConfig {
|
|
|
12
11
|
password: string;
|
|
13
12
|
}
|
|
14
13
|
|
|
14
|
+
interface RCONPacket {
|
|
15
|
+
id: number;
|
|
16
|
+
type: number;
|
|
17
|
+
body: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
15
20
|
export class MinecraftRCON {
|
|
16
21
|
private host: string;
|
|
17
22
|
private port: number;
|
|
18
23
|
private password: string;
|
|
19
24
|
private requestId = 1;
|
|
25
|
+
private readBuffer = new Uint8Array(0);
|
|
20
26
|
|
|
21
27
|
constructor(config: RCONConfig) {
|
|
22
28
|
this.host = config.host;
|
|
@@ -30,36 +36,40 @@ export class MinecraftRCON {
|
|
|
30
36
|
const reader = socket.readable.getReader();
|
|
31
37
|
|
|
32
38
|
try {
|
|
33
|
-
// 1. Авторизация
|
|
34
39
|
await this.writePacket(writer, SERVERDATA_AUTH, this.password);
|
|
35
40
|
|
|
36
|
-
|
|
37
|
-
let
|
|
38
|
-
|
|
41
|
+
let authRes1 = await this.readPacket(reader);
|
|
42
|
+
let authRes2: RCONPacket | null = null;
|
|
43
|
+
|
|
44
|
+
if (authRes1.type === SERVERDATA_RESPONSE_VALUE) {
|
|
45
|
+
authRes2 = await this.readPacket(reader);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const finalAuth = authRes2 || authRes1;
|
|
49
|
+
if (finalAuth.id === -1) {
|
|
39
50
|
throw new Error('RCON Authentication failed: Invalid password');
|
|
40
51
|
}
|
|
41
52
|
|
|
42
|
-
// 2. Выполнение команды
|
|
43
53
|
await this.writePacket(writer, SERVERDATA_EXECCOMMAND, command);
|
|
44
54
|
const cmdRes = await this.readPacket(reader);
|
|
45
|
-
|
|
55
|
+
|
|
46
56
|
return cmdRes.body;
|
|
47
57
|
} finally {
|
|
48
|
-
|
|
49
|
-
|
|
58
|
+
writer.releaseLock();
|
|
59
|
+
reader.releaseLock();
|
|
60
|
+
await socket.close();
|
|
50
61
|
}
|
|
51
62
|
}
|
|
52
63
|
|
|
53
64
|
private async writePacket(writer: WritableStreamDefaultWriter, type: number, body: string) {
|
|
54
65
|
const encoder = new TextEncoder();
|
|
55
66
|
const bodyBytes = encoder.encode(body);
|
|
56
|
-
// Длина: 4(id) + 4(type) + body + 2(null terminators)
|
|
57
67
|
const packetLength = 10 + bodyBytes.length;
|
|
58
68
|
|
|
59
|
-
const buffer = new ArrayBuffer(packetLength + 4);
|
|
69
|
+
const buffer = new ArrayBuffer(packetLength + 4);
|
|
60
70
|
const view = new DataView(buffer);
|
|
61
|
-
|
|
62
|
-
view.setInt32(0, packetLength, true);
|
|
71
|
+
|
|
72
|
+
view.setInt32(0, packetLength, true);
|
|
63
73
|
view.setInt32(4, this.requestId, true);
|
|
64
74
|
view.setInt32(8, type, true);
|
|
65
75
|
|
|
@@ -67,22 +77,43 @@ export class MinecraftRCON {
|
|
|
67
77
|
uint8.set(bodyBytes, 12);
|
|
68
78
|
uint8[buffer.byteLength - 2] = 0;
|
|
69
79
|
uint8[buffer.byteLength - 1] = 0;
|
|
70
|
-
|
|
80
|
+
|
|
71
81
|
await writer.write(buffer);
|
|
72
82
|
}
|
|
73
83
|
|
|
74
|
-
private async readPacket(reader: ReadableStreamDefaultReader) {
|
|
75
|
-
const
|
|
76
|
-
if (done || !value) throw new Error('Connection closed by server');
|
|
84
|
+
private async readPacket(reader: ReadableStreamDefaultReader<Uint8Array>): Promise<RCONPacket> {
|
|
85
|
+
const decoder = new TextDecoder();
|
|
77
86
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
87
|
+
while (true) {
|
|
88
|
+
if (this.readBuffer.length >= 4) {
|
|
89
|
+
const view = new DataView(this.readBuffer.buffer, this.readBuffer.byteOffset, this.readBuffer.byteLength);
|
|
90
|
+
const length = view.getInt32(0, true);
|
|
91
|
+
const totalPacketLength = length + 4;
|
|
92
|
+
|
|
93
|
+
if (this.readBuffer.length >= totalPacketLength) {
|
|
94
|
+
const id = view.getInt32(4, true);
|
|
95
|
+
const type = view.getInt32(8, true);
|
|
96
|
+
|
|
97
|
+
const bodyBytes = this.readBuffer.subarray(12, totalPacketLength - 2);
|
|
98
|
+
const body = decoder.decode(bodyBytes);
|
|
99
|
+
|
|
100
|
+
this.readBuffer = this.readBuffer.slice(totalPacketLength);
|
|
101
|
+
|
|
102
|
+
return { id, type, body };
|
|
103
|
+
}
|
|
104
|
+
}
|
|
85
105
|
|
|
86
|
-
|
|
106
|
+
const { value, done } = await reader.read();
|
|
107
|
+
if (done) {
|
|
108
|
+
throw new Error('Connection closed by RCON server unexpectedly');
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
if (value) {
|
|
112
|
+
const newBuffer = new Uint8Array(this.readBuffer.length + value.length);
|
|
113
|
+
newBuffer.set(this.readBuffer);
|
|
114
|
+
newBuffer.set(value, this.readBuffer.length);
|
|
115
|
+
this.readBuffer = newBuffer;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
87
118
|
}
|
|
88
|
-
}
|
|
119
|
+
}
|
package/dist/index.d.ts
DELETED
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
export interface RCONConfig {
|
|
2
|
-
host: string;
|
|
3
|
-
port: number;
|
|
4
|
-
password: string;
|
|
5
|
-
}
|
|
6
|
-
export declare class MinecraftRCON {
|
|
7
|
-
private host;
|
|
8
|
-
private port;
|
|
9
|
-
private password;
|
|
10
|
-
private requestId;
|
|
11
|
-
constructor(config: RCONConfig);
|
|
12
|
-
run(command: string): Promise<string>;
|
|
13
|
-
private writePacket;
|
|
14
|
-
private readPacket;
|
|
15
|
-
}
|
package/dist/index.js
DELETED
|
@@ -1,67 +0,0 @@
|
|
|
1
|
-
import { connect } from 'cloudflare:sockets';
|
|
2
|
-
// Типы пакетов RCON
|
|
3
|
-
const SERVERDATA_AUTH = 3;
|
|
4
|
-
const SERVERDATA_EXECCOMMAND = 2;
|
|
5
|
-
const SERVERDATA_RESPONSE_VALUE = 0;
|
|
6
|
-
const SERVERDATA_AUTH_RESPONSE = 2;
|
|
7
|
-
export class MinecraftRCON {
|
|
8
|
-
host;
|
|
9
|
-
port;
|
|
10
|
-
password;
|
|
11
|
-
requestId = 1;
|
|
12
|
-
constructor(config) {
|
|
13
|
-
this.host = config.host;
|
|
14
|
-
this.port = config.port;
|
|
15
|
-
this.password = config.password;
|
|
16
|
-
}
|
|
17
|
-
async run(command) {
|
|
18
|
-
const socket = connect({ hostname: this.host, port: this.port });
|
|
19
|
-
const writer = socket.writable.getWriter();
|
|
20
|
-
const reader = socket.readable.getReader();
|
|
21
|
-
try {
|
|
22
|
-
// 1. Авторизация
|
|
23
|
-
await this.writePacket(writer, SERVERDATA_AUTH, this.password);
|
|
24
|
-
// Читаем ответ авторизации
|
|
25
|
-
let authRes = await this.readPacket(reader);
|
|
26
|
-
if (authRes.id === -1) {
|
|
27
|
-
throw new Error('RCON Authentication failed: Invalid password');
|
|
28
|
-
}
|
|
29
|
-
// 2. Выполнение команды
|
|
30
|
-
await this.writePacket(writer, SERVERDATA_EXECCOMMAND, command);
|
|
31
|
-
const cmdRes = await this.readPacket(reader);
|
|
32
|
-
return cmdRes.body;
|
|
33
|
-
}
|
|
34
|
-
finally {
|
|
35
|
-
await writer.close();
|
|
36
|
-
socket.close();
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
async writePacket(writer, type, body) {
|
|
40
|
-
const encoder = new TextEncoder();
|
|
41
|
-
const bodyBytes = encoder.encode(body);
|
|
42
|
-
// Длина: 4(id) + 4(type) + body + 2(null terminators)
|
|
43
|
-
const packetLength = 10 + bodyBytes.length;
|
|
44
|
-
const buffer = new ArrayBuffer(packetLength + 4); // +4 для самого поля длины
|
|
45
|
-
const view = new DataView(buffer);
|
|
46
|
-
view.setInt32(0, packetLength, true);
|
|
47
|
-
view.setInt32(4, this.requestId, true);
|
|
48
|
-
view.setInt32(8, type, true);
|
|
49
|
-
const uint8 = new Uint8Array(buffer);
|
|
50
|
-
uint8.set(bodyBytes, 12);
|
|
51
|
-
uint8[buffer.byteLength - 2] = 0;
|
|
52
|
-
uint8[buffer.byteLength - 1] = 0;
|
|
53
|
-
await writer.write(buffer);
|
|
54
|
-
}
|
|
55
|
-
async readPacket(reader) {
|
|
56
|
-
const { value, done } = await reader.read();
|
|
57
|
-
if (done || !value)
|
|
58
|
-
throw new Error('Connection closed by server');
|
|
59
|
-
const view = new DataView(value.buffer, value.byteOffset, value.byteLength);
|
|
60
|
-
const length = view.getInt32(0, true);
|
|
61
|
-
const id = view.getInt32(4, true);
|
|
62
|
-
const type = view.getInt32(8, true);
|
|
63
|
-
const bodyBytes = value.slice(12, 12 + (length - 10));
|
|
64
|
-
const body = new TextDecoder().decode(bodyBytes);
|
|
65
|
-
return { id, type, body };
|
|
66
|
-
}
|
|
67
|
-
}
|