mirror of
https://github.com/darkwire/darkwire.io.git
synced 2025-07-18 02:44:01 +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
20
readme.md
20
readme.md
@ -16,6 +16,22 @@ The Darkwire.io [web client](/client) is written in JavaScript with React JS and
|
||||
|
||||
### 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
|
||||
|
||||
Install dependencies
|
||||
@ -58,7 +74,7 @@ Here's an overview of a chat between Alice and Bob (also applies to group chats)
|
||||
1. Bob creates a room and immediately creates a public/private key pair (RSA-OAEP).
|
||||
2. Alice joins the room and also creates a public/private key pair. She is sent Bob's public key and she sends Bob her public key.
|
||||
3. When Bob goes to send a message, three things are created: a session key (AES-CBC), a signing key (HMAC SHA-256) and an initialization vector (used in the encryption process).
|
||||
4. Bob's message is encrypted with the session key and initialization vector, and a signature is created using the signing key.
|
||||
4. Bob's message is encrypted with the session key and initialization vector, and a signature is created using the signing key.
|
||||
5. The session key and signing key are encrypted with each recipient's public key (in this case only Alice, but in a group chat multiple).
|
||||
6. The encrypted message, initialization vector, signature, encrypted session key and encrypted signing key are sent to all recipients (in this case just Alice) as a package.
|
||||
7. Alice receives the package and decrypts the session key and signing key using her private key. She decrypts the message with the decrypted session key and vector, and verifies the signature with the decrypted signing key.
|
||||
@ -71,7 +87,7 @@ Darkwire does not provide any guarantee that the person you're communicating wit
|
||||
|
||||
## File Transfer
|
||||
|
||||
Darkwire encodes documents (up to 1MB) into base64 using [btoa](https://developer.mozilla.org/en-US/docs/Web/API/WindowBase64/btoa) and is encrypted the same way chat messages are.
|
||||
Darkwire encodes documents (up to 1MB) into base64 using [btoa](https://developer.mozilla.org/en-US/docs/Web/API/WindowBase64/btoa) and is encrypted the same way chat messages are.
|
||||
|
||||
1. When a file is "uploaded", the document is encoded on the client and the server recieves the encrypted base64 string.
|
||||
2. The server sends the encrypted base64 string to clients in the same chat room.
|
||||
|
@ -1,8 +1,15 @@
|
||||
# Abuse mail configuration
|
||||
MAILGUN_API_KEY=api-key
|
||||
MAILGUN_DOMAIN=darkwire.io
|
||||
ABUSE_TO_EMAIL_ADDRESS=abuse@darkwire.io
|
||||
ABUSE_FROM_EMAIL_ADDRESS=Darkwire <no-reply@darkwire.io>
|
||||
REDIS_URL=redis://localhost:6379
|
||||
|
||||
CLIENT_DIST_DIRECTORY='client/dist/path'
|
||||
|
||||
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() {
|
||||
const redis = getRedis();
|
||||
const store = getStore();
|
||||
|
||||
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`);
|
||||
|
||||
Object.keys(rooms).forEach(async roomId => {
|
||||
Object.keys(rooms).forEach(async (roomId) => {
|
||||
const room = JSON.parse(rooms[roomId]);
|
||||
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) {
|
||||
console.log(`Deleting roomId ${roomId} which hasn't been used in ${timeSinceUpdatedInDays} days`);
|
||||
await redis.hdelAsync('rooms', roomId);
|
||||
console.log(
|
||||
`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 https from 'https';
|
||||
import Koa from 'koa';
|
||||
@ -6,22 +6,13 @@ import Io from 'socket.io';
|
||||
import KoaBody from 'koa-body';
|
||||
import cors from 'kcors';
|
||||
import Router from 'koa-router';
|
||||
import bluebird from 'bluebird';
|
||||
import Redis from 'redis';
|
||||
import socketRedis from 'socket.io-redis';
|
||||
import Socket from './socket';
|
||||
import crypto from 'crypto'
|
||||
import crypto from 'crypto';
|
||||
import mailer from './utils/mailer';
|
||||
import koaStatic from 'koa-static';
|
||||
import koaSend from 'koa-send';
|
||||
import {pollForInactiveRooms} from './inactive_rooms';
|
||||
|
||||
bluebird.promisifyAll(Redis.RedisClient.prototype);
|
||||
bluebird.promisifyAll(Redis.Multi.prototype);
|
||||
|
||||
const redis = Redis.createClient(process.env.REDIS_URL)
|
||||
|
||||
export const getRedis = () => redis
|
||||
import { pollForInactiveRooms } from './inactive_rooms';
|
||||
import getStore from './store';
|
||||
|
||||
const env = process.env.NODE_ENV || 'development';
|
||||
|
||||
@ -35,12 +26,16 @@ const appName = process.env.HEROKU_APP_NAME;
|
||||
const isReviewApp = /-pr-/.test(appName);
|
||||
const siteURL = process.env.SITE_URL;
|
||||
|
||||
const store = getStore();
|
||||
|
||||
if ((siteURL || env === 'development') && !isReviewApp) {
|
||||
app.use(cors({
|
||||
origin: env === 'development' ? '*' : siteURL,
|
||||
allowMethods: ['GET','HEAD','POST'],
|
||||
credentials: true,
|
||||
}));
|
||||
app.use(
|
||||
cors({
|
||||
origin: env === 'development' ? '*' : siteURL,
|
||||
allowMethods: ['GET', 'HEAD', 'POST'],
|
||||
credentials: true,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
router.post('/abuse/:roomId', koaBody, async (ctx) => {
|
||||
@ -48,19 +43,22 @@ router.post('/abuse/:roomId', koaBody, async (ctx) => {
|
||||
|
||||
roomId = roomId.trim();
|
||||
|
||||
if (process.env.ABUSE_FROM_EMAIL_ADDRESS && process.env.ABUSE_TO_EMAIL_ADDRESS) {
|
||||
const abuseForRoomExists = await redis.hgetAsync('abuse', roomId);
|
||||
if (
|
||||
process.env.ABUSE_FROM_EMAIL_ADDRESS &&
|
||||
process.env.ABUSE_TO_EMAIL_ADDRESS
|
||||
) {
|
||||
const abuseForRoomExists = await store.get('abuse', roomId);
|
||||
if (!abuseForRoomExists) {
|
||||
mailer.send({
|
||||
from: process.env.ABUSE_FROM_EMAIL_ADDRESS,
|
||||
to: process.env.ABUSE_TO_EMAIL_ADDRESS,
|
||||
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;
|
||||
});
|
||||
@ -68,7 +66,9 @@ router.post('/abuse/:roomId', koaBody, async (ctx) => {
|
||||
app.use(router.routes());
|
||||
|
||||
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) {
|
||||
ctx.set({
|
||||
@ -78,7 +78,8 @@ function setStaticFileHeaders(ctx) {
|
||||
'X-XSS-Protection': '1; mode=block',
|
||||
'X-Content-Type-Options': 'nosniff',
|
||||
'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) => {
|
||||
setStaticFileHeaders(ctx);
|
||||
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);
|
||||
});
|
||||
|
||||
app.use(async (ctx) => {
|
||||
setStaticFileHeaders(ctx);
|
||||
await koaSend(ctx, 'index.html', { root: clientDistDirectory });
|
||||
})
|
||||
});
|
||||
} else {
|
||||
app.use(async ctx => {
|
||||
app.use(async (ctx) => {
|
||||
ctx.body = { ready: true };
|
||||
});
|
||||
}
|
||||
@ -106,52 +107,52 @@ const protocol = (process.env.PROTOCOL || 'http') === 'http' ? http : https;
|
||||
const server = protocol.createServer(app.callback());
|
||||
const io = Io(server, {
|
||||
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 getRoomIdHash = (id) => {
|
||||
if (env === 'development') {
|
||||
return id
|
||||
return id;
|
||||
}
|
||||
|
||||
if (roomHashSecret) {
|
||||
return crypto
|
||||
.createHmac('sha256', roomHashSecret)
|
||||
.update(id)
|
||||
.digest('hex')
|
||||
return crypto.createHmac('sha256', roomHashSecret).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) => {
|
||||
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)
|
||||
room = JSON.parse(room || '{}')
|
||||
let room = await store.get('rooms', roomIdHash);
|
||||
room = JSON.parse(room || '{}');
|
||||
|
||||
new Socket({
|
||||
roomIdOriginal: roomId,
|
||||
roomId: roomIdHash,
|
||||
socket,
|
||||
room,
|
||||
})
|
||||
})
|
||||
});
|
||||
});
|
||||
|
||||
const init = async () => {
|
||||
server.listen(PORT, () => {
|
||||
console.log(`Darkwire is online at port ${PORT}`);
|
||||
})
|
||||
});
|
||||
|
||||
pollForInactiveRooms();
|
||||
}
|
||||
|
||||
init()
|
||||
};
|
||||
|
||||
init();
|
||||
|
@ -1,27 +1,27 @@
|
||||
import _ from 'lodash';
|
||||
import uuid from 'uuid/v4';
|
||||
import { getIO, getRedis } from './index'
|
||||
import { getIO } from './index';
|
||||
import getStore from './store';
|
||||
|
||||
export default class Socket {
|
||||
constructor(opts) {
|
||||
const { roomId, socket, room, roomIdOriginal } = opts
|
||||
const { roomId, socket, room, roomIdOriginal } = opts;
|
||||
|
||||
this._roomId = roomId
|
||||
this._roomId = roomId;
|
||||
this.socket = socket;
|
||||
this.roomIdOriginal = roomIdOriginal;
|
||||
this.room = room;
|
||||
if (room.isLocked) {
|
||||
this.sendRoomLocked();
|
||||
return
|
||||
return;
|
||||
}
|
||||
|
||||
this.init(opts)
|
||||
}
|
||||
this.init(opts);
|
||||
}
|
||||
|
||||
async init(opts) {
|
||||
const { roomId, socket, room } = opts
|
||||
await this.joinRoom(roomId, socket.id)
|
||||
this.handleSocket(socket)
|
||||
const { roomId, socket, room } = opts;
|
||||
await this.joinRoom(roomId, socket);
|
||||
this.handleSocket(socket);
|
||||
}
|
||||
|
||||
sendRoomLocked() {
|
||||
@ -32,30 +32,41 @@ export default class Socket {
|
||||
const json = {
|
||||
...room,
|
||||
updatedAt: Date.now(),
|
||||
}
|
||||
};
|
||||
|
||||
return getRedis().hsetAsync('rooms', this._roomId, JSON.stringify(json))
|
||||
return getStore().set('rooms', this._roomId, JSON.stringify(json));
|
||||
}
|
||||
|
||||
async destroyRoom() {
|
||||
return getRedis().hdel('rooms', this._roomId)
|
||||
return getStore().del('rooms', this._roomId);
|
||||
}
|
||||
|
||||
fetchRoom() {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
const res = await getRedis().hgetAsync('rooms', this._roomId)
|
||||
resolve(JSON.parse(res || '{}'))
|
||||
})
|
||||
const res = await getStore().get('rooms', this._roomId);
|
||||
resolve(JSON.parse(res || '{}'));
|
||||
});
|
||||
}
|
||||
|
||||
joinRoom(roomId, socketId) {
|
||||
joinRoom(roomId, socket) {
|
||||
return new Promise((resolve, reject) => {
|
||||
getIO().of('/').adapter.remoteJoin(socketId, roomId, (err) => {
|
||||
if (err) {
|
||||
reject()
|
||||
}
|
||||
resolve()
|
||||
});
|
||||
if (getStore().hasSocketAdapter) {
|
||||
getIO()
|
||||
.of('/')
|
||||
.adapter.remoteJoin(socket.id, roomId, (err) => {
|
||||
if (err) {
|
||||
reject();
|
||||
}
|
||||
resolve();
|
||||
});
|
||||
} else {
|
||||
socket.join(roomId, (err) => {
|
||||
if (err) {
|
||||
reject();
|
||||
}
|
||||
resolve();
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@ -65,56 +76,63 @@ export default class Socket {
|
||||
});
|
||||
|
||||
socket.on('USER_ENTER', async (payload) => {
|
||||
let room = await this.fetchRoom()
|
||||
let room = await this.fetchRoom();
|
||||
if (_.isEmpty(room)) {
|
||||
room = {
|
||||
id: this._roomId,
|
||||
users: [],
|
||||
isLocked: false,
|
||||
createdAt: Date.now(),
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
const newRoom = {
|
||||
...room,
|
||||
users: [...(room.users || []), {
|
||||
socketId: socket.id,
|
||||
publicKey: payload.publicKey,
|
||||
isOwner: (room.users || []).length === 0,
|
||||
}]
|
||||
}
|
||||
await this.saveRoom(newRoom)
|
||||
users: [
|
||||
...(room.users || []),
|
||||
{
|
||||
socketId: socket.id,
|
||||
publicKey: payload.publicKey,
|
||||
isOwner: (room.users || []).length === 0,
|
||||
},
|
||||
],
|
||||
};
|
||||
await this.saveRoom(newRoom);
|
||||
|
||||
getIO().to(this._roomId).emit('USER_ENTER', {
|
||||
...newRoom,
|
||||
id: this.roomIdOriginal
|
||||
});
|
||||
})
|
||||
getIO()
|
||||
.to(this._roomId)
|
||||
.emit('USER_ENTER', {
|
||||
...newRoom,
|
||||
id: this.roomIdOriginal,
|
||||
});
|
||||
});
|
||||
|
||||
socket.on('TOGGLE_LOCK_ROOM', async (data, callback) => {
|
||||
const room = await this.fetchRoom()
|
||||
const user = (room.users || []).find(u => u.socketId === socket.id && u.isOwner)
|
||||
const room = await this.fetchRoom();
|
||||
const user = (room.users || []).find(
|
||||
(u) => u.socketId === socket.id && u.isOwner
|
||||
);
|
||||
|
||||
if (!user) {
|
||||
callback({
|
||||
isLocked: room.isLocked,
|
||||
})
|
||||
return
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
await this.saveRoom({
|
||||
...room,
|
||||
isLocked: !room.isLocked,
|
||||
})
|
||||
});
|
||||
|
||||
socket.to(this._roomId).emit('TOGGLE_LOCK_ROOM', {
|
||||
locked: !room.isLocked,
|
||||
publicKey: user && user.publicKey
|
||||
publicKey: user && user.publicKey,
|
||||
});
|
||||
|
||||
callback({
|
||||
isLocked: !room.isLocked,
|
||||
})
|
||||
});
|
||||
});
|
||||
|
||||
socket.on('disconnect', () => this.handleDisconnect(socket));
|
||||
@ -123,25 +141,26 @@ export default class Socket {
|
||||
}
|
||||
|
||||
async handleDisconnect(socket) {
|
||||
let room = await this.fetchRoom()
|
||||
let room = await this.fetchRoom();
|
||||
|
||||
const newRoom = {
|
||||
...room,
|
||||
users: (room.users || []).filter(u => u.socketId !== socket.id).map((u, index) => ({
|
||||
...u,
|
||||
isOwner: index === 0,
|
||||
}))
|
||||
}
|
||||
users: (room.users || [])
|
||||
.filter((u) => u.socketId !== socket.id)
|
||||
.map((u, index) => ({
|
||||
...u,
|
||||
isOwner: index === 0,
|
||||
})),
|
||||
};
|
||||
|
||||
await this.saveRoom(newRoom)
|
||||
await this.saveRoom(newRoom);
|
||||
|
||||
getIO().to(this._roomId).emit('USER_EXIT', newRoom.users);
|
||||
|
||||
if (newRoom.users && newRoom.users.length === 0) {
|
||||
await this.destroyRoom()
|
||||
await this.destroyRoom();
|
||||
}
|
||||
|
||||
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