From dfff13867dfaead208fe77b729b20ea7164d4743 Mon Sep 17 00:00:00 2001 From: extremeheat Date: Sun, 27 Mar 2022 16:15:20 -0400 Subject: [PATCH] Refactor client connection sequence (#189) * Refactor client connection sequence Allow connection info to come in after Client construction, emit "connect_allowed" similar to nmp * Fix breaking ping behavior change * fix createClient connect callback * correct behavior * remove comments * refactor impl * fix incorrect use of `this` --- src/client.js | 38 +++++++++++++++++++++++--------------- src/client/auth.js | 30 ++++++++++++++++++++---------- src/createClient.js | 33 +++++++++++++++++++++------------ 3 files changed, 64 insertions(+), 37 deletions(-) diff --git a/src/client.js b/src/client.js index 219dcec..85f48df 100644 --- a/src/client.js +++ b/src/client.js @@ -4,7 +4,7 @@ const { serialize, isDebug } = require('./datatypes/util') const debug = require('debug')('minecraft-protocol') const Options = require('./options') const auth = require('./client/auth') - +const initRaknet = require('./rak') const { KeyExchange } = require('./handshake/keyExchange') const Login = require('./handshake/login') const LoginVerify = require('./handshake/loginVerify') @@ -19,20 +19,6 @@ class Client extends Connection { constructor (options) { super() this.options = { ...Options.defaultOptions, ...options } - this.validateOptions() - - const { RakClient } = require('./rak')(this.options.useNativeRaknet) - - this.serializer = createSerializer(this.options.version) - this.deserializer = createDeserializer(this.options.version) - - KeyExchange(this, null, this.options) - Login(this, null, this.options) - LoginVerify(this, null, this.options) - - const host = this.options.host - const port = this.options.port - this.connection = new RakClient({ useWorkers: this.options.useRaknetWorkers, host, port }) this.startGameData = {} this.clientRuntimeId = null @@ -42,9 +28,31 @@ class Client extends Connection { this.outLog = (...args) => debug('C <-', ...args) } this.conLog = this.options.conLog === undefined ? console.log : this.options.conLog + + if (!options.delayedInit) { + this.init() + } + } + + init () { + this.validateOptions() + this.serializer = createSerializer(this.options.version) + this.deserializer = createDeserializer(this.options.version) + + KeyExchange(this, null, this.options) + Login(this, null, this.options) + LoginVerify(this, null, this.options) + + const { RakClient } = initRaknet(this.options.useNativeRaknet) + const host = this.options.host + const port = this.options.port + this.connection = new RakClient({ useWorkers: this.options.useRaknetWorkers, host, port }) + + this.emit('connect_allowed') } connect () { + if (!this.connection) throw new Error('Connect not currently allowed') // must wait for `connect_allowed`, or use `createClient` this.on('session', this._connect) if (this.options.offline) { diff --git a/src/client/auth.js b/src/client/auth.js index 62f2a2f..72a44cc 100644 --- a/src/client/auth.js +++ b/src/client/auth.js @@ -4,6 +4,21 @@ const minecraftFolderPath = require('minecraft-folder-path') const debug = require('debug')('minecraft-protocol') const { uuidFrom } = require('../datatypes/util') +function validateOptions (options) { + if (!options.profilesFolder) { + options.profilesFolder = path.join(minecraftFolderPath, 'nmp-cache') + } + if (options.authTitle === undefined) { + options.authTitle = Titles.MinecraftNintendoSwitch + options.deviceType = 'Nintendo' + } +} + +async function realmAuthenticate (options) { + validateOptions(options) + throw new Error('Not implemented') +} + /** * Authenticates to Minecraft via device code based Microsoft auth, * then connects to the specified server in Client Options @@ -13,16 +28,10 @@ const { uuidFrom } = require('../datatypes/util') * @param {object} options - Client Options */ async function authenticate (client, options) { - if (!options.profilesFolder) { - options.profilesFolder = path.join(minecraftFolderPath, 'nmp-cache') - } - if (options.authTitle === undefined) { - options.authTitle = Titles.MinecraftNintendoSwitch - options.deviceType = 'Nintendo' - } + validateOptions(options) try { - const Authflow = new PrismarineAuth(options.username, options.profilesFolder, options, options.onMsaCode) - const chains = await Authflow.getMinecraftBedrockToken(client.clientX509).catch(e => { + const authflow = options.authflow || new PrismarineAuth(options.username, options.profilesFolder, options, options.onMsaCode) + const chains = await authflow.getMinecraftBedrockToken(client.clientX509).catch(e => { if (options.password) console.warn('Sign in failed, try removing the password field') throw e }) @@ -71,5 +80,6 @@ function postAuthenticate (client, profile, chains) { module.exports = { createOfflineSession, - authenticate + authenticate, + realmAuthenticate } diff --git a/src/createClient.js b/src/createClient.js index 7aff088..eec4412 100644 --- a/src/createClient.js +++ b/src/createClient.js @@ -1,27 +1,36 @@ const { Client } = require('./client') const { RakClient } = require('./rak')(true) -const { Versions, CURRENT_VERSION } = require('./options') const { sleep } = require('./datatypes/util') const assert = require('assert') +const Options = require('./options') const advertisement = require('./server/advertisement') +const auth = require('./client/auth') /** @param {{ version?: number, host: string, port?: number, connectTimeout?: number, skipPing?: boolean }} options */ function createClient (options) { assert(options) - const client = new Client({ port: 19132, ...options }) + const client = new Client({ port: 19132, ...options, delayedInit: true }) - if (options.skipPing) { - connect(client) - } else { // Try to ping - client.ping().then(data => { - const ad = advertisement.fromServerName(data) - client.options.version = options.version ?? (Versions[ad.version] ? ad.version : CURRENT_VERSION) - if (client.conLog) client.conLog(`Connecting to server ${ad.motd} (${ad.name}), version ${ad.version}`, client.options.version !== ad.version ? ` (as ${client.options.version})` : '') - client.emit('connect_allowed') - connect(client) - }, client) + function onServerInfo () { + if (options.skipPing) { + client.init() + } else { + ping(options).then(ad => { + const adVersion = ad.version?.split('.').slice(0, 3).join('.') // Only 3 version units + client.options.version = options.version ?? (Options.Versions[adVersion] ? adVersion : Options.CURRENT_VERSION) + client.conLog?.(`Connecting to server ${ad.motd} (${ad.name}), version ${ad.version}`, client.options.version !== ad.version ? ` (as ${client.options.version})` : '') + client.init() + }) + } } + if (options.realms) { + auth.realmAuthenticate(client.options).then(onServerInfo).catch(e => client.emit('error', e)) + } else { + onServerInfo() + } + + client.on('connect_allowed', () => connect(client)) return client }