Skip to content

Commit

Permalink
feature: Handle authentication message
Browse files Browse the repository at this point in the history
is related to #57
  • Loading branch information
Mario Reder committed Jul 27, 2018
1 parent 6ddf527 commit 0c1f0b1
Show file tree
Hide file tree
Showing 14 changed files with 189 additions and 29 deletions.
29 changes: 26 additions & 3 deletions src/main/Connection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ import {
ConnectionDenied,
IServerClient,
IServerMessage,
IConnectionDenied
IConnectionDenied,
Authentication
} from '../../proto/ServerClientMessage'

const UPDATE_INTERVAL = 32
Expand All @@ -28,8 +29,6 @@ const UPDATE_INTERVAL = 32
* Net64+ server.
*/
export class Connection {
// public server: Server

private ws: WS

private playerId?: number
Expand Down Expand Up @@ -270,6 +269,9 @@ export class Connection {
case ServerMessage.MessageType.PLAYER_REORDER:
this.onPlayerReorder(serverMessage)
break
case ServerMessage.MessageType.AUTHENTICATION:
this.onAuthentication(serverMessage)
break
}
}

Expand Down Expand Up @@ -348,6 +350,27 @@ export class Connection {
emulator!.setPlayerId(playerId)
}

/**
* Handle server authentication message.
*
* @param {IServerMessage} serverMessage - The decoded message
*/
private onAuthentication (serverMessage: IServerMessage): void {
const authentication = serverMessage.authentication
if (!authentication) return
const { status, throttle } = authentication
if (status == null) return
switch (status) {
case Authentication.Status.ACCEPTED:
connector.acceptAuthentication()
break
case Authentication.Status.DENIED:
if (throttle == null) return
connector.denyAuthentication(throttle)
break
}
}

