Skip to content

Commit

Permalink
Much better loading screen logic with feedback.
Browse files Browse the repository at this point in the history
The loading screen now shows actual feedback of what's happening.
If you close a connection, it won't connect if it was reloading premium.
  • Loading branch information
retrixe committed Sep 17, 2022
1 parent afaaaa1 commit 1942c40
Show file tree
Hide file tree
Showing 3 changed files with 105 additions and 77 deletions.
104 changes: 58 additions & 46 deletions src/screens/chat/ChatScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,17 @@ import {
enderChatPrefix,
sendMessageError
} from './packetHandler'
import { createConnection } from './sessionBuilder'
import { getSession, createConnection } from './sessionBuilder'
import { RootStackParamList } from '../../App'
import globalStyle from '../../globalStyle'
import useDarkMode from '../../context/useDarkMode'
import AccountsContext from '../../context/accountsContext'
import ServersContext from '../../context/serversContext'
import useSessionStore from '../../context/sessionStore'
import SettingsContext from '../../context/settingsContext'
import ConnectionContext, { Connection } from '../../context/connectionContext'
import ConnectionContext, {
DisconnectReason
} from '../../context/connectionContext'
import {
ChatToJsx,
mojangColorMap,
Expand Down Expand Up @@ -85,9 +87,6 @@ const handleError =
addMessage(enderChatPrefix + translated)
}

const isConnection = (connection: any): connection is Connection =>
!!(connection as Connection).connection

