Skip to content

Commit 935d328

Browse files
committed
Split connection creation, add param to ChatScreen
1 parent 5322c8a commit 935d328

File tree

5 files changed

+199
-148
lines changed

5 files changed

+199
-148
lines changed

src/App.tsx

+14-3
Original file line numberDiff line numberDiff line change
@@ -38,14 +38,19 @@ const Stacks = createNativeStackNavigator()
3838
const Tabs = createMaterialTopTabNavigator() // createBottomTabNavigator()
3939

4040
type HomeNavigationProp = NativeStackNavigationProp<
41-
{ Home: undefined; Chat: undefined },
41+
{ Home: undefined; Chat: { serverName: string; version: number } },
4242
'Home'
4343
>
4444

4545
const HomeScreen = ({ navigation }: { navigation: HomeNavigationProp }) => {
4646
const { connection } = React.useContext(ConnectionContext)
4747
React.useEffect(() => {
48-
if (connection) navigation.push('Chat')
48+
if (connection) {
49+
navigation.push('Chat', {
50+
serverName: connection.serverName,
51+
version: connection.connection.options.protocolVersion
52+
})
53+
}
4954
}, [connection, navigation])
5055

5156
return (
@@ -145,7 +150,13 @@ const App = () => {
145150
}}
146151
>
147152
<Stacks.Screen name='Home' component={HomeScreen} />
148-
<Stacks.Screen name='Chat' component={ChatScreen} />
153+
<Stacks.Screen
154+
name='Chat'
155+
component={ChatScreen}
156+
getId={({ params }) => {
157+
return (params as { serverName: string }).serverName
158+
}}
159+
/>
149160
</Stacks.Navigator>
150161
</NavigationContainer>
151162
</AccountsContext.Provider>

src/minecraft/utils.ts

+11
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,17 @@ export const toggleEndian = (buffer: Buffer, bytes: number = buffer.length) => {
3939
return output
4040
}
4141

42+
export const parseIp = (ipAddress: string): [string, number] => {
43+
const splitAddr = ipAddress.split(':')
44+
const portStr = splitAddr.pop() || ''
45+
let port = +portStr
46+
if (isNaN(+portStr)) {
47+
splitAddr.push(portStr)
48+
port = 25565
49+
}
50+
return [splitAddr.join(':'), port]
51+
}
52+
4253
export const resolveHostname = async (
4354
hostname: string,
4455
port: number,

src/screens/ServerScreen.tsx

+34-131
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import {
1111
} from 'react-native'
1212
import { Picker } from '@react-native-picker/picker'
1313
import Ionicons from 'react-native-vector-icons/Ionicons'
14+
import { NativeStackScreenProps } from '@react-navigation/native-stack'
1415
import allSettled from 'promise.allsettled'
1516

1617
import globalStyle from '../globalStyle'
@@ -26,50 +27,33 @@ import TextField from '../components/TextField'
2627
import ElevatedView from '../components/ElevatedView'
2728
import useDarkMode from '../context/useDarkMode'
2829
import ServersContext from '../context/serversContext'
30+
import useSessionStore from '../context/sessionStore'
2931
import SettingsContext from '../context/settingsContext'
3032
import AccountsContext from '../context/accountsContext'
3133
import ConnectionContext from '../context/connectionContext'
32-
import { resolveHostname, protocolMap } from '../minecraft/utils'
33-
import initiateConnection from '../minecraft/connection'
34-
import {
35-
refreshMSAuthToken,
36-
getXboxLiveTokenAndUserHash,
37-
getXstsTokenAndUserHash,
38-
authenticateWithXsts
39-
} from '../minecraft/api/microsoft'
40-
import { Certificate, getPlayerCertificates } from '../minecraft/api/mojang'
41-
import { refresh } from '../minecraft/api/yggdrasil'
34+
import { parseIp, protocolMap } from '../minecraft/utils'
4235
import {
4336
ChatToJsx,
4437
lightColorMap,
45-
mojangColorMap,
46-
parseValidJson
38+
mojangColorMap
4739
} from '../minecraft/chatToJsx'
48-
import config from '../../config.json'
40+
import { createConnection } from './chat/sessionBuilder'
4941

50-
const parseIp = (ipAddress: string): [string, number] => {
51-
const splitAddr = ipAddress.split(':')
52-
const portStr = splitAddr.pop() || ''
53-
let port = +portStr
54-
if (isNaN(+portStr)) {
55-
splitAddr.push(portStr)
56-
port = 25565
57-
}
58-
return [splitAddr.join(':'), port]
42+
interface RootStackParamList {
43+
[index: string]: any
44+
Home: undefined
45+
Chat: { serverName: string; version: number }
5946
}
47+
type Props = NativeStackScreenProps<RootStackParamList, 'Chat'>
6048

61-
interface Session {
62-
certificate?: Certificate
63-
accessToken: string
64-
}
65-
66-
const ServerScreen = () => {
49+
const ServerScreen = (props: Props) => {
6750
const darkMode = useDarkMode()
6851
const { settings } = useContext(SettingsContext)
6952
const { servers, setServers } = useContext(ServersContext)
7053
const { accounts, setAccounts } = useContext(AccountsContext)
7154
const { connection, setConnection, setDisconnectReason } =
7255
useContext(ConnectionContext)
56+
const { sessions, setSession } = useSessionStore()
7357
const initiatingConnection = useRef(false)
7458

7559
const [ipAddr, setIpAddr] = useState('')
@@ -86,7 +70,6 @@ const ServerScreen = () => {
8670
// false - no route, null - unknown err, undefined - pinging
8771
[ip: string]: LegacyPing | Ping | false | null | undefined
8872
}>({})
89-
const [sessions, setSessions] = useState<{ [uuid: string]: Session }>({})
9073

9174
useEffect(() => {
9275
if (Object.keys(pingResponses).length > 0) {
@@ -168,116 +151,41 @@ const ServerScreen = () => {
168151
if (initiatingConnection.current) return
169152
if (!connection) {
170153
initiatingConnection.current = true
171-
const [hostname, portNumber] = parseIp(servers[server].address)
172-
const [host, port] = await resolveHostname(hostname, portNumber)
173-
const activeAccount = Object.keys(accounts).find(e => accounts[e].active)
174-
if (!activeAccount) {
175-
initiatingConnection.current = false
176-
return setDisconnectReason({
177-
server,
178-
reason:
179-
'No active account selected! Open the Accounts tab and add an account.'
180-
})
181-
}
182-
let protocolVersion = protocolMap[servers[server].version]
183-
if (protocolVersion === -1) {
154+
let version = protocolMap[servers[server].version]
155+
if (version === -1) {
184156
const ping = pingResponses[servers[server].address]
185157
// Try the latest.
186158
// TODO: Make 1.19 the default.
187-
if (!ping) protocolVersion = protocolMap['1.18.2']
159+
if (!ping) version = protocolMap['1.18.2']
188160
else if (typeof ping.version === 'object') {
189-
protocolVersion = ping.version.protocol
190-
} else protocolVersion = (ping as LegacyPing).protocol
161+
version = ping.version.protocol
162+
} else version = (ping as LegacyPing).protocol
191163
}
192-
if (protocolVersion < 754) {
164+
if (version < 754) {
193165
initiatingConnection.current = false
194166
return setDisconnectReason({
195167
server,
196168
reason: 'EnderChat only supports 1.16.4 and newer (for now).'
197169
})
198170
}
199-
const uuid = accounts[activeAccount].type ? activeAccount : undefined
200-
201-
// Create an updated "session" containing access tokens and certificates.
202171
// LOW-TODO: Creating a session would be better with a loading screen, since Microsoft Login is slow.
203172
// Maybe setConnection(null) to bring up ChatScreen while still being in a logged out state?
204-
let session = sessions[activeAccount]
205-
const is119 = protocolVersion >= protocolMap[1.19]
206-
if (uuid && (!session || (!session.certificate && is119))) {
207-
// LOW-TODO: Certificates and access tokens should be updated regularly.
208-
try {
209-
// Create a session with the latest access token.
210-
const account = accounts[activeAccount]
211-
if (!session && accounts[activeAccount].type === 'microsoft') {
212-
const [msAccessToken, msRefreshToken] = await refreshMSAuthToken(
213-
accounts[activeAccount].microsoftRefreshToken || '',
214-
config.clientId,
215-
config.scope
216-
)
217-
const [xlt, xuh] = await getXboxLiveTokenAndUserHash(msAccessToken)
218-
const [xstsToken] = await getXstsTokenAndUserHash(xlt)
219-
const accessToken = await authenticateWithXsts(xstsToken, xuh)
220-
session = { accessToken }
221-
setAccounts({
222-
[activeAccount]: {
223-
...account,
224-
accessToken,
225-
microsoftAccessToken: msAccessToken,
226-
microsoftRefreshToken: msRefreshToken
227-
}
228-
})
229-
} else if (!session && accounts[activeAccount].type === 'mojang') {
230-
const { accessToken, clientToken } = await refresh(
231-
accounts[activeAccount].accessToken || '',
232-
accounts[activeAccount].clientToken || '',
233-
false
234-
)
235-
session = { accessToken }
236-
setAccounts({
237-
[activeAccount]: { ...account, accessToken, clientToken }
238-
})
239-
}
240-
// If connecting to 1.19, get player certificates.
241-
if (!session.certificate && is119) {
242-
const token = session.accessToken
243-
session.certificate = await getPlayerCertificates(token)
244-
}
245-
setSessions({ [activeAccount]: session })
246-
} catch (e) {
247-
const reason =
248-
'Failed to create session! You may need to re-login with your Microsoft Account in the Accounts tab.'
249-
setDisconnectReason({ server, reason })
250-
initiatingConnection.current = false
251-
return
252-
}
253-
}
254-
255-
// Connect to server after setting up the session.
173+
// Or delegate this task to ChatScreen? Would make initiatingConnectionRef unnecessary.
256174
try {
257-
const newConn = await initiateConnection({
258-
host,
259-
port,
260-
username: accounts[activeAccount].username,
261-
protocolVersion,
262-
selectedProfile: uuid,
263-
accessToken: session?.accessToken,
264-
certificate: settings.enableChatSigning
265-
? session?.certificate
266-
: undefined
267-
})
268-
const onCloseOrError = () => {
269-
setConnection(undefined)
270-
if (newConn.disconnectReason) {
271-
const reason = parseValidJson(newConn.disconnectReason)
272-
setDisconnectReason({ server, reason })
273-
}
274-
}
275-
newConn.on('close', onCloseOrError)
276-
newConn.on('error', onCloseOrError)
277-
setConnection({ serverName: server, connection: newConn })
278-
} catch (e) {
279-
setDisconnectReason({ server, reason: 'Failed to connect to server!' })
280-
}
175+
await createConnection(
176+
server,
177+
version,
178+
servers,
179+
settings,
180+
accounts,
181+
sessions,
182+
setSession,
183+
setAccounts,
184+
setConnection,
185+
setDisconnectReason,
186+
() => {}
187+
)
188+
} catch (e) {}
281189
initiatingConnection.current = false
282190
}
283191
}
@@ -445,12 +353,7 @@ const ServerScreen = () => {
445353
const styles = StyleSheet.create({
446354
serverView: { marginBottom: 12 },
447355
serverPressable: { padding: 8, flexDirection: 'row' },
448-
serverImage: {
449-
resizeMode: 'contain',
450-
padding: 4,
451-
height: 72,
452-
width: 72
453-
},
356+
serverImage: { resizeMode: 'contain', padding: 4, height: 72, width: 72 },
454357
serverLoading: {
455358
justifyContent: 'center',
456359
alignItems: 'center',

src/screens/chat/ChatScreen.tsx

+18-14
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,13 @@ import {
99
Clipboard
1010
} from 'react-native'
1111
import Ionicons from 'react-native-vector-icons/Ionicons'
12-
import { NativeStackNavigationProp } from '@react-navigation/native-stack'
12+
import { NativeStackScreenProps } from '@react-navigation/native-stack'
1313

14-
import packetHandler from './packetHandler'
14+
import {
15+
packetHandler,
16+
enderChatPrefix,
17+
sendMessageError
18+
} from './packetHandler'
1519
import globalStyle from '../../globalStyle'
1620
import useDarkMode from '../../context/useDarkMode'
1721
import SettingsContext from '../../context/settingsContext'
@@ -30,13 +34,13 @@ import TextField from '../../components/TextField'
3034
import Text from '../../components/Text'
3135
import SettingScreen from '../settings/SettingScreen'
3236

33-
const enderChatPrefix = '\u00A74[\u00A7cEnderChat\u00A74] \u00A7c'
34-
const sendMessageErr = 'Failed to send message to server!'
37+
interface RootStackParamList {
38+
[index: string]: any
39+
Home: undefined
40+
Chat: { serverName: string; version: number }
41+
}
42+
type Props = NativeStackScreenProps<RootStackParamList, 'Chat'>
3543

36-
type ChatNavigationProp = NativeStackNavigationProp<
37-
{ Home: undefined; Chat: undefined },
38-
'Chat'
39-
>
4044
interface Message {
4145
key: number
4246
text: MinecraftChat
@@ -81,7 +85,7 @@ const handleError =
8185
}
8286

8387
// TODO: Ability to copy text.
84-
const ChatScreen = ({ navigation }: { navigation: ChatNavigationProp }) => {
88+
const ChatScreen = ({ navigation, route }: Props) => {
8589
const darkMode = useDarkMode()
8690
const { settings } = useContext(SettingsContext)
8791
const { connection, setConnection } = useContext(ConnectionContext)
@@ -156,24 +160,24 @@ const ChatScreen = ({ navigation }: { navigation: ChatNavigationProp }) => {
156160
if (connection.connection.options.protocolVersion < protocolMap['1.19']) {
157161
connection.connection
158162
.writePacket(0x03, concatPacketData([msg]))
159-
.catch(handleError(addMessage, sendMessageErr))
163+
.catch(handleError(addMessage, sendMessageError))
160164
} else {
161165
const timestamp = Buffer.alloc(8)
162166
connection.connection
163167
.writePacket(
164168
0x04,
165169
concatPacketData([msg, timestamp, writeVarInt(0), false])
166170
)
167-
.catch(handleError(addMessage, sendMessageErr))
171+
.catch(handleError(addMessage, sendMessageError))
168172
// TODO-1.19: Support sending Chat Command/Chat Message/Chat Preview.
169173
}
170174
}
171175

172176
if (!connection) return <></> // This should never be hit hopefully.
173177
const title =
174-
connection.serverName.length > 12
175-
? connection.serverName.substring(0, 9) + '...'
176-
: connection.serverName
178+
route.params.serverName.length > 12
179+
? route.params.serverName.substring(0, 9) + '...'
180+
: route.params.serverName
177181
const backButton = (
178182
<View style={styles.backButton}>
179183
<Ionicons.Button

0 commit comments

Comments
 (0)