Incomplete websocket implementation
This commit is contained in:
parent
153f50294d
commit
a24576f4f2
@ -50,8 +50,7 @@ async function performGetPublicTablesRequest(token) {
|
|||||||
return fetch("/tables/public", { method: 'POST', body: token })
|
return fetch("/tables/public", { method: 'POST', body: token })
|
||||||
.then(convertServerResponse)
|
.then(convertServerResponse)
|
||||||
.then(function(text) {
|
.then(function(text) {
|
||||||
const decoded = atob(text)
|
return JSON.parse(text);
|
||||||
return JSON.parse(decoded);
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -78,4 +77,4 @@ function convertServerResponse(response) {
|
|||||||
default:
|
default:
|
||||||
throw Error("Unexpected response: " + response.statusText)
|
throw Error("Unexpected response: " + response.statusText)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,23 +1,15 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
import Vapor
|
import Vapor
|
||||||
|
|
||||||
let playerPerTable = 4
|
|
||||||
|
|
||||||
typealias TableId = String
|
|
||||||
typealias TableName = String
|
|
||||||
|
|
||||||
final class Database {
|
final class Database {
|
||||||
|
|
||||||
private let players: PlayerManagement
|
private let players: PlayerManagement
|
||||||
|
|
||||||
private let tables: TableManagement
|
private let tables: TableManagement
|
||||||
|
|
||||||
private var sessions: [SessionToken : WebSocket]
|
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
self.players = PlayerManagement()
|
self.players = PlayerManagement()
|
||||||
self.tables = TableManagement()
|
self.tables = TableManagement()
|
||||||
self.sessions = [:]
|
|
||||||
// TODO: Load server data from disk
|
// TODO: Load server data from disk
|
||||||
// TODO: Save data to disk
|
// TODO: Save data to disk
|
||||||
}
|
}
|
||||||
@ -33,22 +25,19 @@ final class Database {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func deletePlayer(named name: PlayerName) {
|
func deletePlayer(named name: PlayerName) {
|
||||||
if let sessionToken = players.deletePlayer(named: name) {
|
_ = players.deletePlayer(named: name)
|
||||||
closeAndRemoveSession(for: sessionToken)
|
tables.remove(player: name)
|
||||||
}
|
|
||||||
// TODO: Delete player from tables
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func isValid(sessionToken token: SessionToken) -> Bool {
|
func isValid(sessionToken token: SessionToken) -> Bool {
|
||||||
players.isValid(sessionToken: token)
|
players.isValid(sessionToken: token)
|
||||||
}
|
}
|
||||||
|
|
||||||
func startSession(socket: WebSocket, sessionToken: SessionToken) {
|
func startSession(socket: WebSocket, sessionToken: SessionToken) -> Bool {
|
||||||
closeAndRemoveSession(for: sessionToken)
|
guard let player = players.registeredPlayerExists(withSessionToken: sessionToken) else {
|
||||||
sessions[sessionToken] = socket
|
return false
|
||||||
socket.onText { [weak self] socket, text in
|
|
||||||
self?.didReceive(message: text, forSessionToken: sessionToken)
|
|
||||||
}
|
}
|
||||||
|
return tables.connect(player: player, using: socket)
|
||||||
}
|
}
|
||||||
|
|
||||||
private func didReceive(message: String, forSessionToken token: SessionToken) {
|
private func didReceive(message: String, forSessionToken token: SessionToken) {
|
||||||
@ -56,13 +45,15 @@ final class Database {
|
|||||||
print("Session \(token.prefix(6)): \(message)")
|
print("Session \(token.prefix(6)): \(message)")
|
||||||
}
|
}
|
||||||
|
|
||||||
func endSession(forSessionToken token: SessionToken) {
|
func endSession(forSessionToken sessionToken: SessionToken) {
|
||||||
players.endSession(forSessionToken: token)
|
guard let player = players.endSession(forSessionToken: sessionToken) else {
|
||||||
closeAndRemoveSession(for: token)
|
return
|
||||||
|
}
|
||||||
|
closeSession(for: player)
|
||||||
}
|
}
|
||||||
|
|
||||||
private func closeAndRemoveSession(for token: SessionToken) {
|
private func closeSession(for player: PlayerName) {
|
||||||
_ = sessions.removeValue(forKey: token)?.close()
|
tables.disconnect(player: player)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -78,16 +69,12 @@ final class Database {
|
|||||||
players.registeredPlayerExists(withSessionToken: token)
|
players.registeredPlayerExists(withSessionToken: token)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func currentTableOfPlayer(named player: PlayerName) -> TableId {
|
||||||
|
tables.currentTableOfPlayer(named: player) ?? ""
|
||||||
|
}
|
||||||
|
|
||||||
// MARK: Tables
|
// MARK: Tables
|
||||||
|
|
||||||
func tableExists(withId id: TableId) -> Bool {
|
|
||||||
tables.tableExists(withId: id)
|
|
||||||
}
|
|
||||||
|
|
||||||
func tableIsFull(withId id: TableId) -> Bool {
|
|
||||||
tables.tableIsFull(withId: id)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Create a new table with optional players.
|
Create a new table with optional players.
|
||||||
- Parameter name: The name of the table
|
- Parameter name: The name of the table
|
||||||
@ -103,14 +90,7 @@ final class Database {
|
|||||||
tables.getPublicTableInfos()
|
tables.getPublicTableInfos()
|
||||||
}
|
}
|
||||||
|
|
||||||
func join(tableId: TableId, player: PlayerName) {
|
func join(tableId: TableId, player: PlayerName) -> TableManagement.JoinTableResult {
|
||||||
let playersAtTable = tables.join(tableId: tableId, player: player)
|
tables.join(tableId: tableId, player: player)
|
||||||
playersAtTable
|
|
||||||
.compactMap { players.sessionToken(forPlayer: $0) } // Session Tokens
|
|
||||||
.compactMap { sessions[$0] } // Sockets
|
|
||||||
.forEach { socket in
|
|
||||||
// TODO: Notify sessions about changed players
|
|
||||||
// socket.send("")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -86,11 +86,12 @@ final class PlayerManagement {
|
|||||||
return token
|
return token
|
||||||
}
|
}
|
||||||
|
|
||||||
func endSession(forSessionToken token: SessionToken) {
|
func endSession(forSessionToken token: SessionToken) -> PlayerName? {
|
||||||
guard let player = playerNameForToken.removeValue(forKey: token) else {
|
guard let player = playerNameForToken.removeValue(forKey: token) else {
|
||||||
return
|
return nil
|
||||||
}
|
}
|
||||||
sessionTokenForPlayer.removeValue(forKey: player)
|
sessionTokenForPlayer.removeValue(forKey: player)
|
||||||
|
return player
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1,4 +1,10 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
|
import WebSocketKit
|
||||||
|
|
||||||
|
let maximumPlayersPerTable = 4
|
||||||
|
|
||||||
|
typealias TableId = String
|
||||||
|
typealias TableName = String
|
||||||
|
|
||||||
final class TableManagement {
|
final class TableManagement {
|
||||||
|
|
||||||
@ -14,16 +20,10 @@ final class TableManagement {
|
|||||||
/// A reverse list of players and their table id
|
/// A reverse list of players and their table id
|
||||||
private var playerTables = [PlayerName: TableId]()
|
private var playerTables = [PlayerName: TableId]()
|
||||||
|
|
||||||
|
private var playerConnections = [PlayerName : WebSocket]()
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func tableExists(withId id: TableId) -> Bool {
|
|
||||||
tableNames[id] != nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func tableIsFull(withId id: TableId) -> Bool {
|
|
||||||
(tablePlayers[id]?.count ?? playerPerTable) < playerPerTable
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -52,24 +52,54 @@ final class TableManagement {
|
|||||||
}.sorted()
|
}.sorted()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func currentTableOfPlayer(named player: PlayerName) -> TableId? {
|
||||||
|
playerTables[player]
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Join a table.
|
Join a table.
|
||||||
- Returns: The player names present at the table
|
- Returns: The result of the join operation
|
||||||
*/
|
*/
|
||||||
func join(tableId: TableId, player: PlayerName) -> [PlayerName] {
|
func join(tableId: TableId, player: PlayerName) -> JoinTableResult {
|
||||||
guard var players = tablePlayers[tableId] else {
|
guard var players = tablePlayers[tableId] else {
|
||||||
return []
|
return .tableNotFound
|
||||||
|
}
|
||||||
|
guard !players.contains(player) else {
|
||||||
|
return .success
|
||||||
|
}
|
||||||
|
guard players.count < maximumPlayersPerTable else {
|
||||||
|
return .tableIsFull
|
||||||
}
|
}
|
||||||
players.append(player)
|
players.append(player)
|
||||||
if let oldTable = playerTables[tableId] {
|
if let oldTable = playerTables[tableId] {
|
||||||
remove(player: player, fromTable: oldTable)
|
remove(player: player, fromTable: oldTable)
|
||||||
|
// TODO: End game if needed
|
||||||
|
//
|
||||||
}
|
}
|
||||||
tablePlayers[tableId] = players
|
tablePlayers[tableId] = players
|
||||||
playerTables[tableId] = tableId
|
playerTables[tableId] = tableId
|
||||||
return players
|
return .success
|
||||||
}
|
}
|
||||||
|
|
||||||
func remove(player: PlayerName, fromTable tableId: TableId) {
|
func remove(player: PlayerName, fromTable tableId: TableId) {
|
||||||
tablePlayers[tableId] = tablePlayers[tableId]?.filter { $0 != player }
|
tablePlayers[tableId] = tablePlayers[tableId]?.filter { $0 != player }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func remove(player: PlayerName) {
|
||||||
|
fatalError()
|
||||||
|
}
|
||||||
|
|
||||||
|
func connect(player: PlayerName, using socket: WebSocket) -> Bool {
|
||||||
|
fatalError()
|
||||||
|
}
|
||||||
|
|
||||||
|
func disconnect(player: PlayerName) {
|
||||||
|
fatalError()
|
||||||
|
}
|
||||||
|
|
||||||
|
enum JoinTableResult {
|
||||||
|
case tableNotFound
|
||||||
|
case tableIsFull
|
||||||
|
case success
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -135,17 +135,34 @@ func routes(_ app: Application) throws {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Start a new bidirectional session connection.
|
Get the current table of the player, if one exists.
|
||||||
|
- Parameter token: The session token of the player, as a string in the request body
|
||||||
|
- Throws:
|
||||||
|
- 400: Missing token
|
||||||
|
- 401: Invalid token
|
||||||
|
- Returns: The table id, or an empty string
|
||||||
|
*/
|
||||||
|
app.post("player", "table") { req -> String in
|
||||||
|
guard let token = req.body.string else {
|
||||||
|
throw Abort(.badRequest) // 400
|
||||||
|
}
|
||||||
|
guard let player = database.registeredPlayerExists(withSessionToken: token) else {
|
||||||
|
throw Abort(.unauthorized) // 401
|
||||||
|
}
|
||||||
|
return database.currentTableOfPlayer(named: player)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Start a new websocket connection for the client to receive table updates from the server
|
||||||
- Returns: Nothing
|
- Returns: Nothing
|
||||||
- Note: The first message over the connection must be a valid session token.
|
- Note: The first (and only) message from the client over the connection must be a valid session token.
|
||||||
*/
|
*/
|
||||||
app.webSocket("session", "start") { req, socket in
|
app.webSocket("session", "start") { req, socket in
|
||||||
socket.onText { socket, text in
|
socket.onText { socket, text in
|
||||||
guard database.isValid(sessionToken: text) else {
|
guard database.startSession(socket: socket, sessionToken: text) else {
|
||||||
_ = socket.close()
|
_ = socket.close()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
database.startSession(socket: socket, sessionToken: text)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -198,7 +215,8 @@ func routes(_ app: Application) throws {
|
|||||||
throw Abort(.forbidden) // 403
|
throw Abort(.forbidden) // 403
|
||||||
}
|
}
|
||||||
let list = database.getPublicTableInfos()
|
let list = database.getPublicTableInfos()
|
||||||
return try encoder.encode(list).base64EncodedString()
|
let data = try encoder.encode(list)
|
||||||
|
return String(data: data, encoding: .utf8)!
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -208,8 +226,8 @@ func routes(_ app: Application) throws {
|
|||||||
- Throws:
|
- Throws:
|
||||||
- 400: Missing token
|
- 400: Missing token
|
||||||
- 401: The session token is invalid
|
- 401: The session token is invalid
|
||||||
- 404: The table id doesn't exist
|
- 410: The table id doesn't exist
|
||||||
- 406: The table is already full and can't be joined
|
- 417: The table is already full and can't be joined
|
||||||
- Returns: Nothing
|
- Returns: Nothing
|
||||||
*/
|
*/
|
||||||
app.post("table", "join", ":table") { req -> String in
|
app.post("table", "join", ":table") { req -> String in
|
||||||
@ -220,13 +238,13 @@ func routes(_ app: Application) throws {
|
|||||||
guard let player = database.registeredPlayerExists(withSessionToken: token) else {
|
guard let player = database.registeredPlayerExists(withSessionToken: token) else {
|
||||||
throw Abort(.unauthorized) // 401
|
throw Abort(.unauthorized) // 401
|
||||||
}
|
}
|
||||||
guard database.tableExists(withId: table) else {
|
switch database.join(tableId: table, player: player) {
|
||||||
throw Abort(.notFound) // 404
|
case .tableNotFound:
|
||||||
|
throw Abort(.gone) // 410
|
||||||
|
case .tableIsFull:
|
||||||
|
throw Abort(.expectationFailed) // 417
|
||||||
|
case .success:
|
||||||
|
return ""
|
||||||
}
|
}
|
||||||
guard !database.tableIsFull(withId: table) else {
|
|
||||||
throw Abort(.notAcceptable) // 406
|
|
||||||
}
|
|
||||||
database.join(tableId: table, player: player)
|
|
||||||
return ""
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user