mirror of
https://github.com/Discreetly/server.git
synced 2026-01-07 19:53:55 -05:00
works with npm run start/dev with a local redis, but not a docker redis
This commit is contained in:
21
docker-compose.yml
Normal file
21
docker-compose.yml
Normal file
@@ -0,0 +1,21 @@
|
||||
version: '3'
|
||||
services:
|
||||
node:
|
||||
build: .
|
||||
ports:
|
||||
- '3001:3001'
|
||||
- '3002:3002'
|
||||
volumes:
|
||||
- .:/app
|
||||
depends_on:
|
||||
- redis
|
||||
environment:
|
||||
- REDIS_HOST=redis
|
||||
- REDIS_PORT=6379
|
||||
- NODE_ENV=production
|
||||
|
||||
redis:
|
||||
image: "redis:alpine"
|
||||
hostname: redis
|
||||
ports:
|
||||
- "6379:6379"
|
||||
14
dockerfile
Normal file
14
dockerfile
Normal file
@@ -0,0 +1,14 @@
|
||||
FROM node:18-alpine
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY package*.json ./
|
||||
|
||||
RUN npm install
|
||||
|
||||
COPY . .
|
||||
|
||||
EXPOSE 3001
|
||||
EXPOSE 3002
|
||||
|
||||
CMD [ "npm", "start" ]
|
||||
105
package-lock.json
generated
105
package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "discreetly-server",
|
||||
"version": "0.1.0",
|
||||
"version": "0.1.1",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "discreetly-server",
|
||||
"version": "0.1.0",
|
||||
"version": "0.1.1",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@faker-js/faker": "^8.0.2",
|
||||
@@ -14,7 +14,7 @@
|
||||
"body-parser": "^1.20.2",
|
||||
"cors": "^2.8.5",
|
||||
"discreetly-claimcodes": "^1.0.7",
|
||||
"discreetly-interfaces": "^0.1.5",
|
||||
"discreetly-interfaces": "^0.1.11",
|
||||
"dotenv": "^16.3.1",
|
||||
"express": "^4.18.2",
|
||||
"poseidon-lite": "^0.2.0",
|
||||
@@ -32,6 +32,7 @@
|
||||
"nodemon": "^2.0.22",
|
||||
"rollup": "^3.26.2",
|
||||
"rollup-plugin-cleaner": "^1.0.0",
|
||||
"rollup-plugin-include-sourcemaps": "^0.7.0",
|
||||
"rollup-plugin-typescript2": "^0.35.0",
|
||||
"typescript": "^5.1.6"
|
||||
},
|
||||
@@ -1108,6 +1109,15 @@
|
||||
"ms": "2.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/decode-uri-component": {
|
||||
"version": "0.2.2",
|
||||
"resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.2.tgz",
|
||||
"integrity": "sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=0.10"
|
||||
}
|
||||
},
|
||||
"node_modules/deepmerge": {
|
||||
"version": "4.3.1",
|
||||
"resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz",
|
||||
@@ -1140,10 +1150,11 @@
|
||||
"integrity": "sha512-on8ZS7W1WlsY+J9hQmTU2IO18HQ2/pXbztaSCkJaqlpgMDjb2l2Q0MqGQtwshTtv9dfcoIuzeRzuXSk4T69NJg=="
|
||||
},
|
||||
"node_modules/discreetly-interfaces": {
|
||||
"version": "0.1.5",
|
||||
"resolved": "https://registry.npmjs.org/discreetly-interfaces/-/discreetly-interfaces-0.1.5.tgz",
|
||||
"integrity": "sha512-RcdBvop3n8K4xlvD6/P9APD7nKhONNon++fh5UdDqa3bjWxLtZsaHna7pTfIuymjUmimDrqiMVB6JCS3ys4MFw==",
|
||||
"version": "0.1.11",
|
||||
"resolved": "https://registry.npmjs.org/discreetly-interfaces/-/discreetly-interfaces-0.1.11.tgz",
|
||||
"integrity": "sha512-lHBIc6BVx2UbVXFqsXEs1aC0UTs9+FLE28v4K+dVVhN78sXY+aAyTbOELqv2+L1Fae5FMBVkexOqxJ6nDJAvPQ==",
|
||||
"dependencies": {
|
||||
"atob": "^2.1.2",
|
||||
"poseidon-lite": "^0.2.0",
|
||||
"rlnjs": "^3.1.4"
|
||||
}
|
||||
@@ -2515,6 +2526,51 @@
|
||||
"rollup": "> 1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/rollup-plugin-include-sourcemaps": {
|
||||
"version": "0.7.0",
|
||||
"resolved": "https://registry.npmjs.org/rollup-plugin-include-sourcemaps/-/rollup-plugin-include-sourcemaps-0.7.0.tgz",
|
||||
"integrity": "sha512-zAlN2IkFSaptlHhuWVROZ5xrviEULRSInN9AzETsBD++Ab5aMKAtXhDH2aRHbE2cRW6cVT9FFrAQHwZXxqDCIQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@rollup/pluginutils": "^5.0.2",
|
||||
"atob": "^2.1.2",
|
||||
"decode-uri-component": "^0.2.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/node": ">=10.0.0",
|
||||
"rollup": ">=0.31.2"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/node": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/rollup-plugin-include-sourcemaps/node_modules/@rollup/pluginutils": {
|
||||
"version": "5.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.0.2.tgz",
|
||||
"integrity": "sha512-pTd9rIsP92h+B6wWwFbW8RkZv4hiR/xKsqre4SIuAOaOEQRxi0lqLke9k2/7WegC85GgUs9pjmOjCUi3In4vwA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@types/estree": "^1.0.0",
|
||||
"estree-walker": "^2.0.2",
|
||||
"picomatch": "^2.3.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"rollup": "^1.20.0||^2.0.0||^3.0.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"rollup": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/rollup-plugin-typescript2": {
|
||||
"version": "0.35.0",
|
||||
"resolved": "https://registry.npmjs.org/rollup-plugin-typescript2/-/rollup-plugin-typescript2-0.35.0.tgz",
|
||||
@@ -3863,6 +3919,12 @@
|
||||
"ms": "2.0.0"
|
||||
}
|
||||
},
|
||||
"decode-uri-component": {
|
||||
"version": "0.2.2",
|
||||
"resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.2.tgz",
|
||||
"integrity": "sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ==",
|
||||
"dev": true
|
||||
},
|
||||
"deepmerge": {
|
||||
"version": "4.3.1",
|
||||
"resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz",
|
||||
@@ -3885,10 +3947,11 @@
|
||||
"integrity": "sha512-on8ZS7W1WlsY+J9hQmTU2IO18HQ2/pXbztaSCkJaqlpgMDjb2l2Q0MqGQtwshTtv9dfcoIuzeRzuXSk4T69NJg=="
|
||||
},
|
||||
"discreetly-interfaces": {
|
||||
"version": "0.1.5",
|
||||
"resolved": "https://registry.npmjs.org/discreetly-interfaces/-/discreetly-interfaces-0.1.5.tgz",
|
||||
"integrity": "sha512-RcdBvop3n8K4xlvD6/P9APD7nKhONNon++fh5UdDqa3bjWxLtZsaHna7pTfIuymjUmimDrqiMVB6JCS3ys4MFw==",
|
||||
"version": "0.1.11",
|
||||
"resolved": "https://registry.npmjs.org/discreetly-interfaces/-/discreetly-interfaces-0.1.11.tgz",
|
||||
"integrity": "sha512-lHBIc6BVx2UbVXFqsXEs1aC0UTs9+FLE28v4K+dVVhN78sXY+aAyTbOELqv2+L1Fae5FMBVkexOqxJ6nDJAvPQ==",
|
||||
"requires": {
|
||||
"atob": "^2.1.2",
|
||||
"poseidon-lite": "^0.2.0",
|
||||
"rlnjs": "^3.1.4"
|
||||
}
|
||||
@@ -4933,6 +4996,30 @@
|
||||
"rimraf": "^2.6.3"
|
||||
}
|
||||
},
|
||||
"rollup-plugin-include-sourcemaps": {
|
||||
"version": "0.7.0",
|
||||
"resolved": "https://registry.npmjs.org/rollup-plugin-include-sourcemaps/-/rollup-plugin-include-sourcemaps-0.7.0.tgz",
|
||||
"integrity": "sha512-zAlN2IkFSaptlHhuWVROZ5xrviEULRSInN9AzETsBD++Ab5aMKAtXhDH2aRHbE2cRW6cVT9FFrAQHwZXxqDCIQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@rollup/pluginutils": "^5.0.2",
|
||||
"atob": "^2.1.2",
|
||||
"decode-uri-component": "^0.2.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@rollup/pluginutils": {
|
||||
"version": "5.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.0.2.tgz",
|
||||
"integrity": "sha512-pTd9rIsP92h+B6wWwFbW8RkZv4hiR/xKsqre4SIuAOaOEQRxi0lqLke9k2/7WegC85GgUs9pjmOjCUi3In4vwA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/estree": "^1.0.0",
|
||||
"estree-walker": "^2.0.2",
|
||||
"picomatch": "^2.3.1"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"rollup-plugin-typescript2": {
|
||||
"version": "0.35.0",
|
||||
"resolved": "https://registry.npmjs.org/rollup-plugin-typescript2/-/rollup-plugin-typescript2-0.35.0.tgz",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "discreetly-server",
|
||||
"version": "0.1.0",
|
||||
"version": "0.1.1",
|
||||
"description": "",
|
||||
"main": "dist/server.cjs",
|
||||
"scripts": {
|
||||
@@ -8,7 +8,8 @@
|
||||
"start": "node dist/server.cjs",
|
||||
"watch": "rollup --config rollup.config.mjs --watch",
|
||||
"serve": "nodemon -q dist/server.jcs",
|
||||
"dev": "concurrently \"npm run watch\" \"npm run serve\""
|
||||
"dev": "concurrently \"npm run watch\" \"npm run serve\"",
|
||||
"docker": "docker-compose up --build"
|
||||
},
|
||||
"engines": {
|
||||
"node": "18.x.x"
|
||||
@@ -23,7 +24,7 @@
|
||||
"body-parser": "^1.20.2",
|
||||
"cors": "^2.8.5",
|
||||
"discreetly-claimcodes": "^1.0.7",
|
||||
"discreetly-interfaces": "^0.1.5",
|
||||
"discreetly-interfaces": "^0.1.11",
|
||||
"dotenv": "^16.3.1",
|
||||
"express": "^4.18.2",
|
||||
"poseidon-lite": "^0.2.0",
|
||||
@@ -41,6 +42,7 @@
|
||||
"nodemon": "^2.0.22",
|
||||
"rollup": "^3.26.2",
|
||||
"rollup-plugin-cleaner": "^1.0.0",
|
||||
"rollup-plugin-include-sourcemaps": "^0.7.0",
|
||||
"rollup-plugin-typescript2": "^0.35.0",
|
||||
"typescript": "^5.1.6"
|
||||
}
|
||||
|
||||
47
src/mock.ts
Normal file
47
src/mock.ts
Normal file
@@ -0,0 +1,47 @@
|
||||
import { faker } from '@faker-js/faker';
|
||||
import { MessageI } from 'discreetly-interfaces';
|
||||
import { Server as SocketIOServer } from 'socket.io';
|
||||
|
||||
export default function Mock(io: SocketIOServer) {
|
||||
class randomMessagePicker {
|
||||
values: any;
|
||||
weightSums: any[];
|
||||
constructor(values, weights) {
|
||||
this.values = values;
|
||||
this.weightSums = [];
|
||||
let sum = 0;
|
||||
|
||||
for (let weight of weights) {
|
||||
sum += weight;
|
||||
this.weightSums.push(sum);
|
||||
}
|
||||
}
|
||||
|
||||
pick() {
|
||||
const rand = Math.random() * this.weightSums[this.weightSums.length - 1];
|
||||
let index = this.weightSums.findIndex((sum) => rand < sum);
|
||||
return this.values[index]();
|
||||
}
|
||||
}
|
||||
|
||||
const values = [
|
||||
faker.finance.ethereumAddress,
|
||||
faker.company.buzzPhrase,
|
||||
faker.lorem.sentence,
|
||||
faker.hacker.phrase
|
||||
];
|
||||
const weights = [1, 3, 2, 8];
|
||||
const picker = new randomMessagePicker(values, weights);
|
||||
|
||||
setInterval(() => {
|
||||
const message: MessageI = {
|
||||
id: faker.number.bigInt().toString(),
|
||||
room: BigInt('7458174823225695762087107782399226439860424529052640186229953289032606624581'),
|
||||
message: picker.pick(),
|
||||
timestamp: Date.now().toString(),
|
||||
epoch: Math.floor(Date.now() / 10000)
|
||||
};
|
||||
console.log('SENDING TEST MESSAGE');
|
||||
io.emit('messageBroadcast', message);
|
||||
}, 10000);
|
||||
}
|
||||
326
src/server.ts
326
src/server.ts
@@ -1,30 +1,27 @@
|
||||
import express from 'express';
|
||||
import { Server } from 'http';
|
||||
import { Server as SocketIOServer, Socket } from 'socket.io';
|
||||
import { Server as SocketIOServer } from 'socket.io';
|
||||
import cors from 'cors';
|
||||
import { createClient } from 'redis';
|
||||
import { serverConfig, rooms as defaultRooms, rooms } from './config/rooms.js';
|
||||
import type { MessageI, RoomI, RoomGroupI } from 'discreetly-interfaces';
|
||||
import verifyProof from './verifier.js';
|
||||
import { ClaimCodeManager } from 'discreetly-claimcodes';
|
||||
import type { ClaimCodeStatus } from 'discreetly-claimcodes';
|
||||
import { pp, addIdentityToRoom, createGroup, createRoom, findGroupById } from './utils.js';
|
||||
import { faker } from '@faker-js/faker';
|
||||
|
||||
import { pp, shim } from './utils.js';
|
||||
import { initRedisVariables, initSockets, initExpressEndpoints } from './startup.js';
|
||||
import mock from './mock.js';
|
||||
// HTTP is to get info from the server about configuration, rooms, etc
|
||||
const HTTP_PORT = 3001;
|
||||
// Socket is to communicate chat room messages back and forth
|
||||
const SOCKET_PORT = 3002;
|
||||
|
||||
// Deal with bigints in JSON
|
||||
(BigInt.prototype as any).toJSON = function () {
|
||||
return this.toString();
|
||||
};
|
||||
|
||||
const app = express();
|
||||
|
||||
const socket_server = new Server(app);
|
||||
|
||||
shim();
|
||||
|
||||
app.use(express.json());
|
||||
app.use(
|
||||
cors({
|
||||
origin: '*'
|
||||
})
|
||||
);
|
||||
|
||||
const io = new SocketIOServer(socket_server, {
|
||||
cors: {
|
||||
@@ -32,218 +29,17 @@ const io = new SocketIOServer(socket_server, {
|
||||
}
|
||||
});
|
||||
|
||||
interface userCountI {
|
||||
[key: string]: number;
|
||||
}
|
||||
|
||||
let userCount: userCountI = {};
|
||||
let loadedRooms: RoomGroupI[];
|
||||
let TESTGROUPID: BigInt;
|
||||
|
||||
let redisClient;
|
||||
let TESTING = false;
|
||||
|
||||
if (!process.env.REDIS_URL) {
|
||||
console.log('Connecting to redis at localhost');
|
||||
redisClient = createClient();
|
||||
TESTING = true;
|
||||
} else {
|
||||
console.log('Connecting to redis at: ' + process.env.REDIS_URL);
|
||||
console.log(process.env.REDIS_TLS_URL);
|
||||
redisClient = createClient({
|
||||
url: process.env.REDIS_URL,
|
||||
socket: {
|
||||
tls: true,
|
||||
rejectUnauthorized: false
|
||||
}
|
||||
function initAppListeners() {
|
||||
app.listen(HTTP_PORT, () => {
|
||||
pp(`Express Http Server is running at port ${HTTP_PORT}`);
|
||||
});
|
||||
|
||||
socket_server.listen(SOCKET_PORT, () => {
|
||||
pp(`SocketIO Server is running at port ${SOCKET_PORT}`);
|
||||
});
|
||||
}
|
||||
redisClient.connect().then(() => pp('Redis Connected'));
|
||||
|
||||
redisClient.get('rooms').then((rooms) => {
|
||||
rooms = JSON.parse(rooms);
|
||||
if (rooms) {
|
||||
loadedRooms = rooms as unknown as RoomGroupI[];
|
||||
} else {
|
||||
loadedRooms = defaultRooms;
|
||||
redisClient.set('rooms', JSON.stringify(loadedRooms));
|
||||
}
|
||||
});
|
||||
|
||||
let ccm: ClaimCodeManager;
|
||||
|
||||
redisClient.get('ccm').then((cc) => {
|
||||
TESTGROUPID = BigInt(loadedRooms[0].id);
|
||||
if (!cc) {
|
||||
ccm = new ClaimCodeManager();
|
||||
ccm.generateClaimCodeSet(10, TESTGROUPID, 'TEST');
|
||||
const ccs = ccm.getClaimCodeSets();
|
||||
redisClient.set('ccm', JSON.stringify(ccs));
|
||||
} else {
|
||||
ccm = new ClaimCodeManager(JSON.parse(cc));
|
||||
|
||||
if (ccm.getUsedCount(TESTGROUPID).unusedCount < 5) {
|
||||
ccm.generateClaimCodeSet(10, TESTGROUPID, 'TEST');
|
||||
const ccs = ccm.getClaimCodeSets();
|
||||
|
||||
redisClient.set('ccm', JSON.stringify(ccs));
|
||||
}
|
||||
}
|
||||
const ccs = ccm.getClaimCodeSets();
|
||||
});
|
||||
|
||||
redisClient.on('error', (err) => pp('Redis Client Error: ' + err, 'error'));
|
||||
|
||||
io.on('connection', (socket: Socket) => {
|
||||
pp('SocketIO: a user connected', 'debug');
|
||||
|
||||
socket.on('validateMessage', (msg: MessageI) => {
|
||||
pp({ 'VALIDATING MESSAGE ID': msg.id.slice(0, 11), 'MSG:': msg.message });
|
||||
const valid = verifyProof(msg, loadedRooms);
|
||||
if (!valid) {
|
||||
pp('INVALID MESSAGE', 'warn');
|
||||
return;
|
||||
}
|
||||
io.emit('messageBroadcast', msg);
|
||||
});
|
||||
|
||||
socket.on('disconnect', () => {
|
||||
pp('SocketIO: user disconnected');
|
||||
});
|
||||
|
||||
socket.on('joinRoom', (roomID: bigint) => {
|
||||
const id = roomID.toString();
|
||||
userCount[id] = userCount[id] ? userCount[id] + 1 : 1;
|
||||
});
|
||||
|
||||
socket.on('leaveRoom', (roomID: bigint) => {
|
||||
const id = roomID.toString();
|
||||
userCount[id] = userCount[id] ? userCount[id] - 1 : 0;
|
||||
});
|
||||
});
|
||||
|
||||
app.use(
|
||||
cors({
|
||||
origin: '*'
|
||||
})
|
||||
);
|
||||
|
||||
app.get(['/', '/api'], (req, res) => {
|
||||
pp('Express: fetching server info');
|
||||
res.json(serverConfig);
|
||||
});
|
||||
|
||||
app.get('/api/rooms', (req, res) => {
|
||||
pp('Express: fetching rooms');
|
||||
res.json(loadedRooms);
|
||||
});
|
||||
|
||||
app.get('/api/rooms/:id', (req, res) => {
|
||||
// TODO This should return the room info for the given room ID
|
||||
pp(String('Express: fetching room info for ' + req.params.id));
|
||||
const room = loadedRooms
|
||||
.flatMap((rooms) => rooms.rooms)
|
||||
.filter((room) => room.id === req.params.id);
|
||||
res.json(room);
|
||||
});
|
||||
|
||||
// TODO api endpoint that creates new rooms and generates invite codes for them
|
||||
app.post('/join', (req, res) => {
|
||||
const data = req.body;
|
||||
const { code, idc } = data;
|
||||
pp('Express[/join]: claiming code:' + code);
|
||||
const result: ClaimCodeStatus = ccm.claimCode(code);
|
||||
const groupID = result.groupID;
|
||||
if (result.status === 'CLAIMED') {
|
||||
let claimedRooms = [];
|
||||
let alreadyAddedRooms = [];
|
||||
loadedRooms.forEach((group) => {
|
||||
if (group.id == groupID) {
|
||||
group.rooms.forEach((room: RoomI) => {
|
||||
let { status, roomGroups } = addIdentityToRoom(BigInt(room.id), BigInt(idc), loadedRooms);
|
||||
loadedRooms = roomGroups;
|
||||
redisClient.set('rooms', JSON.stringify(loadedRooms));
|
||||
if (status) {
|
||||
claimedRooms.push(room);
|
||||
} else {
|
||||
alreadyAddedRooms.push(room);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
let r = [...claimedRooms, ...alreadyAddedRooms];
|
||||
|
||||
if (claimedRooms.length > 0) {
|
||||
res.status(200).json({ status: 'valid', rooms: r });
|
||||
} else if (alreadyAddedRooms.length > 0) {
|
||||
res.status(200).json({ status: 'already-added', rooms: r });
|
||||
} else {
|
||||
res.status(451).json({ status: 'invalid' });
|
||||
}
|
||||
|
||||
// the DB should be updated after we successfully send a response
|
||||
redisClient.set('ccm', JSON.stringify(ccm.getClaimCodeSets()));
|
||||
} else {
|
||||
res.status(451).json({ status: 'invalid' });
|
||||
}
|
||||
});
|
||||
|
||||
// TODO we are going to need endpoints that take a password that will be in a .env file to generate new roomGroups, rooms, and claim codes
|
||||
app.post('/group/add', (req, res) => {
|
||||
const data = req.body;
|
||||
const { password, groupName, roomNames, codes } = data;
|
||||
if (password === process.env.PASSWORD) {
|
||||
const result = createGroup(groupName, roomNames, loadedRooms);
|
||||
loadedRooms = result.roomGroup;
|
||||
redisClient.set('rooms', JSON.stringify(loadedRooms));
|
||||
if (codes.generate) {
|
||||
codes.amount = codes.amount || 10;
|
||||
ccm.generateClaimCodeSet(codes.amount, result.groupId, groupName);
|
||||
const ccs = ccm.getClaimCodeSets();
|
||||
redisClient.set('ccm', JSON.stringify(ccs));
|
||||
}
|
||||
res.status(201).json({ status: `Created group ${groupName}`, loadedRooms });
|
||||
}
|
||||
});
|
||||
|
||||
app.post('/room/add', (req, res) => {
|
||||
const data = req.body;
|
||||
const { password, groupId, roomName } = data;
|
||||
if (password === process.env.PASSWORD) {
|
||||
const roomGroups = createRoom(groupId, roomName, loadedRooms);
|
||||
loadedRooms = roomGroups;
|
||||
redisClient.set('rooms', JSON.stringify(loadedRooms));
|
||||
res.status(201).json({ status: `Created room ${roomName}`, loadedRooms });
|
||||
}
|
||||
});
|
||||
|
||||
app.post('/group/createcode', (req, res) => {
|
||||
const data = req.body;
|
||||
let { password, groupId, amount } = data;
|
||||
if (password === process.env.PASSWORD) {
|
||||
amount = amount || 10;
|
||||
console.log(loadedRooms, groupId);
|
||||
const group = findGroupById(loadedRooms, groupId);
|
||||
const ccs = ccm.generateClaimCodeSet(amount, groupId, group.name);
|
||||
redisClient.set('ccm', JSON.stringify(ccs));
|
||||
res.status(201).json({ stats: `Created ${amount} codes for ${group.name}`, ccm });
|
||||
}
|
||||
});
|
||||
|
||||
app.get('/logclaimcodes', (req, res) => {
|
||||
pp('-----CLAIMCODES-----', 'debug');
|
||||
pp(ccm.getClaimCodeSet(TESTGROUPID));
|
||||
pp('-----ENDOFCODES-----', 'debug');
|
||||
res.status(200).json({ status: 'ok' });
|
||||
});
|
||||
|
||||
app.listen(HTTP_PORT, () => {
|
||||
pp(`Express Http Server is running at port ${HTTP_PORT}`);
|
||||
});
|
||||
|
||||
socket_server.listen(SOCKET_PORT, () => {
|
||||
pp(`SocketIO Server is running at port ${SOCKET_PORT}`);
|
||||
});
|
||||
|
||||
// Disconnect from redis on exit
|
||||
process.on('SIGINT', () => {
|
||||
@@ -251,46 +47,46 @@ process.on('SIGINT', () => {
|
||||
redisClient.disconnect().then(process.exit());
|
||||
});
|
||||
|
||||
if (TESTING) {
|
||||
class randomMessagePicker {
|
||||
values: any;
|
||||
weightSums: any[];
|
||||
constructor(values, weights) {
|
||||
this.values = values;
|
||||
this.weightSums = [];
|
||||
let sum = 0;
|
||||
|
||||
for (let weight of weights) {
|
||||
sum += weight;
|
||||
this.weightSums.push(sum);
|
||||
}
|
||||
}
|
||||
|
||||
pick() {
|
||||
const rand = Math.random() * this.weightSums[this.weightSums.length - 1];
|
||||
let index = this.weightSums.findIndex((sum) => rand < sum);
|
||||
return this.values[index]();
|
||||
}
|
||||
}
|
||||
|
||||
const values = [
|
||||
faker.finance.ethereumAddress,
|
||||
faker.company.buzzPhrase,
|
||||
faker.lorem.sentence,
|
||||
faker.hacker.phrase
|
||||
];
|
||||
const weights = [1, 3, 2, 8];
|
||||
const picker = new randomMessagePicker(values, weights);
|
||||
|
||||
setInterval(() => {
|
||||
const message: MessageI = {
|
||||
id: faker.number.bigInt().toString(),
|
||||
room: BigInt('7458174823225695762087107782399226439860424529052640186229953289032606624581'),
|
||||
message: picker.pick(),
|
||||
timestamp: Date.now().toString(),
|
||||
epoch: Math.floor(Date.now() / 10000)
|
||||
};
|
||||
console.log('SENDING TEST MESSAGE');
|
||||
io.emit('messageBroadcast', message);
|
||||
}, 10000);
|
||||
/**
|
||||
* This is the main entry point for the server
|
||||
*/
|
||||
if (!process.env.NODE_ENV || process.env.NODE_ENV === 'development') {
|
||||
console.log('Creating Redis client on localhost');
|
||||
redisClient = createClient();
|
||||
redisClient.connect().then(() => {
|
||||
pp('Redis Connected to localhost');
|
||||
});
|
||||
initRedisVariables(redisClient).then(({ loadedRooms, ccm, TESTGROUPID }) => {
|
||||
initExpressEndpoints(app, redisClient, ccm, TESTGROUPID);
|
||||
initSockets(io, loadedRooms);
|
||||
initAppListeners();
|
||||
mock(io);
|
||||
});
|
||||
} else {
|
||||
console.log("Creating Redis client with socket: { host: 'redis', port: 6379 }");
|
||||
redisClient = createClient({
|
||||
socket: {
|
||||
host: process.env.REDIS_HOST,
|
||||
port: parseInt(process.env.REDIS_PORT)
|
||||
},
|
||||
legacyMode: true
|
||||
});
|
||||
console.log('Connecting to redis docker container');
|
||||
redisClient
|
||||
.connect()
|
||||
.then(() => {
|
||||
pp('Redis Connected to redis docker container');
|
||||
})
|
||||
.catch((err) => {
|
||||
pp('Redis Connection Error: ' + err, 'error');
|
||||
});
|
||||
console.log('Initializing Redis Variables');
|
||||
initRedisVariables(redisClient).then(({ loadedRooms, ccm, TESTGROUPID }) => {
|
||||
console.log('Initializing Express Endpoints');
|
||||
initExpressEndpoints(app, redisClient, ccm, TESTGROUPID);
|
||||
console.log('Initializing Sockets');
|
||||
initSockets(io, loadedRooms);
|
||||
console.log('Initializing App Listeners');
|
||||
initAppListeners();
|
||||
});
|
||||
}
|
||||
|
||||
204
src/startup.ts
Normal file
204
src/startup.ts
Normal file
@@ -0,0 +1,204 @@
|
||||
import { serverConfig, rooms as defaultRooms, rooms } from './config/rooms.js';
|
||||
import type { MessageI, RoomI, RoomGroupI } from 'discreetly-interfaces';
|
||||
import { ClaimCodeManager } from 'discreetly-claimcodes';
|
||||
import type { ClaimCodeStatus } from 'discreetly-claimcodes';
|
||||
import { pp, addIdentityToRoom, createGroup, createRoom, findGroupById } from './utils.js';
|
||||
import { Socket, Server as SocketIOServer } from 'socket.io';
|
||||
import { RedisClientType } from 'redis';
|
||||
import verifyProof from './verifier.js';
|
||||
import { userCountI } from './types.js';
|
||||
|
||||
export async function initRedisVariables(redisClient: RedisClientType): Promise<{
|
||||
loadedRooms: RoomGroupI[];
|
||||
ccm: ClaimCodeManager;
|
||||
TESTGROUPID: bigint;
|
||||
}> {
|
||||
let loadedRooms: RoomGroupI[];
|
||||
|
||||
const _CachedRooms = await redisClient.get('rooms');
|
||||
if (rooms) {
|
||||
console.log('Loading cached rooms');
|
||||
loadedRooms = JSON.parse(_CachedRooms) as unknown as RoomGroupI[];
|
||||
} else {
|
||||
console.log('Using default rooms');
|
||||
loadedRooms = defaultRooms as RoomGroupI[];
|
||||
redisClient.set('rooms', JSON.stringify(loadedRooms));
|
||||
}
|
||||
|
||||
let claimCodeManager: ClaimCodeManager;
|
||||
let TESTGROUPID: bigint;
|
||||
const _CachedClaimCodeManager = await redisClient.get('ccm');
|
||||
TESTGROUPID = BigInt(loadedRooms[0].id);
|
||||
if (!_CachedClaimCodeManager) {
|
||||
claimCodeManager = new ClaimCodeManager();
|
||||
claimCodeManager.generateClaimCodeSet(10, TESTGROUPID, 'TEST');
|
||||
const ccs = claimCodeManager.getClaimCodeSets();
|
||||
redisClient.set('ccm', JSON.stringify(ccs));
|
||||
} else {
|
||||
claimCodeManager = new ClaimCodeManager(JSON.parse(_CachedClaimCodeManager));
|
||||
|
||||
if (claimCodeManager.getUsedCount(TESTGROUPID).unusedCount < 5) {
|
||||
claimCodeManager.generateClaimCodeSet(10, TESTGROUPID, 'TEST');
|
||||
const ccs = claimCodeManager.getClaimCodeSets();
|
||||
|
||||
redisClient.set('ccm', JSON.stringify(ccs));
|
||||
}
|
||||
}
|
||||
|
||||
return { loadedRooms, ccm: claimCodeManager, TESTGROUPID };
|
||||
}
|
||||
|
||||
export function initSockets(io: SocketIOServer, loadedRooms: RoomGroupI[]) {
|
||||
let userCount: userCountI = {};
|
||||
io.on('connection', (socket: Socket) => {
|
||||
pp('SocketIO: a user connected', 'debug');
|
||||
|
||||
socket.on('validateMessage', (msg: MessageI) => {
|
||||
pp({ 'VALIDATING MESSAGE ID': msg.id?.slice(0, 11), 'MSG:': msg.message });
|
||||
const valid = verifyProof(msg, loadedRooms);
|
||||
if (!valid) {
|
||||
pp('INVALID MESSAGE', 'warn');
|
||||
return;
|
||||
}
|
||||
io.emit('messageBroadcast', msg);
|
||||
});
|
||||
|
||||
socket.on('disconnect', () => {
|
||||
pp('SocketIO: user disconnected');
|
||||
});
|
||||
|
||||
socket.on('joinRoom', (roomID: bigint) => {
|
||||
const id = roomID.toString();
|
||||
userCount[id] = userCount[id] ? userCount[id] + 1 : 1;
|
||||
});
|
||||
|
||||
socket.on('leaveRoom', (roomID: bigint) => {
|
||||
const id = roomID.toString();
|
||||
userCount[id] = userCount[id] ? userCount[id] - 1 : 0;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export function initExpressEndpoints(
|
||||
app,
|
||||
redisClient: RedisClientType,
|
||||
ccm: ClaimCodeManager,
|
||||
TESTGROUPID: bigint
|
||||
) {
|
||||
let loadedRooms: RoomGroupI[] = rooms;
|
||||
app.get(['/', '/api'], (req, res) => {
|
||||
pp('Express: fetching server info');
|
||||
res.json(serverConfig);
|
||||
});
|
||||
|
||||
app.get('/api/rooms', (req, res) => {
|
||||
pp('Express: fetching rooms');
|
||||
redisClient.get('rooms').then((rooms) => {
|
||||
return res.json(rooms);
|
||||
});
|
||||
});
|
||||
|
||||
app.get('/api/rooms/:id', (req, res) => {
|
||||
// TODO This should return the room info for the given room ID
|
||||
pp(String('Express: fetching room info for ' + req.params.id));
|
||||
const room = loadedRooms
|
||||
.flatMap((rooms) => rooms.rooms)
|
||||
.filter((room) => room.id === req.params.id);
|
||||
res.json(room);
|
||||
});
|
||||
|
||||
// TODO api endpoint that creates new rooms and generates invite codes for them
|
||||
app.post('/join', (req, res) => {
|
||||
const data = req.body;
|
||||
const { code, idc } = data;
|
||||
pp('Express[/join]: claiming code:' + code);
|
||||
const result: ClaimCodeStatus = ccm.claimCode(code);
|
||||
const groupID = result.groupID;
|
||||
if (result.status === 'CLAIMED') {
|
||||
let claimedRooms: any[] = [];
|
||||
let alreadyAddedRooms: any[] = [];
|
||||
loadedRooms.forEach((group) => {
|
||||
if (group.id == groupID) {
|
||||
group.rooms.forEach((room: RoomI) => {
|
||||
let { status, roomGroups } = addIdentityToRoom(
|
||||
BigInt(room.id),
|
||||
BigInt(idc),
|
||||
loadedRooms
|
||||
);
|
||||
loadedRooms = roomGroups;
|
||||
redisClient.set('rooms', JSON.stringify(loadedRooms));
|
||||
if (status) {
|
||||
claimedRooms.push(room);
|
||||
} else {
|
||||
alreadyAddedRooms.push(room);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
let r = [...claimedRooms, ...alreadyAddedRooms];
|
||||
|
||||
if (claimedRooms.length > 0) {
|
||||
res.status(200).json({ status: 'valid', rooms: r });
|
||||
} else if (alreadyAddedRooms.length > 0) {
|
||||
res.status(200).json({ status: 'already-added', rooms: r });
|
||||
} else {
|
||||
res.status(451).json({ status: 'invalid' });
|
||||
}
|
||||
|
||||
// the DB should be updated after we successfully send a response
|
||||
redisClient.set('ccm', JSON.stringify(ccm.getClaimCodeSets()));
|
||||
} else {
|
||||
res.status(451).json({ status: 'invalid' });
|
||||
}
|
||||
});
|
||||
|
||||
// TODO we are going to need endpoints that take a password that will be in a .env file to generate new roomGroups, rooms, and claim codes
|
||||
app.post('/group/add', (req, res) => {
|
||||
const data = req.body;
|
||||
const { password, groupName, roomNames, codes } = data;
|
||||
if (password === process.env.PASSWORD) {
|
||||
const result = createGroup(groupName, roomNames, loadedRooms);
|
||||
loadedRooms = result.roomGroup;
|
||||
redisClient.set('rooms', JSON.stringify(loadedRooms));
|
||||
if (codes.generate) {
|
||||
codes.amount = codes.amount || 10;
|
||||
ccm.generateClaimCodeSet(codes.amount, result.groupId, groupName);
|
||||
const ccs = ccm.getClaimCodeSets();
|
||||
redisClient.set('ccm', JSON.stringify(ccs));
|
||||
}
|
||||
res.status(201).json({ status: `Created group ${groupName}`, loadedRooms });
|
||||
}
|
||||
});
|
||||
|
||||
app.post('/room/add', (req, res) => {
|
||||
const data = req.body;
|
||||
const { password, groupId, roomName } = data;
|
||||
if (password === process.env.PASSWORD) {
|
||||
redisClient.get('rooms').then((loadedRooms) => {
|
||||
const roomGroups = createRoom(groupId, roomName, JSON.parse(loadedRooms) as RoomGroupI[]);
|
||||
redisClient.set('rooms', JSON.stringify(roomGroups));
|
||||
res.status(201).json({ status: `Created room ${roomName}`, roomGroups });
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
app.post('/group/createcode', (req, res) => {
|
||||
const data = req.body;
|
||||
let { password, groupId, amount } = data;
|
||||
if (password === process.env.PASSWORD) {
|
||||
amount = amount || 10;
|
||||
console.log(loadedRooms, groupId);
|
||||
const group = findGroupById(loadedRooms, groupId);
|
||||
const ccs = ccm.generateClaimCodeSet(amount, groupId, group.name);
|
||||
redisClient.set('ccm', JSON.stringify(ccs));
|
||||
res.status(201).json({ stats: `Created ${amount} codes for ${group.name}`, ccm });
|
||||
}
|
||||
});
|
||||
|
||||
app.get('/logclaimcodes', (req, res) => {
|
||||
pp('-----CLAIMCODES-----', 'debug');
|
||||
pp(ccm.getClaimCodeSet(TESTGROUPID));
|
||||
pp('-----ENDOFCODES-----', 'debug');
|
||||
res.status(200).json({ status: 'ok' });
|
||||
});
|
||||
}
|
||||
3
src/types.ts
Normal file
3
src/types.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export interface userCountI {
|
||||
[key: string]: number;
|
||||
}
|
||||
18
src/utils.ts
18
src/utils.ts
@@ -1,9 +1,12 @@
|
||||
import { createClient } from 'redis';
|
||||
import type { RoomI, RoomGroupI, ServerI } from 'discreetly-interfaces';
|
||||
import { genId } from 'discreetly-interfaces';
|
||||
|
||||
const redisClient = createClient();
|
||||
redisClient.connect();
|
||||
export function shim() {
|
||||
// Deal with bigints in JSON
|
||||
(BigInt.prototype as any).toJSON = function () {
|
||||
return this.toString();
|
||||
};
|
||||
}
|
||||
|
||||
export function findRoomById(
|
||||
roomGroups,
|
||||
@@ -55,7 +58,7 @@ export function createGroup(
|
||||
groupName: string,
|
||||
roomNames: string[],
|
||||
roomGroups: RoomGroupI[]
|
||||
): { groupId: bigint, roomGroup: RoomGroupI[] } {
|
||||
): { groupId: bigint; roomGroup: RoomGroupI[] } {
|
||||
const newGroup: RoomGroupI = {
|
||||
id: genId(BigInt(999), groupName).toString() as unknown as bigint,
|
||||
name: groupName,
|
||||
@@ -75,14 +78,15 @@ export function createGroup(
|
||||
export function createRoom(
|
||||
groupId: bigint,
|
||||
roomName: string,
|
||||
roomGroups: RoomGroupI[]): RoomGroupI[] {
|
||||
roomGroups: RoomGroupI[]
|
||||
): RoomGroupI[] {
|
||||
const newRoom: RoomI = {
|
||||
id: genId(BigInt(999), roomName),
|
||||
name: roomName,
|
||||
membership: { identityCommitments: [] },
|
||||
rateLimit: 1000
|
||||
}
|
||||
const groupIndex = roomGroups.findIndex(group => group.id === groupId);
|
||||
};
|
||||
const groupIndex = roomGroups.findIndex((group) => group.id === groupId);
|
||||
if (groupIndex !== -1) {
|
||||
roomGroups[groupIndex].rooms.push(newRoom);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user