baltica 0.1.18 → 0.1.20
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/client/client.d.ts +3 -0
- package/dist/client/client.js +18 -6
- package/dist/client/types/client-events.d.ts +1 -0
- package/dist/client/types/client-options.d.ts +11 -0
- package/dist/shared/auth/authentication.d.ts +4 -3
- package/dist/shared/auth/authentication.js +131 -19
- package/dist/shared/auth.js +2 -0
- package/package.json +5 -5
package/dist/client/client.d.ts
CHANGED
|
@@ -43,6 +43,7 @@ export declare class Client extends Emitter<ClientEvents> {
|
|
|
43
43
|
startGameData: StartGamePacket;
|
|
44
44
|
/** Whether we should continue after sending Login (Proxy use) */
|
|
45
45
|
cancelPastLogin: boolean;
|
|
46
|
+
private disconnectReason?;
|
|
46
47
|
constructor(options: Partial<ClientOptions>);
|
|
47
48
|
/** Connect to the server and start sending/receiving packets. */
|
|
48
49
|
connect(): Promise<[StartGamePacket]>;
|
|
@@ -53,6 +54,8 @@ export declare class Client extends Emitter<ClientEvents> {
|
|
|
53
54
|
processPacket(buffer: Buffer): void;
|
|
54
55
|
/** Do not call this, leaving it public incase someone needs to override this for some reason. */
|
|
55
56
|
handleGamePackets(): void;
|
|
57
|
+
disconnect(reason?: string): void;
|
|
58
|
+
private cleanup;
|
|
56
59
|
startEncryption(iv: Buffer): void;
|
|
57
60
|
private waitForSessionReady;
|
|
58
61
|
/**
|
package/dist/client/client.js
CHANGED
|
@@ -35,16 +35,16 @@ class Client extends shared_1.Emitter {
|
|
|
35
35
|
? new worker_1.WorkerClient({
|
|
36
36
|
address: this.options.address,
|
|
37
37
|
port: this.options.port,
|
|
38
|
+
proxy: this.options.proxy,
|
|
38
39
|
})
|
|
39
40
|
: new raknet_1.Client({
|
|
40
41
|
address: this.options.address,
|
|
41
42
|
port: this.options.port,
|
|
43
|
+
proxy: this.options.proxy,
|
|
42
44
|
});
|
|
43
45
|
/** Create ClientData to store and handle auth data */
|
|
44
46
|
this.data = new types_2.ClientData(this);
|
|
45
|
-
|
|
46
|
-
/** Session event gets mojang (minecraft) auth session */
|
|
47
|
-
/** NOTE! This takes like 30-100ms for offline mode which kinda feels slow but not really */
|
|
47
|
+
this.raknet.on("disconnect", () => this.cleanup());
|
|
48
48
|
this.once("session", () => {
|
|
49
49
|
this.sessionReady = true;
|
|
50
50
|
});
|
|
@@ -182,11 +182,23 @@ class Client extends shared_1.Emitter {
|
|
|
182
182
|
}
|
|
183
183
|
});
|
|
184
184
|
this.on("DisconnectPacket", (packet) => {
|
|
185
|
-
this.
|
|
186
|
-
this.raknet.disconnect();
|
|
187
|
-
console.log(packet.message);
|
|
185
|
+
this.disconnect(packet.message.message ?? undefined);
|
|
188
186
|
});
|
|
189
187
|
}
|
|
188
|
+
disconnect(reason) {
|
|
189
|
+
if (this.status === raknet_1.ConnectionStatus.Disconnected)
|
|
190
|
+
return;
|
|
191
|
+
this.disconnectReason = reason;
|
|
192
|
+
this.raknet.disconnect();
|
|
193
|
+
}
|
|
194
|
+
cleanup() {
|
|
195
|
+
this.status = raknet_1.ConnectionStatus.Disconnected;
|
|
196
|
+
this._encryptionEnabled = false;
|
|
197
|
+
this._compressionEnabled = false;
|
|
198
|
+
this.sessionReady = false;
|
|
199
|
+
this.emit("disconnect", this.disconnectReason);
|
|
200
|
+
this.disconnectReason = undefined;
|
|
201
|
+
}
|
|
190
202
|
startEncryption(iv) {
|
|
191
203
|
this.packetEncryptor = new shared_1.PacketEncryptor(this, iv);
|
|
192
204
|
this._encryptionEnabled = true;
|
|
@@ -47,6 +47,17 @@ export type ClientOptions = {
|
|
|
47
47
|
email?: string;
|
|
48
48
|
/** Password for direct email/password authentication (requires 2FA disabled) */
|
|
49
49
|
password?: string;
|
|
50
|
+
/** SOCKS5 Proxy for the client to use. */
|
|
51
|
+
proxy?: {
|
|
52
|
+
/** Proxy Host */
|
|
53
|
+
host: string;
|
|
54
|
+
/** Proxy Port */
|
|
55
|
+
port: number;
|
|
56
|
+
/** Proxy Username */
|
|
57
|
+
userId?: string;
|
|
58
|
+
/** Proxy Password */
|
|
59
|
+
password?: string;
|
|
60
|
+
};
|
|
50
61
|
};
|
|
51
62
|
/** Default Client Options */
|
|
52
63
|
export declare const defaultClientOptions: ClientOptions;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { live, xnet } from "@xboxreplay/xboxlive-auth";
|
|
2
2
|
import type { AuthenticateResponse, Email } from "@xboxreplay/xboxlive-auth";
|
|
3
3
|
export interface BedrockTokens {
|
|
4
4
|
chains: string[];
|
|
@@ -10,11 +10,12 @@ export interface AuthOptions {
|
|
|
10
10
|
email: string;
|
|
11
11
|
password: string;
|
|
12
12
|
clientPublicKey: string;
|
|
13
|
+
cacheDir?: string;
|
|
13
14
|
}
|
|
14
15
|
/**
|
|
15
16
|
* Authenticates with Xbox Live using email/password and obtains Minecraft Bedrock tokens
|
|
16
|
-
*
|
|
17
|
+
* Caches Xbox user token (~14 days valid) to minimize login requests
|
|
17
18
|
*/
|
|
18
19
|
export declare function authenticateWithCredentials(options: AuthOptions): Promise<BedrockTokens>;
|
|
19
|
-
export {
|
|
20
|
+
export { live, xnet };
|
|
20
21
|
export type { AuthenticateResponse, Email };
|
|
@@ -1,39 +1,144 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
2
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.xnet = exports.live =
|
|
36
|
+
exports.xnet = exports.live = void 0;
|
|
4
37
|
exports.authenticateWithCredentials = authenticateWithCredentials;
|
|
5
38
|
const xboxlive_auth_1 = require("@xboxreplay/xboxlive-auth");
|
|
6
|
-
Object.defineProperty(exports, "authenticate", { enumerable: true, get: function () { return xboxlive_auth_1.authenticate; } });
|
|
7
39
|
Object.defineProperty(exports, "live", { enumerable: true, get: function () { return xboxlive_auth_1.live; } });
|
|
8
40
|
Object.defineProperty(exports, "xnet", { enumerable: true, get: function () { return xboxlive_auth_1.xnet; } });
|
|
41
|
+
const fs = __importStar(require("node:fs"));
|
|
42
|
+
const path = __importStar(require("node:path"));
|
|
9
43
|
const raknet_1 = require("@sanctumterra/raknet");
|
|
10
44
|
const MINECRAFT_BEDROCK_RELYING_PARTY = "https://multiplayer.minecraft.net/";
|
|
45
|
+
function hashString(str) {
|
|
46
|
+
let hash = 0;
|
|
47
|
+
for (let i = 0; i < str.length; i++) {
|
|
48
|
+
const char = str.charCodeAt(i);
|
|
49
|
+
hash = (hash << 5) - hash + char;
|
|
50
|
+
hash = hash & hash;
|
|
51
|
+
}
|
|
52
|
+
return Math.abs(hash).toString(16).slice(0, 6);
|
|
53
|
+
}
|
|
54
|
+
function ensureDir(dir) {
|
|
55
|
+
if (!fs.existsSync(dir)) {
|
|
56
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
function getCacheFile(cacheDir, email) {
|
|
60
|
+
return path.join(cacheDir, `${hashString(email)}_xbl-user-cache.json`);
|
|
61
|
+
}
|
|
62
|
+
function loadCache(cacheFile) {
|
|
63
|
+
try {
|
|
64
|
+
if (fs.existsSync(cacheFile)) {
|
|
65
|
+
const cached = JSON.parse(fs.readFileSync(cacheFile, "utf-8"));
|
|
66
|
+
// Check if token is still valid (with 1 hour buffer)
|
|
67
|
+
const expiresAt = new Date(cached.notAfter).getTime();
|
|
68
|
+
if (Date.now() < expiresAt - 3600000) {
|
|
69
|
+
return cached;
|
|
70
|
+
}
|
|
71
|
+
raknet_1.Logger.info("Cached user token expired");
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
catch {
|
|
75
|
+
/* ignore */
|
|
76
|
+
}
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
79
|
+
function saveCache(cacheFile, userToken, userHash, notAfter) {
|
|
80
|
+
try {
|
|
81
|
+
ensureDir(path.dirname(cacheFile));
|
|
82
|
+
const data = {
|
|
83
|
+
userToken,
|
|
84
|
+
userHash,
|
|
85
|
+
notAfter,
|
|
86
|
+
obtainedOn: Date.now(),
|
|
87
|
+
};
|
|
88
|
+
fs.writeFileSync(cacheFile, JSON.stringify(data, null, 2));
|
|
89
|
+
}
|
|
90
|
+
catch {
|
|
91
|
+
/* ignore */
|
|
92
|
+
}
|
|
93
|
+
}
|
|
11
94
|
/**
|
|
12
95
|
* Authenticates with Xbox Live using email/password and obtains Minecraft Bedrock tokens
|
|
13
|
-
*
|
|
96
|
+
* Caches Xbox user token (~14 days valid) to minimize login requests
|
|
14
97
|
*/
|
|
15
98
|
async function authenticateWithCredentials(options) {
|
|
16
|
-
const { email, password, clientPublicKey } = options;
|
|
17
|
-
|
|
99
|
+
const { email, password, clientPublicKey, cacheDir } = options;
|
|
100
|
+
const cacheFile = cacheDir ? getCacheFile(cacheDir, email) : null;
|
|
101
|
+
let userToken;
|
|
102
|
+
let userHash;
|
|
103
|
+
// Try to use cached user token first
|
|
104
|
+
const cached = cacheFile ? loadCache(cacheFile) : null;
|
|
105
|
+
if (cached) {
|
|
106
|
+
raknet_1.Logger.info("Using cached Xbox user token...");
|
|
107
|
+
userToken = cached.userToken;
|
|
108
|
+
userHash = cached.userHash;
|
|
109
|
+
}
|
|
110
|
+
else {
|
|
111
|
+
// Fresh login required
|
|
112
|
+
raknet_1.Logger.info("Authenticating with Xbox Live...");
|
|
113
|
+
const accessToken = await freshLogin(email, password);
|
|
114
|
+
// Exchange for Xbox user token (valid ~14 days)
|
|
115
|
+
const userTokenResp = await xboxlive_auth_1.xnet.exchangeRpsTicketForUserToken(accessToken, "t");
|
|
116
|
+
userToken = userTokenResp.Token;
|
|
117
|
+
userHash = userTokenResp.DisplayClaims.xui[0].uhs;
|
|
118
|
+
// Cache the user token
|
|
119
|
+
if (cacheFile) {
|
|
120
|
+
saveCache(cacheFile, userToken, userHash, userTokenResp.NotAfter);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
// Get XSTS token for Minecraft Bedrock (short-lived, always fetch fresh)
|
|
124
|
+
const xstsResp = await xboxlive_auth_1.xnet.exchangeTokenForXSTSToken(userToken, {
|
|
125
|
+
XSTSRelyingParty: MINECRAFT_BEDROCK_RELYING_PARTY,
|
|
126
|
+
sandboxId: "RETAIL",
|
|
127
|
+
});
|
|
128
|
+
const xuid = xstsResp.DisplayClaims.xui[0].xid || "";
|
|
129
|
+
// Get Minecraft Bedrock chains
|
|
130
|
+
const chains = await getMinecraftBedrockChains(xstsResp.Token, userHash, clientPublicKey);
|
|
131
|
+
const gamertag = extractGamertagFromChains(chains);
|
|
132
|
+
raknet_1.Logger.info(`Authenticated as: ${gamertag} (${xuid})`);
|
|
133
|
+
return { chains, xuid, gamertag, userHash };
|
|
134
|
+
}
|
|
135
|
+
async function freshLogin(email, password) {
|
|
18
136
|
try {
|
|
19
137
|
const liveToken = await xboxlive_auth_1.live.authenticateWithCredentials({
|
|
20
138
|
email: email,
|
|
21
139
|
password,
|
|
22
140
|
});
|
|
23
|
-
|
|
24
|
-
const userTokenResp = await xboxlive_auth_1.xnet.exchangeRpsTicketForUserToken(liveToken.access_token, "t");
|
|
25
|
-
const userHash = userTokenResp.DisplayClaims.xui[0].uhs;
|
|
26
|
-
// Get XSTS token for Minecraft Bedrock
|
|
27
|
-
const xstsResp = await xboxlive_auth_1.xnet.exchangeTokenForXSTSToken(userTokenResp.Token, {
|
|
28
|
-
XSTSRelyingParty: MINECRAFT_BEDROCK_RELYING_PARTY,
|
|
29
|
-
sandboxId: "RETAIL",
|
|
30
|
-
});
|
|
31
|
-
const xuid = xstsResp.DisplayClaims.xui[0].xid || "";
|
|
32
|
-
// Get Minecraft Bedrock chains using the client's public key
|
|
33
|
-
const chains = await getMinecraftBedrockChains(xstsResp.Token, userHash, clientPublicKey);
|
|
34
|
-
const gamertag = extractGamertagFromChains(chains);
|
|
35
|
-
raknet_1.Logger.info(`Authenticated as: ${gamertag} (${xuid})`);
|
|
36
|
-
return { chains, xuid, gamertag, userHash };
|
|
141
|
+
return liveToken.access_token;
|
|
37
142
|
}
|
|
38
143
|
catch (error) {
|
|
39
144
|
const err = error;
|
|
@@ -56,6 +161,13 @@ async function getMinecraftBedrockChains(xstsToken, userHash, clientPublicKey) {
|
|
|
56
161
|
});
|
|
57
162
|
if (!response.ok) {
|
|
58
163
|
const text = await response.text();
|
|
164
|
+
if (response.status === 401) {
|
|
165
|
+
throw new Error("Minecraft Bedrock authentication failed (401 UNAUTHORIZED).\n" +
|
|
166
|
+
"This usually means:\n" +
|
|
167
|
+
" 1. The account does not have an Xbox profile (create one at xbox.com)\n" +
|
|
168
|
+
" 2. The account does not own Minecraft Bedrock Edition\n" +
|
|
169
|
+
" 3. The account needs to accept Xbox/Minecraft terms of service");
|
|
170
|
+
}
|
|
59
171
|
throw new Error(`Minecraft Bedrock auth failed: ${response.status} - ${text}`);
|
|
60
172
|
}
|
|
61
173
|
const data = (await response.json());
|
package/dist/shared/auth.js
CHANGED
|
@@ -106,6 +106,7 @@ async function authenticateWithEmailPassword(client) {
|
|
|
106
106
|
email: client.options.email,
|
|
107
107
|
password: client.options.password,
|
|
108
108
|
clientPublicKey: client.data.loginData.clientX509,
|
|
109
|
+
cacheDir: client.options.tokensFolder,
|
|
109
110
|
});
|
|
110
111
|
const profile = {
|
|
111
112
|
name: tokens.gamertag,
|
|
@@ -120,6 +121,7 @@ async function authenticateWithEmailPassword(client) {
|
|
|
120
121
|
}
|
|
121
122
|
catch (error) {
|
|
122
123
|
raknet_1.Logger.error(`Email/password authentication failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
124
|
+
raknet_1.Logger.warn("Make sure you have an xbox profile crated!");
|
|
123
125
|
throw error;
|
|
124
126
|
}
|
|
125
127
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "baltica",
|
|
3
3
|
"description": "Library for Minecraft Bedrock Edition community developers.",
|
|
4
|
-
"version": "0.1.
|
|
4
|
+
"version": "0.1.20",
|
|
5
5
|
"minecraft": "1.21.130",
|
|
6
6
|
"main": "dist/index.js",
|
|
7
7
|
"license": "MIT",
|
|
@@ -21,9 +21,9 @@
|
|
|
21
21
|
}
|
|
22
22
|
],
|
|
23
23
|
"dependencies": {
|
|
24
|
-
"@sanctumterra/raknet": "^1.4.
|
|
24
|
+
"@sanctumterra/raknet": "^1.4.8",
|
|
25
25
|
"@serenityjs/binarystream": "^3.0.10",
|
|
26
|
-
"@serenityjs/protocol": "^0.8.
|
|
26
|
+
"@serenityjs/protocol": "^0.8.17",
|
|
27
27
|
"@xboxreplay/xboxlive-auth": "^5.1.0",
|
|
28
28
|
"jose": "^5.10.0",
|
|
29
29
|
"prismarine-auth": "^2.7.0",
|
|
@@ -31,8 +31,8 @@
|
|
|
31
31
|
},
|
|
32
32
|
"devDependencies": {
|
|
33
33
|
"@biomejs/biome": "1.9.4",
|
|
34
|
-
"@types/node": "^22.
|
|
34
|
+
"@types/node": "^22.19.6",
|
|
35
35
|
"@types/uuid-1345": "^0.99.25",
|
|
36
|
-
"typescript": "^5.9.
|
|
36
|
+
"typescript": "^5.9.3"
|
|
37
37
|
}
|
|
38
38
|
}
|