From 97c12f1c3318d2b1b63dbcb00b2d61879554ae59 Mon Sep 17 00:00:00 2001 From: AtHeartEngineer Date: Tue, 15 Aug 2023 23:52:14 -0400 Subject: [PATCH] checkpoint - untested - major refactor to stores --- package-lock.json | 8 +- package.json | 2 +- src/lib/components/card.svelte | 2 +- src/lib/crypto/prover.ts | 5 +- src/lib/data/stores.ts | 54 ------ src/lib/defaults.ts | 14 ++ src/lib/services/api.ts | 6 +- src/lib/services/server.ts | 4 +- src/lib/stores/index.ts | 60 ++++++ src/lib/stores/servers.ts | 33 ++++ src/lib/{data => stores}/storeFactory.ts | 0 src/lib/types/index.ts | 9 +- src/lib/utils.ts | 65 ++----- src/lib/utils/index.ts | 3 + src/lib/{ => utils}/rateLimit.ts | 52 ++--- src/lib/utils/updateServers.ts | 45 +++++ src/routes/+layout.svelte | 4 +- src/routes/+page.svelte | 2 +- src/routes/AppFooter.svelte | 2 +- src/routes/AppHeader.svelte | 2 +- src/routes/chat/+layout.svelte | 8 +- src/routes/chat/Chat.svelte | 211 --------------------- src/routes/chat/ChatRoom.svelte | 79 ++------ src/routes/chat/Sidebar.svelte | 164 ++-------------- src/routes/identity/+page.svelte | 14 +- src/routes/identity/BackupIdentity.svelte | 4 +- src/routes/identity/DeleteIdentity.svelte | 5 +- src/routes/identity/RestoreIdentity.svelte | 4 +- src/routes/signup/+layout.svelte | 2 +- src/routes/signup/Join.svelte | 6 +- src/routes/testing/+page.svelte | 10 +- 31 files changed, 276 insertions(+), 603 deletions(-) delete mode 100644 src/lib/data/stores.ts create mode 100644 src/lib/stores/index.ts create mode 100644 src/lib/stores/servers.ts rename src/lib/{data => stores}/storeFactory.ts (100%) create mode 100644 src/lib/utils/index.ts rename src/lib/{ => utils}/rateLimit.ts (54%) create mode 100644 src/lib/utils/updateServers.ts delete mode 100644 src/routes/chat/Chat.svelte diff --git a/package-lock.json b/package-lock.json index 3fbe3ac..1eb1a92 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,7 +10,7 @@ "dependencies": { "@semaphore-protocol/group": "^3.10.1", "@semaphore-protocol/identity": "^3.10.1", - "discreetly-interfaces": "^0.1.32", + "discreetly-interfaces": "^0.1.34", "libsodium-wrappers": "^0.7.11", "poseidon-lite": "^0.2.0", "qr-scanner": "^1.4.2", @@ -1905,9 +1905,9 @@ } }, "node_modules/discreetly-interfaces": { - "version": "0.1.32", - "resolved": "https://registry.npmjs.org/discreetly-interfaces/-/discreetly-interfaces-0.1.32.tgz", - "integrity": "sha512-qATarb8KuU5IiZ1CQTGbERSL7y3fLhVFFdQ7Yvofeu47lL4e8EFzmzabJYXRabA+QgtTAPqbrazJ7TVXfwZXUw==", + "version": "0.1.34", + "resolved": "https://registry.npmjs.org/discreetly-interfaces/-/discreetly-interfaces-0.1.34.tgz", + "integrity": "sha512-7purPOWOowVH44ebdweBdZ4z2RsBQy5/H7xi6PdsHkaw1xwg8u3Ev2US5EdavP1igZ+SzebJdK8jT0ZTjzX8Kg==", "dependencies": { "poseidon-lite": "^0.2.0", "rlnjs": "^3.1.4" diff --git a/package.json b/package.json index 0625347..5aba903 100644 --- a/package.json +++ b/package.json @@ -44,7 +44,7 @@ "dependencies": { "@semaphore-protocol/group": "^3.10.1", "@semaphore-protocol/identity": "^3.10.1", - "discreetly-interfaces": "^0.1.32", + "discreetly-interfaces": "^0.1.34", "libsodium-wrappers": "^0.7.11", "poseidon-lite": "^0.2.0", "qr-scanner": "^1.4.2", diff --git a/src/lib/components/card.svelte b/src/lib/components/card.svelte index a6ec362..1ce4c65 100644 --- a/src/lib/components/card.svelte +++ b/src/lib/components/card.svelte @@ -1,6 +1,6 @@ diff --git a/src/lib/crypto/prover.ts b/src/lib/crypto/prover.ts index d2f7d2f..d474c5e 100644 --- a/src/lib/crypto/prover.ts +++ b/src/lib/crypto/prover.ts @@ -3,9 +3,10 @@ import { Group } from '@semaphore-protocol/group'; import getMessageHash from './messageHasher'; import getRateCommitmentHash from './rateCommitmentHasher'; import type { Identity } from '@semaphore-protocol/identity'; -import type { MessageI, RoomI } from 'discreetly-interfaces'; +import type { MessageI } from 'discreetly-interfaces'; +import type { RoomI } from '$lib/types'; import type { RLNFullProof, MerkleProof } from 'rlnjs'; -import { getMerkleProof } from '../services/bandada'; +import { getMerkleProof } from '$lib//services/bandada'; const wasmPath = '/rln/circuit.wasm'; const zkeyPath = '/rln/final.zkey'; diff --git a/src/lib/data/stores.ts b/src/lib/data/stores.ts deleted file mode 100644 index 2862e21..0000000 --- a/src/lib/data/stores.ts +++ /dev/null @@ -1,54 +0,0 @@ -import type { ConfigurationI, IdentityStoreI } from '../types/'; -import { IdentityStoreE } from '../types/'; -import type { ServerI } from 'discreetly-interfaces'; -import { storable, sessionable } from './storeFactory'; - -/** - * @description List of server URLs, this is mainly for bootstraping the app - */ -export const serverListStore = storable([] as string[], 'servers'); - -interface serverDataStoreI { - [key: string]: ServerI; -} - -/** - * @description Server Metadata keyed by the server's URL - */ -export const serverDataStore = storable({} as serverDataStoreI, 'serverData'); - -/** - * @description The URL of the currently selected server - */ -export const selectedServer = storable('', 'selectedServer'); - -/** - * @description The room ID of the currently selected room - */ -export const selectedRoom = storable('', 'selectedRoom'); - -export const roomsStore = storable( - { - selectedRoomId: undefined, - roomsData: {} - }, - 'roomsStore' -); - -export const configStore = storable( - { - signUpStatus: { - inviteAccepted: false, - identityBackedUp: false - }, - identityStore: IdentityStoreE.NO_IDENTITY - } as ConfigurationI, - 'signupStatus' -); - -// Session store (removed after the session is cleared) of the last 500 messages or so of each room the user participates in; rooms they don't have selected will not be updated -export const messageStore = sessionable({}, 'messages'); - -// Stores the user's identity // TODO THIS NEEDS TO BE AN ENCRYPTED SEMAPHORE IDENTITY IN THE FUTURE -export const identityStore = storable({} as IdentityStoreI, 'identity'); -serverListStore; diff --git a/src/lib/defaults.ts b/src/lib/defaults.ts index e69de29..30e10f1 100644 --- a/src/lib/defaults.ts +++ b/src/lib/defaults.ts @@ -0,0 +1,14 @@ +import type { ConfigurationI } from './types'; +import { IdentityStoreE } from './types'; + +const discreetlyURL = 'https://server.discreetly.chat/'; + +export const defaultServers = { discreetlyURL: { name: 'Discreetly Server', url: discreetlyURL } }; + +export const configDefaults: ConfigurationI = { + signUpStatus: { + inviteAccepted: false, + identityBackedUp: false + }, + identityStore: IdentityStoreE.NO_IDENTITY +}; diff --git a/src/lib/services/api.ts b/src/lib/services/api.ts index e36ac2f..10b8eab 100644 --- a/src/lib/services/api.ts +++ b/src/lib/services/api.ts @@ -25,8 +25,7 @@ function joinUrlParts(parts: string[] | string): string { /** * @description - makes a get request to the api - * @param {string} baseUrl - the base url of the api - * @param {string} endpoint - the endpoint to be added to the base url + * @param {string[] | string} urlParts - the url parts to be joined to form the url * @returns {object} - the response from the api * @throws {Error} - if the request fails */ @@ -46,8 +45,7 @@ export async function get(urlParts: string[] | string): Promise { /** * @description - makes a get request to the api - * @param {string} baseUrl - the base url of the api - * @param {string} endpoint - the endpoint to be added to the base url + * @param {string[] | string} urlParts - the url parts to be joined to form the url * @param {object} data - the data to be sent to the api * @returns {object} - the response from the api * @throws {Error} - if the request fails diff --git a/src/lib/services/server.ts b/src/lib/services/server.ts index 3c1e83a..0edb17a 100644 --- a/src/lib/services/server.ts +++ b/src/lib/services/server.ts @@ -1,4 +1,6 @@ -import type { RoomI, ServerI } from 'discreetly-interfaces'; +import type { ServerI } from 'discreetly-interfaces'; +import type { RoomI } from '$lib/types'; + import { get, post } from './api'; export async function getIdentityRoomIds(server: string, idCommitment: string): Promise { diff --git a/src/lib/stores/index.ts b/src/lib/stores/index.ts new file mode 100644 index 0000000..b83b0b1 --- /dev/null +++ b/src/lib/stores/index.ts @@ -0,0 +1,60 @@ +import type { ConfigurationI, IdentityStoreI } from '$lib/types'; +import type { MessageI, ServerI } from 'discreetly-interfaces'; +import type { RoomI } from '$lib/types'; +import { storable, sessionable } from './storeFactory'; +import { configDefaults } from '$lib/defaults'; + +export interface serverStoreI { + [key: string]: ServerI; +} + +interface selectedRoomStoreI { + [key: string]: string; +} + +interface roomStoreI { + [key: string]: RoomI; +} + +interface messageStoreI { + [key: string]: MessageI[]; +} + +/* ------------------ Server State ------------------*/ +/** + * @description Server Metadata keyed by the server's URL + */ +export const serverStore = storable({} as serverStoreI, 'serverData'); + +/** + * @description The URL of the currently selected server + */ +export const selectedServer = storable('' as string, 'selectedServer'); + +/* ------------------ Room State ------------------*/ +/** + * @description Room information keyed by the roomId + */ +export const roomsStore = storable({} as roomStoreI, 'roomsStore'); + +/** + * @description The room ID of the currently selected room keyed by the server's URL + */ +export const selectedRoom = storable({} as selectedRoomStoreI, 'selectedRoom'); + +/** + * @description Session store (removed after the session is cleared) of the last 500 messages or so of each room the user participates in; rooms they don't have selected will not be updated + */ +export const messageStore = sessionable({} as messageStoreI, 'messages'); + +/* ------------------ Misc State ------------------*/ +/** + * @description Configuration and misc state + */ +export const configStore = storable(configDefaults as ConfigurationI, 'configStore'); + +//TODO! This needs to be encrypted +/** + * @description Identity store, this is the user's identity + */ +export const identityStore = storable({} as IdentityStoreI, 'identity'); diff --git a/src/lib/stores/servers.ts b/src/lib/stores/servers.ts new file mode 100644 index 0000000..e160ca0 --- /dev/null +++ b/src/lib/stores/servers.ts @@ -0,0 +1,33 @@ +import { get, type Writable } from 'svelte/store'; +import type { serverStoreI } from '.'; +import { serverStore, roomsStore } from '.'; +import { getServerData } from '$lib/services/server'; +import type { RoomI } from '$lib/types'; + +export function getServerList(store: Writable = serverStore): string[] { + return Object.keys(get(store)); +} + +export function getServerRooms(url: string, store: Writable = serverStore): RoomI[] { + let roomIds = get(store)[url].rooms; + if (!roomIds) { + roomIds = []; + } + return roomIds.map((roomId) => { + return get(roomsStore)[roomId]; + }) as RoomI[]; +} + +export async function updateServer( + url: string, + store: Writable = serverStore +): Promise { + const oldServerStore = get(store); + getServerData(url).then((newServerData) => { + const newServerStore = { + ...oldServerStore, + [url]: newServerData + }; + store.set(newServerStore); + }); +} diff --git a/src/lib/data/storeFactory.ts b/src/lib/stores/storeFactory.ts similarity index 100% rename from src/lib/data/storeFactory.ts rename to src/lib/stores/storeFactory.ts diff --git a/src/lib/types/index.ts b/src/lib/types/index.ts index cba2fa0..37a7d3c 100644 --- a/src/lib/types/index.ts +++ b/src/lib/types/index.ts @@ -1,4 +1,5 @@ import type { Identity } from '@semaphore-protocol/identity'; +import type { RoomI as RI } from 'discreetly-interfaces'; export interface ButtonI { link: string; @@ -8,14 +9,14 @@ export interface ButtonI { // For rooms where a user has a unique identity export interface RoomIdentityI { - [key: string]: Identity; // The key is the room id (bigint) as a string + [key: string]: Identity; // The key is the roomId (bigint) as a string } export interface IdentityStoreI { identity: Identity; } -export interface ServerListI { +export interface ServerUrlI { name: string; url: string; } @@ -41,3 +42,7 @@ export interface ConfigurationI { signUpStatus: SignUpStatusI; identityStore: IdentityStoreE; } + +export interface RoomI extends RI { + server?: string; +} diff --git a/src/lib/utils.ts b/src/lib/utils.ts index 8445ca2..ad02828 100644 --- a/src/lib/utils.ts +++ b/src/lib/utils.ts @@ -1,48 +1,11 @@ -import type { RoomI, ServerI } from 'discreetly-interfaces'; -import { identityStore, serverDataStore, serverListStore, roomsStore } from './data/stores'; +import type { RoomI } from '$lib/types'; +import { identityStore, serverStore, roomsStore } from '$lib/stores'; import { get } from 'svelte/store'; -import { getIdentityRoomIds, getRoomById, getServerData } from './services/server'; -import type { ServerListI } from './types'; +import { getIdentityRoomIds, getRoomById } from '$lib/services/server'; -const defaultServers = [ - { name: 'Discreetly Server', url: 'https://server.discreetly.chat/' }, - { name: 'Localhost', url: 'http://localhost:3001/api/' } as ServerListI -]; +// TODO! EVERYTHING IN THIS FILE SHOULD BE DEPRECATED AND MOVED TO PROPER LOCATIONS -export async function updateServers(): Promise<{ [key: string]: ServerI }> { - const serverList = get(serverListStore); - - // If the server list is empty or doesn't have the discreetly server, add the default servers - if ( - serverList.length < 1 || - !serverList.find((s: ServerListI) => s.name === 'Discreetly Server') - ) { - console.error('serverListStore is empty'); - serverListStore.set(defaultServers); - } - - const newServerData: { [key: string]: ServerI } = {}; - - await Promise.all( - serverList.map(async (server: ServerListI) => { - console.log(`Fetching server data from ${server.url}`); - const data = await getServerData(server.url); - console.log(`Setting server data for ${server.url}`); - if (data) { - newServerData[server.url] = { ...data }; - } - }) - ); - - serverDataStore.update((store: { [key: string]: ServerI } = {}) => ({ - ...store, - ...newServerData - })); - - return newServerData; -} - -export async function setRooms(server: string, roomIds: string[] = []): Promise { +export async function __setRooms(server: string, roomIds: string[] = []): Promise { const rooms: RoomI[] = []; for (const roomId of roomIds) { const result = await getRoomById(server, roomId); @@ -67,7 +30,7 @@ export async function setRooms(server: string, roomIds: string[] = []): Promise< return rooms.map((r: RoomI) => r.name); } -export async function setSelectedRoomId(selectedRoomId: string): Promise { +export async function __setSelectedRoomId(selectedRoomId: string): Promise { roomsStore.update(() => { const roomsStoreData = get(roomsStore); return { @@ -77,7 +40,7 @@ export async function setSelectedRoomId(selectedRoomId: string): Promise { }); } -export async function updateRooms( +export async function __updateRooms( selectedServer: string, roomIds: string[] = [] ): Promise { @@ -96,8 +59,8 @@ export async function updateRooms( rooms.forEach((r: RoomI) => { acceptedRoomNames = [...acceptedRoomNames, r.name]; }); - serverDataStore.update(() => { - const serverData = get(serverDataStore); + serverStore.update(() => { + const serverData = get(serverStore); serverData[selectedServer] = { ...serverData[selectedServer], rooms @@ -107,10 +70,10 @@ export async function updateRooms( return acceptedRoomNames; } -export async function updateSingleRoom(selectedServer: string, roomId: string) { +export async function __updateSingleRoom(selectedServer: string, roomId: string) { const room = await getRoomById(selectedServer, roomId); - serverDataStore.update(() => { - const serverData = get(serverDataStore); + serverStore.update(() => { + const serverData = get(serverStore); serverData[selectedServer] = { ...serverData[selectedServer], rooms: [room] @@ -119,13 +82,13 @@ export async function updateSingleRoom(selectedServer: string, roomId: string) { }); } -export function getServerForSelectedRoom(): any { +export function __getServerForSelectedRoom(): any { const roomsStoreData = get(roomsStore); const selectedServer = roomsStoreData.roomsData[roomsStoreData.selectedRoomId]?.server; return selectedServer; } -export function getRoomsForServer(selectedServer: string): [] { +export function __getRoomsForServer(selectedServer: string): [] { const roomsStoreData = get(roomsStore); const rooms: any = Object.values(roomsStoreData.roomsData); return rooms.filter((room: any) => room.server === selectedServer); diff --git a/src/lib/utils/index.ts b/src/lib/utils/index.ts new file mode 100644 index 0000000..8db7795 --- /dev/null +++ b/src/lib/utils/index.ts @@ -0,0 +1,3 @@ +import RateLimiter from './rateLimit'; + +export { RateLimiter }; diff --git a/src/lib/rateLimit.ts b/src/lib/utils/rateLimit.ts similarity index 54% rename from src/lib/rateLimit.ts rename to src/lib/utils/rateLimit.ts index df05436..007424a 100644 --- a/src/lib/rateLimit.ts +++ b/src/lib/utils/rateLimit.ts @@ -1,8 +1,21 @@ -class RateLimiter { - numberMessages: number; - milliSecondsPerEpoch: number; +export interface State { + currentEpoch: number; lastEpochMessageWasSent: number; remainingMessages: number; +} + +export interface EpochDetails { + epoch: number; + timestamp: number; // Unix epoch time + local: string; +} + +class RateLimiter { + private numberMessages: number; + private milliSecondsPerEpoch: number; + private lastEpochMessageWasSent: number; + private remainingMessages: number; + constructor(numberMessages: number, milliSecondsPerEpoch: number) { this.numberMessages = numberMessages; this.milliSecondsPerEpoch = milliSecondsPerEpoch; @@ -10,15 +23,11 @@ class RateLimiter { this.remainingMessages = this.numberMessages; } - public getCurrentEpoch() { - return Math.floor(new Date().getTime() / this.milliSecondsPerEpoch); + getCurrentEpoch(): number { + return Math.floor(Date.now() / this.milliSecondsPerEpoch); } - public updateState(): { - currentEpoch: number; - lastEpochMessageWasSent: number; - remainingMessages: number; - } { + private updateState(): State { const currentEpoch = this.getCurrentEpoch(); if (currentEpoch > this.lastEpochMessageWasSent) { this.remainingMessages = this.numberMessages; @@ -31,31 +40,22 @@ class RateLimiter { }; } - public getRemainingMessages() { + public getRemainingMessages(): number { this.updateState(); return this.remainingMessages; } - // Returns the number of remaining messages - // -1 means you have no more messages left public useMessage(): number { this.updateState(); if (this.remainingMessages > 0) { this.remainingMessages--; - return this.remainingMessages; - } else { - return -1; } + return this.remainingMessages > 0 ? this.remainingMessages : -1; } - public getEpochFromTimestamp(timestamp: number): { - epoch: number; - timestamp: number; // Unix epoch time - local: string; - } { - const _timestamp = timestamp ? new Date(timestamp).toString() : Date.now().toString(); - const epoch = Math.floor(Number(_timestamp) / this.milliSecondsPerEpoch); - const local = new Date(_timestamp).toLocaleString('en-US', { + public getEpochFromTimestamp(timestamp: number = Date.now()): EpochDetails { + const epoch = Math.floor(timestamp / this.milliSecondsPerEpoch); + const local = new Date(timestamp).toLocaleString('en-US', { hour: 'numeric', minute: 'numeric', hour12: true @@ -63,8 +63,8 @@ class RateLimiter { return { epoch, timestamp, local }; } - public getTimestampFromEpoch(epoch?: number | bigint): string { - const time = epoch ? Number(epoch) * this.milliSecondsPerEpoch : new Date(); + public getTimestampFromEpoch(epoch: number = this.getCurrentEpoch()): string { + const time = epoch * this.milliSecondsPerEpoch; return new Date(time).toLocaleString('en-US', { hour: 'numeric', minute: 'numeric', diff --git a/src/lib/utils/updateServers.ts b/src/lib/utils/updateServers.ts new file mode 100644 index 0000000..ebfca59 --- /dev/null +++ b/src/lib/utils/updateServers.ts @@ -0,0 +1,45 @@ +import { defaultServers } from '$lib/defaults'; +import { getServerData } from '$lib/services/server'; +import { serverStore } from '$lib/stores'; +import type { ServerUrlI } from '$lib/types'; +import type { ServerI } from 'discreetly-interfaces'; +import { get } from 'svelte/store'; + +// Function to update a single server +export async function updateServer(server: ServerUrlI): Promise { + console.log(`Fetching server data from ${server.url}`); + const data = await getServerData(server.url); + console.log(`Setting server data for ${server.url}`); + if (data) { + serverStore.update((store: { [key: string]: ServerI } = {}) => ({ + ...store, + [server.url]: data + })); + return data; + } + return null; +} + +// Function to update all servers +export async function updateAllServers(): Promise<{ [key: string]: ServerI }> { + const serverList = Object.keys(get(serverStore)); + + // If the server list is empty or doesn't have the discreetly server, add the default servers + if (serverList.length < 1) { + console.error('serverStore is empty, populating with'); + serverStore.set(defaultServers); + } + + const newServerData: { [key: string]: ServerI } = {}; + + await Promise.all( + serverList.map(async (server: ServerUrlI) => { + const data = await updateServer(server); + if (data) { + newServerData[server.url] = data; + } + }) + ); + + return newServerData; +} diff --git a/src/routes/+layout.svelte b/src/routes/+layout.svelte index 25a922d..7b476c8 100644 --- a/src/routes/+layout.svelte +++ b/src/routes/+layout.svelte @@ -8,8 +8,8 @@ import AppHeader from './AppHeader.svelte'; import AppFooter from './AppFooter.svelte'; import Loading from '$lib/components/loading.svelte'; - import { serverListStore, selectedServer } from '$lib/data/stores'; - import { updateServers } from '$lib/utils'; + import { serverListStore, selectedServer } from '$lib/stores'; + import { updateServers } from '$lib/stores/servers'; // Hack to get BigInt <-> JSON compatibility (BigInt.prototype as any).toJSON = function () { diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte index d504b90..e980307 100644 --- a/src/routes/+page.svelte +++ b/src/routes/+page.svelte @@ -1,5 +1,5 @@ diff --git a/src/routes/AppHeader.svelte b/src/routes/AppHeader.svelte index deceb35..356d001 100644 --- a/src/routes/AppHeader.svelte +++ b/src/routes/AppHeader.svelte @@ -1,7 +1,7 @@ diff --git a/src/routes/chat/+layout.svelte b/src/routes/chat/+layout.svelte index ba0ee21..6215bcb 100644 --- a/src/routes/chat/+layout.svelte +++ b/src/routes/chat/+layout.svelte @@ -1,15 +1,15 @@ -{#if Object.keys($serverDataStore).length} +{#if Object.keys($serverStore).length} {/if} diff --git a/src/routes/chat/Chat.svelte b/src/routes/chat/Chat.svelte deleted file mode 100644 index 97f0b4f..0000000 --- a/src/routes/chat/Chat.svelte +++ /dev/null @@ -1,211 +0,0 @@ - - -
- - - - -
- - diff --git a/src/routes/chat/ChatRoom.svelte b/src/routes/chat/ChatRoom.svelte index ad83167..4b76674 100644 --- a/src/routes/chat/ChatRoom.svelte +++ b/src/routes/chat/ChatRoom.svelte @@ -1,49 +1,32 @@