baltica 0.1.19 → 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
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
|
@@ -44,9 +44,7 @@ class Client extends shared_1.Emitter {
|
|
|
44
44
|
});
|
|
45
45
|
/** Create ClientData to store and handle auth data */
|
|
46
46
|
this.data = new types_2.ClientData(this);
|
|
47
|
-
|
|
48
|
-
/** Session event gets mojang (minecraft) auth session */
|
|
49
|
-
/** NOTE! This takes like 30-100ms for offline mode which kinda feels slow but not really */
|
|
47
|
+
this.raknet.on("disconnect", () => this.cleanup());
|
|
50
48
|
this.once("session", () => {
|
|
51
49
|
this.sessionReady = true;
|
|
52
50
|
});
|
|
@@ -184,11 +182,23 @@ class Client extends shared_1.Emitter {
|
|
|
184
182
|
}
|
|
185
183
|
});
|
|
186
184
|
this.on("DisconnectPacket", (packet) => {
|
|
187
|
-
this.
|
|
188
|
-
this.raknet.disconnect();
|
|
189
|
-
console.log(packet.message);
|
|
185
|
+
this.disconnect(packet.message.message ?? undefined);
|
|
190
186
|
});
|
|
191
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
|
+
}
|
|
192
202
|
startEncryption(iv) {
|
|
193
203
|
this.packetEncryptor = new shared_1.PacketEncryptor(this, iv);
|
|
194
204
|
this._encryptionEnabled = true;
|
|
@@ -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;
|
|
@@ -41,22 +146,6 @@ async function authenticateWithCredentials(options) {
|
|
|
41
146
|
throw new Error("Authentication failed: Invalid credentials or 2FA is enabled.\n" +
|
|
42
147
|
"Direct email/password login only works with 2FA DISABLED.");
|
|
43
148
|
}
|
|
44
|
-
// Check for Xbox Live specific errors
|
|
45
|
-
const xErr = err.data?.attributes?.extra?.body?.XErr;
|
|
46
|
-
if (xErr) {
|
|
47
|
-
const xboxErrors = {
|
|
48
|
-
2148916233: "No Xbox profile exists for this account. Create one at https://xbox.com/live",
|
|
49
|
-
2148916227: "Account banned by Xbox for violating Community Standards.",
|
|
50
|
-
2148916229: "Account restricted - guardian permission required. Visit https://account.microsoft.com/family/",
|
|
51
|
-
2148916234: "Must accept Xbox Terms of Service. Login at https://xbox.com",
|
|
52
|
-
2148916235: "Account region not authorized by Xbox.",
|
|
53
|
-
2148916236: "Account requires age verification. Login at https://login.live.com",
|
|
54
|
-
2148916238: "Account under 18 must be added to a family by an adult.",
|
|
55
|
-
};
|
|
56
|
-
if (xboxErrors[xErr]) {
|
|
57
|
-
throw new Error(`Xbox Live error: ${xboxErrors[xErr]}`);
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
149
|
throw error;
|
|
61
150
|
}
|
|
62
151
|
}
|
|
@@ -72,6 +161,13 @@ async function getMinecraftBedrockChains(xstsToken, userHash, clientPublicKey) {
|
|
|
72
161
|
});
|
|
73
162
|
if (!response.ok) {
|
|
74
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
|
+
}
|
|
75
171
|
throw new Error(`Minecraft Bedrock auth failed: ${response.status} - ${text}`);
|
|
76
172
|
}
|
|
77
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,
|
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
|
}
|