Skip to content

Commit

Permalink
feat: automatic port checking and command args
Browse files Browse the repository at this point in the history
  • Loading branch information
Mario Reder committed Jan 14, 2019
1 parent b557994 commit f0afc2e
Show file tree
Hide file tree
Showing 7 changed files with 126 additions and 54 deletions.
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "net64plus-server",
"version": "2.1.2",
"version": "2.2.0",
"compatVersion": "1.0",
"description": "Net64+ Dedicated Server",
"main": "dist/index.js",
Expand Down Expand Up @@ -54,6 +54,7 @@
]
},
"dependencies": {
"argparse": "^1.0.10",
"axios": "^0.17.1",
"escape-html": "^1.0.3",
"farmhash": "^2.0.5",
Expand All @@ -62,6 +63,7 @@
"xss": "^1.0.3"
},
"devDependencies": {
"@types/argparse": "^1.0.35",
"@types/jest": "^22.2.0",
"@types/uws": "^0.13.1",
"awesome-typescript-loader": "^3.4.1",
Expand Down
54 changes: 54 additions & 0 deletions src/Arguments.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { ArgumentParser } from 'argparse'

import * as fs from 'fs'
import * as path from 'path'

import { Settings, DEFAULT_SETTINGS } from './models/Settings.model'

export class Arguments {
public settings: Settings

private parser = new ArgumentParser({
addHelp: true,
description: 'Net64+ server'
})

constructor () {
let settings: Settings | undefined
let settingsPath = path.join(__dirname, '../settings.json')
try {
settings = JSON.parse(fs.readFileSync(settingsPath, {
encoding: 'utf8'
}))
} catch (err) {
fs.writeFileSync(settingsPath, JSON.stringify(DEFAULT_SETTINGS))
console.info('Failed to find or parse settings.json file. Using default settings instead and created a settings.json just for you.')
}

this.parser.addArgument([ '--port', '-P' ], {
type: (int: string) => parseInt(int)
})
this.parser.addArgument([ '--gamemode', '-g' ])
this.parser.addArgument([ '--disableGamemodeVote', '-G' ], {
action: 'storeFalse'
})
this.parser.addArgument([ '--passwordRequired', '-pr' ], {
action: 'storeTrue'
})
this.parser.addArgument([ '--password', '-p' ])
this.parser.addArgument([ '--name', '-n' ])
this.parser.addArgument([ '--domain', '-D' ])
this.parser.addArgument([ '--description', '-d' ])
this.parser.addArgument([ '--enableWebHook', '-w' ], {
action: 'storeTrue'
})
this.parser.addArgument([ '--apiKey', '-k' ])
const parsed = this.parser.parseArgs() as Settings

this.settings = {} as any
Object.entries(DEFAULT_SETTINGS).forEach(([ key, defaultValue ]: [ string, any ]) => {
// @ts-ignore
this.settings[key] = parsed[key] || (settings ? settings[key] : null) || defaultValue
})
}
}
7 changes: 4 additions & 3 deletions src/Client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -129,15 +129,16 @@ export class Client {

private onDisconnect (): void {
this.identity.startDeleteTimeout()
webSocketServer.removePlayer(this.id)
delete webSocketServer.clients[this.id]
if (this.player) {
webSocketServer.removePlayer(this.id)
}
if (this.connectionTimeout) {
clearTimeout(this.connectionTimeout)
}
if (this.afkTimeout) {
clearInterval(this.afkTimeout)
}
const activeUsers = webSocketServer.clients.filter(client => client).length
console.info(`Active users: ${activeUsers}/24`)
}

/**
Expand Down
18 changes: 13 additions & 5 deletions src/WebHook.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import { webSocketServer } from './globals'
import { Settings } from './models/Settings.model'
import { Server } from './models/Server.model'

const URL_LIST = 'https://smmdb.ddns.net/net64'
export const URL_API = 'https://smmdb.ddns.net/api/net64server'

const apiKey = Symbol('apiKey')
Expand All @@ -19,6 +18,8 @@ export class WebHook {

private compatVersion: string

private id?: string

private ip: string

private port: number
Expand All @@ -41,6 +42,8 @@ export class WebHook {

private apiKey: string

private isDedicated: boolean

constructor (
{ name, domain, description, port, passwordRequired, apiKey }: Omit<Settings, 'gamemode'>,
{ ip, country, countryCode, latitude, longitude }: Server
Expand All @@ -58,22 +61,27 @@ export class WebHook {
this.longitude = longitude
this.passwordRequired = passwordRequired
this.apiKey = apiKey!
this.isDedicated = process.env.TARGET_ENV !== 'win32'
this.loop()
}

private loop = async () => {
try {
const body = Object.assign({}, this)
body.toJSON = this.toJSON
await axios.post(
URL_API,
const url = `${URL_API}${this.id ? `?id=${this.id}` : ''}`
const res = (await axios.post(
url,
body,
{
timeout: 10000,
headers: this.apiKey ? {
'Authorization': `APIKEY ${this.apiKey}`
} : {}
} : {},
responseType: 'json'
}
)
)).data
this.id = res.id
} catch (err) {
if (err.response && err.response.status === 401) {
console.error('Your API key seems to be wrong. Please check your settings!\nWebHook was disabled now')
Expand Down
28 changes: 15 additions & 13 deletions src/WebSocketServer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ export class WebSocketServer {

private command: Command

private ip: string
private ip?: string

private port: number

Expand All @@ -57,54 +57,56 @@ export class WebSocketServer {

private description: string

private countryCode: string
private countryCode?: string

public readonly passwordRequired: boolean

public readonly password: string

constructor (
{ port, gamemode, enableGamemodeVote, passwordRequired, password, name, domain, description }: Settings,
server?: Server
{ port, gamemode, enableGamemodeVote, passwordRequired, password, name, domain, description }: Settings
) {
this.gameMode = gamemode
this.ip = server ? server.ip : ''
this.port = port
this.name = name
this.domain = domain
this.description = description
this.countryCode = server ? server.countryCode : 'LAN'
this.passwordRequired = passwordRequired
this.password = password
this.command = new Command(enableGamemodeVote)
this.onConnection = this.onConnection.bind(this)
this.server = new WSServer({ port: this.port }, () => {
console.info(`\nNet64+ ${process.env.VERSION} server successfully started!\nAccepting connections on Port ${this.port}`)
this.server = new WSServer({ port: this.port }, async () => {
if (passwordRequired) {
console.info('Password protection enabled')
}
if (process.env.TARGET_ENV === 'win32') {
console.info('Connect locally via direct connect 127.0.0.1\nTo accept external connections, your Port must be forwarded.\nTo join via LAN, others must use your LAN IP address: win + "cmd" > ipconfig > IPv4 Address or via Hamachi network and IP')
console.info('\nThis is a precompiled version of the Net64+ server. It has the limitation, that it cannot be displayed on the public server list. It is only meant to be used for user servers!\n')
}
})
this.server.on('connection', this.onConnection)
this.metaData = new MetaData()
this.clients = []
this.players = []
}

public start (server?: Server) {
this.ip = server ? server.ip : ''
this.countryCode = server ? server.countryCode : 'LAN'
console.info(`\nNet64+ ${process.env.VERSION} server successfully started!`)
this.server!.on('connection', this.onConnection)
}

public addPlayer (player: Player): void {
const playerId = player.client.id
this.players[playerId] = player
if (!this.playerWithToken) {
this.grantNewServerToken(player)
}
this.broadcastPlayerListMessage()
const activeUsers = this.players.filter(players => players).length
console.info(`Active users: ${activeUsers}/24`)
}

public removePlayer (clientId: number): void {
delete this.clients[clientId]
const playerToRemove = this.players[clientId]
let shouldGrantNewToken = false
if (playerToRemove === this.playerWithToken) {
Expand All @@ -116,6 +118,8 @@ export class WebSocketServer {
this.grantNewServerToken()
}
this.broadcastPlayerListMessage()
const activeUsers = this.players.filter(player => player).length
console.info(`Active users: ${activeUsers}/24`)
}

private broadcastPlayerListMessage (): void {
Expand Down Expand Up @@ -404,8 +408,6 @@ export class WebSocketServer {
console.info(`A new client connected and received ID: ${id}`)
}
this.clients[id] = new Client(id, ws)
const activeUsers = this.clients.filter(client => client).length
console.info(`Active users: ${activeUsers}/24`)
}

private getNextClientId (): number | null {
Expand Down
61 changes: 30 additions & 31 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,54 +1,53 @@
import axios from 'axios'

import * as fs from 'fs'
import * as path from 'path'

import { webSocketServer, setWebSocketServer } from './globals'
import { WebSocketServer } from './WebSocketServer'
import { WebHook } from './WebHook'
import { DEFAULT_SETTINGS } from './models/Settings.model'
import { Server } from './models/Server.model'
import { Arguments } from './Arguments'

const UPDATE_INTERVAL = 32
const URL_IP_API = 'http://ip-api.com/json'
const PORT_CHECK_API = 'https://smmdb.ddns.net/api/v2/net64/portcheck'

let settings = DEFAULT_SETTINGS
if (process.env.TARGET_ENV !== 'win32') {
try {
settings = JSON.parse(fs.readFileSync(path.join(__dirname, '../settings.json'), {
encoding: 'utf8'
}))
} catch (err) {
console.info('Failed to find or parse settings.json file. Using default settings instead.')
}
}
const args = new Arguments()
const settings = args.settings

const init = async () => {
const portCheck = async (port: number): Promise<Server | undefined> => {
console.info(`Checking whether port ${port} is open...`)
try {
const res = (await axios.get(URL_IP_API, {
const url = `${PORT_CHECK_API}?port=${port}`
const res = (await axios.get(url, {
timeout: 10000,
responseType: 'json'
})).data
return {
ip: res.query,
country: res.country,
countryCode: res.countryCode,
latitude: res.lat,
longitude: res.lon
}
console.info('Looks like we got in there')
return res
} catch (err) {
console.warn('It looks like you are offline. The server will be starting in offline mode')
if (err.response && err.response.status === 400 && err.response.data === 'Port is closed') {
return
}
console.warn(`WARNING: Port check did not succeed. We could not check whether you set up proper port forwarding, sorry.`)
}
}

(async () => {
let serverData: Server | undefined = await init()
setWebSocketServer(new WebSocketServer(settings, serverData))
if (settings.enableWebHook && serverData) {
if (!settings.apiKey) {
throw new Error('You must set an apiKey, if you want to be listed on the server list. Either add an apiKey or disable web hook.')
setWebSocketServer(new WebSocketServer(settings))
let serverData: Server | undefined = await portCheck(settings.port)
const isOnline = !!serverData
if (!isOnline && settings.enableWebHook) {
console.warn(`ERROR: Cannot host a public server if Port check failed.`)
process.exit(1)
}
if (isOnline) {
if (settings.enableWebHook) {
if (!settings.apiKey) {
console.error('ERROR: You must set an apiKey, if you want to be listed on the server list. Either add an apiKey or disable web hook.')
process.exit(1)
}
const webHook = new WebHook(settings, serverData!)
}
const webHook = new WebHook(settings, serverData)
}
webSocketServer.start(serverData)

const main = () => {
webSocketServer.broadcastData()
Expand Down
8 changes: 7 additions & 1 deletion yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,11 @@
version "0.7.0"
resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-0.7.0.tgz#9a06f4f137ee84d7df0460c1fdb1135ffa6c50fd"

"@types/argparse@^1.0.35":
version "1.0.35"
resolved "https://registry.yarnpkg.com/@types/argparse/-/argparse-1.0.35.tgz#b3acbd331a82854cca16d6015599d267b7272ec9"
integrity sha512-+4KNdOqes4Bq0fu5dQCTUVpNQl10ui1JWdXG6S6AvRqHM4K5K1/6SBJ0Qz7PT+K85AcJ0UC7ZDuTz+aL3n6tWw==

"@types/events@*":
version "1.1.0"
resolved "https://registry.yarnpkg.com/@types/events/-/events-1.1.0.tgz#93b1be91f63c184450385272c47b6496fd028e02"
Expand Down Expand Up @@ -366,9 +371,10 @@ are-we-there-yet@~1.1.2:
delegates "^1.0.0"
readable-stream "^2.0.6"

argparse@^1.0.7:
argparse@^1.0.10, argparse@^1.0.7:
version "1.0.10"
resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911"
integrity sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==
dependencies:
sprintf-js "~1.0.2"

Expand Down

0 comments on commit f0afc2e

Please sign in to comment.