Get to the point of working proof-of-work.
parent
9ab0432a5b
commit
adfcfb0ae0
|
@ -103,6 +103,7 @@
|
|||
"style-loader": "^0.21.0",
|
||||
"stylus": "^0.54.5",
|
||||
"stylus-loader": "^3.0.2",
|
||||
"typeface-changa": "^0.0.56",
|
||||
"uglifyjs-webpack-plugin": "^1.2.5",
|
||||
"url-loader": "^1.0.1",
|
||||
"webfontloader": "^1.6.28",
|
||||
|
|
|
@ -4,10 +4,16 @@ import { Provider } from 'react-redux';
|
|||
import store from './redux';
|
||||
|
||||
import MessageBoxes from './containers/MessageBoxes';
|
||||
import ServerTableWrapper from './components/ServerTableWrapper';
|
||||
|
||||
import style from './App.styl';
|
||||
|
||||
const App = () => (
|
||||
<Provider store={store}>
|
||||
<div className={style.app}>
|
||||
<MessageBoxes />
|
||||
<ServerTableWrapper />
|
||||
</div>
|
||||
</Provider>
|
||||
);
|
||||
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
@import '~typeface-changa'
|
||||
|
||||
.app
|
||||
font-family: Changa, sans-serif
|
||||
font-size: 16px
|
|
@ -0,0 +1,54 @@
|
|||
import React from 'react';
|
||||
|
||||
export default class AnimatedEllipsis extends React.Component {
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.state = {
|
||||
dots: 0,
|
||||
};
|
||||
|
||||
this.timer = null;
|
||||
|
||||
this.componentDidMount = this.componentDidMount.bind(this);
|
||||
this.componentWillUnmount = this.componentWillUnmount.bind(this);
|
||||
this.startDotTimer = this.startDotTimer.bind(this);
|
||||
this.stopDotTimer = this.stopDotTimer.bind(this);
|
||||
this.incrementDots = this.incrementDots.bind(this);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.startDotTimer();
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.stopDotTimer();
|
||||
}
|
||||
|
||||
startDotTimer() {
|
||||
if (this.timer === null) {
|
||||
this.timer = setInterval(this.incrementDots, 250);
|
||||
}
|
||||
}
|
||||
|
||||
stopDotTimer() {
|
||||
if (this.timer !== null) {
|
||||
clearInterval(this.timer);
|
||||
this.timer = null;
|
||||
}
|
||||
}
|
||||
|
||||
incrementDots() {
|
||||
this.setState(state => ({
|
||||
dots: (state.dots + 1) % 4,
|
||||
}));
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<span>
|
||||
{'.'.repeat(this.state.dots)}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
import React from 'react';
|
||||
|
||||
import style from './ServerTable.styl';
|
||||
|
||||
const ServerTable = ({ servers }) => (
|
||||
<table className={style.serverTable}>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Server Name</th>
|
||||
<th>Variant</th>
|
||||
<th>Map</th>
|
||||
<th>Players</th>
|
||||
<th>Server Address</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{
|
||||
servers.map(server => (
|
||||
<tr key={`${server.ip}:${server.port}`}>
|
||||
<td>{server.name}</td>
|
||||
<td>{server.variant}<br /><small>{server.variantType}</small></td>
|
||||
<td>{server.map}</td>
|
||||
<td>{server.numPlayers} / {server.maxPlayers}</td>
|
||||
<td>{server.ip}:{server.port}</td>
|
||||
</tr>
|
||||
))
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
);
|
||||
|
||||
export default ServerTable;
|
|
@ -0,0 +1,2 @@
|
|||
.serverTable
|
||||
width: 100%
|
|
@ -0,0 +1,27 @@
|
|||
import React from 'react';
|
||||
|
||||
import style from './ServerTableButtons.styl';
|
||||
|
||||
const ServerTableButtons = ({
|
||||
disableRefreshButton,
|
||||
hideCancelButton,
|
||||
dispatchRefresh,
|
||||
dispatchCancelRefresh,
|
||||
}) => (
|
||||
<div className={style.serverTableButtons}>
|
||||
<button
|
||||
disabled={disableRefreshButton}
|
||||
onClick={dispatchRefresh}
|
||||
>
|
||||
Refresh
|
||||
</button>
|
||||
<button
|
||||
disabled={hideCancelButton}
|
||||
onClick={dispatchCancelRefresh}
|
||||
>
|
||||
Cancel refresh
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
|
||||
export default ServerTableButtons;
|
|
@ -0,0 +1 @@
|
|||
|
|
@ -0,0 +1,29 @@
|
|||
import React from 'react';
|
||||
|
||||
import * as serverlistLoader from '../redux/serverlist_loader';
|
||||
|
||||
import AnimatedEllipsis from './AnimatedEllipsis';
|
||||
|
||||
import style from './ServerTable.styl';
|
||||
|
||||
|
||||
const ServerTableStatus = ({ serverCount, state }) => (
|
||||
<div className={[
|
||||
style.serverTableStatus,
|
||||
state === serverlistLoader.states.LOADING
|
||||
? style.serverTableStatusLoading
|
||||
: style.serverTableStatusIdle,
|
||||
].join(' ')}
|
||||
>
|
||||
<p>
|
||||
{state === serverlistLoader.states.LOADING
|
||||
? <span>Loading<AnimatedEllipsis /></span>
|
||||
: 'Idle'}
|
||||
</p>
|
||||
<p>
|
||||
{serverCount} servers
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
|
||||
export default ServerTableStatus;
|
|
@ -0,0 +1,45 @@
|
|||
import React from 'react';
|
||||
|
||||
import style from './ServerTableWrapper.styl';
|
||||
|
||||
import ServerTable from '../containers/ServerTable';
|
||||
import ServerTableStatus from '../containers/ServerTableStatus';
|
||||
import ServerTableButtons from '../containers/ServerTableButtons';
|
||||
|
||||
export default class ServerTableWrapper extends React.Component {
|
||||
constructor() {
|
||||
super();
|
||||
this.state = {
|
||||
query: undefined,
|
||||
queryText: '',
|
||||
};
|
||||
this.handleQueryChange = this.handleQueryChange.bind(this);
|
||||
}
|
||||
|
||||
handleQueryChange(e) {
|
||||
const queryText = e.target.value;
|
||||
this.setState({
|
||||
queryText,
|
||||
});
|
||||
if (queryText && queryText.length > 0) {
|
||||
this.setState({
|
||||
query: item => item.name.toLowerCase().includes(queryText.toLowerCase()),
|
||||
});
|
||||
} else {
|
||||
this.setState({
|
||||
query: undefined,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div className={style.serverTableWrapper}>
|
||||
<ServerTableStatus />
|
||||
<ServerTableButtons />
|
||||
<input type="text" value={this.state.queryText} onChange={this.handleQueryChange} />
|
||||
<ServerTable filter={this.state.query} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
|
|
@ -0,0 +1,19 @@
|
|||
import { connect } from 'react-redux';
|
||||
|
||||
import { selectors } from '../redux/serverlist';
|
||||
|
||||
import Component from '../components/ServerTable';
|
||||
|
||||
const mapStateToProps = (state, { filter }) => {
|
||||
// TODO - filter
|
||||
const servers = selectors.getServers(state, filter);
|
||||
return ({
|
||||
servers: servers.slice(0, 25),
|
||||
});
|
||||
};
|
||||
|
||||
const ConnectedMessageBoxes = connect(
|
||||
mapStateToProps,
|
||||
)(Component);
|
||||
|
||||
export default ConnectedMessageBoxes;
|
|
@ -0,0 +1,26 @@
|
|||
import { connect } from 'react-redux';
|
||||
|
||||
import * as serverlistLoader from '../redux/serverlist_loader';
|
||||
|
||||
import Component from '../components/ServerTableButtons';
|
||||
|
||||
const mapStateToProps = state => ({
|
||||
hideCancelButton: serverlistLoader.selectors.getState(state) === serverlistLoader.states.IDLE,
|
||||
disableRefreshButton: serverlistLoader.selectors.getState(state) !== serverlistLoader.states.IDLE,
|
||||
});
|
||||
|
||||
const mapDispatchToProps = dispatch => ({
|
||||
dispatchRefresh: () => {
|
||||
dispatch(serverlistLoader.actions.updateGameServers());
|
||||
},
|
||||
dispatchCancelRefresh: () => {
|
||||
dispatch(serverlistLoader.actions.cancelUpdateGameServers());
|
||||
},
|
||||
});
|
||||
|
||||
const ConnectedMessageBoxes = connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps,
|
||||
)(Component);
|
||||
|
||||
export default ConnectedMessageBoxes;
|
|
@ -0,0 +1,17 @@
|
|||
import { connect } from 'react-redux';
|
||||
|
||||
import * as serverlist from '../redux/serverlist';
|
||||
import * as serverlistLoader from '../redux/serverlist_loader';
|
||||
|
||||
import Component from '../components/ServerTableStatus';
|
||||
|
||||
const mapStateToProps = state => ({
|
||||
state: serverlistLoader.selectors.getState(state),
|
||||
serverCount: serverlist.selectors.getServers(state).length,
|
||||
});
|
||||
|
||||
const ConnectedMessageBoxes = connect(
|
||||
mapStateToProps,
|
||||
)(Component);
|
||||
|
||||
export default ConnectedMessageBoxes;
|
|
@ -37,9 +37,9 @@ window.addEventListener('load', () => {
|
|||
<App />
|
||||
), rootContainer);
|
||||
|
||||
setTimeout(() => {
|
||||
store.dispatch(serverlist_loader.actions.updateGameServers());
|
||||
}, 500);
|
||||
// setTimeout(() => {
|
||||
// store.dispatch(serverlist_loader.actions.updateGameServers());
|
||||
// }, 15000);
|
||||
|
||||
// setTimeout(() => {
|
||||
// store.dispatch(ui.actions.add({
|
||||
|
|
|
@ -1,7 +1,11 @@
|
|||
import { combineReducers } from 'redux';
|
||||
// import { combineReducers } from 'redux';
|
||||
|
||||
const REDUX_PATH_SEPARATOR = '/';
|
||||
|
||||
function normalizeNamespace(name) {
|
||||
return name.replace(/_(.)/, match => match[1].toUpperCase());
|
||||
}
|
||||
|
||||
export default class ReduxGenerator {
|
||||
constructor(...id) {
|
||||
this._id = id;
|
||||
|
@ -60,11 +64,22 @@ export default class ReduxGenerator {
|
|||
.reduce((current, [
|
||||
name,
|
||||
{
|
||||
params,
|
||||
params = {},
|
||||
reducer,
|
||||
},
|
||||
]) => {
|
||||
const id = this.actionType(name);
|
||||
|
||||
// Validate params, they should all have a validator function as a value
|
||||
if (params) {
|
||||
const keysWithNonFunctionValidator = Object.keys(params)
|
||||
.filter(paramName => typeof (params[paramName]) !== 'function')
|
||||
.map(v => JSON.stringify(v));
|
||||
if (keysWithNonFunctionValidator.length > 0) {
|
||||
throw new Error(`Following params of ${JSON.stringify(id)} have a non-function value as validator: ${keysWithNonFunctionValidator.join(', ')}`);
|
||||
}
|
||||
}
|
||||
|
||||
const target = {
|
||||
...current,
|
||||
actions: {
|
||||
|
@ -74,24 +89,24 @@ export default class ReduxGenerator {
|
|||
const err = Object.entries(payload)
|
||||
.map(([key, value]) => {
|
||||
const validator = params[key];
|
||||
if (!validator) {
|
||||
if (validator === undefined || validator === null) {
|
||||
console.warn(`${id}: extra argument ${JSON.stringify(key)}, ignoring`);
|
||||
return undefined;
|
||||
}
|
||||
let result = validator(value);
|
||||
console.debug(`${id}: verifying ${JSON.stringify(key)} result:`, result);
|
||||
// console.debug(`${id}: verifying ${JSON.stringify(key)} result:`, result);
|
||||
switch (typeof result) {
|
||||
case 'boolean':
|
||||
case 'boolean': // validator returns either true on success or false on error
|
||||
if (!result) {
|
||||
result = new Error(`${JSON.stringify(key)}: invalid value: ${JSON.stringify(value)}`);
|
||||
} else {
|
||||
result = null;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
if (result instanceof Error) {
|
||||
default: // error or string (or something else?)
|
||||
if (result instanceof Error) { // modify message to include key
|
||||
result.message = `${JSON.stringify(key)}: ${result.message}`;
|
||||
} else {
|
||||
} else { // use value as part of error reaosn
|
||||
result = new Error(`${JSON.stringify(key)}: ${result}`);
|
||||
}
|
||||
}
|
||||
|
@ -122,14 +137,14 @@ export default class ReduxGenerator {
|
|||
[name]: (state, { type, payload }) => {
|
||||
// Ignore non-matching actions
|
||||
if (type !== id) {
|
||||
console.debug(`${type}: skipping action-mismatching reducer`, { state, payload });
|
||||
// console.debug(`${type}: skipping action-mismatching reducer`, { state, payload });
|
||||
return state;
|
||||
}
|
||||
|
||||
// Call original reducer with our payload
|
||||
console.debug(`${type}: action-matched reducer called`, { state, payload });
|
||||
// console.debug(`${type}: action-matched reducer called`, { state, payload });
|
||||
const result = reducer(state, payload);
|
||||
console.debug(' =>', result);
|
||||
// console.debug(' =>', result);
|
||||
return result;
|
||||
},
|
||||
};
|
||||
|
@ -142,9 +157,9 @@ export default class ReduxGenerator {
|
|||
reducers: Object.entries(reducers)
|
||||
.reduce((current, [name, reducer]) => ({
|
||||
[name]: (state, ...args) => {
|
||||
console.debug(`${this._id.join('/', `reducer:${name}`)}: called`, { state, args });
|
||||
// console.debug(`${this._id.join('/', `reducer:${name}`)}: called`, { state, args });
|
||||
const result = reducer(state, ...args);
|
||||
console.debug(' =>', result);
|
||||
// console.debug(' =>', result);
|
||||
return result;
|
||||
},
|
||||
}), {}),
|
||||
|
@ -157,23 +172,24 @@ export default class ReduxGenerator {
|
|||
[name]: (state, ...args) => {
|
||||
const namespaceState = this._id
|
||||
.slice(1) // ignore root id
|
||||
.map(normalizeNamespace)
|
||||
.reduce(
|
||||
(currentState, namespaceName) =>
|
||||
currentState[namespaceName], state);
|
||||
console.debug(`${[...this._id, `selector:${name}`].join('/')}: called`, { state: namespaceState, args });
|
||||
// console.debug(`${[...this._id, `selector:${name}`].join('/')}: called`, { state: namespaceState, args });
|
||||
const result = selector(namespaceState, ...args);
|
||||
console.debug(' =>', result);
|
||||
// console.debug(' =>', result);
|
||||
return result;
|
||||
},
|
||||
}), selectors);
|
||||
|
||||
retval.reducer = (state, ...args) => {
|
||||
console.debug(`${this._id.join('/')}: combined reducer called`, { state, args });
|
||||
// console.debug(`${this._id.join('/')}: combined reducer called`, { state, args });
|
||||
|
||||
if (state === undefined) {
|
||||
console.debug(`${this._id.join('/')}: undefined state passed to reducer`, { state, args });
|
||||
// console.debug(`${this._id.join('/')}: undefined state passed to reducer`, { state, args });
|
||||
const result = initialState;
|
||||
console.debug(' =>', result);
|
||||
// console.debug(' =>', result);
|
||||
return result;
|
||||
}
|
||||
|
||||
|
@ -188,7 +204,7 @@ export default class ReduxGenerator {
|
|||
);
|
||||
}
|
||||
|
||||
console.debug(`${this._id.join('/')}: no reducers for this namespace, skipping`);
|
||||
// console.debug(`${this._id.join('/')}: no reducers for this namespace, skipping`);
|
||||
return state;
|
||||
};
|
||||
|
||||
|
@ -196,7 +212,7 @@ export default class ReduxGenerator {
|
|||
// retval.initialState = initialState;
|
||||
// }
|
||||
|
||||
console.debug('generated redux module:', retval);
|
||||
// console.debug('generated redux module:', retval);
|
||||
return retval;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,7 +7,7 @@ import {
|
|||
import createSagaMiddleware from 'redux-saga';
|
||||
import {
|
||||
all,
|
||||
fork,
|
||||
// fork,
|
||||
} from 'redux-saga/effects';
|
||||
|
||||
import * as serverlist from './serverlist';
|
||||
|
@ -22,7 +22,7 @@ const reduxModules = Object.entries({
|
|||
mastersync,
|
||||
});
|
||||
|
||||
console.debug('all redux modules', reduxModules);
|
||||
// console.debug('all redux modules', reduxModules);
|
||||
|
||||
const reducers =
|
||||
reduxModules
|
||||
|
@ -33,7 +33,7 @@ const reducers =
|
|||
[name]: m.reducer,
|
||||
};
|
||||
}, {});
|
||||
console.debug('combined reducers:', reducers);
|
||||
// console.debug('combined reducers:', reducers);
|
||||
|
||||
const reducer = combineReducers(reducers);
|
||||
|
||||
|
@ -50,10 +50,10 @@ const reducer = combineReducers(reducers);
|
|||
|
||||
// create the saga middleware
|
||||
function* rootSaga() {
|
||||
const sagas = Object.values(reduxModules)
|
||||
.reduce((current, [name, m]) => {
|
||||
const sagas = Object.values(reduxModules) // TODO - !?
|
||||
.reduce((current, [, m]) => {
|
||||
if (!m.sagas) {
|
||||
console.debug(`Module ${name} does not have any sagas`);
|
||||
// console.debug(`Module ${name} does not have any sagas`);
|
||||
return current;
|
||||
}
|
||||
return [
|
||||
|
@ -61,9 +61,9 @@ function* rootSaga() {
|
|||
...m.sagas,
|
||||
];
|
||||
}, []);
|
||||
console.debug('Will run these sagas from root:', sagas);
|
||||
// console.debug('Will run these sagas from root:', sagas);
|
||||
yield all(sagas);
|
||||
console.debug('Root saga done');
|
||||
// console.debug('Root saga done');
|
||||
}
|
||||
const sagaMiddleware = createSagaMiddleware();
|
||||
|
||||
|
|
|
@ -18,7 +18,6 @@ export const states = {
|
|||
};
|
||||
|
||||
function* doDewritoJsonUpdateRequest() {
|
||||
console.debug('mastersync doDewritoJsonUpdateRequest saga called');
|
||||
try {
|
||||
const response = yield call(fetch, 'https://raw.githubusercontent.com/ElDewrito/ElDorito/master/dist/mods/dewrito.json');
|
||||
if (response.status !== 200) {
|
||||
|
@ -33,16 +32,15 @@ function* doDewritoJsonUpdateRequest() {
|
|||
err,
|
||||
}));
|
||||
}
|
||||
console.debug('mastersync doDewritoJsonUpdateRequest: done');
|
||||
}
|
||||
|
||||
function* storeMasters({
|
||||
function* storeMasters(x) {
|
||||
const {
|
||||
payload: {
|
||||
err,
|
||||
dewritoJson,
|
||||
},
|
||||
}) {
|
||||
console.debug('mastersync storeMasters saga called');
|
||||
} = x;
|
||||
if (err) {
|
||||
yield put(ui.actions.add({
|
||||
text: `Failed to fetch master server information: ${err.message}`,
|
||||
|
@ -54,7 +52,6 @@ function* storeMasters({
|
|||
masterServers,
|
||||
} = dewritoJson;
|
||||
yield put(myRedux.actions.setMasters({ masters: masterServers }));
|
||||
console.debug('mastersync storeMasters: done');
|
||||
}
|
||||
|
||||
export const initialState = {
|
||||
|
|
|
@ -33,16 +33,16 @@ const myRedux = reduxGenerator.all({
|
|||
variantType: check.maybe.string,
|
||||
teamScores: check.maybe.array.of.number,
|
||||
status: check.string,
|
||||
numPlayers: check.positiveInteger,
|
||||
numPlayers: v => check.integer(v) && (check.zero(v) || check.positive(v)),
|
||||
mods: check.array, // TODO - fine tune this
|
||||
maxPlayers: check.positiveInteger,
|
||||
maxPlayers: v => check.integer(v) && (check.zero(v) || check.positive(v)),
|
||||
passworded: check.boolean,
|
||||
isDedicated: check.boolean,
|
||||
gameVersion: check.string,
|
||||
eldewritoVersion: check.maybe.string,
|
||||
xnkid: check.maybe.string,
|
||||
xnaddr: check.maybe.string,
|
||||
players: check.maybe.array.of.like({
|
||||
players: v => check.maybe.array.of.like(v, {
|
||||
name: '',
|
||||
serviceTag: '',
|
||||
team: 0,
|
||||
|
@ -59,16 +59,19 @@ const myRedux = reduxGenerator.all({
|
|||
bestStreak: 0,
|
||||
}),
|
||||
},
|
||||
|
||||
reducer(current, payload) {
|
||||
// do we have this server on the list already?
|
||||
if (current.servers.find(server => server.ip === payload.ip)) {
|
||||
if (current.servers.find(server =>
|
||||
server.ip === payload.ip
|
||||
&& server.port === payload.port)) {
|
||||
// skip this server
|
||||
return current;
|
||||
}
|
||||
|
||||
// add server
|
||||
return {
|
||||
current,
|
||||
...current,
|
||||
servers: [
|
||||
...current.servers,
|
||||
payload,
|
||||
|
@ -77,6 +80,27 @@ const myRedux = reduxGenerator.all({
|
|||
},
|
||||
},
|
||||
|
||||
addMultiple: {
|
||||
params: {
|
||||
servers: v => !!v, // TODO
|
||||
},
|
||||
reducer(current, { servers }) {
|
||||
return { ...current,
|
||||
servers: servers.reduce((currentServers, server) => {
|
||||
// is this an actual new server?
|
||||
if (!currentServers.find(registeredServer =>
|
||||
server.ip === registeredServer.ip
|
||||
&& server.port === registeredServer.port)) {
|
||||
return [...currentServers, server];
|
||||
}
|
||||
|
||||
// server is not new, return old list
|
||||
return currentServers;
|
||||
}, current.servers),
|
||||
};
|
||||
},
|
||||
},
|
||||
|
||||
reset: {
|
||||
reducer: () => initialState,
|
||||
},
|
||||
|
@ -86,19 +110,22 @@ const myRedux = reduxGenerator.all({
|
|||
selectors: {
|
||||
/*
|
||||
Example:
|
||||
filter(state, {
|
||||
getServers(state, {
|
||||
name: {
|
||||
$in: ["substring"],
|
||||
}
|
||||
gameType: "slayer",
|
||||
})
|
||||
*/
|
||||
filterServers({ servers }, filter, { order } = {}) {
|
||||
let filtered = obop.where(servers, filter);
|
||||
if (order) {
|
||||
filtered = obop.order(filtered, order);
|
||||
getServers({ servers }, filter, { order } = {}) {
|
||||
let finalServers = servers;
|
||||
if (filter) {
|
||||
finalServers = obop.where(finalServers, filter);
|
||||
}
|
||||
return filtered;
|
||||
if (order) {
|
||||
finalServers = obop.order(finalServers, order);
|
||||
}
|
||||
return finalServers;
|
||||
},
|
||||
},
|
||||
});
|
||||
|
|
|
@ -4,8 +4,10 @@ import {
|
|||
call,
|
||||
select,
|
||||
all,
|
||||
race,
|
||||
put,
|
||||
} from 'redux-saga/effects';
|
||||
import { delay } from 'redux-saga';
|
||||
import fetch from 'node-fetch';
|
||||
|
||||
import check from '../check';
|
||||
|
@ -23,6 +25,7 @@ export const states = {
|
|||
|
||||
export const initialState = {
|
||||
state: states.IDLE,
|
||||
bufferedServers: [],
|
||||
};
|
||||
|
||||
function* inspectGameServer(addr) {
|
||||
|
@ -41,21 +44,23 @@ function* inspectGameServer(addr) {
|
|||
throw new Error(`Unexpected HTTP error code ${response.status}: ${response.statusText}`);
|
||||
}
|
||||
const serverInfo = yield call(response.json.bind(response));
|
||||
check.assert.string(serverInfo.name);
|
||||
check.assert(check.port(serverInfo.port));
|
||||
check.assert.maybe.string(serverInfo.hostPlayer);
|
||||
check.assert.maybe.string(serverInfo.map);
|
||||
check.assert.maybe.string(serverInfo.mapFile);
|
||||
check.assert.maybe.string(serverInfo.variant);
|
||||
check.assert.maybe.string(serverInfo.variantType);
|
||||
check.assert.integer(serverInfo.maxPlayers);
|
||||
check.assert.integer(serverInfo.numPlayers);
|
||||
check.assert.inRange(serverInfo.numPlayers, 0, serverInfo.maxPlayers);
|
||||
check.assert.array(serverInfo.players);
|
||||
yield put(serverlist.actions.add({
|
||||
// check.assert.string(serverInfo.name);
|
||||
// check.assert(check.port(serverInfo.port));
|
||||
// check.assert.maybe.string(serverInfo.hostPlayer);
|
||||
// check.assert.maybe.string(serverInfo.map);
|
||||
// check.assert.maybe.string(serverInfo.mapFile);
|
||||
// check.assert.maybe.string(serverInfo.variant);
|
||||
// check.assert.maybe.string(serverInfo.variantType);
|
||||
// check.assert.integer(serverInfo.maxPlayers);
|
||||
// check.assert.integer(serverInfo.numPlayers);
|
||||
// check.assert.inRange(serverInfo.numPlayers, 0, serverInfo.maxPlayers);
|
||||
// check.assert.array(serverInfo.players);
|
||||
yield put(myRedux.actions.updateGameServersBuffer({
|
||||
server: {
|
||||
ip,
|
||||
port,
|
||||
...serverInfo,
|
||||
},
|
||||
}));
|
||||
} catch (e) {
|
||||
console.error(`Request to game server ${addr} failed:`, e);
|
||||
|
@ -83,7 +88,9 @@ function* updateGameServersFromMaster(masterListUrl) {
|
|||
if (code !== 0) {
|
||||
throw new Error(`Server returned error code ${code}: ${msg}`);
|
||||
}
|
||||
yield all(servers.map(addr => call(inspectGameServer, addr)));
|
||||
yield all(servers
|
||||
// .slice(0, 1) // DEBUG
|
||||
.map(addr => call(inspectGameServer, addr)));
|
||||
break;
|
||||
default:
|
||||
throw new Error(`Unsupported list version: ${listVersion}`);
|
||||
|
@ -94,7 +101,7 @@ function* updateGameServersFromMaster(masterListUrl) {
|
|||
}
|
||||
|
||||
function* updateGameServers() {
|
||||
yield put(mastersync.actions.updateDewritoJsonStarted());
|
||||
yield put(myRedux.actions.updateGameServersStarted());
|
||||
|
||||
// First let's ensure we have master servers at all
|
||||
if (!(yield select(mastersync.selectors.hasMasters))) {
|
||||
|
@ -102,7 +109,7 @@ function* updateGameServers() {
|
|||
yield put(mastersync.actions.updateDewritoJson());
|
||||
const { err } = yield take(mastersync.actionTypes.updateDewritoJsonFinished);
|
||||
if (err) {
|
||||
yield put(mastersync.actions.updateDewritoJsonFinished({ err }));
|
||||
yield put(myRedux.actions.updateGameServersFinished({ err }));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
@ -111,15 +118,38 @@ function* updateGameServers() {
|
|||
yield put(serverlist.actions.reset());
|
||||
const masterListUrls = (yield select(mastersync.selectors.getMasters))
|
||||
.map(server => server.list);
|
||||
yield all(masterListUrls
|
||||
.map(url => call(updateGameServersFromMaster, url)));
|
||||
yield put(mastersync.actions.updateDewritoJsonFinished({}));
|
||||
yield race([
|
||||
all(masterListUrls.map(url => call(updateGameServersFromMaster, url))),
|
||||
take(myRedux.actionTypes.cancelUpdateGameServers),
|
||||
]);
|
||||
yield put(myRedux.actions.updateGameServersFinished());
|
||||
}
|
||||
|
||||
function* pushBufferToGameServersList() {
|
||||
// wait 750 milliseconds or until serverlist checks have all finished
|
||||
const [, cancel] = yield race([
|
||||
delay(250),
|
||||
take(myRedux.actionTypes.updateGameServersBuffer),
|
||||
take(myRedux.actionTypes.updateGameServersFinished),
|
||||
]);
|
||||
if (cancel) return;
|
||||
|
||||
const servers = yield select(myRedux.selectors.getBufferedServers);
|
||||
yield put(myRedux.actions.updateGameServersBuffer({
|
||||
clear: true,
|
||||
}));
|
||||
// console.log(`Would have added ${servers.length} new servers`);
|
||||
yield put(serverlist.actions.addMultiple({
|
||||
servers,
|
||||
}));
|
||||
}
|
||||
|
||||
myRedux = root.sub('serverlist_loader').all({
|
||||
initialState,
|
||||
|
||||
actions: {
|
||||
cancelUpdateGameServers: {},
|
||||
|
||||
updateGameServers: {},
|
||||
|
||||
updateGameServersStarted: {
|
||||
|
@ -129,6 +159,22 @@ myRedux = root.sub('serverlist_loader').all({
|
|||
}),
|
||||
},
|
||||
|
||||
updateGameServersBuffer: {
|
||||
params: {
|
||||
server: () => true, // TODO
|
||||
clear: check.maybe.boolean,
|
||||
},
|
||||
reducer: (state, { server, clear }) => ({
|
||||
...state,
|
||||
bufferedServers: clear
|
||||
? []
|
||||
: [
|
||||
...state.bufferedServers,
|
||||
server,
|
||||
],
|
||||
}),
|
||||
},
|
||||
|
||||
updateGameServersFinished: {
|
||||
reducer: state => ({
|
||||
...state,
|
||||
|
@ -140,11 +186,13 @@ myRedux = root.sub('serverlist_loader').all({
|
|||
// selectors are all function(state, ...parameters)
|
||||
selectors: {
|
||||
getState: ({ state }) => state,
|
||||
getBufferedServers: ({ bufferedServers }) => bufferedServers,
|
||||
},
|
||||
});
|
||||
|
||||
export const sagas = [
|
||||
takeEvery(myRedux.actionTypes.updateGameServers, updateGameServers),
|
||||
takeEvery(myRedux.actionTypes.updateGameServersBuffer, pushBufferToGameServersList),
|
||||
];
|
||||
|
||||
export const {
|
||||
|
|
|
@ -64,8 +64,8 @@ export default (options) => {
|
|||
.replace(/\[chunkhash(.*?)\]/g, '[hash$1]');
|
||||
|
||||
const cssOutputFileName = baseOutputFilename
|
||||
.replace(/\[ext(.*?)\]/g, 'css')
|
||||
.replace(/\[chunkhash(.*?)\]/g, '[contenthash$1]');
|
||||
.replace(/\[ext(.*?)\]/g, 'css');
|
||||
// .replace(/\[chunkhash(.*?)\]/g, '[contenthash$1]');
|
||||
// const cssOutputRebasePath = `${slash(path.relative(path.dirname(cssOutputFileName), ''))}/`;
|
||||
const cssOutputRebasePath = '/';
|
||||
|
||||
|
|
|
@ -8383,6 +8383,10 @@ typedarray@^0.0.6:
|
|||
version "0.0.6"
|
||||
resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777"
|
||||
|
||||
typeface-changa@^0.0.56:
|
||||
version "0.0.56"
|
||||
resolved "https://registry.yarnpkg.com/typeface-changa/-/typeface-changa-0.0.56.tgz#df3ea2e8c9835987917bb0c5aad147be184f5d09"
|
||||
|
||||
ua-parser-js@^0.7.9:
|
||||
version "0.7.17"
|
||||
resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.17.tgz#e9ec5f9498b9ec910e7ae3ac626a805c4d09ecac"
|
||||
|
|
Loading…
Reference in New Issue