@@ -29,13 +29,22 @@ import AccountsContext from '../context/accountsContext'
29
29
import ConnectionContext from '../context/connectionContext'
30
30
import { resolveHostname , protocolMap } from '../minecraft/utils'
31
31
import initiateConnection from '../minecraft/connection'
32
+ import {
33
+ refreshMSAuthToken ,
34
+ getXboxLiveTokenAndUserHash ,
35
+ getXstsTokenAndUserHash ,
36
+ authenticateWithXsts
37
+ } from '../minecraft/api/microsoft'
38
+ import { Certificate , getPlayerCertificates } from '../minecraft/api/mojang'
39
+ import { refresh } from '../minecraft/api/yggdrasil'
32
40
import {
33
41
ChatToJsx ,
34
42
lightColorMap ,
35
43
mojangColorMap ,
36
44
parseValidJson
37
45
} from '../minecraft/chatToJsx'
38
46
import useDarkMode from '../context/useDarkMode'
47
+ import config from '../../config.json'
39
48
40
49
const parseIp = ( ipAddress : string ) : [ string , number ] => {
41
50
const splitAddr = ipAddress . split ( ':' )
@@ -48,10 +57,15 @@ const parseIp = (ipAddress: string): [string, number] => {
48
57
return [ splitAddr . join ( ':' ) , port ]
49
58
}
50
59
60
+ interface Session {
61
+ certificate ?: Certificate
62
+ accessToken : string
63
+ }
64
+
51
65
const ServerScreen = ( ) => {
52
66
const darkMode = useDarkMode ( )
53
67
const { servers, setServers } = useContext ( ServersContext )
54
- const { accounts } = useContext ( AccountsContext )
68
+ const { accounts, setAccounts } = useContext ( AccountsContext )
55
69
const { connection, setConnection, setDisconnectReason } =
56
70
useContext ( ConnectionContext )
57
71
const initiatingConnection = useRef ( false )
@@ -70,6 +84,7 @@ const ServerScreen = () => {
70
84
// false - no route, null - unknown err, undefined - pinging
71
85
[ ip : string ] : LegacyPing | Ping | false | null | undefined
72
86
} > ( { } )
87
+ const [ sessions , setSessions ] = useState < { [ uuid : string ] : Session } > ( { } )
73
88
74
89
useEffect ( ( ) => {
75
90
if ( Object . keys ( pingResponses ) . length > 0 ) {
@@ -103,12 +118,14 @@ const ServerScreen = () => {
103
118
} , [ servers , pingResponses ] )
104
119
105
120
const invalidServerName = newServerName . length > 32
121
+
106
122
const openEditServerDialog = ( server : string ) => {
107
123
setEditServerDialogOpen ( server )
108
124
setNewServerName ( server )
109
125
setServerVersion ( servers [ server ] . version )
110
126
setIpAddr ( servers [ server ] . address )
111
127
}
128
+
112
129
const cancelAddServer = ( ) => {
113
130
setEditServerDialogOpen ( false )
114
131
setServerVersion ( 'auto' )
@@ -117,12 +134,14 @@ const ServerScreen = () => {
117
134
setIpAddrRed ( false )
118
135
setServerNameRed ( false )
119
136
}
137
+
120
138
const deleteServer = ( ) => {
121
139
if ( typeof editServerDialogOpen !== 'string' ) return cancelAddServer ( )
122
140
delete servers [ editServerDialogOpen ]
123
141
setServers ( servers )
124
142
cancelAddServer ( )
125
143
}
144
+
126
145
const editServer = ( ) => {
127
146
const edit = typeof editServerDialogOpen === 'string'
128
147
if (
@@ -142,6 +161,7 @@ const ServerScreen = () => {
142
161
setPingResponses ( { } )
143
162
cancelAddServer ( )
144
163
}
164
+
145
165
const connectToServer = async ( server : string ) => {
146
166
if ( initiatingConnection . current ) return
147
167
if ( ! connection ) {
@@ -173,31 +193,86 @@ const ServerScreen = () => {
173
193
reason : 'EnderChat only supports 1.16.4 and newer (for now).'
174
194
} )
175
195
}
176
- // TODO: Refresh token before trying to connect.
177
196
const uuid = accounts [ activeAccount ] . type ? activeAccount : undefined
178
- const newConn = await initiateConnection ( {
179
- host,
180
- port,
181
- username : accounts [ activeAccount ] . username ,
182
- protocolVersion,
183
- selectedProfile : uuid ,
184
- accessToken : accounts [ activeAccount ] . accessToken
185
- } )
186
- const onCloseOrError : ( ) => void = ( ) => {
187
- setConnection ( undefined )
188
- if ( newConn . disconnectReason ) {
189
- setDisconnectReason ( {
190
- server,
191
- reason : parseValidJson ( newConn . disconnectReason )
192
- } )
197
+
198
+ // Create an updated "session" containing access tokens and certificates.
199
+ // LOW-TODO: Creating a session would be better with a loading screen, since Microsoft Login is slow.
200
+ // Maybe setConnection(null) to bring up ChatScreen while still being in a logged out state?
201
+ let session = sessions [ activeAccount ]
202
+ const is119 = protocolVersion >= protocolMap [ 1.19 ]
203
+ if ( uuid && ( ! session || ( ! session . certificate && is119 ) ) ) {
204
+ // LOW-TODO: Certificates and access tokens should be updated regularly.
205
+ try {
206
+ // Create a session with the latest access token.
207
+ const account = accounts [ activeAccount ]
208
+ if ( ! session && accounts [ activeAccount ] . type === 'microsoft' ) {
209
+ const [ msAccessToken , msRefreshToken ] = await refreshMSAuthToken (
210
+ accounts [ activeAccount ] . microsoftRefreshToken || '' ,
211
+ config . clientId ,
212
+ config . scope
213
+ )
214
+ const [ xlt , xuh ] = await getXboxLiveTokenAndUserHash ( msAccessToken )
215
+ const [ xstsToken ] = await getXstsTokenAndUserHash ( xlt )
216
+ const accessToken = await authenticateWithXsts ( xstsToken , xuh )
217
+ session = { accessToken }
218
+ setAccounts ( {
219
+ [ activeAccount ] : {
220
+ ...account ,
221
+ accessToken,
222
+ microsoftAccessToken : msAccessToken ,
223
+ microsoftRefreshToken : msRefreshToken
224
+ }
225
+ } )
226
+ } else if ( ! session && accounts [ activeAccount ] . type === 'mojang' ) {
227
+ const { accessToken, clientToken } = await refresh (
228
+ accounts [ activeAccount ] . accessToken || '' ,
229
+ accounts [ activeAccount ] . clientToken || '' ,
230
+ false
231
+ )
232
+ session = { accessToken }
233
+ setAccounts ( {
234
+ [ activeAccount ] : { ...account , accessToken, clientToken }
235
+ } )
236
+ }
237
+ // If connecting to 1.19, get player certificates.
238
+ if ( ! session . certificate && is119 ) {
239
+ const token = session . accessToken
240
+ session . certificate = await getPlayerCertificates ( token )
241
+ }
242
+ setSessions ( { [ activeAccount ] : session } )
243
+ } catch ( e ) {
244
+ const reason =
245
+ 'Failed to create session! You may need to re-login with your Microsoft Account in the Accounts tab.'
246
+ setDisconnectReason ( { server, reason } )
247
+ initiatingConnection . current = false
248
+ return
249
+ }
250
+ }
251
+
252
+ // Connect to server after setting up the session.
253
+ try {
254
+ const newConn = await initiateConnection ( {
255
+ host,
256
+ port,
257
+ username : accounts [ activeAccount ] . username ,
258
+ protocolVersion,
259
+ selectedProfile : uuid ,
260
+ accessToken : session ?. accessToken ,
261
+ certificate : session ?. certificate // TODO: Chat Signing toggle?
262
+ } )
263
+ const onCloseOrError = ( ) => {
264
+ setConnection ( undefined )
265
+ if ( newConn . disconnectReason ) {
266
+ const reason = parseValidJson ( newConn . disconnectReason )
267
+ setDisconnectReason ( { server, reason } )
268
+ }
193
269
}
270
+ newConn . on ( 'close' , onCloseOrError )
271
+ newConn . on ( 'error' , onCloseOrError )
272
+ setConnection ( { serverName : server , connection : newConn } )
273
+ } catch ( e ) {
274
+ setDisconnectReason ( { server, reason : 'Failed to connect to server!' } )
194
275
}
195
- newConn . on ( 'close' , onCloseOrError )
196
- newConn . on ( 'error' , onCloseOrError )
197
- setConnection ( {
198
- serverName : server ,
199
- connection : newConn
200
- } )
201
276
initiatingConnection . current = false
202
277
}
203
278
}
0 commit comments