migreate chat to proper sql tables with relations
This commit is contained in:
parent
493b0106ac
commit
482cfd3fe3
3
API.md
3
API.md
|
@ -17,8 +17,9 @@ All requests are made as JSON encoded array.
|
||||||
|
|
||||||
All chat messages, except the once you send with `chat` or `mcchat`, will be sent to you in the form:
|
All chat messages, except the once you send with `chat` or `mcchat`, will be sent to you in the form:
|
||||||
|
|
||||||
```["msg", name, message, country, channelId]```
|
```["msg", name, message, id, country, channelId]```
|
||||||
channelId is an integer, channel 0 is `en` channel 1 is `int` and maybe more to come.
|
channelId is an integer, channel 0 is `en` channel 1 is `int` and maybe more to come.
|
||||||
|
id is the user id
|
||||||
country is the [two-letter country code](https://www.nationsonline.org/oneworld/country_code_list.htm) in lowercase
|
country is the [two-letter country code](https://www.nationsonline.org/oneworld/country_code_list.htm) in lowercase
|
||||||
### Subscribe to online user counter
|
### Subscribe to online user counter
|
||||||
```["sub", "online"]```
|
```["sub", "online"]```
|
||||||
|
|
7022
package-lock.json
generated
7022
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
108
package.json
108
package.json
|
@ -42,30 +42,30 @@
|
||||||
"hammerjs": "^2.0.8",
|
"hammerjs": "^2.0.8",
|
||||||
"http-proxy-agent": "^4.0.1",
|
"http-proxy-agent": "^4.0.1",
|
||||||
"image-q": "^2.1.2",
|
"image-q": "^2.1.2",
|
||||||
"ip-address": "^6.3.0",
|
"ip-address": "^6.4.0",
|
||||||
"isomorphic-fetch": "^2.2.1",
|
"isomorphic-fetch": "^2.2.1",
|
||||||
"js-file-download": "^0.4.12",
|
"js-file-download": "^0.4.12",
|
||||||
"keycode": "^2.1.9",
|
"keycode": "^2.1.9",
|
||||||
"localforage": "^1.5.0",
|
"localforage": "^1.9.0",
|
||||||
"morgan": "^1.10.0",
|
"morgan": "^1.10.0",
|
||||||
"multer": "^1.4.1",
|
"multer": "^1.4.1",
|
||||||
"mysql2": "^2.1.0",
|
"mysql2": "^2.2.5",
|
||||||
"node-sass": "^4.14.0",
|
"node-sass": "^4.14.1",
|
||||||
"nodemailer": "^6.4.6",
|
"nodemailer": "^6.4.11",
|
||||||
"passport": "^0.4.0",
|
"passport": "^0.4.0",
|
||||||
"passport-discord": "^0.1.2",
|
"passport-discord": "^0.1.4",
|
||||||
"passport-facebook": "^3.0.0",
|
"passport-facebook": "^3.0.0",
|
||||||
"passport-google-oauth": "^2.0.0",
|
"passport-google-oauth": "^2.0.0",
|
||||||
"passport-json": "^1.2.0",
|
"passport-json": "^1.2.0",
|
||||||
"passport-reddit": "^0.2.4",
|
"passport-reddit": "^0.2.4",
|
||||||
"passport-vkontakte": "^0.3.2",
|
"passport-vkontakte": "^0.3.3",
|
||||||
"react": "^16.13.1",
|
"react": "^16.13.1",
|
||||||
"react-dom": "^16.13.1",
|
"react-dom": "^16.13.1",
|
||||||
"react-icons": "^3.10.0",
|
"react-icons": "^3.11.0",
|
||||||
"react-modal": "^3.11.2",
|
"react-modal": "^3.11.2",
|
||||||
"react-redux": "^7.2.0",
|
"react-redux": "^7.2.1",
|
||||||
"react-responsive": "^8.0.3",
|
"react-responsive": "^8.1.0",
|
||||||
"react-stay-scrolled": "^7.0.0",
|
"react-stay-scrolled": "^7.1.0",
|
||||||
"react-toggle-button": "^2.1.0",
|
"react-toggle-button": "^2.1.0",
|
||||||
"redis": "^3.0.2",
|
"redis": "^3.0.2",
|
||||||
"redlock": "^4.0.0",
|
"redlock": "^4.0.0",
|
||||||
|
@ -74,69 +74,69 @@
|
||||||
"redux-persist": "^6.0.0",
|
"redux-persist": "^6.0.0",
|
||||||
"redux-thunk": "^2.2.0",
|
"redux-thunk": "^2.2.0",
|
||||||
"sequelize": "^5.21.7",
|
"sequelize": "^5.21.7",
|
||||||
"sharp": "^0.25.2",
|
"sharp": "^0.26.1",
|
||||||
"startaudiocontext": "^1.2.1",
|
"startaudiocontext": "^1.2.1",
|
||||||
"sweetalert2": "^9.10.12",
|
"sweetalert2": "^9.10.12",
|
||||||
"three": "^0.116.0",
|
"three": "^0.120.1",
|
||||||
"three-trackballcontrols-ts": "^0.2.1",
|
"three-trackballcontrols-ts": "^0.2.2",
|
||||||
"url-search-params-polyfill": "^8.1.0",
|
"url-search-params-polyfill": "^8.1.0",
|
||||||
"winston": "^3.2.1",
|
"winston": "^3.3.3",
|
||||||
"winston-daily-rotate-file": "^4.4.2",
|
"winston-daily-rotate-file": "^4.5.0",
|
||||||
"ws": "^7.2.5"
|
"ws": "^7.3.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/core": "^7.9.6",
|
"@babel/core": "^7.11.6",
|
||||||
"@babel/node": "^7.8.7",
|
"@babel/node": "^7.10.5",
|
||||||
"@babel/plugin-proposal-class-properties": "^7.8.3",
|
"@babel/plugin-proposal-class-properties": "^7.10.4",
|
||||||
"@babel/plugin-proposal-decorators": "^7.8.3",
|
"@babel/plugin-proposal-decorators": "^7.10.5",
|
||||||
"@babel/plugin-proposal-do-expressions": "^7.8.3",
|
"@babel/plugin-proposal-do-expressions": "^7.10.4",
|
||||||
"@babel/plugin-proposal-export-default-from": "^7.8.3",
|
"@babel/plugin-proposal-export-default-from": "^7.10.4",
|
||||||
"@babel/plugin-proposal-export-namespace-from": "^7.8.3",
|
"@babel/plugin-proposal-export-namespace-from": "^7.10.4",
|
||||||
"@babel/plugin-proposal-function-bind": "^7.8.3",
|
"@babel/plugin-proposal-function-bind": "^7.11.5",
|
||||||
"@babel/plugin-proposal-function-sent": "^7.8.3",
|
"@babel/plugin-proposal-function-sent": "^7.10.4",
|
||||||
"@babel/plugin-proposal-json-strings": "^7.8.3",
|
"@babel/plugin-proposal-json-strings": "^7.10.4",
|
||||||
"@babel/plugin-proposal-logical-assignment-operators": "^7.8.3",
|
"@babel/plugin-proposal-logical-assignment-operators": "^7.11.0",
|
||||||
"@babel/plugin-proposal-nullish-coalescing-operator": "^7.8.3",
|
"@babel/plugin-proposal-nullish-coalescing-operator": "^7.10.4",
|
||||||
"@babel/plugin-proposal-numeric-separator": "^7.8.3",
|
"@babel/plugin-proposal-numeric-separator": "^7.10.4",
|
||||||
"@babel/plugin-proposal-object-rest-spread": "^7.9.6",
|
"@babel/plugin-proposal-object-rest-spread": "^7.11.0",
|
||||||
"@babel/plugin-proposal-optional-chaining": "^7.9.0",
|
"@babel/plugin-proposal-optional-chaining": "^7.11.0",
|
||||||
"@babel/plugin-proposal-pipeline-operator": "^7.8.3",
|
"@babel/plugin-proposal-pipeline-operator": "^7.10.5",
|
||||||
"@babel/plugin-proposal-throw-expressions": "^7.8.3",
|
"@babel/plugin-proposal-throw-expressions": "^7.10.4",
|
||||||
"@babel/plugin-syntax-dynamic-import": "^7.8.3",
|
"@babel/plugin-syntax-dynamic-import": "^7.8.3",
|
||||||
"@babel/plugin-syntax-import-meta": "^7.8.3",
|
"@babel/plugin-syntax-import-meta": "^7.10.4",
|
||||||
"@babel/plugin-transform-flow-strip-types": "^7.9.0",
|
"@babel/plugin-transform-flow-strip-types": "^7.10.4",
|
||||||
"@babel/plugin-transform-react-constant-elements": "^7.9.0",
|
"@babel/plugin-transform-react-constant-elements": "^7.10.4",
|
||||||
"@babel/plugin-transform-react-inline-elements": "^7.9.0",
|
"@babel/plugin-transform-react-inline-elements": "^7.10.4",
|
||||||
"@babel/polyfill": "^7.8.7",
|
"@babel/polyfill": "^7.11.5",
|
||||||
"@babel/preset-env": "^7.9.6",
|
"@babel/preset-env": "^7.11.5",
|
||||||
"@babel/preset-flow": "^7.9.0",
|
"@babel/preset-flow": "^7.10.4",
|
||||||
"@babel/preset-react": "^7.9.4",
|
"@babel/preset-react": "^7.10.4",
|
||||||
"@babel/preset-typescript": "^7.9.0",
|
"@babel/preset-typescript": "^7.10.4",
|
||||||
"clean-css": "^4.2.3",
|
|
||||||
"assets-webpack-plugin": "^3.9.12",
|
"assets-webpack-plugin": "^3.9.12",
|
||||||
"babel-eslint": "^10.1.0",
|
"babel-eslint": "^10.1.0",
|
||||||
"babel-loader": "^8.1.0",
|
"babel-loader": "^8.1.0",
|
||||||
"babel-plugin-transform-react-pure-class-to-function": "^1.0.1",
|
"babel-plugin-transform-react-pure-class-to-function": "^1.0.1",
|
||||||
"babel-plugin-transform-react-remove-prop-types": "^0.4.24",
|
"babel-plugin-transform-react-remove-prop-types": "^0.4.24",
|
||||||
|
"clean-css": "^4.2.3",
|
||||||
"css-loader": "^3.5.3",
|
"css-loader": "^3.5.3",
|
||||||
"eslint": "^6.8.0",
|
"eslint": "^6.8.0",
|
||||||
"eslint-config-airbnb": "^18.1.0",
|
"eslint-config-airbnb": "^18.2.0",
|
||||||
"eslint-config-airbnb-base": "^14.1.0",
|
"eslint-config-airbnb-base": "^14.2.0",
|
||||||
"eslint-plugin-flowtype": "^4.7.0",
|
"eslint-plugin-flowtype": "^4.7.0",
|
||||||
"eslint-plugin-import": "^2.20.2",
|
"eslint-plugin-import": "^2.22.1",
|
||||||
"eslint-plugin-jsx-a11y": "^6.2.3",
|
"eslint-plugin-jsx-a11y": "^6.3.1",
|
||||||
"eslint-plugin-react": "^7.19.0",
|
"eslint-plugin-react": "^7.21.2",
|
||||||
"flow-bin": "^0.123.0",
|
"flow-bin": "^0.134.0",
|
||||||
"generate-package-json-webpack-plugin": "^1.0.1",
|
"generate-package-json-webpack-plugin": "^1.1.2",
|
||||||
"json-loader": "^0.5.4",
|
"json-loader": "^0.5.4",
|
||||||
"npm-check": "^5.9.2",
|
"npm-check": "^5.9.2",
|
||||||
"react-hot-loader": "^4.12.21",
|
"react-hot-loader": "^4.13.0",
|
||||||
"react-svg-loader": "^3.0.3",
|
"react-svg-loader": "^3.0.3",
|
||||||
"rimraf": "^3.0.2",
|
"rimraf": "^3.0.2",
|
||||||
"sass-loader": "^8.0.2",
|
"sass-loader": "^8.0.2",
|
||||||
"style-loader": "^1.2.1",
|
"style-loader": "^1.2.1",
|
||||||
"webpack": "^4.43.0",
|
"webpack": "^4.44.2",
|
||||||
"webpack-bundle-analyzer": "^3.7.0",
|
"webpack-bundle-analyzer": "^3.9.0",
|
||||||
"webpack-dev-middleware": "^3.7.1",
|
"webpack-dev-middleware": "^3.7.1",
|
||||||
"webpack-hot-middleware": "^2.18.0",
|
"webpack-hot-middleware": "^2.18.0",
|
||||||
"write-file-webpack-plugin": "^4.0.2"
|
"write-file-webpack-plugin": "^4.0.2"
|
||||||
|
|
|
@ -211,13 +211,6 @@ export function receiveChatMessage(
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function receiveChatHistory(data: Array): Action {
|
|
||||||
return {
|
|
||||||
type: 'RECEIVE_CHAT_HISTORY',
|
|
||||||
data,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
let lastNotify = null;
|
let lastNotify = null;
|
||||||
export function notify(notification: string) {
|
export function notify(notification: string) {
|
||||||
return async (dispatch) => {
|
return async (dispatch) => {
|
||||||
|
@ -233,7 +226,6 @@ export function notify(notification: string) {
|
||||||
}
|
}
|
||||||
|
|
||||||
let pixelTimeout = null;
|
let pixelTimeout = null;
|
||||||
|
|
||||||
export function tryPlacePixel(
|
export function tryPlacePixel(
|
||||||
i: number,
|
i: number,
|
||||||
j: number,
|
j: number,
|
||||||
|
@ -314,7 +306,7 @@ export function receivePixelReturn(
|
||||||
msg = 'You can not access this canvas yet. You need to place more pixels';
|
msg = 'You can not access this canvas yet. You need to place more pixels';
|
||||||
break;
|
break;
|
||||||
case 8:
|
case 8:
|
||||||
errorTitle = 'Oww noo';
|
errorTitle = 'nope';
|
||||||
msg = 'This pixel is protected.';
|
msg = 'This pixel is protected.';
|
||||||
break;
|
break;
|
||||||
case 9:
|
case 9:
|
||||||
|
@ -498,6 +490,7 @@ export function receiveMe(
|
||||||
dailyRanking,
|
dailyRanking,
|
||||||
minecraftname,
|
minecraftname,
|
||||||
canvases,
|
canvases,
|
||||||
|
channels,
|
||||||
userlvl,
|
userlvl,
|
||||||
} = me;
|
} = me;
|
||||||
return {
|
return {
|
||||||
|
@ -511,6 +504,7 @@ export function receiveMe(
|
||||||
dailyRanking,
|
dailyRanking,
|
||||||
minecraftname,
|
minecraftname,
|
||||||
canvases,
|
canvases,
|
||||||
|
channels,
|
||||||
userlvl,
|
userlvl,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -575,7 +569,7 @@ export function fetchStats(): PromiseAction {
|
||||||
|
|
||||||
export function fetchMe(): PromiseAction {
|
export function fetchMe(): PromiseAction {
|
||||||
return async (dispatch) => {
|
return async (dispatch) => {
|
||||||
const response = await fetch('/api/me', {
|
const response = await fetch('api/me', {
|
||||||
credentials: 'include',
|
credentials: 'include',
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -586,6 +580,43 @@ export function fetchMe(): PromiseAction {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function receiveChatHistory(
|
||||||
|
cid: number,
|
||||||
|
history: Array,
|
||||||
|
) {
|
||||||
|
return {
|
||||||
|
type: 'RECEIVE_CHAT_HISTORY',
|
||||||
|
cid,
|
||||||
|
history,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function setChatFetching(fetching: boolean): Action {
|
||||||
|
return {
|
||||||
|
type: 'SET_CHAT_FETCHING',
|
||||||
|
fetching,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function fetchChatMessages(
|
||||||
|
cid: number,
|
||||||
|
): PromiseAction {
|
||||||
|
return async (dispatch) => {
|
||||||
|
dispatch(setChatFetching(true));
|
||||||
|
const response = await fetch(`api/chathistory?cid=${cid}&limit=50`, {
|
||||||
|
credentials: 'include',
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
setTimeout(() => { dispatch(setChatFetching(false)); }, 500);
|
||||||
|
const { history } = await response.json();
|
||||||
|
dispatch(receiveChatHistory(cid, history));
|
||||||
|
} else {
|
||||||
|
setTimeout(() => { dispatch(setChatFetching(false)); }, 5000);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
function setCoolDown(coolDown): Action {
|
function setCoolDown(coolDown): Action {
|
||||||
return {
|
return {
|
||||||
type: 'COOLDOWN_SET',
|
type: 'COOLDOWN_SET',
|
||||||
|
|
|
@ -67,8 +67,9 @@ export type Action =
|
||||||
channel: number,
|
channel: number,
|
||||||
isPing: boolean,
|
isPing: boolean,
|
||||||
}
|
}
|
||||||
| { type: 'RECEIVE_CHAT_HISTORY', data: Array }
|
| { type: 'RECEIVE_CHAT_HISTORY', cid: number, history: Array }
|
||||||
| { type: 'SET_CHAT_CHANNEL', channelId: number }
|
| { type: 'SET_CHAT_CHANNEL', channelId: number }
|
||||||
|
| { type: 'SET_CHAT_FETCHING', fetching: boolean }
|
||||||
| { type: 'RECEIVE_ME',
|
| { type: 'RECEIVE_ME',
|
||||||
name: string,
|
name: string,
|
||||||
waitSeconds: number,
|
waitSeconds: number,
|
||||||
|
@ -80,6 +81,7 @@ export type Action =
|
||||||
dailyRanking: number,
|
dailyRanking: number,
|
||||||
minecraftname: string,
|
minecraftname: string,
|
||||||
canvases: Object,
|
canvases: Object,
|
||||||
|
channels: Object,
|
||||||
userlvl: number,
|
userlvl: number,
|
||||||
}
|
}
|
||||||
| { type: 'RECEIVE_STATS', totalRanking: Object, totalDailyRanking: Object }
|
| { type: 'RECEIVE_STATS', totalRanking: Object, totalDailyRanking: Object }
|
||||||
|
|
|
@ -16,7 +16,6 @@ import {
|
||||||
receiveOnline,
|
receiveOnline,
|
||||||
receiveCoolDown,
|
receiveCoolDown,
|
||||||
receiveChatMessage,
|
receiveChatMessage,
|
||||||
receiveChatHistory,
|
|
||||||
receivePixelReturn,
|
receivePixelReturn,
|
||||||
setMobile,
|
setMobile,
|
||||||
tryPlacePixel,
|
tryPlacePixel,
|
||||||
|
@ -57,9 +56,6 @@ function init() {
|
||||||
const isPing = (nameRegExp && text.match(nameRegExp));
|
const isPing = (nameRegExp && text.match(nameRegExp));
|
||||||
store.dispatch(receiveChatMessage(name, text, country, channelId, isPing));
|
store.dispatch(receiveChatMessage(name, text, country, channelId, isPing));
|
||||||
});
|
});
|
||||||
ProtocolClient.on('chatHistory', (data) => {
|
|
||||||
store.dispatch(receiveChatHistory(data));
|
|
||||||
});
|
|
||||||
ProtocolClient.on('changedMe', () => {
|
ProtocolClient.on('changedMe', () => {
|
||||||
store.dispatch(fetchMe());
|
store.dispatch(fetchMe());
|
||||||
});
|
});
|
||||||
|
|
|
@ -423,7 +423,7 @@ function Admintools({
|
||||||
))}
|
))}
|
||||||
</select>
|
</select>
|
||||||
<br />
|
<br />
|
||||||
<textarea rows="10" cols="100" id="iparea" /><br />
|
<textarea rows="10" cols="17" id="iparea" /><br />
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
|
|
|
@ -11,9 +11,13 @@ import { connect } from 'react-redux';
|
||||||
|
|
||||||
import type { State } from '../reducers';
|
import type { State } from '../reducers';
|
||||||
import ChatMessage from './ChatMessage';
|
import ChatMessage from './ChatMessage';
|
||||||
|
import { MAX_CHAT_MESSAGES } from '../core/constants';
|
||||||
|
|
||||||
import { showUserAreaModal, setChatChannel } from '../actions';
|
import {
|
||||||
import { MAX_CHAT_MESSAGES, CHAT_CHANNELS } from '../core/constants';
|
showUserAreaModal,
|
||||||
|
setChatChannel,
|
||||||
|
fetchChatMessages,
|
||||||
|
} from '../actions';
|
||||||
import ProtocolClient from '../socket/ProtocolClient';
|
import ProtocolClient from '../socket/ProtocolClient';
|
||||||
import { saveSelection, restoreSelection } from '../utils/storeSelection';
|
import { saveSelection, restoreSelection } from '../utils/storeSelection';
|
||||||
import splitChatMessage from '../core/chatMessageFilter';
|
import splitChatMessage from '../core/chatMessageFilter';
|
||||||
|
@ -23,23 +27,30 @@ function escapeRegExp(string) {
|
||||||
}
|
}
|
||||||
|
|
||||||
const Chat = ({
|
const Chat = ({
|
||||||
chatMessages,
|
channels,
|
||||||
|
messages,
|
||||||
chatChannel,
|
chatChannel,
|
||||||
ownName,
|
ownName,
|
||||||
open,
|
open,
|
||||||
setChannel,
|
setChannel,
|
||||||
|
fetchMessages,
|
||||||
|
fetching,
|
||||||
}) => {
|
}) => {
|
||||||
const listRef = useRef();
|
const listRef = useRef();
|
||||||
const inputRef = useRef();
|
const inputRef = useRef();
|
||||||
const [inputMessage, setInputMessage] = useState('');
|
const [inputMessage, setInputMessage] = useState('');
|
||||||
const [selection, setSelection] = useState(null);
|
const [selection, setSelection] = useState(null);
|
||||||
const [nameRegExp, setNameRegExp] = useState(null);
|
const [nameRegExp, setNameRegExp] = useState(null);
|
||||||
|
|
||||||
const { stayScrolled } = useStayScrolled(listRef, {
|
const { stayScrolled } = useStayScrolled(listRef, {
|
||||||
initialScroll: Infinity,
|
initialScroll: Infinity,
|
||||||
inaccuracy: 10,
|
inaccuracy: 10,
|
||||||
});
|
});
|
||||||
|
|
||||||
const channelMessages = chatMessages[chatChannel];
|
const channelMessages = messages[chatChannel] || [];
|
||||||
|
if (!messages[chatChannel] && !fetching) {
|
||||||
|
fetchMessages(chatChannel);
|
||||||
|
}
|
||||||
|
|
||||||
useLayoutEffect(() => {
|
useLayoutEffect(() => {
|
||||||
stayScrolled();
|
stayScrolled();
|
||||||
|
@ -77,6 +88,22 @@ const Chat = ({
|
||||||
setInputMessage('');
|
setInputMessage('');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* if selected channel isn't in channel list anymore
|
||||||
|
* for whatever reason (left faction etc.)
|
||||||
|
* set channel to first available one
|
||||||
|
*/
|
||||||
|
let i = 0;
|
||||||
|
while (i < channels.length) {
|
||||||
|
// eslint-disable-next-line eqeqeq
|
||||||
|
if (channels[i][0] == chatChannel) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
i += 1;
|
||||||
|
}
|
||||||
|
if (i && i === channels.length) {
|
||||||
|
setChannel(channels[0][0]);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={{ display: 'flex', flexDirection: 'column', height: '100%' }}>
|
<div style={{ display: 'flex', flexDirection: 'column', height: '100%' }}>
|
||||||
|
@ -87,6 +114,17 @@ const Chat = ({
|
||||||
onMouseUp={() => { setSelection(saveSelection); }}
|
onMouseUp={() => { setSelection(saveSelection); }}
|
||||||
role="presentation"
|
role="presentation"
|
||||||
>
|
>
|
||||||
|
{
|
||||||
|
(!channelMessages.length)
|
||||||
|
&& (
|
||||||
|
<ChatMessage
|
||||||
|
name="info"
|
||||||
|
msgArray={splitChatMessage('Start chatting here', nameRegExp)}
|
||||||
|
country="xx"
|
||||||
|
insertText={(txt) => padToInputMessage(txt)}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
{
|
{
|
||||||
channelMessages.map((message) => (
|
channelMessages.map((message) => (
|
||||||
<ChatMessage
|
<ChatMessage
|
||||||
|
@ -121,11 +159,20 @@ const Chat = ({
|
||||||
</button>
|
</button>
|
||||||
<select
|
<select
|
||||||
style={{ flexGrow: 0 }}
|
style={{ flexGrow: 0 }}
|
||||||
onChange={(evt) => setChannel(evt.target.selectedIndex)}
|
onChange={(evt) => {
|
||||||
|
const sel = evt.target;
|
||||||
|
setChannel(sel.options[sel.selectedIndex].value);
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
{
|
{
|
||||||
CHAT_CHANNELS.map((ch) => (
|
channels.map((ch) => (
|
||||||
<option selected={ch === chatChannel}>{ch}</option>
|
<option
|
||||||
|
// eslint-disable-next-line eqeqeq
|
||||||
|
selected={ch[0] == chatChannel}
|
||||||
|
value={ch[0]}
|
||||||
|
>
|
||||||
|
{ch[1]}
|
||||||
|
</option>
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
</select>
|
</select>
|
||||||
|
@ -147,9 +194,16 @@ const Chat = ({
|
||||||
};
|
};
|
||||||
|
|
||||||
function mapStateToProps(state: State) {
|
function mapStateToProps(state: State) {
|
||||||
const { chatMessages, name } = state.user;
|
const { name } = state.user;
|
||||||
const { chatChannel } = state.gui;
|
const { chatChannel } = state.gui;
|
||||||
return { chatMessages, chatChannel, ownName: name };
|
const { channels, messages, fetching } = state.chat;
|
||||||
|
return {
|
||||||
|
channels,
|
||||||
|
messages,
|
||||||
|
fetching,
|
||||||
|
chatChannel,
|
||||||
|
ownName: name,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function mapDispatchToProps(dispatch) {
|
function mapDispatchToProps(dispatch) {
|
||||||
|
@ -160,6 +214,9 @@ function mapDispatchToProps(dispatch) {
|
||||||
setChannel(channelId) {
|
setChannel(channelId) {
|
||||||
dispatch(setChatChannel(channelId));
|
dispatch(setChatChannel(channelId));
|
||||||
},
|
},
|
||||||
|
fetchMessages(channelId) {
|
||||||
|
dispatch(fetchChatMessages(channelId));
|
||||||
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
113
src/core/ChatMessageBuffer.js
Normal file
113
src/core/ChatMessageBuffer.js
Normal file
|
@ -0,0 +1,113 @@
|
||||||
|
/*
|
||||||
|
* Buffer for chatMessages for the server
|
||||||
|
* it just buffers the msot recent 200 messages for each channel
|
||||||
|
*
|
||||||
|
* @flow
|
||||||
|
*/
|
||||||
|
import logger from './logger';
|
||||||
|
|
||||||
|
import { RegUser, Message } from '../data/models';
|
||||||
|
|
||||||
|
const MAX_BUFFER_TIME = 120000;
|
||||||
|
|
||||||
|
class ChatMessageBuffer {
|
||||||
|
constructor() {
|
||||||
|
this.buffer = new Map();
|
||||||
|
this.timestamps = new Map();
|
||||||
|
|
||||||
|
this.cleanBuffer = this.cleanBuffer.bind(this);
|
||||||
|
this.cleanLoop = setInterval(this.cleanBuffer, 3 * 60 * 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
async getMessages(cid, limit = 30) {
|
||||||
|
if (limit > 200) {
|
||||||
|
return ChatMessageBuffer.getMessagesFromDatabase(cid, limit);
|
||||||
|
}
|
||||||
|
|
||||||
|
let messages = this.buffer.get(cid);
|
||||||
|
if (!messages) {
|
||||||
|
messages = await ChatMessageBuffer.getMessagesFromDatabase(cid);
|
||||||
|
this.buffer.set(cid, messages);
|
||||||
|
}
|
||||||
|
this.timestamps.set(cid, Date.now());
|
||||||
|
return messages.slice(-limit);
|
||||||
|
}
|
||||||
|
|
||||||
|
cleanBuffer() {
|
||||||
|
const curTime = Date.now();
|
||||||
|
const toDelete = [];
|
||||||
|
this.timestamps.forEach((cid, timestamp) => {
|
||||||
|
if (curTime > timestamp + MAX_BUFFER_TIME) {
|
||||||
|
toDelete.push(cid);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
toDelete.forEach((cid) => {
|
||||||
|
this.buffer.delete(cid);
|
||||||
|
this.timestamps.delete(cid);
|
||||||
|
});
|
||||||
|
logger.info(
|
||||||
|
`Cleaned ${toDelete.length} channels from chat message buffer`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
async addMessage(
|
||||||
|
name,
|
||||||
|
message,
|
||||||
|
cid,
|
||||||
|
uid,
|
||||||
|
flag,
|
||||||
|
) {
|
||||||
|
Message.create({
|
||||||
|
cid,
|
||||||
|
uid,
|
||||||
|
message,
|
||||||
|
});
|
||||||
|
const messages = this.buffer.get(cid);
|
||||||
|
if (messages) {
|
||||||
|
messages.push([
|
||||||
|
name,
|
||||||
|
message,
|
||||||
|
flag,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static async getMessagesFromDatabase(cid, limit = 200) {
|
||||||
|
const messagesModel = await Message.findAll({
|
||||||
|
include: [
|
||||||
|
{
|
||||||
|
model: RegUser,
|
||||||
|
as: 'user',
|
||||||
|
foreignKey: 'uid',
|
||||||
|
attributes: [
|
||||||
|
'name',
|
||||||
|
'flag',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
attributes: [
|
||||||
|
'message',
|
||||||
|
],
|
||||||
|
where: { cid },
|
||||||
|
limit,
|
||||||
|
order: ['createdAt'],
|
||||||
|
raw: true,
|
||||||
|
});
|
||||||
|
const messages = [];
|
||||||
|
messagesModel.forEach((msg) => {
|
||||||
|
const {
|
||||||
|
message,
|
||||||
|
'user.name': name,
|
||||||
|
'user.flag': flag,
|
||||||
|
} = msg;
|
||||||
|
messages.push([
|
||||||
|
name,
|
||||||
|
message,
|
||||||
|
flag,
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
return messages;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ChatMessageBuffer;
|
|
@ -1,25 +1,19 @@
|
||||||
/* @flow */
|
/* @flow */
|
||||||
|
import Sequelize from 'sequelize';
|
||||||
|
|
||||||
import logger from './logger';
|
import logger from './logger';
|
||||||
import redis from '../data/redis';
|
import redis from '../data/redis';
|
||||||
import User from '../data/models/User';
|
import User from '../data/models/User';
|
||||||
import webSockets from '../socket/websockets';
|
import webSockets from '../socket/websockets';
|
||||||
|
import { Channel } from '../data/models';
|
||||||
|
import ChatMessageBuffer from './ChatMessageBuffer';
|
||||||
|
|
||||||
import { CHAT_CHANNELS } from './constants';
|
import { CHAT_CHANNELS } from './constants';
|
||||||
|
|
||||||
export class ChatProvider {
|
export class ChatProvider {
|
||||||
/*
|
|
||||||
* TODO:
|
|
||||||
* history really be saved in redis
|
|
||||||
*/
|
|
||||||
history: Array;
|
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.history = [];
|
this.defaultChannels = [];
|
||||||
for (let i = 0; i < CHAT_CHANNELS.length; i += 1) {
|
this.defaultChannelIds = [];
|
||||||
this.history.push([]);
|
|
||||||
}
|
|
||||||
this.caseCheck = /^[A-Z !.]*$/;
|
this.caseCheck = /^[A-Z !.]*$/;
|
||||||
this.cyrillic = new RegExp('[\u0436-\u043B]');
|
this.cyrillic = new RegExp('[\u0436-\u043B]');
|
||||||
this.filters = [
|
this.filters = [
|
||||||
|
@ -47,27 +41,69 @@ export class ChatProvider {
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
this.mutedCountries = [];
|
this.mutedCountries = [];
|
||||||
|
this.chatMessageBuffer = new ChatMessageBuffer();
|
||||||
}
|
}
|
||||||
|
|
||||||
addMessage(name, message, country, channelId = 0) {
|
async initialize() {
|
||||||
const channelHistory = this.history[channelId];
|
// find or create default channels
|
||||||
if (channelHistory.length > 20) {
|
this.defaultChannels.length = 0;
|
||||||
channelHistory.shift();
|
this.defaultChannelIds.length = 0;
|
||||||
|
for (let i = 0; i < CHAT_CHANNELS.length; i += 1) {
|
||||||
|
const { name } = CHAT_CHANNELS[i];
|
||||||
|
// eslint-disable-next-line no-await-in-loop
|
||||||
|
const channel = await Channel.findOrCreate({
|
||||||
|
attributes: [
|
||||||
|
'id',
|
||||||
|
'lastMessage',
|
||||||
|
],
|
||||||
|
where: { name },
|
||||||
|
defaults: {
|
||||||
|
name,
|
||||||
|
lastMessage: Sequelize.literal('CURRENT_TIMESTAMP'),
|
||||||
|
},
|
||||||
|
raw: true,
|
||||||
|
});
|
||||||
|
const { id } = channel[0];
|
||||||
|
this.defaultChannels.push([
|
||||||
|
id,
|
||||||
|
name,
|
||||||
|
]);
|
||||||
|
this.defaultChannelIds.push(id);
|
||||||
}
|
}
|
||||||
channelHistory.push([name, message, country]);
|
}
|
||||||
|
|
||||||
|
userHasChannelAccess(user, cid, write = false) {
|
||||||
|
if (this.defaultChannelIds.includes(cid)) {
|
||||||
|
if (!write || user.regUser) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
} else if (user.regUser && user.channelIds.includes(cid)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
getHistory(cid, limit = 30) {
|
||||||
|
return this.chatMessageBuffer.getMessages(cid, limit);
|
||||||
}
|
}
|
||||||
|
|
||||||
async sendMessage(user, message, channelId: number = 0) {
|
async sendMessage(user, message, channelId: number = 0) {
|
||||||
const name = (user.regUser) ? user.regUser.name : null;
|
const { id } = user;
|
||||||
const country = user.country || 'xx';
|
const name = user.getName();
|
||||||
|
if (!name || !id) {
|
||||||
|
// eslint-disable-next-line max-len
|
||||||
|
return 'Couldn\'t send your message, pls log out and back in again.';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.userHasChannelAccess(user, channelId)) {
|
||||||
|
return 'You don\'t have access to this channel';
|
||||||
|
}
|
||||||
|
|
||||||
|
const country = user.regUser.flag || 'xx';
|
||||||
let displayCountry = (name.endsWith('berg') || name.endsWith('stein'))
|
let displayCountry = (name.endsWith('berg') || name.endsWith('stein'))
|
||||||
? 'il'
|
? 'il'
|
||||||
: country;
|
: country;
|
||||||
|
|
||||||
if (!name) {
|
|
||||||
// eslint-disable-next-line max-len
|
|
||||||
return 'Couldn\'t send your message, pls log out and back in again.';
|
|
||||||
}
|
|
||||||
if (!user.regUser.verified) {
|
if (!user.regUser.verified) {
|
||||||
return 'Your mail has to be verified in order to chat';
|
return 'Your mail has to be verified in order to chat';
|
||||||
}
|
}
|
||||||
|
@ -166,23 +202,49 @@ export class ChatProvider {
|
||||||
return 'Your country is temporary muted from chat';
|
return 'Your country is temporary muted from chat';
|
||||||
}
|
}
|
||||||
|
|
||||||
this.addMessage(name, message, displayCountry, channelId);
|
this.chatMessageBuffer.addMessage(
|
||||||
webSockets.broadcastChatMessage(name, message, displayCountry, channelId);
|
name,
|
||||||
|
message,
|
||||||
|
channelId,
|
||||||
|
id,
|
||||||
|
displayCountry,
|
||||||
|
);
|
||||||
|
webSockets.broadcastChatMessage(
|
||||||
|
name,
|
||||||
|
message,
|
||||||
|
channelId,
|
||||||
|
id,
|
||||||
|
displayCountry,
|
||||||
|
);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
broadcastChatMessage(
|
broadcastChatMessage(
|
||||||
name,
|
name,
|
||||||
message,
|
message,
|
||||||
|
channelId: number = 1,
|
||||||
|
id = -1,
|
||||||
country: string = 'xx',
|
country: string = 'xx',
|
||||||
channelId: number = 0,
|
|
||||||
sendapi: boolean = true,
|
sendapi: boolean = true,
|
||||||
) {
|
) {
|
||||||
if (message.length > 250) {
|
if (message.length > 250) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.addMessage(name, message, country, channelId);
|
this.chatMessageBuffer.addMessage(
|
||||||
webSockets.broadcastChatMessage(name, message, country, channelId, sendapi);
|
name,
|
||||||
|
message,
|
||||||
|
channelId,
|
||||||
|
id,
|
||||||
|
country,
|
||||||
|
);
|
||||||
|
webSockets.broadcastChatMessage(
|
||||||
|
name,
|
||||||
|
message,
|
||||||
|
channelId,
|
||||||
|
id,
|
||||||
|
country,
|
||||||
|
sendapi,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
static automute(name, channelId = 0) {
|
static automute(name, channelId = 0) {
|
||||||
|
|
|
@ -89,6 +89,13 @@ export const HOUR = 60 * MINUTE;
|
||||||
export const DAY = 24 * HOUR;
|
export const DAY = 24 * HOUR;
|
||||||
export const MONTH = 30 * DAY;
|
export const MONTH = 30 * DAY;
|
||||||
|
|
||||||
// available Chat Channels
|
// available public Chat Channels
|
||||||
export const CHAT_CHANNELS = ['en', 'int'];
|
export const CHAT_CHANNELS = [
|
||||||
|
{
|
||||||
|
name: 'en',
|
||||||
|
}, {
|
||||||
|
name: 'int',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
export const MAX_CHAT_MESSAGES = 100;
|
export const MAX_CHAT_MESSAGES = 100;
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
*/
|
*/
|
||||||
// eslint-disable-next-line import/no-unresolved
|
// eslint-disable-next-line import/no-unresolved
|
||||||
import canvases from './canvases.json';
|
import canvases from './canvases.json';
|
||||||
|
import chatProvider from './ChatProvider';
|
||||||
|
|
||||||
|
|
||||||
export default async function getMe(user) {
|
export default async function getMe(user) {
|
||||||
|
@ -29,7 +30,10 @@ export default async function getMe(user) {
|
||||||
delete userdata.mailVerified;
|
delete userdata.mailVerified;
|
||||||
delete userdata.mcVerified;
|
delete userdata.mcVerified;
|
||||||
|
|
||||||
|
const channels = [...chatProvider.defaultChannels];
|
||||||
|
|
||||||
userdata.canvases = canvases;
|
userdata.canvases = canvases;
|
||||||
|
userdata.channels = channels;
|
||||||
|
|
||||||
return userdata;
|
return userdata;
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,7 +15,7 @@ import { OAuth2Strategy as GoogleStrategy } from 'passport-google-oauth';
|
||||||
import logger from './logger';
|
import logger from './logger';
|
||||||
import { sanitizeName } from '../utils/validation';
|
import { sanitizeName } from '../utils/validation';
|
||||||
|
|
||||||
import { User, RegUser } from '../data/models';
|
import { User, RegUser, Channel } from '../data/models';
|
||||||
import { auth } from './config';
|
import { auth } from './config';
|
||||||
import { compareToHash } from '../utils/hash';
|
import { compareToHash } from '../utils/hash';
|
||||||
import { getIPFromRequest } from '../utils/ip';
|
import { getIPFromRequest } from '../utils/ip';
|
||||||
|
@ -28,13 +28,22 @@ passport.serializeUser((user, done) => {
|
||||||
passport.deserializeUser(async (req, id, done) => {
|
passport.deserializeUser(async (req, id, done) => {
|
||||||
const user = new User(id, getIPFromRequest(req));
|
const user = new User(id, getIPFromRequest(req));
|
||||||
if (id) {
|
if (id) {
|
||||||
RegUser.findOne({ where: { id } }).then((reguser) => {
|
RegUser.findByPk(id, {
|
||||||
|
include: {
|
||||||
|
model: Channel,
|
||||||
|
as: 'channel',
|
||||||
|
},
|
||||||
|
}).then((reguser) => {
|
||||||
if (reguser) {
|
if (reguser) {
|
||||||
user.regUser = reguser;
|
user.regUser = reguser;
|
||||||
user.id = id;
|
user.id = id;
|
||||||
|
for (let i = 0; i < reguser.channel.length; i += 1) {
|
||||||
|
user.channelIds.push(reguser.channel[i].id);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
user.id = null;
|
user.id = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
done(null, user);
|
done(null, user);
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
|
|
33
src/data/models/Channel.js
Normal file
33
src/data/models/Channel.js
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
/*
|
||||||
|
*
|
||||||
|
* Database layout for Chat Channels
|
||||||
|
*
|
||||||
|
* @flow
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
import DataType from 'sequelize';
|
||||||
|
import Model from '../sequelize';
|
||||||
|
|
||||||
|
const Channel = Model.define('Channel', {
|
||||||
|
// Channel ID
|
||||||
|
id: {
|
||||||
|
type: DataType.INTEGER.UNSIGNED,
|
||||||
|
autoIncrement: true,
|
||||||
|
primaryKey: true,
|
||||||
|
},
|
||||||
|
|
||||||
|
name: {
|
||||||
|
type: `${DataType.CHAR(32)} CHARSET utf8mb4 COLLATE utf8mb4_unicode_ci`,
|
||||||
|
allowNull: false,
|
||||||
|
},
|
||||||
|
|
||||||
|
lastMessage: {
|
||||||
|
type: DataType.DATE,
|
||||||
|
allowNull: false,
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
updatedAt: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
export default Channel;
|
40
src/data/models/Message.js
Normal file
40
src/data/models/Message.js
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
/*
|
||||||
|
*
|
||||||
|
* Database layout for Chat Message History
|
||||||
|
*
|
||||||
|
* @flow
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
import DataType from 'sequelize';
|
||||||
|
import Model from '../sequelize';
|
||||||
|
import Channel from './Channel';
|
||||||
|
import RegUser from './RegUser';
|
||||||
|
|
||||||
|
const Message = Model.define('Message', {
|
||||||
|
// Message ID
|
||||||
|
id: {
|
||||||
|
type: DataType.INTEGER.UNSIGNED,
|
||||||
|
autoIncrement: true,
|
||||||
|
primaryKey: true,
|
||||||
|
},
|
||||||
|
|
||||||
|
message: {
|
||||||
|
type: `${DataType.CHAR(200)} CHARSET utf8mb4 COLLATE utf8mb4_unicode_ci`,
|
||||||
|
allowNull: false,
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
updatedAt: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
Message.belongsTo(Channel, {
|
||||||
|
as: 'channel',
|
||||||
|
foreignKey: 'cid',
|
||||||
|
});
|
||||||
|
|
||||||
|
Message.belongsTo(RegUser, {
|
||||||
|
as: 'user',
|
||||||
|
foreignKey: 'uid',
|
||||||
|
});
|
||||||
|
|
||||||
|
export default Message;
|
|
@ -25,7 +25,7 @@ const RegUser = Model.define('User', {
|
||||||
},
|
},
|
||||||
|
|
||||||
name: {
|
name: {
|
||||||
type: DataType.CHAR(32),
|
type: `${DataType.CHAR(32)} CHARSET utf8mb4 COLLATE utf8mb4_unicode_ci`,
|
||||||
allowNull: false,
|
allowNull: false,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -91,6 +91,13 @@ const RegUser = Model.define('User', {
|
||||||
allowNull: true,
|
allowNull: true,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// flag == country code
|
||||||
|
flag: {
|
||||||
|
type: DataType.CHAR(2),
|
||||||
|
defaultValue: 'xx',
|
||||||
|
allowNull: false,
|
||||||
|
},
|
||||||
|
|
||||||
lastLogIn: {
|
lastLogIn: {
|
||||||
type: DataType.DATE,
|
type: DataType.DATE,
|
||||||
allowNull: true,
|
allowNull: true,
|
||||||
|
|
|
@ -25,12 +25,14 @@ class User {
|
||||||
regUser: Object;
|
regUser: Object;
|
||||||
|
|
||||||
constructor(id: string = null, ip: string = '127.0.0.1') {
|
constructor(id: string = null, ip: string = '127.0.0.1') {
|
||||||
// id should stay null if unregistered, and user email if registered
|
// id should stay null if unregistered
|
||||||
this.id = id;
|
this.id = id;
|
||||||
this.ip = ip;
|
this.ip = ip;
|
||||||
this.ipSub = getIPv6Subnet(ip);
|
this.ipSub = getIPv6Subnet(ip);
|
||||||
this.wait = null;
|
this.wait = null;
|
||||||
|
// following gets populated by passport
|
||||||
this.regUser = null;
|
this.regUser = null;
|
||||||
|
this.channelIds = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
static async name2Id(name: string) {
|
static async name2Id(name: string) {
|
||||||
|
@ -48,6 +50,10 @@ class User {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getName() {
|
||||||
|
return (this.regUser) ? this.regUser.name : null;
|
||||||
|
}
|
||||||
|
|
||||||
async setWait(coolDown: number, canvasId: number): Promise<boolean> {
|
async setWait(coolDown: number, canvasId: number): Promise<boolean> {
|
||||||
if (!coolDown) return false;
|
if (!coolDown) return false;
|
||||||
this.wait = Date.now() + coolDown;
|
this.wait = Date.now() + coolDown;
|
||||||
|
@ -115,6 +121,14 @@ class User {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async setCountry(country) {
|
||||||
|
if (this.regUser && this.regUser.flag !== country) {
|
||||||
|
this.regUser.update({
|
||||||
|
flag: country,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async updateLogInTimestamp(): Promise<boolean> {
|
async updateLogInTimestamp(): Promise<boolean> {
|
||||||
if (!this.regUser) return false;
|
if (!this.regUser) return false;
|
||||||
try {
|
try {
|
||||||
|
|
23
src/data/models/UserChannel.js
Normal file
23
src/data/models/UserChannel.js
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
/*
|
||||||
|
*
|
||||||
|
* Junction table for User -> Channels
|
||||||
|
* A channel can be anything,
|
||||||
|
* Group, Public Chat, DM, etc.
|
||||||
|
*
|
||||||
|
* @flow
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
import DataType from 'sequelize';
|
||||||
|
import Model from '../sequelize';
|
||||||
|
|
||||||
|
const UserChannel = Model.define('UserChannel', {
|
||||||
|
lastRead: {
|
||||||
|
type: DataType.DATE,
|
||||||
|
allowNull: true,
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
timestamps: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
export default UserChannel;
|
|
@ -5,13 +5,32 @@ import Blacklist from './Blacklist';
|
||||||
import Whitelist from './Whitelist';
|
import Whitelist from './Whitelist';
|
||||||
import User from './User';
|
import User from './User';
|
||||||
import RegUser from './RegUser';
|
import RegUser from './RegUser';
|
||||||
|
import Channel from './Channel';
|
||||||
|
import UserChannel from './UserChannel';
|
||||||
|
import Message from './Message';
|
||||||
|
|
||||||
|
RegUser.belongsToMany(Channel, {
|
||||||
|
as: 'channel',
|
||||||
|
through: UserChannel,
|
||||||
|
});
|
||||||
|
Channel.belongsToMany(RegUser, {
|
||||||
|
as: 'user',
|
||||||
|
through: UserChannel,
|
||||||
|
});
|
||||||
|
|
||||||
function sync(...args) {
|
function sync(...args) {
|
||||||
return sequelize.sync(...args);
|
return sequelize.sync(...args);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default { sync };
|
/*
|
||||||
|
* makes sure that minimum required rows are present
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
function validateTables() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export default { sync, validateTables };
|
||||||
export {
|
export {
|
||||||
Whitelist, Blacklist, User, RegUser,
|
Whitelist, Blacklist, User, RegUser, Channel, UserChannel, Message,
|
||||||
};
|
};
|
||||||
|
|
79
src/reducers/chat.js
Normal file
79
src/reducers/chat.js
Normal file
|
@ -0,0 +1,79 @@
|
||||||
|
/* @flow */
|
||||||
|
|
||||||
|
import { MAX_CHAT_MESSAGES } from '../core/constants';
|
||||||
|
|
||||||
|
import type { Action } from '../actions/types';
|
||||||
|
|
||||||
|
export type ChatState = {
|
||||||
|
// [[cid, name], [cid2, name2],...]
|
||||||
|
channels: Array,
|
||||||
|
// { cid: [message1,message2,message2,...]}
|
||||||
|
messages: Object,
|
||||||
|
// if currently fetching messages
|
||||||
|
fetching: boolean,
|
||||||
|
}
|
||||||
|
|
||||||
|
const initialState: ChatState = {
|
||||||
|
channels: [],
|
||||||
|
messages: {},
|
||||||
|
fetching: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function chat(
|
||||||
|
state: ChatState = initialState,
|
||||||
|
action: Action,
|
||||||
|
): ChatState {
|
||||||
|
switch (action.type) {
|
||||||
|
case 'RECEIVE_ME': {
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
channels: action.channels,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'SET_CHAT_FETCHING': {
|
||||||
|
const { fetching } = action;
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
fetching,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'RECEIVE_CHAT_MESSAGE': {
|
||||||
|
const {
|
||||||
|
name, text, country, channel,
|
||||||
|
} = action;
|
||||||
|
if (!state.messages[channel]) {
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
const messages = {
|
||||||
|
...state.messages,
|
||||||
|
[channel]: [
|
||||||
|
...state.messages[channel],
|
||||||
|
[name, text, country],
|
||||||
|
],
|
||||||
|
};
|
||||||
|
if (messages[channel].length > MAX_CHAT_MESSAGES) {
|
||||||
|
messages[channel].shift();
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
messages,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'RECEIVE_CHAT_HISTORY': {
|
||||||
|
const { cid, history } = action;
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
messages: {
|
||||||
|
...state.messages,
|
||||||
|
[cid]: history,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
}
|
|
@ -30,7 +30,7 @@ const initialState: GUIState = {
|
||||||
compactPalette: false,
|
compactPalette: false,
|
||||||
paletteOpen: true,
|
paletteOpen: true,
|
||||||
menuOpen: false,
|
menuOpen: false,
|
||||||
chatChannel: 0,
|
chatChannel: 1,
|
||||||
style: 'default',
|
style: 'default',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -7,12 +7,14 @@ import canvas from './canvas';
|
||||||
import gui from './gui';
|
import gui from './gui';
|
||||||
import modal from './modal';
|
import modal from './modal';
|
||||||
import user from './user';
|
import user from './user';
|
||||||
|
import chat from './chat';
|
||||||
|
|
||||||
import type { AudioState } from './audio';
|
import type { AudioState } from './audio';
|
||||||
import type { CanvasState } from './canvas';
|
import type { CanvasState } from './canvas';
|
||||||
import type { GUIState } from './gui';
|
import type { GUIState } from './gui';
|
||||||
import type { ModalState } from './modal';
|
import type { ModalState } from './modal';
|
||||||
import type { UserState } from './user';
|
import type { UserState } from './user';
|
||||||
|
import type { ChatState } from './chat';
|
||||||
|
|
||||||
export type State = {
|
export type State = {
|
||||||
audio: AudioState,
|
audio: AudioState,
|
||||||
|
@ -20,12 +22,13 @@ export type State = {
|
||||||
gui: GUIState,
|
gui: GUIState,
|
||||||
modal: ModalState,
|
modal: ModalState,
|
||||||
user: UserState,
|
user: UserState,
|
||||||
|
chat: ChatState,
|
||||||
};
|
};
|
||||||
|
|
||||||
const config = {
|
const config = {
|
||||||
key: 'primary',
|
key: 'primary',
|
||||||
storage: localForage,
|
storage: localForage,
|
||||||
blacklist: ['user', 'canvas', 'modal'],
|
blacklist: ['user', 'canvas', 'modal', 'chat'],
|
||||||
};
|
};
|
||||||
|
|
||||||
export default persistCombineReducers(config, {
|
export default persistCombineReducers(config, {
|
||||||
|
@ -34,4 +37,5 @@ export default persistCombineReducers(config, {
|
||||||
gui,
|
gui,
|
||||||
modal,
|
modal,
|
||||||
user,
|
user,
|
||||||
|
chat,
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,7 +1,5 @@
|
||||||
/* @flow */
|
/* @flow */
|
||||||
|
|
||||||
import { MAX_CHAT_MESSAGES } from '../core/constants';
|
|
||||||
|
|
||||||
import type { Action } from '../actions/types';
|
import type { Action } from '../actions/types';
|
||||||
|
|
||||||
|
|
||||||
|
@ -24,8 +22,6 @@ export type UserState = {
|
||||||
// global stats
|
// global stats
|
||||||
totalRanking: Object,
|
totalRanking: Object,
|
||||||
totalDailyRanking: Object,
|
totalDailyRanking: Object,
|
||||||
// chat
|
|
||||||
chatMessages: Array,
|
|
||||||
// minecraft
|
// minecraft
|
||||||
minecraftname: string,
|
minecraftname: string,
|
||||||
// if user is using touchscreen
|
// if user is using touchscreen
|
||||||
|
@ -48,10 +44,6 @@ const initialState: UserState = {
|
||||||
mailreg: false,
|
mailreg: false,
|
||||||
totalRanking: {},
|
totalRanking: {},
|
||||||
totalDailyRanking: {},
|
totalDailyRanking: {},
|
||||||
chatMessages: [
|
|
||||||
[['info', 'Welcome to the PixelPlanet Chat', 'il']],
|
|
||||||
[['info', 'Welcome to the PixelPlanet Chat', 'il']],
|
|
||||||
],
|
|
||||||
minecraftname: null,
|
minecraftname: null,
|
||||||
isOnMobile: false,
|
isOnMobile: false,
|
||||||
notification: null,
|
notification: null,
|
||||||
|
@ -138,33 +130,6 @@ export default function user(
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
case 'RECEIVE_CHAT_MESSAGE': {
|
|
||||||
const {
|
|
||||||
name, text, country, channel,
|
|
||||||
} = action;
|
|
||||||
const chatMessages = state.chatMessages.slice();
|
|
||||||
let channelMessages = chatMessages[channel];
|
|
||||||
if (channelMessages.length > MAX_CHAT_MESSAGES) {
|
|
||||||
channelMessages = channelMessages.slice(-50);
|
|
||||||
}
|
|
||||||
channelMessages = channelMessages.concat([
|
|
||||||
[name, text, country],
|
|
||||||
]);
|
|
||||||
chatMessages[channel] = channelMessages;
|
|
||||||
return {
|
|
||||||
...state,
|
|
||||||
chatMessages,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
case 'RECEIVE_CHAT_HISTORY': {
|
|
||||||
const { data: chatMessages } = action;
|
|
||||||
return {
|
|
||||||
...state,
|
|
||||||
chatMessages,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
case 'RECEIVE_ME': {
|
case 'RECEIVE_ME': {
|
||||||
const {
|
const {
|
||||||
name,
|
name,
|
||||||
|
|
57
src/routes/api/chathistory.js
Normal file
57
src/routes/api/chathistory.js
Normal file
|
@ -0,0 +1,57 @@
|
||||||
|
/*
|
||||||
|
*
|
||||||
|
* returns chat messages of given channel
|
||||||
|
*
|
||||||
|
* @flow
|
||||||
|
*/
|
||||||
|
|
||||||
|
import type { Request, Response } from 'express';
|
||||||
|
|
||||||
|
import chatProvider from '../../core/ChatProvider';
|
||||||
|
|
||||||
|
async function chatHistory(req: Request, res: Response) {
|
||||||
|
let { cid, limit } = req.query;
|
||||||
|
if (!cid || !limit) {
|
||||||
|
res.status(400);
|
||||||
|
res.json({
|
||||||
|
errors: ['cid or limit not defined'],
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
cid = parseInt(cid, 10);
|
||||||
|
limit = parseInt(limit, 10);
|
||||||
|
if (Number.isNaN(cid) || Number.isNaN(limit)
|
||||||
|
|| limit <= 0 || limit > 300) {
|
||||||
|
res.status(400);
|
||||||
|
res.json({
|
||||||
|
errors: ['cid or limit not a valid value'],
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { user } = req;
|
||||||
|
|
||||||
|
if (!chatProvider.userHasChannelAccess(user, cid, false)) {
|
||||||
|
res.status(401);
|
||||||
|
res.json({
|
||||||
|
errors: ['You don\'t have access to this channel'],
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// try {
|
||||||
|
const history = await chatProvider.getHistory(cid, limit);
|
||||||
|
res.json({
|
||||||
|
history,
|
||||||
|
});
|
||||||
|
/*
|
||||||
|
} catch {
|
||||||
|
res.status(500);
|
||||||
|
res.json({
|
||||||
|
errors: ['Can not fetch messages'],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
|
||||||
|
export default chatHistory;
|
|
@ -17,6 +17,7 @@ import captcha from './captcha';
|
||||||
import auth from './auth';
|
import auth from './auth';
|
||||||
import ranking from './ranking';
|
import ranking from './ranking';
|
||||||
import history from './history';
|
import history from './history';
|
||||||
|
import chatHistory from './chathistory';
|
||||||
|
|
||||||
|
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
|
@ -77,6 +78,8 @@ router.get('/me', me);
|
||||||
|
|
||||||
router.post('/mctp', mctp);
|
router.post('/mctp', mctp);
|
||||||
|
|
||||||
|
router.get('/chathistory', chatHistory);
|
||||||
|
|
||||||
router.use('/auth', auth(passport));
|
router.use('/auth', auth(passport));
|
||||||
|
|
||||||
export default router;
|
export default router;
|
||||||
|
|
|
@ -18,7 +18,7 @@ const CANVAS_MIN_XY = -CANVAS_MAX_XY;
|
||||||
|
|
||||||
export default async (req: Request, res: Response) => {
|
export default async (req: Request, res: Response) => {
|
||||||
const { user } = req;
|
const { user } = req;
|
||||||
if (!user) {
|
if (!user.regUser) {
|
||||||
res.status(401);
|
res.status(401);
|
||||||
res.json({
|
res.json({
|
||||||
success: false,
|
success: false,
|
||||||
|
|
|
@ -86,8 +86,9 @@ class APISocketServer extends WebSocketEvents {
|
||||||
broadcastChatMessage(
|
broadcastChatMessage(
|
||||||
name,
|
name,
|
||||||
msg,
|
msg,
|
||||||
country,
|
|
||||||
channelId,
|
channelId,
|
||||||
|
id,
|
||||||
|
country,
|
||||||
sendapi,
|
sendapi,
|
||||||
ws = null,
|
ws = null,
|
||||||
) {
|
) {
|
||||||
|
@ -249,14 +250,44 @@ class APISocketServer extends WebSocketEvents {
|
||||||
const chatname = (user.id)
|
const chatname = (user.id)
|
||||||
? `[MC] ${user.regUser.name}`
|
? `[MC] ${user.regUser.name}`
|
||||||
: `[MC] ${minecraftname}`;
|
: `[MC] ${minecraftname}`;
|
||||||
chatProvider.broadcastChatMessage(chatname, msg, 'xx', 0, false);
|
chatProvider.broadcastChatMessage(
|
||||||
this.broadcastChatMessage(chatname, msg, 'xx', 0, true, ws);
|
chatname,
|
||||||
|
msg,
|
||||||
|
0,
|
||||||
|
-1,
|
||||||
|
'xx',
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
this.broadcastChatMessage(
|
||||||
|
chatname,
|
||||||
|
msg,
|
||||||
|
0,
|
||||||
|
-1,
|
||||||
|
'xx',
|
||||||
|
true,
|
||||||
|
ws,
|
||||||
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (command === 'chat') {
|
if (command === 'chat') {
|
||||||
const [name, msg, country, channelId] = packet;
|
const [name, msg, country, channelId] = packet;
|
||||||
chatProvider.broadcastChatMessage(name, msg, country, channelId, false);
|
chatProvider.broadcastChatMessage(
|
||||||
this.broadcastChatMessage(name, msg, country, channelId, true, ws);
|
name,
|
||||||
|
msg,
|
||||||
|
channelId,
|
||||||
|
-1,
|
||||||
|
country,
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
this.broadcastChatMessage(
|
||||||
|
name,
|
||||||
|
msg,
|
||||||
|
channelId,
|
||||||
|
-1,
|
||||||
|
country,
|
||||||
|
true,
|
||||||
|
ws,
|
||||||
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (command === 'linkacc') {
|
if (command === 'linkacc') {
|
||||||
|
|
|
@ -16,7 +16,6 @@ import RegisterCanvas from './packets/RegisterCanvas';
|
||||||
import RegisterChunk from './packets/RegisterChunk';
|
import RegisterChunk from './packets/RegisterChunk';
|
||||||
import RegisterMultipleChunks from './packets/RegisterMultipleChunks';
|
import RegisterMultipleChunks from './packets/RegisterMultipleChunks';
|
||||||
import DeRegisterChunk from './packets/DeRegisterChunk';
|
import DeRegisterChunk from './packets/DeRegisterChunk';
|
||||||
import RequestChatHistory from './packets/RequestChatHistory';
|
|
||||||
import ChangedMe from './packets/ChangedMe';
|
import ChangedMe from './packets/ChangedMe';
|
||||||
|
|
||||||
const chunks = [];
|
const chunks = [];
|
||||||
|
@ -26,6 +25,7 @@ class ProtocolClient extends EventEmitter {
|
||||||
ws: WebSocket;
|
ws: WebSocket;
|
||||||
name: string;
|
name: string;
|
||||||
canvasId: number;
|
canvasId: number;
|
||||||
|
channelId: number;
|
||||||
timeConnected: number;
|
timeConnected: number;
|
||||||
isConnected: number;
|
isConnected: number;
|
||||||
isConnecting: boolean;
|
isConnecting: boolean;
|
||||||
|
@ -39,6 +39,7 @@ class ProtocolClient extends EventEmitter {
|
||||||
this.ws = null;
|
this.ws = null;
|
||||||
this.name = null;
|
this.name = null;
|
||||||
this.canvasId = '0';
|
this.canvasId = '0';
|
||||||
|
this.channelId = 0;
|
||||||
this.msgQueue = [];
|
this.msgQueue = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -82,7 +83,6 @@ class ProtocolClient extends EventEmitter {
|
||||||
this.isConnecting = false;
|
this.isConnecting = false;
|
||||||
this.isConnected = true;
|
this.isConnected = true;
|
||||||
this.emit('open', {});
|
this.emit('open', {});
|
||||||
this.requestChatHistory();
|
|
||||||
if (this.canvasId !== null) {
|
if (this.canvasId !== null) {
|
||||||
this.ws.send(RegisterCanvas.dehydrate(this.canvasId));
|
this.ws.send(RegisterCanvas.dehydrate(this.canvasId));
|
||||||
}
|
}
|
||||||
|
@ -143,11 +143,6 @@ class ProtocolClient extends EventEmitter {
|
||||||
this.sendWhenReady(buffer);
|
this.sendWhenReady(buffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
requestChatHistory() {
|
|
||||||
const buffer = RequestChatHistory.dehydrate();
|
|
||||||
if (this.isConnected) this.ws.send(buffer);
|
|
||||||
}
|
|
||||||
|
|
||||||
sendChatMessage(message, channelId) {
|
sendChatMessage(message, channelId) {
|
||||||
if (this.isConnected) {
|
if (this.isConnected) {
|
||||||
this.ws.send(JSON.stringify([message, channelId]));
|
this.ws.send(JSON.stringify([message, channelId]));
|
||||||
|
@ -174,11 +169,6 @@ class ProtocolClient extends EventEmitter {
|
||||||
const data = JSON.parse(message);
|
const data = JSON.parse(message);
|
||||||
|
|
||||||
if (Array.isArray(data)) {
|
if (Array.isArray(data)) {
|
||||||
if (Array.isArray(data[0])) {
|
|
||||||
// Array in Array: Chat History
|
|
||||||
this.emit('chatHistory', data);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (data.length === 4) {
|
if (data.length === 4) {
|
||||||
// Ordinary array: Chat message
|
// Ordinary array: Chat message
|
||||||
const [name, text, country, channelId] = data;
|
const [name, text, country, channelId] = data;
|
||||||
|
|
|
@ -16,7 +16,6 @@ import RegisterChunk from './packets/RegisterChunk';
|
||||||
import RegisterMultipleChunks from './packets/RegisterMultipleChunks';
|
import RegisterMultipleChunks from './packets/RegisterMultipleChunks';
|
||||||
import DeRegisterChunk from './packets/DeRegisterChunk';
|
import DeRegisterChunk from './packets/DeRegisterChunk';
|
||||||
import DeRegisterMultipleChunks from './packets/DeRegisterMultipleChunks';
|
import DeRegisterMultipleChunks from './packets/DeRegisterMultipleChunks';
|
||||||
import RequestChatHistory from './packets/RequestChatHistory';
|
|
||||||
import ChangedMe from './packets/ChangedMe';
|
import ChangedMe from './packets/ChangedMe';
|
||||||
import OnlineCounter from './packets/OnlineCounter';
|
import OnlineCounter from './packets/OnlineCounter';
|
||||||
|
|
||||||
|
@ -85,7 +84,7 @@ class SocketServer extends WebSocketEvents {
|
||||||
ws.on('pong', heartbeat);
|
ws.on('pong', heartbeat);
|
||||||
const user = await authenticateClient(req);
|
const user = await authenticateClient(req);
|
||||||
ws.user = user;
|
ws.user = user;
|
||||||
ws.name = (user.regUser) ? user.regUser.name : null;
|
ws.name = user.getName();
|
||||||
ws.rateLimiter = new RateLimiter(20, 15, true);
|
ws.rateLimiter = new RateLimiter(20, 15, true);
|
||||||
cheapDetector(user.ip);
|
cheapDetector(user.ip);
|
||||||
|
|
||||||
|
@ -166,13 +165,16 @@ class SocketServer extends WebSocketEvents {
|
||||||
broadcastChatMessage(
|
broadcastChatMessage(
|
||||||
name: string,
|
name: string,
|
||||||
message: string,
|
message: string,
|
||||||
|
channelId: number,
|
||||||
|
id: number,
|
||||||
country: string,
|
country: string,
|
||||||
channelId: number = 0,
|
|
||||||
) {
|
) {
|
||||||
const text = JSON.stringify([name, message, country, channelId]);
|
const text = JSON.stringify([name, message, country, channelId]);
|
||||||
this.wss.clients.forEach((ws) => {
|
this.wss.clients.forEach((ws) => {
|
||||||
if (ws.readyState === WebSocket.OPEN) {
|
if (ws.readyState === WebSocket.OPEN) {
|
||||||
ws.send(text);
|
if (chatProvider.userHasChannelAccess(ws.user, channelId)) {
|
||||||
|
ws.send(text);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -199,7 +201,9 @@ class SocketServer extends WebSocketEvents {
|
||||||
// eslint-disable-next-line no-underscore-dangle
|
// eslint-disable-next-line no-underscore-dangle
|
||||||
client._socket.write(buffer);
|
client._socket.write(buffer);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error('(!) Catched error on write socket:', error);
|
logger.error(
|
||||||
|
`WebSocket broadcast pixelbuffer error: ${error.message}`,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -313,8 +317,10 @@ class SocketServer extends WebSocketEvents {
|
||||||
} else {
|
} else {
|
||||||
logger.info('Got empty message or message from unidentified ws');
|
logger.info('Got empty message or message from unidentified ws');
|
||||||
}
|
}
|
||||||
} catch {
|
} catch (error) {
|
||||||
logger.info('Got invalid ws text message');
|
logger.error('Got invalid ws text message');
|
||||||
|
logger.error(error.message);
|
||||||
|
logger.error(error.stack);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -397,11 +403,6 @@ class SocketServer extends WebSocketEvents {
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case RequestChatHistory.OP_CODE: {
|
|
||||||
const history = JSON.stringify(chatProvider.history);
|
|
||||||
ws.send(history);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,13 +0,0 @@
|
||||||
/* @flow */
|
|
||||||
|
|
||||||
const OP_CODE = 0xA5;
|
|
||||||
|
|
||||||
export default {
|
|
||||||
OP_CODE,
|
|
||||||
dehydrate(): ArrayBuffer {
|
|
||||||
const buffer = new ArrayBuffer(1);
|
|
||||||
const view = new DataView(buffer);
|
|
||||||
view.setInt8(0, OP_CODE);
|
|
||||||
return buffer;
|
|
||||||
},
|
|
||||||
};
|
|
|
@ -22,9 +22,10 @@ function authenticateClient(req) {
|
||||||
((resolve) => {
|
((resolve) => {
|
||||||
router(req, {}, async () => {
|
router(req, {}, async () => {
|
||||||
const country = req.headers['cf-ipcountry'] || 'xx';
|
const country = req.headers['cf-ipcountry'] || 'xx';
|
||||||
|
const countryCode = country.toLowerCase();
|
||||||
const user = (req.user) ? req.user
|
const user = (req.user) ? req.user
|
||||||
: new User(null, getIPFromRequest(req));
|
: new User(null, getIPFromRequest(req));
|
||||||
user.country = country.toLowerCase();
|
user.setCountry(countryCode);
|
||||||
resolve(user);
|
resolve(user);
|
||||||
});
|
});
|
||||||
}),
|
}),
|
||||||
|
|
|
@ -71,8 +71,9 @@ class WebSockets {
|
||||||
broadcastChatMessage(
|
broadcastChatMessage(
|
||||||
name: string,
|
name: string,
|
||||||
message: string,
|
message: string,
|
||||||
country: string,
|
channelId: number = 1,
|
||||||
channelId: number = 0,
|
id: number = -1,
|
||||||
|
country: string = 'xx',
|
||||||
sendapi: boolean = true,
|
sendapi: boolean = true,
|
||||||
) {
|
) {
|
||||||
country = country || 'xx';
|
country = country || 'xx';
|
||||||
|
@ -80,8 +81,9 @@ class WebSockets {
|
||||||
(listener) => listener.broadcastChatMessage(
|
(listener) => listener.broadcastChatMessage(
|
||||||
name,
|
name,
|
||||||
message,
|
message,
|
||||||
country,
|
|
||||||
channelId,
|
channelId,
|
||||||
|
id,
|
||||||
|
country,
|
||||||
sendapi,
|
sendapi,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
|
@ -223,7 +223,7 @@ class ChunkLoader {
|
||||||
this.store.dispatch(requestBigChunk(center));
|
this.store.dispatch(requestBigChunk(center));
|
||||||
chunkRGB.isBasechunk = true;
|
chunkRGB.isBasechunk = true;
|
||||||
try {
|
try {
|
||||||
const url = `/chunks/${this.canvasId}/${cx}/${cy}.bmp`;
|
const url = `chunks/${this.canvasId}/${cx}/${cy}.bmp`;
|
||||||
const response = await fetch(url);
|
const response = await fetch(url);
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
const arrayBuffer = await response.arrayBuffer();
|
const arrayBuffer = await response.arrayBuffer();
|
||||||
|
@ -247,7 +247,7 @@ class ChunkLoader {
|
||||||
const center = [zoom, cx, cy];
|
const center = [zoom, cx, cy];
|
||||||
this.store.dispatch(requestBigChunk(center));
|
this.store.dispatch(requestBigChunk(center));
|
||||||
try {
|
try {
|
||||||
const url = `/tiles/${this.canvasId}/${zoom}/${cx}/${cy}.png`;
|
const url = `tiles/${this.canvasId}/${zoom}/${cx}/${cy}.png`;
|
||||||
const img = await loadImage(url);
|
const img = await loadImage(url);
|
||||||
chunkRGB.fromImage(img);
|
chunkRGB.fromImage(img);
|
||||||
this.store.dispatch(receiveBigChunk(center));
|
this.store.dispatch(receiveBigChunk(center));
|
||||||
|
|
|
@ -99,7 +99,7 @@ class ChunkLoader {
|
||||||
const center = [0, cx, cz];
|
const center = [0, cx, cz];
|
||||||
this.store.dispatch(requestBigChunk(center));
|
this.store.dispatch(requestBigChunk(center));
|
||||||
try {
|
try {
|
||||||
const url = `/chunks/${this.canvasId}/${cx}/${cz}.bmp`;
|
const url = `chunks/${this.canvasId}/${cx}/${cz}.bmp`;
|
||||||
const response = await fetch(url);
|
const response = await fetch(url);
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
const arrayBuffer = await response.arrayBuffer();
|
const arrayBuffer = await response.arrayBuffer();
|
||||||
|
|
|
@ -14,6 +14,7 @@ import assets from './assets.json'; // eslint-disable-line import/no-unresolved
|
||||||
import logger from './core/logger';
|
import logger from './core/logger';
|
||||||
import rankings from './core/ranking';
|
import rankings from './core/ranking';
|
||||||
import models from './data/models';
|
import models from './data/models';
|
||||||
|
import chatProvider from './core/ChatProvider';
|
||||||
|
|
||||||
import SocketServer from './socket/SocketServer';
|
import SocketServer from './socket/SocketServer';
|
||||||
import APISocketServer from './socket/APISocketServer';
|
import APISocketServer from './socket/APISocketServer';
|
||||||
|
@ -192,10 +193,14 @@ app.get('/', async (req, res) => {
|
||||||
//
|
//
|
||||||
// ip config
|
// ip config
|
||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
const promise = models.sync().catch((err) => logger.error(err.stack));
|
// use this if models changed:
|
||||||
|
const promise = models.sync({ alter: { drop: false } })
|
||||||
|
// const promise = models.sync()
|
||||||
|
.catch((err) => logger.error(err.stack));
|
||||||
promise.then(() => {
|
promise.then(() => {
|
||||||
server.listen(PORT, () => {
|
server.listen(PORT, () => {
|
||||||
rankings.updateRanking();
|
rankings.updateRanking();
|
||||||
|
chatProvider.initialize();
|
||||||
const address = server.address();
|
const address = server.address();
|
||||||
logger.log('info', `web is running at http://localhost:${address.port}/`);
|
logger.log('info', `web is running at http://localhost:${address.port}/`);
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in New Issue
Block a user