summaryrefslogtreecommitdiff
path: root/frontend/src
diff options
context:
space:
mode:
Diffstat (limited to 'frontend/src')
-rw-r--r--frontend/src/api/model.js31
-rw-r--r--frontend/src/api/sevenWondersApi.js46
-rw-r--r--frontend/src/api/websocket.js24
-rw-r--r--frontend/src/sagas/errors.js17
-rw-r--r--frontend/src/sagas/gameBrowser.js21
-rw-r--r--frontend/src/sagas/home.js22
-rw-r--r--frontend/src/sagas/lobby.js35
-rw-r--r--frontend/src/sagas/utils.js10
8 files changed, 108 insertions, 98 deletions
diff --git a/frontend/src/api/model.js b/frontend/src/api/model.js
index eb7ad9c5..2f9fad37 100644
--- a/frontend/src/api/model.js
+++ b/frontend/src/api/model.js
@@ -1,15 +1,16 @@
-export class ApiError {
+// @flow
+export type ApiError = {
message: string;
details: ApiErrorDetail[];
}
-export class ApiErrorDetail {
+export type ApiErrorDetail = {
message: string;
}
export type ApiGameState = "LOBBY" | "PLAYING";
-export class ApiLobby {
+export type ApiLobby = {
id: number;
name: string;
owner: ApiPlayer;
@@ -20,7 +21,7 @@ export class ApiLobby {
export type ApiWonderSidePickMethod = "EACH_RANDOM" | "ALL_A" | "ALL_B" | "SAME_RANDOM_FOR_ALL";
-export class ApiSettings {
+export type ApiSettings = {
randomSeedForTests: number;
timeLimitInSeconds: number;
wonderSidePickMethod: ApiWonderSidePickMethod;
@@ -32,26 +33,28 @@ export class ApiSettings {
wonPointsPerVictoryPerAge: Map<number, number>;
}
-export class ApiPlayer {
+export type ApiPlayer = {
username: string;
displayName: string;
index: number;
ready: boolean;
}
-export class ApiTable {}
+export type ApiTable = {}
-export class ApiHandCard {}
+export type ApiAction = {}
-export class ApiCard {}
+export type ApiHandCard = {}
-export class ApiPreparedCard {}
+export type ApiCard = {}
-export class ApiPlayerTurnInfo {
+export type ApiPreparedCard = {}
+
+export type ApiPlayerTurnInfo = {
playerIndex: number;
table: ApiTable;
currentAge: number;
- action: Action;
+ action: ApiAction;
hand: ApiHandCard[];
neighbourGuildCards: ApiCard[];
message: string;
@@ -61,15 +64,15 @@ export type ApiMoveType = "PLAY" | "PLAY_FREE" | "UPGRADE_WONDER" | "DISCARD" |
export type ApiProvider = "LEFT_NEIGHBOUR" | "RIGHT_NEIGHBOUR";
export type ApiResourceType = "WOOD" | "STONE" | "ORE" | "CLAY" | "GLASS" | "PAPYRUS" | "LOOM";
-export class ApiResources {
+export type ApiResources = {
quantities: Map<ApiResourceType, number>;
}
-export class ApiBoughtResources {
+export type ApiBoughtResources = {
provider: ApiProvider;
resources: ApiResources;
}
-export class ApiPlayerMove {
+export type ApiPlayerMove = {
type: ApiMoveType;
cardName: string;
boughtResources: ApiBoughtResources[];
diff --git a/frontend/src/api/sevenWondersApi.js b/frontend/src/api/sevenWondersApi.js
index 1004c81e..9a68ec66 100644
--- a/frontend/src/api/sevenWondersApi.js
+++ b/frontend/src/api/sevenWondersApi.js
@@ -1,5 +1,7 @@
-import { createJsonStompClient, JsonStompClient, Callback } from './websocket';
-import { ApiError, ApiLobby, ApiPlayer, ApiPlayerMove, ApiPlayerTurnInfo, ApiPreparedCard, ApiTable } from './model';
+// @flow
+import { createJsonStompClient } from './websocket';
+import type { JsonStompClient, SubscribeFn } from './websocket'
+import type { ApiError, ApiLobby, ApiPlayer, ApiPlayerMove, ApiPlayerTurnInfo, ApiPreparedCard, ApiTable } from './model';
const wsURL = '/seven-wonders-websocket';
@@ -10,32 +12,32 @@ export class SevenWondersSession {
this.client = client;
}
- watchErrors(callback: Callback<ApiError>): void {
- return this.client.subscribe('/user/queue/errors', callback);
+ watchErrors(): SubscribeFn<ApiError> {
+ return this.client.subscriber('/user/queue/errors');
}
chooseName(displayName: string): void {
this.client.send('/app/chooseName', { playerName: displayName });
}
- watchNameChoice(callback: Callback<ApiPlayer>): void {
- return this.client.subscribe('/user/queue/nameChoice', callback);
+ watchNameChoice(): SubscribeFn<ApiPlayer> {
+ return this.client.subscriber('/user/queue/nameChoice');
}
- watchGames(callback: Callback<ApiLobby[]>): void {
- return this.client.subscribe('/topic/games', callback);
+ watchGames(): SubscribeFn<ApiLobby[]> {
+ return this.client.subscriber('/topic/games');
}
- watchLobbyJoined(callback: Callback<Object>): void {
- return this.client.subscribe('/user/queue/lobby/joined', callback);
+ watchLobbyJoined(): SubscribeFn<Object> {
+ return this.client.subscriber('/user/queue/lobby/joined');
}
- watchLobbyUpdated(currentGameId: number, callback: Callback<Object>): void {
- return this.client.subscribe(`/topic/lobby/${currentGameId}/updated`, callback);
+ watchLobbyUpdated(currentGameId: number): SubscribeFn<Object> {
+ return this.client.subscriber(`/topic/lobby/${currentGameId}/updated`);
}
- watchGameStarted(currentGameId: number, callback: Callback<Object>): void {
- return this.client.subscribe(`/topic/lobby/${currentGameId}/started`, callback);
+ watchGameStarted(currentGameId: number): SubscribeFn<Object> {
+ return this.client.subscriber(`/topic/lobby/${currentGameId}/started`);
}
createGame(gameName: string): void {
@@ -50,20 +52,20 @@ export class SevenWondersSession {
this.client.send('/app/lobby/startGame');
}
- watchPlayerReady(currentGameId: number, callback: Callback<string>): void {
- return this.client.subscribe(`/topic/game/${currentGameId}/playerReady`, callback);
+ watchPlayerReady(currentGameId: number): SubscribeFn<string> {
+ return this.client.subscriber(`/topic/game/${currentGameId}/playerReady`);
}
- watchTableUpdates(currentGameId: number, callback: Callback<ApiTable>): void {
- return this.client.subscribe(`/topic/game/${currentGameId}/tableUpdates`, callback);
+ watchTableUpdates(currentGameId: number): SubscribeFn<ApiTable> {
+ return this.client.subscriber(`/topic/game/${currentGameId}/tableUpdates`);
}
- watchPreparedMove(currentGameId: number, callback: Callback<ApiPreparedCard>): void {
- return this.client.subscribe(`/topic/game/${currentGameId}/prepared`, callback);
+ watchPreparedMove(currentGameId: number): SubscribeFn<ApiPreparedCard> {
+ return this.client.subscriber(`/topic/game/${currentGameId}/prepared`);
}
- watchTurnInfo(callback: Callback<ApiPlayerTurnInfo>): void {
- return this.client.subscribe('/user/queue/game/turnInfo', callback);
+ watchTurnInfo(): SubscribeFn<ApiPlayerTurnInfo> {
+ return this.client.subscriber('/user/queue/game/turnInfo');
}
sayReady(): void {
diff --git a/frontend/src/api/websocket.js b/frontend/src/api/websocket.js
index d411587a..8a893a87 100644
--- a/frontend/src/api/websocket.js
+++ b/frontend/src/api/websocket.js
@@ -7,9 +7,9 @@ const DEFAULT_DEBUG_OPTIONS = {
debug: process.env.NODE_ENV !== 'production',
};
-export type Callback<T> = (value: T) => void;
-
-export type Callable = () => void;
+export type Callback<T> = (value?: T) => void;
+export type UnsubscribeFn = () => void;
+export type SubscribeFn<T> = (callback: Callback<T>) => UnsubscribeFn;
export class JsonStompClient {
client: Client;
@@ -24,16 +24,28 @@ export class JsonStompClient {
});
}
- subscribe<T>(path: string, callback: Callback<T>): Callable {
+ subscribe<T>(path: string, callback: Callback<T>): UnsubscribeFn {
const socketSubscription: Subscription = this.client.subscribe(path, (frame: Frame) => {
// not all frames have a JSON body
- const value = frame && frame.body && JSON.parse(frame.body);
+ const value: T = frame && JsonStompClient.parseBody(frame);
callback(value);
});
return () => socketSubscription.unsubscribe();
}
- send(url: string, body: Object) {
+ static parseBody<T>(frame: Frame): T | void {
+ try {
+ return frame.body && JSON.parse(frame.body);
+ } catch (jsonParseError) {
+ throw new Error('Cannot parse websocket message as JSON: ' + jsonParseError.message);
+ }
+ }
+
+ subscriber<T>(path: string): SubscribeFn<T> {
+ return (callback: Callback<T>) => this.subscribe(path, callback);
+ }
+
+ send(url: string, body?: Object) {
const strBody = body ? JSON.stringify(body) : '';
this.client.send(url, strBody);
}
diff --git a/frontend/src/sagas/errors.js b/frontend/src/sagas/errors.js
index 86fa0124..eece98c8 100644
--- a/frontend/src/sagas/errors.js
+++ b/frontend/src/sagas/errors.js
@@ -1,10 +1,13 @@
-import {apply, cancelled, take} from 'redux-saga/effects';
-import {toastr} from 'react-redux-toastr';
-import {ApiError, SevenWondersSession} from '../api/sevenWondersApi';
-import type {Channel} from 'redux-saga';
+// @flow
+import { toastr } from 'react-redux-toastr'
+import type { Channel } from 'redux-saga'
+import { eventChannel } from 'redux-saga'
+import { apply, cancelled, take } from 'redux-saga/effects'
+import type { ApiError } from '../api/model'
+import type { SevenWondersSession } from '../api/sevenWondersApi'
-export default function* errorHandlingSaga(session: SevenWondersSession) {
- const errorChannel: Channel<ApiError> = yield apply(session, session.watchErrors, []);
+export default function* errorHandlingSaga(session: SevenWondersSession): * {
+ const errorChannel: Channel<ApiError> = yield eventChannel(session.watchErrors());
try {
while (true) {
const error: ApiError = yield take(errorChannel);
@@ -18,7 +21,7 @@ export default function* errorHandlingSaga(session: SevenWondersSession) {
}
}
-function* handleOneError(err: ApiError) {
+function* handleOneError(err: ApiError): * {
console.error('Error received on web socket channel', err);
const msg = buildMsg(err);
yield apply(toastr, toastr.error, [msg, { icon: 'error' }]);
diff --git a/frontend/src/sagas/gameBrowser.js b/frontend/src/sagas/gameBrowser.js
index 871908f6..5b815f8d 100644
--- a/frontend/src/sagas/gameBrowser.js
+++ b/frontend/src/sagas/gameBrowser.js
@@ -1,17 +1,16 @@
// @flow
-import { call, put, take, apply } from 'redux-saga/effects';
-import { push } from 'react-router-redux';
+import { normalize } from 'normalizr'
+import { push } from 'react-router-redux'
+import { eventChannel } from 'redux-saga'
+import { apply, call, put, take } from 'redux-saga/effects'
+import type { SevenWondersSession } from '../api/sevenWondersApi'
-import { normalize } from 'normalizr';
-import { game as gameSchema, gameList as gameListSchema } from '../schemas/games';
-
-import { actions as gameActions, types } from '../redux/games';
-import { actions as playerActions } from '../redux/players';
-import type { SevenWondersSession } from '../api/sevenWondersApi';
-import { createChannel } from './utils';
+import { actions as gameActions, types } from '../redux/games'
+import { actions as playerActions } from '../redux/players'
+import { game as gameSchema, gameList as gameListSchema } from '../schemas/games'
function* watchGames(session: SevenWondersSession): * {
- const gamesChannel = yield createChannel(session, session.watchGames);
+ const gamesChannel = yield eventChannel(session.watchGames());
try {
while (true) {
const gameList = yield take(gamesChannel);
@@ -26,7 +25,7 @@ function* watchGames(session: SevenWondersSession): * {
}
function* watchLobbyJoined(session: SevenWondersSession): * {
- const joinedLobbyChannel = yield createChannel(session, session.watchLobbyJoined);
+ const joinedLobbyChannel = yield eventChannel(session.watchLobbyJoined());
try {
const joinedLobby = yield take(joinedLobbyChannel);
const normalized = normalize(joinedLobby, gameSchema);
diff --git a/frontend/src/sagas/home.js b/frontend/src/sagas/home.js
index b51bf4dc..0b30f784 100644
--- a/frontend/src/sagas/home.js
+++ b/frontend/src/sagas/home.js
@@ -1,28 +1,30 @@
-import { apply, call, put, take } from 'redux-saga/effects';
-import { push } from 'react-router-redux';
+// @flow
+import { apply, call, put, take } from 'redux-saga/effects'
+import { push } from 'react-router-redux'
+import { eventChannel } from 'redux-saga'
-import { actions, types } from '../redux/players';
-import type { SevenWondersSession } from '../api/sevenWondersApi';
-import { createChannel } from './utils';
+import { actions, types } from '../redux/players'
+import type { SevenWondersSession } from '../api/sevenWondersApi'
+import type { ApiPlayer } from '../api/model'
-function* sendUsername(session: SevenWondersSession) {
+function* sendUsername(session: SevenWondersSession): * {
while (true) {
const { username } = yield take(types.REQUEST_CHOOSE_USERNAME);
yield apply(session, session.chooseName, [username]);
}
}
-function* validateUsername(session: SevenWondersSession) {
- const usernameChannel = yield createChannel(session, session.watchNameChoice);
+function* validateUsername(session: SevenWondersSession): * {
+ const usernameChannel = yield eventChannel(session.watchNameChoice());
while (true) {
- const user = yield take(usernameChannel);
+ const user: ApiPlayer = yield take(usernameChannel);
yield put(actions.setCurrentPlayer(user));
yield apply(usernameChannel, usernameChannel.close);
yield put(push('/games'));
}
}
-function* homeSaga(session: SevenWondersSession) {
+function* homeSaga(session: SevenWondersSession): * {
yield [call(sendUsername, session), call(validateUsername, session)];
}
diff --git a/frontend/src/sagas/lobby.js b/frontend/src/sagas/lobby.js
index cc704086..93e0960f 100644
--- a/frontend/src/sagas/lobby.js
+++ b/frontend/src/sagas/lobby.js
@@ -1,24 +1,23 @@
-import { call, put, take, apply } from 'redux-saga/effects';
-import type { Channel } from 'redux-saga';
-
-import { push } from 'react-router-redux';
-
-import { normalize } from 'normalizr';
-import { game as gameSchema } from '../schemas/games';
-
-import { actions as gameActions, types } from '../redux/games';
-import { actions as playerActions } from '../redux/players';
-import { SevenWondersSession } from '../api/sevenWondersApi';
-import { createChannel } from './utils';
+// @flow
+import { normalize } from 'normalizr'
+import { push } from 'react-router-redux'
+import type { Channel } from 'redux-saga'
+import { eventChannel } from 'redux-saga'
+import { apply, call, put, take } from 'redux-saga/effects'
+import { SevenWondersSession } from '../api/sevenWondersApi'
+import { actions as gameActions, types } from '../redux/games'
+import { actions as playerActions } from '../redux/players'
+
+import { game as gameSchema } from '../schemas/games'
function getCurrentGameId(): number {
const path = window.location.pathname;
return path.split('lobby/')[1];
}
-function* watchLobbyUpdates(session: SevenWondersSession) {
+function* watchLobbyUpdates(session: SevenWondersSession): * {
const currentGameId: number = getCurrentGameId();
- const lobbyUpdatesChannel: Channel = yield createChannel(session, session.watchLobbyUpdated, currentGameId);
+ const lobbyUpdatesChannel: Channel = yield eventChannel(session.watchLobbyUpdated(currentGameId));
try {
while (true) {
const lobby = yield take(lobbyUpdatesChannel);
@@ -31,9 +30,9 @@ function* watchLobbyUpdates(session: SevenWondersSession) {
}
}
-function* watchGameStart(session: SevenWondersSession) {
+function* watchGameStart(session: SevenWondersSession): * {
const currentGameId = getCurrentGameId();
- const gameStartedChannel = yield createChannel(session, session.watchGameStarted, currentGameId);
+ const gameStartedChannel = yield eventChannel(session.watchGameStarted(currentGameId));
try {
yield take(gameStartedChannel);
yield put(gameActions.enterGame());
@@ -43,14 +42,14 @@ function* watchGameStart(session: SevenWondersSession) {
}
}
-function* startGame(session: SevenWondersSession) {
+function* startGame(session: SevenWondersSession): * {
while (true) {
yield take(types.REQUEST_START_GAME);
yield apply(session, session.startGame, []);
}
}
-function* lobbySaga(session: SevenWondersSession) {
+function* lobbySaga(session: SevenWondersSession): * {
yield [call(watchLobbyUpdates, session), call(watchGameStart, session), call(startGame, session)];
}
diff --git a/frontend/src/sagas/utils.js b/frontend/src/sagas/utils.js
deleted file mode 100644
index 28017c87..00000000
--- a/frontend/src/sagas/utils.js
+++ /dev/null
@@ -1,10 +0,0 @@
-import { SevenWondersSession } from '../api/sevenWondersApi';
-import { eventChannel } from 'redux-saga';
-
-type Emitter = (value: any) => void;
-
-export function createChannel(session: SevenWondersSession, methodWithCallback: () => void, ...args: Array<any>) {
- return eventChannel((emitter: Emitter) => {
- return methodWithCallback.call(session, ...args, emitter);
- });
-}
bgstack15