// TODO: Ability to copy text.
const ChatScreen = ({ navigation, route }: Props) => {
const darkMode = useDarkMode()
Expand All @@ -97,25 +96,27 @@ const ChatScreen = ({ navigation, route }: Props) => {
const { connection, setConnection, setDisconnectReason } =
useContext(ConnectionContext)
const { sessions, setSession } = useSessionStore()

// TODO: Show command history.
const [, setCommandHistory] = useState<string[]>([])
const [messages, setMessages] = useState<Message[]>([])
const [loggedIn, setLoggedIn] = useState(false)
const [loading, setLoading] = useState('Connecting to server...')
const [message, setMessage] = useState('')

const messagesBufferRef = useRef<Message[]>([])
const healthRef = useRef<number | null>(null)
const statusRef = useRef<Status>(connection ? 'CONNECTING' : 'OPENING')
const idRef = useRef(0)

const charLimit =
connection && connection.connection.options.protocolVersion >= 306 // 16w38a
? 256
: 100
const { version, serverName } = route.params
const charLimit = version >= 306 /* 16w38a */ ? 256 : 100

const addMessage = (text: MinecraftChat) =>
messagesBufferRef.current.unshift({ key: idRef.current++, text })
const closeChatScreen = () => {
if (navigation.canGoBack() && statusRef.current !== 'CLOSED') {
navigation.goBack()
const closeChatScreen = (reason?: DisconnectReason) => {
if (statusRef.current !== 'CLOSED') {
if (navigation.canGoBack()) navigation.goBack()
if (reason) setDisconnectReason(reason)
}
}

Expand Down Expand Up @@ -149,36 +150,45 @@ const ChatScreen = ({ navigation, route }: Props) => {
useEffect(() => {
if (statusRef.current === 'OPENING') {
statusRef.current = 'CONNECTING'
createConnection(
route.params.serverName,
route.params.version,
servers,
settings,
accounts,
sessions,
setSession,
setAccounts,
setConnection,
setDisconnectReason,
closeChatScreen
)
.then(conn => {
if (statusRef.current !== 'CLOSED') {
if (isConnection(conn)) setConnection(conn)
else {
closeChatScreen()
setDisconnectReason(conn)
;(async () => {
const session = await getSession(
version,
accounts,
sessions,
setSession,
setLoading,
setAccounts
)
if (typeof session === 'string') {
closeChatScreen({ server: serverName, reason: session })
} else if (statusRef.current !== 'CLOSED') {
setLoading('Connecting to server...')
const conn = await createConnection(
serverName,
version,
servers,
session,
settings,
accounts,
setConnection,
closeChatScreen
)
if ((statusRef.current as 'CLOSED' | 'CONNECTING') !== 'CLOSED') {
if (typeof conn === 'string') {
closeChatScreen({ server: serverName, reason: conn })
} else {
setConnection(conn)
setLoading('Logging in...')
}
} else if (isConnection(conn)) conn.connection.close() // No memory leaky
})
.catch(e => {
console.error(e)
closeChatScreen()
setDisconnectReason({
server: route.params.serverName,
reason: 'An error occurred resolving the server hostname!'
})
})
} else if (typeof conn !== 'string') conn.connection.close()
}
})().catch(err => {
console.error(err)
if (statusRef.current !== 'CLOSED') {
const reason = 'An unknown error occurred!\n' + err
closeChatScreen({ server: serverName, reason })
}
})
}
})

Expand All @@ -190,7 +200,7 @@ const ChatScreen = ({ navigation, route }: Props) => {
packetHandler(
healthRef,
statusRef,
setLoggedIn,
setLoading,
connection.connection,
addMessage,
settings.joinMessage,
Expand Down Expand Up @@ -276,7 +286,7 @@ const ChatScreen = ({ navigation, route }: Props) => {
onPress={() => navigation.push('Settings')}
/>
</View>
{(!loggedIn || !connection) && (
{(loading || !connection) && (
<View style={styles.loadingScreen}>
<ActivityIndicator
color='#00aaff'
Expand All @@ -285,10 +295,12 @@ const ChatScreen = ({ navigation, route }: Props) => {
default: 'large'
})}
/>
<Text style={styles.loadingScreenText}>Connecting...</Text>
<Text style={styles.loadingScreenText}>
{loading || 'Connecting to server...'}
</Text>
</View>
)}
{loggedIn && connection && (
{!loading && connection && (
<>
<ChatMessageListMemo
messages={messages}
Expand Down
4 changes: 2 additions & 2 deletions src/screens/chat/packetHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ export const packetHandler =
(
healthRef: React.MutableRefObject<number | null>,
statusRef: React.MutableRefObject<Status>,
setLoggedIn: React.Dispatch<React.SetStateAction<boolean>>,
setLoading: React.Dispatch<React.SetStateAction<string>>,
connection: ServerConnection,
addMessage: (text: MinecraftChat) => any,
joinMessage: string,
Expand All @@ -60,7 +60,7 @@ export const packetHandler =
) =>
(packet: Packet) => {
if (statusRef.current === 'CONNECTING' && connection.loggedIn) {
setLoggedIn(true)
setLoading('')
statusRef.current = 'CONNECTED'
if (sendJoinMessage) {
connection
Expand Down
74 changes: 45 additions & 29 deletions src/screens/chat/sessionBuilder.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Accounts } from '../../context/accountsContext'
import { Connection, DisconnectReason } from '../../context/connectionContext'
import { Sessions, SetSession } from '../../context/sessionStore'
import { Session, Sessions, SetSession } from '../../context/sessionStore'
import { Servers } from '../../context/serversContext'
import { Settings } from '../../context/settingsContext'
import {
Expand All @@ -16,40 +16,32 @@ import initiateConnection from '../../minecraft/connection'
import { parseIp, protocolMap, resolveHostname } from '../../minecraft/utils'
import config from '../../../config.json'

export const createConnection = async (
server: string,
export const getSession = async (
version: number,
servers: Servers,
settings: Settings,
accounts: Accounts,
sessions: Sessions,
setSession: SetSession,
setAccounts: (accs: Accounts) => void,
setConnection: (conn?: Connection) => void,
setDisconnectReason: (reason: DisconnectReason) => void,
closeChatScreen: () => void
): Promise<Connection | DisconnectReason> => {
const [hostname, portNumber] = parseIp(servers[server].address)
const [host, port] = await resolveHostname(hostname, portNumber)
setLoading: (msg: string) => void,
setAccounts: (accs: Accounts) => void
): Promise<Session | string> => {
const activeAccount = Object.keys(accounts).find(e => accounts[e].active)
if (!activeAccount) {
return {
server,
reason:
'No active account selected! Open the Accounts tab and add an account.'
}
return 'No active account selected! Open the Accounts tab and add an account.'
}
const uuid = accounts[activeAccount].type ? activeAccount : undefined

// Create an updated "session" containing access tokens and certificates.
let session = sessions[activeAccount]
const is119 = version >= protocolMap[1.19]
// TODO: We should store session store in a persistent cache. Certificates and access tokens should be updated regularly.
// TODO: We should store session store in a persistent cache.
// Certificates and access tokens should be updated regularly.
if (uuid && (!session || (!session.certificate && is119))) {
// We should probably lock access to them via a semaphore.
try {
// Create a session with the latest access token.
const account = accounts[activeAccount]
if (!session && accounts[activeAccount].type === 'microsoft') {
setLoading('Reloading your Microsoft Account...')
const [msAccessToken, msRefreshToken] = await refreshMSAuthToken(
accounts[activeAccount].microsoftRefreshToken || '',
config.clientId,
Expand All @@ -68,6 +60,7 @@ export const createConnection = async (
}
})
} else if (!session && accounts[activeAccount].type === 'mojang') {
setLoading('Reloading your Mojang Account...')
const { accessToken, clientToken } = await refresh(
accounts[activeAccount].accessToken || '',
accounts[activeAccount].clientToken || '',
Expand All @@ -85,14 +78,37 @@ export const createConnection = async (
}
setSession(activeAccount, session)
} catch (e) {
const reason =
'Failed to create session! You may need to re-login with your Microsoft Account in the Accounts tab.'
return { server, reason }
return 'Failed to create session! You may need to re-login with your Microsoft Account in the Accounts tab.'
}
}
return session
}

export const createConnection = async (
server: string,
version: number,
servers: Servers,
session: Session | undefined,
settings: Settings,
accounts: Accounts,
setConnection: (conn?: Connection) => void,
closeChatScreen: (reason?: DisconnectReason) => void
): Promise<Connection | string> => {
let host: string
let port: number
try {
const [hostname, portNumber] = parseIp(servers[server].address)
;[host, port] = await resolveHostname(hostname, portNumber)
} catch (e) {
return 'Failed to resolve server hostname!'
}

const activeAccount = Object.keys(accounts).find(e => accounts[e].active)
if (!activeAccount) {
return 'No active account selected! Open the Accounts tab and add an account.'
}
const uuid = accounts[activeAccount].type ? activeAccount : undefined

// TODO: Better connection cancellation support. The session load can take a lot of time.
// Connect to server after setting up the session.
try {
const newConn = await initiateConnection({
host,
Expand All @@ -104,18 +120,18 @@ export const createConnection = async (
certificate: settings.enableChatSigning ? session?.certificate : undefined
})
const onCloseOrError = () => {
closeChatScreen()
closeChatScreen(
newConn.disconnectReason
? { server, reason: parseValidJson(newConn.disconnectReason) }
: undefined
)
setConnection(undefined)
if (newConn.disconnectReason) {
const reason = parseValidJson(newConn.disconnectReason)
setDisconnectReason({ server, reason })
}
}
newConn.on('close', onCloseOrError)
newConn.on('error', onCloseOrError)
return { serverName: server, connection: newConn }
} catch (e) {
console.error(e)
return { server, reason: 'Failed to connect to server!' }
return 'Failed to connect to server!'
}
}

0 comments on commit 1942c40

Please sign in to comment.