feat: Add loading component to Join form

- Added a loading component to the Join.svelte form to show a loading message when the invite code is being submitted.
- Updated the addCode function to set the loading variable to true before making the API call and set it back to false when the API call is completed.
- Added a msg variable to store any error messages returned by the API call and display them to the user.
- Updated the submit button to show a loading message when the loading variable is true and disable the button when there is no invite code.
- Added a new <aside> element to display the error message if there is one.
- Added a style tag to set the overflow-wrap style property for the <aside> element.

fix: Fix SelectRoom style issue

- Updated the SelectRoom.svelte component to use the variant-ghost class for the <select> element and set the background color and text color based on whether the room is selected or not.

fix: Update SelectServer component

- Updated the SelectServer.svelte component to use the variant-ghost class for the <select> element and added a margin to the component.
- Updated the add server button to use the variant-ghost class and added some margin to the button.

feat: Add loading component

- Added a new loading component to show a loading message and a progress indicator when the page is loading.

refactor: Update prover.ts

- Updated the logging in the genProof function in prover.ts to use console.info instead of console.debug to improve visibility of proof generation.

fix: Update postAuth function

- Added a new postAuth function to make a POST request with basic authentication to the API.
- Updated the createInvite function in server.ts to use the postAuth function to create invite codes with basic authentication.

feat: Add invite code functionality

- Added new types for invite codes and
This commit is contained in:
AtHeartEngineer
2023-08-28 01:38:15 -04:00
parent ebf4fa0efa
commit a62b6ae7de
35 changed files with 475 additions and 257 deletions

View File

@@ -11,22 +11,4 @@
<body data-sveltekit-preload-data="hover">
<div>%sveltekit.body%</div>
</body>
<!-- Matomo -->
<script>
var _paq = (window._paq = window._paq || []);
_paq.push(['trackPageView']);
_paq.push(['enableLinkTracking']);
(function () {
var u = 'https://psedev.matomo.cloud/';
_paq.push(['setTrackerUrl', u + 'matomo.php']);
_paq.push(['setSiteId', '3']);
var d = document,
g = d.createElement('script'),
s = d.getElementsByTagName('script')[0];
g.async = true;
g.src = '//cdn.matomo.cloud/psedev.matomo.cloud/matomo.js';
s.parentNode.insertBefore(g, s);
})();
</script>
<!-- End Matomo Code -->
</html>

View File

