bedrock-portal 1.1.6 → 2.0.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.
package/README.md CHANGED
@@ -12,6 +12,10 @@ npm install bedrock-portal
12
12
  ### Warning
13
13
  This package is not meant to be used with your main account. It is meant to be used with alt accounts. If you use this package with your main account, you may be banned from the XSAPI. This package is not affiliated with Mojang or Microsoft.
14
14
 
15
+ ## Migration
16
+
17
+ Upgrading from v1.x to v2.x introduces breaking API changes. See [docs/migration-v2.md](docs/migration-v2.md).
18
+
15
19
  ## Usage
16
20
 
17
21
  ### BedrockPortal(options)
@@ -20,11 +24,12 @@ This package is not meant to be used with your main account. It is meant to be u
20
24
  - **ip** - The IP address of the server to redirect players to (required)
21
25
  - **port** - The port of the server to redirect players to | default: 19132
22
26
  - **joinability** - The joinability of the session | default: Joinability.FriendsOfFriends
23
- - **authflow** - Either an Authflow instance from [prismarine-auth](https://github.com/PrismarineJS/prismarine-auth#authflow) or an object with the following properties:
27
+ - **host** - Either an Authflow instance from [prismarine-auth](https://github.com/PrismarineJS/prismarine-auth#authflow) or an object with the following properties:
24
28
  - **username** - The username of the account to use for the session | default: 'BedrockPortal'
25
29
  - **cache** - The cache directory to use for the session | default: './'
26
30
  - **options** - Options for the Authflow instance from [prismarine-auth](https://github.com/PrismarineJS/prismarine-auth#authflow)
27
- - **updatePresence** - Whether to update the presence of the authenticated account making it appear as if they are playing Minecraft | default: true
31
+ - **peers** - Additional accounts that should be connected to the session. Each entry can be either an Authflow instance or an object with the same shape as **host**. | default: []
32
+ - **updatePresence** - Whether to update the presence of the authenticated account making it appear as if they are playing Minecraft. Will also update the presence of any connected peers. | default: true
28
33
  - **world** - The world config to use for the session. Changes the session card which is displayed in the Minecraft client. (optional)
29
34
  - **hostName** - string
30
35
  - **name** - string
@@ -57,10 +62,37 @@ const main = async () => {
57
62
  main();
58
63
  ```
59
64
 
65
+ ### Use multiple joinable accounts
66
+ ```js
67
+ const { BedrockPortal, Modules } = require('bedrock-portal')
68
+ const { Titles } = require('prismarine-auth')
69
+
70
+ const main = async () => {
71
+ const portal = new BedrockPortal({
72
+ ip: 'your.server.ip',
73
+ port: 19132,
74
+ peers: [
75
+ { username: 'account1' },
76
+ { username: 'account2' },
77
+ ],
78
+ })
79
+
80
+ portal.use(Modules.AutoFriendAdd, {
81
+ inviteOnAdd: true,
82
+ })
83
+
84
+ await portal.start()
85
+ }
86
+
87
+ main()
88
+ ```
89
+
60
90
  ## Modules
61
91
 
62
92
  Modules are used to extend the functionality of the BedrockPortal class. **Modules should be initialised before calling the `start` method.**
63
93
 
94
+ Modules that operate on Xbox accounts use the primary **host** account and any configured **peers** automatically.
95
+
64
96
  ### ServerFormList
65
97
 
66
98
  Instead of redirecting players to a server, the portal will display a list of servers to join from a form. `#.use(Modules.serverFormList, options);`
@@ -99,41 +131,6 @@ const main = async () => {
99
131
  main()
100
132
  ```
101
133
 
102
- ### MultipleAccounts
103
-
104
- Allows the portal to use multiple accounts to redirect players to the server. `#.use(Modules.MultipleAccounts, options);`
105
-
106
- Options:
107
- - **accounts**: Authflow[] - An array of authflows from [prismarine-auth](https://github.com/PrismarineJS/prismarine-auth), these accounts are automatically added to the host session and allows players to add them as a friend to join the game. (required)
108
-
109
- ```js
110
- const { Authflow, Titles } = require('prismarine-auth')
111
- const { BedrockPortal, Modules } = require('bedrock-portal')
112
-
113
- const main = async () => {
114
- const portal = new BedrockPortal( {
115
- ip: 'your.server.ip',
116
- port: 19132,
117
- })
118
-
119
- portal.use(Modules.AutoFriendAdd, {
120
- inviteOnAdd: true,
121
- })
122
-
123
- portal.use(Modules.MultipleAccounts, {
124
- accounts: [
125
- new Authflow('account1', './', { authTitle: Titles.MinecraftIOS, deviceType: 'iOS', flow: 'sisu' }),
126
- new Authflow('account2', './', { authTitle: Titles.MinecraftIOS, deviceType: 'iOS', flow: 'sisu' }),
127
- ],
128
- })
129
-
130
- await portal.start()
131
- }
132
-
133
- main()
134
- ```
135
-
136
-
137
134
  ### RedirectFromRealm
138
135
 
139
136
  > Requires [bedrock-protocol](https://github.com/PrismarineJS/bedrock-protocol) to be installed. `npm install bedrock-protocol`
@@ -23,33 +23,67 @@ class Host {
23
23
  this.rest = new rest_1.default(this.authflow);
24
24
  }
25
25
  async connect() {
26
- const auth = await this.authflow.getXboxToken();
27
- this.profile = await this.rest.getProfile(auth.userXUID);
28
- this.rta = new xbox_rta_1.XboxRTA(this.authflow);
29
- await this.rta.connect();
30
- const subResponse = await this.rta.subscribe('https://sessiondirectory.xboxlive.com/connections/');
31
- this.connectionId = subResponse.data.ConnectionId;
32
- this.rta.on('subscribe', (event) => this.onSubscribe(event));
33
- if (this.portal.options.updatePresence) {
34
- const updatePresence = () => {
35
- if (this.profile) {
36
- this.rest.setPresence(this.profile.xuid)
37
- .catch(e => { debug('Failed to set presence', e); });
38
- }
39
- };
40
- this.presenceInterval = setInterval(updatePresence, 300000);
41
- updatePresence();
26
+ let rta = null;
27
+ let presenceInterval = null;
28
+ try {
29
+ const auth = await this.authflow.getXboxToken();
30
+ const profile = await this.rest.getProfile(auth.userXUID);
31
+ rta = new xbox_rta_1.XboxRTA(this.authflow);
32
+ await rta.connect();
33
+ const subResponse = await rta.subscribe('https://sessiondirectory.xboxlive.com/connections/');
34
+ const subData = subResponse.data;
35
+ this.profile = profile;
36
+ this.rta = rta;
37
+ this.connectionId = subData.ConnectionId ?? null;
38
+ this.subscriptionId = (0, uuid_1.v4)();
39
+ this.rta.on('subscribe', (event) => this.onSubscribe(event));
40
+ if (this.portal.options.updatePresence) {
41
+ const updatePresence = () => {
42
+ if (this.profile) {
43
+ this.rest.setPresence(this.profile.xuid)
44
+ .catch(e => { debug('Failed to set presence', e); });
45
+ }
46
+ };
47
+ presenceInterval = setInterval(updatePresence, 300000);
48
+ this.presenceInterval = presenceInterval;
49
+ updatePresence();
50
+ }
51
+ }
52
+ catch (e) {
53
+ if (presenceInterval) {
54
+ clearInterval(presenceInterval);
55
+ }
56
+ if (rta) {
57
+ await rta.destroy()
58
+ .catch(error => { debug('Failed to destroy host RTA during connect cleanup', error); });
59
+ }
60
+ this.rta = null;
61
+ this.profile = null;
62
+ this.connectionId = null;
63
+ this.presenceInterval = null;
64
+ this.subscriptionId = (0, uuid_1.v4)();
65
+ throw e;
42
66
  }
43
67
  }
44
68
  async disconnect() {
45
- if (this.rta) {
46
- await this.rta.destroy();
69
+ const rta = this.rta;
70
+ const presenceInterval = this.presenceInterval;
71
+ const sessionName = this.portal.session.name;
72
+ this.rta = null;
73
+ this.profile = null;
74
+ this.connectionId = null;
75
+ this.presenceInterval = null;
76
+ this.subscriptionId = (0, uuid_1.v4)();
77
+ if (presenceInterval) {
78
+ clearInterval(presenceInterval);
79
+ }
80
+ if (rta) {
81
+ await rta.destroy();
47
82
  }
48
- if (this.presenceInterval) {
49
- clearInterval(this.presenceInterval);
83
+ if (sessionName) {
84
+ await this.rest.leaveSession(sessionName);
50
85
  }
51
- await this.rest.leaveSession(this.portal.session.name)
52
- .catch(() => { debug('Failed to leave session as host'); });
86
+ debug('Successfully disconnected host', this.authflow.username);
53
87
  }
54
88
  async onSubscribe(event) {
55
89
  const connectionId = event.data?.ConnectionId;
@@ -57,6 +91,9 @@ class Host {
57
91
  debug('Received RTA subscribe event', event);
58
92
  try {
59
93
  this.connectionId = connectionId;
94
+ if (!this.portal.session.name) {
95
+ return;
96
+ }
60
97
  await this.rest.updateConnection(this.portal.session.name, connectionId);
61
98
  await this.rest.setActivity(this.portal.session.name);
62
99
  }
package/dist/index.d.ts CHANGED
@@ -3,7 +3,7 @@ import type { Message } from 'xbox-message';
3
3
  import { EventResponse } from 'xbox-rta';
4
4
  import { TypedEmitter } from 'tiny-typed-emitter';
5
5
  import { Server } from 'bedrock-portal-nethernet';
6
- import { Authflow, CacheFactory, MicrosoftAuthFlowOptions } from 'prismarine-auth';
6
+ import { Authflow, CacheFactory, MicrosoftAuthFlowOptions, ServerDeviceCodeResponse } from 'prismarine-auth';
7
7
  import Host from './classes/Host';
8
8
  import Player from './classes/Player';
9
9
  import Module from './classes/Module';
@@ -11,14 +11,14 @@ import { Joinability } from './common/constants';
11
11
  import AutoFriendAdd from './modules/autoFriendAdd';
12
12
  import InviteOnMessage from './modules/inviteOnMessage';
13
13
  import RedirectFromRealm from './modules/redirectFromRealm';
14
- import MultipleAccounts from './modules/multipleAccounts';
15
14
  import AutoFriendAccept from './modules/autoFriendAccept';
16
15
  import UpdateMemberCount from './modules/updateMemberCount';
17
16
  import ServerFromList from './modules/serverFormList';
18
17
  type AuthflowOptions = {
19
- username: string;
20
- cache: string | CacheFactory;
21
- options: MicrosoftAuthFlowOptions;
18
+ username?: string;
19
+ cache?: string | CacheFactory;
20
+ options?: MicrosoftAuthFlowOptions;
21
+ onMsaCode?: (res: ServerDeviceCodeResponse) => void;
22
22
  };
23
23
  type BedrockPortalOptions = {
24
24
  /**
@@ -60,12 +60,17 @@ type BedrockPortalOptions = {
60
60
  */
61
61
  updatePresence: boolean;
62
62
  /**
63
- * The authentication flow to use for the session.
63
+ * The authflow options or authflow instance to use for the host account.
64
64
  */
65
- authflow: AuthflowOptions | Authflow;
65
+ host: AuthflowOptions | Authflow;
66
66
  /**
67
- * The world config to use for the session. Changes the session card which is displayed in the Minecraft client
68
- */
67
+ * Additional accounts which are connected to the session and can be used to join the portal..
68
+ * @default []
69
+ */
70
+ peers: (AuthflowOptions | Authflow)[];
71
+ /**
72
+ * The world config to use for the session. Changes the session card which is displayed in the Minecraft client
73
+ */
69
74
  world: {
70
75
  /**
71
76
  * The host name of the world.
@@ -122,13 +127,13 @@ interface PortalEvents {
122
127
  }) => void;
123
128
  }
124
129
  export declare class BedrockPortal extends TypedEmitter<PortalEvents> {
125
- authflow: Authflow;
126
130
  host: Host;
127
131
  options: BedrockPortalOptions;
128
132
  session: {
129
133
  name: string;
130
134
  };
131
135
  players: Map<string, Player>;
136
+ peers: Map<string, Host>;
132
137
  modules: Map<string, Module>;
133
138
  server: Server;
134
139
  constructor(options?: Partial<BedrockPortalOptions>);
@@ -145,6 +150,7 @@ export declare class BedrockPortal extends TypedEmitter<PortalEvents> {
145
150
  * Returns the current members in the session.
146
151
  */
147
152
  getSessionMembers(): Map<string, Player>;
153
+ getHostAndPeers(): Host[];
148
154
  /**
149
155
  * Invites a player to the BedrockPortal instance.
150
156
  * @param identifyer The player's gamertag or XUID.
@@ -178,6 +184,8 @@ export declare class BedrockPortal extends TypedEmitter<PortalEvents> {
178
184
  use<T extends ExtendedModule>(mod: ExtendedModuleConstructor<T>, options?: Partial<T['options']>): void;
179
185
  onServerConnection: (client: any) => void;
180
186
  private createAndPublishSession;
187
+ private connectPeers;
188
+ private disconnectPeers;
181
189
  private createSessionBody;
182
190
  }
183
191
  export { Joinability } from './common/constants';
@@ -186,7 +194,6 @@ declare const Modules: {
186
194
  AutoFriendAdd: typeof AutoFriendAdd;
187
195
  InviteOnMessage: typeof InviteOnMessage;
188
196
  RedirectFromRealm: typeof RedirectFromRealm;
189
- MultipleAccounts: typeof MultipleAccounts;
190
197
  AutoFriendAccept: typeof AutoFriendAccept;
191
198
  UpdateMemberCount: typeof UpdateMemberCount;
192
199
  ServerFromList: typeof ServerFromList;
package/dist/index.js CHANGED
@@ -17,19 +17,31 @@ const Event_1 = __importDefault(require("./handlers/Event"));
17
17
  const autoFriendAdd_1 = __importDefault(require("./modules/autoFriendAdd"));
18
18
  const inviteOnMessage_1 = __importDefault(require("./modules/inviteOnMessage"));
19
19
  const redirectFromRealm_1 = __importDefault(require("./modules/redirectFromRealm"));
20
- const multipleAccounts_1 = __importDefault(require("./modules/multipleAccounts"));
21
20
  const autoFriendAccept_1 = __importDefault(require("./modules/autoFriendAccept"));
22
21
  const updateMemberCount_1 = __importDefault(require("./modules/updateMemberCount"));
23
22
  const serverFormList_1 = __importDefault(require("./modules/serverFormList"));
24
23
  const start_game_1 = require("./common/start_game");
25
24
  const util_1 = require("./common/util");
26
25
  const debug = (0, debug_1.default)('bedrock-portal');
26
+ const getDefaultAuthflowOptions = (options = {}) => ({
27
+ username: 'BedrockPortal',
28
+ cache: './',
29
+ options: {
30
+ authTitle: prismarine_auth_1.Titles.MinecraftIOS,
31
+ flow: 'sisu',
32
+ deviceType: 'iOS',
33
+ },
34
+ onMsaCode: (res) => {
35
+ console.log(`${options.username} - ${res.message}`);
36
+ },
37
+ ...options,
38
+ });
27
39
  class BedrockPortal extends tiny_typed_emitter_1.TypedEmitter {
28
- authflow;
29
40
  host;
30
41
  options;
31
42
  session;
32
43
  players;
44
+ peers;
33
45
  modules = new Map();
34
46
  server = new bedrock_portal_nethernet_1.Server();
35
47
  constructor(options = {}) {
@@ -41,16 +53,8 @@ class BedrockPortal extends tiny_typed_emitter_1.TypedEmitter {
41
53
  webRTCNetworkId: (0, util_1.getRandomUint64)(),
42
54
  updatePresence: true,
43
55
  ...options,
44
- authflow: options.authflow instanceof prismarine_auth_1.Authflow ? options.authflow : {
45
- username: 'BedrockPortal',
46
- cache: './',
47
- options: {
48
- authTitle: prismarine_auth_1.Titles.MinecraftIOS,
49
- flow: 'sisu',
50
- deviceType: 'iOS',
51
- },
52
- ...options.authflow,
53
- },
56
+ host: options.host instanceof prismarine_auth_1.Authflow ? options.host : getDefaultAuthflowOptions(options.host),
57
+ peers: options.peers ? options.peers.map(peer => peer instanceof prismarine_auth_1.Authflow ? peer : getDefaultAuthflowOptions(peer)) : [],
54
58
  world: {
55
59
  hostName: 'Bedrock Portal v1.0.0',
56
60
  name: 'By LucienHH',
@@ -63,10 +67,10 @@ class BedrockPortal extends tiny_typed_emitter_1.TypedEmitter {
63
67
  },
64
68
  };
65
69
  this.validateOptions(this.options);
66
- this.authflow = this.options.authflow instanceof prismarine_auth_1.Authflow ? this.options.authflow : new prismarine_auth_1.Authflow(this.options.authflow.username, this.options.authflow.cache, this.options.authflow.options);
67
- this.host = new Host_1.default(this, this.authflow);
70
+ 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));
68
71
  this.session = { name: '' };
69
72
  this.players = new Map();
73
+ this.peers = new Map();
70
74
  this.modules = new Map();
71
75
  }
72
76
  validateOptions(options) {
@@ -82,9 +86,10 @@ class BedrockPortal extends tiny_typed_emitter_1.TypedEmitter {
82
86
  this.players = new Map();
83
87
  await this.host.connect();
84
88
  this.session.name = (0, uuid_1.v4)();
85
- await this.server.listen(this.authflow, this.options.webRTCNetworkId);
89
+ await this.server.listen(this.host.authflow, this.options.webRTCNetworkId);
86
90
  this.server.on('connect', (client) => this.onServerConnection(client));
87
91
  const session = await this.createAndPublishSession();
92
+ await this.connectPeers();
88
93
  this.host.rta.on('event', (event) => {
89
94
  (0, Event_1.default)(this, event)
90
95
  .catch(e => debug('Failed to handle event', e));
@@ -102,13 +107,14 @@ class BedrockPortal extends tiny_typed_emitter_1.TypedEmitter {
102
107
  * Ends the BedrockPortal instance.
103
108
  */
104
109
  async end(resume = false) {
105
- await this.host.disconnect();
106
110
  if (this.modules) {
107
111
  for (const mod of this.modules.values()) {
108
112
  debug(`Stopping module: ${mod.name}`);
109
113
  await mod.stop();
110
114
  }
111
115
  }
116
+ await this.disconnectPeers();
117
+ await this.host.disconnect();
112
118
  debug(`Abandoned session, name: ${this.session.name} - Resume: ${resume}`);
113
119
  if (resume) {
114
120
  return this.start();
@@ -120,6 +126,9 @@ class BedrockPortal extends tiny_typed_emitter_1.TypedEmitter {
120
126
  getSessionMembers() {
121
127
  return this.players;
122
128
  }
129
+ getHostAndPeers() {
130
+ return [this.host, ...this.peers.values()];
131
+ }
123
132
  /**
124
133
  * Invites a player to the BedrockPortal instance.
125
134
  * @param identifyer The player's gamertag or XUID.
@@ -217,6 +226,49 @@ class BedrockPortal extends tiny_typed_emitter_1.TypedEmitter {
217
226
  debug(`Published session, name: ${this.session.name}, ID: ${this.options.webRTCNetworkId}`);
218
227
  return session;
219
228
  }
229
+ async connectPeers() {
230
+ for (const account of this.options.peers) {
231
+ let peer = null;
232
+ try {
233
+ peer = new Host_1.default(this, account instanceof prismarine_auth_1.Authflow ? account : new prismarine_auth_1.Authflow(account.username, account.cache, account.options, account.onMsaCode));
234
+ await peer.connect();
235
+ if (!peer.profile || !peer.connectionId) {
236
+ throw Error(`Failed to connect to ${account.username}`);
237
+ }
238
+ debug(`Connected ${peer.profile.gamertag}`);
239
+ const hostAddPeer = await this.host.rest.addXboxFriend(peer.profile.xuid)
240
+ .then(() => null)
241
+ .catch(error => error);
242
+ const peerAddHost = await peer.rest.addXboxFriend(this.host.profile.xuid)
243
+ .then(() => null)
244
+ .catch(error => error);
245
+ if (hostAddPeer || peerAddHost) {
246
+ if (hostAddPeer)
247
+ debug(`Failed to add ${peer.profile.gamertag} as a friend - ${hostAddPeer.message}`);
248
+ if (peerAddHost)
249
+ debug(`Failed to add ${this.host.profile.gamertag} as a friend - ${peerAddHost.message}`);
250
+ throw Error(`Failed to create friendship between ${this.host.profile.gamertag} and ${peer.profile.gamertag}`);
251
+ }
252
+ await peer.rest.addConnection(this.session.name, peer.profile.xuid, peer.connectionId, peer.subscriptionId);
253
+ await peer.rest.setActivity(this.session.name);
254
+ this.peers.set(peer.profile.xuid, peer);
255
+ }
256
+ catch (error) {
257
+ debug(`Failed to initialise ${account.username} - ${error.message}`);
258
+ if (peer) {
259
+ await peer.disconnect()
260
+ .catch((err) => debug(`Failed to disconnect ${peer?.authflow.username ?? account.username}`, err));
261
+ }
262
+ }
263
+ }
264
+ }
265
+ async disconnectPeers() {
266
+ for (const peer of this.peers.values()) {
267
+ await peer.disconnect()
268
+ .catch((err) => debug(`Failed to disconnect ${peer.authflow.username}`, err));
269
+ }
270
+ this.peers.clear();
271
+ }
220
272
  createSessionBody() {
221
273
  if (!this.host.profile || !this.host.connectionId)
222
274
  throw new Error('No session owner');
@@ -290,7 +342,6 @@ const Modules = {
290
342
  AutoFriendAdd: autoFriendAdd_1.default,
291
343
  InviteOnMessage: inviteOnMessage_1.default,
292
344
  RedirectFromRealm: redirectFromRealm_1.default,
293
- MultipleAccounts: multipleAccounts_1.default,
294
345
  AutoFriendAccept: autoFriendAccept_1.default,
295
346
  UpdateMemberCount: updateMemberCount_1.default,
296
347
  ServerFromList: serverFormList_1.default,
@@ -1,6 +1,8 @@
1
1
  import type { BedrockPortal } from '../index';
2
2
  import type { FriendRequestPerson } from '../types/peoplehub';
3
+ import type { EventResponse } from 'xbox-rta';
3
4
  import Module from '../classes/Module';
5
+ import Host from '../classes/Host';
4
6
  export default class AutoFriendAccept extends Module {
5
7
  options: {
6
8
  /**
@@ -16,6 +18,10 @@ export default class AutoFriendAccept extends Module {
16
18
  */
17
19
  conditionToMeet: (request: FriendRequestPerson) => boolean;
18
20
  };
21
+ listeners: Map<string, {
22
+ host: Host;
23
+ listener: (event: EventResponse) => Promise<void>;
24
+ }>;
19
25
  constructor(portal: BedrockPortal);
20
26
  run(): Promise<void>;
21
27
  stop(): Promise<void>;
@@ -5,19 +5,23 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  const Module_1 = __importDefault(require("../classes/Module"));
7
7
  const Player_1 = __importDefault(require("../classes/Player"));
8
- const multipleAccounts_1 = __importDefault(require("./multipleAccounts"));
9
8
  class AutoFriendAccept extends Module_1.default {
10
9
  options;
10
+ listeners;
11
11
  constructor(portal) {
12
12
  super(portal, 'autoFriendAccept', 'Automatically accept friend requests');
13
13
  this.options = {
14
14
  inviteOnAdd: false,
15
15
  conditionToMeet: () => true,
16
16
  };
17
+ this.listeners = new Map();
17
18
  }
18
19
  async run() {
19
20
  const addXboxFriend = async (host) => {
20
- host.rta?.on('event', async (event) => {
21
+ if (!host.rta || !host.profile || this.listeners.has(host.authflow.username)) {
22
+ return;
23
+ }
24
+ const listener = async (event) => {
21
25
  if (event.data.NotificationType !== 'IncomingFriendRequestCountChanged')
22
26
  return;
23
27
  this.debug('Received Friend RTA event', event);
@@ -35,21 +39,22 @@ class AutoFriendAccept extends Module_1.default {
35
39
  await this.portal.invitePlayer(person.xuid).catch(error => this.debug(`Error: Failed to invite ${person.gamertag}`, error));
36
40
  }
37
41
  }
38
- });
39
- await host.rta?.subscribe(`https://social.xboxlive.com/users/xuid(${host.profile?.xuid})/friends`);
42
+ };
43
+ this.listeners.set(host.authflow.username, { host, listener });
44
+ host.rta.on('event', listener);
45
+ await host.rta.subscribe(`https://social.xboxlive.com/users/xuid(${host.profile.xuid})/friends`);
40
46
  };
41
- const multipleAccounts = this.portal.modules.get('multipleAccounts');
42
- if (multipleAccounts && multipleAccounts instanceof multipleAccounts_1.default) {
43
- for (const account of multipleAccounts.peers.values()) {
44
- addXboxFriend(account)
45
- .catch(error => this.debug(`Error: ${error.message}`, error));
46
- }
47
+ for (const account of this.portal.getHostAndPeers()) {
48
+ addXboxFriend(account)
49
+ .catch(error => this.debug(`Error: ${error.message}`, error));
47
50
  }
48
- addXboxFriend(this.portal.host)
49
- .catch(error => this.debug(`Error: ${error.message}`, error));
50
51
  }
51
52
  async stop() {
52
53
  super.stop();
54
+ for (const { host, listener } of this.listeners.values()) {
55
+ host.rta?.removeListener('event', listener);
56
+ }
57
+ this.listeners.clear();
53
58
  }
54
59
  }
55
60
  exports.default = AutoFriendAccept;
@@ -6,7 +6,6 @@ Object.defineProperty(exports, "__esModule", { value: true });
6
6
  const promises_1 = require("timers/promises");
7
7
  const Module_1 = __importDefault(require("../classes/Module"));
8
8
  const Player_1 = __importDefault(require("../classes/Player"));
9
- const multipleAccounts_1 = __importDefault(require("./multipleAccounts"));
10
9
  class AutoFriendAdd extends Module_1.default {
11
10
  interval = null;
12
11
  options;
@@ -57,15 +56,10 @@ class AutoFriendAdd extends Module_1.default {
57
56
  }
58
57
  };
59
58
  this.interval = setInterval(() => {
60
- const multipleAccounts = this.portal.modules.get('multipleAccounts');
61
- if (multipleAccounts && multipleAccounts instanceof multipleAccounts_1.default) {
62
- for (const account of multipleAccounts.peers.values()) {
63
- addXboxFriend(account)
64
- .catch(error => this.debug(`Error: ${error.message}`, error));
65
- }
59
+ for (const account of this.portal.getHostAndPeers()) {
60
+ addXboxFriend(account)
61
+ .catch(error => this.debug(`Error: ${error.message}`, error));
66
62
  }
67
- addXboxFriend(this.portal.host)
68
- .catch(error => this.debug(`Error: ${error.message}`, error));
69
63
  }, this.options.checkInterval);
70
64
  }
71
65
  async stop() {
@@ -5,7 +5,6 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  const xbox_message_1 = require("xbox-message");
7
7
  const Module_1 = __importDefault(require("../classes/Module"));
8
- const multipleAccounts_1 = __importDefault(require("./multipleAccounts"));
9
8
  class IniteOnMessage extends Module_1.default {
10
9
  options;
11
10
  clients;
@@ -32,20 +31,17 @@ class IniteOnMessage extends Module_1.default {
32
31
  });
33
32
  await client.connect();
34
33
  };
35
- const multipleAccounts = this.portal.modules.get('multipleAccounts');
36
- if (multipleAccounts && multipleAccounts instanceof multipleAccounts_1.default) {
37
- for (const account of multipleAccounts.peers.values()) {
38
- xboxMessageHandler(account)
39
- .catch(error => this.debug(`Error: ${error.message}`, error));
40
- }
34
+ for (const account of this.portal.getHostAndPeers()) {
35
+ xboxMessageHandler(account)
36
+ .catch(error => this.debug(`Error: ${error.message}`, error));
41
37
  }
42
- xboxMessageHandler(this.portal.host);
43
38
  }
44
39
  async stop() {
45
40
  super.stop();
46
41
  for (const client of this.clients.values()) {
47
42
  await client.destroy();
48
43
  }
44
+ this.clients.clear();
49
45
  }
50
46
  }
51
47
  exports.default = IniteOnMessage;
@@ -0,0 +1,126 @@
1
+ # V2 Migration Guide
2
+
3
+ This guide covers the breaking changes required to move from v1.x to v2.x.
4
+
5
+ ## Summary
6
+
7
+ The main API changes in v2 are:
8
+
9
+ - `authflow` has been renamed to `host` if you previously set a custom authflow in the constructor
10
+ - additional accounts are now configured with `peers` on `BedrockPortal`
11
+ - `Modules.MultipleAccounts` has been removed
12
+ - account-oriented modules automatically operate on the primary host and any configured peers
13
+
14
+ ## Constructor Changes
15
+
16
+ If you did not pass `authflow` in v1, you do not need to add `host` in v2 unless you want to override the default account configuration.
17
+
18
+ ### v1
19
+
20
+ ```js
21
+ const portal = new BedrockPortal({
22
+ ip: 'your.server.ip',
23
+ port: 19132,
24
+ authflow: new Authflow('main-account', './', {
25
+ authTitle: Titles.MinecraftIOS,
26
+ deviceType: 'iOS',
27
+ flow: 'sisu',
28
+ }),
29
+ })
30
+ ```
31
+
32
+ ### v2
33
+
34
+ ```js
35
+ const portal = new BedrockPortal({
36
+ ip: 'your.server.ip',
37
+ port: 19132,
38
+ host: new Authflow('main-account', './', {
39
+ authTitle: Titles.MinecraftIOS,
40
+ deviceType: 'iOS',
41
+ flow: 'sisu',
42
+ }),
43
+ })
44
+ ```
45
+
46
+ You can also pass a plain config object instead of an `Authflow` instance:
47
+
48
+ ```js
49
+ const portal = new BedrockPortal({
50
+ ip: 'your.server.ip',
51
+ port: 19132,
52
+ host: {
53
+ username: 'main-account',
54
+ cache: './',
55
+ options: {
56
+ authTitle: Titles.MinecraftIOS,
57
+ deviceType: 'iOS',
58
+ flow: 'sisu',
59
+ },
60
+ },
61
+ })
62
+ ```
63
+
64
+ ## Multiple Account Changes
65
+
66
+ ### v1
67
+
68
+ ```js
69
+ const portal = new BedrockPortal({
70
+ ip: 'your.server.ip',
71
+ port: 19132,
72
+ })
73
+
74
+ portal.use(Modules.MultipleAccounts, {
75
+ accounts: [
76
+ new Authflow('account1', './', {
77
+ authTitle: Titles.MinecraftIOS,
78
+ deviceType: 'iOS',
79
+ flow: 'sisu',
80
+ }),
81
+ new Authflow('account2', './', {
82
+ authTitle: Titles.MinecraftIOS,
83
+ deviceType: 'iOS',
84
+ flow: 'sisu',
85
+ }),
86
+ ],
87
+ })
88
+ ```
89
+
90
+ ### v2
91
+
92
+ ```js
93
+ const portal = new BedrockPortal({
94
+ ip: 'your.server.ip',
95
+ port: 19132,
96
+ peers: [
97
+ new Authflow('account1', './', {
98
+ authTitle: Titles.MinecraftIOS,
99
+ deviceType: 'iOS',
100
+ flow: 'sisu',
101
+ }),
102
+ // or with a config object instead of an Authflow instance:
103
+ {
104
+ username: 'account2',
105
+ },
106
+ ],
107
+ })
108
+ ```
109
+
110
+ ## Module Behavior Changes
111
+
112
+ Modules that interact with Xbox accounts now automatically use all connected accounts.
113
+
114
+ This affects modules such as:
115
+
116
+ - `AutoFriendAdd`
117
+ - `AutoFriendAccept`
118
+ - `InviteOnMessage`
119
+
120
+ You do not need to add a separate module to make those features work across peer accounts.
121
+
122
+ ## Migration Checklist
123
+
124
+ - Rename `authflow` to `host` if your v1 code explicitly set `authflow`
125
+ - Move any `Modules.MultipleAccounts` configuration into `peers`
126
+ - Remove `portal.use(Modules.MultipleAccounts, ...)`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bedrock-portal",
3
- "version": "1.1.6",
3
+ "version": "2.0.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",
@@ -1,23 +0,0 @@
1
- import type { Authflow } from 'prismarine-auth';
2
- import type { BedrockPortal } from '../index';
3
- import Module from '../classes/Module';
4
- import Host from '../classes/Host';
5
- export default class MultipleAccounts extends Module {
6
- options: {
7
- /**
8
- * An array of authflows from prismarine-auth, these accounts are automatically added to the host session and allows players to add them as a friend to join the game
9
- * @example
10
- * portal.use(Modules.MultipleAccounts, {
11
- accounts: [
12
- new Authflow('account1', './'),
13
- new Authflow('account2', './')
14
- ],
15
- })
16
- */
17
- accounts: Authflow[];
18
- };
19
- peers: Map<string, Host>;
20
- constructor(portal: BedrockPortal);
21
- run(): Promise<void>;
22
- stop(): Promise<void>;
23
- }
@@ -1,58 +0,0 @@
1
- "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
- Object.defineProperty(exports, "__esModule", { value: true });
6
- const Module_1 = __importDefault(require("../classes/Module"));
7
- const Host_1 = __importDefault(require("../classes/Host"));
8
- class MultipleAccounts extends Module_1.default {
9
- options;
10
- peers;
11
- constructor(portal) {
12
- super(portal, 'multipleAccounts', 'Make the portal session joinable from multiple accounts');
13
- this.options = {
14
- accounts: [],
15
- };
16
- this.peers = new Map();
17
- }
18
- async run() {
19
- if (!this.portal.host || !this.portal.host.profile) {
20
- this.debug('No host to connect to');
21
- return;
22
- }
23
- await Promise.all(this.options.accounts.map(account => account.getXboxToken()));
24
- for (const account of this.options.accounts) {
25
- const peer = new Host_1.default(this.portal, account);
26
- await peer.connect();
27
- if (!peer.profile || !peer.connectionId) {
28
- this.debug(`Failed to connect to ${account.username}`);
29
- continue;
30
- }
31
- this.peers.set(peer.profile.xuid, peer);
32
- this.debug(`Connected ${peer.profile.gamertag}`);
33
- const hostAddPeer = await this.portal.host.rest.addXboxFriend(peer.profile.xuid)
34
- .catch(error => ({ error }));
35
- const peerAddHost = await peer.rest.addXboxFriend(this.portal.host.profile.xuid)
36
- .catch(error => ({ error }));
37
- if ((hostAddPeer && 'error' in hostAddPeer) || (peerAddHost && 'error' in peerAddHost)) {
38
- if (hostAddPeer)
39
- this.debug(`Failed to add ${peer.profile.gamertag} as a friend - ${hostAddPeer.error.message}`);
40
- if (peerAddHost)
41
- this.debug(`Failed to add ${this.portal.host.profile.gamertag} as a friend - ${peerAddHost.error.message}`);
42
- console.error(`Error creating a friendship between ${this.portal.host.profile.gamertag} and ${peer.profile.gamertag} - BedrockPortal will continue to run, but will not be joinable from ${peer.profile.gamertag}`);
43
- continue;
44
- }
45
- await peer.rest.addConnection(this.portal.session.name, peer.profile.xuid, peer.connectionId, peer.subscriptionId);
46
- await peer.rest.setActivity(this.portal.session.name);
47
- }
48
- }
49
- async stop() {
50
- super.stop();
51
- for (const peer of this.peers.values()) {
52
- await peer.disconnect()
53
- .then(() => this.debug(`Disconnected ${peer.profile?.gamertag}`))
54
- .catch(() => this.debug(`Failed to disconnect ${peer.profile?.gamertag}`));
55
- }
56
- }
57
- }
58
- exports.default = MultipleAccounts;