mirror of
https://github.com/Discreetly/server.git
synced 2026-01-10 21:38:02 -05:00
refactored message validation and db to be stricter/cleaner (#69)
~Please review this and test it, I am at the airport and can't fully test this right now.~
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -5,3 +5,4 @@ dist/
|
||||
.env
|
||||
thunder-tests/
|
||||
coverage/
|
||||
.vscode/settings.json
|
||||
|
||||
53
package-lock.json
generated
53
package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "discreetly-server",
|
||||
"version": "0.1.2",
|
||||
"version": "0.1.3",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "discreetly-server",
|
||||
"version": "0.1.2",
|
||||
"version": "0.1.3",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@ethersproject/bytes": "^5.7.0",
|
||||
@@ -25,7 +25,7 @@
|
||||
"helmet": "^7.0.0",
|
||||
"mongodb": "^5.8.0",
|
||||
"poseidon-lite": "^0.2.0",
|
||||
"rlnjs": "^3.1.4",
|
||||
"rlnjs": "^3.2.0",
|
||||
"socket.io": "^4.6.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
@@ -2664,8 +2664,7 @@
|
||||
"node_modules/asynckit": {
|
||||
"version": "0.4.0",
|
||||
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
|
||||
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
|
||||
"dev": true
|
||||
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="
|
||||
},
|
||||
"node_modules/atob": {
|
||||
"version": "2.1.2",
|
||||
@@ -2679,6 +2678,16 @@
|
||||
"node": ">= 4.5.0"
|
||||
}
|
||||
},
|
||||
"node_modules/axios": {
|
||||
"version": "1.5.0",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-1.5.0.tgz",
|
||||
"integrity": "sha512-D4DdjDo5CY50Qms0qGQTTw6Q44jl7zRwY7bthds06pUGfChBCTcQs+N743eFWGEd6pRTMd6A+I87aWyFV5wiZQ==",
|
||||
"dependencies": {
|
||||
"follow-redirects": "^1.15.0",
|
||||
"form-data": "^4.0.0",
|
||||
"proxy-from-env": "^1.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/b4a": {
|
||||
"version": "1.6.4",
|
||||
"resolved": "https://registry.npmjs.org/b4a/-/b4a-1.6.4.tgz",
|
||||
@@ -3207,7 +3216,6 @@
|
||||
"version": "1.0.8",
|
||||
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
|
||||
"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"delayed-stream": "~1.0.0"
|
||||
},
|
||||
@@ -3401,7 +3409,6 @@
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
|
||||
"integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=0.4.0"
|
||||
}
|
||||
@@ -4336,11 +4343,29 @@
|
||||
"integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/follow-redirects": {
|
||||
"version": "1.15.2",
|
||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz",
|
||||
"integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "individual",
|
||||
"url": "https://github.com/sponsors/RubenVerborgh"
|
||||
}
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=4.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"debug": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/form-data": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
|
||||
"integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"asynckit": "^0.4.0",
|
||||
"combined-stream": "^1.0.8",
|
||||
@@ -6503,6 +6528,11 @@
|
||||
"node": ">= 0.10"
|
||||
}
|
||||
},
|
||||
"node_modules/proxy-from-env": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
|
||||
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="
|
||||
},
|
||||
"node_modules/pstree.remy": {
|
||||
"version": "1.1.8",
|
||||
"resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz",
|
||||
@@ -6750,9 +6780,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/rlnjs": {
|
||||
"version": "3.1.4",
|
||||
"resolved": "https://registry.npmjs.org/rlnjs/-/rlnjs-3.1.4.tgz",
|
||||
"integrity": "sha512-BKTBATi5pofLv3LxP+H2tV4riNkeMB6DSV6TQ9jaMqtv9MHCm165P87QjPa40X/fgomSmhsJU9VPFOuoBcN6XQ==",
|
||||
"version": "3.2.0",
|
||||
"resolved": "https://registry.npmjs.org/rlnjs/-/rlnjs-3.2.0.tgz",
|
||||
"integrity": "sha512-+9mqWafWbsfflMR01HV61BcFFxZ5XJLU4pa/qWHxbVvU9mAcl6UKr0UE7clS9oogoGIu/NPjTDIHmkFfBgqgXg==",
|
||||
"dependencies": {
|
||||
"@ethersproject/bytes": "^5.7.0",
|
||||
"@ethersproject/keccak256": "^5.7.0",
|
||||
@@ -6761,6 +6791,7 @@
|
||||
"@semaphore-protocol/group": "^3.10.1",
|
||||
"@semaphore-protocol/identity": "^3.10.1",
|
||||
"@zk-kit/incremental-merkle-tree": "^0.4.3",
|
||||
"axios": "^1.5.0",
|
||||
"ethers": "^6.4.0",
|
||||
"ffjavascript": "0.2.55",
|
||||
"poseidon-lite": "^0.0.2",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "discreetly-server",
|
||||
"version": "0.1.2",
|
||||
"version": "0.1.3",
|
||||
"description": "",
|
||||
"main": "dist/server.cjs",
|
||||
"prisma": {
|
||||
@@ -39,7 +39,7 @@
|
||||
"helmet": "^7.0.0",
|
||||
"mongodb": "^5.8.0",
|
||||
"poseidon-lite": "^0.2.0",
|
||||
"rlnjs": "^3.1.4",
|
||||
"rlnjs": "^3.2.0",
|
||||
"socket.io": "^4.6.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
@@ -68,4 +68,4 @@
|
||||
"ts-node": "^10.9.1",
|
||||
"typescript": "^5.1.6"
|
||||
}
|
||||
}
|
||||
}
|
||||
4
src/crypto/index.ts
Normal file
4
src/crypto/index.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
export * from './rateCommitmentHasher';
|
||||
export * from './shamirRecovery';
|
||||
export * from './signalHash';
|
||||
export * from './verifier';
|
||||
@@ -1,7 +1,8 @@
|
||||
import { poseidon2 } from 'poseidon-lite/poseidon2';
|
||||
|
||||
function getRateCommitmentHash(identityCommitment: bigint, userMessageLimit: number | bigint) {
|
||||
export function getRateCommitmentHash(
|
||||
identityCommitment: bigint,
|
||||
userMessageLimit: number | bigint
|
||||
): bigint {
|
||||
return poseidon2([identityCommitment, userMessageLimit]);
|
||||
}
|
||||
|
||||
export default getRateCommitmentHash;
|
||||
|
||||
@@ -6,7 +6,11 @@ import { calculateSignalHash } from './signalHash';
|
||||
|
||||
const v = new RLNVerifier(vkey);
|
||||
|
||||
async function verifyProof(msg: MessageI, room: RoomI, epochErrorRange = 5): Promise<boolean> {
|
||||
export async function verifyProof(
|
||||
room: RoomI,
|
||||
msg: MessageI,
|
||||
epochErrorRange = 5
|
||||
): Promise<boolean> {
|
||||
if (!msg.roomId || !msg.message || !msg.proof || !msg.epoch) {
|
||||
console.warn('Missing required fields:', msg);
|
||||
return false;
|
||||
@@ -62,5 +66,3 @@ async function verifyProof(msg: MessageI, room: RoomI, epochErrorRange = 5): Pro
|
||||
// Check that the proof is correct
|
||||
return v.verifyProof(rlnIdentifier, proof);
|
||||
}
|
||||
|
||||
export default verifyProof;
|
||||
|
||||
473
src/data/db.ts
473
src/data/db.ts
@@ -1,473 +0,0 @@
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
||||
import { PrismaClient } from '@prisma/client';
|
||||
import { genId } from 'discreetly-interfaces';
|
||||
import type { RoomI } from 'discreetly-interfaces';
|
||||
import { serverConfig } from '../config/serverConfig';
|
||||
import { genMockUsers, genClaimCodeArray, pp } from '../utils';
|
||||
import getRateCommitmentHash from '../crypto/rateCommitmentHasher';
|
||||
|
||||
const prisma = new PrismaClient();
|
||||
|
||||
interface CodeStatus {
|
||||
claimed: boolean;
|
||||
roomIds: string[];
|
||||
}
|
||||
|
||||
interface RoomsFromClaimCode {
|
||||
roomIds: string[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a room by id
|
||||
* @param {string} id The id of the room to get
|
||||
* @returns {Promise<RoomI | null>}The room, or null if it doesn't exist
|
||||
*/
|
||||
|
||||
export async function getRoomByID(id: string): Promise<RoomI | null> {
|
||||
const room = await prisma.rooms
|
||||
.findUnique({
|
||||
where: {
|
||||
roomId: id
|
||||
},
|
||||
// Filter out the information we want from the room
|
||||
select: {
|
||||
id: true,
|
||||
roomId: true,
|
||||
name: true,
|
||||
identities: true,
|
||||
rateLimit: true,
|
||||
userMessageLimit: true,
|
||||
membershipType: true,
|
||||
contractAddress: true,
|
||||
bandadaAddress: true,
|
||||
bandadaGroupId: true,
|
||||
type: true
|
||||
}
|
||||
})
|
||||
.then((room) => {
|
||||
return room;
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error(err);
|
||||
throw err; // Add this line to throw the error
|
||||
});
|
||||
return new Promise((resolve, reject) => {
|
||||
if (room) {
|
||||
resolve(room as RoomI);
|
||||
}
|
||||
reject('Room not found');
|
||||
});
|
||||
}
|
||||
|
||||
/* TODO Need to create a system here where the client needs to provide a
|
||||
proof they know the secrets to some Identity Commitment with a unix epoch
|
||||
time stamp to prevent replay attacks
|
||||
|
||||
https://github.com/Discreetly/IdentityCommitmentNullifierCircuit <- Circuit and JS to do this
|
||||
*/
|
||||
|
||||
/**
|
||||
* This function takes in an identity and returns the rooms the identity is in.
|
||||
* @param identity - the identity of a user
|
||||
* @returns an array of roomIds
|
||||
*/
|
||||
|
||||
export async function getRoomsByIdentity(identity: string): Promise<string[]> {
|
||||
const r: string[] = [];
|
||||
try {
|
||||
const rooms = await prisma.rooms.findMany({
|
||||
where: {
|
||||
semaphoreIdentities: {
|
||||
has: identity
|
||||
}
|
||||
}
|
||||
});
|
||||
rooms.forEach((room) => {
|
||||
r.push(room.roomId);
|
||||
});
|
||||
return r;
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds a claim code in the database.
|
||||
*
|
||||
* @param {string} code - The code to find.
|
||||
* @returns {Promise<CodeStatus | null>} - The claim code, if found.
|
||||
*/
|
||||
|
||||
export function findClaimCode(code: string): Promise<CodeStatus | null> {
|
||||
return prisma.claimCodes.findUnique({
|
||||
where: { claimcode: code }
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the claim_code table to mark the given code as claimed.
|
||||
* @param {string} code - The code to update
|
||||
* @returns {Promise<RoomsFromClaimCode>} - The rooms associated with the claim code
|
||||
*/
|
||||
|
||||
export function updateClaimCode(code: string): Promise<RoomsFromClaimCode> {
|
||||
return prisma.claimCodes.update({
|
||||
where: { claimcode: code },
|
||||
data: { claimed: true }
|
||||
});
|
||||
}
|
||||
|
||||
/*
|
||||
The sanitizeIDC function takes a string and returns a string.
|
||||
The string is converted to a BigInt and then back to a string.
|
||||
If the string has no loss of precision, it is returned.
|
||||
Otherwise, an error is thrown.
|
||||
*/
|
||||
|
||||
function sanitizeIDC(idc: string): string {
|
||||
try {
|
||||
const tempBigInt = BigInt(idc);
|
||||
const tempString = tempBigInt.toString();
|
||||
if (idc === tempString) {
|
||||
return idc;
|
||||
} else {
|
||||
throw new Error('Invalid IDC provided.');
|
||||
}
|
||||
} catch (error) {
|
||||
throw new Error('Invalid IDC provided.');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This code updates the identity commitments of a list of rooms.
|
||||
* It adds the identity commitment to the identity list of each room,
|
||||
* and also adds it to the bandada of each room. The identity commitment is
|
||||
* sanitized before being added to the database.
|
||||
* @param idc - The identity commitment of the user
|
||||
* @param roomIds - The list of roomIds that the user is in
|
||||
* @returns {Promise<void>} - A promise that resolves when the update is complete
|
||||
*/
|
||||
|
||||
export async function updateRoomIdentities(
|
||||
idc: string,
|
||||
roomIds: string[]
|
||||
): Promise<string[] | void> {
|
||||
const identityCommitment = sanitizeIDC(idc);
|
||||
return await prisma.rooms
|
||||
.findMany({
|
||||
where: { id: { in: roomIds } }
|
||||
})
|
||||
.then(async (rooms) => {
|
||||
const identityRooms = await addIdentityToIdentityListRooms(
|
||||
rooms,
|
||||
identityCommitment
|
||||
);
|
||||
const bandadaRooms = await addIdentityToBandadaRooms(
|
||||
rooms,
|
||||
identityCommitment
|
||||
);
|
||||
return [...identityRooms, ...bandadaRooms] as string[];
|
||||
})
|
||||
.catch((err) => {
|
||||
pp(err, 'error');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a user's identity commitment to the semaphoreIdentities list and adds their rate commitment to the identities list for each of the identity list rooms that they are in.
|
||||
* @param {rooms} - The list of rooms that the user is in
|
||||
* @param {string} identityCommitment - The user's identity commitment
|
||||
* @return {string[]} addedRooms - The list of rooms that the user was added to
|
||||
*/
|
||||
async function addIdentityToIdentityListRooms(
|
||||
rooms,
|
||||
identityCommitment: string
|
||||
): Promise<string[]> {
|
||||
const identityListRooms = rooms
|
||||
.filter(
|
||||
(room: RoomI) =>
|
||||
room.membershipType === 'IDENTITY_LIST' &&
|
||||
!room.semaphoreIdentities?.includes(identityCommitment)
|
||||
)
|
||||
.map((room) => room.id as string);
|
||||
|
||||
const addedRooms: string[] = [];
|
||||
|
||||
const promises = identityListRooms.map(async (roomId) => {
|
||||
const room = rooms.find((r) => r.id === roomId);
|
||||
if (room) {
|
||||
try {
|
||||
await prisma.rooms.update({
|
||||
where: { id: roomId },
|
||||
data: {
|
||||
identities: {
|
||||
push: getRateCommitmentHash(
|
||||
BigInt(identityCommitment),
|
||||
BigInt((room.userMessageLimit as number) ?? 1)
|
||||
).toString()
|
||||
},
|
||||
semaphoreIdentities: { push: identityCommitment }
|
||||
}
|
||||
});
|
||||
console.debug(
|
||||
`Successfully added user to Identity List room ${room.roomId}`
|
||||
);
|
||||
addedRooms.push(roomId as string);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
await Promise.all(promises);
|
||||
return addedRooms;
|
||||
}
|
||||
|
||||
/**
|
||||
* This code adds a new identity commitment to the list of identities in a bandada room.
|
||||
* First we get the list of bandada rooms that contain the identity commitment.
|
||||
* Then we iterate over the list of rooms and add the identity commitment to each room.
|
||||
* After that we update the list of identities in each room in the database.
|
||||
* Finally, we send a POST request to the bandada server to add the identity to the group.
|
||||
* @param {RoomI[]} rooms - The list of rooms that contain the identity commitment.
|
||||
* @param {string} identityCommitment - The identity commitment to be added to the bandada room.
|
||||
* @return {string[]} addedRooms - The list of rooms that the user was added to
|
||||
*/
|
||||
|
||||
async function addIdentityToBandadaRooms(
|
||||
rooms,
|
||||
identityCommitment: string
|
||||
): Promise<string[]> {
|
||||
const bandadaGroupRooms = rooms
|
||||
.filter(
|
||||
(room: RoomI) =>
|
||||
room.membershipType === 'BANDADA_GROUP' &&
|
||||
!room.semaphoreIdentities?.includes(identityCommitment)
|
||||
)
|
||||
.map((room) => room as RoomI);
|
||||
|
||||
const addedRooms: string[] = [];
|
||||
|
||||
if (bandadaGroupRooms.length > 0) {
|
||||
const promises = bandadaGroupRooms.map(async (room) => {
|
||||
const rateCommitment = getRateCommitmentHash(
|
||||
BigInt(identityCommitment),
|
||||
BigInt((room.userMessageLimit as number) ?? 1)
|
||||
).toString();
|
||||
|
||||
if (!room.bandadaAPIKey) {
|
||||
console.error('API key is missing for room:', room);
|
||||
return;
|
||||
}
|
||||
|
||||
const requestOptions = {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'x-api-key': room.bandadaAPIKey
|
||||
}
|
||||
};
|
||||
|
||||
try {
|
||||
await prisma.rooms.update({
|
||||
where: { id: room.id },
|
||||
data: {
|
||||
identities: {
|
||||
push: rateCommitment
|
||||
},
|
||||
semaphoreIdentities: { push: identityCommitment }
|
||||
}
|
||||
});
|
||||
|
||||
const url = `https://${room.bandadaAddress}/groups/${room.bandadaGroupId}/members/${rateCommitment}`;
|
||||
const response = await fetch(url, requestOptions);
|
||||
console.log(response);
|
||||
if (response.status == 201) {
|
||||
console.debug(
|
||||
`Successfully added user to Bandada group ${room.bandadaAddress}`
|
||||
);
|
||||
addedRooms.push(room.id as string);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
});
|
||||
|
||||
await Promise.all(promises);
|
||||
}
|
||||
|
||||
return addedRooms;
|
||||
}
|
||||
/**
|
||||
* This function is used to find rooms that have been updated
|
||||
* It is used in the findUpdatedRooms function
|
||||
* It is important because it allows the user to see which rooms have been updated
|
||||
* @param {string[]} roomIds - The list of roomIds that the user is in
|
||||
* @returns {Promise<RoomI[]>} - A promise that resolves to a list of rooms
|
||||
*/
|
||||
|
||||
export async function findUpdatedRooms(roomIds: string[]): Promise<RoomI[]> {
|
||||
const rooms = await prisma.rooms.findMany({
|
||||
where: { id: { in: roomIds } }
|
||||
});
|
||||
return new Promise((resolve, reject) => {
|
||||
if (rooms) {
|
||||
resolve(rooms as RoomI[]);
|
||||
}
|
||||
reject('No rooms found');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* This function creates a system message in a room.
|
||||
* The message will be the same in all rooms if no roomId is passed.
|
||||
* If a roomId is passed, the message will be created in that room.
|
||||
* @param {string} message - The message to be created
|
||||
* @param {string} roomId - The roomId to create the message in
|
||||
*/
|
||||
export function createSystemMessages(
|
||||
message: string,
|
||||
roomId?: string
|
||||
): Promise<unknown> {
|
||||
const query = roomId ? { where: { roomId } } : undefined;
|
||||
return prisma.rooms
|
||||
.findMany(query)
|
||||
.then((rooms) => {
|
||||
if (roomId && rooms.length === 0) {
|
||||
return Promise.reject('Room not found');
|
||||
}
|
||||
const createMessages = rooms.map((room) => {
|
||||
return prisma.messages.create({
|
||||
data: {
|
||||
message,
|
||||
roomId: room.roomId,
|
||||
messageId: '0',
|
||||
proof: JSON.stringify({})
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
return Promise.all(createMessages);
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error(err);
|
||||
return Promise.reject(err);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
export interface BandadaRoom extends RoomI {
|
||||
bandadaAPIKey: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* This function takes in an identity and a room and removes the identity from the room
|
||||
* by setting its semaphoreIdentities to 0n and identities to 0n
|
||||
* @param {string} idc - The identity of the user
|
||||
* @param {RoomI} room - The room to remove the identity from
|
||||
* @returns {Promise<void | RoomI>} - A promise that resolves to the room
|
||||
*/
|
||||
|
||||
export function removeIdentityFromRoom(
|
||||
idc: string,
|
||||
room: RoomI
|
||||
): Promise<void | RoomI> {
|
||||
const updateSemaphoreIdentities =
|
||||
room.semaphoreIdentities?.map((identity) =>
|
||||
identity === idc ? '0' : (identity as string)
|
||||
) ?? [];
|
||||
|
||||
const rateCommitmentsToUpdate = getRateCommitmentHash(
|
||||
BigInt(idc),
|
||||
BigInt(room.userMessageLimit!)
|
||||
).toString();
|
||||
|
||||
const updatedRateCommitments =
|
||||
room.identities?.map((limiter) =>
|
||||
limiter == rateCommitmentsToUpdate ? '0' : (limiter as string)
|
||||
) ?? [];
|
||||
|
||||
|
||||
return prisma.rooms
|
||||
.update({
|
||||
where: { id: room.id },
|
||||
data: {
|
||||
identities: updatedRateCommitments,
|
||||
semaphoreIdentities: updateSemaphoreIdentities
|
||||
}
|
||||
})
|
||||
.then((room) => {
|
||||
return room as RoomI;
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error(err);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new room with the given name and optional parameters.
|
||||
* @param {string} name - The name of the room.
|
||||
* @param {number} [rateLimit=1000] - The length of an epoch in milliseconds
|
||||
* @param {number} [userMessageLimit=1] - The message limit per user per epoch
|
||||
* @param {number} [numClaimCodes=0] - The number of claim codes to generate for the room.
|
||||
* @param {number} [approxNumMockUsers=20] - The approximate number of mock users to generate for the room.
|
||||
* @param {string} [type='IDENTITY_LIST'] - The type of room to create.
|
||||
* @param {string} [bandadaAddress] - The address of the bandada server.
|
||||
* @param {string} [bandadaGroupId] - The id of the bandada group.
|
||||
* @param {string} [bandadaAPIKey] - The API key for the bandada server.
|
||||
* @param {string} [membershipType] - The membership type of the room.
|
||||
* @returns {Promise<boolean>} - A promise that resolves to true if the room was created successfully.
|
||||
*/
|
||||
export async function createRoom(
|
||||
roomName: string,
|
||||
rateLimit = 1000,
|
||||
userMessageLimit = 1,
|
||||
numClaimCodes = 0,
|
||||
approxNumMockUsers = 20,
|
||||
type: string,
|
||||
bandadaAddress?: string,
|
||||
bandadaGroupId?: string,
|
||||
bandadaAPIKey?: string,
|
||||
membershipType?: string
|
||||
): Promise<boolean> {
|
||||
const claimCodes: { claimcode: string }[] = genClaimCodeArray(numClaimCodes);
|
||||
const mockUsers: string[] = genMockUsers(approxNumMockUsers);
|
||||
const identityCommitments: string[] = mockUsers.map((user) =>
|
||||
getRateCommitmentHash(BigInt(user), BigInt(userMessageLimit)).toString()
|
||||
);
|
||||
const roomData = {
|
||||
where: {
|
||||
roomId: genId(serverConfig.id as bigint, roomName).toString()
|
||||
},
|
||||
update: {},
|
||||
create: {
|
||||
roomId: genId(serverConfig.id as bigint, roomName).toString(),
|
||||
name: roomName,
|
||||
rateLimit: rateLimit,
|
||||
userMessageLimit: userMessageLimit,
|
||||
semaphoreIdentities: mockUsers,
|
||||
identities: identityCommitments,
|
||||
type,
|
||||
bandadaAddress,
|
||||
bandadaGroupId,
|
||||
bandadaAPIKey,
|
||||
membershipType,
|
||||
claimCodes: {
|
||||
create: claimCodes
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return await prisma.rooms
|
||||
.upsert(roomData)
|
||||
.then(() => {
|
||||
return true;
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error(err);
|
||||
return false;
|
||||
});
|
||||
}
|
||||
137
src/data/db/create.ts
Normal file
137
src/data/db/create.ts
Normal file
@@ -0,0 +1,137 @@
|
||||
import { PrismaClient } from '@prisma/client';
|
||||
import { getRateCommitmentHash, MessageI, randomBigInt } from 'discreetly-interfaces';
|
||||
import { genClaimCodeArray, genMockUsers } from '../../utils';
|
||||
|
||||
const prisma = new PrismaClient();
|
||||
|
||||
/**
|
||||
* Creates a new room with the given name and optional parameters.
|
||||
* @param {string} name - The name of the room.
|
||||
* @param {number} [rateLimit=1000] - The length of an epoch in milliseconds
|
||||
* @param {number} [userMessageLimit=1] - The message limit per user per epoch
|
||||
* @param {number} [numClaimCodes=0] - The number of claim codes to generate for the room.
|
||||
* @param {number} [approxNumMockUsers=20] - The approximate number of mock users to generate for the room.
|
||||
* @param {string} [type='IDENTITY_LIST'] - The type of room to create.
|
||||
* @param {string} [bandadaAddress] - The address of the bandada server.
|
||||
* @param {string} [bandadaGroupId] - The id of the bandada group.
|
||||
* @param {string} [bandadaAPIKey] - The API key for the bandada server.
|
||||
* @param {string} [membershipType] - The membership type of the room.
|
||||
* @returns {Promise<boolean>} - A promise that resolves to true if the room was created successfully.
|
||||
*/
|
||||
export async function createRoom(
|
||||
roomName: string,
|
||||
rateLimit = 1000,
|
||||
userMessageLimit = 1,
|
||||
numClaimCodes = 0,
|
||||
approxNumMockUsers = 20,
|
||||
type: string,
|
||||
bandadaAddress?: string,
|
||||
bandadaGroupId?: string,
|
||||
bandadaAPIKey?: string,
|
||||
membershipType?: string
|
||||
): Promise<string | undefined> {
|
||||
const claimCodes: { claimcode: string }[] = genClaimCodeArray(numClaimCodes);
|
||||
const mockUsers: string[] = genMockUsers(approxNumMockUsers);
|
||||
const identityCommitments: string[] = mockUsers.map((user) =>
|
||||
getRateCommitmentHash(BigInt(user), BigInt(userMessageLimit)).toString()
|
||||
);
|
||||
const roomIdFromRandom = randomBigInt().toString();
|
||||
const roomData = {
|
||||
where: {
|
||||
roomId: roomIdFromRandom
|
||||
},
|
||||
update: {},
|
||||
create: {
|
||||
roomId: roomIdFromRandom,
|
||||
name: roomName,
|
||||
rateLimit: rateLimit,
|
||||
userMessageLimit: userMessageLimit,
|
||||
semaphoreIdentities: mockUsers,
|
||||
identities: identityCommitments,
|
||||
type,
|
||||
bandadaAddress,
|
||||
bandadaGroupId,
|
||||
bandadaAPIKey,
|
||||
membershipType,
|
||||
claimCodes: {
|
||||
create: claimCodes
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return await prisma.rooms
|
||||
.upsert(roomData)
|
||||
.then(() => {
|
||||
return roomIdFromRandom;
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error(err);
|
||||
return undefined;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* This function creates a system message in a room.
|
||||
* The message will be the same in all rooms if no roomId is passed.
|
||||
* If a roomId is passed, the message will be created in that room.
|
||||
* @param {string} message - The message to be created
|
||||
* @param {string} roomId - The roomId to create the message in
|
||||
*/
|
||||
export function createSystemMessages(message: string, roomId?: string): Promise<unknown> {
|
||||
const query = roomId ? { where: { roomId } } : undefined;
|
||||
return prisma.rooms
|
||||
.findMany(query)
|
||||
.then((rooms) => {
|
||||
if (roomId && rooms.length === 0) {
|
||||
return Promise.reject('Room not found');
|
||||
}
|
||||
const createMessages = rooms.map((room) => {
|
||||
return prisma.messages.create({
|
||||
data: {
|
||||
message,
|
||||
roomId: room.roomId,
|
||||
messageId: '0',
|
||||
proof: JSON.stringify({})
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
return Promise.all(createMessages);
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error(err);
|
||||
return Promise.reject(err);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a message to a room.
|
||||
* @param {string} roomId - The ID of the room to add the message to.
|
||||
* @param {MessageI} message - The message to add to the room.
|
||||
* @returns {Promise<unknown>} - A promise that resolves when the message has been added to the room.
|
||||
*/
|
||||
export function createMessageInRoom(roomId: string, message: MessageI): Promise<unknown> {
|
||||
if (!message.epoch) {
|
||||
throw new Error('Epoch not provided');
|
||||
}
|
||||
return prisma.rooms.update({
|
||||
where: {
|
||||
roomId: roomId
|
||||
},
|
||||
data: {
|
||||
epochs: {
|
||||
create: {
|
||||
epoch: String(message.epoch),
|
||||
messages: {
|
||||
create: {
|
||||
message: message.message ? String(message.message) : '',
|
||||
messageId: message.messageId ? message.messageId.toString() : '',
|
||||
proof: JSON.stringify(message.proof),
|
||||
roomId: roomId
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
135
src/data/db/find.ts
Normal file
135
src/data/db/find.ts
Normal file
@@ -0,0 +1,135 @@
|
||||
import { PrismaClient } from '@prisma/client';
|
||||
import { MessageI, RoomI } from 'discreetly-interfaces';
|
||||
import { CodeStatus } from '../../types/';
|
||||
const prisma = new PrismaClient();
|
||||
|
||||
/**
|
||||
* Gets a room by id
|
||||
* @param {string} id The id of the room to get
|
||||
* @returns {Promise<RoomI | null>}The room, or null if it doesn't exist
|
||||
*/
|
||||
export async function findRoomById(id: string): Promise<RoomI | null> {
|
||||
const room = await prisma.rooms
|
||||
.findUnique({
|
||||
where: {
|
||||
roomId: id
|
||||
},
|
||||
// Filter out the information we want from the room
|
||||
select: {
|
||||
id: true,
|
||||
roomId: true,
|
||||
name: true,
|
||||
identities: true,
|
||||
rateLimit: true,
|
||||
userMessageLimit: true,
|
||||
membershipType: true,
|
||||
contractAddress: true,
|
||||
bandadaAddress: true,
|
||||
bandadaGroupId: true,
|
||||
type: true
|
||||
}
|
||||
})
|
||||
.then((room) => {
|
||||
return room;
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error(err);
|
||||
throw err; // Add this line to throw the error
|
||||
});
|
||||
return new Promise((resolve, reject) => {
|
||||
if (room) {
|
||||
resolve(room as RoomI);
|
||||
}
|
||||
reject('Room not found');
|
||||
});
|
||||
}
|
||||
|
||||
/* TODO Need to create a system here where the client needs to provide a
|
||||
proof they know the secrets to some Identity Commitment with a unix epoch
|
||||
time stamp to prevent replay attacks
|
||||
|
||||
https://github.com/Discreetly/IdentityCommitmentNullifierCircuit <- Circuit and JS to do this
|
||||
*/
|
||||
/**
|
||||
* This function takes in an identity and returns the rooms the identity is in.
|
||||
* @param identity - the identity of a user
|
||||
* @returns an array of roomIds
|
||||
*/
|
||||
export async function findRoomsByIdentity(identity: string): Promise<string[]> {
|
||||
const r: string[] = [];
|
||||
try {
|
||||
const rooms = await prisma.rooms.findMany({
|
||||
where: {
|
||||
semaphoreIdentities: {
|
||||
has: identity
|
||||
}
|
||||
}
|
||||
});
|
||||
rooms.forEach((room) => {
|
||||
r.push(room.roomId);
|
||||
});
|
||||
return r;
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds a claim code in the database.
|
||||
*
|
||||
* @param {string} code - The code to find.
|
||||
* @returns {Promise<CodeStatus | null>} - The claim code, if found.
|
||||
*/
|
||||
export function findClaimCode(code: string): Promise<CodeStatus | null> {
|
||||
return prisma.claimCodes.findUnique({
|
||||
where: { claimcode: code }
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* This function is used to find rooms that have been updated
|
||||
* It is used in the findUpdatedRooms function
|
||||
* It is important because it allows the user to see which rooms have been updated
|
||||
* @param {string[]} roomIds - The list of roomIds that the user is in
|
||||
* @returns {Promise<RoomI[]>} - A promise that resolves to a list of rooms
|
||||
*/
|
||||
export async function findUpdatedRooms(roomIds: string[]): Promise<RoomI[]> {
|
||||
const rooms = await prisma.rooms.findMany({
|
||||
where: { id: { in: roomIds } }
|
||||
});
|
||||
return new Promise((resolve, reject) => {
|
||||
if (rooms) {
|
||||
resolve(rooms as RoomI[]);
|
||||
}
|
||||
reject('No rooms found');
|
||||
});
|
||||
}
|
||||
|
||||
export async function findRoomWithMessageId(
|
||||
roomId: string,
|
||||
message: MessageI
|
||||
): Promise<MessageI | null> {
|
||||
try {
|
||||
const room = await prisma.rooms.findFirst({
|
||||
where: { roomId },
|
||||
include: {
|
||||
epochs: {
|
||||
where: { epoch: String(message.epoch) },
|
||||
include: {
|
||||
messages: {
|
||||
where: { messageId: message.messageId }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
if (!room) {
|
||||
return null;
|
||||
}
|
||||
return room.epochs[0].messages[0];
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
4
src/data/db/index.ts
Normal file
4
src/data/db/index.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
export * from './create';
|
||||
export * from './find';
|
||||
export * from './update';
|
||||
export * from './remove';
|
||||
51
src/data/db/remove.ts
Normal file
51
src/data/db/remove.ts
Normal file
@@ -0,0 +1,51 @@
|
||||
import { PrismaClient } from '@prisma/client';
|
||||
import { RoomI, getRateCommitmentHash } from 'discreetly-interfaces';
|
||||
const prisma = new PrismaClient();
|
||||
|
||||
/**
|
||||
* This function takes in an identity and a room and removes the identity from the room
|
||||
* by setting its semaphoreIdentities to 0n and identities to 0n
|
||||
* @param {string} idc - The identity of the user
|
||||
* @param {RoomI} room - The room to remove the identity from
|
||||
* @returns {Promise<void | RoomI>} - A promise that resolves to the room
|
||||
*/
|
||||
export function removeIdentityFromRoom(idc: string, room: RoomI): Promise<void | RoomI> {
|
||||
const updateSemaphoreIdentities =
|
||||
room.semaphoreIdentities?.map((identity) => (identity === idc ? '0' : (identity as string))) ??
|
||||
[];
|
||||
|
||||
const rateCommitmentsToUpdate = getRateCommitmentHash(
|
||||
BigInt(idc),
|
||||
BigInt(room.userMessageLimit!)
|
||||
).toString();
|
||||
|
||||
const updatedRateCommitments =
|
||||
room.identities?.map((limiter) =>
|
||||
limiter == rateCommitmentsToUpdate ? '0' : (limiter as string)
|
||||
) ?? [];
|
||||
|
||||
return prisma.rooms
|
||||
.update({
|
||||
where: { id: room.id },
|
||||
data: {
|
||||
identities: updatedRateCommitments,
|
||||
semaphoreIdentities: updateSemaphoreIdentities
|
||||
}
|
||||
})
|
||||
.then((room) => {
|
||||
return room as RoomI;
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error(err);
|
||||
});
|
||||
}
|
||||
|
||||
export function removeRoom(roomId: string) {
|
||||
console.warn('removeRoom not implemented', roomId);
|
||||
//TODO removeRoom function
|
||||
}
|
||||
|
||||
export function removeMessage(roomId: string, messageId: string) {
|
||||
console.warn('removeMessage not implemented', roomId, messageId);
|
||||
//TODO removeMessage function
|
||||
}
|
||||
174
src/data/db/update.ts
Normal file
174
src/data/db/update.ts
Normal file
@@ -0,0 +1,174 @@
|
||||
import { PrismaClient } from '@prisma/client';
|
||||
import { sanitizeIDC } from '../utils';
|
||||
import { RoomI } from 'discreetly-interfaces';
|
||||
import { getRateCommitmentHash } from '../../crypto';
|
||||
import { pp } from '../../utils';
|
||||
import { RoomWithSecretsI, RoomsFromClaimCode } from '../../types/';
|
||||
|
||||
const prisma = new PrismaClient();
|
||||
|
||||
/**
|
||||
* This code updates the identity commitments of a list of rooms.
|
||||
* It adds the identity commitment to the identity list of each room,
|
||||
* and also adds it to the bandada of each room. The identity commitment is
|
||||
* sanitized before being added to the database.
|
||||
* @param idc - The identity commitment of the user
|
||||
* @param roomIds - The list of roomIds that the user is in
|
||||
* @returns {Promise<void>} - A promise that resolves when the update is complete
|
||||
*/
|
||||
export async function updateRoomIdentities(
|
||||
idc: string,
|
||||
roomIds: string[]
|
||||
): Promise<string[] | void> {
|
||||
try {
|
||||
const identityCommitment: string = sanitizeIDC(idc);
|
||||
const rooms: RoomWithSecretsI[] | null = (await prisma.rooms.findMany({
|
||||
where: { id: { in: roomIds } }
|
||||
})) as RoomWithSecretsI[];
|
||||
|
||||
if (!rooms) {
|
||||
throw new Error('No rooms found with the provided IDs');
|
||||
}
|
||||
|
||||
const identityRooms: string[] = await addIdentityToIdentityListRooms(rooms, identityCommitment);
|
||||
const bandadaRooms: string[] = await addIdentityToBandadaRooms(rooms, identityCommitment);
|
||||
|
||||
return [...identityRooms, ...bandadaRooms];
|
||||
} catch (err) {
|
||||
pp(err, 'error');
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the claim_code table to mark the given code as claimed.
|
||||
* @param {string} code - The code to update
|
||||
* @returns {Promise<RoomsFromClaimCode>} - The rooms associated with the claim code
|
||||
*/
|
||||
export function updateClaimCode(code: string): Promise<RoomsFromClaimCode> {
|
||||
return prisma.claimCodes.update({
|
||||
where: { claimcode: code },
|
||||
data: { claimed: true }
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a user's identity commitment to the semaphoreIdentities list and adds their rate commitment to the identities list for each of the identity list rooms that they are in.
|
||||
* @param {rooms} - The list of rooms that the user is in
|
||||
* @param {string} identityCommitment - The user's identity commitment
|
||||
* @return {string[]} addedRooms - The list of rooms that the user was added to
|
||||
*/
|
||||
export async function addIdentityToIdentityListRooms(
|
||||
rooms: RoomI[] | RoomWithSecretsI[],
|
||||
identityCommitment: string
|
||||
): Promise<string[]> {
|
||||
const identityListRooms = rooms
|
||||
.filter(
|
||||
(room: RoomI) =>
|
||||
room.membershipType === 'IDENTITY_LIST' &&
|
||||
!room.semaphoreIdentities?.includes(identityCommitment)
|
||||
)
|
||||
.map((room) => room.roomId as string);
|
||||
|
||||
const addedRooms: string[] = [];
|
||||
|
||||
const promises = identityListRooms.map(async (roomId) => {
|
||||
const room = rooms.find((r) => r.roomId === roomId);
|
||||
if (room) {
|
||||
try {
|
||||
await prisma.rooms.update({
|
||||
where: { id: roomId },
|
||||
data: {
|
||||
identities: {
|
||||
push: getRateCommitmentHash(
|
||||
BigInt(identityCommitment),
|
||||
BigInt(room.userMessageLimit! ?? 1)
|
||||
).toString()
|
||||
},
|
||||
semaphoreIdentities: { push: identityCommitment }
|
||||
}
|
||||
});
|
||||
console.debug(`Successfully added user to Identity List room ${room.roomId}`);
|
||||
addedRooms.push(roomId);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
await Promise.all(promises);
|
||||
return addedRooms;
|
||||
}
|
||||
|
||||
/**
|
||||
* This code adds a new identity commitment to the list of identities in a bandada room.
|
||||
* First we get the list of bandada rooms that contain the identity commitment.
|
||||
* Then we iterate over the list of rooms and add the identity commitment to each room.
|
||||
* After that we update the list of identities in each room in the database.
|
||||
* Finally, we send a POST request to the bandada server to add the identity to the group.
|
||||
* @param {RoomI[]} rooms - The list of rooms that contain the identity commitment.
|
||||
* @param {string} identityCommitment - The identity commitment to be added to the bandada room.
|
||||
* @return {string[]} addedRooms - The list of rooms that the user was added to
|
||||
*/
|
||||
export async function addIdentityToBandadaRooms(
|
||||
rooms: RoomWithSecretsI[],
|
||||
identityCommitment: string
|
||||
): Promise<string[]> {
|
||||
const bandadaGroupRooms = rooms
|
||||
.filter(
|
||||
(room: RoomI) =>
|
||||
room.membershipType === 'BANDADA_GROUP' &&
|
||||
!room.semaphoreIdentities?.includes(identityCommitment)
|
||||
)
|
||||
.map((room) => room);
|
||||
|
||||
const addedRooms: string[] = [];
|
||||
|
||||
if (bandadaGroupRooms.length > 0) {
|
||||
const promises = bandadaGroupRooms.map(async (room) => {
|
||||
const rateCommitment = getRateCommitmentHash(
|
||||
BigInt(identityCommitment),
|
||||
BigInt(room.userMessageLimit! ?? 1)
|
||||
).toString();
|
||||
|
||||
if (!room.bandadaAPIKey) {
|
||||
console.error('API key is missing for room:', room);
|
||||
return;
|
||||
}
|
||||
|
||||
const requestOptions = {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'x-api-key': room.bandadaAPIKey
|
||||
}
|
||||
};
|
||||
|
||||
try {
|
||||
await prisma.rooms.update({
|
||||
where: { id: room.id },
|
||||
data: {
|
||||
identities: {
|
||||
push: rateCommitment
|
||||
},
|
||||
semaphoreIdentities: { push: identityCommitment }
|
||||
}
|
||||
});
|
||||
|
||||
const url = `https://${room.bandadaAddress}/groups/${room.bandadaGroupId}/members/${rateCommitment}`;
|
||||
const response = await fetch(url, requestOptions);
|
||||
console.log(response);
|
||||
if (response.status == 201) {
|
||||
console.debug(`Successfully added user to Bandada group ${room.bandadaAddress}`);
|
||||
addedRooms.push(room.id);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
});
|
||||
|
||||
await Promise.all(promises);
|
||||
}
|
||||
|
||||
return addedRooms;
|
||||
}
|
||||
@@ -1,10 +1,8 @@
|
||||
import { getRoomByID, removeIdentityFromRoom } from './db';
|
||||
import { PrismaClient } from '@prisma/client';
|
||||
import { MessageI } from 'discreetly-interfaces';
|
||||
import { createMessageInRoom, findRoomWithMessageId, removeIdentityFromRoom } from './db/';
|
||||
import { MessageI, RoomI } from 'discreetly-interfaces';
|
||||
import { shamirRecovery, getIdentityCommitmentFromSecret } from '../crypto/shamirRecovery';
|
||||
import { RLNFullProof } from 'rlnjs';
|
||||
|
||||
const prisma = new PrismaClient();
|
||||
import { verifyProof } from '../crypto/';
|
||||
|
||||
interface CollisionCheckResult {
|
||||
collision: boolean;
|
||||
@@ -21,152 +19,104 @@ interface CollisionCheckResult {
|
||||
*/
|
||||
|
||||
async function checkRLNCollision(roomId: string, message: MessageI): Promise<CollisionCheckResult> {
|
||||
return new Promise((res) => {
|
||||
prisma.rooms
|
||||
.findFirst({
|
||||
where: { roomId },
|
||||
include: {
|
||||
epochs: {
|
||||
where: { epoch: String(message.epoch) },
|
||||
include: {
|
||||
messages: {
|
||||
where: { messageId: message.messageId }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
.then((oldMessage) => {
|
||||
if (!message.proof) {
|
||||
throw new Error('Proof not provided');
|
||||
}
|
||||
const oldMessage: MessageI | null = await findRoomWithMessageId(roomId, message);
|
||||
|
||||
if (
|
||||
!oldMessage ||
|
||||
!oldMessage?.epochs[0]?.messages ||
|
||||
!oldMessage?.epochs[0]?.messages[0] ||
|
||||
!oldMessage?.epochs[0]?.messages[0]?.proof
|
||||
) {
|
||||
console.debug('No collision', oldMessage);
|
||||
res({ collision: false } as CollisionCheckResult);
|
||||
} else {
|
||||
const oldMessageProof = JSON.parse(
|
||||
oldMessage.epochs[0].messages[0].proof
|
||||
) as RLNFullProof;
|
||||
const oldMessagex2 = BigInt(oldMessageProof.snarkProof.publicSignals.x);
|
||||
const oldMessagey2 = BigInt(oldMessageProof.snarkProof.publicSignals.y);
|
||||
|
||||
let proof: RLNFullProof;
|
||||
|
||||
if (typeof message.proof === 'string') {
|
||||
proof = JSON.parse(message.proof) as RLNFullProof;
|
||||
} else {
|
||||
proof = message.proof;
|
||||
}
|
||||
const [x1, y1] = [
|
||||
BigInt(proof.snarkProof.publicSignals.x),
|
||||
BigInt(proof.snarkProof.publicSignals.y)
|
||||
];
|
||||
const [x2, y2] = [oldMessagex2, oldMessagey2];
|
||||
|
||||
const secret = shamirRecovery(x1, x2, y1, y2);
|
||||
|
||||
res({
|
||||
collision: true,
|
||||
secret,
|
||||
oldMessage: oldMessage.epochs[0].messages[0] as unknown as MessageI
|
||||
} as CollisionCheckResult);
|
||||
}
|
||||
})
|
||||
.catch((err) => console.error(err));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a message to a room.
|
||||
* @param {string} roomId - The ID of the room to add the message to.
|
||||
* @param {MessageI} message - The message to add to the room.
|
||||
* @returns {Promise<unknown>} - A promise that resolves when the message has been added to the room.
|
||||
*/
|
||||
|
||||
function addMessageToRoom(roomId: string, message: MessageI): Promise<unknown> {
|
||||
if (!message.epoch) {
|
||||
throw new Error('Epoch not provided');
|
||||
if (!message.proof) {
|
||||
throw new Error('Proof not provided');
|
||||
}
|
||||
return prisma.rooms.update({
|
||||
where: {
|
||||
roomId: roomId
|
||||
},
|
||||
data: {
|
||||
epochs: {
|
||||
create: {
|
||||
epoch: String(message.epoch),
|
||||
messages: {
|
||||
create: {
|
||||
message: message.message ? String(message.message) : '',
|
||||
messageId: message.messageId ? message.messageId.toString() : '',
|
||||
proof: JSON.stringify(message.proof),
|
||||
roomId: roomId
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!oldMessage?.proof) {
|
||||
console.debug('No collision', oldMessage);
|
||||
return { collision: false } as CollisionCheckResult;
|
||||
} else {
|
||||
let oldMessageProof: RLNFullProof;
|
||||
if (typeof oldMessage.proof === 'string') {
|
||||
oldMessageProof = JSON.parse(oldMessage.proof) as RLNFullProof;
|
||||
} else {
|
||||
oldMessageProof = oldMessage.proof;
|
||||
}
|
||||
});
|
||||
const oldMessagex2 = BigInt(oldMessageProof.snarkProof.publicSignals.x);
|
||||
const oldMessagey2 = BigInt(oldMessageProof.snarkProof.publicSignals.y);
|
||||
|
||||
let proof: RLNFullProof;
|
||||
|
||||
if (typeof message.proof === 'string') {
|
||||
proof = JSON.parse(message.proof) as RLNFullProof;
|
||||
} else {
|
||||
proof = message.proof;
|
||||
}
|
||||
const [x1, y1] = [
|
||||
BigInt(proof.snarkProof.publicSignals.x),
|
||||
BigInt(proof.snarkProof.publicSignals.y)
|
||||
];
|
||||
const [x2, y2] = [oldMessagex2, oldMessagey2];
|
||||
|
||||
const secret = shamirRecovery(x1, x2, y1, y2);
|
||||
|
||||
return {
|
||||
collision: true,
|
||||
secret,
|
||||
oldMessage: oldMessage
|
||||
} as CollisionCheckResult;
|
||||
}
|
||||
}
|
||||
|
||||
export interface createMessageResult {
|
||||
export interface validateMessageResult {
|
||||
success: boolean;
|
||||
message?: MessageI;
|
||||
idc?: string | bigint;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a message in a room
|
||||
* @param {string} roomId - The ID of the room in which the message will be added
|
||||
* @param {MessageI} message - The message to be created
|
||||
* @returns {Promise<createMessageResult>} - A result object which contains a boolean indicating whether the operation was successful
|
||||
*/
|
||||
export function createMessage(roomId: string, message: MessageI): Promise<createMessageResult> {
|
||||
return new Promise((resolve, reject) => {
|
||||
getRoomByID(roomId)
|
||||
.then(async (room) => {
|
||||
if (room) {
|
||||
await checkRLNCollision(roomId, message)
|
||||
.then((collisionResult) => {
|
||||
console.log('HERE', collisionResult);
|
||||
|
||||
if (!collisionResult.collision) {
|
||||
addMessageToRoom(roomId, message)
|
||||
.then(() => {
|
||||
console.log('Message added to room');
|
||||
return resolve({ success: true });
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error(`Couldn't add message room ${error}`);
|
||||
return reject({ success: false });
|
||||
});
|
||||
} else {
|
||||
console.debug('Collision found');
|
||||
const identityCommitment = getIdentityCommitmentFromSecret(collisionResult.secret!);
|
||||
removeIdentityFromRoom(identityCommitment.toString(), room)
|
||||
.then(() => {
|
||||
return reject({ success: false });
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error(`Couldn't remove identity from room ${error}`);
|
||||
});
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error(`Error getting room: ${error}`);
|
||||
return reject({ success: false });
|
||||
});
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error(`Error getting room: ${error}`);
|
||||
return reject({ success: false });
|
||||
});
|
||||
});
|
||||
async function handleCollision(
|
||||
room: RoomI,
|
||||
message: MessageI,
|
||||
collisionResult: CollisionCheckResult
|
||||
): Promise<validateMessageResult> {
|
||||
const roomId = room.roomId.toString();
|
||||
if (!collisionResult.collision) {
|
||||
try {
|
||||
await createMessageInRoom(roomId, message);
|
||||
console.debug(
|
||||
`Message added to room: ${
|
||||
typeof message.message === 'string'
|
||||
? message.message.slice(0, 10)
|
||||
: JSON.stringify(message.message).slice(0, 10)
|
||||
}...`
|
||||
);
|
||||
return { success: true };
|
||||
} catch (error) {
|
||||
console.error(`Couldn't add message room ${error}`);
|
||||
return { success: false };
|
||||
}
|
||||
} else {
|
||||
console.warn('Collision found');
|
||||
const identityCommitment = getIdentityCommitmentFromSecret(collisionResult.secret!);
|
||||
try {
|
||||
await removeIdentityFromRoom(identityCommitment.toString(), room);
|
||||
return { success: false, idc: identityCommitment.toString() };
|
||||
} catch (error) {
|
||||
console.error(`Couldn't remove identity from room ${error}`);
|
||||
}
|
||||
}
|
||||
return { success: false };
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates a message and adds it to the room if it is valid.
|
||||
* @param {RoomI} room - The room which the message will be added
|
||||
* @param {MessageI} message - The message to be created
|
||||
* @returns {Promise<validateMessageResult>} - A result object which contains a boolean indicating whether the operation was successful
|
||||
*/
|
||||
export async function validateMessage(
|
||||
room: RoomI,
|
||||
message: MessageI
|
||||
): Promise<validateMessageResult> {
|
||||
const roomId = room.roomId.toString();
|
||||
const validProof: boolean = await verifyProof(room, message);
|
||||
if (validProof) {
|
||||
const collisionResult = await checkRLNCollision(roomId, message);
|
||||
const result = await handleCollision(room, message, collisionResult);
|
||||
return result;
|
||||
}
|
||||
return { success: false };
|
||||
}
|
||||
|
||||
23
src/data/utils.ts
Normal file
23
src/data/utils.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
/**
|
||||
* The sanitizeIDC function takes a string and returns a string.
|
||||
* The string is converted to a BigInt and then back to a string.
|
||||
* If the string has no loss of precision, it is returned.
|
||||
* Otherwise, an error is thrown.
|
||||
*
|
||||
* @param {string} idc - The string to be sanitized.
|
||||
* @returns {string} - The sanitized string if it has no loss of precision.
|
||||
* @throws {Error} - Throws an error if the string cannot be converted to a BigInt or if it loses precision.
|
||||
*/
|
||||
export function sanitizeIDC(idc: string): string {
|
||||
try {
|
||||
const tempBigInt = BigInt(idc);
|
||||
const tempString = tempBigInt.toString();
|
||||
if (idc === tempString) {
|
||||
return idc;
|
||||
} else {
|
||||
throw new Error('Invalid IDC provided.');
|
||||
}
|
||||
} catch (error) {
|
||||
throw new Error('Invalid IDC provided.');
|
||||
}
|
||||
}
|
||||
@@ -3,15 +3,15 @@ import { PrismaClient } from '@prisma/client';
|
||||
import { serverConfig } from '../config/serverConfig';
|
||||
import { genClaimCodeArray, pp } from '../utils';
|
||||
import {
|
||||
getRoomByID,
|
||||
getRoomsByIdentity,
|
||||
findRoomById,
|
||||
findRoomsByIdentity,
|
||||
findClaimCode,
|
||||
updateClaimCode,
|
||||
updateRoomIdentities,
|
||||
findUpdatedRooms,
|
||||
createRoom,
|
||||
createSystemMessages
|
||||
} from '../data/db';
|
||||
} from '../data/db/';
|
||||
import { MessageI, RoomI } from 'discreetly-interfaces';
|
||||
import { RLNFullProof } from 'rlnjs';
|
||||
|
||||
@@ -47,14 +47,11 @@ export function initEndpoints(app: Express, adminAuth: RequestHandler) {
|
||||
} else {
|
||||
const requestRoomId = req.params.id ?? '0';
|
||||
pp(String('Express: fetching room info for ' + req.params.id));
|
||||
getRoomByID(requestRoomId)
|
||||
findRoomById(requestRoomId)
|
||||
.then((room: RoomI) => {
|
||||
if (!room) {
|
||||
// This is set as a timeout to prevent someone from trying to brute force room ids
|
||||
setTimeout(
|
||||
() => res.status(500).json({ error: 'Internal Server Error' }),
|
||||
1000
|
||||
);
|
||||
setTimeout(() => res.status(500).json({ error: 'Internal Server Error' }), 1000);
|
||||
} else {
|
||||
const {
|
||||
roomId,
|
||||
@@ -103,7 +100,7 @@ export function initEndpoints(app: Express, adminAuth: RequestHandler) {
|
||||
['/rooms/:idc', '/api/rooms/:idc'],
|
||||
asyncHandler(async (req: Request, res: Response) => {
|
||||
try {
|
||||
res.status(200).json(await getRoomsByIdentity(req.params.idc));
|
||||
res.status(200).json(await findRoomsByIdentity(req.params.idc));
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
res.status(500).json({ error: 'Internal Server Error' });
|
||||
@@ -135,9 +132,7 @@ export function initEndpoints(app: Express, adminAuth: RequestHandler) {
|
||||
const parsedBody: JoinData = req.body as JoinData;
|
||||
|
||||
if (!parsedBody.code || !parsedBody.idc) {
|
||||
res
|
||||
.status(400)
|
||||
.json({ message: '{code: string, idc: string} expected' });
|
||||
res.status(400).json({ message: '{code: string, idc: string} expected' });
|
||||
}
|
||||
const { code, idc } = parsedBody;
|
||||
console.debug('Invite Code:', code);
|
||||
@@ -162,7 +157,9 @@ export function initEndpoints(app: Express, adminAuth: RequestHandler) {
|
||||
roomIds: updatedRooms.map((room: RoomI) => room.roomId)
|
||||
});
|
||||
} else {
|
||||
res.status(400).json({ message: `No rooms found or identity already exists in ${String(roomIds)}` });
|
||||
res
|
||||
.status(400)
|
||||
.json({ message: `No rooms found or identity already exists in ${String(roomIds)}` });
|
||||
}
|
||||
})
|
||||
);
|
||||
@@ -235,7 +232,7 @@ export function initEndpoints(app: Express, adminAuth: RequestHandler) {
|
||||
.then((result) => {
|
||||
if (result) {
|
||||
// TODO should return roomID and claim codes if they are generated
|
||||
res.status(200).json({ message: 'Room created successfully' });
|
||||
res.status(200).json({ message: 'Room created successfully', roomId: result });
|
||||
} else {
|
||||
res.status(500).json({ error: 'Internal Server Error' });
|
||||
}
|
||||
@@ -320,30 +317,33 @@ export function initEndpoints(app: Express, adminAuth: RequestHandler) {
|
||||
return await prisma.rooms.findMany(query).then((rooms) => {
|
||||
const roomIds = rooms.map((room) => room.id);
|
||||
const createCodes = codes.map((code) => {
|
||||
return prisma.claimCodes.create({
|
||||
data: {
|
||||
claimcode: code.claimcode,
|
||||
claimed: false,
|
||||
roomIds: roomIds
|
||||
}
|
||||
}).then((newCode) => {
|
||||
const updatePromises = rooms.map((room) => {
|
||||
return prisma.rooms.update({
|
||||
where: {
|
||||
roomId: room.roomId
|
||||
},
|
||||
data: {
|
||||
claimCodeIds: {
|
||||
push: newCode.id
|
||||
return prisma.claimCodes
|
||||
.create({
|
||||
data: {
|
||||
claimcode: code.claimcode,
|
||||
claimed: false,
|
||||
roomIds: roomIds
|
||||
}
|
||||
})
|
||||
.then((newCode) => {
|
||||
const updatePromises = rooms.map((room) => {
|
||||
return prisma.rooms.update({
|
||||
where: {
|
||||
roomId: room.roomId
|
||||
},
|
||||
data: {
|
||||
claimCodeIds: {
|
||||
push: newCode.id
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
return Promise.all(updatePromises);
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error(err);
|
||||
res.status(500).json({ error: 'Internal Server Error' });
|
||||
});
|
||||
return Promise.all(updatePromises);
|
||||
}).catch((err) => {
|
||||
console.error(err);
|
||||
res.status(500).json({ error: 'Internal Server Error' });
|
||||
});
|
||||
});
|
||||
|
||||
return Promise.all(createCodes)
|
||||
@@ -368,50 +368,46 @@ export function initEndpoints(app: Express, adminAuth: RequestHandler) {
|
||||
* }
|
||||
*/
|
||||
|
||||
app.post(
|
||||
['/room/:roomId/addcode', '/api/room/:roomId/addcode'],
|
||||
adminAuth,
|
||||
(req, res) => {
|
||||
const { roomId } = req.params;
|
||||
const { numCodes } = req.body as { numCodes: number };
|
||||
const codes = genClaimCodeArray(numCodes);
|
||||
app.post(['/room/:roomId/addcode', '/api/room/:roomId/addcode'], adminAuth, (req, res) => {
|
||||
const { roomId } = req.params;
|
||||
const { numCodes } = req.body as { numCodes: number };
|
||||
const codes = genClaimCodeArray(numCodes);
|
||||
|
||||
prisma.rooms
|
||||
.findUnique({
|
||||
where: { roomId: roomId },
|
||||
include: { claimCodes: true }
|
||||
})
|
||||
.then((room) => {
|
||||
if (!room) {
|
||||
res.status(404).json({ error: 'Room not found' });
|
||||
return;
|
||||
}
|
||||
// Map over the codes array and create a claim code for each code
|
||||
const createCodes = codes.map((code) => {
|
||||
return prisma.claimCodes.create({
|
||||
data: {
|
||||
claimcode: code.claimcode,
|
||||
claimed: false,
|
||||
rooms: {
|
||||
connect: {
|
||||
roomId: roomId
|
||||
}
|
||||
prisma.rooms
|
||||
.findUnique({
|
||||
where: { roomId: roomId },
|
||||
include: { claimCodes: true }
|
||||
})
|
||||
.then((room) => {
|
||||
if (!room) {
|
||||
res.status(404).json({ error: 'Room not found' });
|
||||
return;
|
||||
}
|
||||
// Map over the codes array and create a claim code for each code
|
||||
const createCodes = codes.map((code) => {
|
||||
return prisma.claimCodes.create({
|
||||
data: {
|
||||
claimcode: code.claimcode,
|
||||
claimed: false,
|
||||
rooms: {
|
||||
connect: {
|
||||
roomId: roomId
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return Promise.all(createCodes);
|
||||
})
|
||||
.then(() => {
|
||||
res.status(200).json({ message: 'Claim codes added successfully' });
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error(err);
|
||||
res.status(500).json({ error: 'Internal Server Error' });
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
return Promise.all(createCodes);
|
||||
})
|
||||
.then(() => {
|
||||
res.status(200).json({ message: 'Claim codes added successfully' });
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error(err);
|
||||
res.status(500).json({ error: 'Internal Server Error' });
|
||||
});
|
||||
});
|
||||
|
||||
// This code fetches the claim codes from the database.
|
||||
|
||||
|
||||
@@ -1 +1,16 @@
|
||||
import { RoomI } from 'discreetly-interfaces';
|
||||
|
||||
export interface CodeStatus {
|
||||
claimed: boolean;
|
||||
roomIds: string[];
|
||||
}
|
||||
|
||||
export interface RoomsFromClaimCode {
|
||||
roomIds: string[];
|
||||
}
|
||||
|
||||
export interface RoomWithSecretsI extends RoomI {
|
||||
bandadaAPIKey: string;
|
||||
}
|
||||
|
||||
export type userCountI = Record<string, number>;
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
import { MessageI, RoomI } from 'discreetly-interfaces';
|
||||
import { Socket, Server as SocketIOServer } from 'socket.io';
|
||||
import verifyProof from '../crypto/verifier';
|
||||
import { getRoomByID, createSystemMessages } from '../data/db';
|
||||
import { findRoomById } from '../data/db/';
|
||||
import { pp } from '../utils';
|
||||
import { createMessage } from '../data/messages';
|
||||
import type { createMessageResult } from '../data/messages';
|
||||
import { validateMessage } from '../data/messages';
|
||||
import type { validateMessageResult } from '../data/messages';
|
||||
const userCount: Record<string, number> = {};
|
||||
|
||||
export function websocketSetup(io: SocketIOServer) {
|
||||
@@ -12,75 +11,43 @@ export function websocketSetup(io: SocketIOServer) {
|
||||
pp('SocketIO: a user connected', 'debug');
|
||||
|
||||
socket.on('validateMessage', async (msg: MessageI) => {
|
||||
pp({
|
||||
'VALIDATING MESSAGE ID': String(msg.roomId).slice(0, 11),
|
||||
'MSG:': msg.message
|
||||
});
|
||||
let validProof: boolean;
|
||||
if (typeof msg.message == 'string') {
|
||||
if (msg.message.replaceAll(' ', '') == '') {
|
||||
pp('INVALID MESSAGE, EMPTY', 'warn');
|
||||
try {
|
||||
const room: RoomI | null = await findRoomById(String(msg.roomId));
|
||||
if (!room) {
|
||||
pp('INVALID ROOM', 'warn');
|
||||
return;
|
||||
}
|
||||
const validMessage: validateMessageResult = await validateMessage(room, msg);
|
||||
if (validMessage.success) {
|
||||
// Send messages to only users who are listening to that room
|
||||
io.to(room.roomId.toString()).emit('messageBroadcast', msg);
|
||||
} else {
|
||||
pp('INVALID MESSAGE', 'warn');
|
||||
return;
|
||||
}
|
||||
} catch (err) {
|
||||
pp(err, 'error');
|
||||
}
|
||||
await getRoomByID(String(msg.roomId))
|
||||
.then((room: RoomI) => {
|
||||
if (!room) {
|
||||
pp('INVALID ROOM', 'warn');
|
||||
return;
|
||||
}
|
||||
verifyProof(msg, room)
|
||||
.then(async (v) => {
|
||||
console.log('validProof', v);
|
||||
validProof = v;
|
||||
// TODO import createMessageResult, and broadcast the idc and message ID that were removed to those room users
|
||||
const validMessage: createMessageResult = await createMessage(
|
||||
String(msg.roomId),
|
||||
msg
|
||||
);
|
||||
if (!validProof || !validMessage.success) {
|
||||
pp('INVALID MESSAGE', 'warn');
|
||||
return;
|
||||
}
|
||||
io.emit('messageBroadcast', msg);
|
||||
})
|
||||
.catch((err) => {
|
||||
err;
|
||||
});
|
||||
})
|
||||
.catch((err) => pp(err, 'error'));
|
||||
});
|
||||
|
||||
socket.on('disconnect', () => {
|
||||
pp('SocketIO: user disconnected');
|
||||
});
|
||||
|
||||
socket.on('joinRoom', (roomID: bigint) => {
|
||||
const id = roomID.toString();
|
||||
userCount[id] = userCount[id] ? userCount[id] + 1 : 1;
|
||||
void socket.join(id);
|
||||
io.to(id).emit('Members', userCount[id] ? userCount[id] : 0);
|
||||
socket.on('joiningRoom', (roomID: string) => {
|
||||
userCount[roomID] = userCount[roomID] ? userCount[roomID] + 1 : 1;
|
||||
void socket.join(roomID);
|
||||
io.to(roomID).emit('Members', userCount[roomID] ? userCount[roomID] : 0);
|
||||
});
|
||||
|
||||
socket.on('leaveRoom', (roomID: bigint) => {
|
||||
const id = roomID.toString();
|
||||
userCount[id] = userCount[id] ? userCount[id] - 1 : 0;
|
||||
io.to(id).emit('Members', userCount[id] ? userCount[id] : 0);
|
||||
socket.on('leavingRoom', (roomID: string) => {
|
||||
void socket.leave(roomID);
|
||||
userCount[roomID] = userCount[roomID] ? userCount[roomID] - 1 : 0;
|
||||
io.to(roomID).emit('Members', userCount[roomID] ? userCount[roomID] : 0);
|
||||
});
|
||||
|
||||
socket.on('systemMessage', (msg: string, roomID: bigint) => {
|
||||
const id = roomID.toString();
|
||||
createSystemMessages(msg, id)
|
||||
.then(() => {
|
||||
if (roomID) {
|
||||
io.to(id).emit('systemMessage', msg);
|
||||
} else {
|
||||
io.emit('systemMessage', msg);
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
pp(err, 'error');
|
||||
});
|
||||
});
|
||||
setInterval(() => {
|
||||
io.emit('TotalMembers', io.engine.clientsCount);
|
||||
}, 10000);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -30,13 +30,13 @@ const room = {
|
||||
type: 'PUBLIC_CHAT'
|
||||
};
|
||||
|
||||
const roomByIdTest = genId(serverConfig.id as bigint, room.roomName).toString();
|
||||
let roomByIdTest: string;
|
||||
let testCode = '';
|
||||
const testIdentity = randBigint();
|
||||
const username = 'admin';
|
||||
const password = process.env.PASSWORD;
|
||||
|
||||
describe('Endpoints should all work hopefully', () => {
|
||||
describe('Endpoints should all work', () => {
|
||||
test('It should respond with server info', async () => {
|
||||
await request(_app)
|
||||
.get('/')
|
||||
@@ -59,7 +59,12 @@ describe('Endpoints should all work hopefully', () => {
|
||||
|
||||
.then((res) => {
|
||||
try {
|
||||
expect(res.body).toEqual({ message: 'Room created successfully' });
|
||||
console.log(res);
|
||||
expect(res.status).toEqual(200);
|
||||
const result = res.body;
|
||||
expect(res.body.message).toEqual('Room created successfully');
|
||||
expect(result.roomId).toBeDefined();
|
||||
roomByIdTest = result.roomId;
|
||||
} catch (error) {
|
||||
console.warn('POST /room/add - ' + error);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user