@@ -4,23 +4,37 @@
import { postInviteCode } from '$lib/services/server';
import type { JoinResponseI } from '$lib/types';
import { getCommitment, updateRooms } from '$lib/utils/';
import Loading from './loading.svelte';
import Button from './button.svelte';
let code = '';
export let code = '';
let acceptedRoomNames: string[] = [];
let loading = false;
let msg: string | undefined;
async function addCode(newCode: string) {
const idc = getCommitment();
const result = (await postInviteCode($selectedServer, {
code: newCode.toLowerCase(),
idc
})) as JoinResponseI;
console.log('INVITE CODE RESPONSE: ', result);
if (result.status == 'valid' || result.status == 'already-added') {
acceptedRoomNames = await updateRooms($selectedServer, result.roomIds);
code = '';
$configStore.signUpStatus.inviteAccepted = true;
} else {
alert('Invalid invite code');
try {
const idc = getCommitment();
loading = true;
const result = (await postInviteCode($selectedServer, {
code: newCode.toLowerCase(),
idc
})) as JoinResponseI;
console.debug('INVITE CODE RESPONSE: ', result);
if (result.status == 'valid' || result.status == 'already-added') {
console.debug('Updating new rooms');
acceptedRoomNames = await updateRooms($selectedServer, result.roomIds);
code = '';
console.log(`Added to rooms: ${acceptedRoomNames}`);
$configStore.signUpStatus.inviteAccepted = true;
$configStore.signUpStatus.inviteCode = '';
} else {
alert('Invalid invite code');
msg = 'Invalid invite code.';
}
loading = false;
} catch (e: unknown) {
msg = String(e.message);
}
}
function inviteCodeKeyPress(event: KeyboardEvent) {
@@ -77,27 +91,44 @@
}
</script>
<div class="grid place-content-center">
<div class="grid place-content-center mb-2 sm:mb-4">
<label class="label">
<span>Select or Add a Server</span>
<SelectServer />
</label>
<label class="label mt-3">
<span>Invite Code</span>
<input
class="input max-w-md"
type="text"
placeholder="Invite Code"
bind:value={code}
on:keydown={(event) => inviteCodeKeyPress(event)}
/>
<button
class="btn variant-ghost-success"
type="button"
disabled={!code}
on:click={() => addCode(code)}>Submit</button
>
<div class="m-2 sm:md-3">
<input
class="input max-w-md mb-2 variant-ghost"
type="text"
placeholder="Invite Code"
bind:value={code}
on:keydown={(event) => inviteCodeKeyPress(event)}
/>
{#if !loading}
<button
class="btn variant-ghost-success"
type="button"
disabled={!code}
on:click={() => addCode(code)}>Submit</button
>
{:else}
<p class="italic">Loading...</p>
{/if}
</div>
</label>
{#if msg}
<aside class="p">
<div>{msg}</div>
<div>
If you are having trouble and would like help, please message us on <a
href="https://discord.gg/brJQ36KVxk"
class="underline link">Discord</a
>
</div>
</aside>
{/if}
{#if acceptedRoomNames.length > 0}
<p class="text-center mt-2">You've been added to:</p>
<div class="my-2">
@@ -107,3 +138,9 @@
</div>
{/if}
</div>
<style>
aside {
overflow-wrap: normal;
}
</style>

View File

@@ -1,30 +1,21 @@
<script lang="ts">
import { selectedServer, selectedRoom, currentRoomsStore } from '$lib/stores';
import { updateMessages } from '$lib/utils';
import { drawerStore } from '@skeletonlabs/skeleton';
function setRoom(roomId: string) {
$selectedRoom[$selectedServer] = roomId;
updateMessages($selectedServer, roomId);
drawerStore.close();
}
</script>
<!-- <select class="select text-primary-500" bind:value={$selectedRoom[$selectedServer]}>
{#each $currentRoomsStore as room}
{#if room.roomId == $selectedRoom[$selectedServer]}
<option value={room.roomId} title={room.roomId ? room.roomId.toString() : ''} selected
>{room.name}</option
>
{:else}
<option value={room.roomId}>{room.name}</option>
{/if}
{/each}
</select> -->
<dl class="m-2 md:my-3 md:mx-2">
<dl class="m-2 sm:md-3 md:my-3 md:mx-2">
{#each $currentRoomsStore as room}
<div
class="border my-1 md:my-2 py-1 px-2 md:py-3 md:px-4 rounded-token border-surface-400-500-token"
class:bg-primary-active-token={room.roomId == $selectedRoom[$selectedServer]}
class:bg-secondary-600-300-token={room.roomId == $selectedRoom[$selectedServer]}
class:text-secondary-50-900-token={room.roomId == $selectedRoom[$selectedServer]}
on:click={() => {
setRoom(room.roomId.toString());
}}

View File

@@ -14,7 +14,7 @@
valueAttr: { type: 'url', required: true },
// Returns the updated response value
response: (r: string) => {
console.log('response:', r);
console.debug('response:', r);
if (getServerList().includes(r)) {
console.warn('Server already exists');
return;
@@ -24,15 +24,15 @@
};
</script>
<div class=" flex flex-row">
<select class="select text-primary-500" bind:value={$selectedServer}>
<div class="m-2 sm:md-3 flex flex-row">
<select class="select variant-ghost" bind:value={$selectedServer}>
{#each Object.entries($serverStore) as [key, s]}
<option value={key}>{s.name}</option>
{/each}
</select>
<button
type="button"
class="btn btn-sm variant-ghost-primary ms-2"
class="btn btn-sm variant-ghost ms-2"
on:click={() => {
modalStore.trigger(addServerModal);
}}>+</button

View File

@@ -2,9 +2,8 @@
export let link: string;
export let cls: string | undefined = 'btn-primary';
export let text: string | undefined = '';
export let size: string | undefined = 'lg';
</script>
<a href={link} class="btn btn-{size} px-4 d-inline-flex align-items-center {cls}" type="button">
<a href={link} class="btn variant-ghost px-4 d-inline-flex align-items-center {cls}" type="button">
<slot>{text}</slot>
</a>

View File

@@ -2,4 +2,7 @@
import { ProgressRadial } from '@skeletonlabs/skeleton';
</script>
<div class="grid place-content-center"><ProgressRadial /></div>
<div class="grid place-content-center">
<h5 class="h6">Loading...</h5>
<ProgressRadial />
</div>

View File

@@ -131,7 +131,9 @@ async function genProof(
x: messageHash,
epoch: BigInt(epoch)
};
//console.debug('PROOFINPUTS:', proofInputs);
console.info(
`Generating proof: epoch ${epoch}, message ID ${messageId}, message hash {messageHash}`
);
return prover.generateProof(proofInputs).then((proof: RLNFullProof) => {
console.log('Proof generated!');
console.debug('PROOF:', proof);

View File

@@ -70,3 +70,33 @@ export async function post(urlParts: string[] | string, data: object): Promise<o
throw new Error(`Failed to post to ${url}`);
}
}
/**
* @description - makes a get request to the api
* @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
*/
export async function postAuth(
urlParts: string[] | string,
data: object,
username: string,
password: string
): Promise<object> {
const url = cleanURL(urlParts);
const res = await fetch(url, {
method: 'POST',
headers: {
'Access-Control-Allow-Origin': '*',
'Content-Type': 'application/json',
Authorization: 'Basic ' + btoa(username + ':' + password)
},
body: JSON.stringify(data)
});
if (res.ok) {
return res.json();
} else {
throw new Error(`Failed to post to ${url}`);
}
}

View File

@@ -1,7 +1,7 @@
import type { MessageI, ServerI } from 'discreetly-interfaces';
import type { RoomI } from '$lib/types';
import type { Invites, RoomI } from '$lib/types';
import { get, post } from './api';
import { get, post, postAuth } from './api';
export async function getIdentityRoomIds(server: string, idCommitment: string): Promise<string[]> {
return get([server, `api/rooms/${idCommitment}`]) as Promise<string[]>;
@@ -22,3 +22,14 @@ export async function postInviteCode(serverUrl: string, data: { code: string; id
export async function getMessages(serverUrl: string, roomId: string) {
return get([serverUrl, `api/room/${roomId}/messages`]) as Promise<MessageI[]>;
}
export async function createInvite(
serverUrl: string,
numCodes: number,
roomIds: string[],
username: string,
password: string
) {
const data = { numCodes, rooms: roomIds };
return postAuth([serverUrl, `api/addcode`], data, username, password) as Promise<Invites>;
}

View File

@@ -27,13 +27,24 @@ export enum IdentityStoreE {
export interface SignUpStatusI {
inviteAccepted: boolean;
identityBackedUp: boolean;
inviteCode?: string;
}
export interface ConfigurationI {
signUpStatus: SignUpStatusI;
identityStore: IdentityStoreE;
apiUsername?: string;
apiPassword?: string;
}
export interface RoomI extends RI {
server?: string;
}
export interface InviteCode {
claimcode: string;
}
export interface Invites {
message?: string;
codes: InviteCode[];
}

View File

@@ -5,9 +5,10 @@ import type { IdentityStoreI } from '$lib/types';
export function createIdentity(regenerate = false) {
if (!get(identityStore)._commitment || regenerate) {
console.log('Creating identity');
console.debug('Creating identity');
const id = new Identity() as unknown as IdentityStoreI;
identityStore.set(id);
console.log('Identity Created! Congrats on your new journey');
return 'created';
} else {
console.warn('Identity already exists');
@@ -24,7 +25,7 @@ export function getCommitment() {
}
export function getIdentityBackup() {
return getIdentity().toString();
return JSON.stringify(get(identityStore));
}
export function doesIdentityExist(): boolean {

View File

@@ -35,7 +35,7 @@ export async function updateServer(
url = Object.keys(get(serverStore))[0];
selectedServer.set(url);
}
console.log('Updating server', url);
console.debug('Updating server', url);
const oldServerStore = get(store);
getServerData(url).then((newServerData) => {
const newServerStore = {

View File

@@ -39,7 +39,7 @@
>
<Modal />
<Toast position="t" />
<Drawer position="top" padding="p-4">
<Drawer position="top" padding="p-4" rounded="rounded-token">
<SelectServer />
<SelectRoom />
</Drawer>

View File

@@ -10,19 +10,17 @@
<p class="card py-2 px-4 md:px-5 mb-3">
If you have an <code class="code">invite code</code> head over to the <Button
link="/signup"
cls="variant-ghost-secondary m-2 sm:m-3"
size="sm">Sign Up</Button
cls="variant-ghost-primary m-2 sm:m-3">Sign Up</Button
>
</p>
<p class="card py-2 px-4 md:px-5">
Or if you want to request an invite code, join our <Button
link="https://discord.gg/brJQ36KVxk"
cls="variant-ghost-tertiary m-2 sm:m-3"
size="sm">Discord</Button
cls="variant-ghost-tertiary m-2 sm:m-3">Discord</Button
>
</p>
{:else}
<Button link="/chat" cls="variant-ghost-success" size="sm">Go Chat</Button>
<Button link="/chat" cls="variant-ghost-success">Go Chat</Button>
{/if}
<slot />
</div>

View File

@@ -16,25 +16,29 @@
gridColumns="grid-cols-3"
slotDefault="place-self-center"
slotTrail="place-content-end"
background="bg-surface-50-800-token"
background="bg-surface-50-900-token"
padding="p-2 md:p-4"
class="border-b border-surface-500/30"
>
<svelte:fragment slot="lead">
<h1 class="h4 text-secondary-500">
<h1 class="h4 text-primary-500">
<a href="/"><img class="max-h-7" src="/logo-text.png" alt="discreetly" /></a>
</h1>
</svelte:fragment>
<a href="/about" class="hidden sm:inline me-2">About</a>
<a href="/about" class="btn btn-sm variant-ringed-secondary hidden sm:inline me-2">About</a>
{#if identityExists}
<a href="/chat" class="hidden sm:inline me-2">Chat</a>
<a class="btn inline sm:hidden me-2" on:click={drawerOpen}>{roomName}</a>
<a href="/chat" class="hidden btn btn-sm variant-ringed-secondary sm:inline me-2">Chat</a>
<a
class="btn btn-sm variant-ringed-secondary font-medium text-sm inline sm:hidden me-2"
on:click={drawerOpen}>{roomName}</a
>
{:else}
<a href="/signup">Sign Up</a>
<a class="btn btn-sm variant-ringed-secondary" href="/signup">Sign Up</a>
{/if}
<svelte:fragment slot="trail">
<div class="hidden sm:inline me-2 text-secondary-500">Alpha Version!</div>
<div class="hidden sm:inline me-2 text-primary-500">Alpha Version!</div>
{#if identityExists}
<a href="/identity"></a>
<a href="/identity"></a>
{/if}
<LightSwitch />
</svelte:fragment>

View File

@@ -3,7 +3,7 @@
import Sidebar from './Sidebar.svelte';
</script>
<section id="chat-wrapper" class="bg-surface-100-800-token">
<section id="chat-wrapper" class="bg-surface-50-900-token">
<Sidebar />
<ChatRoom />
</section>

View File

@@ -115,7 +115,7 @@
scrollChatToBottom();
}
socket.on('Members', (data: string) => {
console.log(data);
console.debug(data);
});
socket.on('systemBroadcast', (data: string) => {

View File

@@ -65,7 +65,7 @@
<header
class="hidden border-b border-surface-500/30 px-2 py-1 md:px-5 md:py-3 sm:flex flex-row justify-between place-items-center text-xs md:text-base"
>
<h2 id="roomTitle" class="h5 text-primary-500" class:connected title={roomId}>
<h2 id="roomTitle" class="h5 text-secondary-800-100-token" class:connected title={roomId}>
{roomName}
</h2>

View File

@@ -37,18 +37,24 @@
{#if $currentRoomMessages}
{#each $currentRoomMessages as bubble}
<div class="flex flex-col items-start">
<div class="card p-2 md:p-4 space-y-1 md:space-y-2 bg-surface-200-700-token">
<div
class="card p-2 md:p-4 space-y-1 md:space-y-2 bg-surface-100-800-token border-1 border-secondary-500/30"
>
<header class="flex justify-between items-center text-xs md:text-sm">
<small class="opacity-50 text-primary-500 mr-2 md:mr-4">{getTime(bubble)}</small>
<small class="opacity-50 text-surface-700-200-token mr-2 md:mr-4"
>{getTime(bubble)}</small
>
{#if bubble.epoch}
<small class="hidden md:block opacity-50 text-primary-500"
<small class="hidden md:block opacity-50 text-surface-700-200-token"
>epoch: {bubble.epoch}</small
>
{:else}
<small class="hidden md:block opacity-70 text-error-500">SYSTEM MESSAGE</small>
<small class="hidden md:block opacity-70 text-error-500-400-token text-"
>SYSTEM MESSAGE</small
>
{/if}
</header>
<p class="text-primary-500">{bubble.message}</p>
<p class="text-surface-800-100-token">{bubble.message}</p>
</div>
</div>
{/each}

View File

@@ -105,9 +105,9 @@
<textarea
bind:value={messageText}
maxlength="2000"
class="p-1 md:p-2 text-primary-400 border"
class:bg-surface-900={!canSendMessage}
class:bg-surface-500={canSendMessage}
class=" p-1 md:p-2 text-surface-900-50-token border"
class:bg-surface-300-600-token={!canSendMessage}
class:bg-surface-200-700-token={canSendMessage}
name="prompt"
id="prompt"
placeholder={placeholderText()}
@@ -118,8 +118,8 @@
<button
class:hidden={!canSendMessage}
class={canSendMessage && messageText
? 'text-xs md:text-base variant-ghost-primary'
: 'text-xs md:text-base variant-ghost-surface'}
? 'text-xs md:text-base variant-ghost-success'
: 'text-xs md:text-base variant-ghost-secondary'}
disabled={!canSendMessage}
on:click={sendMessage}
>

View File

@@ -1,11 +1,11 @@
<script lang="ts">
import SelectRoom from '../../lib/components/SelectRoom.svelte';
import SelectRoom from '$lib/components/SelectRoom.svelte';
import SelectServer from '$lib/components/SelectServer.svelte';
</script>
<div id="sidebar" class="hidden sm:grid grid-rows-[auto_1fr_auto] border-r border-surface-500/30">
<!-- Header -->
<header class="border-b border-surface-500/30 p-4">
<header class="border-b border-surface-500/30 p-1">
<SelectServer />
</header>
<!-- List -->

View File

@@ -1,5 +1,7 @@
<div class="mx-auto mt-10 max-w-[80ch]">
<h2 class="h2 mb-8 text-center">Manage Your Identity</h2>
<div class=" mx-2 md:mx-4">
<div class="mx-auto mt-10 max-w-[80ch]">
<h2 class="h2 mb-8 text-center">Manage Your Identity</h2>
<slot />
<slot />
</div>
</div>

View File

@@ -1,28 +1,33 @@
<script lang="ts">
import JoinMore from './JoinMore.svelte';
import { identityStore } from '$lib/stores';
import DeleteIdentity from './DeleteIdentity.svelte';
import BackupIdentity from './BackupIdentity.svelte';
import RestoreIdentity from './RestoreIdentity.svelte';
import Join from '$lib/components/Join.svelte';
import { createIdentity } from '$lib/utils/';
$: identityExists = !!$identityStore._commitment;
</script>
{#if !identityExists}
<div class="mb-8 text-center">
<span class="badge variant-ghost-secondary text-sm px-4 py-2">Identity Not Found</span>
<span class="text-base italic px-4 py-2 font-mono badge variant-outline-error"
>Identity Not Found!</span
>
</div>
{/if}
<div class="grid grid-flow-rows gap-5 my-5 max-w-md mx-auto">
{#if !identityExists}
<button on:click={() => createIdentity()} class="btn variant-filled-success" type="button">
<button
on:click={() => createIdentity()}
class="btn variant-ghost-primary font-medium"
type="button"
>
Generate Identity
</button>
<RestoreIdentity />
{:else}
<BackupIdentity />
<DeleteIdentity />
<h4 class="h4 mt-4">Join More Rooms</h4>
<Join />
<JoinMore />
{/if}
</div>

View File

@@ -1,43 +1,47 @@
<script lang="ts">
import { getIdentityBackup } from '$lib/utils/';
import Loading from '$lib/components/loading.svelte';
import QRCode from 'qrcode';
let loading: boolean = false;
let imageUrl: string | undefined = undefined;
function generateQR() {
loading = true;
const opts = {
type: 'image/jpeg',
errorCorrectionLevel: 'M',
mode: 'Alphanumeric',
color: {
dark: '#202626',
light: '#ffffff'
}
};
QRCode.toDataURL(getIdentityBackup(), opts).then((response) => {
imageUrl = response;
});
setTimeout(() => {
imageUrl = undefined;
loading = false;
}, 15000);
let revealIdentity = false;
let id = '';
function reveal() {
id = getIdentityBackup();
if (revealIdentity == false) {
revealIdentity = true;
setTimeout(() => {
revealIdentity = false;
id = '';
}, 60000);
} else {
revealIdentity = false;
}
}
</script>
<h4 class="h4">Backup Your Identity</h4>
<a
class="btn variant-ghost-success"
href={'data:text/json;charset=utf-8,' + encodeURIComponent(getIdentityBackup())}
download="identity.json">JSON</a
>
<a class="btn variant-ghost-success" on:click={generateQR} style="cursor:pointer">QR Code</a>
<div class="card variant-ghost-secondary">
<header class="card-header">
<h4 class="h4">Backup Your Identity</h4>
</header>
<section class="px-4 pt-4 mb-5">
<div class="m-2 sm:m-3 flex flex-col gap-4">
<a
class="btn variant-ghost-success"
href={'data:text/json;charset=utf-8,' +
encodeURIComponent(JSON.stringify(getIdentityBackup()))}
download="identity.json">Download Identity Backup as JSON</a
>
{#if !revealIdentity}
<div class="btn variant-ghost-success" on:click={reveal}>Show Identity</div>
{:else}
<div class="btn variant-ghost-success" on:click={reveal}>Hide Identity</div>
<div id="reveleadIdentity" class="text-sm p-2 sm:p-4 bg-error-300-600-token max-w-sm">
Identity:{id}
</div>
{/if}
</div>
</section>
</div>
{#if loading && !imageUrl}
<Loading />
{:else}
<img src={imageUrl} class="mx-auto" />
{/if}
<style>
#reveleadIdentity {
overflow-wrap: break-word;
}
</style>

View File

@@ -23,27 +23,33 @@
isCheckboxChecked = checkbox.checked;
isButtonDisabled = !isCheckboxChecked;
});
checkbox.addEventListener('keydown', (e: KeyboardEvent) => {
if (e.key === 'Escape') {
checkbox.checked = false;
}
});
}
});
</script>
<div class="card variant-soft-error">
<div class="card variant-ghost-error">
<header class="card-header">
<h3 class="h4">Delete Your Identity & Reset Application</h3>
</header>
<section class="px-4 pt-4">
<input type="checkbox" id="checkbox" class="mx-2 p-1" />
<span class="ms-2 text-error-500"
>I promise I backed up my identity, or I really want to destroy it.</span
<span class="ms-2"
>I promise I backed up my identity, or I really want to destroy it forever.</span
>
</section>
<footer class="card-footer text-center">
<button
id="delete-identity"
on:click={deleteIdentity}
disabled={isButtonDisabled}
class="btn btn-sm variant-ghost-error mt-8 text-sm font-bold text-warning-100"
>Delete Identity</button
>
<footer class="card-footer text-center mb-2">
{#if !isButtonDisabled}
<button
id="delete-identity"
on:click={deleteIdentity}
disabled={isButtonDisabled}
class="btn btn-sm variant-ghost-error mt-8 text-sm font-bold">Delete Identity</button
>
{/if}
</footer>
</div>

View File

@@ -0,0 +1,12 @@
<script lang="ts">
import Join from '$lib/components/Join.svelte';
</script>
<div class="card variant-ghost-tertiary">
<header class="card-header">
<h4 class="h4">Join More Rooms</h4>
</header>
<section class="py-2 mb-2">
<Join />
</section>
</div>

View File

@@ -1,24 +1,39 @@
<script lang="ts">
import { identityStore } from '$lib/stores';
import { Identity } from '@semaphore-protocol/identity';
import QrScanner from 'qr-scanner';
import { FileDropzone } from '@skeletonlabs/skeleton';
import { poseidon2 } from 'poseidon-lite/poseidon2';
import { poseidon1 } from 'poseidon-lite/poseidon1';
let files: FileList;
let status = '';
let hasCamera = false;
QrScanner.hasCamera().then((result) => {
hasCamera = result;
});
function restoreBackup(backup: any) {
console.debug('Attempting restore of identity from backup file...');
console.debug(backup);
$identityStore = new Identity(JSON.stringify(backup));
console.log($identityStore.identity);
const id = JSON.parse(backup);
if (!id._nullifier) {
console.error("_nullifier doesn't exist in backup");
}
if (!id._trapdoor) {
console.error("_trapdoor doesn't exist in backup");
}
if (!id._secret) {
console.error("_secret doesn't exist in backup");
}
const checkSecret = poseidon2([id._nullifier, id._trapdoor]);
if (checkSecret != id._secret) {
console.error('Secret does not match secret from backup');
}
if (!id._commitment) {
console.error("_commitment doesn't exist in backup");
}
const checkCommitment = poseidon1([id._secret]);
if (checkCommitment != id._commitment) {
console.error('Commitment does not match commitment backup');
}
$identityStore = id;
console.log(
'Identity restored from backup file with identity commitment:',
$identityStore.identity.commitment
$identityStore._commitment
);
}
@@ -33,47 +48,17 @@
if (f.type == 'application/json' || f.type == 'text/plain') {
f.text().then((text) => {
unverifiedBackup = JSON.parse(text);
console.debug(unverifiedBackup);
restoreBackup(unverifiedBackup);
});
} else if (f.type == 'image/jpeg' || f.type == 'image/png') {
QrScanner.scanImage(f)
.then((result) => {
unverifiedBackup = JSON.parse(result);
console.debug(unverifiedBackup);
restoreBackup(unverifiedBackup);
})
.catch((error) => {
status = error || 'No QR code found.';
console.log(error || 'No QR code found.');
});
} else {
status = 'Invalid file type, must be JSON or QR code in jpeg or png.';
status =
'Invalid file type, must be a JSON object with the _nullifier, _trapdoor, _secret, and _commitment as stringified bigints';
console.warn('Invalid file type');
}
}
function scanQR() {
const videoDiv = document.getElementById('qr-camera');
if (!videoDiv) return;
const video = videoDiv.getElementsByTagName('video')[0];
const qrScanner = new QrScanner(video, (result) => restoreBackup(JSON.parse(result.data)), {
highlightCodeOutline: true
});
qrScanner.start();
setTimeout(() => qrScanner.stop(), 30000);
}
</script>
<h4 class="h4">Restore Your Identity</h4>
<FileDropzone name="identity.json" bind:files on:change={onChangeHandler} />
{#if hasCamera}
<div id="qr-camera" class="flex flex-col justify-center">
<button on:click={scanQR} class="btn variant-filled-success" type="button"
>Scan a QR Code</button
>
<video />
</div>
{/if}
<div class="">{status}</div>

View File

@@ -0,0 +1,112 @@
<script lang="ts">
import { createInvite } from '$lib/services/server';
import { selectedServer, configStore, currentRoomsStore } from '$lib/stores';
import { Accordion, AccordionItem } from '@skeletonlabs/skeleton';
import qrcode from 'qrcode';
let numCodes = 1;
let roomIds: string[] = [];
function makeInviteQRCode(inviteCode: string) {
const canvasContainer = document.getElementById('qr');
const div = document.createElement('div');
const canvas = document.createElement('canvas');
div?.appendChild(canvas);
const p = document.createElement('p');
p.innerText = inviteCode;
div?.appendChild(p);
canvasContainer?.appendChild(div);
const url = `${$selectedServer}/signup/${inviteCode}`;
qrcode.toCanvas(canvas, url);
}
async function newCodes(numCodes: number) {
console.log(`Requesting ${numCodes} codes`);
const canvasContainer = document.getElementById('qr');
canvasContainer?.replaceChildren();
const resp = await createInvite(
$selectedServer,
numCodes,
roomIds,
$configStore.apiUsername as string,
$configStore.apiPassword as string
);
console.log(resp);
const codes = resp.codes;
codes.forEach((code) => {
makeInviteQRCode(code.claimcode);
});
}
function updateRoomList(e: Event) {
const target = e.target as HTMLInputElement;
const roomId = target.value;
if (target.checked) {
roomIds.push(roomId);
} else {
roomIds = roomIds.filter((id) => id !== roomId);
}
}
</script>
<div class="flex flex-col place-content-center max-w-sm m-auto pt-5">
<div id="qr" class="flex flex-col gap-12 place-content-center">
<div>
<canvas class="variant-ghost-success" />
<p>no codes yet</p>
</div>
</div>
<div
class="btn my-12 variant-ghost-primary text-center items-center"
on:click={() => {
newCodes(numCodes);
}}
>
New Codes
</div>
<Accordion>
<AccordionItem>
<svelte:fragment slot="summary">Number of Codes</svelte:fragment>
<svelte:fragment slot="content"
><label class="label">
<span>Num Codes: {numCodes}</span>
<input type="range" min="1" max="5" bind:value={numCodes} />
</label></svelte:fragment
>
</AccordionItem>
<AccordionItem>
<svelte:fragment slot="summary">Rooms</svelte:fragment>
<svelte:fragment slot="content"
><div>
{#each $currentRoomsStore as room}
<label class="label">
<input type="checkbox" value={room.roomId} on:change={updateRoomList} />
<span title={String(room.roomId)}>{room.name}</span>
</label>
{/each}
</div></svelte:fragment
>
</AccordionItem>
<AccordionItem>
<svelte:fragment slot="summary">API</svelte:fragment>
<svelte:fragment slot="content"
><label class="label">
<span>Api Username</span>
<input type="text" class="input" bind:value={$configStore.apiUsername} />
</label>
<label class="label">
<span>Api Password</span>
<input type="password" class="input" bind:value={$configStore.apiPassword} />
</label></svelte:fragment
>
</AccordionItem>
</Accordion>
</div>
<style>
#qr > div > canvas {
margin: 0 auto;
}
#qr > div > p {
text-align: center;
}
</style>

View File

@@ -11,12 +11,14 @@
import { IdentityStoreE } from '$lib/types';
</script>
<div class="h-100">
<div class="h-100 mx-2 md:mx-4">
<Stepper
class="max-w-sm sm:max-w-md md:max-w-3xl mx-auto mt-16"
on:complete={() => {
goto('/chat');
}}
buttonNext="variant-filled-surface-50-900-token"
buttonComplete="variant-filled-success"
>
<Step>
<svelte:fragment slot="header"

View File

@@ -1,29 +1,35 @@
<script lang="ts">
console.info(
'I see you are checking out the logs, let us know what you think on our discord: https://discord.gg/brJQ36KVxk'
);
</script>
<h5 class="text-center h4 my-4 md:my-7">
This app is a little different from what you're used to.
</h5>
<div class="grid place-content-center md:text-lg mb-6 md:mb-10 px-2 md:px-0">
<div id="info">
<p>
<b class="text-secondary-500">Discreetly</b> is an <b>anonymous</b> chat app
<b class="text-primary-500">Discreetly</b> is an <b>anonymous</b> chat app
</p>
<p>Yes, you are <i>actually</i> anonymous, don't abuse it</p>
<p>
But you can still get <b class="text-secondary-500">banned</b> if you
<b class="text-secondary-500">spam</b>, or if you get
<b class="text-secondary-500">voted out</b>
But you can still get <b class="text-primary-500">banned</b> if you
<b class="text-primary-500">spam</b>, or if you get
<b class="text-primary-500">voted out</b>
<small class="text-surface-400 italic">coming soon</small>.
</p>
<p>
There is
<b class="text-secondary-500">no unban</b>
<b class="text-primary-500">no unban</b>
</p>
<p>
<b>Backup your identity</b>, there is
<i class="text-secondary-500">no account recovery</i>
<i class="text-primary-500">no account recovery</i>
</p>
<p class="mt-2 mb-4">
If you want to understand how this works:
<a href="/about" class="btn btn-sm variant-soft-tertiary ms-2 mt-2">Read More Here</a>
<a href="/about" class="btn btn-sm variant-ghost-tertiary ms-2 mt-2">Read More Here</a>
</p>
</div>
</div>
@@ -35,6 +41,6 @@
#info p::before {
content: '•';
padding-right: 0.45em;
color: rgb(var(--color-secondary-500));
color: rgb(var(--color-primary-500));
}
</style>

View File

@@ -7,12 +7,13 @@
<div class="grid place-content-center gap-5">
{#if !identityExists}
<button on:click={() => createIdentity()} class="btn variant-filled-success" type="button">
<button on:click={() => createIdentity()} class="btn variant-ghost-primary" type="button">
Generate Identity
</button>
<RestoreIdentity />
{:else}
<button on:click={() => createIdentity(true)} class="btn variant-ghost-warning" type="button">
<p class="h4">Identity Created ✅</p>
<button on:click={() => createIdentity(true)} class="btn variant-ghost-success" type="button">
Re-Generate Identity
</button>
<RestoreIdentity />

View File

@@ -1,5 +1,6 @@
<script lang="ts">
import Join from '$lib/components/Join.svelte';
import { configStore } from '$lib/stores';
</script>
<Join />
<Join code={$configStore.signUpStatus.inviteCode} />

View File

@@ -1,5 +1,6 @@
<script lang="ts">
import { identityStore, configStore } from '$lib/stores';
console.log('Yes, you should backup your identity, there is no recovery');
</script>
<div class="grid place-content-center">

View File

@@ -0,0 +1,6 @@
<script lang="ts">
import { page } from '$app/stores';
import { configStore } from '$lib/stores';
$configStore.signUpStatus.inviteCode = $page.params.invite;
console.log(`Invited with code: ${$page.params.invite}`);
</script>

View File

@@ -3,8 +3,8 @@
/* =~= Theme Properties =~= */
--theme-font-family-base: 'Space Grotesk', Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, 'Noto Sans', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji';
--theme-font-family-heading: 'Nippo', ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
--theme-font-color-base: var(--color-surface-800);
--theme-font-color-dark: var(--color-primary-200);
--theme-font-color-base: var(--color-surface-900);
--theme-font-color-dark: var(--color-surface-50);
--theme-rounded-base: 4px;
--theme-rounded-container: 6px;
--theme-border-base: 1px;
@@ -17,28 +17,28 @@
--on-error: 255 255 255;
--on-surface: 255 255 255;
/* =~= Theme Colors =~= */
/* primary | #99b5b8 */
--color-primary-50: 240 244 244; /* ⬅ #f0f4f4 */
--color-primary-100: 235 240 241; /* ⬅ #ebf0f1 */
--color-primary-200: 230 237 237; /* ⬅ #e6eded */
--color-primary-300: 214 225 227; /* ⬅ #d6e1e3 */
--color-primary-400: 184 203 205; /* ⬅ #b8cbcd */
--color-primary-500: 153 181 184; /* ⬅ #99b5b8 */
--color-primary-600: 138 163 166; /* ⬅ #8aa3a6 */
--color-primary-700: 115 136 138; /* ⬅ #73888a */
--color-primary-800: 92 109 110; /* ⬅ #5c6d6e */
--color-primary-900: 75 89 90; /* ⬅ #4b595a */
/* secondary | #fa5f5f */
--color-secondary-50: 254 231 231; /* ⬅ #fee7e7 */
--color-secondary-100: 254 223 223; /* ⬅ #fedfdf */
--color-secondary-200: 254 215 215; /* ⬅ #fed7d7 */
--color-secondary-300: 253 191 191; /* ⬅ #fdbfbf */
--color-secondary-400: 252 143 143; /* ⬅ #fc8f8f */
--color-secondary-500: 250 95 95; /* ⬅ #fa5f5f */
--color-secondary-600: 225 86 86; /* ⬅ #e15656 */
--color-secondary-700: 188 71 71; /* ⬅ #bc4747 */
--color-secondary-800: 150 57 57; /* ⬅ #963939 */
--color-secondary-900: 123 47 47; /* ⬅ #7b2f2f */
/* primary | #fa5f5f */
--color-primary-50: 254 231 231; /* ⬅ #fee7e7 */
--color-primary-100: 254 223 223; /* ⬅ #fedfdf */
--color-primary-200: 254 215 215; /* ⬅ #fed7d7 */
--color-primary-300: 253 191 191; /* ⬅ #fdbfbf */
--color-primary-400: 252 143 143; /* ⬅ #fc8f8f */
--color-primary-500: 250 95 95; /* ⬅ #fa5f5f */
--color-primary-600: 225 86 86; /* ⬅ #e15656 */
--color-primary-700: 188 71 71; /* ⬅ #bc4747 */
--color-primary-800: 150 57 57; /* ⬅ #963939 */
--color-primary-900: 123 47 47; /* ⬅ #7b2f2f */
/* secondary | #99b5b8 */
--color-secondary-50: 240 244 244; /* ⬅ #f0f4f4 */
--color-secondary-100: 235 240 241; /* ⬅ #ebf0f1 */
--color-secondary-200: 230 237 237; /* ⬅ #e6eded */
--color-secondary-300: 214 225 227; /* ⬅ #d6e1e3 */
--color-secondary-400: 184 203 205; /* ⬅ #b8cbcd */
--color-secondary-500: 153 181 184; /* ⬅ #99b5b8 */
--color-secondary-600: 138 163 166; /* ⬅ #8aa3a6 */
--color-secondary-700: 115 136 138; /* ⬅ #73888a */
--color-secondary-800: 92 109 110; /* ⬅ #5c6d6e */
--color-secondary-900: 75 89 90; /* ⬅ #4b595a */
/* tertiary | #158993 */
--color-tertiary-50: 220 237 239; /* ⬅ #dcedef */
--color-tertiary-100: 208 231 233; /* ⬅ #d0e7e9 */
@@ -61,17 +61,17 @@
--color-success-700: 52 121 75; /* ⬅ #34794b */
--color-success-800: 41 97 60; /* ⬅ #29613c */
--color-success-900: 34 79 49; /* ⬅ #224f31 */
/* warning | #ffb585 */
--color-warning-50: 255 244 237; /* ⬅ #fff4ed */
--color-warning-100: 255 240 231; /* ⬅ #fff0e7 */
--color-warning-200: 255 237 225; /* ⬅ #ffede1 */
--color-warning-300: 255 225 206; /* ⬅ #ffe1ce */
--color-warning-400: 255 203 170; /* ⬅ #ffcbaa */
--color-warning-500: 255 181 133; /* ⬅ #ffb585 */
--color-warning-600: 230 163 120; /* ⬅ #e6a378 */
--color-warning-700: 191 136 100; /* ⬅ #bf8864 */
--color-warning-800: 153 109 80; /* ⬅ #996d50 */
--color-warning-900: 125 89 65; /* ⬅ #7d5941 */
/* warning | #d2b45c */
--color-warning-50: 248 244 231; /* ⬅ #f8f4e7 */
--color-warning-100: 246 240 222; /* ⬅ #f6f0de */
--color-warning-200: 244 236 214; /* ⬅ #f4ecd6 */
--color-warning-300: 237 225 190; /* ⬅ #ede1be */
--color-warning-400: 224 203 141; /* ⬅ #e0cb8d */
--color-warning-500: 210 180 92; /* ⬅ #d2b45c */
--color-warning-600: 189 162 83; /* ⬅ #bda253 */
--color-warning-700: 158 135 69; /* ⬅ #9e8745 */
--color-warning-800: 126 108 55; /* ⬅ #7e6c37 */
--color-warning-900: 103 88 45; /* ⬅ #67582d */
/* error | #fa5f5f */
--color-error-50: 254 231 231; /* ⬅ #fee7e7 */
--color-error-100: 254 223 223; /* ⬅ #fedfdf */
@@ -83,16 +83,16 @@
--color-error-700: 188 71 71; /* ⬅ #bc4747 */
--color-error-800: 150 57 57; /* ⬅ #963939 */
--color-error-900: 123 47 47; /* ⬅ #7b2f2f */
/* surface | #363f3f */
--color-surface-50: 225 226 226; /* ⬅ #e1e2e2 */
--color-surface-100: 215 217 217; /* ⬅ #d7d9d9 */
--color-surface-200: 205 207 207; /* ⬅ #cdcfcf */
--color-surface-300: 175 178 178; /* ⬅ #afb2b2 */
--color-surface-400: 114 121 121; /* ⬅ #727979 */
--color-surface-500: 54 63 63; /* ⬅ #363f3f */
--color-surface-600: 49 57 57; /* ⬅ #313939 */
--color-surface-700: 41 47 47; /* ⬅ #292f2f */
--color-surface-800: 32 38 38; /* ⬅ #202626 */
--color-surface-900: 26 31 31; /* ⬅ #1a1f1f */
/* surface | #2c3434 */
--color-surface-50: 223 225 225; /* ⬅ #dfe1e1 */
--color-surface-100: 213 214 214; /* ⬅ #d5d6d6 */
--color-surface-200: 202 204 204; /* ⬅ #cacccc */
--color-surface-300: 171 174 174; /* ⬅ #abaeae */
--color-surface-400: 107 113 113; /* ⬅ #6b7171 */
--color-surface-500: 44 52 52; /* ⬅ #2c3434 */
--color-surface-600: 40 47 47; /* ⬅ #282f2f */
--color-surface-700: 33 39 39; /* ⬅ #212727 */
--color-surface-800: 26 31 31; /* ⬅ #1a1f1f */
--color-surface-900: 22 25 25; /* ⬅ #161919 */
}