mirror of
https://github.com/darkwire/darkwire.io.git
synced 2025-07-18 10:49:02 +00:00
Allow to launch server without Redis (#119)
* Add nvmrc file for nvm use * Add help to run redis store with docker * Add Redis and memory store * Rename dist files * Allow to launch server without Redis * Slit stores in their own files * Update readme.md Co-authored-by: Alan Friedman <d.alan.friedman@gmail.com> * Mimic Redis API * Move store logic in is own file Co-authored-by: Alan Friedman <d.alan.friedman@gmail.com>
This commit is contained in:
parent
4c5207d205
commit
2a8d3281db
16
readme.md
16
readme.md
@ -16,6 +16,22 @@ The Darkwire.io [web client](/client) is written in JavaScript with React JS and
|
|||||||
|
|
||||||
### Development
|
### Development
|
||||||
|
|
||||||
|
#### Prerequisites
|
||||||
|
|
||||||
|
Copy `.env.dist` files in `server/` and `client/` directories without the `.dist`
|
||||||
|
extensions and adapt them to your needs.
|
||||||
|
|
||||||
|
You need [Redis](https://redis.io/) in order to make the server works.
|
||||||
|
A simple way to achieve this, if you have docker, is to execute the following
|
||||||
|
command:
|
||||||
|
|
||||||
|
```
|
||||||
|
docker run --name darkwire-redis --rm -p 6379:6379 -d redis redis-server --appendonly yes
|
||||||
|
```
|
||||||
|
|
||||||
|
Alternatively, you can select the _memory_ `STORE_BACKEND` instead of _redis_
|
||||||
|
in your server `.env` file to avoid Redis use.
|
||||||
|
|
||||||
#### Setup
|
#### Setup
|
||||||
|
|
||||||
Install dependencies
|
Install dependencies
|
||||||
|
@ -1,8 +1,15 @@
|
|||||||
|
# Abuse mail configuration
|
||||||
MAILGUN_API_KEY=api-key
|
MAILGUN_API_KEY=api-key
|
||||||
MAILGUN_DOMAIN=darkwire.io
|
MAILGUN_DOMAIN=darkwire.io
|
||||||
ABUSE_TO_EMAIL_ADDRESS=abuse@darkwire.io
|
ABUSE_TO_EMAIL_ADDRESS=abuse@darkwire.io
|
||||||
ABUSE_FROM_EMAIL_ADDRESS=Darkwire <no-reply@darkwire.io>
|
ABUSE_FROM_EMAIL_ADDRESS=Darkwire <no-reply@darkwire.io>
|
||||||
REDIS_URL=redis://localhost:6379
|
|
||||||
CLIENT_DIST_DIRECTORY='client/dist/path'
|
CLIENT_DIST_DIRECTORY='client/dist/path'
|
||||||
|
|
||||||
ROOM_HASH_SECRET='some-uuid'
|
ROOM_HASH_SECRET='some-uuid'
|
||||||
|
|
||||||
SITE_URL=https://darkwire.io
|
SITE_URL=https://darkwire.io
|
||||||
|
|
||||||
|
# Store configuration
|
||||||
|
STORE_BACKEND=redis
|
||||||
|
STORE_HOST=redis://localhost:6379
|
@ -1,21 +1,25 @@
|
|||||||
import { getRedis } from './index'
|
import getStore from './store';
|
||||||
|
|
||||||
export async function pollForInactiveRooms() {
|
export async function pollForInactiveRooms() {
|
||||||
const redis = getRedis();
|
const store = getStore();
|
||||||
|
|
||||||
console.log('Checking for inactive rooms...');
|
console.log('Checking for inactive rooms...');
|
||||||
const rooms = await redis.hgetallAsync('rooms') || {};
|
const rooms = (await store.getAll('rooms')) || {};
|
||||||
console.log(`${Object.keys(rooms).length} rooms found`);
|
console.log(`${Object.keys(rooms).length} rooms found`);
|
||||||
|
|
||||||
Object.keys(rooms).forEach(async roomId => {
|
Object.keys(rooms).forEach(async (roomId) => {
|
||||||
const room = JSON.parse(rooms[roomId]);
|
const room = JSON.parse(rooms[roomId]);
|
||||||
const timeSinceUpdatedInSeconds = (Date.now() - room.updatedAt) / 1000;
|
const timeSinceUpdatedInSeconds = (Date.now() - room.updatedAt) / 1000;
|
||||||
const timeSinceUpdatedInDays = Math.round(timeSinceUpdatedInSeconds / 60 / 60 / 24);
|
const timeSinceUpdatedInDays = Math.round(
|
||||||
|
timeSinceUpdatedInSeconds / 60 / 60 / 24
|
||||||
|
);
|
||||||
if (timeSinceUpdatedInDays > 7) {
|
if (timeSinceUpdatedInDays > 7) {
|
||||||
console.log(`Deleting roomId ${roomId} which hasn't been used in ${timeSinceUpdatedInDays} days`);
|
console.log(
|
||||||
await redis.hdelAsync('rooms', roomId);
|
`Deleting roomId ${roomId} which hasn't been used in ${timeSinceUpdatedInDays} days`
|
||||||
|
);
|
||||||
|
await store.del('rooms', roomId);
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
|
|
||||||
setTimeout(pollForInactiveRooms, (1000 * 60 * 60 * 12)); // every 12 hours
|
setTimeout(pollForInactiveRooms, 1000 * 60 * 60 * 12); // every 12 hours
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
require('dotenv').config()
|
require('dotenv').config();
|
||||||
import http from 'http';
|
import http from 'http';
|
||||||
import https from 'https';
|
import https from 'https';
|
||||||
import Koa from 'koa';
|
import Koa from 'koa';
|
||||||
@ -6,22 +6,13 @@ import Io from 'socket.io';
|
|||||||
import KoaBody from 'koa-body';
|
import KoaBody from 'koa-body';
|
||||||
import cors from 'kcors';
|
import cors from 'kcors';
|
||||||
import Router from 'koa-router';
|
import Router from 'koa-router';
|
||||||
import bluebird from 'bluebird';
|
|
||||||
import Redis from 'redis';
|
|
||||||
import socketRedis from 'socket.io-redis';
|
|
||||||
import Socket from './socket';
|
import Socket from './socket';
|
||||||
import crypto from 'crypto'
|
import crypto from 'crypto';
|
||||||
import mailer from './utils/mailer';
|
import mailer from './utils/mailer';
|
||||||
import koaStatic from 'koa-static';
|
import koaStatic from 'koa-static';
|
||||||
import koaSend from 'koa-send';
|
import koaSend from 'koa-send';
|
||||||
import { pollForInactiveRooms } from './inactive_rooms';
|
import { pollForInactiveRooms } from './inactive_rooms';
|
||||||
|
import getStore from './store';
|
||||||
bluebird.promisifyAll(Redis.RedisClient.prototype);
|
|
||||||
bluebird.promisifyAll(Redis.Multi.prototype);
|
|
||||||
|
|
||||||
const redis = Redis.createClient(process.env.REDIS_URL)
|
|
||||||
|
|
||||||
export const getRedis = () => redis
|
|
||||||
|
|
||||||
const env = process.env.NODE_ENV || 'development';
|
const env = process.env.NODE_ENV || 'development';
|
||||||
|
|
||||||
@ -35,12 +26,16 @@ const appName = process.env.HEROKU_APP_NAME;
|
|||||||
const isReviewApp = /-pr-/.test(appName);
|
const isReviewApp = /-pr-/.test(appName);
|
||||||
const siteURL = process.env.SITE_URL;
|
const siteURL = process.env.SITE_URL;
|
||||||
|
|
||||||
|
const store = getStore();
|
||||||
|
|
||||||
if ((siteURL || env === 'development') && !isReviewApp) {
|
if ((siteURL || env === 'development') && !isReviewApp) {
|
||||||
app.use(cors({
|
app.use(
|
||||||
|
cors({
|
||||||
origin: env === 'development' ? '*' : siteURL,
|
origin: env === 'development' ? '*' : siteURL,
|
||||||
allowMethods: ['GET', 'HEAD', 'POST'],
|
allowMethods: ['GET', 'HEAD', 'POST'],
|
||||||
credentials: true,
|
credentials: true,
|
||||||
}));
|
})
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
router.post('/abuse/:roomId', koaBody, async (ctx) => {
|
router.post('/abuse/:roomId', koaBody, async (ctx) => {
|
||||||
@ -48,19 +43,22 @@ router.post('/abuse/:roomId', koaBody, async (ctx) => {
|
|||||||
|
|
||||||
roomId = roomId.trim();
|
roomId = roomId.trim();
|
||||||
|
|
||||||
if (process.env.ABUSE_FROM_EMAIL_ADDRESS && process.env.ABUSE_TO_EMAIL_ADDRESS) {
|
if (
|
||||||
const abuseForRoomExists = await redis.hgetAsync('abuse', roomId);
|
process.env.ABUSE_FROM_EMAIL_ADDRESS &&
|
||||||
|
process.env.ABUSE_TO_EMAIL_ADDRESS
|
||||||
|
) {
|
||||||
|
const abuseForRoomExists = await store.get('abuse', roomId);
|
||||||
if (!abuseForRoomExists) {
|
if (!abuseForRoomExists) {
|
||||||
mailer.send({
|
mailer.send({
|
||||||
from: process.env.ABUSE_FROM_EMAIL_ADDRESS,
|
from: process.env.ABUSE_FROM_EMAIL_ADDRESS,
|
||||||
to: process.env.ABUSE_TO_EMAIL_ADDRESS,
|
to: process.env.ABUSE_TO_EMAIL_ADDRESS,
|
||||||
subject: 'Darkwire Abuse Notification',
|
subject: 'Darkwire Abuse Notification',
|
||||||
text: `Room ID: ${roomId}`
|
text: `Room ID: ${roomId}`,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
await redis.hincrbyAsync('abuse', roomId, 1);
|
await store.inc('abuse', roomId);
|
||||||
|
|
||||||
ctx.status = 200;
|
ctx.status = 200;
|
||||||
});
|
});
|
||||||
@ -68,7 +66,9 @@ router.post('/abuse/:roomId', koaBody, async (ctx) => {
|
|||||||
app.use(router.routes());
|
app.use(router.routes());
|
||||||
|
|
||||||
const apiHost = process.env.API_HOST;
|
const apiHost = process.env.API_HOST;
|
||||||
const cspDefaultSrc = `'self'${apiHost ? ` https://${apiHost} wss://${apiHost}` : ''}`
|
const cspDefaultSrc = `'self'${
|
||||||
|
apiHost ? ` https://${apiHost} wss://${apiHost}` : ''
|
||||||
|
}`;
|
||||||
|
|
||||||
function setStaticFileHeaders(ctx) {
|
function setStaticFileHeaders(ctx) {
|
||||||
ctx.set({
|
ctx.set({
|
||||||
@ -78,7 +78,8 @@ function setStaticFileHeaders(ctx) {
|
|||||||
'X-XSS-Protection': '1; mode=block',
|
'X-XSS-Protection': '1; mode=block',
|
||||||
'X-Content-Type-Options': 'nosniff',
|
'X-Content-Type-Options': 'nosniff',
|
||||||
'Referrer-Policy': 'no-referrer',
|
'Referrer-Policy': 'no-referrer',
|
||||||
'Feature-Policy': "geolocation 'none'; vr 'none'; payment 'none'; microphone 'none'",
|
'Feature-Policy':
|
||||||
|
"geolocation 'none'; vr 'none'; payment 'none'; microphone 'none'",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -87,16 +88,16 @@ if (clientDistDirectory) {
|
|||||||
app.use(async (ctx, next) => {
|
app.use(async (ctx, next) => {
|
||||||
setStaticFileHeaders(ctx);
|
setStaticFileHeaders(ctx);
|
||||||
await koaStatic(clientDistDirectory, {
|
await koaStatic(clientDistDirectory, {
|
||||||
maxage: ctx.req.url === '/' ? 60 * 1000 : 365 * 24 * 60 * 60 * 1000 // one minute in ms for html doc, one year for css, js, etc
|
maxage: ctx.req.url === '/' ? 60 * 1000 : 365 * 24 * 60 * 60 * 1000, // one minute in ms for html doc, one year for css, js, etc
|
||||||
})(ctx, next);
|
})(ctx, next);
|
||||||
});
|
});
|
||||||
|
|
||||||
app.use(async (ctx) => {
|
app.use(async (ctx) => {
|
||||||
setStaticFileHeaders(ctx);
|
setStaticFileHeaders(ctx);
|
||||||
await koaSend(ctx, 'index.html', { root: clientDistDirectory });
|
await koaSend(ctx, 'index.html', { root: clientDistDirectory });
|
||||||
})
|
});
|
||||||
} else {
|
} else {
|
||||||
app.use(async ctx => {
|
app.use(async (ctx) => {
|
||||||
ctx.body = { ready: true };
|
ctx.body = { ready: true };
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -106,52 +107,52 @@ const protocol = (process.env.PROTOCOL || 'http') === 'http' ? http : https;
|
|||||||
const server = protocol.createServer(app.callback());
|
const server = protocol.createServer(app.callback());
|
||||||
const io = Io(server, {
|
const io = Io(server, {
|
||||||
pingInterval: 20000,
|
pingInterval: 20000,
|
||||||
pingTimeout: 5000
|
pingTimeout: 5000,
|
||||||
});
|
});
|
||||||
io.adapter(socketRedis(process.env.REDIS_URL));
|
|
||||||
|
// Only use socket adapter if store has one
|
||||||
|
if (store.hasSocketAdapter) {
|
||||||
|
io.adapter(store.getSocketAdapter());
|
||||||
|
}
|
||||||
|
|
||||||
const roomHashSecret = process.env.ROOM_HASH_SECRET;
|
const roomHashSecret = process.env.ROOM_HASH_SECRET;
|
||||||
|
|
||||||
const getRoomIdHash = (id) => {
|
const getRoomIdHash = (id) => {
|
||||||
if (env === 'development') {
|
if (env === 'development') {
|
||||||
return id
|
return id;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (roomHashSecret) {
|
if (roomHashSecret) {
|
||||||
return crypto
|
return crypto.createHmac('sha256', roomHashSecret).update(id).digest('hex');
|
||||||
.createHmac('sha256', roomHashSecret)
|
|
||||||
.update(id)
|
|
||||||
.digest('hex')
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return crypto.createHash('sha256').update(id).digest('hex');
|
return crypto.createHash('sha256').update(id).digest('hex');
|
||||||
}
|
};
|
||||||
|
|
||||||
export const getIO = () => io
|
export const getIO = () => io;
|
||||||
|
|
||||||
io.on('connection', async (socket) => {
|
io.on('connection', async (socket) => {
|
||||||
const roomId = socket.handshake.query.roomId
|
const roomId = socket.handshake.query.roomId;
|
||||||
|
|
||||||
const roomIdHash = getRoomIdHash(roomId)
|
const roomIdHash = getRoomIdHash(roomId);
|
||||||
|
|
||||||
let room = await redis.hgetAsync('rooms', roomIdHash)
|
let room = await store.get('rooms', roomIdHash);
|
||||||
room = JSON.parse(room || '{}')
|
room = JSON.parse(room || '{}');
|
||||||
|
|
||||||
new Socket({
|
new Socket({
|
||||||
roomIdOriginal: roomId,
|
roomIdOriginal: roomId,
|
||||||
roomId: roomIdHash,
|
roomId: roomIdHash,
|
||||||
socket,
|
socket,
|
||||||
room,
|
room,
|
||||||
})
|
});
|
||||||
})
|
});
|
||||||
|
|
||||||
const init = async () => {
|
const init = async () => {
|
||||||
server.listen(PORT, () => {
|
server.listen(PORT, () => {
|
||||||
console.log(`Darkwire is online at port ${PORT}`);
|
console.log(`Darkwire is online at port ${PORT}`);
|
||||||
})
|
});
|
||||||
|
|
||||||
pollForInactiveRooms();
|
pollForInactiveRooms();
|
||||||
}
|
};
|
||||||
|
|
||||||
init()
|
|
||||||
|
|
||||||
|
init();
|
||||||
|
@ -1,27 +1,27 @@
|
|||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import uuid from 'uuid/v4';
|
import { getIO } from './index';
|
||||||
import { getIO, getRedis } from './index'
|
import getStore from './store';
|
||||||
|
|
||||||
export default class Socket {
|
export default class Socket {
|
||||||
constructor(opts) {
|
constructor(opts) {
|
||||||
const { roomId, socket, room, roomIdOriginal } = opts
|
const { roomId, socket, room, roomIdOriginal } = opts;
|
||||||
|
|
||||||
this._roomId = roomId
|
this._roomId = roomId;
|
||||||
this.socket = socket;
|
this.socket = socket;
|
||||||
this.roomIdOriginal = roomIdOriginal;
|
this.roomIdOriginal = roomIdOriginal;
|
||||||
this.room = room;
|
this.room = room;
|
||||||
if (room.isLocked) {
|
if (room.isLocked) {
|
||||||
this.sendRoomLocked();
|
this.sendRoomLocked();
|
||||||
return
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.init(opts)
|
this.init(opts);
|
||||||
}
|
}
|
||||||
|
|
||||||
async init(opts) {
|
async init(opts) {
|
||||||
const { roomId, socket, room } = opts
|
const { roomId, socket, room } = opts;
|
||||||
await this.joinRoom(roomId, socket.id)
|
await this.joinRoom(roomId, socket);
|
||||||
this.handleSocket(socket)
|
this.handleSocket(socket);
|
||||||
}
|
}
|
||||||
|
|
||||||
sendRoomLocked() {
|
sendRoomLocked() {
|
||||||
@ -32,30 +32,41 @@ export default class Socket {
|
|||||||
const json = {
|
const json = {
|
||||||
...room,
|
...room,
|
||||||
updatedAt: Date.now(),
|
updatedAt: Date.now(),
|
||||||
}
|
};
|
||||||
|
|
||||||
return getRedis().hsetAsync('rooms', this._roomId, JSON.stringify(json))
|
return getStore().set('rooms', this._roomId, JSON.stringify(json));
|
||||||
}
|
}
|
||||||
|
|
||||||
async destroyRoom() {
|
async destroyRoom() {
|
||||||
return getRedis().hdel('rooms', this._roomId)
|
return getStore().del('rooms', this._roomId);
|
||||||
}
|
}
|
||||||
|
|
||||||
fetchRoom() {
|
fetchRoom() {
|
||||||
return new Promise(async (resolve, reject) => {
|
return new Promise(async (resolve, reject) => {
|
||||||
const res = await getRedis().hgetAsync('rooms', this._roomId)
|
const res = await getStore().get('rooms', this._roomId);
|
||||||
resolve(JSON.parse(res || '{}'))
|
resolve(JSON.parse(res || '{}'));
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
joinRoom(roomId, socketId) {
|
joinRoom(roomId, socket) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
getIO().of('/').adapter.remoteJoin(socketId, roomId, (err) => {
|
if (getStore().hasSocketAdapter) {
|
||||||
|
getIO()
|
||||||
|
.of('/')
|
||||||
|
.adapter.remoteJoin(socket.id, roomId, (err) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
reject()
|
reject();
|
||||||
}
|
}
|
||||||
resolve()
|
resolve();
|
||||||
});
|
});
|
||||||
|
} else {
|
||||||
|
socket.join(roomId, (err) => {
|
||||||
|
if (err) {
|
||||||
|
reject();
|
||||||
|
}
|
||||||
|
resolve();
|
||||||
|
});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -65,56 +76,63 @@ export default class Socket {
|
|||||||
});
|
});
|
||||||
|
|
||||||
socket.on('USER_ENTER', async (payload) => {
|
socket.on('USER_ENTER', async (payload) => {
|
||||||
let room = await this.fetchRoom()
|
let room = await this.fetchRoom();
|
||||||
if (_.isEmpty(room)) {
|
if (_.isEmpty(room)) {
|
||||||
room = {
|
room = {
|
||||||
id: this._roomId,
|
id: this._roomId,
|
||||||
users: [],
|
users: [],
|
||||||
isLocked: false,
|
isLocked: false,
|
||||||
createdAt: Date.now(),
|
createdAt: Date.now(),
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const newRoom = {
|
const newRoom = {
|
||||||
...room,
|
...room,
|
||||||
users: [...(room.users || []), {
|
users: [
|
||||||
|
...(room.users || []),
|
||||||
|
{
|
||||||
socketId: socket.id,
|
socketId: socket.id,
|
||||||
publicKey: payload.publicKey,
|
publicKey: payload.publicKey,
|
||||||
isOwner: (room.users || []).length === 0,
|
isOwner: (room.users || []).length === 0,
|
||||||
}]
|
},
|
||||||
}
|
],
|
||||||
await this.saveRoom(newRoom)
|
};
|
||||||
|
await this.saveRoom(newRoom);
|
||||||
|
|
||||||
getIO().to(this._roomId).emit('USER_ENTER', {
|
getIO()
|
||||||
|
.to(this._roomId)
|
||||||
|
.emit('USER_ENTER', {
|
||||||
...newRoom,
|
...newRoom,
|
||||||
id: this.roomIdOriginal
|
id: this.roomIdOriginal,
|
||||||
|
});
|
||||||
});
|
});
|
||||||
})
|
|
||||||
|
|
||||||
socket.on('TOGGLE_LOCK_ROOM', async (data, callback) => {
|
socket.on('TOGGLE_LOCK_ROOM', async (data, callback) => {
|
||||||
const room = await this.fetchRoom()
|
const room = await this.fetchRoom();
|
||||||
const user = (room.users || []).find(u => u.socketId === socket.id && u.isOwner)
|
const user = (room.users || []).find(
|
||||||
|
(u) => u.socketId === socket.id && u.isOwner
|
||||||
|
);
|
||||||
|
|
||||||
if (!user) {
|
if (!user) {
|
||||||
callback({
|
callback({
|
||||||
isLocked: room.isLocked,
|
isLocked: room.isLocked,
|
||||||
})
|
});
|
||||||
return
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.saveRoom({
|
await this.saveRoom({
|
||||||
...room,
|
...room,
|
||||||
isLocked: !room.isLocked,
|
isLocked: !room.isLocked,
|
||||||
})
|
});
|
||||||
|
|
||||||
socket.to(this._roomId).emit('TOGGLE_LOCK_ROOM', {
|
socket.to(this._roomId).emit('TOGGLE_LOCK_ROOM', {
|
||||||
locked: !room.isLocked,
|
locked: !room.isLocked,
|
||||||
publicKey: user && user.publicKey
|
publicKey: user && user.publicKey,
|
||||||
});
|
});
|
||||||
|
|
||||||
callback({
|
callback({
|
||||||
isLocked: !room.isLocked,
|
isLocked: !room.isLocked,
|
||||||
})
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
socket.on('disconnect', () => this.handleDisconnect(socket));
|
socket.on('disconnect', () => this.handleDisconnect(socket));
|
||||||
@ -123,25 +141,26 @@ export default class Socket {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async handleDisconnect(socket) {
|
async handleDisconnect(socket) {
|
||||||
let room = await this.fetchRoom()
|
let room = await this.fetchRoom();
|
||||||
|
|
||||||
const newRoom = {
|
const newRoom = {
|
||||||
...room,
|
...room,
|
||||||
users: (room.users || []).filter(u => u.socketId !== socket.id).map((u, index) => ({
|
users: (room.users || [])
|
||||||
|
.filter((u) => u.socketId !== socket.id)
|
||||||
|
.map((u, index) => ({
|
||||||
...u,
|
...u,
|
||||||
isOwner: index === 0,
|
isOwner: index === 0,
|
||||||
}))
|
})),
|
||||||
}
|
};
|
||||||
|
|
||||||
await this.saveRoom(newRoom)
|
await this.saveRoom(newRoom);
|
||||||
|
|
||||||
getIO().to(this._roomId).emit('USER_EXIT', newRoom.users);
|
getIO().to(this._roomId).emit('USER_EXIT', newRoom.users);
|
||||||
|
|
||||||
if (newRoom.users && newRoom.users.length === 0) {
|
if (newRoom.users && newRoom.users.length === 0) {
|
||||||
await this.destroyRoom()
|
await this.destroyRoom();
|
||||||
}
|
}
|
||||||
|
|
||||||
socket.disconnect(true);
|
socket.disconnect(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
48
server/src/store/Memory.js
Normal file
48
server/src/store/Memory.js
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
/**
|
||||||
|
* Memory store more for testing purpose than production use.
|
||||||
|
*/
|
||||||
|
export class MemoryStore {
|
||||||
|
constructor() {
|
||||||
|
this.store = {};
|
||||||
|
this.hasSocketAdapter = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
async get(key, field) {
|
||||||
|
if (this.store[key] === undefined || this.store[key][field] === undefined) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return this.store[key][field];
|
||||||
|
}
|
||||||
|
|
||||||
|
async getAll(key) {
|
||||||
|
if (this.store[key] === undefined) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.store[key];
|
||||||
|
}
|
||||||
|
|
||||||
|
async set(key, field, value) {
|
||||||
|
if (this.store[key] === undefined) {
|
||||||
|
this.store[key] = {};
|
||||||
|
}
|
||||||
|
this.store[key][field] = value;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
async del(key, field) {
|
||||||
|
if (this.store[key] === undefined || this.store[key][field] === undefined) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
delete this.store[key][field];
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
async inc(key, field, inc = 1) {
|
||||||
|
this.store[key][field] += inc;
|
||||||
|
return this.store[key][field];
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export default MemoryStore
|
42
server/src/store/Redis.js
Normal file
42
server/src/store/Redis.js
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
import Redis from 'redis';
|
||||||
|
import bluebird from 'bluebird';
|
||||||
|
import socketRedis from 'socket.io-redis';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Redis store.
|
||||||
|
*/
|
||||||
|
export class RedisStore {
|
||||||
|
constructor(redisUrl) {
|
||||||
|
bluebird.promisifyAll(Redis.RedisClient.prototype);
|
||||||
|
bluebird.promisifyAll(Redis.Multi.prototype);
|
||||||
|
this.redisUrl = redisUrl;
|
||||||
|
this.redis = Redis.createClient(redisUrl);
|
||||||
|
this.hasSocketAdapter = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
get(key, field) {
|
||||||
|
return this.redis.hgetAsync(key, field);
|
||||||
|
}
|
||||||
|
|
||||||
|
getAll(key) {
|
||||||
|
return this.redis.hgetallAsync(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
set(key, field, value) {
|
||||||
|
return this.redis.hsetAsync(key, field, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
del(key, field) {
|
||||||
|
return this.hdelAsync(key, field);
|
||||||
|
}
|
||||||
|
|
||||||
|
inc(key, field, inc = 1) {
|
||||||
|
return this.redis.incrbyAsync(key, field, inc);
|
||||||
|
}
|
||||||
|
|
||||||
|
getSocketAdapter() {
|
||||||
|
return socketRedis(this.redisUrl);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default RedisStore
|
19
server/src/store/index.js
Normal file
19
server/src/store/index.js
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
import MemoryStore from './Memory';
|
||||||
|
import RedisStore from './Redis';
|
||||||
|
|
||||||
|
const storeBackend = process.env.STORE_BACKEND || 'redis';
|
||||||
|
|
||||||
|
let store;
|
||||||
|
switch (storeBackend) {
|
||||||
|
case 'memory':
|
||||||
|
store = new MemoryStore();
|
||||||
|
break;
|
||||||
|
case 'redis':
|
||||||
|
default:
|
||||||
|
store = new RedisStore(process.env.STORE_HOST);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
const getStore = () => store;
|
||||||
|
|
||||||
|
export default getStore;
|
Loading…
x
Reference in New Issue
Block a user