flow: good work
This commit is contained in:
parent
96dd0fce9c
commit
ddec5d2283
|
@ -2,6 +2,7 @@ const config = {
|
||||||
globals: {
|
globals: {
|
||||||
__non_webpack_require__: true,
|
__non_webpack_require__: true,
|
||||||
process: true,
|
process: true,
|
||||||
|
window: true,
|
||||||
},
|
},
|
||||||
rules: {
|
rules: {
|
||||||
'babel/object-curly-spacing': 'off',
|
'babel/object-curly-spacing': 'off',
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
require('dotenv/config');
|
require('dotenv/config');
|
||||||
|
|
||||||
const airbnbBase = require('@neutrinojs/airbnb-base');
|
const airbnb = require('@neutrinojs/airbnb');
|
||||||
const clean = require('@neutrinojs/clean');
|
const clean = require('@neutrinojs/clean');
|
||||||
const mocha = require('@neutrinojs/mocha');
|
const mocha = require('@neutrinojs/mocha');
|
||||||
const node = require('@neutrinojs/node');
|
const node = require('@neutrinojs/node');
|
||||||
|
@ -12,7 +12,7 @@ module.exports = {
|
||||||
root: __dirname,
|
root: __dirname,
|
||||||
},
|
},
|
||||||
use: [
|
use: [
|
||||||
airbnbBase({
|
airbnb({
|
||||||
eslint: {
|
eslint: {
|
||||||
cache: false,
|
cache: false,
|
||||||
baseConfig: require('./.eslint.defaults'),
|
baseConfig: require('./.eslint.defaults'),
|
||||||
|
@ -24,10 +24,6 @@ module.exports = {
|
||||||
mocha(),
|
mocha(),
|
||||||
node(),
|
node(),
|
||||||
(neutrino) => {
|
(neutrino) => {
|
||||||
neutrino.config.resolve.modules
|
|
||||||
.add(neutrino.options.source);
|
|
||||||
neutrino.config.resolve.modules
|
|
||||||
.add(`${neutrino.options.source}/../node_modules`);
|
|
||||||
if (process.env.LATUS_LINTING) {
|
if (process.env.LATUS_LINTING) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
"build": "webpack --mode production",
|
"build": "webpack --mode production",
|
||||||
"dev": "webpack --mode development",
|
"dev": "webpack --mode development",
|
||||||
"forcelatus": "pkgs=$(find node_modules/@latus -maxdepth 1 -mindepth 1 -printf '@latus/%f '); yarn upgrade $pkgs",
|
"forcelatus": "pkgs=$(find node_modules/@latus -maxdepth 1 -mindepth 1 -printf '@latus/%f '); yarn upgrade $pkgs",
|
||||||
|
"forcereddichat": "pkgs=$(find node_modules/@reddichat -maxdepth 1 -mindepth 1 -printf '@reddichat/%f '); yarn upgrade $pkgs",
|
||||||
"lint": "eslint --cache --format codeframe --ext mjs,jsx,js src",
|
"lint": "eslint --cache --format codeframe --ext mjs,jsx,js src",
|
||||||
"repl": "rlwrap -C qmp socat STDIO UNIX:$(ls /tmp/latus-*.sock | tail -n 1)",
|
"repl": "rlwrap -C qmp socat STDIO UNIX:$(ls /tmp/latus-*.sock | tail -n 1)",
|
||||||
"start": "NODE_ENV=production node build/index.js",
|
"start": "NODE_ENV=production node build/index.js",
|
||||||
|
@ -22,14 +23,23 @@
|
||||||
"@latus/repl": "^1.0.0",
|
"@latus/repl": "^1.0.0",
|
||||||
"@latus/socket": "^1.0.0",
|
"@latus/socket": "^1.0.0",
|
||||||
"@latus/user": "^1.0.0",
|
"@latus/user": "^1.0.0",
|
||||||
|
"@reddichat/app": "^1.0.0",
|
||||||
|
"@reddichat/chat": "^1.0.0",
|
||||||
|
"@reddichat/state": "^1.0.0",
|
||||||
|
"@reddichat/user": "^1.0.0",
|
||||||
"@reduxjs/toolkit": "^1.5.0",
|
"@reduxjs/toolkit": "^1.5.0",
|
||||||
"classnames": "^2.2.6",
|
"classnames": "^2.2.6",
|
||||||
|
"connected-react-router": "^6.8.0",
|
||||||
|
"deepmerge": "^4.2.2",
|
||||||
"dotenv": "8.2.0",
|
"dotenv": "8.2.0",
|
||||||
|
"history": "^4.7.2",
|
||||||
|
"react": "^17.0.1",
|
||||||
"react-hot-loader": "4.13.0",
|
"react-hot-loader": "4.13.0",
|
||||||
|
"react-redux": "^7.2.2",
|
||||||
"react-router-dom": "^5.2.0"
|
"react-router-dom": "^5.2.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@neutrinojs/airbnb-base": "^9.1.0",
|
"@neutrinojs/airbnb": "^9.1.0",
|
||||||
"@neutrinojs/clean": "^9.1.0",
|
"@neutrinojs/clean": "^9.1.0",
|
||||||
"@neutrinojs/mocha": "^9.1.0",
|
"@neutrinojs/mocha": "^9.1.0",
|
||||||
"@neutrinojs/node": "^9.1.0",
|
"@neutrinojs/node": "^9.1.0",
|
||||||
|
|
|
@ -7,7 +7,9 @@ const About = () => (
|
||||||
<div className="about">
|
<div className="about">
|
||||||
<h1 className="about__title">
|
<h1 className="about__title">
|
||||||
<span className="about__titleMessage">
|
<span className="about__titleMessage">
|
||||||
<span class="about__nowrap">Hi!</span> <span class="about__nowrap about__smile">^_^</span>
|
<span className="about__nowrap">Hi!</span>
|
||||||
|
{' '}
|
||||||
|
<span className="about__nowrap about__smile">^_^</span>
|
||||||
{' '}
|
{' '}
|
||||||
<img
|
<img
|
||||||
alt="cha0s's icon"
|
alt="cha0s's icon"
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
import './index.scss';
|
import './index.scss';
|
||||||
|
|
||||||
|
import {isAnonymousSelector} from '@reddichat/user/client';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import {useSelector} from 'react-redux';
|
||||||
import {hot} from 'react-hot-loader';
|
import {hot} from 'react-hot-loader';
|
||||||
import {Route, Router, Switch} from 'react-router-dom';
|
import {Route, Router, Switch} from 'react-router-dom';
|
||||||
import {createBrowserHistory} from 'history';
|
import {createBrowserHistory} from 'history';
|
||||||
|
@ -13,9 +15,11 @@ import Right from 'components/right';
|
||||||
|
|
||||||
const history = createBrowserHistory();
|
const history = createBrowserHistory();
|
||||||
|
|
||||||
const App = () => (
|
const App = () => {
|
||||||
|
const isAnonymous = useSelector(isAnonymousSelector);
|
||||||
|
return (
|
||||||
<div className="app">
|
<div className="app">
|
||||||
<Left />
|
{!isAnonymous && <Left />}
|
||||||
<Router history={history}>
|
<Router history={history}>
|
||||||
<Switch>
|
<Switch>
|
||||||
<Route
|
<Route
|
||||||
|
@ -33,8 +37,9 @@ const App = () => (
|
||||||
/>
|
/>
|
||||||
</Switch>
|
</Switch>
|
||||||
</Router>
|
</Router>
|
||||||
<Right />
|
{!isAnonymous && <Right />}
|
||||||
</div>
|
</div>
|
||||||
);
|
)
|
||||||
|
};
|
||||||
|
|
||||||
export default hot(module)(App);
|
export default hot(module)(App);
|
||||||
|
|
3
app/src/react/history.js
Normal file
3
app/src/react/history.js
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
import {createBrowserHistory} from 'history';
|
||||||
|
|
||||||
|
export default createBrowserHistory();
|
|
@ -1,9 +1,32 @@
|
||||||
import './index.scss';
|
import './index.scss';
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import {ConnectedRouter} from 'connected-react-router';
|
||||||
|
import {Provider} from 'react-redux';
|
||||||
|
|
||||||
import App from 'components/app';
|
import App from 'components/app';
|
||||||
|
|
||||||
|
import {configureStore} from '@reddichat/state/client';
|
||||||
|
|
||||||
|
import history from './history';
|
||||||
|
|
||||||
|
const Index = async (latus) => {
|
||||||
|
const store = await configureStore(latus, {history});
|
||||||
|
return () => (
|
||||||
|
<Provider store={store}>
|
||||||
|
<ConnectedRouter history={history}>
|
||||||
|
{/* <Dispatcher> */}
|
||||||
|
<App />
|
||||||
|
{/* <Unread /> */}
|
||||||
|
{/* </Dispatcher> */}
|
||||||
|
</ConnectedRouter>
|
||||||
|
</Provider>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
hooks: {
|
hooks: {
|
||||||
'@latus/react/components': () => App,
|
'@latus/react/components': (latus) => Index(latus),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -16,11 +16,12 @@ else {
|
||||||
const config = readConfig();
|
const config = readConfig();
|
||||||
const paths = Object.entries(config).map(([plugin]) => {
|
const paths = Object.entries(config).map(([plugin]) => {
|
||||||
try {
|
try {
|
||||||
require.resolve(plugin);
|
const local = join(process.cwd(), 'src', plugin);
|
||||||
return plugin;
|
require.resolve(local);
|
||||||
|
return local;
|
||||||
}
|
}
|
||||||
catch (error) {
|
catch (error) {
|
||||||
return join(process.cwd(), plugin);
|
return plugin;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
const latus = new Latus({
|
const latus = new Latus({
|
||||||
|
|
237
app/yarn.lock
237
app/yarn.lock
|
@ -871,7 +871,15 @@
|
||||||
pirates "^4.0.0"
|
pirates "^4.0.0"
|
||||||
source-map-support "^0.5.16"
|
source-map-support "^0.5.16"
|
||||||
|
|
||||||
"@babel/runtime@^7.1.2", "@babel/runtime@^7.12.1", "@babel/runtime@^7.8.4":
|
"@babel/runtime-corejs3@^7.10.2":
|
||||||
|
version "7.12.5"
|
||||||
|
resolved "https://npm.i12e.cha0s.io/@babel%2fruntime-corejs3/-/runtime-corejs3-7.12.5.tgz#ffee91da0eb4c6dae080774e94ba606368e414f4"
|
||||||
|
integrity sha512-roGr54CsTmNPPzZoCP1AmDXuBoNao7tnSA83TXTwt+UK5QVyh1DIJnrgYRPWKCF2flqZQXwa7Yr8v7VmLzF0YQ==
|
||||||
|
dependencies:
|
||||||
|
core-js-pure "^3.0.0"
|
||||||
|
regenerator-runtime "^0.13.4"
|
||||||
|
|
||||||
|
"@babel/runtime@^7.1.2", "@babel/runtime@^7.10.2", "@babel/runtime@^7.11.2", "@babel/runtime@^7.12.1", "@babel/runtime@^7.8.4":
|
||||||
version "7.12.5"
|
version "7.12.5"
|
||||||
resolved "https://npm.i12e.cha0s.io/@babel%2fruntime/-/runtime-7.12.5.tgz#410e7e487441e1b360c29be715d870d9b985882e"
|
resolved "https://npm.i12e.cha0s.io/@babel%2fruntime/-/runtime-7.12.5.tgz#410e7e487441e1b360c29be715d870d9b985882e"
|
||||||
integrity sha512-plcc+hbExy3McchJCEQG3knOsuh3HH+Prx1P6cLIkET/0dLuQDEnrT+s27Axgc9bqfsmNUNHfscgMUdBpC9xfg==
|
integrity sha512-plcc+hbExy3McchJCEQG3knOsuh3HH+Prx1P6cLIkET/0dLuQDEnrT+s27Axgc9bqfsmNUNHfscgMUdBpC9xfg==
|
||||||
|
@ -913,8 +921,8 @@
|
||||||
|
|
||||||
"@latus/core@1.0.0", "@latus/core@^1.0.0":
|
"@latus/core@1.0.0", "@latus/core@^1.0.0":
|
||||||
version "1.0.0"
|
version "1.0.0"
|
||||||
resolved "https://npm.i12e.cha0s.io/@latus%2fcore/-/core-1.0.0.tgz#919a3e6d8ddbe7da80c512f11c2d0e183616c994"
|
resolved "https://npm.i12e.cha0s.io/@latus%2fcore/-/core-1.0.0.tgz#01f0121bb91579b8bd1bdadff8dabf8673f44a88"
|
||||||
integrity sha512-yowxOdYxAfnme4PCxrUoIpkDd32RbAmG72FJlxYcTsd3Vntjz2IEH6WlqOj+1rO+0OfyffrB7ORu7FGt51TqRQ==
|
integrity sha512-NK7DuYoVIXmFEjazKi44vW335DFcE1pr62DXf9+9bZ8iyexptFBdfX/5sYFMGaeJlN/opFp0qFx94VPdVCsChg==
|
||||||
dependencies:
|
dependencies:
|
||||||
debug "4.3.1"
|
debug "4.3.1"
|
||||||
js-yaml "3.14.0"
|
js-yaml "3.14.0"
|
||||||
|
@ -933,8 +941,8 @@
|
||||||
|
|
||||||
"@latus/governor@^1.0.0":
|
"@latus/governor@^1.0.0":
|
||||||
version "1.0.0"
|
version "1.0.0"
|
||||||
resolved "https://npm.i12e.cha0s.io/@latus%2fgovernor/-/governor-1.0.0.tgz#e0b2f45349bda2a652755f80056960647fe090c2"
|
resolved "https://npm.i12e.cha0s.io/@latus%2fgovernor/-/governor-1.0.0.tgz#7cbcc3ae04aaf7d26e78a94dd3306a3dc16b18a1"
|
||||||
integrity sha512-bpdCFSW+73cqEWJZf91q8c8s5Nq32bqhlrZRRlDjQ3asqspzlrU9RAxLyS1kvSJ1NrM0S7s53AMuFksV4cMgBQ==
|
integrity sha512-gZB30h/HgvyS0PWtAJaW4OpnlRJUpMcyDzDnCmuWi+zdr8EWYq7cwUOpWhD4TKIIviFYuEwvhaRzmg3c8rQM8A==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@latus/redis" "^1.0.0"
|
"@latus/redis" "^1.0.0"
|
||||||
"@latus/socket" "^1.0.0"
|
"@latus/socket" "^1.0.0"
|
||||||
|
@ -943,8 +951,8 @@
|
||||||
|
|
||||||
"@latus/http@^1.0.0":
|
"@latus/http@^1.0.0":
|
||||||
version "1.0.0"
|
version "1.0.0"
|
||||||
resolved "https://npm.i12e.cha0s.io/@latus%2fhttp/-/http-1.0.0.tgz#4131aca03a7d7e4f66e54deaf4cf5edf94503137"
|
resolved "https://npm.i12e.cha0s.io/@latus%2fhttp/-/http-1.0.0.tgz#ed08c6c85112ce4d66a652f5810a220a66a0a1b3"
|
||||||
integrity sha512-BYGQwIglwo9/BTaU1DNF1ALxhxYbjhXtd1qPmNdqrLZXJVHg4MhpRLEl/L5SS3g8AkLO9e3CjXvseQBZMHBh8g==
|
integrity sha512-1qSwE1vX0bg4nFJFrPjFZT1PepDuDT9RIZRt3SzpvT5OsmE+6LjxtqTbMsgpljnHuhwjWPzDnR6An7sUhR6A0A==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@latus/core" "1.0.0"
|
"@latus/core" "1.0.0"
|
||||||
"@neutrinojs/web" "^9.1.0"
|
"@neutrinojs/web" "^9.1.0"
|
||||||
|
@ -960,8 +968,8 @@
|
||||||
|
|
||||||
"@latus/react@^1.0.0":
|
"@latus/react@^1.0.0":
|
||||||
version "1.0.0"
|
version "1.0.0"
|
||||||
resolved "https://npm.i12e.cha0s.io/@latus%2freact/-/react-1.0.0.tgz#dea047ecac755f95d503e552163c25cb9e138f7f"
|
resolved "https://npm.i12e.cha0s.io/@latus%2freact/-/react-1.0.0.tgz#e68c8cd7015725c17a2aa18aebff52b39bbecca5"
|
||||||
integrity sha512-FK2P7L9H6OfwmS7yci9PbFbhon9m4+jHkcSsxAqLm3+5CgmMQmmVFqhkLBKPzumqwwV49579Fl2cooEH6G7k3A==
|
integrity sha512-ywXWx21xcJvVfp67Mj6rIaw4p9wepDDmRF0zzhyaJbhAu3weJ5oK9yd3IwWEYONtc/lzblak+3/fEaK+GFM01Q==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@neutrinojs/react" "^9.4.0"
|
"@neutrinojs/react" "^9.4.0"
|
||||||
debug "4.3.1"
|
debug "4.3.1"
|
||||||
|
@ -993,8 +1001,8 @@
|
||||||
|
|
||||||
"@latus/socket@^1.0.0":
|
"@latus/socket@^1.0.0":
|
||||||
version "1.0.0"
|
version "1.0.0"
|
||||||
resolved "https://npm.i12e.cha0s.io/@latus%2fsocket/-/socket-1.0.0.tgz#751037f2a2a2ea51618060ad3d15b86e9399ecdb"
|
resolved "https://npm.i12e.cha0s.io/@latus%2fsocket/-/socket-1.0.0.tgz#75e95bb522fbec52d94d72ae4a33dab75e47cb5e"
|
||||||
integrity sha512-/EiO7DVuYM0Iw0RqKP/sYpvyBA7Z8nrrToALtpqSWZaZ3gHrsP/9u6sflDgbL8RUlFrRPZcpJBDVmOMF1mUAVw==
|
integrity sha512-UGmRxwkL1ZKT3oZUnQlsJSJOKAlJeaxdVS0VDTo1HBMneYP0HwoMeh7cJ7wWzV34Uj7/FhG6PWwwTz3wFz/c3A==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@latus/core" "^1.0.0"
|
"@latus/core" "^1.0.0"
|
||||||
"@latus/http" "^1.0.0"
|
"@latus/http" "^1.0.0"
|
||||||
|
@ -1019,14 +1027,18 @@
|
||||||
passport "0.4.1"
|
passport "0.4.1"
|
||||||
passport-local "^1.0.0"
|
passport-local "^1.0.0"
|
||||||
|
|
||||||
"@neutrinojs/airbnb-base@^9.1.0":
|
"@neutrinojs/airbnb@^9.1.0":
|
||||||
version "9.4.0"
|
version "9.4.0"
|
||||||
resolved "https://npm.i12e.cha0s.io/@neutrinojs%2fairbnb-base/-/airbnb-base-9.4.0.tgz#20af4c27ee7b8ec520b7bb781ca79d1ee118034f"
|
resolved "https://npm.i12e.cha0s.io/@neutrinojs%2fairbnb/-/airbnb-9.4.0.tgz#de7dc8413e4332e95ae4ba7506b81661191f2493"
|
||||||
integrity sha512-CYZ1dNhIzjEcje3cIHS0zrZ+2kjoDViuUhFOBPn6eGrOQp1G3l5E+QwhNhJii66a009dtUXuUesxfrlgr9H8vw==
|
integrity sha512-EjV/B906d1yRHKu1DJilS6cNejNjCvSf3dfHxYqe5tldMVZTlgE57+PVSG57RDvdoxq8zRMQwmBAbgn7keoh6w==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@neutrinojs/eslint" "9.4.0"
|
"@neutrinojs/eslint" "9.4.0"
|
||||||
|
eslint-config-airbnb "^18.2.0"
|
||||||
eslint-config-airbnb-base "^14.2.0"
|
eslint-config-airbnb-base "^14.2.0"
|
||||||
eslint-plugin-import "^2.22.0"
|
eslint-plugin-import "^2.22.0"
|
||||||
|
eslint-plugin-jsx-a11y "^6.3.1"
|
||||||
|
eslint-plugin-react "^7.20.6"
|
||||||
|
eslint-plugin-react-hooks "^4.1.0"
|
||||||
|
|
||||||
"@neutrinojs/banner@9.4.0":
|
"@neutrinojs/banner@9.4.0":
|
||||||
version "9.4.0"
|
version "9.4.0"
|
||||||
|
@ -1170,6 +1182,69 @@
|
||||||
babel-merge "^3.0.0"
|
babel-merge "^3.0.0"
|
||||||
deepmerge "^1.5.2"
|
deepmerge "^1.5.2"
|
||||||
|
|
||||||
|
"@reddichat/app@^1.0.0":
|
||||||
|
version "1.0.0"
|
||||||
|
resolved "https://npm.i12e.cha0s.io/@reddichat%2fapp/-/app-1.0.0.tgz#594d87b240e9635b7503c0f665d36d0c9c55cee6"
|
||||||
|
integrity sha512-QEPIw4WSOfsspHlXeZSRbf1vKSazkySn8RWlhZh+wvyRgK+5RLyYCQdYn1VizL3mykKAsHCbXEkjHAt0B/TV3Q==
|
||||||
|
dependencies:
|
||||||
|
"@reddichat/state" "^1.0.0"
|
||||||
|
"@reduxjs/toolkit" "^1.5.0"
|
||||||
|
connected-react-router "^6.8.0"
|
||||||
|
debug "4.3.1"
|
||||||
|
|
||||||
|
"@reddichat/chat@^1.0.0":
|
||||||
|
version "1.0.0"
|
||||||
|
resolved "https://npm.i12e.cha0s.io/@reddichat%2fchat/-/chat-1.0.0.tgz#007e210ef28021c4c5846f7d686ba83ad875f248"
|
||||||
|
integrity sha512-WR32qj/o/TwFcGiqFxIGzxwjRTfReTUneu5aPkGwxVomUg/fjUhxhfEVgtEFR/FMPooF6eB+yr2YlCneHHvtcg==
|
||||||
|
dependencies:
|
||||||
|
"@latus/core" "^1.0.0"
|
||||||
|
"@latus/db" "^1.0.0"
|
||||||
|
"@latus/governor" "^1.0.0"
|
||||||
|
"@latus/socket" "^1.0.0"
|
||||||
|
"@reddichat/core" "^1.0.0"
|
||||||
|
"@reddichat/state" "^1.0.0"
|
||||||
|
"@reduxjs/toolkit" "^1.5.0"
|
||||||
|
debug "4.3.1"
|
||||||
|
uuid "^8.3.1"
|
||||||
|
|
||||||
|
"@reddichat/core@^1.0.0":
|
||||||
|
version "1.0.0"
|
||||||
|
resolved "https://npm.i12e.cha0s.io/@reddichat%2fcore/-/core-1.0.0.tgz#f8f7b5350bbfb985697144681e6f8509545be745"
|
||||||
|
integrity sha512-1f03IIacxnzYrkCJZlQRrBX2BPiHjGdeo+PCOxgfqbmgp3xUANdfGY4G2hXzNy+WNE8+pH4xdZ6UEXqtFq9cmw==
|
||||||
|
dependencies:
|
||||||
|
debug "4.3.1"
|
||||||
|
|
||||||
|
"@reddichat/state@^1.0.0":
|
||||||
|
version "1.0.0"
|
||||||
|
resolved "https://npm.i12e.cha0s.io/@reddichat%2fstate/-/state-1.0.0.tgz#88695971edef8996dc243cec96dad2793b82a961"
|
||||||
|
integrity sha512-jtIGiO71Cqt/ZZtrdmCEHf/6k2EMjB2baklC7HC7MafbQKxk014ZikwvVtm74OAFapgv4qayRqOvw1gOjM91nw==
|
||||||
|
dependencies:
|
||||||
|
"@latus/core" "^1.0.0"
|
||||||
|
"@latus/db" "^1.0.0"
|
||||||
|
"@latus/redis" "^1.0.0"
|
||||||
|
"@reddichat/core" "^1.0.0"
|
||||||
|
"@reduxjs/toolkit" "^1.5.0"
|
||||||
|
connected-react-router "^6.8.0"
|
||||||
|
debug "4.3.1"
|
||||||
|
deepmerge "^4.2.2"
|
||||||
|
lodash.throttle "^4.1.1"
|
||||||
|
redux "^4.0.5"
|
||||||
|
|
||||||
|
"@reddichat/user@^1.0.0":
|
||||||
|
version "1.0.0"
|
||||||
|
resolved "https://npm.i12e.cha0s.io/@reddichat%2fuser/-/user-1.0.0.tgz#c13b89d14797b49cdc076d4dcc0a31b055a7e52b"
|
||||||
|
integrity sha512-Hik217AJ7Vu26I+ehLifHmLCN46W1yolKXB/FJFUfbP8Nz2ZtUpGrZNYGIgmND/4ZmuW1B4r7v6njBQPGioYgg==
|
||||||
|
dependencies:
|
||||||
|
"@latus/db" "^1.0.0"
|
||||||
|
"@latus/socket" "^1.0.0"
|
||||||
|
"@reddichat/chat" "^1.0.0"
|
||||||
|
"@reddichat/core" "^1.0.0"
|
||||||
|
"@reddichat/state" "^1.0.0"
|
||||||
|
"@reduxjs/toolkit" "^1.5.0"
|
||||||
|
connected-react-router "^6.8.0"
|
||||||
|
debug "4.3.1"
|
||||||
|
deepmerge "^4.2.2"
|
||||||
|
|
||||||
"@reduxjs/toolkit@^1.5.0":
|
"@reduxjs/toolkit@^1.5.0":
|
||||||
version "1.5.0"
|
version "1.5.0"
|
||||||
resolved "https://npm.i12e.cha0s.io/@reduxjs%2ftoolkit/-/toolkit-1.5.0.tgz#1025c1ccb224d1fc06d8d98a61f6717d57e6d477"
|
resolved "https://npm.i12e.cha0s.io/@reduxjs%2ftoolkit/-/toolkit-1.5.0.tgz#1025c1ccb224d1fc06d8d98a61f6717d57e6d477"
|
||||||
|
@ -1566,6 +1641,14 @@ argparse@^1.0.7:
|
||||||
dependencies:
|
dependencies:
|
||||||
sprintf-js "~1.0.2"
|
sprintf-js "~1.0.2"
|
||||||
|
|
||||||
|
aria-query@^4.2.2:
|
||||||
|
version "4.2.2"
|
||||||
|
resolved "https://npm.i12e.cha0s.io/aria-query/-/aria-query-4.2.2.tgz#0d2ca6c9aceb56b8977e9fed6aed7e15bbd2f83b"
|
||||||
|
integrity sha512-o/HelwhuKpTj/frsOsbNLNgnNGVIFsVP/SW2BSF14gVl7kAfMOJ6/8wUAUvG1R1NHKrfG+2sHZTu0yauT1qBrA==
|
||||||
|
dependencies:
|
||||||
|
"@babel/runtime" "^7.10.2"
|
||||||
|
"@babel/runtime-corejs3" "^7.10.2"
|
||||||
|
|
||||||
arr-diff@^4.0.0:
|
arr-diff@^4.0.0:
|
||||||
version "4.0.0"
|
version "4.0.0"
|
||||||
resolved "https://npm.i12e.cha0s.io/arr-diff/-/arr-diff-4.0.0.tgz#d6461074febfec71e7e15235761a329a5dc7c520"
|
resolved "https://npm.i12e.cha0s.io/arr-diff/-/arr-diff-4.0.0.tgz#d6461074febfec71e7e15235761a329a5dc7c520"
|
||||||
|
@ -1688,6 +1771,11 @@ assign-symbols@^1.0.0:
|
||||||
resolved "https://npm.i12e.cha0s.io/assign-symbols/-/assign-symbols-1.0.0.tgz#59667f41fadd4f20ccbc2bb96b8d4f7f78ec0367"
|
resolved "https://npm.i12e.cha0s.io/assign-symbols/-/assign-symbols-1.0.0.tgz#59667f41fadd4f20ccbc2bb96b8d4f7f78ec0367"
|
||||||
integrity sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=
|
integrity sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=
|
||||||
|
|
||||||
|
ast-types-flow@^0.0.7:
|
||||||
|
version "0.0.7"
|
||||||
|
resolved "https://npm.i12e.cha0s.io/ast-types-flow/-/ast-types-flow-0.0.7.tgz#f70b735c6bca1a5c9c22d982c3e39e7feba3bdad"
|
||||||
|
integrity sha1-9wtzXGvKGlycItmCw+Oef+ujva0=
|
||||||
|
|
||||||
ast-types@0.9.6:
|
ast-types@0.9.6:
|
||||||
version "0.9.6"
|
version "0.9.6"
|
||||||
resolved "https://npm.i12e.cha0s.io/ast-types/-/ast-types-0.9.6.tgz#102c9e9e9005d3e7e3829bf0c4fa24ee862ee9b9"
|
resolved "https://npm.i12e.cha0s.io/ast-types/-/ast-types-0.9.6.tgz#102c9e9e9005d3e7e3829bf0c4fa24ee862ee9b9"
|
||||||
|
@ -1740,6 +1828,16 @@ aws4@^1.8.0:
|
||||||
resolved "https://npm.i12e.cha0s.io/aws4/-/aws4-1.11.0.tgz#d61f46d83b2519250e2784daf5b09479a8b41c59"
|
resolved "https://npm.i12e.cha0s.io/aws4/-/aws4-1.11.0.tgz#d61f46d83b2519250e2784daf5b09479a8b41c59"
|
||||||
integrity sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA==
|
integrity sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA==
|
||||||
|
|
||||||
|
axe-core@^4.0.2:
|
||||||
|
version "4.1.1"
|
||||||
|
resolved "https://npm.i12e.cha0s.io/axe-core/-/axe-core-4.1.1.tgz#70a7855888e287f7add66002211a423937063eaf"
|
||||||
|
integrity sha512-5Kgy8Cz6LPC9DJcNb3yjAXTu3XihQgEdnIg50c//zOC/MyLP0Clg+Y8Sh9ZjjnvBrDZU4DgXS9C3T9r4/scGZQ==
|
||||||
|
|
||||||
|
axobject-query@^2.2.0:
|
||||||
|
version "2.2.0"
|
||||||
|
resolved "https://npm.i12e.cha0s.io/axobject-query/-/axobject-query-2.2.0.tgz#943d47e10c0b704aa42275e20edf3722648989be"
|
||||||
|
integrity sha512-Td525n+iPOOyUQIeBfcASuG6uJsDOITl7Mds5gFyerkWiX7qhUTdYUBlSgNMyVqtSJqwpt1kXGLdUt6SykLMRA==
|
||||||
|
|
||||||
babel-eslint@^10.1.0:
|
babel-eslint@^10.1.0:
|
||||||
version "10.1.0"
|
version "10.1.0"
|
||||||
resolved "https://npm.i12e.cha0s.io/babel-eslint/-/babel-eslint-10.1.0.tgz#6968e568a910b78fb3779cdd8b6ac2f479943232"
|
resolved "https://npm.i12e.cha0s.io/babel-eslint/-/babel-eslint-10.1.0.tgz#6968e568a910b78fb3779cdd8b6ac2f479943232"
|
||||||
|
@ -2514,6 +2612,13 @@ connect-redis@^5.0.0:
|
||||||
resolved "https://npm.i12e.cha0s.io/connect-redis/-/connect-redis-5.0.0.tgz#68fe890117e761ee98e13a14b835338bd6bf044c"
|
resolved "https://npm.i12e.cha0s.io/connect-redis/-/connect-redis-5.0.0.tgz#68fe890117e761ee98e13a14b835338bd6bf044c"
|
||||||
integrity sha512-R4nTW5uXeG5s6zr/q4abmtcdloglZrL/A3cpa0JU0RLFJU4mTR553HUY8OZ0ngeySkGDclwQ5xmCcjjKkxdOSg==
|
integrity sha512-R4nTW5uXeG5s6zr/q4abmtcdloglZrL/A3cpa0JU0RLFJU4mTR553HUY8OZ0ngeySkGDclwQ5xmCcjjKkxdOSg==
|
||||||
|
|
||||||
|
connected-react-router@^6.8.0:
|
||||||
|
version "6.8.0"
|
||||||
|
resolved "https://npm.i12e.cha0s.io/connected-react-router/-/connected-react-router-6.8.0.tgz#ddc687b31d498322445d235d660798489fa56cae"
|
||||||
|
integrity sha512-E64/6krdJM3Ag3MMmh2nKPtMbH15s3JQDuaYJvOVXzu6MbHbDyIvuwLOyhQIuP4Om9zqEfZYiVyflROibSsONg==
|
||||||
|
dependencies:
|
||||||
|
prop-types "^15.7.2"
|
||||||
|
|
||||||
console-browserify@^1.1.0:
|
console-browserify@^1.1.0:
|
||||||
version "1.2.0"
|
version "1.2.0"
|
||||||
resolved "https://npm.i12e.cha0s.io/console-browserify/-/console-browserify-1.2.0.tgz#67063cef57ceb6cf4993a2ab3a55840ae8c49336"
|
resolved "https://npm.i12e.cha0s.io/console-browserify/-/console-browserify-1.2.0.tgz#67063cef57ceb6cf4993a2ab3a55840ae8c49336"
|
||||||
|
@ -2593,6 +2698,11 @@ core-js-compat@^3.7.0:
|
||||||
browserslist "^4.15.0"
|
browserslist "^4.15.0"
|
||||||
semver "7.0.0"
|
semver "7.0.0"
|
||||||
|
|
||||||
|
core-js-pure@^3.0.0:
|
||||||
|
version "3.8.1"
|
||||||
|
resolved "https://npm.i12e.cha0s.io/core-js-pure/-/core-js-pure-3.8.1.tgz#23f84048f366fdfcf52d3fd1c68fec349177d119"
|
||||||
|
integrity sha512-Se+LaxqXlVXGvmexKGPvnUIYC1jwXu1H6Pkyb3uBM5d8/NELMYCHs/4/roD7721NxrTLyv7e5nXd5/QLBO+10g==
|
||||||
|
|
||||||
core-js@^2.4.0:
|
core-js@^2.4.0:
|
||||||
version "2.6.12"
|
version "2.6.12"
|
||||||
resolved "https://npm.i12e.cha0s.io/core-js/-/core-js-2.6.12.tgz#d9333dfa7b065e347cc5682219d6f690859cc2ec"
|
resolved "https://npm.i12e.cha0s.io/core-js/-/core-js-2.6.12.tgz#d9333dfa7b065e347cc5682219d6f690859cc2ec"
|
||||||
|
@ -2732,6 +2842,11 @@ cyclist@^1.0.1:
|
||||||
resolved "https://npm.i12e.cha0s.io/cyclist/-/cyclist-1.0.1.tgz#596e9698fd0c80e12038c2b82d6eb1b35b6224d9"
|
resolved "https://npm.i12e.cha0s.io/cyclist/-/cyclist-1.0.1.tgz#596e9698fd0c80e12038c2b82d6eb1b35b6224d9"
|
||||||
integrity sha1-WW6WmP0MgOEgOMK4LW6xs1tiJNk=
|
integrity sha1-WW6WmP0MgOEgOMK4LW6xs1tiJNk=
|
||||||
|
|
||||||
|
damerau-levenshtein@^1.0.6:
|
||||||
|
version "1.0.6"
|
||||||
|
resolved "https://npm.i12e.cha0s.io/damerau-levenshtein/-/damerau-levenshtein-1.0.6.tgz#143c1641cb3d85c60c32329e26899adea8701791"
|
||||||
|
integrity sha512-JVrozIeElnj3QzfUIt8tB8YMluBJom4Vw9qTPpjGYQ9fYlB3D/rb6OordUxf3xeFB35LKWs0xqcO5U6ySvBtug==
|
||||||
|
|
||||||
dashdash@^1.12.0:
|
dashdash@^1.12.0:
|
||||||
version "1.14.1"
|
version "1.14.1"
|
||||||
resolved "https://npm.i12e.cha0s.io/dashdash/-/dashdash-1.14.1.tgz#853cfa0f7cbe2fed5de20326b8dd581035f6e2f0"
|
resolved "https://npm.i12e.cha0s.io/dashdash/-/dashdash-1.14.1.tgz#853cfa0f7cbe2fed5de20326b8dd581035f6e2f0"
|
||||||
|
@ -2816,6 +2931,11 @@ deepmerge@^2.2.1:
|
||||||
resolved "https://npm.i12e.cha0s.io/deepmerge/-/deepmerge-2.2.1.tgz#5d3ff22a01c00f645405a2fbc17d0778a1801170"
|
resolved "https://npm.i12e.cha0s.io/deepmerge/-/deepmerge-2.2.1.tgz#5d3ff22a01c00f645405a2fbc17d0778a1801170"
|
||||||
integrity sha512-R9hc1Xa/NOBi9WRVUWg19rl1UB7Tt4kuPd+thNJgFZoxXsTz7ncaPaeIm+40oSGuP33DfMb4sZt1QIGiJzC4EA==
|
integrity sha512-R9hc1Xa/NOBi9WRVUWg19rl1UB7Tt4kuPd+thNJgFZoxXsTz7ncaPaeIm+40oSGuP33DfMb4sZt1QIGiJzC4EA==
|
||||||
|
|
||||||
|
deepmerge@^4.2.2:
|
||||||
|
version "4.2.2"
|
||||||
|
resolved "https://npm.i12e.cha0s.io/deepmerge/-/deepmerge-4.2.2.tgz#44d2ea3679b8f4d4ffba33f03d865fc1e7bf4955"
|
||||||
|
integrity sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==
|
||||||
|
|
||||||
default-gateway@^4.2.0:
|
default-gateway@^4.2.0:
|
||||||
version "4.2.0"
|
version "4.2.0"
|
||||||
resolved "https://npm.i12e.cha0s.io/default-gateway/-/default-gateway-4.2.0.tgz#167104c7500c2115f6dd69b0a536bb8ed720552b"
|
resolved "https://npm.i12e.cha0s.io/default-gateway/-/default-gateway-4.2.0.tgz#167104c7500c2115f6dd69b0a536bb8ed720552b"
|
||||||
|
@ -3070,9 +3190,9 @@ ee-first@1.1.1:
|
||||||
integrity sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=
|
integrity sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=
|
||||||
|
|
||||||
electron-to-chromium@^1.3.612:
|
electron-to-chromium@^1.3.612:
|
||||||
version "1.3.616"
|
version "1.3.619"
|
||||||
resolved "https://npm.i12e.cha0s.io/electron-to-chromium/-/electron-to-chromium-1.3.616.tgz#de63d1c79bb8eb61168774df0c11c9e1af69f9e8"
|
resolved "https://npm.i12e.cha0s.io/electron-to-chromium/-/electron-to-chromium-1.3.619.tgz#4dc529ae802f5c9c31e7eea830144340539b62b4"
|
||||||
integrity sha512-CI8L38UN2BEnqXw3/oRIQTmde0LiSeqWSRlPA42ZTYgJQ8fYenzAM2Z3ni+jtILTcrs5aiXZCGJ96Pm+3/yGyQ==
|
integrity sha512-WFGatwtk7Fw0QcKCZzfGD72hvbcXV8kLY8aFuj0Ip0QRnOtyLYMsc+wXbSjb2w4lk1gcAeNU1/lQ20A+tvuypQ==
|
||||||
|
|
||||||
elliptic@^6.5.3:
|
elliptic@^6.5.3:
|
||||||
version "6.5.3"
|
version "6.5.3"
|
||||||
|
@ -3097,6 +3217,11 @@ emoji-regex@^8.0.0:
|
||||||
resolved "https://npm.i12e.cha0s.io/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37"
|
resolved "https://npm.i12e.cha0s.io/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37"
|
||||||
integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==
|
integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==
|
||||||
|
|
||||||
|
emoji-regex@^9.0.0:
|
||||||
|
version "9.2.0"
|
||||||
|
resolved "https://npm.i12e.cha0s.io/emoji-regex/-/emoji-regex-9.2.0.tgz#a26da8e832b16a9753309f25e35e3c0efb9a066a"
|
||||||
|
integrity sha512-DNc3KFPK18bPdElMJnf/Pkv5TXhxFU3YFDEuGLDRtPmV4rkmCjBkCSEp22u6rBHdSN9Vlp/GK7k98prmE1Jgug==
|
||||||
|
|
||||||
emojis-list@^3.0.0:
|
emojis-list@^3.0.0:
|
||||||
version "3.0.0"
|
version "3.0.0"
|
||||||
resolved "https://npm.i12e.cha0s.io/emojis-list/-/emojis-list-3.0.0.tgz#5570662046ad29e2e916e71aae260abdff4f6a78"
|
resolved "https://npm.i12e.cha0s.io/emojis-list/-/emojis-list-3.0.0.tgz#5570662046ad29e2e916e71aae260abdff4f6a78"
|
||||||
|
@ -3268,7 +3393,7 @@ escape-string-regexp@^1.0.2, escape-string-regexp@^1.0.5:
|
||||||
resolved "https://npm.i12e.cha0s.io/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4"
|
resolved "https://npm.i12e.cha0s.io/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4"
|
||||||
integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=
|
integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=
|
||||||
|
|
||||||
eslint-config-airbnb-base@^14.2.0:
|
eslint-config-airbnb-base@^14.2.0, eslint-config-airbnb-base@^14.2.1:
|
||||||
version "14.2.1"
|
version "14.2.1"
|
||||||
resolved "https://npm.i12e.cha0s.io/eslint-config-airbnb-base/-/eslint-config-airbnb-base-14.2.1.tgz#8a2eb38455dc5a312550193b319cdaeef042cd1e"
|
resolved "https://npm.i12e.cha0s.io/eslint-config-airbnb-base/-/eslint-config-airbnb-base-14.2.1.tgz#8a2eb38455dc5a312550193b319cdaeef042cd1e"
|
||||||
integrity sha512-GOrQyDtVEc1Xy20U7vsB2yAoB4nBlfH5HZJeatRXHleO+OS5Ot+MWij4Dpltw4/DyIkqUfqz1epfhVR5XWWQPA==
|
integrity sha512-GOrQyDtVEc1Xy20U7vsB2yAoB4nBlfH5HZJeatRXHleO+OS5Ot+MWij4Dpltw4/DyIkqUfqz1epfhVR5XWWQPA==
|
||||||
|
@ -3277,6 +3402,15 @@ eslint-config-airbnb-base@^14.2.0:
|
||||||
object.assign "^4.1.2"
|
object.assign "^4.1.2"
|
||||||
object.entries "^1.1.2"
|
object.entries "^1.1.2"
|
||||||
|
|
||||||
|
eslint-config-airbnb@^18.2.0:
|
||||||
|
version "18.2.1"
|
||||||
|
resolved "https://npm.i12e.cha0s.io/eslint-config-airbnb/-/eslint-config-airbnb-18.2.1.tgz#b7fe2b42f9f8173e825b73c8014b592e449c98d9"
|
||||||
|
integrity sha512-glZNDEZ36VdlZWoxn/bUR1r/sdFKPd1mHPbqUtkctgNG4yT2DLLtJ3D+yCV+jzZCc2V1nBVkmdknOJBZ5Hc0fg==
|
||||||
|
dependencies:
|
||||||
|
eslint-config-airbnb-base "^14.2.1"
|
||||||
|
object.assign "^4.1.2"
|
||||||
|
object.entries "^1.1.2"
|
||||||
|
|
||||||
eslint-import-resolver-node@^0.3.4:
|
eslint-import-resolver-node@^0.3.4:
|
||||||
version "0.3.4"
|
version "0.3.4"
|
||||||
resolved "https://npm.i12e.cha0s.io/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.4.tgz#85ffa81942c25012d8231096ddf679c03042c717"
|
resolved "https://npm.i12e.cha0s.io/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.4.tgz#85ffa81942c25012d8231096ddf679c03042c717"
|
||||||
|
@ -3346,6 +3480,23 @@ eslint-plugin-import@^2.22.0:
|
||||||
resolve "^1.17.0"
|
resolve "^1.17.0"
|
||||||
tsconfig-paths "^3.9.0"
|
tsconfig-paths "^3.9.0"
|
||||||
|
|
||||||
|
eslint-plugin-jsx-a11y@^6.3.1:
|
||||||
|
version "6.4.1"
|
||||||
|
resolved "https://npm.i12e.cha0s.io/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.4.1.tgz#a2d84caa49756942f42f1ffab9002436391718fd"
|
||||||
|
integrity sha512-0rGPJBbwHoGNPU73/QCLP/vveMlM1b1Z9PponxO87jfr6tuH5ligXbDT6nHSSzBC8ovX2Z+BQu7Bk5D/Xgq9zg==
|
||||||
|
dependencies:
|
||||||
|
"@babel/runtime" "^7.11.2"
|
||||||
|
aria-query "^4.2.2"
|
||||||
|
array-includes "^3.1.1"
|
||||||
|
ast-types-flow "^0.0.7"
|
||||||
|
axe-core "^4.0.2"
|
||||||
|
axobject-query "^2.2.0"
|
||||||
|
damerau-levenshtein "^1.0.6"
|
||||||
|
emoji-regex "^9.0.0"
|
||||||
|
has "^1.0.3"
|
||||||
|
jsx-ast-utils "^3.1.0"
|
||||||
|
language-tags "^1.0.5"
|
||||||
|
|
||||||
eslint-plugin-react-hooks@^4.1.0:
|
eslint-plugin-react-hooks@^4.1.0:
|
||||||
version "4.2.0"
|
version "4.2.0"
|
||||||
resolved "https://npm.i12e.cha0s.io/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.2.0.tgz#8c229c268d468956334c943bb45fc860280f5556"
|
resolved "https://npm.i12e.cha0s.io/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.2.0.tgz#8c229c268d468956334c943bb45fc860280f5556"
|
||||||
|
@ -4250,7 +4401,7 @@ he@1.2.x, he@^1.2.0:
|
||||||
resolved "https://npm.i12e.cha0s.io/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f"
|
resolved "https://npm.i12e.cha0s.io/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f"
|
||||||
integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==
|
integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==
|
||||||
|
|
||||||
history@^4.9.0:
|
history@^4.7.2, history@^4.9.0:
|
||||||
version "4.10.1"
|
version "4.10.1"
|
||||||
resolved "https://npm.i12e.cha0s.io/history/-/history-4.10.1.tgz#33371a65e3a83b267434e2b3f3b1b4c58aad4cf3"
|
resolved "https://npm.i12e.cha0s.io/history/-/history-4.10.1.tgz#33371a65e3a83b267434e2b3f3b1b4c58aad4cf3"
|
||||||
integrity sha512-36nwAD620w12kuzPAsyINPWJqlNbij+hpK1k9XRloDtym8mxzGYl2c17LnV6IAGB2Dmg4tEa7G7DlawS0+qjew==
|
integrity sha512-36nwAD620w12kuzPAsyINPWJqlNbij+hpK1k9XRloDtym8mxzGYl2c17LnV6IAGB2Dmg4tEa7G7DlawS0+qjew==
|
||||||
|
@ -4271,7 +4422,7 @@ hmac-drbg@^1.0.0:
|
||||||
minimalistic-assert "^1.0.0"
|
minimalistic-assert "^1.0.0"
|
||||||
minimalistic-crypto-utils "^1.0.1"
|
minimalistic-crypto-utils "^1.0.1"
|
||||||
|
|
||||||
hoist-non-react-statics@^3.1.0, hoist-non-react-statics@^3.3.0:
|
hoist-non-react-statics@^3.1.0, hoist-non-react-statics@^3.3.0, hoist-non-react-statics@^3.3.2:
|
||||||
version "3.3.2"
|
version "3.3.2"
|
||||||
resolved "https://npm.i12e.cha0s.io/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz#ece0acaf71d62c2969c2ec59feff42a4b1a85b45"
|
resolved "https://npm.i12e.cha0s.io/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz#ece0acaf71d62c2969c2ec59feff42a4b1a85b45"
|
||||||
integrity sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==
|
integrity sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==
|
||||||
|
@ -4952,7 +5103,7 @@ js-base64@^2.1.8:
|
||||||
resolved "https://npm.i12e.cha0s.io/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499"
|
resolved "https://npm.i12e.cha0s.io/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499"
|
||||||
integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==
|
integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==
|
||||||
|
|
||||||
js-yaml@3.14.0, js-yaml@^3.13.1:
|
js-yaml@3.14.0:
|
||||||
version "3.14.0"
|
version "3.14.0"
|
||||||
resolved "https://npm.i12e.cha0s.io/js-yaml/-/js-yaml-3.14.0.tgz#a7a34170f26a21bb162424d8adacb4113a69e482"
|
resolved "https://npm.i12e.cha0s.io/js-yaml/-/js-yaml-3.14.0.tgz#a7a34170f26a21bb162424d8adacb4113a69e482"
|
||||||
integrity sha512-/4IbIeHcD9VMHFqDR/gQ7EdZdLimOvW2DdcxFjdyyZ9NsbS+ccrXqVWDtab/lRl5AlUqmpBx8EhPaWR+OtY17A==
|
integrity sha512-/4IbIeHcD9VMHFqDR/gQ7EdZdLimOvW2DdcxFjdyyZ9NsbS+ccrXqVWDtab/lRl5AlUqmpBx8EhPaWR+OtY17A==
|
||||||
|
@ -4960,6 +5111,14 @@ js-yaml@3.14.0, js-yaml@^3.13.1:
|
||||||
argparse "^1.0.7"
|
argparse "^1.0.7"
|
||||||
esprima "^4.0.0"
|
esprima "^4.0.0"
|
||||||
|
|
||||||
|
js-yaml@^3.13.1:
|
||||||
|
version "3.14.1"
|
||||||
|
resolved "https://npm.i12e.cha0s.io/js-yaml/-/js-yaml-3.14.1.tgz#dae812fdb3825fa306609a8717383c50c36a0537"
|
||||||
|
integrity sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==
|
||||||
|
dependencies:
|
||||||
|
argparse "^1.0.7"
|
||||||
|
esprima "^4.0.0"
|
||||||
|
|
||||||
jsbn@~0.1.0:
|
jsbn@~0.1.0:
|
||||||
version "0.1.1"
|
version "0.1.1"
|
||||||
resolved "https://npm.i12e.cha0s.io/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513"
|
resolved "https://npm.i12e.cha0s.io/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513"
|
||||||
|
@ -5036,7 +5195,7 @@ jsprim@^1.2.2:
|
||||||
json-schema "0.2.3"
|
json-schema "0.2.3"
|
||||||
verror "1.10.0"
|
verror "1.10.0"
|
||||||
|
|
||||||
"jsx-ast-utils@^2.4.1 || ^3.0.0":
|
"jsx-ast-utils@^2.4.1 || ^3.0.0", jsx-ast-utils@^3.1.0:
|
||||||
version "3.1.0"
|
version "3.1.0"
|
||||||
resolved "https://npm.i12e.cha0s.io/jsx-ast-utils/-/jsx-ast-utils-3.1.0.tgz#642f1d7b88aa6d7eb9d8f2210e166478444fa891"
|
resolved "https://npm.i12e.cha0s.io/jsx-ast-utils/-/jsx-ast-utils-3.1.0.tgz#642f1d7b88aa6d7eb9d8f2210e166478444fa891"
|
||||||
integrity sha512-d4/UOjg+mxAWxCiF0c5UTSwyqbchkbqCvK87aBovhnh8GtysTjWmgC63tY0cJx/HzGgm9qnA147jVBdpOiQ2RA==
|
integrity sha512-d4/UOjg+mxAWxCiF0c5UTSwyqbchkbqCvK87aBovhnh8GtysTjWmgC63tY0cJx/HzGgm9qnA147jVBdpOiQ2RA==
|
||||||
|
@ -5078,6 +5237,18 @@ klona@^2.0.4:
|
||||||
resolved "https://npm.i12e.cha0s.io/klona/-/klona-2.0.4.tgz#7bb1e3affb0cb8624547ef7e8f6708ea2e39dfc0"
|
resolved "https://npm.i12e.cha0s.io/klona/-/klona-2.0.4.tgz#7bb1e3affb0cb8624547ef7e8f6708ea2e39dfc0"
|
||||||
integrity sha512-ZRbnvdg/NxqzC7L9Uyqzf4psi1OM4Cuc+sJAkQPjO6XkQIJTNbfK2Rsmbw8fx1p2mkZdp2FZYo2+LwXYY/uwIA==
|
integrity sha512-ZRbnvdg/NxqzC7L9Uyqzf4psi1OM4Cuc+sJAkQPjO6XkQIJTNbfK2Rsmbw8fx1p2mkZdp2FZYo2+LwXYY/uwIA==
|
||||||
|
|
||||||
|
language-subtag-registry@~0.3.2:
|
||||||
|
version "0.3.21"
|
||||||
|
resolved "https://npm.i12e.cha0s.io/language-subtag-registry/-/language-subtag-registry-0.3.21.tgz#04ac218bea46f04cb039084602c6da9e788dd45a"
|
||||||
|
integrity sha512-L0IqwlIXjilBVVYKFT37X9Ih11Um5NEl9cbJIuU/SwP/zEEAbBPOnEeeuxVMf45ydWQRDQN3Nqc96OgbH1K+Pg==
|
||||||
|
|
||||||
|
language-tags@^1.0.5:
|
||||||
|
version "1.0.5"
|
||||||
|
resolved "https://npm.i12e.cha0s.io/language-tags/-/language-tags-1.0.5.tgz#d321dbc4da30ba8bf3024e040fa5c14661f9193a"
|
||||||
|
integrity sha1-0yHbxNowuovzAk4ED6XBRmH5GTo=
|
||||||
|
dependencies:
|
||||||
|
language-subtag-registry "~0.3.2"
|
||||||
|
|
||||||
levn@^0.3.0, levn@~0.3.0:
|
levn@^0.3.0, levn@~0.3.0:
|
||||||
version "0.3.0"
|
version "0.3.0"
|
||||||
resolved "https://npm.i12e.cha0s.io/levn/-/levn-0.3.0.tgz#3b09924edf9f083c0490fdd4c0bc4421e04764ee"
|
resolved "https://npm.i12e.cha0s.io/levn/-/levn-0.3.0.tgz#3b09924edf9f083c0490fdd4c0bc4421e04764ee"
|
||||||
|
@ -5188,6 +5359,11 @@ lodash.templatesettings@^4.0.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
lodash._reinterpolate "^3.0.0"
|
lodash._reinterpolate "^3.0.0"
|
||||||
|
|
||||||
|
lodash.throttle@^4.1.1:
|
||||||
|
version "4.1.1"
|
||||||
|
resolved "https://npm.i12e.cha0s.io/lodash.throttle/-/lodash.throttle-4.1.1.tgz#c23e91b710242ac70c37f1e1cda9274cc39bf2f4"
|
||||||
|
integrity sha1-wj6RtxAkKscMN/HhzaknTMOb8vQ=
|
||||||
|
|
||||||
lodash@^4.0.0, lodash@^4.17.11, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.20, lodash@^4.17.4, lodash@~4.17.10:
|
lodash@^4.0.0, lodash@^4.17.11, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.20, lodash@^4.17.4, lodash@~4.17.10:
|
||||||
version "4.17.20"
|
version "4.17.20"
|
||||||
resolved "https://npm.i12e.cha0s.io/lodash/-/lodash-4.17.20.tgz#b44a9b6297bcb698f1c51a3545a2b3b368d59c52"
|
resolved "https://npm.i12e.cha0s.io/lodash/-/lodash-4.17.20.tgz#b44a9b6297bcb698f1c51a3545a2b3b368d59c52"
|
||||||
|
@ -6774,7 +6950,7 @@ react-hot-loader@4.13.0, react-hot-loader@^4.13.0:
|
||||||
shallowequal "^1.1.0"
|
shallowequal "^1.1.0"
|
||||||
source-map "^0.7.3"
|
source-map "^0.7.3"
|
||||||
|
|
||||||
react-is@^16.6.0, react-is@^16.7.0, react-is@^16.8.1:
|
react-is@^16.13.1, react-is@^16.6.0, react-is@^16.7.0, react-is@^16.8.1:
|
||||||
version "16.13.1"
|
version "16.13.1"
|
||||||
resolved "https://npm.i12e.cha0s.io/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
|
resolved "https://npm.i12e.cha0s.io/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
|
||||||
integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==
|
integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==
|
||||||
|
@ -6784,6 +6960,17 @@ react-lifecycles-compat@^3.0.4:
|
||||||
resolved "https://npm.i12e.cha0s.io/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz#4f1a273afdfc8f3488a8c516bfda78f872352362"
|
resolved "https://npm.i12e.cha0s.io/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz#4f1a273afdfc8f3488a8c516bfda78f872352362"
|
||||||
integrity sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==
|
integrity sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==
|
||||||
|
|
||||||
|
react-redux@^7.2.2:
|
||||||
|
version "7.2.2"
|
||||||
|
resolved "https://npm.i12e.cha0s.io/react-redux/-/react-redux-7.2.2.tgz#03862e803a30b6b9ef8582dadcc810947f74b736"
|
||||||
|
integrity sha512-8+CQ1EvIVFkYL/vu6Olo7JFLWop1qRUeb46sGtIMDCSpgwPQq8fPLpirIB0iTqFe9XYEFPHssdX8/UwN6pAkEA==
|
||||||
|
dependencies:
|
||||||
|
"@babel/runtime" "^7.12.1"
|
||||||
|
hoist-non-react-statics "^3.3.2"
|
||||||
|
loose-envify "^1.4.0"
|
||||||
|
prop-types "^15.7.2"
|
||||||
|
react-is "^16.13.1"
|
||||||
|
|
||||||
react-router-dom@^5.2.0:
|
react-router-dom@^5.2.0:
|
||||||
version "5.2.0"
|
version "5.2.0"
|
||||||
resolved "https://npm.i12e.cha0s.io/react-router-dom/-/react-router-dom-5.2.0.tgz#9e65a4d0c45e13289e66c7b17c7e175d0ea15662"
|
resolved "https://npm.i12e.cha0s.io/react-router-dom/-/react-router-dom-5.2.0.tgz#9e65a4d0c45e13289e66c7b17c7e175d0ea15662"
|
||||||
|
@ -6943,7 +7130,7 @@ redux-thunk@^2.3.0:
|
||||||
resolved "https://npm.i12e.cha0s.io/redux-thunk/-/redux-thunk-2.3.0.tgz#51c2c19a185ed5187aaa9a2d08b666d0d6467622"
|
resolved "https://npm.i12e.cha0s.io/redux-thunk/-/redux-thunk-2.3.0.tgz#51c2c19a185ed5187aaa9a2d08b666d0d6467622"
|
||||||
integrity sha512-km6dclyFnmcvxhAcrQV2AkZmPQjzPDjgVlQtR0EQjxZPyJ0BnMf3in1ryuR8A2qU0HldVRfxYXbFSKlI3N7Slw==
|
integrity sha512-km6dclyFnmcvxhAcrQV2AkZmPQjzPDjgVlQtR0EQjxZPyJ0BnMf3in1ryuR8A2qU0HldVRfxYXbFSKlI3N7Slw==
|
||||||
|
|
||||||
redux@^4.0.0:
|
redux@^4.0.0, redux@^4.0.5:
|
||||||
version "4.0.5"
|
version "4.0.5"
|
||||||
resolved "https://npm.i12e.cha0s.io/redux/-/redux-4.0.5.tgz#4db5de5816e17891de8a80c424232d06f051d93f"
|
resolved "https://npm.i12e.cha0s.io/redux/-/redux-4.0.5.tgz#4db5de5816e17891de8a80c424232d06f051d93f"
|
||||||
integrity sha512-VSz1uMAH24DM6MF72vcojpYPtrTUu3ByVWfPL1nPfVRb5mZVTve5GnNCUV53QM/BZ66xfWrm0CTWoM+Xlz8V1w==
|
integrity sha512-VSz1uMAH24DM6MF72vcojpYPtrTUu3ByVWfPL1nPfVRb5mZVTve5GnNCUV53QM/BZ66xfWrm0CTWoM+Xlz8V1w==
|
||||||
|
@ -8485,7 +8672,7 @@ uuid@^3.3.2, uuid@^3.4.0:
|
||||||
resolved "https://npm.i12e.cha0s.io/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee"
|
resolved "https://npm.i12e.cha0s.io/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee"
|
||||||
integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==
|
integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==
|
||||||
|
|
||||||
uuid@^8.1.0:
|
uuid@^8.1.0, uuid@^8.3.1:
|
||||||
version "8.3.1"
|
version "8.3.1"
|
||||||
resolved "https://npm.i12e.cha0s.io/uuid/-/uuid-8.3.1.tgz#2ba2e6ca000da60fce5a196954ab241131e05a31"
|
resolved "https://npm.i12e.cha0s.io/uuid/-/uuid-8.3.1.tgz#2ba2e6ca000da60fce5a196954ab241131e05a31"
|
||||||
integrity sha512-FOmRr+FmWEIG8uhZv6C2bTgEVXsHk08kE7mPlrBbEe+c3r9pjceVPgupIfNIhc4yx55H69OXANrUaSuu9eInKg==
|
integrity sha512-FOmRr+FmWEIG8uhZv6C2bTgEVXsHk08kE7mPlrBbEe+c3r9pjceVPgupIfNIhc4yx55H69OXANrUaSuu9eInKg==
|
||||||
|
|
|
@ -3,4 +3,6 @@
|
||||||
// Neutrino's inspect feature can be used to view/export the generated configuration.
|
// Neutrino's inspect feature can be used to view/export the generated configuration.
|
||||||
const neutrino = require('neutrino');
|
const neutrino = require('neutrino');
|
||||||
|
|
||||||
module.exports = neutrino(require(`${__dirname}/.neutrinorc`)).webpack();
|
const configOfConfigs = require(`${__dirname}/.neutrinorc`);
|
||||||
|
const configs = Array.isArray(configOfConfigs) ? configOfConfigs : [configOfConfigs];
|
||||||
|
module.exports = configs.map((config) => neutrino(config).webpack());
|
||||||
|
|
3
packages/app/.gitignore
vendored
Normal file
3
packages/app/.gitignore
vendored
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
/*.js
|
||||||
|
/*.js.map
|
||||||
|
!/webpack.config.js
|
41
packages/app/package.json
Normal file
41
packages/app/package.json
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
{
|
||||||
|
"name": "@reddichat/app",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"main": "index.js",
|
||||||
|
"author": "cha0s",
|
||||||
|
"license": "MIT",
|
||||||
|
"scripts": {
|
||||||
|
"build": "NODE_PATH=./node_modules webpack --mode production",
|
||||||
|
"clean": "rm -f yarn.lock && yarn",
|
||||||
|
"dev": "NODE_PATH=./node_modules webpack --mode development",
|
||||||
|
"forcepub": "npm unpublish --force $(node -e 'process.stdout.write(require(`./package.json`).name)') && npm publish",
|
||||||
|
"lint": "NODE_PATH=./node_modules eslint --format codeframe --ext mjs,js .",
|
||||||
|
"test": "NODE_PATH=./node_modules mocha --config ../../config/.mocharc.js",
|
||||||
|
"watch": "NODE_PATH=./node_modules webpack --watch --mode development"
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
"client.js",
|
||||||
|
"client.js.map",
|
||||||
|
"index.js",
|
||||||
|
"index.js.map"
|
||||||
|
],
|
||||||
|
"dependencies": {
|
||||||
|
"@reddichat/state": "^1.0.0",
|
||||||
|
"@reduxjs/toolkit": "^1.5.0",
|
||||||
|
"connected-react-router": "^6.8.0",
|
||||||
|
"debug": "4.3.1"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@neutrinojs/airbnb-base": "^9.4.0",
|
||||||
|
"@neutrinojs/copy": "9.4.0",
|
||||||
|
"@neutrinojs/library": "^9.4.0",
|
||||||
|
"@neutrinojs/mocha": "^9.4.0",
|
||||||
|
"chai": "4.2.0",
|
||||||
|
"eslint": "^7",
|
||||||
|
"eslint-import-resolver-webpack": "0.13.0",
|
||||||
|
"mocha": "^8",
|
||||||
|
"neutrino": "^9.4.0",
|
||||||
|
"webpack": "^4",
|
||||||
|
"webpack-cli": "^3"
|
||||||
|
}
|
||||||
|
}
|
16
packages/app/src/client/index.js
Normal file
16
packages/app/src/client/index.js
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
import {connectRouter} from 'connected-react-router';
|
||||||
|
|
||||||
|
import app from './state/app';
|
||||||
|
import historyState from './state/history';
|
||||||
|
|
||||||
|
export * from './state';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
hooks: {
|
||||||
|
'@reddichat/state/reducers': ({history}) => ({
|
||||||
|
app,
|
||||||
|
history: historyState(history),
|
||||||
|
router: connectRouter(history),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
};
|
63
packages/app/src/client/state/app.js
Normal file
63
packages/app/src/client/state/app.js
Normal file
|
@ -0,0 +1,63 @@
|
||||||
|
import {storage} from '@reddichat/state/client';
|
||||||
|
import {createSlice, createSelector} from '@reduxjs/toolkit';
|
||||||
|
|
||||||
|
export const appSelector = (state) => state.app;
|
||||||
|
|
||||||
|
export const leftActiveIndexSelector = createSelector(
|
||||||
|
appSelector,
|
||||||
|
(app) => app.leftActiveIndex,
|
||||||
|
);
|
||||||
|
|
||||||
|
export const rightActiveIndexSelector = createSelector(
|
||||||
|
appSelector,
|
||||||
|
(app) => app.rightActiveIndex,
|
||||||
|
);
|
||||||
|
|
||||||
|
export const leftIsOpenSelector = createSelector(
|
||||||
|
appSelector,
|
||||||
|
(app) => app.leftIsOpen,
|
||||||
|
);
|
||||||
|
|
||||||
|
export const rightIsOpenSelector = createSelector(
|
||||||
|
appSelector,
|
||||||
|
(app) => app.rightIsOpen,
|
||||||
|
);
|
||||||
|
|
||||||
|
const slice = createSlice({
|
||||||
|
name: 'reddichat/app',
|
||||||
|
initialState: {
|
||||||
|
leftActiveIndex: 0,
|
||||||
|
leftIsOpen: true,
|
||||||
|
rightActiveIndex: 0,
|
||||||
|
rightIsOpen: true,
|
||||||
|
...(storage(appSelector) || {}),
|
||||||
|
},
|
||||||
|
/* eslint-disable no-param-reassign */
|
||||||
|
extraReducers: {},
|
||||||
|
reducers: {
|
||||||
|
setLeftActiveIndex: (state, {payload: activeItem}) => {
|
||||||
|
state.leftActiveIndex = activeItem;
|
||||||
|
},
|
||||||
|
setRightActiveIndex: (state, {payload: activeItem}) => {
|
||||||
|
state.rightActiveIndex = activeItem;
|
||||||
|
},
|
||||||
|
toggleLeftIsOpen: (state) => {
|
||||||
|
state.leftIsOpen = !state.leftIsOpen;
|
||||||
|
},
|
||||||
|
toggleRightIsOpen: (state) => {
|
||||||
|
state.rightIsOpen = !state.rightIsOpen;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
/* eslint-enable no-param-reassign */
|
||||||
|
});
|
||||||
|
|
||||||
|
slice.reducer.subscription = slice.reducer;
|
||||||
|
|
||||||
|
export const {
|
||||||
|
setLeftActiveIndex,
|
||||||
|
setRightActiveIndex,
|
||||||
|
toggleLeftIsOpen,
|
||||||
|
toggleRightIsOpen,
|
||||||
|
} = slice.actions;
|
||||||
|
|
||||||
|
export default slice.reducer;
|
15
packages/app/src/client/state/history.js
Normal file
15
packages/app/src/client/state/history.js
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
/* eslint-disable no-param-reassign */
|
||||||
|
import {
|
||||||
|
createSelector,
|
||||||
|
} from '@reduxjs/toolkit';
|
||||||
|
|
||||||
|
export const historySelector = (state) => state.history;
|
||||||
|
|
||||||
|
export const historyLengthSelector = createSelector(
|
||||||
|
[historySelector],
|
||||||
|
({length}) => length,
|
||||||
|
);
|
||||||
|
|
||||||
|
export default (history) => () => ({
|
||||||
|
length: history.length,
|
||||||
|
});
|
2
packages/app/src/client/state/index.js
Normal file
2
packages/app/src/client/state/index.js
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
export * from './app';
|
||||||
|
export * from './history';
|
8
packages/app/src/index.js
Normal file
8
packages/app/src/index.js
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
export default {
|
||||||
|
hooks: {
|
||||||
|
'@reddichat/state/defaultState': async () => ({
|
||||||
|
app: undefined,
|
||||||
|
history: undefined,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
};
|
8
packages/app/webpack.config.js
Normal file
8
packages/app/webpack.config.js
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
// Whilst the configuration object can be modified here, the recommended way of making
|
||||||
|
// changes is via the presets' options or Neutrino's API in `.neutrinorc.js` instead.
|
||||||
|
// Neutrino's inspect feature can be used to view/export the generated configuration.
|
||||||
|
const neutrino = require('neutrino');
|
||||||
|
|
||||||
|
const configOfConfigs = require(`${__dirname}/.neutrinorc`);
|
||||||
|
const configs = Array.isArray(configOfConfigs) ? configOfConfigs : [configOfConfigs];
|
||||||
|
module.exports = configs.map((config) => neutrino(config).webpack());
|
5769
packages/app/yarn.lock
Normal file
5769
packages/app/yarn.lock
Normal file
File diff suppressed because it is too large
Load Diff
|
@ -20,7 +20,15 @@
|
||||||
"index.js.map"
|
"index.js.map"
|
||||||
],
|
],
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"debug": "4.3.1"
|
"@latus/core": "^1.0.0",
|
||||||
|
"@latus/db": "^1.0.0",
|
||||||
|
"@latus/governor": "^1.0.0",
|
||||||
|
"@latus/socket": "^1.0.0",
|
||||||
|
"@reddichat/core": "^1.0.0",
|
||||||
|
"@reddichat/state": "^1.0.0",
|
||||||
|
"@reduxjs/toolkit": "^1.5.0",
|
||||||
|
"debug": "4.3.1",
|
||||||
|
"uuid": "^8.3.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@neutrinojs/airbnb-base": "^9.4.0",
|
"@neutrinojs/airbnb-base": "^9.4.0",
|
||||||
|
|
|
@ -0,0 +1,20 @@
|
||||||
|
import Join from '../packets/join';
|
||||||
|
import Leave from '../packets/leave';
|
||||||
|
import Message from '../packets/message';
|
||||||
|
|
||||||
|
import chat from './state';
|
||||||
|
|
||||||
|
export * from './state';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
hooks: {
|
||||||
|
'@latus/socket/packets': (latus) => ({
|
||||||
|
Join: Join(latus),
|
||||||
|
Leave: Leave(latus),
|
||||||
|
Message: Message(latus),
|
||||||
|
}),
|
||||||
|
'@reddichat/state/reducers': () => ({
|
||||||
|
chat,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
};
|
145
packages/chat/src/client/state.js
Normal file
145
packages/chat/src/client/state.js
Normal file
|
@ -0,0 +1,145 @@
|
||||||
|
import {
|
||||||
|
createSelector,
|
||||||
|
createSlice,
|
||||||
|
} from '@reduxjs/toolkit';
|
||||||
|
|
||||||
|
import {renderChannel} from '@reddichat/core';
|
||||||
|
|
||||||
|
import {localStorage, storage} from '@reddichat/state/client';
|
||||||
|
|
||||||
|
export const chatSelector = (state) => state.chat;
|
||||||
|
|
||||||
|
export const channelsSelector = createSelector(
|
||||||
|
chatSelector,
|
||||||
|
(chat) => chat.channels,
|
||||||
|
);
|
||||||
|
|
||||||
|
export const channelSelector = createSelector(
|
||||||
|
[channelsSelector, (_, channel) => channel],
|
||||||
|
(channels, channel) => channel && channels[renderChannel(channel)],
|
||||||
|
);
|
||||||
|
|
||||||
|
export const channelUsersSelector = createSelector(
|
||||||
|
[channelSelector],
|
||||||
|
(channel) => (channel ? channel.users : []),
|
||||||
|
);
|
||||||
|
|
||||||
|
export const inputSelector = createSelector(
|
||||||
|
chatSelector,
|
||||||
|
(chat) => chat.input,
|
||||||
|
);
|
||||||
|
|
||||||
|
export const inputChannelSelector = createSelector(
|
||||||
|
[inputSelector, (_, channel) => channel],
|
||||||
|
(input, channel) => input[renderChannel(channel)],
|
||||||
|
);
|
||||||
|
|
||||||
|
export const messagesSelector = createSelector(
|
||||||
|
chatSelector,
|
||||||
|
(chat) => chat.messages,
|
||||||
|
);
|
||||||
|
|
||||||
|
export const channelMessagesSelector = createSelector(
|
||||||
|
[channelSelector, messagesSelector],
|
||||||
|
(channel, messages) => (!channel ? [] : channel.messages.map((uuid) => messages[uuid])),
|
||||||
|
);
|
||||||
|
|
||||||
|
const slice = createSlice({
|
||||||
|
name: 'reddichat/chat',
|
||||||
|
initialState: {
|
||||||
|
channels: {},
|
||||||
|
input: {},
|
||||||
|
messages: {},
|
||||||
|
...(storage(chatSelector) || {}),
|
||||||
|
},
|
||||||
|
/* eslint-disable no-param-reassign */
|
||||||
|
extraReducers: {
|
||||||
|
[localStorage]: ({input}) => ({input}),
|
||||||
|
},
|
||||||
|
reducers: {
|
||||||
|
addMessage: ({
|
||||||
|
channels,
|
||||||
|
messages,
|
||||||
|
}, {payload}) => {
|
||||||
|
const {channel, uuid} = payload;
|
||||||
|
messages[uuid] = payload;
|
||||||
|
channels[renderChannel(channel)].messages.push(uuid);
|
||||||
|
},
|
||||||
|
confirmMessage: ({channels, messages}, {payload: {previous, current, timestamp}}) => {
|
||||||
|
const {pending, ...previousMessage} = messages[previous];
|
||||||
|
messages[current] = {
|
||||||
|
...previousMessage,
|
||||||
|
timestamp,
|
||||||
|
uuid: current,
|
||||||
|
};
|
||||||
|
delete messages[previous];
|
||||||
|
const {[renderChannel(messages[current].channel)]: channel} = channels;
|
||||||
|
const index = channel.messages.findIndex((uuid) => uuid === previous);
|
||||||
|
channel.messages[index] = current;
|
||||||
|
},
|
||||||
|
editMessage: ({messages}, {payload: {uuid, message}}) => {
|
||||||
|
messages[uuid].message = message;
|
||||||
|
},
|
||||||
|
inputText: ({input}, {payload: {channel, text}}) => {
|
||||||
|
const rendered = renderChannel(channel);
|
||||||
|
if (!text) {
|
||||||
|
delete input[rendered];
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
input[rendered] = text;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
join: ({channels, messages}, {payload: {channel, messages: channelMessages, users}}) => {
|
||||||
|
channelMessages.forEach((message) => {
|
||||||
|
messages[message.uuid] = message;
|
||||||
|
});
|
||||||
|
channels[renderChannel(channel)] = {
|
||||||
|
messages: channelMessages.map((message) => message.uuid),
|
||||||
|
users,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
joined: ({channels}, {payload: {channel, id}}) => {
|
||||||
|
channels[renderChannel(channel)].users.push(id);
|
||||||
|
},
|
||||||
|
leave: ({channels}, {payload: {channel}}) => {
|
||||||
|
delete channels[renderChannel(channel)];
|
||||||
|
},
|
||||||
|
left: ({channels}, {payload: {channel, id}}) => {
|
||||||
|
const {users} = channels[renderChannel(channel)];
|
||||||
|
users.splice(users.indexOf(id), 1);
|
||||||
|
},
|
||||||
|
rejectMessage: ({messages}, {payload: uuid}) => {
|
||||||
|
delete messages[uuid].pending;
|
||||||
|
messages[uuid].rejected = true;
|
||||||
|
},
|
||||||
|
removeMessage: (state, {payload: {channel, uuid}}) => {
|
||||||
|
delete state.messages[uuid];
|
||||||
|
const {messages} = state.channels[renderChannel(channel)];
|
||||||
|
messages.splice(messages.indexOf(uuid), 1);
|
||||||
|
},
|
||||||
|
submitJoin: () => {},
|
||||||
|
submitLeave: () => {},
|
||||||
|
submitMessage: () => {},
|
||||||
|
},
|
||||||
|
/* eslint-enable no-param-reassign */
|
||||||
|
});
|
||||||
|
|
||||||
|
export const {
|
||||||
|
addMessage,
|
||||||
|
confirmMessage,
|
||||||
|
editMessage,
|
||||||
|
inputText,
|
||||||
|
join,
|
||||||
|
joined,
|
||||||
|
leave,
|
||||||
|
left,
|
||||||
|
rejectMessage,
|
||||||
|
removeMessage,
|
||||||
|
submitJoin,
|
||||||
|
submitLeave,
|
||||||
|
submitMessage,
|
||||||
|
} = slice.actions;
|
||||||
|
|
||||||
|
slice.reducer.subscription = slice.reducer;
|
||||||
|
|
||||||
|
export default slice.reducer;
|
|
@ -0,0 +1,28 @@
|
||||||
|
import Join from './packets/join.server';
|
||||||
|
import Leave from './packets/leave.server';
|
||||||
|
import Message from './packets/message.server';
|
||||||
|
|
||||||
|
import defaultState, {channelsToHydrate} from './state';
|
||||||
|
import joinChannel from './join-channel';
|
||||||
|
|
||||||
|
export * from './state';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
hooks: {
|
||||||
|
'@latus/socket/packets': (latus) => ({
|
||||||
|
Join: Join(latus),
|
||||||
|
Leave: Leave(latus),
|
||||||
|
Message: Message(latus),
|
||||||
|
}),
|
||||||
|
'@latus/socket/connect': async (socket) => {
|
||||||
|
const channels = await channelsToHydrate(socket.req);
|
||||||
|
const joins = channels
|
||||||
|
.filter(({type}) => 'r' === type)
|
||||||
|
.map((channel) => joinChannel(channel, socket));
|
||||||
|
return Promise.all(joins);
|
||||||
|
},
|
||||||
|
'@reddichat/state/defaultState': async (req, latus) => ({
|
||||||
|
chat: await defaultState(req, latus),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
};
|
17
packages/chat/src/join-channel.js
Normal file
17
packages/chat/src/join-channel.js
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
import {promisify} from 'util';
|
||||||
|
|
||||||
|
import {channelIsAnonymous, renderChannel} from '@reddichat/core';
|
||||||
|
import {channelUsers} from '@reddichat/state';
|
||||||
|
|
||||||
|
export default async (latus, channel, socket) => {
|
||||||
|
const {req} = socket;
|
||||||
|
const [id, username] = channelIsAnonymous(channel)
|
||||||
|
? [0, 'anonymous']
|
||||||
|
: [req.user.id, req.user.redditUsername];
|
||||||
|
const users = await channelUsers(req, channel);
|
||||||
|
const rendered = renderChannel(channel);
|
||||||
|
if (-1 === users.indexOf(id)) {
|
||||||
|
socket.constructor.send(socket.to(rendered), ['Join', {channel, id, username}]);
|
||||||
|
}
|
||||||
|
await promisify(socket.join.bind(socket))(rendered);
|
||||||
|
};
|
15
packages/chat/src/leave-channel.js
Normal file
15
packages/chat/src/leave-channel.js
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
import {promisify} from 'util';
|
||||||
|
|
||||||
|
import {channelIsAnonymous, renderChannel} from '@reddichat/core';
|
||||||
|
import {channelUserCounts} from '@reddichat/state';
|
||||||
|
|
||||||
|
export default async (channel, socket) => {
|
||||||
|
const {req} = socket;
|
||||||
|
const rendered = renderChannel(channel);
|
||||||
|
const userId = channelIsAnonymous(channel) ? 0 : req.userId;
|
||||||
|
await promisify(socket.leave.bind(socket))(rendered);
|
||||||
|
const userCounts = await channelUserCounts(req, channel);
|
||||||
|
if (!userCounts[userId]) {
|
||||||
|
socket.to(rendered, ['Leave', {channel, id: userId}]);
|
||||||
|
}
|
||||||
|
};
|
23
packages/chat/src/packets/join.js
Normal file
23
packages/chat/src/packets/join.js
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
import {Packet, ValidationError} from '@latus/socket/packets';
|
||||||
|
import {validateChannel} from '@reddichat/core/client';
|
||||||
|
|
||||||
|
export default () => class Join extends Packet {
|
||||||
|
|
||||||
|
static get data() {
|
||||||
|
return {
|
||||||
|
id: 'uint32',
|
||||||
|
channel: {
|
||||||
|
type: 'string',
|
||||||
|
name: 'string',
|
||||||
|
},
|
||||||
|
username: 'string',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
static async validate({data: {channel}}) {
|
||||||
|
if (!validateChannel(channel)) {
|
||||||
|
throw new ValidationError({code: 400, reason: 'Malformed channel'});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
24
packages/chat/src/packets/join.server.js
Normal file
24
packages/chat/src/packets/join.server.js
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
import {ModelMap} from '@latus/db';
|
||||||
|
|
||||||
|
import {channelState} from '@reddichat/state';
|
||||||
|
|
||||||
|
import joinChannel from '../join-channel';
|
||||||
|
import Join from './join';
|
||||||
|
|
||||||
|
export default (latus) => class JoinServer extends Join(latus) {
|
||||||
|
|
||||||
|
static async respond({data: {channel}}, socket) {
|
||||||
|
const {req} = socket;
|
||||||
|
const {User} = ModelMap(latus);
|
||||||
|
await joinChannel(channel, socket);
|
||||||
|
const state = await channelState(req, channel);
|
||||||
|
const usernames = Object.fromEntries(await Promise.all(
|
||||||
|
state.users.map(async (id) => [
|
||||||
|
id,
|
||||||
|
0 === id ? 'anonymous' : (await User.findByPk(id)).redditUsername,
|
||||||
|
]),
|
||||||
|
));
|
||||||
|
return {...state, usernames};
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
22
packages/chat/src/packets/leave.js
Normal file
22
packages/chat/src/packets/leave.js
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
import {validateChannel} from '@reddichat/core/client';
|
||||||
|
import {Packet, ValidationError} from '@latus/socket/packets';
|
||||||
|
|
||||||
|
export default () => class Leave extends Packet {
|
||||||
|
|
||||||
|
static get data() {
|
||||||
|
return {
|
||||||
|
id: 'uint32',
|
||||||
|
channel: {
|
||||||
|
type: 'string',
|
||||||
|
name: 'string',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
static async validate({data: channel}) {
|
||||||
|
if (!validateChannel(channel)) {
|
||||||
|
throw new ValidationError({code: 400, reason: 'Malformed channel'});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
10
packages/chat/src/packets/leave.server.js
Normal file
10
packages/chat/src/packets/leave.server.js
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
import leaveChannel from '../leave-channel';
|
||||||
|
import Leave from './leave';
|
||||||
|
|
||||||
|
export default () => class LeaveServer extends Leave() {
|
||||||
|
|
||||||
|
static async respond({data: channel}, socket) {
|
||||||
|
return leaveChannel(channel, socket);
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
41
packages/chat/src/packets/message.js
Normal file
41
packages/chat/src/packets/message.js
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
import {validateChannel} from '@reddichat/core/client';
|
||||||
|
import {createLimiter} from '@latus/governor/client';
|
||||||
|
import {Packet, ValidationError} from '@latus/socket/packets';
|
||||||
|
|
||||||
|
export default () => class Message extends Packet {
|
||||||
|
|
||||||
|
static characterLimiter = createLimiter({
|
||||||
|
keyPrefix: 'characterLimiter',
|
||||||
|
points: 2048,
|
||||||
|
duration: 2,
|
||||||
|
});
|
||||||
|
|
||||||
|
static get data() {
|
||||||
|
return {
|
||||||
|
channel: {
|
||||||
|
type: 'string',
|
||||||
|
name: 'string',
|
||||||
|
},
|
||||||
|
message: 'string',
|
||||||
|
owner: 'uint32',
|
||||||
|
timestamp: 'float64',
|
||||||
|
uuid: 'string',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
static limit = {
|
||||||
|
points: 10,
|
||||||
|
duration: 15,
|
||||||
|
};
|
||||||
|
|
||||||
|
static async validate({data: {channel, message}}, socket) {
|
||||||
|
await this.characterLimiter.consume(socket.id, message.length);
|
||||||
|
if (!validateChannel(channel)) {
|
||||||
|
throw new ValidationError({code: 400, reason: 'Malformed channel'});
|
||||||
|
}
|
||||||
|
if (message.length > 512) {
|
||||||
|
throw new ValidationError({code: 400, reason: 'Message larger than 512 bytes'});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
92
packages/chat/src/packets/message.server.js
Normal file
92
packages/chat/src/packets/message.server.js
Normal file
|
@ -0,0 +1,92 @@
|
||||||
|
import {v4 as uuidv4} from 'uuid';
|
||||||
|
|
||||||
|
import {ModelMap} from '@latus/db';
|
||||||
|
import {createLimiter} from '@latus/governor';
|
||||||
|
import {ValidationError} from '@latus/socket';
|
||||||
|
import {channelIsAnonymous, renderChannel, validateChannel} from '@reddichat/core';
|
||||||
|
|
||||||
|
import Message from './message';
|
||||||
|
|
||||||
|
export default (latus) => class MessageServer extends Message(latus) {
|
||||||
|
|
||||||
|
static characterLimiter = createLimiter(latus, {
|
||||||
|
keyPrefix: 'characterLimiter',
|
||||||
|
points: 2048,
|
||||||
|
duration: 2,
|
||||||
|
});
|
||||||
|
|
||||||
|
static async respond({data}, socket) {
|
||||||
|
const {req} = socket;
|
||||||
|
const {pubClient} = req.adapter;
|
||||||
|
const {User} = ModelMap(latus);
|
||||||
|
const {channel, message} = data;
|
||||||
|
const {name, type} = channel;
|
||||||
|
const isAnonymous = channelIsAnonymous(channel);
|
||||||
|
const owner = isAnonymous ? 0 : req.userId;
|
||||||
|
const rendered = renderChannel(channel);
|
||||||
|
const serverChannel = 'r' === type
|
||||||
|
? rendered
|
||||||
|
: `/u/${[name, req.user.redditUsername].sort().join('$')}`;
|
||||||
|
const timestamp = Date.now();
|
||||||
|
const uuid = uuidv4();
|
||||||
|
const key = `${serverChannel}:messages:${uuid}`;
|
||||||
|
let destinations = [];
|
||||||
|
if ('u' === type) {
|
||||||
|
const other = await User.findOne({where: {redditUsername: name}});
|
||||||
|
destinations = [
|
||||||
|
{
|
||||||
|
type: 'u',
|
||||||
|
name: other.id,
|
||||||
|
username: name,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'u',
|
||||||
|
name: req.userId,
|
||||||
|
username: req.user.redditUsername,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
destinations = [
|
||||||
|
channel,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
destinations.forEach((room, i) => (
|
||||||
|
socket.to(renderChannel(room), new Message({
|
||||||
|
...data,
|
||||||
|
channel: 'r' === type
|
||||||
|
? room
|
||||||
|
: {
|
||||||
|
type: 'u',
|
||||||
|
name: destinations[1 - i].username,
|
||||||
|
},
|
||||||
|
owner,
|
||||||
|
timestamp,
|
||||||
|
uuid,
|
||||||
|
}))
|
||||||
|
));
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
pubClient
|
||||||
|
.multi()
|
||||||
|
.set(key, JSON.stringify({
|
||||||
|
message,
|
||||||
|
owner,
|
||||||
|
socket: socket.id,
|
||||||
|
timestamp,
|
||||||
|
}))
|
||||||
|
.expire(key, channelIsAnonymous(channel) ? 60 : 600)
|
||||||
|
.exec((error) => (error ? reject(error) : resolve([timestamp, uuid])));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
static async validate({data: {channel, message}}, socket) {
|
||||||
|
await this.characterLimiter.consume(socket.id, message.length);
|
||||||
|
if (!validateChannel(channel)) {
|
||||||
|
throw new ValidationError({code: 400, reason: 'Malformed channel'});
|
||||||
|
}
|
||||||
|
if (message.length > 512) {
|
||||||
|
throw new ValidationError({code: 400, reason: 'Message larger than 512 bytes'});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
118
packages/chat/src/state.js
Normal file
118
packages/chat/src/state.js
Normal file
|
@ -0,0 +1,118 @@
|
||||||
|
/* eslint-disable import/no-extraneous-dependencies */
|
||||||
|
import {promisify} from 'util';
|
||||||
|
|
||||||
|
import {ModelMap} from '@latus/db';
|
||||||
|
import {createClient, keys} from '@latus/redis';
|
||||||
|
import {channelIsAnonymous, parseChannel, renderChannel} from '@reddichat/core';
|
||||||
|
|
||||||
|
export const channelUserCounts = async (req, channel) => {
|
||||||
|
const clients = promisify(req.adapter.clients.bind(req.adapter));
|
||||||
|
const rendered = renderChannel(channel);
|
||||||
|
const socketKeys = await clients([rendered]);
|
||||||
|
const customRequest = promisify(req.adapter.customRequest.bind(req.adapter));
|
||||||
|
// eslint-disable-next-line no-nested-ternary
|
||||||
|
const replies = channelIsAnonymous(channel)
|
||||||
|
? (
|
||||||
|
socketKeys.length > 0
|
||||||
|
? [socketKeys.reduce((r, socketKey) => ({...r, [socketKey]: 0}), {})]
|
||||||
|
: []
|
||||||
|
)
|
||||||
|
: await customRequest({type: 'socketUsers', payload: socketKeys});
|
||||||
|
const socketUsers = replies.reduce((r, m) => ({...r, ...m}), {});
|
||||||
|
return 0 === socketKeys.length
|
||||||
|
? []
|
||||||
|
: Object.values(socketUsers).reduce((r, uid) => ({...r, [uid]: 1 + (r[uid] || 0)}), {});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const channelUsers = async (req, channel) => (
|
||||||
|
Object.keys(await channelUserCounts(req, channel)).map((id) => parseInt(id, 10))
|
||||||
|
);
|
||||||
|
|
||||||
|
export const channelState = async (req, latus, channel) => {
|
||||||
|
const {name, type} = channel;
|
||||||
|
const redisClient = createClient(latus);
|
||||||
|
const mget = promisify(redisClient.mget.bind(redisClient));
|
||||||
|
const realName = 'r' === type
|
||||||
|
? name
|
||||||
|
: `${[name, req.user.redditUsername].sort().join('$')}`;
|
||||||
|
const messagesKey = `${renderChannel({type, name: realName})}:messages:*`;
|
||||||
|
const messageKeys = await keys(redisClient, messagesKey);
|
||||||
|
const messages = 0 === messageKeys.length
|
||||||
|
? []
|
||||||
|
: (await mget(messageKeys))
|
||||||
|
.map((reply, i) => ({
|
||||||
|
...JSON.parse(reply),
|
||||||
|
uuid: messageKeys[i].split(':')[2],
|
||||||
|
}))
|
||||||
|
.sort((l, r) => l.timestamp - r.timestamp);
|
||||||
|
const users = new Set(await channelUsers(req, channel));
|
||||||
|
users.add(channelIsAnonymous(channel) ? 0 : req.userId);
|
||||||
|
return {
|
||||||
|
messages,
|
||||||
|
users: Array.from(users.values()),
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export const channelsToHydrate = async (req, latus) => {
|
||||||
|
const {channel, user} = req;
|
||||||
|
if (!user) {
|
||||||
|
return channel ? [renderChannel(channel)] : [];
|
||||||
|
}
|
||||||
|
const {User} = ModelMap(latus);
|
||||||
|
const channels = await Promise.all(
|
||||||
|
[]
|
||||||
|
.concat(channel ? [channel] : [])
|
||||||
|
.concat(await req.user.favorites())
|
||||||
|
.concat(await Promise.all((await user.friendships()).map(
|
||||||
|
async ({adderId, addeeId}) => ({
|
||||||
|
type: 'u',
|
||||||
|
name: (await User.findByPk(adderId !== user.id ? adderId : addeeId)).redditUsername,
|
||||||
|
}),
|
||||||
|
))),
|
||||||
|
);
|
||||||
|
return Array.from((new Set(channels.map(renderChannel))).values()).map(parseChannel);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const chatUserIds = async (req, latus) => {
|
||||||
|
const toHydrate = await channelsToHydrate(req, latus);
|
||||||
|
if (0 === toHydrate.length) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
const entries = await Promise.all(
|
||||||
|
toHydrate.map((channel) => channelState(req, latus, channel)),
|
||||||
|
);
|
||||||
|
const chatUserIds = new Set();
|
||||||
|
for (let i = 0; i < toHydrate.length; i++) {
|
||||||
|
const {messages, users} = entries[i];
|
||||||
|
Object.values(messages).map((message) => message.owner).forEach((id) => chatUserIds.add(id));
|
||||||
|
users.forEach((id) => chatUserIds.add(id));
|
||||||
|
}
|
||||||
|
return Array.from(chatUserIds.keys());
|
||||||
|
};
|
||||||
|
|
||||||
|
export default async (req, latus) => {
|
||||||
|
const toHydrate = await channelsToHydrate(req, latus);
|
||||||
|
const chat = {
|
||||||
|
channels: {},
|
||||||
|
messages: {},
|
||||||
|
users: {},
|
||||||
|
};
|
||||||
|
if (0 === toHydrate.length) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
const entries = await Promise.all(
|
||||||
|
toHydrate.map((favorite) => channelState(req, latus, favorite)),
|
||||||
|
);
|
||||||
|
for (let i = 0; i < toHydrate.length; i++) {
|
||||||
|
const channel = renderChannel(toHydrate[i]);
|
||||||
|
const {messages, users} = entries[i];
|
||||||
|
chat.channels[channel] = {
|
||||||
|
messages: messages.map((message) => message.uuid),
|
||||||
|
users,
|
||||||
|
};
|
||||||
|
messages.forEach((message) => {
|
||||||
|
chat.messages[message.uuid] = message;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return chat;
|
||||||
|
};
|
|
@ -3,4 +3,6 @@
|
||||||
// Neutrino's inspect feature can be used to view/export the generated configuration.
|
// Neutrino's inspect feature can be used to view/export the generated configuration.
|
||||||
const neutrino = require('neutrino');
|
const neutrino = require('neutrino');
|
||||||
|
|
||||||
module.exports = neutrino(require(`${__dirname}/.neutrinorc`)).webpack();
|
const configOfConfigs = require(`${__dirname}/.neutrinorc`);
|
||||||
|
const configs = Array.isArray(configOfConfigs) ? configOfConfigs : [configOfConfigs];
|
||||||
|
module.exports = configs.map((config) => neutrino(config).webpack());
|
||||||
|
|
8704
packages/chat/yarn.lock
Normal file
8704
packages/chat/yarn.lock
Normal file
File diff suppressed because it is too large
Load Diff
3
packages/core/.gitignore
vendored
Normal file
3
packages/core/.gitignore
vendored
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
/*.js
|
||||||
|
/*.js.map
|
||||||
|
!/webpack.config.js
|
38
packages/core/package.json
Normal file
38
packages/core/package.json
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
{
|
||||||
|
"name": "@reddichat/core",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"main": "index.js",
|
||||||
|
"author": "cha0s",
|
||||||
|
"license": "MIT",
|
||||||
|
"scripts": {
|
||||||
|
"build": "NODE_PATH=./node_modules webpack --mode production",
|
||||||
|
"clean": "rm -f yarn.lock && yarn",
|
||||||
|
"dev": "NODE_PATH=./node_modules webpack --mode development",
|
||||||
|
"forcepub": "npm unpublish --force $(node -e 'process.stdout.write(require(`./package.json`).name)') && npm publish",
|
||||||
|
"lint": "NODE_PATH=./node_modules eslint --format codeframe --ext mjs,js .",
|
||||||
|
"test": "NODE_PATH=./node_modules mocha --config ../../config/.mocharc.js",
|
||||||
|
"watch": "NODE_PATH=./node_modules webpack --watch --mode development"
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
"client.js",
|
||||||
|
"client.js.map",
|
||||||
|
"index.js",
|
||||||
|
"index.js.map"
|
||||||
|
],
|
||||||
|
"dependencies": {
|
||||||
|
"debug": "4.3.1"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@neutrinojs/airbnb-base": "^9.4.0",
|
||||||
|
"@neutrinojs/copy": "9.4.0",
|
||||||
|
"@neutrinojs/library": "^9.4.0",
|
||||||
|
"@neutrinojs/mocha": "^9.4.0",
|
||||||
|
"chai": "4.2.0",
|
||||||
|
"eslint": "^7",
|
||||||
|
"eslint-import-resolver-webpack": "0.13.0",
|
||||||
|
"mocha": "^8",
|
||||||
|
"neutrino": "^9.4.0",
|
||||||
|
"webpack": "^4",
|
||||||
|
"webpack-cli": "^3"
|
||||||
|
}
|
||||||
|
}
|
40
packages/core/src/channel.js
Normal file
40
packages/core/src/channel.js
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
export const channelIsAnonymous = ({type, name}) => 'r' === type && 'anonymous' === name;
|
||||||
|
|
||||||
|
export const parseChannel = (channel) => {
|
||||||
|
const matches = channel.match(/^\/([^/]+)\/([^/]+)/i);
|
||||||
|
if (matches) {
|
||||||
|
const [, type, name] = matches;
|
||||||
|
return {name, type};
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const parseChatChannel = (url) => {
|
||||||
|
if (0 !== url.indexOf('/chat/')) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
return parseChannel(url.slice(5));
|
||||||
|
};
|
||||||
|
|
||||||
|
export const renderChannel = (channel) => (channel ? `/${channel.type}/${channel.name}` : '');
|
||||||
|
|
||||||
|
const countryExceptions = ['de', 'es', 'it'];
|
||||||
|
export const validateSubreddit = (name) => {
|
||||||
|
if (-1 !== countryExceptions.indexOf(name)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return !!name.match(/^[A-Za-z0-9][A-Za-z0-9_]{2,20}$/i);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const validateUsername = (name) => !!name.trim().match(/^[\w-]{3,20}$/);
|
||||||
|
|
||||||
|
export const validateChannel = (channel) => {
|
||||||
|
if (!channel) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const {type, name} = channel;
|
||||||
|
if (-1 === ['r', 'u'].indexOf(type)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return ('r' === type ? validateSubreddit : validateUsername)(name);
|
||||||
|
};
|
9
packages/core/src/client.js
Normal file
9
packages/core/src/client.js
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
export {
|
||||||
|
channelIsAnonymous,
|
||||||
|
parseChannel,
|
||||||
|
parseChatChannel,
|
||||||
|
renderChannel,
|
||||||
|
validateSubreddit,
|
||||||
|
validateUsername,
|
||||||
|
validateChannel,
|
||||||
|
} from './channel';
|
9
packages/core/src/index.js
Normal file
9
packages/core/src/index.js
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
export {
|
||||||
|
channelIsAnonymous,
|
||||||
|
parseChannel,
|
||||||
|
parseChatChannel,
|
||||||
|
renderChannel,
|
||||||
|
validateSubreddit,
|
||||||
|
validateUsername,
|
||||||
|
validateChannel,
|
||||||
|
} from './channel';
|
8
packages/core/webpack.config.js
Normal file
8
packages/core/webpack.config.js
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
// Whilst the configuration object can be modified here, the recommended way of making
|
||||||
|
// changes is via the presets' options or Neutrino's API in `.neutrinorc.js` instead.
|
||||||
|
// Neutrino's inspect feature can be used to view/export the generated configuration.
|
||||||
|
const neutrino = require('neutrino');
|
||||||
|
|
||||||
|
const configOfConfigs = require(`${__dirname}/.neutrinorc`);
|
||||||
|
const configs = Array.isArray(configOfConfigs) ? configOfConfigs : [configOfConfigs];
|
||||||
|
module.exports = configs.map((config) => neutrino(config).webpack());
|
5333
packages/core/yarn.lock
Normal file
5333
packages/core/yarn.lock
Normal file
File diff suppressed because it is too large
Load Diff
|
@ -3,4 +3,6 @@
|
||||||
// Neutrino's inspect feature can be used to view/export the generated configuration.
|
// Neutrino's inspect feature can be used to view/export the generated configuration.
|
||||||
const neutrino = require('neutrino');
|
const neutrino = require('neutrino');
|
||||||
|
|
||||||
module.exports = neutrino(require(`${__dirname}/.neutrinorc`)).webpack();
|
const configOfConfigs = require(`${__dirname}/.neutrinorc`);
|
||||||
|
const configs = Array.isArray(configOfConfigs) ? configOfConfigs : [configOfConfigs];
|
||||||
|
module.exports = configs.map((config) => neutrino(config).webpack());
|
||||||
|
|
3
packages/state/.gitignore
vendored
Normal file
3
packages/state/.gitignore
vendored
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
/*.js
|
||||||
|
/*.js.map
|
||||||
|
!/webpack.config.js
|
47
packages/state/package.json
Normal file
47
packages/state/package.json
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
{
|
||||||
|
"name": "@reddichat/state",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"main": "index.js",
|
||||||
|
"author": "cha0s",
|
||||||
|
"license": "MIT",
|
||||||
|
"scripts": {
|
||||||
|
"build": "NODE_PATH=./node_modules webpack --mode production",
|
||||||
|
"clean": "rm -f yarn.lock && yarn",
|
||||||
|
"dev": "NODE_PATH=./node_modules webpack --mode development",
|
||||||
|
"forcepub": "npm unpublish --force $(node -e 'process.stdout.write(require(`./package.json`).name)') && npm publish",
|
||||||
|
"lint": "NODE_PATH=./node_modules eslint --format codeframe --ext mjs,js .",
|
||||||
|
"test": "NODE_PATH=./node_modules mocha --config ../../config/.mocharc.js",
|
||||||
|
"watch": "NODE_PATH=./node_modules webpack --watch --mode development"
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
"client.js",
|
||||||
|
"client.js.map",
|
||||||
|
"index.js",
|
||||||
|
"index.js.map"
|
||||||
|
],
|
||||||
|
"dependencies": {
|
||||||
|
"@latus/core": "^1.0.0",
|
||||||
|
"@latus/db": "^1.0.0",
|
||||||
|
"@latus/redis": "^1.0.0",
|
||||||
|
"@reddichat/core": "^1.0.0",
|
||||||
|
"@reduxjs/toolkit": "^1.5.0",
|
||||||
|
"connected-react-router": "^6.8.0",
|
||||||
|
"debug": "4.3.1",
|
||||||
|
"deepmerge": "^4.2.2",
|
||||||
|
"lodash.throttle": "^4.1.1",
|
||||||
|
"redux": "^4.0.5"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@neutrinojs/airbnb-base": "^9.4.0",
|
||||||
|
"@neutrinojs/copy": "9.4.0",
|
||||||
|
"@neutrinojs/library": "^9.4.0",
|
||||||
|
"@neutrinojs/mocha": "^9.4.0",
|
||||||
|
"chai": "4.2.0",
|
||||||
|
"eslint": "^7",
|
||||||
|
"eslint-import-resolver-webpack": "0.13.0",
|
||||||
|
"mocha": "^8",
|
||||||
|
"neutrino": "^9.4.0",
|
||||||
|
"webpack": "^4",
|
||||||
|
"webpack-cli": "^3"
|
||||||
|
}
|
||||||
|
}
|
135
packages/state/src/client/effects.js
vendored
Normal file
135
packages/state/src/client/effects.js
vendored
Normal file
|
@ -0,0 +1,135 @@
|
||||||
|
// import RateLimiterMemory from 'rate-limiter-flexible/lib/RateLimiterMemory';
|
||||||
|
// import {v4 as uuidv4} from 'uuid';
|
||||||
|
|
||||||
|
// import AddFavorite from '~/common/packets/add-favorite.packet';
|
||||||
|
// import AddFriend from '~/common/packets/add-friend.packet';
|
||||||
|
// import Block from '~/common/packets/block.packet';
|
||||||
|
// import ConfirmFriend from '~/common/packets/confirm-friend.packet';
|
||||||
|
// import Join from '~/common/packets/join.packet';
|
||||||
|
// import Leave from '~/common/packets/leave.packet';
|
||||||
|
// import Message from '~/common/packets/message.packet';
|
||||||
|
// import RemoveFavorite from '~/common/packets/remove-favorite.packet';
|
||||||
|
// import RemoveFriend from '~/common/packets/remove-friend.packet';
|
||||||
|
// import Unblock from '~/common/packets/unblock.packet';
|
||||||
|
// import {
|
||||||
|
// addMessage,
|
||||||
|
// confirmMessage,
|
||||||
|
// join,
|
||||||
|
// leave,
|
||||||
|
// rejectMessage,
|
||||||
|
// submitJoin,
|
||||||
|
// submitLeave,
|
||||||
|
// submitMessage,
|
||||||
|
// } from '~/common/state/chat';
|
||||||
|
// import {
|
||||||
|
// addFriendship,
|
||||||
|
// confirmFriendship,
|
||||||
|
// idSelector,
|
||||||
|
// submitAddFavorite,
|
||||||
|
// submitAddFriend,
|
||||||
|
// submitBlock,
|
||||||
|
// submitConfirmFriend,
|
||||||
|
// submitRemoveFavorite,
|
||||||
|
// submitRemoveFriend,
|
||||||
|
// submitUnblock,
|
||||||
|
// } from '~/common/state/user';
|
||||||
|
// import {
|
||||||
|
// setUsernames,
|
||||||
|
// } from '@reddichat/state/client';
|
||||||
|
|
||||||
|
// import {socket} from '~/client/hooks/useSocket';
|
||||||
|
|
||||||
|
// const characterLimiter = new RateLimiterMemory({points: 2048, duration: 2});
|
||||||
|
// const messageLimiter = new RateLimiterMemory({points: 10, duration: 15});
|
||||||
|
|
||||||
|
const effects = {
|
||||||
|
// [submitAddFavorite]: (store, {payload}) => socket.send(new AddFavorite(payload)),
|
||||||
|
// [submitAddFriend]: ({dispatch, getState}, {payload: name}) => {
|
||||||
|
// const state = getState();
|
||||||
|
// const userId = idSelector(state);
|
||||||
|
// socket.send(new AddFriend({name}), (error, id) => {
|
||||||
|
// if (error) {
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
// dispatch(addFriendship({
|
||||||
|
// addeeId: id,
|
||||||
|
// adderId: userId,
|
||||||
|
// status: 'pending',
|
||||||
|
// }));
|
||||||
|
// dispatch(setUsernames({[id]: name}));
|
||||||
|
// });
|
||||||
|
// },
|
||||||
|
// [submitBlock]: (store, {payload: id}) => socket.send(new Block(id)),
|
||||||
|
// [submitConfirmFriend]: ({dispatch}, {payload: adderId}) => {
|
||||||
|
// socket.send(new ConfirmFriend(adderId), (error) => {
|
||||||
|
// if (error) {
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
// dispatch(confirmFriendship(adderId));
|
||||||
|
// });
|
||||||
|
// },
|
||||||
|
// [submitJoin]: ({dispatch}, {payload}) => {
|
||||||
|
// const {channel} = payload;
|
||||||
|
// socket.send(new Join(payload), (error, result) => {
|
||||||
|
// if (error) {
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
// const {messages, users} = result;
|
||||||
|
// dispatch(join({channel, messages, users}));
|
||||||
|
// });
|
||||||
|
// },
|
||||||
|
// [submitLeave]: ({dispatch}, {payload}) => {
|
||||||
|
// const {channel} = payload;
|
||||||
|
// socket.send(new Leave(payload), () => dispatch(leave({channel})));
|
||||||
|
// },
|
||||||
|
// [submitMessage]: async ({dispatch}, {payload}) => {
|
||||||
|
// const reject = (ttr) => {
|
||||||
|
// setTimeout(() => {
|
||||||
|
// dispatch(addMessage({
|
||||||
|
// ...payload,
|
||||||
|
// message: [
|
||||||
|
// 'You are sending too much.',
|
||||||
|
// `Try again in ${ttr} second${1 === ttr ? '' : 's'}.`,
|
||||||
|
// ].join(' '),
|
||||||
|
// owner: -1,
|
||||||
|
// uuid: uuidv4(),
|
||||||
|
// }));
|
||||||
|
// dispatch(rejectMessage(payload.uuid));
|
||||||
|
// }, 0);
|
||||||
|
// };
|
||||||
|
// try {
|
||||||
|
// await characterLimiter.consume('', payload.message.length);
|
||||||
|
// await messageLimiter.consume('');
|
||||||
|
// socket.send(new Message(payload), (error, result) => {
|
||||||
|
// if (error) {
|
||||||
|
// switch (error.code) {
|
||||||
|
// case 429: {
|
||||||
|
// reject(error.ttr);
|
||||||
|
// break;
|
||||||
|
// }
|
||||||
|
// default:
|
||||||
|
// }
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
// const [timestamp, current] = result;
|
||||||
|
// dispatch(confirmMessage({current, previous: payload.uuid, timestamp}));
|
||||||
|
// });
|
||||||
|
// }
|
||||||
|
// catch (error) {
|
||||||
|
// reject(Math.round(Math.max(0, error.msBeforeNext) / 1000) || 1);
|
||||||
|
// }
|
||||||
|
// },
|
||||||
|
// [submitRemoveFavorite]: (store, {payload}) => socket.send(new RemoveFavorite(payload)),
|
||||||
|
// [submitRemoveFriend]: (store, {payload: id}) => socket.send(new RemoveFriend(id)),
|
||||||
|
// [submitUnblock]: (store, {payload}) => socket.send(new Unblock(payload)),
|
||||||
|
};
|
||||||
|
|
||||||
|
export const middleware = (store) => (next) => (action) => {
|
||||||
|
const result = next(action);
|
||||||
|
if (effects[action.type]) {
|
||||||
|
setTimeout(() => effects[action.type](store, action), 0);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default effects;
|
6
packages/state/src/client/index.js
Normal file
6
packages/state/src/client/index.js
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
export * from './storage';
|
||||||
|
export {default as storage} from './storage';
|
||||||
|
|
||||||
|
export {default as configureStore} from './store';
|
||||||
|
|
||||||
|
export default {};
|
35
packages/state/src/client/storage.js
Normal file
35
packages/state/src/client/storage.js
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
import throttle from 'lodash.throttle';
|
||||||
|
import {createAction} from '@reduxjs/toolkit';
|
||||||
|
|
||||||
|
export const localStorage = createAction('reddichat/localStorage');
|
||||||
|
|
||||||
|
const hasStorage = (() => {
|
||||||
|
try {
|
||||||
|
window.localStorage.setItem('__redux-test', true);
|
||||||
|
window.localStorage.removeItem('__redux-test');
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
|
||||||
|
export const storageSubscription = (store, reducer) => (
|
||||||
|
throttle(
|
||||||
|
!hasStorage ? (() => {}) : () => (
|
||||||
|
window.localStorage.setItem(
|
||||||
|
'redux-state',
|
||||||
|
JSON.stringify(reducer(store.getState(), localStorage())),
|
||||||
|
)
|
||||||
|
),
|
||||||
|
1000,
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
export default (selector) => {
|
||||||
|
const state = hasStorage && window.localStorage.getItem('redux-state');
|
||||||
|
if (!state) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
return selector(JSON.parse(state));
|
||||||
|
};
|
37
packages/state/src/client/store.js
Normal file
37
packages/state/src/client/store.js
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
import {ensureUniqueReduction} from '@latus/core/client';
|
||||||
|
import {configureStore as configureStoreR, getDefaultMiddleware} from '@reduxjs/toolkit';
|
||||||
|
import merge from 'deepmerge';
|
||||||
|
import {routerMiddleware} from 'connected-react-router';
|
||||||
|
import {combineReducers} from 'redux';
|
||||||
|
|
||||||
|
import {storageSubscription} from './storage';
|
||||||
|
|
||||||
|
import {middleware as effectsMiddleware} from './effects';
|
||||||
|
|
||||||
|
export default async function configureStore(latus, options = {}) {
|
||||||
|
const {history} = options;
|
||||||
|
const reducers = await ensureUniqueReduction(latus, '@reddichat/state/reducers', options);
|
||||||
|
const {defaultState} = latus.config['@reddichat/state/client'];
|
||||||
|
const reducer = combineReducers(reducers);
|
||||||
|
const store = configureStoreR(
|
||||||
|
merge(
|
||||||
|
options,
|
||||||
|
{
|
||||||
|
middleware: [
|
||||||
|
...getDefaultMiddleware(),
|
||||||
|
routerMiddleware(history),
|
||||||
|
effectsMiddleware,
|
||||||
|
],
|
||||||
|
preloadedState: reducer(defaultState, {type: null}),
|
||||||
|
reducer,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
const subscriptionReducers = Object.entries(reducers).map(([key, reducer]) => [
|
||||||
|
key,
|
||||||
|
reducer.subscription || (() => null),
|
||||||
|
]);
|
||||||
|
const subscriptionReducer = combineReducers(subscriptionReducers);
|
||||||
|
store.subscribe((store) => storageSubscription(store, subscriptionReducer));
|
||||||
|
return store;
|
||||||
|
}
|
11
packages/state/src/index.js
Normal file
11
packages/state/src/index.js
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
import {ensureUniqueReduction} from '@latus/core';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
hooks: {
|
||||||
|
'@latus/http/plugins': async (req, latus) => ({
|
||||||
|
'@reddichat/state/client': {
|
||||||
|
defaultState: await ensureUniqueReduction(latus, '@reddichat/state/defaultState', req),
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
};
|
8
packages/state/webpack.config.js
Normal file
8
packages/state/webpack.config.js
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
// Whilst the configuration object can be modified here, the recommended way of making
|
||||||
|
// changes is via the presets' options or Neutrino's API in `.neutrinorc.js` instead.
|
||||||
|
// Neutrino's inspect feature can be used to view/export the generated configuration.
|
||||||
|
const neutrino = require('neutrino');
|
||||||
|
|
||||||
|
const configOfConfigs = require(`${__dirname}/.neutrinorc`);
|
||||||
|
const configs = Array.isArray(configOfConfigs) ? configOfConfigs : [configOfConfigs];
|
||||||
|
module.exports = configs.map((config) => neutrino(config).webpack());
|
5746
packages/state/yarn.lock
Normal file
5746
packages/state/yarn.lock
Normal file
File diff suppressed because it is too large
Load Diff
|
@ -20,7 +20,15 @@
|
||||||
"index.js.map"
|
"index.js.map"
|
||||||
],
|
],
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"debug": "4.3.1"
|
"@latus/db": "^1.0.0",
|
||||||
|
"@latus/socket": "^1.0.0",
|
||||||
|
"@reddichat/chat": "^1.0.0",
|
||||||
|
"@reddichat/core": "^1.0.0",
|
||||||
|
"@reddichat/state": "^1.0.0",
|
||||||
|
"@reduxjs/toolkit": "^1.5.0",
|
||||||
|
"connected-react-router": "^6.8.0",
|
||||||
|
"debug": "4.3.1",
|
||||||
|
"deepmerge": "^4.2.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@neutrinojs/airbnb-base": "^9.4.0",
|
"@neutrinojs/airbnb-base": "^9.4.0",
|
||||||
|
|
|
@ -0,0 +1,32 @@
|
||||||
|
import AddFavorite from '../packets/add-favorite';
|
||||||
|
import AddFriend from '../packets/add-friend';
|
||||||
|
import Block from '../packets/block';
|
||||||
|
import ConfirmFriend from '../packets/confirm-friend';
|
||||||
|
import RemoveFavorite from '../packets/remove-favorite';
|
||||||
|
import RemoveFriend from '../packets/remove-friend';
|
||||||
|
import Unblock from '../packets/unblock';
|
||||||
|
import Usernames from '../packets/usernames';
|
||||||
|
|
||||||
|
import user from './state/user';
|
||||||
|
import usernames from './state/usernames';
|
||||||
|
|
||||||
|
export * from './state';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
hooks: {
|
||||||
|
'@latus/socket/packets': (latus) => ({
|
||||||
|
AddFavorite: AddFavorite(latus),
|
||||||
|
AddFriend: AddFriend(latus),
|
||||||
|
Block: Block(latus),
|
||||||
|
ConfirmFriend: ConfirmFriend(latus),
|
||||||
|
RemoveFavorite: RemoveFavorite(latus),
|
||||||
|
RemoveFriend: RemoveFriend(latus),
|
||||||
|
Unblock: Unblock(latus),
|
||||||
|
Usernames: Usernames(latus),
|
||||||
|
}),
|
||||||
|
'@reddichat/state/reducers': () => ({
|
||||||
|
user,
|
||||||
|
usernames,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
};
|
2
packages/user/src/client/state/index.js
Normal file
2
packages/user/src/client/state/index.js
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
export * from './user';
|
||||||
|
export * from './usernames';
|
245
packages/user/src/client/state/user.js
Normal file
245
packages/user/src/client/state/user.js
Normal file
|
@ -0,0 +1,245 @@
|
||||||
|
import {LOCATION_CHANGE} from 'connected-react-router';
|
||||||
|
import merge from 'deepmerge';
|
||||||
|
import {addMessage, join, leave} from '@reddichat/chat/client';
|
||||||
|
import {renderChannel} from '@reddichat/core/client';
|
||||||
|
import {localStorage, storage} from '@reddichat/state/client';
|
||||||
|
import {
|
||||||
|
createSelector,
|
||||||
|
createSlice,
|
||||||
|
} from '@reduxjs/toolkit';
|
||||||
|
|
||||||
|
export const userSelector = (state) => state.user;
|
||||||
|
|
||||||
|
export const blockedSelector = createSelector(
|
||||||
|
userSelector,
|
||||||
|
(user) => user.blocked,
|
||||||
|
);
|
||||||
|
|
||||||
|
export const blurredSelector = createSelector(
|
||||||
|
userSelector,
|
||||||
|
(user) => user.blurred,
|
||||||
|
);
|
||||||
|
|
||||||
|
export const favoritesSelector = createSelector(
|
||||||
|
[userSelector],
|
||||||
|
({favorites}) => favorites,
|
||||||
|
);
|
||||||
|
|
||||||
|
const createFavoriteSelector = (type) => (
|
||||||
|
createSelector(
|
||||||
|
[favoritesSelector],
|
||||||
|
(favorites) => (
|
||||||
|
favorites
|
||||||
|
.filter((favorite) => 0 === favorite.indexOf(`/${type}/`))
|
||||||
|
.map((favorite) => favorite.substr(3))
|
||||||
|
),
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
export const favoriteChannelsSelector = createFavoriteSelector('r');
|
||||||
|
|
||||||
|
export const favoriteUsersSelector = createFavoriteSelector('u');
|
||||||
|
|
||||||
|
export const friendshipIndex = (friendship, test) => (
|
||||||
|
friendship
|
||||||
|
.findIndex(({addeeId, adderId}) => test.adderId === adderId && test.addeeId === addeeId)
|
||||||
|
);
|
||||||
|
|
||||||
|
export const friendshipIdIndex = (friendship, id) => (
|
||||||
|
friendship
|
||||||
|
.findIndex(({addeeId, adderId}) => id === adderId || id === addeeId)
|
||||||
|
);
|
||||||
|
|
||||||
|
export const friendshipSelector = createSelector(
|
||||||
|
userSelector,
|
||||||
|
(user) => user.friendship,
|
||||||
|
);
|
||||||
|
|
||||||
|
export const activeFriendshipSelector = createSelector(
|
||||||
|
friendshipSelector,
|
||||||
|
(friendship) => friendship.filter(({status}) => 'active' === status),
|
||||||
|
);
|
||||||
|
|
||||||
|
export const idSelector = createSelector(
|
||||||
|
[userSelector],
|
||||||
|
({id}) => id,
|
||||||
|
);
|
||||||
|
|
||||||
|
export const isAnonymousSelector = createSelector(
|
||||||
|
[userSelector],
|
||||||
|
({isAnonymous}) => isAnonymous,
|
||||||
|
);
|
||||||
|
|
||||||
|
export const pendingFriendshipSelector = createSelector(
|
||||||
|
friendshipSelector,
|
||||||
|
(friendship) => friendship.filter(({status}) => 'pending' === status),
|
||||||
|
);
|
||||||
|
|
||||||
|
export const recentSelector = createSelector(
|
||||||
|
[userSelector],
|
||||||
|
({recent}) => recent,
|
||||||
|
);
|
||||||
|
|
||||||
|
export const redditUsernameSelector = createSelector(
|
||||||
|
userSelector,
|
||||||
|
(user) => user.redditUsername,
|
||||||
|
);
|
||||||
|
|
||||||
|
export const unreadSelector = createSelector(
|
||||||
|
[userSelector],
|
||||||
|
(user) => user.unread,
|
||||||
|
);
|
||||||
|
|
||||||
|
const createUnreadSelector = (type) => createSelector(
|
||||||
|
[unreadSelector],
|
||||||
|
(unread) => (
|
||||||
|
Object.entries(unread)
|
||||||
|
.filter(([name]) => name.charCodeAt(1) === type.charCodeAt(0))
|
||||||
|
.reduce((r, [, count]) => r + count, 0)
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
export const unreadChannelSelector = createUnreadSelector('r');
|
||||||
|
|
||||||
|
export const unreadUserSelector = createUnreadSelector('u');
|
||||||
|
|
||||||
|
export const unreadForChannelSelector = createSelector(
|
||||||
|
[unreadSelector, (_, channel) => channel],
|
||||||
|
(unread, channel) => unread[channel] || 0,
|
||||||
|
);
|
||||||
|
|
||||||
|
const slice = createSlice({
|
||||||
|
name: 'reddichat/user',
|
||||||
|
initialState: merge.all([
|
||||||
|
{
|
||||||
|
activeChannel: '',
|
||||||
|
blocked: [],
|
||||||
|
blurred: false,
|
||||||
|
favorites: [],
|
||||||
|
friendship: [],
|
||||||
|
id: 0,
|
||||||
|
isAnonymous: true,
|
||||||
|
recent: [],
|
||||||
|
redditUsername: 'anonymous',
|
||||||
|
unread: {},
|
||||||
|
},
|
||||||
|
storage(userSelector) || {},
|
||||||
|
], {arrayMerge: (target, source) => Array.from(new Set(source.concat(target)).values())}),
|
||||||
|
/* eslint-disable no-param-reassign */
|
||||||
|
extraReducers: {
|
||||||
|
[addMessage]: ({activeChannel, blurred, unread}, {payload: {channel}}) => {
|
||||||
|
const rendered = renderChannel(channel);
|
||||||
|
if (blurred || activeChannel !== rendered) {
|
||||||
|
unread[rendered] = (unread[rendered] || 0) + 1;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[join]: ({recent}, {payload: {channel}}) => {
|
||||||
|
const {type, name} = channel;
|
||||||
|
if ('r' === type && -1 === recent.indexOf(name)) {
|
||||||
|
recent.push(name);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[leave]: ({unread}, {payload: {channel}}) => {
|
||||||
|
delete unread[renderChannel(channel)];
|
||||||
|
},
|
||||||
|
[localStorage]: ({recent}) => ({recent}),
|
||||||
|
[LOCATION_CHANGE]: (state, {payload: {location: {pathname}}}) => {
|
||||||
|
const {unread} = state;
|
||||||
|
state.activeChannel = pathname.match(/^\/chat\//) ? pathname.substr('/chat'.length) : '';
|
||||||
|
if (unread[state.activeChannel]) {
|
||||||
|
delete unread[state.activeChannel];
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
reducers: {
|
||||||
|
addFriendship: ({friendship}, {payload}) => {
|
||||||
|
friendship.push(payload);
|
||||||
|
},
|
||||||
|
addToFavorites: ({favorites}, {payload: favorite}) => {
|
||||||
|
favorites.push(favorite);
|
||||||
|
},
|
||||||
|
block: ({blocked}, {payload}) => {
|
||||||
|
blocked.push(payload);
|
||||||
|
},
|
||||||
|
confirmFriendship: ({friendship, id}, {payload: otherId}) => {
|
||||||
|
const otherIndex = friendshipIndex(friendship, {adderId: otherId, addeeId: id});
|
||||||
|
const selfIndex = friendshipIndex(friendship, {adderId: id, addeeId: otherId});
|
||||||
|
const index = -1 === otherIndex ? selfIndex : otherIndex;
|
||||||
|
friendship[index].status = 'active';
|
||||||
|
},
|
||||||
|
logOut: () => ({
|
||||||
|
blocked: [],
|
||||||
|
blurred: false,
|
||||||
|
favorites: [],
|
||||||
|
activeChannel: '',
|
||||||
|
friendship: [],
|
||||||
|
id: 0,
|
||||||
|
isAnonymous: true,
|
||||||
|
recent: [],
|
||||||
|
redditUsername: 'anonymous',
|
||||||
|
unread: {},
|
||||||
|
}),
|
||||||
|
removeFriendship: ({friendship, id}, {payload: otherId}) => {
|
||||||
|
const otherIndex = friendshipIndex(friendship, {adderId: otherId, addeeId: id});
|
||||||
|
const selfIndex = friendshipIndex(friendship, {adderId: id, addeeId: otherId});
|
||||||
|
const index = -1 === otherIndex ? selfIndex : otherIndex;
|
||||||
|
friendship.splice(index, 1);
|
||||||
|
},
|
||||||
|
removeFromFavorites: ({favorites}, {payload: favorite}) => {
|
||||||
|
favorites.splice(favorites.indexOf(favorite), 1);
|
||||||
|
},
|
||||||
|
removeRecent: ({recent}, {payload: channel}) => {
|
||||||
|
recent.splice(recent.indexOf(channel), 1);
|
||||||
|
},
|
||||||
|
setBlurred: (state, {payload: blurred}) => {
|
||||||
|
const {pathname} = window.location;
|
||||||
|
const {unread} = state;
|
||||||
|
state.blurred = blurred;
|
||||||
|
const activeChannel = pathname.match(/^\/chat\//) ? pathname.substr('/chat'.length) : '';
|
||||||
|
if (
|
||||||
|
false === blurred
|
||||||
|
&& state.activeChannel === activeChannel
|
||||||
|
&& unread[state.activeChannel]
|
||||||
|
) {
|
||||||
|
delete unread[state.activeChannel];
|
||||||
|
}
|
||||||
|
},
|
||||||
|
submitAddFavorite: () => {},
|
||||||
|
submitAddFriend: () => {},
|
||||||
|
submitBlock: () => {},
|
||||||
|
submitConfirmFriend: () => {},
|
||||||
|
submitRemoveFavorite: () => {},
|
||||||
|
submitRemoveFriend: () => {},
|
||||||
|
submitUnblock: () => {},
|
||||||
|
unblock: ({blocked}, {payload}) => {
|
||||||
|
blocked.splice(blocked.indexOf(payload), 1);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
/* eslint-enable no-param-reassign */
|
||||||
|
});
|
||||||
|
|
||||||
|
slice.reducer.subscription = slice.reducer;
|
||||||
|
|
||||||
|
export const {
|
||||||
|
addFriend,
|
||||||
|
addFriendship,
|
||||||
|
confirmFriendship,
|
||||||
|
addToFavorites,
|
||||||
|
block,
|
||||||
|
logOut,
|
||||||
|
removeFriend,
|
||||||
|
removeFriendship,
|
||||||
|
removeFromFavorites,
|
||||||
|
removeRecent,
|
||||||
|
setBlurred,
|
||||||
|
submitAddFavorite,
|
||||||
|
submitAddFriend,
|
||||||
|
submitBlock,
|
||||||
|
submitConfirmFriend,
|
||||||
|
submitRemoveFriend,
|
||||||
|
submitRemoveFavorite,
|
||||||
|
submitUnblock,
|
||||||
|
unblock,
|
||||||
|
} = slice.actions;
|
||||||
|
|
||||||
|
export default slice.reducer;
|
37
packages/user/src/client/state/usernames.js
Normal file
37
packages/user/src/client/state/usernames.js
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
import {
|
||||||
|
join,
|
||||||
|
joined,
|
||||||
|
} from '@reddichat/chat/client';
|
||||||
|
import {
|
||||||
|
createSelector,
|
||||||
|
createSlice,
|
||||||
|
} from '@reduxjs/toolkit';
|
||||||
|
|
||||||
|
export const usernamesSelector = (state) => state.usernames;
|
||||||
|
|
||||||
|
export const usernameSelector = createSelector(
|
||||||
|
[usernamesSelector, (_, id) => id],
|
||||||
|
(usernames, id) => usernames[id],
|
||||||
|
);
|
||||||
|
|
||||||
|
const slice = createSlice({
|
||||||
|
name: 'reddichat/usernames',
|
||||||
|
initialState: {},
|
||||||
|
/* eslint-disable no-param-reassign */
|
||||||
|
extraReducers: {
|
||||||
|
[join]: (state, {payload: {usernames}}) => ({...state, ...usernames}),
|
||||||
|
[joined]: (state, {payload: {id, username}}) => ({...state, [id]: username}),
|
||||||
|
},
|
||||||
|
reducers: {
|
||||||
|
setUsernames: (state, {payload}) => ({...state, ...payload}),
|
||||||
|
},
|
||||||
|
/* eslint-enable no-param-reassign */
|
||||||
|
});
|
||||||
|
|
||||||
|
slice.reducer.subscription = slice.reducer;
|
||||||
|
|
||||||
|
export const {
|
||||||
|
setUsernames,
|
||||||
|
} = slice.actions;
|
||||||
|
|
||||||
|
export default slice.reducer;
|
|
@ -0,0 +1,35 @@
|
||||||
|
import UserReddichat from './models/user-reddichat';
|
||||||
|
import AddFavorite from './packets/add-favorite.server';
|
||||||
|
import AddFriend from './packets/add-friend.server';
|
||||||
|
import Block from './packets/block.server';
|
||||||
|
import ConfirmFriend from './packets/confirm-friend.server';
|
||||||
|
import RemoveFavorite from './packets/remove-favorite.server';
|
||||||
|
import RemoveFriend from './packets/remove-friend.server';
|
||||||
|
import Unblock from './packets/unblock.server';
|
||||||
|
import Usernames from './packets/usernames.server';
|
||||||
|
|
||||||
|
import userDefaultState from './state/user';
|
||||||
|
import usernamesDefaultState from './state/usernames';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
hooks: {
|
||||||
|
'@latus/db/models.decorate': (Models, latus) => ({
|
||||||
|
...Models,
|
||||||
|
User: UserReddichat(Models.User, latus),
|
||||||
|
}),
|
||||||
|
'@latus/socket/packets': (latus) => ({
|
||||||
|
AddFavorite: AddFavorite(latus),
|
||||||
|
AddFriend: AddFriend(latus),
|
||||||
|
Block: Block(latus),
|
||||||
|
ConfirmFriend: ConfirmFriend(latus),
|
||||||
|
RemoveFavorite: RemoveFavorite(latus),
|
||||||
|
RemoveFriend: RemoveFriend(latus),
|
||||||
|
Unblock: Unblock(latus),
|
||||||
|
Usernames: Usernames(latus),
|
||||||
|
}),
|
||||||
|
'@reddichat/state/defaultState': async (req, latus) => ({
|
||||||
|
user: await userDefaultState(req, latus),
|
||||||
|
usernames: await usernamesDefaultState(req, latus),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
};
|
22
packages/user/src/models/block.js
Normal file
22
packages/user/src/models/block.js
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
import {Model, Types} from '@latus/db';
|
||||||
|
|
||||||
|
class Block extends Model {
|
||||||
|
|
||||||
|
static get attributes() {
|
||||||
|
return {
|
||||||
|
blocked: Types.INTEGER,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
static associate({User}) {
|
||||||
|
User.hasMany(this);
|
||||||
|
this.belongsTo(User);
|
||||||
|
}
|
||||||
|
|
||||||
|
static get name() {
|
||||||
|
return 'Block';
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Block;
|
22
packages/user/src/models/favorite.js
Normal file
22
packages/user/src/models/favorite.js
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
import {Model, Types} from '@latus/db';
|
||||||
|
|
||||||
|
class Favorite extends Model {
|
||||||
|
|
||||||
|
static get attributes() {
|
||||||
|
return {
|
||||||
|
channel: Types.STRING,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
static associate({User}) {
|
||||||
|
User.hasMany(this);
|
||||||
|
this.belongsTo(User);
|
||||||
|
}
|
||||||
|
|
||||||
|
static get name() {
|
||||||
|
return 'Favorite';
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Favorite;
|
33
packages/user/src/models/friendship.js
Normal file
33
packages/user/src/models/friendship.js
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
import {Model, Types} from '@latus/db';
|
||||||
|
|
||||||
|
class Friendship extends Model {
|
||||||
|
|
||||||
|
static get attributes() {
|
||||||
|
return {
|
||||||
|
status: {
|
||||||
|
type: Types.ENUM(['pending', 'active']),
|
||||||
|
defaultValue: 'pending',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
static associate({User}) {
|
||||||
|
User.hasMany(this, {
|
||||||
|
as: 'adder',
|
||||||
|
foreignKey: 'adderId',
|
||||||
|
onDelete: 'CASCADE',
|
||||||
|
});
|
||||||
|
User.hasMany(this, {
|
||||||
|
as: 'addee',
|
||||||
|
foreignKey: 'addeeId',
|
||||||
|
onDelete: 'CASCADE',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
static get name() {
|
||||||
|
return 'Friendship';
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Friendship;
|
36
packages/user/src/models/user-reddichat.js
Normal file
36
packages/user/src/models/user-reddichat.js
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
import {ModelMap, Op, Types} from '@latus/db';
|
||||||
|
|
||||||
|
import {parseChannel} from '@reddichat/core/client';
|
||||||
|
|
||||||
|
export default (User, latus) => class UserReddichat extends User {
|
||||||
|
|
||||||
|
static get attributes() {
|
||||||
|
return {
|
||||||
|
...super.atributes,
|
||||||
|
redditAccessToken: Types.STRING,
|
||||||
|
redditUsername: Types.STRING,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async blocks() {
|
||||||
|
return (await this.getBlocks()).map(({blocked}) => blocked);
|
||||||
|
}
|
||||||
|
|
||||||
|
async favorites() {
|
||||||
|
return (await this.getFavorites()).map(({channel}) => parseChannel(channel));
|
||||||
|
}
|
||||||
|
|
||||||
|
async friendship() {
|
||||||
|
const {Friendship} = ModelMap(latus);
|
||||||
|
const friendship = await Friendship.findAll({
|
||||||
|
where: {
|
||||||
|
[Op.or]: [
|
||||||
|
{adderId: this.id},
|
||||||
|
{addeeId: this.id},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
return friendship.map(({adderId, addeeId, status}) => ({adderId, addeeId, status}));
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
22
packages/user/src/packets/add-favorite.js
Normal file
22
packages/user/src/packets/add-favorite.js
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
import {Packet, ValidationError} from '@latus/socket/packets';
|
||||||
|
import {validateChannel} from '@reddichat/core/client';
|
||||||
|
|
||||||
|
export default () => class AddFavorite extends Packet {
|
||||||
|
|
||||||
|
static get data() {
|
||||||
|
return {
|
||||||
|
type: 'string',
|
||||||
|
name: 'string',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
static async validate({data: channel}, {req: {user}}) {
|
||||||
|
if (!user) {
|
||||||
|
throw new ValidationError({code: 401, reason: 'Unauthorized'});
|
||||||
|
}
|
||||||
|
if (!validateChannel(channel)) {
|
||||||
|
throw new ValidationError({code: 400, reason: 'Malformed channel'});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
11
packages/user/src/packets/add-favorite.server.js
Normal file
11
packages/user/src/packets/add-favorite.server.js
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
import AddFavorite from './add-favorite';
|
||||||
|
|
||||||
|
export default () => class AddFavoriteServer extends AddFavorite() {
|
||||||
|
|
||||||
|
static async respond(packet, socket) {
|
||||||
|
const {req: {user}} = socket;
|
||||||
|
await user.createFavorite({channel: packet.data});
|
||||||
|
socket.to(`/u/${user.id}`, packet);
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
42
packages/user/src/packets/add-friend.js
Normal file
42
packages/user/src/packets/add-friend.js
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
import {Packet, ValidationError} from '@latus/socket/packets';
|
||||||
|
import {validateUsername} from '@reddichat/core/client';
|
||||||
|
|
||||||
|
export default () => class AddFriend extends Packet {
|
||||||
|
|
||||||
|
constructor(...args) {
|
||||||
|
super(...args);
|
||||||
|
this.data = {
|
||||||
|
addeeId: 0,
|
||||||
|
adderId: 0,
|
||||||
|
...this.data,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
static get data() {
|
||||||
|
return {
|
||||||
|
addeeId: 'uint32',
|
||||||
|
adderId: 'uint32',
|
||||||
|
name: 'string',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
static limit = {
|
||||||
|
points: 20,
|
||||||
|
duration: 60,
|
||||||
|
};
|
||||||
|
|
||||||
|
static async validate({data: {addeeId, name}}, {req: {user}}) {
|
||||||
|
if (!user) {
|
||||||
|
throw new ValidationError({code: 401, reason: 'Unauthorized'});
|
||||||
|
}
|
||||||
|
if (0 === addeeId && '' === name) {
|
||||||
|
throw new ValidationError({code: 400, reason: 'Malformed'});
|
||||||
|
}
|
||||||
|
else if ('' !== name) {
|
||||||
|
if (!validateUsername(name)) {
|
||||||
|
throw new ValidationError({code: 400, reason: 'Malformed'});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
40
packages/user/src/packets/add-friend.server.js
Normal file
40
packages/user/src/packets/add-friend.server.js
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
import {ModelMap} from '@latus/db';
|
||||||
|
|
||||||
|
import AddFriend from './add-friend';
|
||||||
|
|
||||||
|
export default (latus) => class AddFriendServer extends AddFriend(latus) {
|
||||||
|
|
||||||
|
static async respond({data: {name}}, socket) {
|
||||||
|
const {req} = socket;
|
||||||
|
const {
|
||||||
|
Friendship,
|
||||||
|
User,
|
||||||
|
} = ModelMap(latus);
|
||||||
|
const adderId = req.user.id;
|
||||||
|
const adderName = req.user.redditUsername;
|
||||||
|
const user = (
|
||||||
|
await User.findOne({where: {redditUsername: name}})
|
||||||
|
|| await User.create({redditUsername: name})
|
||||||
|
);
|
||||||
|
const addeeId = user.id;
|
||||||
|
const addeeName = user.redditUsername;
|
||||||
|
const friendship = await Friendship.findOne(
|
||||||
|
{where: {addeeId: adderId, adderId: addeeId}},
|
||||||
|
);
|
||||||
|
if (friendship) {
|
||||||
|
friendship.status = 'active';
|
||||||
|
await friendship.save();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
await Friendship.create({addeeId, adderId});
|
||||||
|
}
|
||||||
|
[addeeId, adderId].forEach((id) => {
|
||||||
|
const packet = friendship
|
||||||
|
? ['ConfirmFriend', id === adderId ? addeeId : adderId]
|
||||||
|
: ['AddFriend', {addeeId, adderId, name: id === adderId ? addeeName : adderName}];
|
||||||
|
socket.to(`/u/${id}`, packet);
|
||||||
|
});
|
||||||
|
return addeeId;
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
9
packages/user/src/packets/block.js
Normal file
9
packages/user/src/packets/block.js
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
import {Packet} from '@latus/socket/packets';
|
||||||
|
|
||||||
|
export default () => class Block extends Packet {
|
||||||
|
|
||||||
|
static get data() {
|
||||||
|
return 'uint32';
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
39
packages/user/src/packets/block.server.js
Normal file
39
packages/user/src/packets/block.server.js
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
import {ModelMap, Op, ValidationError} from '@latus/db';
|
||||||
|
|
||||||
|
import removeFavoritedUser from '../remove-favorited-user';
|
||||||
|
import Block from './block';
|
||||||
|
|
||||||
|
export default (latus) => class BlockServer extends Block(latus) {
|
||||||
|
|
||||||
|
static async respond(packet, socket) {
|
||||||
|
const {req} = socket;
|
||||||
|
const id = packet.data;
|
||||||
|
const {Friendship, User} = ModelMap(latus);
|
||||||
|
await req.user.createBlock({blocked: id});
|
||||||
|
await Friendship.destroy({
|
||||||
|
where: {
|
||||||
|
[Op.or]: [
|
||||||
|
{[Op.and]: [{addeeId: req.userId}, {adderId: id}]},
|
||||||
|
{[Op.and]: [{addeeId: id}, {adderId: req.userId}]},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const user = await User.findByPk(id);
|
||||||
|
removeFavoritedUser(latus, socket, user, req.user);
|
||||||
|
removeFavoritedUser(latus, socket, req.user, user);
|
||||||
|
socket.to(`/u/${req.userId}`, packet);
|
||||||
|
socket.to(`/u/${req.userId}`, ['RemoveFriend', id]);
|
||||||
|
socket.to(`/u/${id}`, ['RemoveFriend', req.userId]);
|
||||||
|
}
|
||||||
|
|
||||||
|
static async validate({data: id}, {req: {user}}) {
|
||||||
|
if (!user) {
|
||||||
|
throw new ValidationError({code: 401, reason: 'Unauthorized'});
|
||||||
|
}
|
||||||
|
const {User} = ModelMap(latus);
|
||||||
|
if (!await User.count({where: {id}})) {
|
||||||
|
throw new ValidationError({code: 400, reason: 'No such user'});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
14
packages/user/src/packets/confirm-friend.js
Normal file
14
packages/user/src/packets/confirm-friend.js
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
import {Packet} from '@latus/socket/packets';
|
||||||
|
|
||||||
|
export default () => class ConfirmFriend extends Packet {
|
||||||
|
|
||||||
|
static get data() {
|
||||||
|
return 'uint32';
|
||||||
|
}
|
||||||
|
|
||||||
|
static limit = {
|
||||||
|
points: 20,
|
||||||
|
duration: 60,
|
||||||
|
};
|
||||||
|
|
||||||
|
};
|
31
packages/user/src/packets/confirm-friend.server.js
Normal file
31
packages/user/src/packets/confirm-friend.server.js
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
import {ModelMap, ValidationError} from '@latus/db';
|
||||||
|
|
||||||
|
import ConfirmFriend from './confirm-friend';
|
||||||
|
|
||||||
|
export default (latus) => class ConfirmFriendServer extends ConfirmFriend(latus) {
|
||||||
|
|
||||||
|
static async respond({data: adderId}, socket) {
|
||||||
|
const {req} = socket;
|
||||||
|
const {Friendship} = ModelMap(latus);
|
||||||
|
const addeeId = req.user.id;
|
||||||
|
const friendship = await Friendship.findOne({where: {adderId, addeeId}});
|
||||||
|
friendship.status = 'active';
|
||||||
|
await friendship.save();
|
||||||
|
[addeeId, adderId].forEach((id) => {
|
||||||
|
socket.to(`/u/${id}`, ['ConfirmFriend', id === adderId ? addeeId : adderId]);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
static async validate({data: adderId}, {req: {user}}) {
|
||||||
|
if (!user) {
|
||||||
|
throw new ValidationError({code: 401, reason: 'Unauthorized'});
|
||||||
|
}
|
||||||
|
const addeeId = user.id;
|
||||||
|
const {Friendship} = ModelMap(latus);
|
||||||
|
const friendship = await Friendship.findOne({where: {adderId, addeeId}});
|
||||||
|
if (!friendship) {
|
||||||
|
throw new ValidationError({code: 400, reason: 'Malformed'});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
9
packages/user/src/packets/remove-favorite.js
Normal file
9
packages/user/src/packets/remove-favorite.js
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
import {Packet} from '@latus/socket/packets';
|
||||||
|
|
||||||
|
export default () => class RemoveFavorite extends Packet {
|
||||||
|
|
||||||
|
static get data() {
|
||||||
|
return 'string';
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
35
packages/user/src/packets/remove-favorite.server.js
Normal file
35
packages/user/src/packets/remove-favorite.server.js
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
import {ModelMap, ValidationError} from '@latus/db';
|
||||||
|
import {parseChannel, validateChannel} from '@reddichat/core';
|
||||||
|
|
||||||
|
import RemoveFavorite from './remove-favorite';
|
||||||
|
|
||||||
|
export default (latus) => class RemoveFavoriteServer extends RemoveFavorite(latus) {
|
||||||
|
|
||||||
|
static async respond(packet, socket) {
|
||||||
|
const {data: {channel}} = packet;
|
||||||
|
const {req} = socket;
|
||||||
|
const {Favorite} = ModelMap(latus);
|
||||||
|
const favorite = await Favorite.findOne({
|
||||||
|
where: {
|
||||||
|
channel: parseChannel(channel),
|
||||||
|
user_id: req.user.id,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
await Favorite.destroy({where: {id: favorite.id}});
|
||||||
|
socket.to(`/u/${req.userId}`, packet);
|
||||||
|
}
|
||||||
|
|
||||||
|
static async validate({data: channel}, {req: {user}}) {
|
||||||
|
if (!user) {
|
||||||
|
throw new ValidationError({code: 401, reason: 'Unauthorized'});
|
||||||
|
}
|
||||||
|
const {Favorite} = ModelMap(latus);
|
||||||
|
if (!validateChannel(channel)) {
|
||||||
|
throw new ValidationError({code: 400, reason: 'Malformed channel.'});
|
||||||
|
}
|
||||||
|
if (0 === await Favorite.count({where: {user_id: user.id, channel: parseChannel(channel)}})) {
|
||||||
|
throw new ValidationError({code: 400, reason: 'No such favorite existed.'});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
9
packages/user/src/packets/remove-friend.js
Normal file
9
packages/user/src/packets/remove-friend.js
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
import {Packet} from '@latus/socket/packets';
|
||||||
|
|
||||||
|
export default () => class RemoveFriend extends Packet {
|
||||||
|
|
||||||
|
static get data() {
|
||||||
|
return 'uint32';
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
46
packages/user/src/packets/remove-friend.server.js
Normal file
46
packages/user/src/packets/remove-friend.server.js
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
import {ModelMap, Op, ValidationError} from '@latus/db';
|
||||||
|
|
||||||
|
import removeFavoritedUser from '../remove-favorited-user';
|
||||||
|
import RemoveFriend from './remove-friend';
|
||||||
|
|
||||||
|
export default (latus) => class RemoveFriendServer extends RemoveFriend(latus) {
|
||||||
|
|
||||||
|
static async respond({data: id}, socket) {
|
||||||
|
const {req} = socket;
|
||||||
|
const {Friendship, User} = ModelMap(latus);
|
||||||
|
await Friendship.destroy({
|
||||||
|
where: {
|
||||||
|
[Op.or]: [
|
||||||
|
{[Op.and]: [{addeeId: req.userId}, {adderId: id}]},
|
||||||
|
{[Op.and]: [{addeeId: id}, {adderId: req.userId}]},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
socket.to(`/u/${id}`, ['RemoveFriend', req.userId]);
|
||||||
|
socket.to(`/u/${req.userId}`, ['RemoveFriend', id]);
|
||||||
|
const user = await User.findByPk(id);
|
||||||
|
return Promise.all([
|
||||||
|
removeFavoritedUser(socket, user, req.user),
|
||||||
|
removeFavoritedUser(socket, req.user, user),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
static async validate({data: id}, {req: {user, userId}}) {
|
||||||
|
if (!user) {
|
||||||
|
throw new ValidationError({code: 401, reason: 'Unauthorized'});
|
||||||
|
}
|
||||||
|
const {Friendship} = ModelMap(latus);
|
||||||
|
const hasFriendship = !!await Friendship.count({
|
||||||
|
where: {
|
||||||
|
[Op.or]: [
|
||||||
|
{[Op.and]: [{addeeId: userId}, {adderId: id}]},
|
||||||
|
{[Op.and]: [{addeeId: id}, {adderId: userId}]},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
if (!hasFriendship) {
|
||||||
|
throw new ValidationError({code: 400, reason: 'Malformed friendship.'});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
9
packages/user/src/packets/unblock.js
Normal file
9
packages/user/src/packets/unblock.js
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
import {Packet} from '@latus/socket/packets';
|
||||||
|
|
||||||
|
export default () => class Unblock extends Packet {
|
||||||
|
|
||||||
|
static get data() {
|
||||||
|
return 'uint32';
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
36
packages/user/src/packets/unblock.server.js
Normal file
36
packages/user/src/packets/unblock.server.js
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
import {ModelMap, ValidationError} from '@latus/db';
|
||||||
|
|
||||||
|
import Unblock from './unblock';
|
||||||
|
|
||||||
|
export default (latus) => class UnblockServer extends Unblock(latus) {
|
||||||
|
|
||||||
|
static async respond(packet, socket) {
|
||||||
|
const {data: blocked} = packet;
|
||||||
|
const {req} = socket;
|
||||||
|
const {Block: BlockModel} = ModelMap(latus);
|
||||||
|
await BlockModel.destroy({
|
||||||
|
where: {
|
||||||
|
blocked,
|
||||||
|
user_id: req.userId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
socket.to(`/u/${req.userId}`, packet);
|
||||||
|
}
|
||||||
|
|
||||||
|
static async validate({data: blocked}, {req: {user}}) {
|
||||||
|
if (!user) {
|
||||||
|
throw new ValidationError({code: 401, reason: 'Unauthorized'});
|
||||||
|
}
|
||||||
|
const {Block: BlockModel} = ModelMap(latus);
|
||||||
|
const hasBlock = !!await BlockModel.count({
|
||||||
|
where: {
|
||||||
|
blocked,
|
||||||
|
user_id: user.id,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
if (!hasBlock) {
|
||||||
|
throw new ValidationError({code: 400, reason: "Wasn't blocking."});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
11
packages/user/src/packets/usernames.js
Normal file
11
packages/user/src/packets/usernames.js
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
import {Packet} from '@latus/socket/packets';
|
||||||
|
|
||||||
|
export default () => class Usernames extends Packet {
|
||||||
|
|
||||||
|
static get data() {
|
||||||
|
return [
|
||||||
|
'uint32',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
14
packages/user/src/packets/usernames.server.js
Normal file
14
packages/user/src/packets/usernames.server.js
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
import {ModelMap} from '@latus/db';
|
||||||
|
|
||||||
|
import Usernames from './usernames';
|
||||||
|
|
||||||
|
export default (latus) => class UsernamesServer extends Usernames(latus) {
|
||||||
|
|
||||||
|
static async respond(packet) {
|
||||||
|
const {User} = ModelMap(latus);
|
||||||
|
return Promise.all(packet.data.map(
|
||||||
|
async (id) => (await User.findByPk(id)).redditUsername,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
12
packages/user/src/remove-favorited-user.js
Normal file
12
packages/user/src/remove-favorited-user.js
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
import {ModelMap} from '@latus/db';
|
||||||
|
|
||||||
|
export default async (latus, socket, user, other) => {
|
||||||
|
const {Favorite} = ModelMap(latus);
|
||||||
|
const favorite = await Favorite.findOne(
|
||||||
|
{where: {channel: `/u/${other.redditUsername}`, user_id: user.id}},
|
||||||
|
);
|
||||||
|
if (favorite) {
|
||||||
|
await Favorite.destroy({where: {id: favorite.id}});
|
||||||
|
socket.to(`/u/${user.id}`, ['RemoveFavorite', `/u/${other.redditUsername}`]);
|
||||||
|
}
|
||||||
|
};
|
0
packages/user/src/state/index.js
Normal file
0
packages/user/src/state/index.js
Normal file
20
packages/user/src/state/user.js
Normal file
20
packages/user/src/state/user.js
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
import {renderChannel} from '@reddichat/core';
|
||||||
|
|
||||||
|
import {channelsToHydrate} from '@reddichat/chat';
|
||||||
|
|
||||||
|
export default async (req, latus) => {
|
||||||
|
const {/* channel, */user} = req;
|
||||||
|
if (!user) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
const toHydrate = await channelsToHydrate(req, latus);
|
||||||
|
return {
|
||||||
|
// activeChannel: channel ? renderChannel(channel) : '',
|
||||||
|
blocked: user ? await user.blocks() : [],
|
||||||
|
favorites: (await user.favorites()).map(renderChannel),
|
||||||
|
friendship: user ? await user.friendships() : [],
|
||||||
|
id: user.id,
|
||||||
|
redditUsername: user.redditUsername,
|
||||||
|
recent: toHydrate.filter(({type}) => 'r' === type).map(({name}) => name),
|
||||||
|
};
|
||||||
|
};
|
24
packages/user/src/state/usernames.js
Normal file
24
packages/user/src/state/usernames.js
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
import {ModelMap} from '@latus/db';
|
||||||
|
|
||||||
|
import {chatUserIds} from '@reddichat/chat';
|
||||||
|
|
||||||
|
export default async (req, latus) => {
|
||||||
|
const {user} = req;
|
||||||
|
const {User} = ModelMap(latus);
|
||||||
|
const allIds = Array.from(new Set(
|
||||||
|
[]
|
||||||
|
.concat(user ? await user.blocks() : [])
|
||||||
|
.concat(await chatUserIds(latus, req))
|
||||||
|
.concat(
|
||||||
|
(user ? await user.friendships() : [])
|
||||||
|
.reduce((r, {addeeId, adderId}) => ([...r, addeeId, adderId]), []),
|
||||||
|
),
|
||||||
|
).values());
|
||||||
|
return Object.fromEntries(await Promise.all(
|
||||||
|
allIds
|
||||||
|
.map(async (id) => [
|
||||||
|
id,
|
||||||
|
0 === id ? 'anonymous' : (await User.findByPk(id)).redditUsername,
|
||||||
|
]),
|
||||||
|
));
|
||||||
|
};
|
|
@ -3,4 +3,6 @@
|
||||||
// Neutrino's inspect feature can be used to view/export the generated configuration.
|
// Neutrino's inspect feature can be used to view/export the generated configuration.
|
||||||
const neutrino = require('neutrino');
|
const neutrino = require('neutrino');
|
||||||
|
|
||||||
module.exports = neutrino(require(`${__dirname}/.neutrinorc`)).webpack();
|
const configOfConfigs = require(`${__dirname}/.neutrinorc`);
|
||||||
|
const configs = Array.isArray(configOfConfigs) ? configOfConfigs : [configOfConfigs];
|
||||||
|
module.exports = configs.map((config) => neutrino(config).webpack());
|
||||||
|
|
8710
packages/user/yarn.lock
Normal file
8710
packages/user/yarn.lock
Normal file
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user