From 6a97c894a6e19e78f1843709e225fd82c426c7db Mon Sep 17 00:00:00 2001 From: Clemens Wolff Date: Sat, 16 May 2020 13:39:00 -0400 Subject: [PATCH 1/5] Expose API router --- docs/documentation/api/Server.md | 3 +++ src/server/api.test.ts | 16 ++++++++++++---- src/server/api.ts | 27 ++++++--------------------- src/server/index.test.ts | 29 +++++------------------------ src/server/index.ts | 22 ++++++++++++---------- src/types.ts | 2 -- 6 files changed, 38 insertions(+), 61 deletions(-) diff --git a/docs/documentation/api/Server.md b/docs/documentation/api/Server.md index c6b90b74f..3c52dae8a 100644 --- a/docs/documentation/api/Server.md +++ b/docs/documentation/api/Server.md @@ -29,6 +29,8 @@ A config object with the following options: 5. `authenticateCredentials` (_function_): an optional function that tests if a player’s move is made with the correct credentials when using the default socket.io transport implementation. +6. `lobbyConfig` (_object_): configuration options for the Lobby API server. + ### Returns An object that contains: @@ -39,6 +41,7 @@ An object that contains: _({ apiServer, appServer }) => {}_ 3. app (_object_): The Koa app. 4. db (_object_): The `db` implementation. +5. router (_object_): The Koa Router for the server API. ### Usage diff --git a/src/server/api.test.ts b/src/server/api.test.ts index c64aaf239..a8e740993 100644 --- a/src/server/api.test.ts +++ b/src/server/api.test.ts @@ -9,7 +9,7 @@ import request from 'supertest'; import Koa from 'koa'; -import { addApiToServer, createApiServer } from './api'; +import { createRouter, configureApp } from './api'; import { ProcessGameConfig } from '../core/game'; import * as StorageAPI from './db/base'; import { Game } from '../types'; @@ -62,8 +62,18 @@ class AsyncStorage extends StorageAPI.Async { return this.mocks.listGames(...args); } } +describe('.createRouter', () => { + function addApiToServer({ app, ...args }) { + const router = createRouter(args as any); + configureApp(app, router); + } + + function createApiServer(args) { + const app = new Koa(); + addApiToServer({ app, ...args }); + return app; + } -describe('.createApiServer', () => { describe('creating a game', () => { let response; let app: Koa; @@ -1244,9 +1254,7 @@ describe('.createApiServer', () => { }); }); }); -}); -describe('.addApiToServer', () => { describe('when server app is provided', () => { let db: AsyncStorage; let server; diff --git a/src/server/api.ts b/src/server/api.ts index 9b8141cec..63c5f252c 100644 --- a/src/server/api.ts +++ b/src/server/api.ts @@ -55,34 +55,17 @@ export const CreateGame = async ( return gameID; }; -export const createApiServer = ({ +export const createRouter = ({ db, games, lobbyConfig, generateCredentials, }: { - db: StorageAPI.Sync | StorageAPI.Async; - games: Game[]; - lobbyConfig?: Server.LobbyConfig; - generateCredentials?: Server.GenerateCredentials; -}) => { - const app = new Koa(); - return addApiToServer({ app, db, games, lobbyConfig, generateCredentials }); -}; - -export const addApiToServer = ({ - app, - db, - games, - lobbyConfig, - generateCredentials, -}: { - app: Koa; games: Game[]; lobbyConfig?: Server.LobbyConfig; generateCredentials?: Server.GenerateCredentials; db: StorageAPI.Sync | StorageAPI.Async; -}) => { +}): Router => { if (!lobbyConfig) lobbyConfig = {}; lobbyConfig = { ...lobbyConfig, @@ -335,6 +318,10 @@ export const addApiToServer = ({ router.post('/games/:name/:id/update', koaBody(), updatePlayerMetadata); + return router; +}; + +export const configureApp = (app: Koa, router: Router): void => { app.use(cors()); // If API_SECRET is set, then require that requests set an @@ -351,6 +338,4 @@ export const addApiToServer = ({ }); app.use(router.routes()).use(router.allowedMethods()); - - return app; }; diff --git a/src/server/index.test.ts b/src/server/index.test.ts index 14f0ff757..a5eff8ac5 100644 --- a/src/server/index.test.ts +++ b/src/server/index.test.ts @@ -7,7 +7,6 @@ */ import { Server, createServerRunConfig, KoaServer } from '.'; -import * as api from './api'; import { StorageAPI } from '../types'; const game = { seed: 0 }; @@ -17,20 +16,6 @@ jest.mock('../core/logger', () => ({ error: () => {}, })); -const mockApiServerListen = jest.fn((port, listeningCallback?: () => void) => { - if (listeningCallback) listeningCallback(); - return { - address: () => ({ port: 'mock-api-port' }), - close: () => {}, - }; -}); -jest.mock('./api', () => ({ - createApiServer: jest.fn(() => ({ - listen: mockApiServerListen, - })), - addApiToServer: jest.fn(), -})); - jest.mock('koa-socket-2', () => { class MockSocket { on() {} @@ -56,6 +41,7 @@ jest.mock('koa', () => { return class { constructor() { (this as any).context = {}; + (this as any).use = () => this; (this as any).callback = () => {}; (this as any).listen = (port, listeningCallback?: () => void) => { if (listeningCallback) listeningCallback(); @@ -98,9 +84,6 @@ describe('run', () => { beforeEach(() => { server = null; runningServer = null; - (api.createApiServer as jest.Mock).mockClear(); - (api.addApiToServer as jest.Mock).mockClear(); - (mockApiServerListen as jest.Mock).mockClear(); }); afterEach(() => { @@ -115,9 +98,8 @@ describe('run', () => { runningServer = await server.run(undefined); expect(server).not.toBeUndefined(); - expect(api.addApiToServer).toBeCalled(); - expect(api.createApiServer).not.toBeCalled(); - expect(mockApiServerListen).not.toBeCalled(); + expect(runningServer.appServer).not.toBeUndefined(); + expect(runningServer.apiServer).toBeUndefined(); }); test('multiple servers running', async () => { @@ -128,9 +110,8 @@ describe('run', () => { }); expect(server).not.toBeUndefined(); - expect(api.addApiToServer).not.toBeCalled(); - expect(api.createApiServer).toBeCalled(); - expect(mockApiServerListen).toBeCalled(); + expect(runningServer.appServer).not.toBeUndefined(); + expect(runningServer.apiServer).not.toBeUndefined(); }); }); diff --git a/src/server/index.ts b/src/server/index.ts index 83aa0c728..7f98a24fc 100644 --- a/src/server/index.ts +++ b/src/server/index.ts @@ -8,7 +8,7 @@ import Koa from 'koa'; -import { addApiToServer, createApiServer } from './api'; +import { createRouter, configureApp } from './api'; import { DBFromEnv } from './db'; import { ProcessGameConfig } from '../core/game'; import * as logger from '../core/logger'; @@ -65,6 +65,7 @@ interface ServerOpts { authenticateCredentials?: ServerTypes.AuthenticateCredentials; generateCredentials?: ServerTypes.GenerateCredentials; https?: HttpsOptions; + lobbyConfig?: ServerTypes.LobbyConfig; } /** @@ -76,6 +77,7 @@ interface ServerOpts { * @param authenticateCredentials - Function to test player credentials. * @param generateCredentials - Method for API to generate player credentials. * @param https - HTTPS configuration options passed through to the TLS module. + * @param lobbyConfig - Configuration options for the Lobby API server. */ export function Server({ games, @@ -84,6 +86,7 @@ export function Server({ authenticateCredentials, generateCredentials, https, + lobbyConfig, }: ServerOpts) { const app = new Koa(); @@ -106,29 +109,28 @@ export function Server({ } transport.init(app, games); + const router = createRouter({ db, games, lobbyConfig, generateCredentials }); + return { app, db, + router, - run: async (portOrConfig: number | object, callback?: () => void) => { + run: async (portOrConfig: number | ServerConfig, callback?: () => void) => { const serverRunConfig = createServerRunConfig(portOrConfig, callback); // DB await db.connect(); // Lobby API - const lobbyConfig: ServerTypes.LobbyConfig = serverRunConfig.lobbyConfig; + const lobbyConfig = serverRunConfig.lobbyConfig; let apiServer: KoaServer | undefined; if (!lobbyConfig || !lobbyConfig.apiPort) { - addApiToServer({ app, db, games, lobbyConfig, generateCredentials }); + configureApp(app, router); } else { // Run API in a separate Koa app. - const api = createApiServer({ - db, - games, - lobbyConfig, - generateCredentials, - }); + const api = new Koa(); + configureApp(api, router); await new Promise(resolve => { apiServer = api.listen(lobbyConfig.apiPort, resolve); }); diff --git a/src/types.ts b/src/types.ts index d8badcfd6..2a80eff3a 100644 --- a/src/types.ts +++ b/src/types.ts @@ -288,8 +288,6 @@ export namespace Server { export interface LobbyConfig { uuid?: () => string; generateCredentials?: GenerateCredentials; - apiPort?: number; - apiCallback?: () => void; } } From 6b431620a07517f457d57b118a6bc2ee50f15a52 Mon Sep 17 00:00:00 2001 From: delucis Date: Sun, 17 May 2020 13:06:03 +0200 Subject: [PATCH 2/5] refactor(server): Pass uuid directly to Server and createRouter Instead of passing a lobbyConfig object which may contain a uuid method and potentially also a generateCredentials method, this moves those props to the top-level options, simplifying some specificity chains. --- src/server/api.test.ts | 25 +++++++++--------- src/server/api.ts | 57 ++++++++++++++++++++++-------------------- src/server/index.ts | 6 ++--- src/types.ts | 5 ---- 4 files changed, 45 insertions(+), 48 deletions(-) diff --git a/src/server/api.test.ts b/src/server/api.test.ts index a8e740993..9123699c6 100644 --- a/src/server/api.test.ts +++ b/src/server/api.test.ts @@ -62,13 +62,17 @@ class AsyncStorage extends StorageAPI.Async { return this.mocks.listGames(...args); } } + describe('.createRouter', () => { - function addApiToServer({ app, ...args }) { - const router = createRouter(args as any); + function addApiToServer({ + app, + ...args + }: { app: Koa } & Parameters[0]) { + const router = createRouter(args); configureApp(app, router); } - function createApiServer(args) { + function createApiServer(args: Parameters[0]) { const app = new Koa(); addApiToServer({ app, ...args }); return app; @@ -100,8 +104,7 @@ describe('.createRouter', () => { delete process.env.API_SECRET; const uuid = () => 'gameID'; - const lobbyConfig = { uuid }; - app = createApiServer({ db, games, lobbyConfig }); + app = createApiServer({ db, games, uuid }); response = await request(app.callback()) .post('/games/foo/create') @@ -316,9 +319,7 @@ describe('.createRouter', () => { const app = createApiServer({ db, games, - lobbyConfig: { - uuid: () => 'gameID', - }, + uuid: () => 'gameID', generateCredentials: () => credentials, }); response = await request(app.callback()) @@ -1037,10 +1038,8 @@ describe('.createRouter', () => { }); test('creates new game data', async () => { - const lobbyConfig = { - uuid: () => 'newGameID', - }; - const app = createApiServer({ db, games, lobbyConfig }); + const uuid = () => 'newGameID'; + const app = createApiServer({ db, games, uuid }); response = await request(app.callback()) .post('/games/foo/1/playAgain') .send('playerID=0&credentials=SECRET1&numPlayers=4'); @@ -1280,7 +1279,7 @@ describe('.createRouter', () => { test('call .use method several times with uuid', async () => { const uuid = () => 'foo'; - addApiToServer({ app: server, db, games, lobbyConfig: { uuid } }); + addApiToServer({ app: server, db, games, uuid }); expect(server.use.mock.calls.length).toBeGreaterThan(1); }); }); diff --git a/src/server/api.ts b/src/server/api.ts index 63c5f252c..378639add 100644 --- a/src/server/api.ts +++ b/src/server/api.ts @@ -9,7 +9,7 @@ import Koa from 'koa'; import Router from 'koa-router'; import koaBody from 'koa-body'; -import { generate as uuid } from 'shortid'; +import { generate as shortid } from 'shortid'; import cors from '@koa/cors'; import { InitializeGame } from '../core/initialize'; @@ -27,14 +27,21 @@ import { Server, Game } from '../types'; * @param {object } lobbyConfig - Configuration options for the lobby. * @param {boolean} unlisted - Whether the game should be excluded from public listing. */ -export const CreateGame = async ( - db: StorageAPI.Sync | StorageAPI.Async, - game: Game, - numPlayers: number, - setupData: any, - lobbyConfig: Server.LobbyConfig, - unlisted: boolean -) => { +export const CreateGame = async ({ + db, + game, + numPlayers, + setupData, + uuid, + unlisted, +}: { + db: StorageAPI.Sync | StorageAPI.Async; + game: Game; + numPlayers: number; + setupData: any; + uuid: () => string; + unlisted: boolean; +}) => { if (!numPlayers || typeof numPlayers !== 'number') numPlayers = 2; const metadata: Server.GameMetadata = { @@ -47,7 +54,7 @@ export const CreateGame = async ( metadata.players[playerIndex] = { id: playerIndex }; } - const gameID = lobbyConfig.uuid(); + const gameID = uuid(); const initialState = InitializeGame({ game, numPlayers, setupData }); await db.createGame(gameID, { metadata, initialState }); @@ -58,20 +65,16 @@ export const CreateGame = async ( export const createRouter = ({ db, games, - lobbyConfig, + uuid, generateCredentials, }: { games: Game[]; - lobbyConfig?: Server.LobbyConfig; + uuid?: () => string; generateCredentials?: Server.GenerateCredentials; db: StorageAPI.Sync | StorageAPI.Async; }): Router => { - if (!lobbyConfig) lobbyConfig = {}; - lobbyConfig = { - ...lobbyConfig, - uuid: lobbyConfig.uuid || uuid, - generateCredentials: generateCredentials || lobbyConfig.uuid || uuid, - }; + uuid = uuid || shortid; + generateCredentials = generateCredentials || uuid; const router = new Router(); router.get('/games', async ctx => { @@ -91,14 +94,14 @@ export const createRouter = ({ const game = games.find(g => g.name === gameName); if (!game) ctx.throw(404, 'Game ' + gameName + ' not found'); - const gameID = await CreateGame( + const gameID = await CreateGame({ db, game, numPlayers, setupData, - lobbyConfig, - unlisted - ); + uuid, + unlisted, + }); ctx.body = { gameID, @@ -177,7 +180,7 @@ export const createRouter = ({ metadata.players[playerID].data = data; } metadata.players[playerID].name = playerName; - const playerCredentials = await lobbyConfig.generateCredentials(ctx); + const playerCredentials = await generateCredentials(ctx); metadata.players[playerID].credentials = playerCredentials; await db.setMetadata(gameID, metadata); @@ -254,14 +257,14 @@ export const createRouter = ({ } const game = games.find(g => g.name === gameName); - const nextRoomID = await CreateGame( + const nextRoomID = await CreateGame({ db, game, numPlayers, setupData, - lobbyConfig, - unlisted - ); + uuid, + unlisted, + }); metadata.nextRoomID = nextRoomID; await db.setMetadata(gameID, metadata); diff --git a/src/server/index.ts b/src/server/index.ts index 7f98a24fc..7f8d87679 100644 --- a/src/server/index.ts +++ b/src/server/index.ts @@ -62,10 +62,10 @@ interface ServerOpts { games: Game[]; db?: StorageAPI.Async | StorageAPI.Sync; transport?; + uuid?: () => string; authenticateCredentials?: ServerTypes.AuthenticateCredentials; generateCredentials?: ServerTypes.GenerateCredentials; https?: HttpsOptions; - lobbyConfig?: ServerTypes.LobbyConfig; } /** @@ -86,7 +86,7 @@ export function Server({ authenticateCredentials, generateCredentials, https, - lobbyConfig, + uuid, }: ServerOpts) { const app = new Koa(); @@ -109,7 +109,7 @@ export function Server({ } transport.init(app, games); - const router = createRouter({ db, games, lobbyConfig, generateCredentials }); + const router = createRouter({ db, games, uuid, generateCredentials }); return { app, diff --git a/src/types.ts b/src/types.ts index 2a80eff3a..616dfdf38 100644 --- a/src/types.ts +++ b/src/types.ts @@ -284,11 +284,6 @@ export namespace Server { nextRoomID?: string; unlisted?: boolean; } - - export interface LobbyConfig { - uuid?: () => string; - generateCredentials?: GenerateCredentials; - } } export namespace CredentialedActionShape { From 08498adf86ea77ebe2f7114d5801cc3666a452bd Mon Sep 17 00:00:00 2001 From: delucis Date: Sun, 17 May 2020 13:53:14 +0200 Subject: [PATCH 3/5] docs(server): Move uuid from lobbyConfig on run to Server options --- docs/documentation/api/Lobby.md | 1 - docs/documentation/api/Server.md | 8 ++++---- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/docs/documentation/api/Lobby.md b/docs/documentation/api/Lobby.md index c3dcd42ee..665fa3466 100644 --- a/docs/documentation/api/Lobby.md +++ b/docs/documentation/api/Lobby.md @@ -44,7 +44,6 @@ Options are: - `apiPort`: If specified, it runs the Lobby API in a separate Koa server on this port. Otherwise, it shares the same Koa server runnning on the default boardgame.io `port`. - `apiCallback`: Called when the Koa server is ready. Only applicable if `apiPort` is specified. -- `uuid`: Function that returns an unique identifier, needed for creating new game ID codes. If not specified, uses [shortid](https://www.npmjs.com/package/shortid). #### Creating a room diff --git a/docs/documentation/api/Server.md b/docs/documentation/api/Server.md index 3c52dae8a..514de523b 100644 --- a/docs/documentation/api/Server.md +++ b/docs/documentation/api/Server.md @@ -24,12 +24,12 @@ A config object with the following options: 3. `transport` (_object_): the transport implementation. If not provided, socket.io is used. - -4. `generateCredentials` (_function_): an optional function that returns player credentials to store in the game metadata and validate against. If not specified, the Lobby’s `uuid` implementation will be used. -5. `authenticateCredentials` (_function_): an optional function that tests if a player’s move is made with the correct credentials when using the default socket.io transport implementation. +4. `uuid` (_function_): an optional function that returns a unique identifier, used to create new game IDs and — if `generateCredentials` is not specified — player credentials. Defaults to [shortid](https://www.npmjs.com/package/shortid). -6. `lobbyConfig` (_object_): configuration options for the Lobby API server. +5. `generateCredentials` (_function_): an optional function that returns player credentials to store in the game metadata and validate against. If not specified, the `uuid` function will be used. + +6. `authenticateCredentials` (_function_): an optional function that tests if a player’s move is made with the correct credentials when using the default socket.io transport implementation. ### Returns From f925bb49fbcfc81193f6d414f2a3e34fbb453599 Mon Sep 17 00:00:00 2001 From: Clemens Wolff Date: Sun, 17 May 2020 12:11:44 -0400 Subject: [PATCH 4/5] Extract ServerInstance class --- src/server/index.ts | 202 ++++++++++++++++++++++++++------------------ src/types.ts | 5 ++ 2 files changed, 125 insertions(+), 82 deletions(-) diff --git a/src/server/index.ts b/src/server/index.ts index 7f98a24fc..6de0c7973 100644 --- a/src/server/index.ts +++ b/src/server/index.ts @@ -7,6 +7,7 @@ */ import Koa from 'koa'; +import Router from 'koa-router'; import { createRouter, configureApp } from './api'; import { DBFromEnv } from './db'; @@ -17,13 +18,12 @@ import { Server as ServerTypes, Game, StorageAPI } from '../types'; export type KoaServer = ReturnType; +type LobbyConfig = ServerTypes.LobbyConfig & ServerTypes.LobbyRuntimeSettings; + interface ServerConfig { port?: number; callback?: () => void; - lobbyConfig?: { - apiPort: number; - apiCallback?: () => void; - }; + lobbyConfig?: LobbyConfig; } interface HttpsOptions { @@ -58,9 +58,12 @@ const getPortFromServer = (server: KoaServer): string | number | null => { return address.port; }; +type DB = StorageAPI.Async | StorageAPI.Sync; +type Servers = { apiServer?: KoaServer; appServer: KoaServer }; + interface ServerOpts { games: Game[]; - db?: StorageAPI.Async | StorageAPI.Sync; + db?: DB; transport?; authenticateCredentials?: ServerTypes.AuthenticateCredentials; generateCredentials?: ServerTypes.GenerateCredentials; @@ -68,6 +71,116 @@ interface ServerOpts { lobbyConfig?: ServerTypes.LobbyConfig; } +class ServerInstance { + private _app: Koa; + private _db: DB; + private _router: Router; + private _games: Game[]; + private _generateCredentials: ServerTypes.GenerateCredentials; + + constructor({ + games, + db, + transport, + authenticateCredentials, + generateCredentials, + https, + lobbyConfig, + }: ServerOpts) { + const app = new Koa(); + + games = games.map(ProcessGameConfig); + + if (db === undefined) { + db = DBFromEnv(); + } + app.context.db = db; + + if (transport === undefined) { + const auth = + typeof authenticateCredentials === 'function' + ? authenticateCredentials + : true; + transport = new SocketIO({ + auth, + https, + }); + } + transport.init(app, games); + + this._games = games; + this._generateCredentials = generateCredentials; + this._app = app; + this._db = db; + this.setRouter(lobbyConfig); + } + + private setRouter(lobbyConfig: ServerTypes.LobbyConfig): void { + this._router = createRouter({ + db: this._db, + games: this._games, + lobbyConfig, + generateCredentials: this._generateCredentials, + }); + } + + get app(): Koa { + return this._app; + } + + get db(): DB { + return this._db; + } + + get router(): Router { + return this._router; + } + + async run( + portOrConfig: number | ServerConfig, + callback?: () => void + ): Promise { + const serverRunConfig = createServerRunConfig(portOrConfig, callback); + + // DB + await this.db.connect(); + + // Lobby API + const lobbyConfig = serverRunConfig.lobbyConfig; + if (lobbyConfig) this.setRouter(lobbyConfig); + let apiServer: KoaServer | undefined; + if (!lobbyConfig || !lobbyConfig.apiPort) { + configureApp(this.app, this.router); + } else { + // Run API in a separate Koa app. + const api = new Koa(); + configureApp(api, this.router); + await new Promise(resolve => { + apiServer = api.listen(lobbyConfig.apiPort, resolve); + }); + if (lobbyConfig.apiCallback) lobbyConfig.apiCallback(); + logger.info(`API serving on ${getPortFromServer(apiServer)}...`); + } + + // Run Game Server (+ API, if necessary). + let appServer: KoaServer; + await new Promise(resolve => { + appServer = this.app.listen(serverRunConfig.port, resolve); + }); + if (serverRunConfig.callback) serverRunConfig.callback(); + logger.info(`App serving on ${getPortFromServer(appServer)}...`); + + return { apiServer, appServer }; + } + + kill(servers: Servers): void { + if (servers.apiServer) { + servers.apiServer.close(); + } + servers.appServer.close(); + } +} + /** * Instantiate a game server. * @@ -79,81 +192,6 @@ interface ServerOpts { * @param https - HTTPS configuration options passed through to the TLS module. * @param lobbyConfig - Configuration options for the Lobby API server. */ -export function Server({ - games, - db, - transport, - authenticateCredentials, - generateCredentials, - https, - lobbyConfig, -}: ServerOpts) { - const app = new Koa(); - - games = games.map(ProcessGameConfig); - - if (db === undefined) { - db = DBFromEnv(); - } - app.context.db = db; - - if (transport === undefined) { - const auth = - typeof authenticateCredentials === 'function' - ? authenticateCredentials - : true; - transport = new SocketIO({ - auth, - https, - }); - } - transport.init(app, games); - - const router = createRouter({ db, games, lobbyConfig, generateCredentials }); - - return { - app, - db, - router, - - run: async (portOrConfig: number | ServerConfig, callback?: () => void) => { - const serverRunConfig = createServerRunConfig(portOrConfig, callback); - - // DB - await db.connect(); - - // Lobby API - const lobbyConfig = serverRunConfig.lobbyConfig; - let apiServer: KoaServer | undefined; - if (!lobbyConfig || !lobbyConfig.apiPort) { - configureApp(app, router); - } else { - // Run API in a separate Koa app. - const api = new Koa(); - configureApp(api, router); - await new Promise(resolve => { - apiServer = api.listen(lobbyConfig.apiPort, resolve); - }); - if (lobbyConfig.apiCallback) lobbyConfig.apiCallback(); - logger.info(`API serving on ${getPortFromServer(apiServer)}...`); - } - - // Run Game Server (+ API, if necessary). - let appServer: KoaServer; - await new Promise(resolve => { - appServer = app.listen(serverRunConfig.port, resolve); - }); - if (serverRunConfig.callback) serverRunConfig.callback(); - logger.info(`App serving on ${getPortFromServer(appServer)}...`); - - return { apiServer, appServer }; - }, - - kill: (servers: { apiServer?: KoaServer; appServer: KoaServer }) => { - if (servers.apiServer) { - servers.apiServer.close(); - } - servers.appServer.close(); - }, - }; +export function Server(opts: ServerOpts): ServerInstance { + return new ServerInstance(opts); } diff --git a/src/types.ts b/src/types.ts index 2a80eff3a..219e66d69 100644 --- a/src/types.ts +++ b/src/types.ts @@ -289,6 +289,11 @@ export namespace Server { uuid?: () => string; generateCredentials?: GenerateCredentials; } + + export interface LobbyRuntimeSettings { + apiPort?: number; + apiCallback?: () => void; + } } export namespace CredentialedActionShape { From 21fb9e1ef8949d99ac8750282dcdd739d265a9f4 Mon Sep 17 00:00:00 2001 From: delucis Date: Sun, 17 May 2020 20:25:09 +0200 Subject: [PATCH 5/5] revert: Extract ServerInstance class This reverts commit f925bb49fbcfc81193f6d414f2a3e34fbb453599. --- src/server/index.ts | 202 ++++++++++++++++++-------------------------- src/types.ts | 5 -- 2 files changed, 82 insertions(+), 125 deletions(-) diff --git a/src/server/index.ts b/src/server/index.ts index 6de0c7973..7f98a24fc 100644 --- a/src/server/index.ts +++ b/src/server/index.ts @@ -7,7 +7,6 @@ */ import Koa from 'koa'; -import Router from 'koa-router'; import { createRouter, configureApp } from './api'; import { DBFromEnv } from './db'; @@ -18,12 +17,13 @@ import { Server as ServerTypes, Game, StorageAPI } from '../types'; export type KoaServer = ReturnType; -type LobbyConfig = ServerTypes.LobbyConfig & ServerTypes.LobbyRuntimeSettings; - interface ServerConfig { port?: number; callback?: () => void; - lobbyConfig?: LobbyConfig; + lobbyConfig?: { + apiPort: number; + apiCallback?: () => void; + }; } interface HttpsOptions { @@ -58,12 +58,9 @@ const getPortFromServer = (server: KoaServer): string | number | null => { return address.port; }; -type DB = StorageAPI.Async | StorageAPI.Sync; -type Servers = { apiServer?: KoaServer; appServer: KoaServer }; - interface ServerOpts { games: Game[]; - db?: DB; + db?: StorageAPI.Async | StorageAPI.Sync; transport?; authenticateCredentials?: ServerTypes.AuthenticateCredentials; generateCredentials?: ServerTypes.GenerateCredentials; @@ -71,116 +68,6 @@ interface ServerOpts { lobbyConfig?: ServerTypes.LobbyConfig; } -class ServerInstance { - private _app: Koa; - private _db: DB; - private _router: Router; - private _games: Game[]; - private _generateCredentials: ServerTypes.GenerateCredentials; - - constructor({ - games, - db, - transport, - authenticateCredentials, - generateCredentials, - https, - lobbyConfig, - }: ServerOpts) { - const app = new Koa(); - - games = games.map(ProcessGameConfig); - - if (db === undefined) { - db = DBFromEnv(); - } - app.context.db = db; - - if (transport === undefined) { - const auth = - typeof authenticateCredentials === 'function' - ? authenticateCredentials - : true; - transport = new SocketIO({ - auth, - https, - }); - } - transport.init(app, games); - - this._games = games; - this._generateCredentials = generateCredentials; - this._app = app; - this._db = db; - this.setRouter(lobbyConfig); - } - - private setRouter(lobbyConfig: ServerTypes.LobbyConfig): void { - this._router = createRouter({ - db: this._db, - games: this._games, - lobbyConfig, - generateCredentials: this._generateCredentials, - }); - } - - get app(): Koa { - return this._app; - } - - get db(): DB { - return this._db; - } - - get router(): Router { - return this._router; - } - - async run( - portOrConfig: number | ServerConfig, - callback?: () => void - ): Promise { - const serverRunConfig = createServerRunConfig(portOrConfig, callback); - - // DB - await this.db.connect(); - - // Lobby API - const lobbyConfig = serverRunConfig.lobbyConfig; - if (lobbyConfig) this.setRouter(lobbyConfig); - let apiServer: KoaServer | undefined; - if (!lobbyConfig || !lobbyConfig.apiPort) { - configureApp(this.app, this.router); - } else { - // Run API in a separate Koa app. - const api = new Koa(); - configureApp(api, this.router); - await new Promise(resolve => { - apiServer = api.listen(lobbyConfig.apiPort, resolve); - }); - if (lobbyConfig.apiCallback) lobbyConfig.apiCallback(); - logger.info(`API serving on ${getPortFromServer(apiServer)}...`); - } - - // Run Game Server (+ API, if necessary). - let appServer: KoaServer; - await new Promise(resolve => { - appServer = this.app.listen(serverRunConfig.port, resolve); - }); - if (serverRunConfig.callback) serverRunConfig.callback(); - logger.info(`App serving on ${getPortFromServer(appServer)}...`); - - return { apiServer, appServer }; - } - - kill(servers: Servers): void { - if (servers.apiServer) { - servers.apiServer.close(); - } - servers.appServer.close(); - } -} - /** * Instantiate a game server. * @@ -192,6 +79,81 @@ class ServerInstance { * @param https - HTTPS configuration options passed through to the TLS module. * @param lobbyConfig - Configuration options for the Lobby API server. */ -export function Server(opts: ServerOpts): ServerInstance { - return new ServerInstance(opts); +export function Server({ + games, + db, + transport, + authenticateCredentials, + generateCredentials, + https, + lobbyConfig, +}: ServerOpts) { + const app = new Koa(); + + games = games.map(ProcessGameConfig); + + if (db === undefined) { + db = DBFromEnv(); + } + app.context.db = db; + + if (transport === undefined) { + const auth = + typeof authenticateCredentials === 'function' + ? authenticateCredentials + : true; + transport = new SocketIO({ + auth, + https, + }); + } + transport.init(app, games); + + const router = createRouter({ db, games, lobbyConfig, generateCredentials }); + + return { + app, + db, + router, + + run: async (portOrConfig: number | ServerConfig, callback?: () => void) => { + const serverRunConfig = createServerRunConfig(portOrConfig, callback); + + // DB + await db.connect(); + + // Lobby API + const lobbyConfig = serverRunConfig.lobbyConfig; + let apiServer: KoaServer | undefined; + if (!lobbyConfig || !lobbyConfig.apiPort) { + configureApp(app, router); + } else { + // Run API in a separate Koa app. + const api = new Koa(); + configureApp(api, router); + await new Promise(resolve => { + apiServer = api.listen(lobbyConfig.apiPort, resolve); + }); + if (lobbyConfig.apiCallback) lobbyConfig.apiCallback(); + logger.info(`API serving on ${getPortFromServer(apiServer)}...`); + } + + // Run Game Server (+ API, if necessary). + let appServer: KoaServer; + await new Promise(resolve => { + appServer = app.listen(serverRunConfig.port, resolve); + }); + if (serverRunConfig.callback) serverRunConfig.callback(); + logger.info(`App serving on ${getPortFromServer(appServer)}...`); + + return { apiServer, appServer }; + }, + + kill: (servers: { apiServer?: KoaServer; appServer: KoaServer }) => { + if (servers.apiServer) { + servers.apiServer.close(); + } + servers.appServer.close(); + }, + }; } diff --git a/src/types.ts b/src/types.ts index 219e66d69..2a80eff3a 100644 --- a/src/types.ts +++ b/src/types.ts @@ -289,11 +289,6 @@ export namespace Server { uuid?: () => string; generateCredentials?: GenerateCredentials; } - - export interface LobbyRuntimeSettings { - apiPort?: number; - apiCallback?: () => void; - } } export namespace CredentialedActionShape {