/**
* Handle player list update message.
*
Expand Down
8 changes: 8 additions & 0 deletions src/main/Connector.ts
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,14 @@ export class Connector {
this.window.webContents.send(MainMessage.WRONG_VERSION, { majorVersion, minorVersion })
}

public acceptAuthentication (): void {
this.window.webContents.send(MainMessage.AUTHENTICATION_ACCEPTED)
}

public denyAuthentication (throttle: number): void {
this.window.webContents.send(MainMessage.AUTHENTICATION_DENIED, throttle)
}

public globalChatMessage (message: string, senderId: number): void {
this.window.webContents.send(MainMessage.CHAT_GLOBAL, { message, senderId })
}
Expand Down
2 changes: 2 additions & 0 deletions src/models/Message.model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ export enum MainMessage {
GAME_MODE = 'GAME_MODE',
SERVER_FULL = 'SERVER_FULL',
WRONG_VERSION = 'WRONG_VERSION',
AUTHENTICATION_ACCEPTED = 'AUTHENTICATION_ACCEPTED',
AUTHENTICATION_DENIED = 'AUTHENTICATION_DENIED',
CHAT_GLOBAL = 'CHAT_GLOBAL',
CHAT_COMMAND = 'CHAT_COMMAND',
SET_CONNECTION_ERROR = 'SET_CONNECTION_ERROR',
Expand Down
2 changes: 2 additions & 0 deletions src/models/State.model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ export type RouterState = Readonly<RouterStateDraft>

export interface ConnectionStateDraft {
server: Server | null
authenticated: boolean
authenticationThrottle: number
hasToken: boolean
error: string
}
Expand Down
27 changes: 25 additions & 2 deletions src/renderer/Connector.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,17 @@ import { push } from 'react-router-redux'

import { store } from '.'
import { addGlobalMessage, clearGlobalMessages } from './utils/chat.util'
import { disconnect, setConnectionError, setPlayer, setPlayers, setServer, setGameMode } from './actions/connection'
import {
authenticationAccepted,
authenticationDenied,
authenticationRequired,
disconnect,
setConnectionError,
setGameMode,
setPlayer,
setPlayers,
setServer
} from './actions/connection'
import { isConnectedToEmulator, setEmulatorError } from './actions/emulator'
import { MainMessage, RendererMessage } from '../models/Message.model'
import { Server } from '../models/Server.model'
Expand All @@ -20,6 +30,8 @@ export class Connector {
ipcRenderer.on(MainMessage.GAME_MODE, this.onSetGameMode)
ipcRenderer.on(MainMessage.SERVER_FULL, this.onServerFull)
ipcRenderer.on(MainMessage.WRONG_VERSION, this.onWrongVersion)
ipcRenderer.on(MainMessage.AUTHENTICATION_ACCEPTED, this.onAuthenticationAccepted)
ipcRenderer.on(MainMessage.AUTHENTICATION_DENIED, this.onAuthenticationDenied)
ipcRenderer.on(MainMessage.CHAT_GLOBAL, this.onGlobalChatMessage)
ipcRenderer.on(MainMessage.CHAT_COMMAND, this.onCommandMessage)
ipcRenderer.on(MainMessage.SET_CONNECTION_ERROR, this.onConnectionError)
Expand Down Expand Up @@ -49,6 +61,9 @@ export class Connector {
console.info('CONNECTED TO SERVER', server)
}
store.dispatch(setServer(server))
if (server.passwordRequired) {
store.dispatch(authenticationRequired())
}
}

private onSetPlayers = (_: Electron.Event, players: IPlayerUpdate[]) => {
Expand All @@ -72,14 +87,22 @@ export class Connector {
}

private onWrongVersion = (
event: Electron.Event,
_: Electron.Event,
{ majorVersion, minorVersion }:
{ majorVersion: number, minorVersion: number}
) => {
store.dispatch(setConnectionError(`The server's network API version (${majorVersion}.${minorVersion}) is incompatible with your client API version (${process.env.MAJOR}.${process.env.MINOR})`))
// TODO add server version -> client version mapping
}

private onAuthenticationAccepted = () => {
store.dispatch(authenticationAccepted())
}

private onAuthenticationDenied = (_: Electron.Event, throttle: number) => {
store.dispatch(authenticationDenied(throttle))
}

private onGlobalChatMessage = (
_: Electron.Event,
{ message, senderId }:
Expand Down
22 changes: 22 additions & 0 deletions src/renderer/actions/connection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ import {
SetPlayersAction,
SetPlayerAction,
SetGameModeAction,
AuthenticationRequired,
AuthenticationAccepted,
AuthenticationDenied,
DisconnectAction,
ConnectionActionType
} from './models/connection.model'
Expand Down Expand Up @@ -46,6 +49,25 @@ export function setGameMode (gameMode: number): SetGameModeAction {
}
}

export function authenticationRequired (): AuthenticationRequired {
return {
type: ConnectionActionType.AUTHENTICATION_REQUIRED
}
}

export function authenticationAccepted (): AuthenticationAccepted {
return {
type: ConnectionActionType.AUTHENTICATION_ACCEPTED
}
}

export function authenticationDenied (throttle: number): AuthenticationDenied {
return {
type: ConnectionActionType.AUTHENTICATION_DENIED,
throttle
}
}

export function disconnect (): DisconnectAction {
return {
type: ConnectionActionType.DISCONNECT
Expand Down
14 changes: 14 additions & 0 deletions src/renderer/actions/models/connection.model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,14 @@ export interface SetGameModeAction extends Action {
gameMode: number
}

export type AuthenticationRequired = Action

export type AuthenticationAccepted = Action

export interface AuthenticationDenied extends Action {
throttle: number
}

export type DisconnectAction = Action

export type ConnectionAction =
Expand All @@ -32,6 +40,9 @@ export type ConnectionAction =
& SetPlayersAction
& SetPlayerAction
& SetGameModeAction
& AuthenticationRequired
& AuthenticationAccepted
& AuthenticationDenied
& DisconnectAction

export enum ConnectionActionType {
Expand All @@ -40,5 +51,8 @@ export enum ConnectionActionType {
SET_PLAYERS = 'SET_PLAYERS',
SET_PLAYER = 'SET_PLAYER',
GAME_MODE = 'GAME_MODE',
AUTHENTICATION_REQUIRED = 'AUTHENTICATION_REQUIRED',
AUTHENTICATION_ACCEPTED = 'AUTHENTICATION_ACCEPTED',
AUTHENTICATION_DENIED = 'AUTHENTICATION_DENIED',
DISCONNECT = 'DISCONNECT'
}
32 changes: 16 additions & 16 deletions src/renderer/components/areas/ConnectionArea.tsx
Original file line number Diff line number Diff line change
@@ -1,29 +1,23 @@
import * as React from 'react'
import { Dispatch } from 'redux'
import { connect } from 'react-redux'

import { SendPasswordArea } from './SendPasswordArea'
import { ServerPanel } from '../panels/ServerPanel'
import { ChatArea } from '../areas/ChatArea'
import { State } from '../../../models/State.model'
import { Server } from '../../../models/Server.model'

interface ConnectionAreaProps {
dispatch: Dispatch<State>
server: Server
authenticated: boolean
authenticationThrottle: number
}

interface ConnectionAreaState {
passwordAccepted: boolean
}

export class ConnectionArea extends React.PureComponent<ConnectionAreaProps, ConnectionAreaState> {
constructor (props: ConnectionAreaProps) {
super(props)
this.state = {
passwordAccepted: !props.server.passwordRequired
}
}

class Area extends React.PureComponent<ConnectionAreaProps, {}> {
public render (): JSX.Element {
const { server } = this.props
const { passwordAccepted } = this.state
const { server, authenticated, authenticationThrottle } = this.props
const styles: React.CSSProperties = {
area: {
overflowY: 'auto',
Expand All @@ -39,10 +33,16 @@ export class ConnectionArea extends React.PureComponent<ConnectionAreaProps, Con
<ServerPanel server={server} isConnected />
<ChatArea />
{
!passwordAccepted &&
<SendPasswordArea />
!authenticated &&
<SendPasswordArea
throttle={authenticationThrottle}
/>
}
</div>
)
}
}
export const ConnectionArea = connect((state: State) => ({
authenticated: state.connection.authenticated,
authenticationThrottle: state.connection.authenticationThrottle
}))(Area)
53 changes: 50 additions & 3 deletions src/renderer/components/areas/SendPasswordArea.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,24 +14,62 @@ export const MAX_LENGTH_PASSWORD = 30

interface SendPasswordProps {
dispatch: Dispatch<State>
throttle: number
}

interface SendPasswordAreaState {
password: string
timeout: string
}

class Area extends React.PureComponent<SendPasswordProps, SendPasswordAreaState> {
private throttleUpdateInterval?: NodeJS.Timer

constructor (props: SendPasswordProps) {
super(props)
this.state = {
password: ''
password: '',
timeout: ''
}
this.onPasswordChange = this.onPasswordChange.bind(this)
this.onKeyPress = this.onKeyPress.bind(this)
this.onSubmit = this.onSubmit.bind(this)
this.onDisconnect = this.onDisconnect.bind(this)
}

public componentDidMount (): void {
if (!this.props.throttle || this.throttleUpdateInterval) return
this.startThrottleUpdate()
}

public componentWillReceiveProps (nextProps: SendPasswordProps): void {
if (nextProps.throttle == null || this.throttleUpdateInterval) return
this.startThrottleUpdate()
}

private startThrottleUpdate (): void {
this.throttleUpdateInterval = setInterval(() => {
const { throttle } = this.props
const remainingThrottleTime = (throttle - Date.now()) / 1000
if (remainingThrottleTime <= 0) {
this.setState({
timeout: ''
})
return
}
const timeout = String(Math.trunc(Math.ceil(remainingThrottleTime)))
this.setState({
timeout
})
}, 100)
}

public componentWillUnmount (): void {
if (!this.throttleUpdateInterval) return
clearInterval(this.throttleUpdateInterval)
delete this.throttleUpdateInterval
}

private onPasswordChange ({ target }: React.ChangeEvent<HTMLInputElement>): void {
let value = target.value
if (value.length > MAX_LENGTH_PASSWORD) {
Expand Down Expand Up @@ -59,7 +97,7 @@ class Area extends React.PureComponent<SendPasswordProps, SendPasswordAreaState>
}

public render (): JSX.Element {
const { password } = this.state
const { password, timeout } = this.state
return (
<div className='send-password-area-wrapper'>
<div className='send-password-area'>
Expand All @@ -74,8 +112,17 @@ class Area extends React.PureComponent<SendPasswordProps, SendPasswordAreaState>
onChange={this.onPasswordChange}
onKeyPress={this.onKeyPress}
/>
{
timeout &&
<label>You can try again in {timeout} seconds</label>
}
</div>
<SMMButton text='Submit' iconSrc='img/net64.svg' onClick={this.onSubmit} />
<SMMButton
text='Submit'
iconSrc='img/net64.svg'
onClick={this.onSubmit}
disabled={!!timeout}
/>
<SMMButton
onClick={this.onDisconnect}
text='Disconnect'
Expand Down
6 changes: 6 additions & 0 deletions src/renderer/components/buttons/SMMButton.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
.smm-button {
&-disabled {
background-color: #666 !important;
pointer-events: none;
}
}
Loading

0 comments on commit 0c1f0b1

Please sign in to comment.