works with npm run start/dev with a local redis, but not a docker redis

This commit is contained in:
AtHeartEngineer
2023-07-14 15:19:09 -04:00
parent cfe8986488
commit b91f5f72d6
9 changed files with 462 additions and 284 deletions

21
docker-compose.yml Normal file
View 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
View 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
View File

@@ -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",

View File

@@ -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
View 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);
}

View File

@@ -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
View 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
View File

@@ -0,0 +1,3 @@
export interface userCountI {
[key: string]: number;
}

View File

@@ -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);
}