bedrock-portal 2.2.0 → 2.3.0

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.
@@ -61,7 +61,7 @@ exports.start_game = {
61
61
  persona_disabled: false,
62
62
  custom_skins_disabled: false,
63
63
  emote_chat_muted: false,
64
- game_version: '1.26.20',
64
+ game_version: '*',
65
65
  limited_world_width: 16,
66
66
  limited_world_length: 16,
67
67
  is_new_nether: false,
@@ -11,6 +11,7 @@ exports.default = async (portal, event) => {
11
11
  return;
12
12
  portal.emit('rtaEvent', event);
13
13
  const session = await portal.getSession();
14
+ await portal.syncSessionNonces(session);
14
15
  portal.emit('sessionUpdated', session);
15
16
  debug('Received RTA event, session has been updated', session);
16
17
  const sessionMembers = Object.values(session.members).filter(member => member.constants.system.xuid !== portal.host?.profile?.xuid);
package/dist/index.d.ts CHANGED
@@ -131,6 +131,8 @@ export declare class BedrockPortal extends TypedEmitter<PortalEvents> {
131
131
  options: BedrockPortalOptions;
132
132
  session: {
133
133
  name: string;
134
+ pmsgId: string;
135
+ nonces: Record<string, string>;
134
136
  };
135
137
  players: Map<string, Player>;
136
138
  peers: Map<string, Host>;
@@ -173,6 +175,7 @@ export declare class BedrockPortal extends TypedEmitter<PortalEvents> {
173
175
  * @param payload The payload to update the session with.
174
176
  */
175
177
  updateSession(payload: SessionRequest): Promise<void>;
178
+ syncSessionNonces(session: RESTSessionResponse): Promise<void>;
176
179
  /**
177
180
  * Enables a module for the BedrockPortal instance.
178
181
  * @see [Modules](https://github.com/LucienHH/bedrock-portal#modules) for a list of available modules or to create your own.
package/dist/index.js CHANGED
@@ -4,6 +4,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.Modules = exports.Module = exports.Joinability = exports.BedrockPortal = void 0;
7
+ const crypto_1 = require("crypto");
7
8
  const debug_1 = __importDefault(require("debug"));
8
9
  const uuid_1 = require("uuid");
9
10
  const tiny_typed_emitter_1 = require("tiny-typed-emitter");
@@ -68,7 +69,7 @@ class BedrockPortal extends tiny_typed_emitter_1.TypedEmitter {
68
69
  };
69
70
  this.validateOptions(this.options);
70
71
  this.host = new Host_1.default(this, this.options.host instanceof prismarine_auth_1.Authflow ? this.options.host : new prismarine_auth_1.Authflow(this.options.host.username, this.options.host.cache, this.options.host.options, this.options.host.onMsaCode));
71
- this.session = { name: '' };
72
+ this.session = { name: '', pmsgId: '', nonces: {} };
72
73
  this.players = new Map();
73
74
  this.peers = new Map();
74
75
  this.modules = new Map();
@@ -84,9 +85,15 @@ class BedrockPortal extends tiny_typed_emitter_1.TypedEmitter {
84
85
  */
85
86
  async start() {
86
87
  this.players = new Map();
88
+ this.session.pmsgId = '';
89
+ this.session.nonces = {};
87
90
  await this.host.connect();
88
91
  this.session.name = (0, uuid_1.v4)();
89
92
  await this.server.listen(this.host.authflow, this.options.webRTCNetworkId);
93
+ const pmsgId = this.server.signaling?.pmsgId;
94
+ if (!pmsgId)
95
+ throw new Error('Failed to determine signaling pmsgId');
96
+ this.session.pmsgId = pmsgId;
90
97
  this.server.on('connect', (client) => this.onServerConnection(client));
91
98
  const session = await this.createAndPublishSession();
92
99
  await this.connectPeers();
@@ -166,6 +173,29 @@ class BedrockPortal extends tiny_typed_emitter_1.TypedEmitter {
166
173
  async updateSession(payload) {
167
174
  await this.host.rest.updateSession(this.session.name, payload);
168
175
  }
176
+ async syncSessionNonces(session) {
177
+ const hostProfile = this.host.profile;
178
+ if (!hostProfile) {
179
+ return;
180
+ }
181
+ const activeXuids = [...new Set(Object.values(session.members)
182
+ .map(member => member.constants.system.xuid)
183
+ .filter(xuid => xuid !== hostProfile.xuid))].sort();
184
+ const nextNonces = Object.fromEntries(activeXuids.map(xuid => [xuid, this.session.nonces[xuid] ?? (0, crypto_1.randomBytes)(8).toString('hex')]));
185
+ const hasChanges = Object.keys(this.session.nonces).length !== activeXuids.length
186
+ || activeXuids.some(xuid => this.session.nonces[xuid] !== nextNonces[xuid]);
187
+ if (!hasChanges) {
188
+ return;
189
+ }
190
+ this.session.nonces = nextNonces;
191
+ await this.updateSession({
192
+ properties: {
193
+ custom: {
194
+ nonces: nextNonces,
195
+ },
196
+ },
197
+ });
198
+ }
169
199
  /**
170
200
  * Enables a module for the BedrockPortal instance.
171
201
  * @see [Modules](https://github.com/LucienHH/bedrock-portal#modules) for a list of available modules or to create your own.
@@ -210,14 +240,8 @@ class BedrockPortal extends tiny_typed_emitter_1.TypedEmitter {
210
240
  has_editor_packs: false,
211
241
  });
212
242
  client.once('resource_pack_client_response', () => {
213
- client.write('voxel_shapes', {
214
- shapes: [],
215
- name_map: [],
216
- custom_shape_count: 0,
217
- });
218
243
  client.write('start_game', start_game_1.start_game);
219
- client.write('play_status', { status: 'player_spawn' });
220
- client.once('set_local_player_as_initialized', () => {
244
+ client.once('set_player_game_type', () => {
221
245
  client.write('transfer', { server_address: this.options.ip, port: this.options.port });
222
246
  });
223
247
  });
@@ -278,6 +302,8 @@ class BedrockPortal extends tiny_typed_emitter_1.TypedEmitter {
278
302
  createSessionBody() {
279
303
  if (!this.host.profile || !this.host.connectionId)
280
304
  throw new Error('No session owner');
305
+ if (!this.session.pmsgId)
306
+ throw new Error('No session connection payload id');
281
307
  const joinability = constants_1.JoinabilityConfig[this.options.joinability];
282
308
  return {
283
309
  properties: {
@@ -306,13 +332,14 @@ class BedrockPortal extends tiny_typed_emitter_1.TypedEmitter {
306
332
  WebRTCNetworkId: this.options.webRTCNetworkId,
307
333
  isHardcore: this.options.world.isHardcore,
308
334
  isEditorWorld: this.options.world.isEditor,
335
+ nonces: this.session.nonces,
309
336
  SupportedConnections: [
310
337
  {
311
- ConnectionType: 3,
338
+ ConnectionType: 7,
312
339
  HostIpAddress: '',
313
340
  HostPort: 0,
314
- WebRTCNetworkId: this.options.webRTCNetworkId,
315
341
  NetherNetId: this.options.webRTCNetworkId,
342
+ PmsgId: this.session.pmsgId,
316
343
  },
317
344
  ],
318
345
  },
package/dist/rest.d.ts CHANGED
@@ -1,35 +1,18 @@
1
- import { AxiosRequestConfig } from 'axios';
2
1
  import { Authflow } from 'prismarine-auth';
3
2
  import { RESTXblmessageInboxResponse } from './types/xblmessaging';
4
3
  import { SessionRequest, RESTSessionResponse, SessionHandlePayload } from './types/sessiondirectory';
5
4
  import { RESTSocialPostBulkFriendRequestResponse } from './types/social';
6
- type RequestHeaders = {
7
- [x: string]: string | boolean | number | undefined;
8
- };
9
- type MethodRequestConfig = {
10
- relyingParty?: string;
11
- contractVersion?: string;
12
- params?: AxiosRequestConfig['params'];
13
- data?: AxiosRequestConfig['data'];
14
- headers?: RequestHeaders;
15
- };
16
- type RequestConfig = MethodRequestConfig & {
17
- url: string;
5
+ type RestOptions = {
6
+ headers?: HeadersInit;
18
7
  };
19
8
  export default class Rest {
20
9
  auth: Authflow;
21
- options: {
22
- headers?: RequestHeaders;
23
- };
24
- constructor(authflow: Authflow, options?: {});
25
- get(url: string, config?: MethodRequestConfig): Promise<any>;
26
- post(url: string, config?: MethodRequestConfig): Promise<any>;
27
- put(url: string, config?: MethodRequestConfig): Promise<any>;
28
- delete(url: string, config?: MethodRequestConfig): Promise<any>;
29
- _request(method: 'GET' | 'POST' | 'PUT' | 'DELETE', config: RequestConfig): Promise<any>;
30
- sendHandle(payload: SessionHandlePayload): Promise<any>;
31
- setActivity(sessionName: string): Promise<any>;
32
- sendInvite(sessionName: string, xuid: string): Promise<any>;
10
+ options: RestOptions;
11
+ constructor(authflow: Authflow, options?: RestOptions);
12
+ private fetchJson;
13
+ sendHandle(payload: SessionHandlePayload): Promise<unknown>;
14
+ setActivity(sessionName: string): Promise<unknown>;
15
+ sendInvite(sessionName: string, xuid: string): Promise<unknown>;
33
16
  getSession(sessionName: string): Promise<RESTSessionResponse>;
34
17
  updateSession(sessionName: string, payload: SessionRequest): Promise<RESTSessionResponse>;
35
18
  updateMemberCount(sessionName: string, count: number, maxCount?: number): Promise<void>;
package/dist/rest.js CHANGED
@@ -1,9 +1,5 @@
1
1
  "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
2
  Object.defineProperty(exports, "__esModule", { value: true });
6
- const axios_1 = __importDefault(require("axios"));
7
3
  const json_bigint_1 = require("json-bigint");
8
4
  const constants_1 = require("./common/constants");
9
5
  const util_1 = require("./common/util");
@@ -14,47 +10,39 @@ class Rest {
14
10
  this.auth = authflow;
15
11
  this.options = options;
16
12
  }
17
- async get(url, config = {}) {
18
- return await this._request('GET', { url, ...config });
19
- }
20
- async post(url, config = {}) {
21
- return await this._request('POST', { url, ...config });
22
- }
23
- async put(url, config = {}) {
24
- return await this._request('PUT', { url, ...config });
25
- }
26
- async delete(url, config = {}) {
27
- return await this._request('DELETE', { url, ...config });
28
- }
29
- async _request(method, config) {
13
+ async fetchJson(url, init = {}) {
14
+ const { contractVersion, headers: requestHeaders, ...requestInit } = init;
30
15
  const auth = await this.auth.getXboxToken('http://xboxlive.com');
31
- const payload = {
32
- method,
33
- url: config.url,
34
- headers: {
35
- 'authorization': `XBL3.0 x=${auth.userHash};${auth.XSTSToken}`,
36
- 'content-type': 'application/json',
37
- 'accept-language': 'en-US',
38
- ...config.headers,
39
- },
40
- data: undefined,
41
- params: undefined,
42
- };
43
- if (config.contractVersion)
44
- payload.headers['x-xbl-contract-version'] = config.contractVersion;
45
- if (config.params)
46
- payload.params = config.params;
47
- if (config.data)
48
- payload.data = config.data;
49
- return (0, axios_1.default)({
50
- ...payload,
51
- transformResponse: [data => data ? (0, json_bigint_1.parse)(data) : undefined],
52
- transformRequest: [data => data ? (0, json_bigint_1.stringify)(data) : undefined],
53
- }).then(e => e.data);
16
+ const headers = new Headers({
17
+ 'authorization': `XBL3.0 x=${auth.userHash};${auth.XSTSToken}`,
18
+ 'accept-language': 'en-US',
19
+ });
20
+ for (const [key, value] of new Headers(this.options.headers).entries()) {
21
+ headers.set(key, value);
22
+ }
23
+ for (const [key, value] of new Headers(requestHeaders).entries()) {
24
+ headers.set(key, value);
25
+ }
26
+ if (requestInit.body !== undefined && !headers.has('content-type')) {
27
+ headers.set('content-type', 'application/json');
28
+ }
29
+ if (contractVersion)
30
+ headers.set('x-xbl-contract-version', contractVersion);
31
+ const response = await fetch(url, {
32
+ ...requestInit,
33
+ headers,
34
+ });
35
+ const body = await response.text();
36
+ if (!response.ok) {
37
+ const detail = body ? `: ${body}` : '';
38
+ throw new Error(`Request failed with status ${response.status} ${response.statusText}${detail}`);
39
+ }
40
+ return (body ? (0, json_bigint_1.parse)(body) : undefined);
54
41
  }
55
42
  async sendHandle(payload) {
56
- return this.post('https://sessiondirectory.xboxlive.com/handles', {
57
- data: payload,
43
+ return this.fetchJson('https://sessiondirectory.xboxlive.com/handles', {
44
+ method: 'POST',
45
+ body: (0, json_bigint_1.stringify)(payload),
58
46
  contractVersion: '107',
59
47
  });
60
48
  }
@@ -75,14 +63,15 @@ class Rest {
75
63
  });
76
64
  }
77
65
  async getSession(sessionName) {
78
- const response = await this.get(`https://sessiondirectory.xboxlive.com/serviceconfigs/${constants_1.SessionConfig.MinecraftSCID}/sessionTemplates/${constants_1.SessionConfig.MinecraftTemplateName}/sessions/${sessionName}`, {
66
+ const response = await this.fetchJson(`https://sessiondirectory.xboxlive.com/serviceconfigs/${constants_1.SessionConfig.MinecraftSCID}/sessionTemplates/${constants_1.SessionConfig.MinecraftTemplateName}/sessions/${sessionName}`, {
79
67
  contractVersion: '107',
80
68
  });
81
69
  return response;
82
70
  }
83
71
  async updateSession(sessionName, payload) {
84
- const response = await this.put(`https://sessiondirectory.xboxlive.com/serviceconfigs/${constants_1.SessionConfig.MinecraftSCID}/sessionTemplates/${constants_1.SessionConfig.MinecraftTemplateName}/sessions/${sessionName}`, {
85
- data: payload,
72
+ const response = await this.fetchJson(`https://sessiondirectory.xboxlive.com/serviceconfigs/${constants_1.SessionConfig.MinecraftSCID}/sessionTemplates/${constants_1.SessionConfig.MinecraftTemplateName}/sessions/${sessionName}`, {
73
+ method: 'PUT',
74
+ body: (0, json_bigint_1.stringify)(payload),
86
75
  contractVersion: '107',
87
76
  });
88
77
  return response;
@@ -117,48 +106,69 @@ class Rest {
117
106
  let xuid = input;
118
107
  if (!(0, util_1.isXuid)(input)) {
119
108
  const target = input === 'me' ? 'me' : `gt(${encodeURIComponent(input)})`;
120
- const response = await this.get(`https://profile.xboxlive.com/users/${target}/settings`, { contractVersion: '2' });
109
+ const response = await this.fetchJson(`https://profile.xboxlive.com/users/${target}/settings`, { contractVersion: '2' });
121
110
  xuid = response.profileUsers[0].id;
122
111
  }
123
- const response = await this.get(`https://peoplehub.xboxlive.com/users/me/people/xuids(${xuid})/decoration/detail,preferredcolor`, { contractVersion: '5' });
112
+ const response = await this.fetchJson(`https://peoplehub.xboxlive.com/users/me/people/xuids(${xuid})/decoration/detail,preferredcolor`, { contractVersion: '5' });
124
113
  return response.people.shift();
125
114
  }
126
115
  async getProfiles(xuids) {
127
- const response = await this.post('https://peoplehub.xboxlive.com/users/me/people/batch/decoration/detail,preferredcolor', { data: { xuids }, contractVersion: '5' });
116
+ const response = await this.fetchJson('https://peoplehub.xboxlive.com/users/me/people/batch/decoration/detail,preferredcolor', {
117
+ method: 'POST',
118
+ body: (0, json_bigint_1.stringify)({ xuids }),
119
+ contractVersion: '5',
120
+ });
128
121
  return response.people;
129
122
  }
130
123
  async getXboxFriends() {
131
- const response = await this.get('https://peoplehub.xboxlive.com/users/me/people/social/decoration/detail,preferredColor,follower', { contractVersion: '5' });
124
+ const response = await this.fetchJson('https://peoplehub.xboxlive.com/users/me/people/social/decoration/detail,preferredColor,follower', { contractVersion: '5' });
132
125
  return response.people;
133
126
  }
134
127
  async getXboxFollowers() {
135
- const response = await this.get('https://peoplehub.xboxlive.com/users/me/people/followers/decoration/detail,preferredColor,follower', { contractVersion: '5' });
128
+ const response = await this.fetchJson('https://peoplehub.xboxlive.com/users/me/people/followers/decoration/detail,preferredColor,follower', { contractVersion: '5' });
136
129
  return response.people;
137
130
  }
138
131
  async getFriendRequestsReceived() {
139
- const response = await this.get('https://peoplehub.xboxlive.com/users/me/people/friendrequests(received)/decoration/detail,preferredColor,follower', { contractVersion: '7' });
132
+ const response = await this.fetchJson('https://peoplehub.xboxlive.com/users/me/people/friendrequests(received)/decoration/detail,preferredColor,follower', { contractVersion: '7' });
140
133
  return response.people;
141
134
  }
142
135
  async acceptFriendRequests(xuids) {
143
- const response = await this.post('https://social.xboxlive.com/bulk/users/me/people/friends/v2?method=add', { data: { xuids }, contractVersion: '3' });
136
+ const response = await this.fetchJson('https://social.xboxlive.com/bulk/users/me/people/friends/v2?method=add', {
137
+ method: 'POST',
138
+ body: (0, json_bigint_1.stringify)({ xuids }),
139
+ contractVersion: '3',
140
+ });
144
141
  return response;
145
142
  }
146
143
  async declineFriendRequests(xuids) {
147
- const response = await this.post('https://social.xboxlive.com/bulk/users/me/people/friends/v2?method=remove', { data: { xuids }, contractVersion: '3' });
144
+ const response = await this.fetchJson('https://social.xboxlive.com/bulk/users/me/people/friends/v2?method=remove', {
145
+ method: 'POST',
146
+ body: (0, json_bigint_1.stringify)({ xuids }),
147
+ contractVersion: '3',
148
+ });
148
149
  return response;
149
150
  }
150
151
  async addXboxFriend(xuid) {
151
- await this.put(`https://social.xboxlive.com/users/me/people/xuid(${xuid})`, { contractVersion: '2' });
152
+ await this.fetchJson(`https://social.xboxlive.com/users/me/people/xuid(${xuid})`, {
153
+ method: 'PUT',
154
+ contractVersion: '2',
155
+ });
152
156
  }
153
157
  async removeXboxFriend(xuid) {
154
- await this.delete(`https://social.xboxlive.com/users/me/people/xuid(${xuid})`, { contractVersion: '2' });
158
+ await this.fetchJson(`https://social.xboxlive.com/users/me/people/xuid(${xuid})`, {
159
+ method: 'DELETE',
160
+ contractVersion: '2',
161
+ });
155
162
  }
156
163
  async getInboxMessages(inbox) {
157
- const response = await this.get(`https://xblmessaging.xboxlive.com/network/xbox/users/me/inbox/${inbox}`, { contractVersion: '1' });
164
+ const response = await this.fetchJson(`https://xblmessaging.xboxlive.com/network/xbox/users/me/inbox/${inbox}`, { contractVersion: '1' });
158
165
  return response;
159
166
  }
160
167
  async setPresence(xuid) {
161
- await this.post(`https://userpresence.xboxlive.com/users/xuid(${xuid})/devices/current/titles/current`, { data: { state: 'active' } });
168
+ await this.fetchJson(`https://userpresence.xboxlive.com/users/xuid(${xuid})/devices/current/titles/current`, {
169
+ method: 'POST',
170
+ body: (0, json_bigint_1.stringify)({ state: 'active' }),
171
+ });
162
172
  }
163
173
  }
164
174
  exports.default = Rest;
@@ -97,7 +97,7 @@ export interface SessionConnection {
97
97
  HostIpAddress: string;
98
98
  HostPort: number;
99
99
  NetherNetId: bigint;
100
- WebRTCNetworkId: bigint;
100
+ PmsgId: string;
101
101
  }
102
102
  export interface SessionRequest {
103
103
  properties?: {
@@ -127,6 +127,7 @@ export interface SessionRequest {
127
127
  TitleId?: number;
128
128
  isHardcore?: boolean;
129
129
  isEditorWorld?: boolean;
130
+ nonces?: Record<string, string>;
130
131
  SupportedConnections?: SessionConnection[];
131
132
  };
132
133
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bedrock-portal",
3
- "version": "2.2.0",
3
+ "version": "2.3.0",
4
4
  "description": "Creates a joinable session in Minecraft Bedrock Edition that can route players to their a specified host",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",
@@ -21,11 +21,10 @@
21
21
  },
22
22
  "homepage": "https://github.com/LucienHH/bedrock-portal#readme",
23
23
  "dependencies": {
24
- "axios": "^1.0.0",
25
24
  "bedrock-portal-nethernet": "^2.0.0",
26
25
  "debug": "^4.3.4",
27
26
  "json-bigint": "^1.0.0",
28
- "prismarine-auth": "github:LucienHH/prismarine-auth#playfab",
27
+ "prismarine-auth": "^3.1.1",
29
28
  "tiny-typed-emitter": "^2.1.0",
30
29
  "uuid": "^9.0.0",
31
30
  "xbox-message": "^0.2.0",
@@ -40,6 +39,6 @@
40
39
  "@typescript-eslint/parser": "^6.17.0",
41
40
  "bedrock-portal": "file:.",
42
41
  "eslint": "^8.33.0",
43
- "typescript": "^5.3.3"
42
+ "typescript": "^6.0.3"
44
43
  }
45
44
  }