|
1 | 1 | // import { createHash } from 'react-native-crypto'
|
| 2 | +import { InteractionManager } from 'react-native' |
2 | 3 | import net from 'react-native-tcp'
|
3 | 4 | import events from 'events'
|
4 | 5 | import {
|
@@ -100,92 +101,96 @@ const initiateConnection = async (opts: {
|
100 | 101 | // Handle timeout after 20 seconds of no data.
|
101 | 102 | if (conn.disconnectTimer) clearTimeout(conn.disconnectTimer)
|
102 | 103 | conn.disconnectTimer = setTimeout(() => conn.close(), 20000)
|
103 |
| - // Buffer data for read. |
104 |
| - conn.bufferedData = Buffer.concat([conn.bufferedData, newData]) |
105 |
| - // ;(async () => { This would need a mutex. |
106 |
| - while (true) { |
107 |
| - const packet = conn.compressionEnabled |
108 |
| - ? parseCompressedPacket(conn.bufferedData) |
109 |
| - : parsePacket(conn.bufferedData) |
110 |
| - if (packet) { |
111 |
| - if (packet.id === 0x03 && !conn.loggedIn) { |
112 |
| - const [threshold] = readVarInt(packet.data) |
113 |
| - conn.compressionThreshold = threshold |
114 |
| - conn.compressionEnabled = threshold >= 0 |
115 |
| - } else if (packet.id === 0x02 && !conn.loggedIn) { |
116 |
| - conn.loggedIn = true |
117 |
| - } else if (packet.id === 0x21) { |
118 |
| - conn |
119 |
| - .writePacket(0x0f, packet.data) |
120 |
| - .catch(err => conn.emit('error', err)) |
121 |
| - } else if ( |
122 |
| - (packet.id === 0x00 && !conn.loggedIn) || |
123 |
| - (packet.id === 0x1a && conn.loggedIn) |
124 |
| - ) { |
125 |
| - const [chatLength, chatVarIntLength] = readVarInt(packet.data) |
126 |
| - conn.disconnectReason = packet.data |
127 |
| - .slice(chatVarIntLength, chatVarIntLength + chatLength) |
128 |
| - .toString('utf8') |
129 |
| - } /* else if (packet.id === 0x01 && !conn.loggedIn) { |
130 |
| - // What if no accessToken was provided? |
131 |
| - const [serverIdLen, serverIdLenLen] = readVarInt(packet.data) |
132 |
| - const serverId = packet.data.slice( |
133 |
| - serverIdLenLen, |
134 |
| - serverIdLen + serverIdLenLen |
135 |
| - ) |
136 |
| - const data = packet.data.slice(serverIdLen + serverIdLenLen) |
137 |
| - const [pkLen, pkLenLen] = readVarInt(data) |
138 |
| - const publicKey = data.slice(pkLenLen, pkLen + pkLenLen) |
139 |
| - const verifyTokenData = data.slice(pkLen + pkLenLen) |
140 |
| - const [, verifyTokenLengthLength] = readVarInt(verifyTokenData) |
141 |
| - const verifyToken = verifyTokenData.slice(verifyTokenLengthLength) |
142 |
| - // TODO: https://wiki.vg/Protocol_Encryption |
143 |
| - // eslint-disable-next-line @typescript-eslint/no-floating-promises |
144 |
| - ;(async () => { |
145 |
| - // Generate random 16-byte shared secret. |
146 |
| - const sharedSecret = await generateSharedSecret() |
147 |
| - // Generate hash. |
148 |
| - const sha1 = createHash('sha1') |
149 |
| - sha1.update(serverId) // ASCII encoding of the server id string from Encryption Request |
150 |
| - sha1.update(sharedSecret) |
151 |
| - sha1.update(publicKey) // Server's encoded public key from Encryption Request |
152 |
| - const hash = mcHexDigest(sha1.digest()) |
153 |
| - // Send hash to Mojang servers. |
154 |
| - const req = await fetch( |
155 |
| - 'https://sessionserver.mojang.com/session/minecraft/join', |
156 |
| - { |
157 |
| - method: 'POST', |
158 |
| - body: JSON.stringify({}) |
159 |
| - } |
| 104 | + // Run after interactions to improve user experience. |
| 105 | + InteractionManager.runAfterInteractions(() => { |
| 106 | + // Buffer data for read. |
| 107 | + // TODO: Implement decryption. |
| 108 | + conn.bufferedData = Buffer.concat([conn.bufferedData, newData]) |
| 109 | + // ;(async () => { This would need a mutex. |
| 110 | + while (true) { |
| 111 | + const packet = conn.compressionEnabled |
| 112 | + ? parseCompressedPacket(conn.bufferedData) |
| 113 | + : parsePacket(conn.bufferedData) |
| 114 | + if (packet) { |
| 115 | + if (packet.id === 0x03 && !conn.loggedIn) { |
| 116 | + const [threshold] = readVarInt(packet.data) |
| 117 | + conn.compressionThreshold = threshold |
| 118 | + conn.compressionEnabled = threshold >= 0 |
| 119 | + } else if (packet.id === 0x02 && !conn.loggedIn) { |
| 120 | + conn.loggedIn = true |
| 121 | + } else if (packet.id === 0x21) { |
| 122 | + conn |
| 123 | + .writePacket(0x0f, packet.data) |
| 124 | + .catch(err => conn.emit('error', err)) |
| 125 | + } else if ( |
| 126 | + (packet.id === 0x00 && !conn.loggedIn) || |
| 127 | + (packet.id === 0x1a && conn.loggedIn) |
| 128 | + ) { |
| 129 | + const [chatLength, chatVarIntLength] = readVarInt(packet.data) |
| 130 | + conn.disconnectReason = packet.data |
| 131 | + .slice(chatVarIntLength, chatVarIntLength + chatLength) |
| 132 | + .toString('utf8') |
| 133 | + } /* else if (packet.id === 0x01 && !conn.loggedIn) { |
| 134 | + // What if no accessToken was provided? |
| 135 | + const [serverIdLen, serverIdLenLen] = readVarInt(packet.data) |
| 136 | + const serverId = packet.data.slice( |
| 137 | + serverIdLenLen, |
| 138 | + serverIdLen + serverIdLenLen |
160 | 139 | )
|
161 |
| - // POST https://sessionserver.mojang.com/session/minecraft/join |
162 |
| - // Body: {"accessToken": "<accessToken>", |
163 |
| - // "selectedProfile": "<player's uuid without dashes>", |
164 |
| - // "serverId": "<serverHash>"} |
165 |
| - // Encrypt shared secret and verify token with public key. |
166 |
| - // Send encryption response packet. |
167 |
| - // Encrypted Shared Secret Length - VarInt |
168 |
| - // Encrypted Shared Secret - Byte Array |
169 |
| - // Encrypted Verify Token Length - VarInt |
170 |
| - // Encrypted Verify Token - Byte Array |
171 |
| - // It then sends a Login Success, and enables AES/CFB8 encryption. |
172 |
| - // For the Initial Vector (IV) and AES setup, both sides use the shared |
173 |
| - // secret as both the IV and the key. Similarly, the client will also |
174 |
| - // enable encryption upon sending Encryption Response. |
175 |
| - // From this point forward, everything is encrypted. |
176 |
| - // Note: the entire packet is encrypted, including the length |
177 |
| - // fields and the packet's data. |
178 |
| - // The Login Success packet is sent encrypted. |
179 |
| - })() |
180 |
| - } */ |
181 |
| - conn.bufferedData = |
182 |
| - conn.bufferedData.length <= packet.packetLength |
183 |
| - ? Buffer.alloc(0) // Avoid errors shortening. |
184 |
| - : conn.bufferedData.slice(packet.packetLength) |
185 |
| - conn.emit('packet', packet) |
186 |
| - } else break |
187 |
| - } |
188 |
| - conn.emit('data', newData) |
| 140 | + const data = packet.data.slice(serverIdLen + serverIdLenLen) |
| 141 | + const [pkLen, pkLenLen] = readVarInt(data) |
| 142 | + const publicKey = data.slice(pkLenLen, pkLen + pkLenLen) |
| 143 | + const verifyTokenData = data.slice(pkLen + pkLenLen) |
| 144 | + const [, verifyTokenLengthLength] = readVarInt(verifyTokenData) |
| 145 | + const verifyToken = verifyTokenData.slice(verifyTokenLengthLength) |
| 146 | + // TODO: https://wiki.vg/Protocol_Encryption |
| 147 | + // eslint-disable-next-line @typescript-eslint/no-floating-promises |
| 148 | + ;(async () => { |
| 149 | + // Generate random 16-byte shared secret. |
| 150 | + const sharedSecret = await generateSharedSecret() |
| 151 | + // Generate hash. |
| 152 | + const sha1 = createHash('sha1') |
| 153 | + sha1.update(serverId) // ASCII encoding of the server id string from Encryption Request |
| 154 | + sha1.update(sharedSecret) |
| 155 | + sha1.update(publicKey) // Server's encoded public key from Encryption Request |
| 156 | + const hash = mcHexDigest(sha1.digest()) |
| 157 | + // Send hash to Mojang servers. |
| 158 | + const req = await fetch( |
| 159 | + 'https://sessionserver.mojang.com/session/minecraft/join', |
| 160 | + { |
| 161 | + method: 'POST', |
| 162 | + body: JSON.stringify({}) |
| 163 | + } |
| 164 | + ) |
| 165 | + // POST https://sessionserver.mojang.com/session/minecraft/join |
| 166 | + // Body: {"accessToken": "<accessToken>", |
| 167 | + // "selectedProfile": "<player's uuid without dashes>", |
| 168 | + // "serverId": "<serverHash>"} |
| 169 | + // Encrypt shared secret and verify token with public key. |
| 170 | + // Send encryption response packet. |
| 171 | + // Encrypted Shared Secret Length - VarInt |
| 172 | + // Encrypted Shared Secret - Byte Array |
| 173 | + // Encrypted Verify Token Length - VarInt |
| 174 | + // Encrypted Verify Token - Byte Array |
| 175 | + // It then sends a Login Success, and enables AES/CFB8 encryption. |
| 176 | + // For the Initial Vector (IV) and AES setup, both sides use the shared |
| 177 | + // secret as both the IV and the key. Similarly, the client will also |
| 178 | + // enable encryption upon sending Encryption Response. |
| 179 | + // From this point forward, everything is encrypted. |
| 180 | + // Note: the entire packet is encrypted, including the length |
| 181 | + // fields and the packet's data. |
| 182 | + // The Login Success packet is sent encrypted. |
| 183 | + })() |
| 184 | + } */ |
| 185 | + conn.bufferedData = |
| 186 | + conn.bufferedData.length <= packet.packetLength |
| 187 | + ? Buffer.alloc(0) // Avoid errors shortening. |
| 188 | + : conn.bufferedData.slice(packet.packetLength) |
| 189 | + conn.emit('packet', packet) |
| 190 | + } else break |
| 191 | + } |
| 192 | + conn.emit('data', newData) |
| 193 | + }).then(() => {}, console.error) |
189 | 194 | })
|
190 | 195 | socket.on('close', () => {
|
191 | 196 | conn.closed = true
|
|
0 commit comments