checkpoint with overscroll issue

This commit is contained in:
2023-10-14 13:27:00 -04:00
parent 35d9fd7753
commit 2f166db75b
62 changed files with 6441 additions and 992 deletions

3
.npmrc
View File

@@ -1,2 +1 @@
engine-strict=true engine-strict=true
resolution-mode=highest

5812
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,17 +1,21 @@
{ {
"name": "discreetly", "name": "discreetly",
"version": "0.0.1", "version": "0.3.0",
"private": true, "private": true,
"license": "GPL-3.0-or-later",
"scripts": { "scripts": {
"dev": "vite dev", "dev": "vite dev",
"build": "vite build", "build": "vite build",
"preview": "vite preview", "preview": "vite preview",
"test": "playwright test", "test": "npm run test:unit && npm run test:e2e",
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json", "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch", "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch --output human --threshold error",
"lint": "prettier --plugin-search-dir . --check . && eslint .", "lint": "prettier --plugin-search-dir . --check . && eslint .",
"format": "prettier --plugin-search-dir . --write .", "format": "prettier --plugin-search-dir . --write .",
"prepare": "svelte-kit sync" "test:e2e": "playwright test",
"test:unit": "vitest",
"test:unit:ui": "vitest --ui --open --watch",
"test:unit:coverage": "vitest run --coverage"
}, },
"devDependencies": { "devDependencies": {
"@faker-js/faker": "^8.0.2", "@faker-js/faker": "^8.0.2",
@@ -22,36 +26,45 @@
"@sveltejs/adapter-cloudflare": "^2.3.0", "@sveltejs/adapter-cloudflare": "^2.3.0",
"@sveltejs/adapter-static": "^2.0.3", "@sveltejs/adapter-static": "^2.0.3",
"@sveltejs/kit": "^1.25.0", "@sveltejs/kit": "^1.25.0",
"@sveltejs/vite-plugin-svelte": "^2.4.6",
"@tailwindcss/forms": "^0.5.6", "@tailwindcss/forms": "^0.5.6",
"@tailwindcss/typography": "0.5.10",
"@testing-library/svelte": "^4.0.3",
"@types/dompurify": "^3.0.2", "@types/dompurify": "^3.0.2",
"@types/node": "^20.6.3", "@types/node": "^20.6.3",
"@types/qrcode": "^1.5.1", "@types/qrcode": "^1.5.1",
"@typescript-eslint/eslint-plugin": "^5.45.0", "@typescript-eslint/eslint-plugin": "^5.45.0",
"@typescript-eslint/parser": "^5.45.0", "@typescript-eslint/parser": "^5.45.0",
"@vitest/ui": "^0.34.6",
"autoprefixer": "^10.4.14", "autoprefixer": "^10.4.14",
"eslint": "^8.28.0",
"eslint-config-prettier": "^8.5.0", "eslint-config-prettier": "^8.5.0",
"eslint-plugin-svelte": "^2.30.0", "eslint-plugin-svelte": "^2.30.0",
"eslint": "^8.28.0",
"jsdom": "^22.1.0",
"lightningcss": "^1.21.7", "lightningcss": "^1.21.7",
"postcss": "^8.4.24",
"postcss-load-config": "^4.0.1", "postcss-load-config": "^4.0.1",
"prettier": "^2.8.0", "postcss": "^8.4.24",
"prettier-plugin-svelte": "^2.10.1", "prettier-plugin-svelte": "^2.10.1",
"rollup-plugin-sizes": "^1.0.5", "prettier": "^2.8.0",
"svelte": "^4.0.5",
"svelte-check": "^3.4.5", "svelte-check": "^3.4.5",
"svelte-kit": "^1.2.0", "svelte-kit": "^1.2.0",
"svelte": "^4.0.5",
"tailwindcss": "^3.3.2", "tailwindcss": "^3.3.2",
"terser": "^5.19.2", "terser": "^5.19.2",
"tslib": "^2.4.1", "tslib": "^2.4.1",
"typescript": "^5.0.0", "typescript": "^5.0.0",
"vite": "^4.3.6" "vite-plugin-tailwind-purgecss": "0.1.3",
"vite": "^4.4.11",
"vitest": "0.32.2"
}, },
"type": "module", "type": "module",
"dependencies": { "dependencies": {
"@ethersproject/bytes": "^5.7.0", "@ethersproject/bytes": "^5.7.0",
"@ethersproject/keccak256": "^5.7.0", "@ethersproject/keccak256": "^5.7.0",
"@ethersproject/strings": "^5.7.0", "@ethersproject/strings": "^5.7.0",
"@floating-ui/dom": "1.5.3",
"@personaelabs/spartan-ecdsa": "^2.3.0",
"@rainbow-me/rainbowkit": "^1.1.1",
"@semaphore-protocol/group": "^3.10.1", "@semaphore-protocol/group": "^3.10.1",
"@semaphore-protocol/identity": "^3.10.1", "@semaphore-protocol/identity": "^3.10.1",
"autolinker": "^4.0.0", "autolinker": "^4.0.0",
@@ -65,6 +78,7 @@
"qr-scanner": "^1.4.2", "qr-scanner": "^1.4.2",
"qrcode": "^1.5.3", "qrcode": "^1.5.3",
"socket.io-client": "^4.7.1", "socket.io-client": "^4.7.1",
"svelte-material-icons": "^3.0.5" "svelte-material-icons": "^3.0.5",
"wagmi": "^1.4.3"
} }
} }

14
playwright.config.ts Normal file
View File

@@ -0,0 +1,14 @@
import type { PlaywrightTestConfig } from '@playwright/test';
const config: PlaywrightTestConfig = {
webServer: {
command: 'npm run build && npm run preview',
port: 4173,
stdout: 'ignore',
stderr: 'ignore'
},
testDir: 'tests',
testMatch: /(.+\.)?(test|spec)\.[jt]s/
};
export default config;

View File

@@ -1,13 +1,6 @@
const tailwindcss = require('tailwindcss'); module.exports = {
const autoprefixer = require('autoprefixer'); plugins: {
tailwindcss: {},
const config = { autoprefixer: {}
plugins: [ }
//Some plugins, like tailwindcss/nesting, need to run before Tailwind,
tailwindcss(),
//But others, like autoprefixer, need to run after,
autoprefixer
]
}; };
module.exports = config;

5
src/+error.svelte Normal file
View File

@@ -0,0 +1,5 @@
<script lang="ts">
import { page } from '$app/stores';
</script>
<h1 class="h3">{$page.error.message}</h1>

View File

@@ -8,6 +8,6 @@
%sveltekit.head% %sveltekit.head%
</head> </head>
<body data-sveltekit-preload-data="hover" data-theme="discreetly-theme"> <body data-sveltekit-preload-data="hover" data-theme="discreetly-theme">
<div>%sveltekit.body%</div> <div style="display: contents" class="h-full overflow-hidden">%sveltekit.body%</div>
</body> </body>
</html> </html>

View File

@@ -91,3 +91,7 @@ input:focus-visible {
font-family: 'Nippo'; font-family: 'Nippo';
font-weight: 300; font-weight: 300;
} }
.rail-icon {
@apply w-full h3;
}

12
src/error.html Normal file
View File

@@ -0,0 +1,12 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>%sveltekit.error.message%</title>
</head>
<body>
<h1>Something didn't work right</h1>
<p>Status: %sveltekit.status%</p>
<p>Message: %sveltekit.error.message%</p>
</body>
</html>

View File

@@ -1,4 +1,5 @@
// FIXME: This is a potential hack to get proofs to generate on the front end // FIXME: This is a potential hack to get proofs to generate on the front end
export async function handle({ event, resolve }) { // eslint-disable-next-line @typescript-eslint/no-explicit-any
export async function handle({ event, resolve }: { event: unknown; resolve: any }) {
return resolve(event, { ssr: false }); return resolve(event, { ssr: false });
} }

View File

@@ -1,4 +1,5 @@
<script lang="ts"> <script lang="ts">
import { Ratings } from '@skeletonlabs/skeleton';
import FullCircle from 'svelte-material-icons/Circle.svelte'; import FullCircle from 'svelte-material-icons/Circle.svelte';
import CircleEmpty from 'svelte-material-icons/CircleOutline.svelte'; import CircleEmpty from 'svelte-material-icons/CircleOutline.svelte';
@@ -9,11 +10,11 @@
$: emptycircles = maxHealth - health; $: emptycircles = maxHealth - health;
</script> </script>
<div class="flex flex-row ms-2 place-items-center"> <Ratings value={health} max={maxHealth} spacing="space-x-0">
{#each { length: fullcircles } as _, i} <svelte:fragment slot="empty">
<FullCircle class="w-4 h-4 text-green-500" />
{/each}
{#each { length: emptycircles } as _, i}
<CircleEmpty class="w-4 h-4 text-surface-600-300-token" /> <CircleEmpty class="w-4 h-4 text-surface-600-300-token" />
{/each} </svelte:fragment>
</div> <svelte:fragment slot="full">
<FullCircle class="w-4 h-4 text-green-500" />
</svelte:fragment>
</Ratings>

View File

@@ -2,7 +2,6 @@
import FullShield from 'svelte-material-icons/Shield.svelte'; import FullShield from 'svelte-material-icons/Shield.svelte';
import ShieldHalfFullHalfEmpty from 'svelte-material-icons/ShieldHalfFull.svelte'; import ShieldHalfFullHalfEmpty from 'svelte-material-icons/ShieldHalfFull.svelte';
import ShieldEmpty from 'svelte-material-icons/ShieldOutline.svelte'; import ShieldEmpty from 'svelte-material-icons/ShieldOutline.svelte';
import ShieldHalfFull from 'svelte-material-icons/ShieldHalf.svelte';
export let health: number; export let health: number;
export let maxHealth: number; export let maxHealth: number;

View File

@@ -1,27 +1,41 @@
<script lang="ts"> <script lang="ts">
import { getIdentityBackup } from '$lib/utils/'; import { getIdentityBackup } from '$lib/utils/';
$: id = getIdentityBackup();
$: identityBackupExists = id ? true : false;
$: encodedIdentity = 'data:text/json;charset=utf-8,' + encodeURIComponent(id!);
let revealIdentity = false; let revealIdentity = false;
let id = '';
function reveal() { function reveal() {
id = getIdentityBackup(); id = getIdentityBackup();
if (revealIdentity == false) { if (id === undefined || id === null) {
revealIdentity = true; id = 'No Identity Backup Found';
setTimeout(() => {
revealIdentity = false;
id = '';
}, 60000);
} else { } else {
revealIdentity = false; if (revealIdentity == false) {
revealIdentity = true;
setTimeout(() => {
revealIdentity = false;
id = '';
}, 60000);
} else {
revealIdentity = false;
}
} }
} }
</script> </script>
<div class="m-2 sm:m-3 flex flex-col gap-4"> <div class="m-2 sm:m-3 flex flex-col gap-4">
<a {#if identityBackupExists}
class="btn variant-ghost-success" <a class="btn variant-ghost-success" href={encodedIdentity} download="Discreetly_Identity.json"
href={'data:text/json;charset=utf-8,' + encodeURIComponent(getIdentityBackup())} >Download Identity Backup as JSON</a
download="Discreetly_Identity.json">Download Identity Backup as JSON</a >
> {:else}
<div class="text-sm text-primary-500">
Error getting your identity backup. Please contact the developers for help.
</div>
{/if}
{#if !revealIdentity} {#if !revealIdentity}
<div class="btn variant-ghost-success" on:click={reveal}>Show Identity</div> <div class="btn variant-ghost-success" on:click={reveal}>Show Identity</div>
{:else} {:else}

View File

@@ -1,5 +1,6 @@
<script lang="ts"> <script lang="ts">
import SelectServer from '$lib/components/SelectServer.svelte'; import SelectServer from '$lib/components/SelectServer.svelte';
import { alertAll } from '$lib/utils';
import { inviteCode } from '$lib/utils/inviteCode'; import { inviteCode } from '$lib/utils/inviteCode';
export let code = ''; export let code = '';
@@ -12,13 +13,13 @@
inviteCode(code) inviteCode(code)
.then(({ acceptedRoomNames, err }) => { .then(({ acceptedRoomNames, err }) => {
if (err) { if (err) {
alert(err); alertAll(err);
} else { } else {
acceptedRoomNames = acceptedRoomNames; acceptedRoomNames = acceptedRoomNames;
} }
}) })
.catch((err) => { .catch((err) => {
alert(err); alertAll(err);
}) })
.finally(() => { .finally(() => {
loading = false; loading = false;

View File

@@ -7,21 +7,23 @@
import { getModalStore, type ModalSettings } from '@skeletonlabs/skeleton'; import { getModalStore, type ModalSettings } from '@skeletonlabs/skeleton';
import { deriveKey, hashPassword } from '$lib/crypto/crypto'; import { deriveKey, hashPassword } from '$lib/crypto/crypto';
import { onMount } from 'svelte'; import { onMount } from 'svelte';
import { setPassword } from '$lib/utils'; import { alertAll, setPassword } from '$lib/utils';
const modalStore = getModalStore(); const modalStore = getModalStore();
export let cls: string = ''; export let cls: string = '';
let minPasswordLength = 3;
function setPasswordModal() { function setPasswordModal() {
const modal: ModalSettings = { const modal: ModalSettings = {
type: 'prompt', type: 'prompt',
title: 'Set a Password', title: 'Set a Password',
body: 'Set a password to encrypt your identity and room passwords', body: 'Set a password or pin to encrypt your identity and room passwords',
value: '', value: '',
valueAttr: { type: 'password', minlength: 3, required: true }, valueAttr: { type: 'password', minlength: minPasswordLength, required: true },
response: async (r: string) => { response: async (r: string) => {
setPassword(r); if (r != '' && r != null && r != undefined && r.length >= minPasswordLength) {
setPassword(r);
}
} }
}; };
modalStore.trigger(modal); modalStore.trigger(modal);
@@ -33,13 +35,14 @@
title: 'Unlock', title: 'Unlock',
body: 'Enter your password to unlock your keystores', body: 'Enter your password to unlock your keystores',
value: '', value: '',
valueAttr: { type: 'password', minlength: 3, required: true }, valueAttr: { type: 'password', minlength: 4, required: true },
response: async (r: string) => { response: async (r: string) => {
if (r != 'false') { if (r != 'false' && r != '' && r != null && r != undefined) {
const hashedPassword = await hashPassword(r); const hashedPassword = await hashPassword(r);
if ($configStore.hashedPwd == hashedPassword) { if ($configStore.hashedPwd == hashedPassword) {
$keyStore = await deriveKey(r); $keyStore = await deriveKey(r);
} else { } else {
alertAll('Incorrect Password');
$keyStore = null; $keyStore = null;
} }
} }
@@ -53,7 +56,7 @@
} }
onMount(() => { onMount(() => {
console.debug( console.debug(
'PasswordLock: ', 'PadLock:',
$passwordSet ? 'password set,' : 'password not set,', $passwordSet ? 'password set,' : 'password not set,',
$keyStore !== null && $keyStore !== undefined ? 'unlocked' : 'locked' $keyStore !== null && $keyStore !== undefined ? 'unlocked' : 'locked'
); );
@@ -64,14 +67,14 @@
{#if $passwordSet} {#if $passwordSet}
{#if $keyStore instanceof CryptoKey} {#if $keyStore instanceof CryptoKey}
<div on:click={lock} title="Unlocked, click to lock"> <div on:click={lock} title="Unlocked, click to lock">
<LockOpen class="text-warning-300-600-token" /> <LockOpen class="w-full text-warning-300-600-token" />
</div> </div>
{:else} {:else}
<div on:click={unlock} title="Locked" class="text-success-500"><Lock /></div> <div on:click={unlock} title="Locked" class="w-full text-success-500"><Lock /></div>
{/if} {/if}
{:else} {:else}
<div on:click={setPasswordModal} title="Password not set"> <div on:click={setPasswordModal} title="Password not set">
<NoPassword class="text-primary-500" /> <NoPassword class="w-full text-error-500" />
</div> </div>
{/if} {/if}
</div> </div>

View File

@@ -0,0 +1,24 @@
<script lang="ts">
import Button from '$lib/components/button.svelte';
import { identityExists } from '$lib/stores';
</script>
{#if !$identityExists}
<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-primary btn-sm 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 btn-sm m-2 sm:m-3">Discord</Button
>
</p>
{:else}
<p>
It looks like you are already signed up!
<Button link="/chat" cls="variant-ghost-success">Go Chat</Button>
</p>
{/if}

View File

@@ -10,8 +10,11 @@ export const defaultServers = {
export const configDefaults: ConfigurationI = { export const configDefaults: ConfigurationI = {
signUpStatus: { signUpStatus: {
inviteAccepted: false, completedSignup: false,
identityBackedUp: false identityBackedUp: false
}, },
identityStore: IdentityStoreE.NO_IDENTITY identityStore: IdentityStoreE.NO_IDENTITY,
numMessagesToSave: 500,
hashedPwd: undefined,
beta: false
}; };

View File

@@ -23,6 +23,14 @@ export async function getMessages(serverUrl: string, roomId: string) {
return get([serverUrl, `api/room/${roomId}/messages`]) as Promise<MessageI[]>; return get([serverUrl, `api/room/${roomId}/messages`]) as Promise<MessageI[]>;
} }
interface CreateInviteData {
numCodes: number;
expiresAt?: number;
usesLeft?: number;
roomIds?: string[];
all?: boolean;
}
export async function createInvite( export async function createInvite(
serverUrl: string, serverUrl: string,
username: string, username: string,
@@ -32,7 +40,7 @@ export async function createInvite(
expiresAt?: number, expiresAt?: number,
usesLeft?: number usesLeft?: number
) { ) {
const data = { numCodes, expiresAt, usesLeft }; const data: CreateInviteData = { numCodes, expiresAt, usesLeft };
if (roomIds.length > 0) { if (roomIds.length > 0) {
data['roomIds'] = roomIds; data['roomIds'] = roomIds;
} else { } else {

View File

@@ -129,3 +129,14 @@ export const identityStore = storable({} as IdentityStoreI, 'identity');
* @description Identity store, this is the user's identity ENCRYPTED * @description Identity store, this is the user's identity ENCRYPTED
*/ */
export const identityKeyStore = encryptable({} as IdentityStoreI, 'identityencrypted'); export const identityKeyStore = encryptable({} as IdentityStoreI, 'identityencrypted');
export const identityExists = derived(
[identityStore, identityKeyStore],
([$identityStore, $identityKeyStore]) => {
if ($identityStore._commitment || $identityKeyStore._commitment) {
return true;
} else {
return false;
}
}
);

View File

@@ -6,6 +6,7 @@ export enum Experiences {
export enum IdentityStoreE { export enum IdentityStoreE {
'NO_IDENTITY', 'NO_IDENTITY',
'localStorage', 'localStorage',
'localStorageEncrypted',
'cryptKeeper', 'cryptKeeper',
'PCDPass' 'PCDPass'
} }

View File

@@ -36,10 +36,8 @@ export interface JoinResponseI {
roomIds: string[]; roomIds: string[];
} }
// Keyed by roomId
export interface SignUpStatusI { export interface SignUpStatusI {
inviteAccepted: boolean; completedSignup: boolean;
identityBackedUp: boolean; identityBackedUp: boolean;
inviteCode?: string; inviteCode?: string;
} }

View File

@@ -30,3 +30,12 @@ export const clearConsoleMessages = () => {
return newState; return newState;
}); });
}; };
export function isInputFieldFocused() {
const activeElement = document.activeElement;
return (
activeElement &&
(activeElement.tagName.toLowerCase() === 'input' ||
activeElement.tagName.toLowerCase() === 'textarea')
);
}

View File

@@ -65,35 +65,35 @@ export function getIdentity(): IdentityStoreI {
export function getCommitment() { export function getCommitment() {
const id = get(identityKeyStore) as IdentityStoreI; const id = get(identityKeyStore) as IdentityStoreI;
const id_old = get(identityStore); const id_ = get(identityStore);
if (id !== null && id !== undefined) { if (id !== null && id !== undefined) {
return id._commitment; return id._commitment;
} }
if (id_old !== null && id_old !== undefined) { if (id_ !== null && id_ !== undefined) {
console.warn('PLEASE ADD A PASSWORD!'); console.warn('PLEASE ADD A PASSWORD!');
return id_old._commitment; return id_._commitment;
} }
} }
export function getIdentityBackup() { export function getIdentityBackup() {
const id = get(identityKeyStore); const id = get(identityKeyStore);
const id_old = get(identityStore); const id_ = get(identityStore);
if (id !== null && id !== undefined) { if (id !== null && id !== undefined) {
return JSON.stringify(id); return JSON.stringify(id);
} }
if (id_old !== null && id_old !== undefined) { if (id_ !== null && id_ !== undefined) {
console.warn('PLEASE ADD A PASSWORD!'); console.warn('PLEASE ADD A PASSWORD!');
return JSON.stringify(id_old); return JSON.stringify(id_);
} }
} }
export function doesIdentityExist(): 'safe' | 'unsafe' | 'none' { export function doesIdentityExist(): 'safe' | 'unsafe' | 'none' {
const id = get(identityKeyStore); const id = get(identityKeyStore);
const id_old = get(identityStore); const id_ = get(identityStore);
if (id._commitment !== null && id._commitment !== undefined) { if (id._commitment !== null && id._commitment !== undefined) {
return 'safe'; return 'safe';
} }
if (id_old._commitment !== null && id_old._commitment !== undefined) { if (id_._commitment !== null && id_._commitment !== undefined) {
console.warn('PLEASE ADD A PASSWORD'); console.warn('PLEASE ADD A PASSWORD');
return 'unsafe'; return 'unsafe';
} }

View File

@@ -1,4 +1,4 @@
import { getCommitment, updateRooms } from '$lib/utils/'; import { alertAll, getCommitment, updateRooms } from '$lib/utils/';
import { postInviteCode } from '$lib/services/server'; import { postInviteCode } from '$lib/services/server';
import { selectedServer, configStore } from '$lib/stores'; import { selectedServer, configStore } from '$lib/stores';
import { get } from 'svelte/store'; import { get } from 'svelte/store';
@@ -10,6 +10,11 @@ export async function inviteCode(newCode: string) {
const server = get(selectedServer); const server = get(selectedServer);
try { try {
const idc = getCommitment(); const idc = getCommitment();
if (!idc) {
// TODO convert this to alertAll at some point
alertAll('No identity commitment found');
throw new Error('No identity commitment found');
}
const result = (await postInviteCode(server, { const result = (await postInviteCode(server, {
code: newCode.toLowerCase(), code: newCode.toLowerCase(),
idc idc
@@ -20,7 +25,7 @@ export async function inviteCode(newCode: string) {
acceptedRoomNames = await updateRooms(server, result.roomIds); acceptedRoomNames = await updateRooms(server, result.roomIds);
console.log(`Added to rooms: ${acceptedRoomNames}`); console.log(`Added to rooms: ${acceptedRoomNames}`);
configStore.update((store) => { configStore.update((store) => {
store['signUpStatus']['inviteAccepted'] = true; store['signUpStatus']['completedSignup'] = true;
store['signUpStatus']['inviteCode'] = ''; store['signUpStatus']['inviteCode'] = '';
return store; return store;
}); });

View File

@@ -20,9 +20,11 @@ export function getEpochFromTimestamp(
let relative = ''; let relative = '';
try { try {
relative = formatRelative(new Date(timestamp), new Date()); relative = formatRelative(new Date(timestamp), new Date());
} catch (err) { } catch (err: unknown) {
let message = 'Unknown Error';
if (err instanceof Error) message = err.message;
relative = 'Unknown'; relative = 'Unknown';
console.debug(`${err.message}: ${epoch} * ${ratelimit} = ${timestamp}`); console.debug(`${message}: ${epoch} * ${ratelimit} = ${timestamp}`);
} }
return { epoch, relative, timestamp }; return { epoch, relative, timestamp };
} }

View File

@@ -43,6 +43,11 @@ function updateRoomStore(rooms: RoomI[], serverURL: string = get(selectedServer)
async function getRoomIdsIfEmpty(server: string, roomIds: string[]): Promise<string[]> { async function getRoomIdsIfEmpty(server: string, roomIds: string[]): Promise<string[]> {
if (roomIds.length < 1) { if (roomIds.length < 1) {
const idc = getCommitment(); const idc = getCommitment();
if (!idc) {
// TODO convert this to alertAll at some point
console.error('No identity commitment found');
throw new Error('No identity commitment found');
}
return await getRoomIdsByIdentityCommitment(server, idc); return await getRoomIdsByIdentityCommitment(server, idc);
} }
return roomIds; return roomIds;

View File

@@ -1,5 +1,5 @@
import { get, type Writable } from 'svelte/store'; import { get, type Writable } from 'svelte/store';
import type { serverStoreI } from '../stores'; import type { serverStoreI } from '../types/stores';
import { serverStore, roomsStore, selectedServer } from '../stores'; import { serverStore, roomsStore, selectedServer } from '../stores';
import { getServerData } from '$lib/services/server'; import { getServerData } from '$lib/services/server';
import type { RoomI } from '$lib/types'; import type { RoomI } from '$lib/types';
@@ -19,7 +19,7 @@ export function getServerRooms(url: string, store: Writable<serverStoreI> = serv
if (!roomIds) { if (!roomIds) {
roomIds = []; roomIds = [];
} }
return roomIds.map((roomId) => { return roomIds.map((roomId: string) => {
return get(roomsStore)[roomId]; return get(roomsStore)[roomId];
}) as RoomI[]; }) as RoomI[];
} }

View File

@@ -1,19 +1,23 @@
<script lang="ts"> <script lang="ts">
import { AppShell, Modal, initializeStores } from '@skeletonlabs/skeleton'; import { Modal, initializeStores } from '@skeletonlabs/skeleton';
import { Toast } from '@skeletonlabs/skeleton'; import { Toast } from '@skeletonlabs/skeleton';
import '../app.postcss'; import '../app.postcss';
import { onMount } from 'svelte'; import { onMount } from 'svelte';
import AppHeader from './AppHeader.svelte'; import AppHeader from './AppHeader.svelte';
import Loading from '$lib/components/loading.svelte'; import Loading from '$lib/components/loading.svelte';
import { selectedServer } from '$lib/stores'; import { selectedServer } from '$lib/stores';
import { getServerList, setDefaultServers } from '$lib/utils/'; import { getServerList, isInputFieldFocused, setDefaultServers } from '$lib/utils/';
import { updateServer } from '$lib/utils/'; import { updateServer } from '$lib/utils/';
import { Drawer } from '@skeletonlabs/skeleton'; import { Drawer, getDrawerStore } from '@skeletonlabs/skeleton';
import SelectServer from '$lib/components/SelectServer.svelte'; import SelectServer from '$lib/components/SelectServer.svelte';
import SelectRoom from '$lib/components/SelectRoom.svelte'; import SelectRoom from '$lib/components/SelectRoom.svelte';
import Console from './console/Console.svelte';
import Sidebar from './Sidebar.svelte';
import AppFooter from './AppFooter.svelte';
initializeStores(); initializeStores();
const drawerStore = getDrawerStore();
// Hack to get BigInt <-> JSON compatibility // Hack to get BigInt <-> JSON compatibility
(BigInt.prototype as any).toJSON = function () { (BigInt.prototype as any).toJSON = function () {
return this.toString(); return this.toString();
@@ -25,19 +29,48 @@
setDefaultServers(); setDefaultServers();
} }
updateServer($selectedServer); updateServer($selectedServer);
document.addEventListener('keydown', function (event) {
if (event.key === '`') {
if (!isInputFieldFocused()) {
event.preventDefault();
if ($drawerStore.open !== true) {
drawerStore.open({ id: 'console' });
} else {
drawerStore.close();
}
} else {
console.log('Input field focused, not opening console');
}
}
});
}); });
</script> </script>
<Modal /> <Modal />
<Toast position="t" background="variant-filled-primary" /> <Toast position="t" background="variant-filled-primary" />
<Drawer position="top" padding="p-4" rounded="rounded-token"> <Drawer position="top" padding="p-4" rounded="rounded-token">
<SelectServer /> {#if $drawerStore.id === 'roomselect'}
<SelectRoom /> <SelectServer />
<SelectRoom />
{:else if $drawerStore.id === 'console'}
<Console />
{/if}
</Drawer> </Drawer>
<AppShell> <div class="w-full h-screen flex flex-col overflow-hidden">
<svelte:fragment slot="header"><AppHeader /></svelte:fragment> <div class="flex-none z-10"><AppHeader /></div>
<slot> <div class="grid grid-cols-[1fr,auto] h-full min-w-full justify-between">
<Loading /> <main class="flex flex-col justify-between">
</slot> <slot class="flex flex-col justify-center">
</AppShell> <Loading />
</slot>
<div class="block lg:hidden">
<AppFooter />
</div>
</main>
<div class="hidden lg:block"><Sidebar /></div>
</div>
</div>
<style>
</style>

View File

@@ -1,7 +1,5 @@
<script lang="ts"> <script lang="ts">
import Button from '$lib/components/button.svelte'; import Welcome from '$lib/components/Welcome.svelte';
import { identityStore } from '$lib/stores';
$: identityExists = !!$identityStore._commitment;
console.info( console.info(
'I see you are checking out the logs, let us know what you think on our discord: https://discord.gg/brJQ36KVxk' 'I see you are checking out the logs, let us know what you think on our discord: https://discord.gg/brJQ36KVxk'
@@ -10,21 +8,6 @@
<div class="mx-5 lg:mx-auto mt-10 max-w-[80ch]"> <div class="mx-5 lg:mx-auto mt-10 max-w-[80ch]">
<h2 class="h2 mb-5">Welcome to Discreetly!</h2> <h2 class="h2 mb-5">Welcome to Discreetly!</h2>
{#if !identityExists} <Welcome />
<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-primary btn-sm 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 btn-sm m-2 sm:m-3">Discord</Button
>
</p>
{:else}
<Button link="/chat" cls="variant-ghost-success">Go Chat</Button>
{/if}
<slot /> <slot />
</div> </div>

View File

@@ -1,27 +1,97 @@
<script lang="ts"> <script lang="ts">
import { identityStore } from '$lib/stores'; import { page } from '$app/stores';
import { AppBar } from '@skeletonlabs/skeleton'; import { passwordSet, configStore, keyStore } from '$lib/stores';
$: identityExists = !!$identityStore._commitment; import { getModalStore, type ModalSettings } from '@skeletonlabs/skeleton';
import { deriveKey, hashPassword } from '$lib/crypto/crypto';
import { onMount } from 'svelte';
import { TabAnchor, TabGroup } from '@skeletonlabs/skeleton';
import Chat from 'svelte-material-icons/Chat.svelte';
import Settings from 'svelte-material-icons/TuneVariant.svelte';
import Information from 'svelte-material-icons/Information.svelte';
import Lock from 'svelte-material-icons/Lock.svelte';
import LockOpen from 'svelte-material-icons/LockOpenVariant.svelte';
import NoPassword from 'svelte-material-icons/LockOff.svelte';
import { alertAll } from '$lib/utils';
const modalStore = getModalStore();
function unlock() {
const modal: ModalSettings = {
type: 'prompt',
title: 'Unlock',
body: 'Enter your password to unlock your keystores',
value: '',
valueAttr: { type: 'password', minlength: 4, required: true },
response: async (r: string) => {
if (r != 'false' && r != '' && r != null && r != undefined) {
const hashedPassword = await hashPassword(r);
if ($configStore.hashedPwd == hashedPassword) {
$keyStore = await deriveKey(r);
} else {
alertAll('Incorrect Password');
$keyStore = null;
}
}
}
};
modalStore.trigger(modal);
}
function lock() {
$keyStore = null;
}
onMount(() => {
console.debug(
'PadLock:',
$passwordSet ? 'password set,' : 'password not set,',
$keyStore !== null && $keyStore !== undefined ? 'unlocked' : 'locked'
);
});
</script> </script>
<AppBar class="hidden md:block" padding="py-3 px-8"> <TabGroup
<svelte:fragment slot="lead"> justify="justify-center"
<a href="/">Home</a> active="variant-filled-primary"
{#if identityExists} hover="hover:variant-soft-primary"
<a href="/chat">Chat</a> flex="flex-1 lg:flex-none"
{:else} class="bg-surface-100-800-token w-full"
<a href="/signup">Sign Up</a> >
{/if} <TabAnchor href="/about" selected={$page.url.pathname === '/about'} title="About">
<a href="/about">About</a> <svelte:fragment slot="lead"><Information class="rail-icon" /></svelte:fragment>
</svelte:fragment> <span>About</span>
<svelte:fragment slot="trail"> </TabAnchor>
<p class="hidden sm:inline me-2 text-primary-500">Alpha Version!</p>
<p><a href="/testing" style="margin:0 !important">© 2023 Privacy & Scaling Explorations</a></p>
</svelte:fragment>
</AppBar>
<style> <TabAnchor href="/chat" selected={$page.url.pathname === '/chat'} title="Chat">
a { <svelte:fragment slot="lead"><Chat class="rail-icon" /></svelte:fragment>
margin-right: 1.5rem; <span>Chat</span>
} </TabAnchor>
</style> {#if $passwordSet}
{#if $keyStore instanceof CryptoKey}
<TabAnchor on:click={lock} title="Unlocked, click to lock">
<svelte:fragment slot="lead">
<LockOpen class="rail-icon text-warning-300-600-token" />
</svelte:fragment>
<span>Lock</span>
</TabAnchor>
{:else}
<TabAnchor on:click={unlock} title="Locked">
<svelte:fragment slot="lead">
<Lock class="rail-icon text-success-500" />
</svelte:fragment>
<span>Unlock</span>
</TabAnchor>
{/if}
{:else}
<TabAnchor href="/settings/security" title="Password not set">
<svelte:fragment slot="lead">
<NoPassword class="rail-icon text-error-500" />
</svelte:fragment>
<span>Secure</span>
</TabAnchor>
{/if}
<TabAnchor href="/settings" selected={$page.url.pathname === '/settings'} title="Settings">
<svelte:fragment slot="lead"><Settings class="rail-icon" /></svelte:fragment>
<span>Settings</span>
</TabAnchor>
</TabGroup>

View File

@@ -1,21 +1,17 @@
<script lang="ts"> <script lang="ts">
import { AppBar } from '@skeletonlabs/skeleton'; import { AppBar, type DrawerSettings } from '@skeletonlabs/skeleton';
import { LightSwitch } from '@skeletonlabs/skeleton'; import { currentSelectedRoom, identityExists } from '$lib/stores';
import { page } from '$app/stores';
import { configStore, currentSelectedRoom, identityStore, keyStore } from '$lib/stores';
import { getDrawerStore } from '@skeletonlabs/skeleton'; import { getDrawerStore } from '@skeletonlabs/skeleton';
import Settings from 'svelte-material-icons/TuneVariant.svelte';
import Information from 'svelte-material-icons/Information.svelte';
import Console from 'svelte-material-icons/Console.svelte';
import PasswordLock from '$lib/components/Padlock.svelte';
import Chat from 'svelte-material-icons/Chat.svelte';
$: identityExists = !!$identityStore._commitment;
$: roomName = $currentSelectedRoom?.name ?? 'Select Room'; $: roomName = $currentSelectedRoom?.name ?? 'Select Room';
const drawerStore = getDrawerStore(); const drawerStore = getDrawerStore();
const settings: DrawerSettings = { id: 'roomselect' };
// Open the drawer: // Open the drawer:
function drawerOpen(): void { function drawerOpen(): void {
drawerStore.open(); drawerStore.open(settings);
} }
</script> </script>
@@ -29,43 +25,43 @@
> >
<svelte:fragment slot="lead"> <svelte:fragment slot="lead">
<h1 class="h4 text-primary-500"> <h1 class="h4 text-primary-500">
{#if identityExists} {#if $identityExists}
<a href="/chat"><img class="max-h-7" src="/logo-text.png" alt="discreetly" /></a> <a href="/chat" role="button" tabindex="0"
><img class="max-h-7" src="/logo-text.png" alt="discreetly" /></a
>
{:else} {:else}
<a href="/"><img class="max-h-7" src="/logo-text.png" alt="discreetly" /></a> <a href="/" role="button" tabindex="0"
><img class="max-h-7" src="/logo-text.png" alt="discreetly" /></a
>
{/if} {/if}
</h1> </h1>
</svelte:fragment> </svelte:fragment>
<a href="/about" class="btn btn-sm variant-ringed-secondary hidden sm:inline">About</a> {#if $identityExists}
{#if identityExists} <a
<a href="/chat" class="hidden btn btn-sm variant-ringed-secondary sm:inline">Chat</a> href="/chat"
role="button"
tabindex="0"
class="hidden btn btn-sm variant-ringed-secondary sm:inline">Chat</a
>
<!-- svelte-ignore a11y-missing-attribute -->
<a <a
class="btn btn-sm variant-ringed-secondary font-medium text-sm inline sm:hidden" class="btn btn-sm variant-ringed-secondary font-medium text-sm inline sm:hidden"
on:click={drawerOpen}>{roomName}</a on:click={drawerOpen}
on:keypress={() => {
drawerOpen();
}}
role="button"
tabindex="0"
> >
{roomName}
</a>
{:else} {:else}
<a class="btn btn-sm variant-ringed-secondary" href="/signup">Sign Up</a> <a class="btn btn-sm variant-ringed-secondary" href="/signup" role="button" tabindex="0"
>Sign Up</a
>
{/if} {/if}
<a
class="btn btn-sm variant-ringed-secondary font-medium text-sm hidden sm:inline"
href="/console">Console</a
>
<svelte:fragment slot="trail"> <svelte:fragment slot="trail">
<a href="/about" class="hidden sm:inline"><Information size="1.2em" /></a> <div class="hidden sm:inline text-primary-500">Alpha Version!</div>
{#if identityExists && $page.url.pathname !== '/chat'}
<a href="/chat" class="inline"><Chat size="1.2em" /></a>
{/if}
{#if $page.url.pathname !== '/console'}
<a href="/console" class="inline"><Console size="1.2em" /></a>
{/if}
<PasswordLock cls="inline" />
{#if identityExists}
<a href="/settings"><Settings size="1.2em" /></a>
{/if}
<div class="hidden sm:inline">
<LightSwitch />
</div>
</svelte:fragment> </svelte:fragment>
</AppBar> </AppBar>

104
src/routes/Sidebar.svelte Normal file
View File

@@ -0,0 +1,104 @@
<script lang="ts">
import { page } from '$app/stores';
import {
AppRail,
AppRailAnchor,
getModalStore,
type ModalSettings
} from '@skeletonlabs/skeleton';
import Chat from 'svelte-material-icons/Chat.svelte';
import Settings from 'svelte-material-icons/TuneVariant.svelte';
import Information from 'svelte-material-icons/Information.svelte';
import Console from 'svelte-material-icons/Console.svelte';
import Lock from 'svelte-material-icons/Lock.svelte';
import LockOpen from 'svelte-material-icons/LockOpenVariant.svelte';
import NoPassword from 'svelte-material-icons/LockOff.svelte';
import { hashPassword, deriveKey } from '$lib/crypto/crypto';
import { configStore, keyStore, passwordSet } from '$lib/stores';
import { alertAll } from '$lib/utils';
import { onMount } from 'svelte';
const modalStore = getModalStore();
function unlock() {
const modal: ModalSettings = {
type: 'prompt',
title: 'Unlock',
body: 'Enter your password to unlock your keystores',
value: '',
valueAttr: { type: 'password', minlength: 4, required: true },
response: async (r: string) => {
if (r != 'false' && r != '' && r != null && r != undefined) {
const hashedPassword = await hashPassword(r);
if ($configStore.hashedPwd == hashedPassword) {
$keyStore = await deriveKey(r);
} else {
alertAll('Incorrect Password');
$keyStore = null;
}
}
}
};
modalStore.trigger(modal);
}
function lock() {
$keyStore = null;
}
onMount(() => {
console.debug(
'PadLock:',
$passwordSet ? 'password set,' : 'password not set,',
$keyStore !== null && $keyStore !== undefined ? 'unlocked' : 'locked'
);
});
</script>
<AppRail height="h-full">
<AppRailAnchor href="/chat" selected={$page.url.pathname === '/chat'} title="Chat">
<svelte:fragment slot="lead"><Chat class="rail-icon" /></svelte:fragment>
<span>Chat</span>
</AppRailAnchor>
<AppRailAnchor href="/about" selected={$page.url.pathname === '/about'} title="About">
<svelte:fragment slot="lead"><Information class="rail-icon" /></svelte:fragment>
<span>About</span>
</AppRailAnchor>
<AppRailAnchor href="/console" selected={$page.url.pathname === '/console'} title="About">
<svelte:fragment slot="lead"><Console class="rail-icon" /></svelte:fragment>
<span>Console</span>
</AppRailAnchor>
<svelte:fragment slot="trail">
{#if $passwordSet}
{#if $keyStore instanceof CryptoKey}
<AppRailAnchor on:click={lock} title="Unlocked, click to lock">
<svelte:fragment slot="lead">
<LockOpen class="rail-icon text-warning-300-600-token" />
</svelte:fragment>
<span>Lock</span>
</AppRailAnchor>
{:else}
<AppRailAnchor on:click={unlock} title="Locked">
<svelte:fragment slot="lead">
<Lock class="rail-icon text-success-500" />
</svelte:fragment>
<span>Unlock</span>
</AppRailAnchor>
{/if}
{:else}
<AppRailAnchor href="/settings/security" title="Password not set">
<svelte:fragment slot="lead">
<NoPassword class="rail-icon text-error-500" />
</svelte:fragment>
<span>Secure</span>
</AppRailAnchor>
{/if}
<AppRailAnchor href="/settings" selected={$page.url.pathname === '/settings'} title="Settings">
<svelte:fragment slot="lead"><Settings class="rail-icon" /></svelte:fragment>
<span>Settings</span>
</AppRailAnchor>
</svelte:fragment>
</AppRail>

View File

@@ -1,13 +1,11 @@
<script lang="ts"> <script lang="ts">
import { goto } from '$app/navigation'; import { goto } from '$app/navigation';
import { identityStore, serverStore } from '$lib/stores'; import { identityExists, serverStore } from '$lib/stores';
import { updateServer } from '$lib/utils'; import { updateServer } from '$lib/utils';
import { onMount } from 'svelte'; import { onMount } from 'svelte';
$: identityExists = !!$identityStore._commitment;
onMount(() => { onMount(() => {
if (!identityExists) { if (!$identityExists) {
goto('/signup'); goto('/signup');
} }
if (!Object.keys($serverStore).length) { if (!Object.keys($serverStore).length) {

View File

@@ -2,4 +2,6 @@
import Console from './Console.svelte'; import Console from './Console.svelte';
</script> </script>
<Console /> <div class="mx-2 h-full">
<Console />
</div>

View File

@@ -3,30 +3,39 @@
import { consoleStore } from '$lib/stores'; import { consoleStore } from '$lib/stores';
import { clearConsoleMessages } from '$lib/utils'; import { clearConsoleMessages } from '$lib/utils';
import TrashCan from 'svelte-material-icons/TrashCanOutline.svelte'; import TrashCan from 'svelte-material-icons/TrashCanOutline.svelte';
import { onMount } from 'svelte';
let elemChat: HTMLElement;
export let placeholder: string = 'Enter / Command'; export let placeholder: string = 'Enter / Command';
function scrollChatBottom(behavior?: ScrollBehavior): void {
setTimeout(() => {
elemChat.scrollTo({ top: elemChat.scrollHeight, behavior });
}, 0);
}
onMount(() => {
scrollChatBottom('smooth');
});
</script> </script>
<div class="mx-3 my-2"> <div class="p-4 small:p-2 h-full overflow-y-hidden grid grid-rows-[auto,1fr,auto]">
<h3 class="h3 mb-1 small:mb-3">Console</h3> <header class="flex flex-row justify-between px-2">
<div class="card variant-ghost-surface"> <h6 class="h4">Console</h6>
<header class="card-header"> <button
<button class="btn btn-sm variant-ghost-primary float-right"
class="btn btn-sm variant-ghost-primary float-right" on:click={() => clearConsoleMessages()}
on:click={() => clearConsoleMessages()} >
> <TrashCan />
<TrashCan /> </button>
</button> </header>
</header> <section class="p-4 overflow-y-scroll" bind:this={elemChat}>
<section class="p-4"> {#each $consoleStore.messages as line, idx}
{#each $consoleStore.messages as line, idx} <p class={line.type}>{line.message}</p>
<p class={line.type}>{line.message}</p> {/each}
{/each} </section>
</section> <footer>
<footer class="card-footer"> <InputPrompt {placeholder} />
<InputPrompt {placeholder} /> </footer>
</footer>
</div>
</div> </div>
<style> <style>

View File

@@ -17,7 +17,6 @@
addConsoleMessage('`/clear` Clears the console'); addConsoleMessage('`/clear` Clears the console');
addConsoleMessage('`/join invite-code` Joins a room via invite code, Example:'); addConsoleMessage('`/join invite-code` Joins a room via invite code, Example:');
addConsoleMessage('`/password Password`'); addConsoleMessage('`/password Password`');
addConsoleMessage('`/clearPassword`');
addConsoleMessage('`/unlock Password`'); addConsoleMessage('`/unlock Password`');
addConsoleMessage('`/lock`'); addConsoleMessage('`/lock`');
addConsoleMessage('`/backup`'); addConsoleMessage('`/backup`');
@@ -75,10 +74,6 @@
addConsoleMessage('/password OLDPASSWORD NEWPASSWORD', 'warning'); addConsoleMessage('/password OLDPASSWORD NEWPASSWORD', 'warning');
} }
break; break;
case '/clearPassword':
$configStore.hashedPwd = null;
addConsoleMessage('Password Cleared');
break;
case '/lock': case '/lock':
$keyStore = null; $keyStore = null;
addConsoleMessage('Locked!'); addConsoleMessage('Locked!');

View File

@@ -0,0 +1,5 @@
<script lang="ts">
import JoinMore from './JoinMore.svelte';
</script>
<JoinMore />

View File

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

View File

@@ -1,67 +1,49 @@
<script lang="ts"> <script lang="ts">
import JoinMore from './JoinMore.svelte'; import { identityExists } from '$lib/stores';
import { identityStore } from '$lib/stores'; import DeleteIdentity from './identity/DeleteIdentity.svelte';
import { page } from '$app/stores'; import BackupIdentity from './identity/BackupIdentity.svelte';
import DeleteIdentity from './DeleteIdentity.svelte'; import RestoreIdentity from './identity/RestoreIdentity.svelte';
import BackupIdentity from './BackupIdentity.svelte';
import RestoreIdentity from './RestoreIdentity.svelte';
import { createIdentity } from '$lib/utils/'; import { createIdentity } from '$lib/utils/';
import { Tab, TabGroup } from '@skeletonlabs/skeleton'; import ActionRepresentation from './ui/ActionRepresentation.svelte';
import ActionRepresentation from './ActionRepresentation.svelte'; import IdentityIcon from 'svelte-material-icons/Account.svelte';
import { onMount } from 'svelte'; import Eye from 'svelte-material-icons/Eye.svelte';
$: identityExists = !!$identityStore._commitment;
let tabSet: number = 0;
onMount(() => {
if ($page.url.hash) {
const hash = $page.url.hash.replace('#', '');
if (hash === 'join-more') {
tabSet = 1;
}
}
});
</script> </script>
{#if !identityExists} <h2 class="h2 mb-3 sm:mb-5 text-center">Manage Settings</h2>
<div class="mb-8 text-center"> {#if !$identityExists}
<div class="mb-3 sm:mb-8 text-center">
<span class="text-base italic px-4 py-2 font-mono badge variant-outline-error" <span class="text-base italic px-4 py-2 font-mono badge variant-outline-error"
>Identity Not Found!</span >Identity Not Found!</span
> >
</div> </div>
{/if} {/if}
<div class="grid grid-flow-rows gap-5 my-5 max-w-md mx-auto"> <div class="flex flex-row flex-wrap gap-5 my-5 mx-auto justify-center">
{#if !identityExists} {#if !$identityExists}
<button <button
on:click={() => createIdentity()} on:click={() => createIdentity()}
class="btn variant-ghost-primary font-medium" class="btn variant-ghost-primary font-medium"
type="button" type="button"
> >
Generate Identity Generate New Identity
</button> </button>
<RestoreIdentity /> <RestoreIdentity />
{:else} {:else}
<TabGroup <div>
justify="justify-around" <h3 class="h3 flex flex-row gap-2 items-center"><IdentityIcon /> Identity</h3>
active="variant-soft-secondary" <div class="flex flex-col gap-3 sm:gap-5 items-stretch">
flex="flex-1 lg:flex-none" <BackupIdentity />
class="w-full" <DeleteIdentity />
> <RestoreIdentity />
<Tab bind:group={tabSet} name="id" value={0} class="center"> </div>
<span>Identity</span> </div>
</Tab> <div>
<Tab bind:group={tabSet} name="server" value={1}>Server</Tab> <h3 class="h3 flex flex-row gap-2 items-center"><Eye /> UI</h3>
<Tab bind:group={tabSet} name="misc" value={2}>Settings</Tab> <div class="flex flex-col gap-3 sm:gap-5 items-stretch">
<!-- Tab Panels ---> <ActionRepresentation />
<svelte:fragment slot="panel"> <BackupIdentity />
{#if tabSet === 0} <DeleteIdentity />
<BackupIdentity /> <RestoreIdentity />
<DeleteIdentity /> </div>
<RestoreIdentity /> </div>
{:else if tabSet === 1}
<JoinMore />
{:else if tabSet === 2}
<ActionRepresentation />
{/if}
</svelte:fragment>
</TabGroup>
{/if} {/if}
</div> </div>

View File

@@ -0,0 +1,9 @@
<script>
import BackupIdentity from './BackupIdentity.svelte';
import DeleteIdentity from './DeleteIdentity.svelte';
import RestoreIdentity from './RestoreIdentity.svelte';
</script>
<BackupIdentity />
<DeleteIdentity />
<RestoreIdentity />

View File

@@ -2,9 +2,9 @@
import BackupIdentity from '$lib/components/BackupIdentity.svelte'; import BackupIdentity from '$lib/components/BackupIdentity.svelte';
</script> </script>
<div class="card variant-ghost-secondary mb-5"> <div class="card variant-ghost-secondary">
<header class="card-header"> <header class="card-header">
<h4 class="h4">Backup Your Identity</h4> <h3 class="h4">Backup Your Identity</h3>
</header> </header>
<section class="p-2 sm:p-4"> <section class="p-2 sm:p-4">
<BackupIdentity /> <BackupIdentity />

View File

@@ -32,7 +32,7 @@
}); });
</script> </script>
<div class="card variant-ghost-error mb-5"> <div class="card variant-ghost-error">
<header class="card-header"> <header class="card-header">
<h3 class="h4">Delete Your Identity & Reset Application</h3> <h3 class="h4">Delete Your Identity & Reset Application</h3>
</header> </header>
@@ -42,7 +42,7 @@
>I promise I backed up my identity, or I really want to destroy it forever.</span >I promise I backed up my identity, or I really want to destroy it forever.</span
> >
</section> </section>
<footer class="card-footer text-center mb-2"> <footer class="card-footer text-center">
{#if !isButtonDisabled} {#if !isButtonDisabled}
<button <button
id="delete-identity" id="delete-identity"

View File

@@ -0,0 +1,5 @@
<script lang="ts">
import BackupIdentity from '../BackupIdentity.svelte';
</script>
<BackupIdentity />

View File

@@ -0,0 +1,5 @@
<script lang="ts">
import RestoreIdentity from '../RestoreIdentity.svelte';
</script>
<RestoreIdentity />

View File

@@ -0,0 +1,47 @@
<script lang="ts">
import { setPassword } from '$lib/utils';
import { passwordSet } from '$lib/stores';
const minPasswordLength = 4;
let r = '';
function onSubmit() {
if (r != '' && r != null && r != undefined && r.length >= minPasswordLength) {
setPassword(r);
}
}
</script>
<div class="max-w-lg mx-auto">
{#if !$passwordSet}
<h2 class="h3 mb-3">Set A Password!</h2>
<p class="mt-3">
In order to secure your identity and other sensitive data that is stored in your browser, we
need you to set a password.
</p>
<p class="my-3">
This password is only used locally, there is not username and password for your account, so
don't forget to <a href="/settings/backup" title="Backup Identity">backup your identity</a>.
</p>
<form on:submit|preventDefault={() => onSubmit()} class="flex flex-col w-full">
<label for="setPasswordInput" class="label" />
<input
id="setPasswordInput"
type="password"
name="password"
class="input"
bind:value={r}
minlength={minPasswordLength}
required
/>
<small>Minimum password length: {minPasswordLength}</small>
<button
class="btn variant-filled-primary mt-3"
disabled={r.length < minPasswordLength}
type="submit">Set Password</button
>
</form>
{:else}
<h2 class="h3">Password Set!</h2>
{/if}
</div>

View File

@@ -0,0 +1,39 @@
<script lang="ts">
import { configStore, identityKeyStore, identityStore, serverStore } from '$lib/stores';
import { IdentityStoreE } from '$lib/types';
</script>
<div id="status" class="flex flex-col gap-5">
<div>
<h2 class="h3">configStore</h2>
<div>Completed Signup: {JSON.stringify($configStore.signUpStatus.completedSignup)}</div>
<div>Identity Backedup: {JSON.stringify($configStore.signUpStatus.identityBackedUp)}</div>
<div>IdentityStore Type: {IdentityStoreE[$configStore.identityStore]}</div>
<div>Beta: {JSON.stringify($configStore.beta)}</div>
<div>Hashed Password: {JSON.stringify($configStore.hashedPwd)}</div>
</div>
<div>
<h2 class="h3">Identity Data</h2>
{#each Object.keys($identityStore) as key}
<div>Unprotected {key}: {JSON.stringify($identityStore[key])}</div>
{/each}
{#each Object.keys($identityKeyStore) as key}
<div>Protected {key}: {JSON.stringify($identityKeyStore[key])}</div>
{/each}
</div>
<div>
<h2 class="h3">Server Data</h2>
{#each Object.keys($serverStore) as key}
<div>{$serverStore[key].name}:</div>
<div class="ps-5">id: {$serverStore[key].id}</div>
<div class="ps-5">version: {$serverStore[key].version}</div>
<div class="ps-5">url: {key}</div>
{/each}
</div>
</div>
<style>
#status div div {
margin-left: 1.25rem;
}
</style>

View File

@@ -0,0 +1,5 @@
<script>
import ActionRepresentation from './ActionRepresentation.svelte';
</script>
<ActionRepresentation />

View File

@@ -2,6 +2,8 @@
import AP from '$lib/components/AP.svelte'; import AP from '$lib/components/AP.svelte';
import { configStore } from '$lib/stores'; import { configStore } from '$lib/stores';
import { ActionRepresentationE } from '$lib/types'; import { ActionRepresentationE } from '$lib/types';
import { RangeSlider } from '@skeletonlabs/skeleton';
import { max } from 'date-fns';
$: if ($configStore.actionRepresentation == undefined) { $: if ($configStore.actionRepresentation == undefined) {
$configStore.actionRepresentation = ActionRepresentationE.AP; $configStore.actionRepresentation = ActionRepresentationE.AP;
} }
@@ -12,7 +14,7 @@
<div class="card variant-soft-secondary"> <div class="card variant-soft-secondary">
<header class="card-header"> <header class="card-header">
<h4 class="h4">Action Representation</h4> <h3 class="h4">Action Representation</h3>
</header> </header>
<section class="p-2 mb-2 sm:p-4 sm:mb-4"> <section class="p-2 mb-2 sm:p-4 sm:mb-4">
<div class="flex flex-col"> <div class="flex flex-col">
@@ -31,14 +33,18 @@
<div class="border-t mt-2 pt-2 mb-2 sm:mb-4 flex flex-col place-items-center"> <div class="border-t mt-2 pt-2 mb-2 sm:mb-4 flex flex-col place-items-center">
<h6 class="h6">Demo</h6> <h6 class="h6">Demo</h6>
<AP {health} {maxHealth} /> <AP {health} {maxHealth} />
<label <RangeSlider name="range-slider" bind:value={health} max={maxHealth} step={1} ticked>
><div>Health: {health}</div> <div class="flex justify-between items-center">
<input type="range" min="0" max={maxHealth} bind:value={health} /> <div class="font-bold">Health</div>
</label> <div class="text-xs">{health} / {maxHealth}</div>
<label </div>
><div>Max Health: {maxHealth}</div> </RangeSlider>
<input type="range" min="1" max={10} bind:value={maxHealth} /> <RangeSlider name="range-slider" bind:value={maxHealth} max={10} step={1} ticked>
</label> <div class="flex justify-between items-center">
<div class="font-bold">Max Health</div>
<div class="text-xs">{maxHealth} / {10}</div>
</div>
</RangeSlider>
</div> </div>
</div> </div>
</section> </section>

View File

@@ -46,7 +46,6 @@
}); });
</script> </script>
<Console placeholder="/join invite-code-goes-here" />
<div> <div>
<a href="/settings" class="btn btn-sm variant-ghost-primary"> <a href="/settings" class="btn btn-sm variant-ghost-primary">
<span>Restore Identity</span> <span>Restore Identity</span>

View File

@@ -1,16 +1,21 @@
# Installing Webfonts # Installing Webfonts
Follow these simple Steps. Follow these simple Steps.
## 1. ## 1.
Put `nippo/` Folder into a Folder called `fonts/`. Put `nippo/` Folder into a Folder called `fonts/`.
## 2. ## 2.
Put `nippo.css` into your `css/` Folder. Put `nippo.css` into your `css/` Folder.
## 3. (Optional) ## 3. (Optional)
You may adapt the `url('path')` in `nippo.css` depends on your Website Filesystem. You may adapt the `url('path')` in `nippo.css` depends on your Website Filesystem.
## 4. ## 4.
Import `nippo.css` at the top of you main Stylesheet. Import `nippo.css` at the top of you main Stylesheet.
``` ```
@@ -19,7 +24,6 @@ Import `nippo.css` at the top of you main Stylesheet.
## 5. ## 5.
``` ```
font-family: 'Nippo-Variable'; font-family: 'Nippo-Variable';
font-family: 'Nippo-Extralight'; font-family: 'Nippo-Extralight';
@@ -28,4 +32,3 @@ font-family: 'Nippo-Regular';
font-family: 'Nippo-Medium'; font-family: 'Nippo-Medium';
font-family: 'Nippo-Bold'; font-family: 'Nippo-Bold';
``` ```

View File

@@ -16,7 +16,6 @@
* *
*/ */
/** /**
* This is a variable font * This is a variable font
* You can controll variable axes as shown below: * You can controll variable axes as shown below:
@@ -29,67 +28,61 @@
*/ */
@font-face { @font-face {
font-family: 'Nippo-Variable'; font-family: 'Nippo-Variable';
src: url('../fonts/Nippo-Variable.woff2') format('woff2'), src: url('../fonts/Nippo-Variable.woff2') format('woff2'),
url('../fonts/Nippo-Variable.woff') format('woff'), url('../fonts/Nippo-Variable.woff') format('woff'),
url('../fonts/Nippo-Variable.ttf') format('truetype'); url('../fonts/Nippo-Variable.ttf') format('truetype');
font-weight: 200 700; font-weight: 200 700;
font-display: swap; font-display: swap;
font-style: normal; font-style: normal;
} }
@font-face { @font-face {
font-family: 'Nippo-Extralight'; font-family: 'Nippo-Extralight';
src: url('../fonts/Nippo-Extralight.woff2') format('woff2'), src: url('../fonts/Nippo-Extralight.woff2') format('woff2'),
url('../fonts/Nippo-Extralight.woff') format('woff'), url('../fonts/Nippo-Extralight.woff') format('woff'),
url('../fonts/Nippo-Extralight.ttf') format('truetype'); url('../fonts/Nippo-Extralight.ttf') format('truetype');
font-weight: 200; font-weight: 200;
font-display: swap; font-display: swap;
font-style: normal; font-style: normal;
} }
@font-face { @font-face {
font-family: 'Nippo-Light'; font-family: 'Nippo-Light';
src: url('../fonts/Nippo-Light.woff2') format('woff2'), src: url('../fonts/Nippo-Light.woff2') format('woff2'),
url('../fonts/Nippo-Light.woff') format('woff'), url('../fonts/Nippo-Light.woff') format('woff'),
url('../fonts/Nippo-Light.ttf') format('truetype'); url('../fonts/Nippo-Light.ttf') format('truetype');
font-weight: 300; font-weight: 300;
font-display: swap; font-display: swap;
font-style: normal; font-style: normal;
} }
@font-face { @font-face {
font-family: 'Nippo-Regular'; font-family: 'Nippo-Regular';
src: url('../fonts/Nippo-Regular.woff2') format('woff2'), src: url('../fonts/Nippo-Regular.woff2') format('woff2'),
url('../fonts/Nippo-Regular.woff') format('woff'), url('../fonts/Nippo-Regular.woff') format('woff'),
url('../fonts/Nippo-Regular.ttf') format('truetype'); url('../fonts/Nippo-Regular.ttf') format('truetype');
font-weight: 400; font-weight: 400;
font-display: swap; font-display: swap;
font-style: normal; font-style: normal;
} }
@font-face { @font-face {
font-family: 'Nippo-Medium'; font-family: 'Nippo-Medium';
src: url('../fonts/Nippo-Medium.woff2') format('woff2'), src: url('../fonts/Nippo-Medium.woff2') format('woff2'),
url('../fonts/Nippo-Medium.woff') format('woff'), url('../fonts/Nippo-Medium.woff') format('woff'),
url('../fonts/Nippo-Medium.ttf') format('truetype'); url('../fonts/Nippo-Medium.ttf') format('truetype');
font-weight: 500; font-weight: 500;
font-display: swap; font-display: swap;
font-style: normal; font-style: normal;
} }
@font-face { @font-face {
font-family: 'Nippo-Bold'; font-family: 'Nippo-Bold';
src: url('../fonts/Nippo-Bold.woff2') format('woff2'), src: url('../fonts/Nippo-Bold.woff2') format('woff2'),
url('../fonts/Nippo-Bold.woff') format('woff'), url('../fonts/Nippo-Bold.woff') format('woff'),
url('../fonts/Nippo-Bold.ttf') format('truetype'); url('../fonts/Nippo-Bold.ttf') format('truetype');
font-weight: 700; font-weight: 700;
font-display: swap; font-display: swap;
font-style: normal; font-style: normal;
} }

View File

@@ -1,41 +1,41 @@
{ {
"name": "App", "name": "App",
"icons": [ "icons": [
{ {
"src": "\/android-icon-36x36.png", "src": "/android-icon-36x36.png",
"sizes": "36x36", "sizes": "36x36",
"type": "image\/png", "type": "image/png",
"density": "0.75" "density": "0.75"
}, },
{ {
"src": "\/android-icon-48x48.png", "src": "/android-icon-48x48.png",
"sizes": "48x48", "sizes": "48x48",
"type": "image\/png", "type": "image/png",
"density": "1.0" "density": "1.0"
}, },
{ {
"src": "\/android-icon-72x72.png", "src": "/android-icon-72x72.png",
"sizes": "72x72", "sizes": "72x72",
"type": "image\/png", "type": "image/png",
"density": "1.5" "density": "1.5"
}, },
{ {
"src": "\/android-icon-96x96.png", "src": "/android-icon-96x96.png",
"sizes": "96x96", "sizes": "96x96",
"type": "image\/png", "type": "image/png",
"density": "2.0" "density": "2.0"
}, },
{ {
"src": "\/android-icon-144x144.png", "src": "/android-icon-144x144.png",
"sizes": "144x144", "sizes": "144x144",
"type": "image\/png", "type": "image/png",
"density": "3.0" "density": "3.0"
}, },
{ {
"src": "\/android-icon-192x192.png", "src": "/android-icon-192x192.png",
"sizes": "192x192", "sizes": "192x192",
"type": "image\/png", "type": "image/png",
"density": "4.0" "density": "4.0"
} }
] ]
} }

View File

@@ -1,114 +1,105 @@
{ {
"protocol": "groth16", "protocol": "groth16",
"curve": "bn128", "curve": "bn128",
"nPublic": 5, "nPublic": 5,
"vk_alpha_1": [ "vk_alpha_1": [
"20491192805390485299153009773594534940189261866228447918068658471970481763042", "20491192805390485299153009773594534940189261866228447918068658471970481763042",
"9383485363053290200918347156157836566562967994039712273449902621266178545958", "9383485363053290200918347156157836566562967994039712273449902621266178545958",
"1" "1"
], ],
"vk_beta_2": [ "vk_beta_2": [
[ [
"6375614351688725206403948262868962793625744043794305715222011528459656738731", "6375614351688725206403948262868962793625744043794305715222011528459656738731",
"4252822878758300859123897981450591353533073413197771768651442665752259397132" "4252822878758300859123897981450591353533073413197771768651442665752259397132"
], ],
[ [
"10505242626370262277552901082094356697409835680220590971873171140371331206856", "10505242626370262277552901082094356697409835680220590971873171140371331206856",
"21847035105528745403288232691147584728191162732299865338377159692350059136679" "21847035105528745403288232691147584728191162732299865338377159692350059136679"
], ],
[ ["1", "0"]
"1", ],
"0" "vk_gamma_2": [
] [
], "10857046999023057135944570762232829481370756359578518086990519993285655852781",
"vk_gamma_2": [ "11559732032986387107991004021392285783925812861821192530917403151452391805634"
[ ],
"10857046999023057135944570762232829481370756359578518086990519993285655852781", [
"11559732032986387107991004021392285783925812861821192530917403151452391805634" "8495653923123431417604973247489272438418190587263600148770280649306958101930",
], "4082367875863433681332203403145435568316851327593401208105741076214120093531"
[ ],
"8495653923123431417604973247489272438418190587263600148770280649306958101930", ["1", "0"]
"4082367875863433681332203403145435568316851327593401208105741076214120093531" ],
], "vk_delta_2": [
[ [
"1", "3817638527075299341910923013750491829772293424386698324526317429044327254930",
"0" "16817041674734304676071688134655235316477394149172545683919861305510126014325"
] ],
], [
"vk_delta_2": [ "15321616806892601534034512759307826486280009165298528953245727688557811743473",
[ "6325777744419158965546141708181579937852352378408429766464847802592525079966"
"3817638527075299341910923013750491829772293424386698324526317429044327254930", ],
"16817041674734304676071688134655235316477394149172545683919861305510126014325" ["1", "0"]
], ],
[ "vk_alphabeta_12": [
"15321616806892601534034512759307826486280009165298528953245727688557811743473", [
"6325777744419158965546141708181579937852352378408429766464847802592525079966" [
], "2029413683389138792403550203267699914886160938906632433982220835551125967885",
[ "21072700047562757817161031222997517981543347628379360635925549008442030252106"
"1", ],
"0" [
] "5940354580057074848093997050200682056184807770593307860589430076672439820312",
], "12156638873931618554171829126792193045421052652279363021382169897324752428276"
"vk_alphabeta_12": [ ],
[ [
[ "7898200236362823042373859371574133993780991612861777490112507062703164551277",
"2029413683389138792403550203267699914886160938906632433982220835551125967885", "7074218545237549455313236346927434013100842096812539264420499035217050630853"
"21072700047562757817161031222997517981543347628379360635925549008442030252106" ]
], ],
[ [
"5940354580057074848093997050200682056184807770593307860589430076672439820312", [
"12156638873931618554171829126792193045421052652279363021382169897324752428276" "7077479683546002997211712695946002074877511277312570035766170199895071832130",
], "10093483419865920389913245021038182291233451549023025229112148274109565435465"
[ ],
"7898200236362823042373859371574133993780991612861777490112507062703164551277", [
"7074218545237549455313236346927434013100842096812539264420499035217050630853" "4595479056700221319381530156280926371456704509942304414423590385166031118820",
] "19831328484489333784475432780421641293929726139240675179672856274388269393268"
], ],
[ [
[ "11934129596455521040620786944827826205713621633706285934057045369193958244500",
"7077479683546002997211712695946002074877511277312570035766170199895071832130", "8037395052364110730298837004334506829870972346962140206007064471173334027475"
"10093483419865920389913245021038182291233451549023025229112148274109565435465" ]
], ]
[ ],
"4595479056700221319381530156280926371456704509942304414423590385166031118820", "IC": [
"19831328484489333784475432780421641293929726139240675179672856274388269393268" [
], "4920513730204767532050733107749276406754520419375654722016092399980613788208",
[ "10950491564509418434657706642388934308456795265036074733953533582377584967294",
"11934129596455521040620786944827826205713621633706285934057045369193958244500", "1"
"8037395052364110730298837004334506829870972346962140206007064471173334027475" ],
] [
] "6815064660695497986531118446154820702646540722664044216580897159556261271171",
], "17838140936832571103329556013529166877877534025488014783346458943575275015438",
"IC": [ "1"
[ ],
"4920513730204767532050733107749276406754520419375654722016092399980613788208", [
"10950491564509418434657706642388934308456795265036074733953533582377584967294", "16364982450206976302246609763791333525052810246590359380676749324389440643932",
"1" "17092624338100676284548565502349491320314889021833923882585524649862570629227",
], "1"
[ ],
"6815064660695497986531118446154820702646540722664044216580897159556261271171", [
"17838140936832571103329556013529166877877534025488014783346458943575275015438", "3679639231485547795420532910726924727560917141402837495597760107842698404034",
"1" "16213191511474848247596810551723578773353083440353745908057321946068926848382",
], "1"
[ ],
"16364982450206976302246609763791333525052810246590359380676749324389440643932", [
"17092624338100676284548565502349491320314889021833923882585524649862570629227", "9215428431027260354679105025212521481930206886203677270216204485256690813172",
"1" "934602510541226149881779979217731465262250233587980565969044391353665291792",
], "1"
[ ],
"3679639231485547795420532910726924727560917141402837495597760107842698404034", [
"16213191511474848247596810551723578773353083440353745908057321946068926848382", "8935861545794299876685457331391349387048184820319250771243971382360441890897",
"1" "4993459033694759724715904486381952906869986989682015547152342336961693234616",
], "1"
[ ]
"9215428431027260354679105025212521481930206886203677270216204485256690813172", ]
"934602510541226149881779979217731465262250233587980565969044391353665291792", }
"1"
],
[
"8935861545794299876685457331391349387048184820319250771243971382360441890897",
"4993459033694759724715904486381952906869986989682015547152342336961693234616",
"1"
]
]
}

View File

@@ -1,99 +1,90 @@
{ {
"protocol": "groth16", "protocol": "groth16",
"curve": "bn128", "curve": "bn128",
"nPublic": 2, "nPublic": 2,
"vk_alpha_1": [ "vk_alpha_1": [
"20491192805390485299153009773594534940189261866228447918068658471970481763042", "20491192805390485299153009773594534940189261866228447918068658471970481763042",
"9383485363053290200918347156157836566562967994039712273449902621266178545958", "9383485363053290200918347156157836566562967994039712273449902621266178545958",
"1" "1"
], ],
"vk_beta_2": [ "vk_beta_2": [
[ [
"6375614351688725206403948262868962793625744043794305715222011528459656738731", "6375614351688725206403948262868962793625744043794305715222011528459656738731",
"4252822878758300859123897981450591353533073413197771768651442665752259397132" "4252822878758300859123897981450591353533073413197771768651442665752259397132"
], ],
[ [
"10505242626370262277552901082094356697409835680220590971873171140371331206856", "10505242626370262277552901082094356697409835680220590971873171140371331206856",
"21847035105528745403288232691147584728191162732299865338377159692350059136679" "21847035105528745403288232691147584728191162732299865338377159692350059136679"
], ],
[ ["1", "0"]
"1", ],
"0" "vk_gamma_2": [
] [
], "10857046999023057135944570762232829481370756359578518086990519993285655852781",
"vk_gamma_2": [ "11559732032986387107991004021392285783925812861821192530917403151452391805634"
[ ],
"10857046999023057135944570762232829481370756359578518086990519993285655852781", [
"11559732032986387107991004021392285783925812861821192530917403151452391805634" "8495653923123431417604973247489272438418190587263600148770280649306958101930",
], "4082367875863433681332203403145435568316851327593401208105741076214120093531"
[ ],
"8495653923123431417604973247489272438418190587263600148770280649306958101930", ["1", "0"]
"4082367875863433681332203403145435568316851327593401208105741076214120093531" ],
], "vk_delta_2": [
[ [
"1", "11983007890138271431646631339128325393343025477496193427884097571341594637227",
"0" "7369399572723042520302573420729576910581887995143697476970345965043452827346"
] ],
], [
"vk_delta_2": [ "18498623948725074905368922260006856461276039836160071250761949723295691187028",
[ "20253812994897318378765322974844822348210001364477588185294466023467389614920"
"11983007890138271431646631339128325393343025477496193427884097571341594637227", ],
"7369399572723042520302573420729576910581887995143697476970345965043452827346" ["1", "0"]
], ],
[ "vk_alphabeta_12": [
"18498623948725074905368922260006856461276039836160071250761949723295691187028", [
"20253812994897318378765322974844822348210001364477588185294466023467389614920" [
], "2029413683389138792403550203267699914886160938906632433982220835551125967885",
[ "21072700047562757817161031222997517981543347628379360635925549008442030252106"
"1", ],
"0" [
] "5940354580057074848093997050200682056184807770593307860589430076672439820312",
], "12156638873931618554171829126792193045421052652279363021382169897324752428276"
"vk_alphabeta_12": [ ],
[ [
[ "7898200236362823042373859371574133993780991612861777490112507062703164551277",
"2029413683389138792403550203267699914886160938906632433982220835551125967885", "7074218545237549455313236346927434013100842096812539264420499035217050630853"
"21072700047562757817161031222997517981543347628379360635925549008442030252106" ]
], ],
[ [
"5940354580057074848093997050200682056184807770593307860589430076672439820312", [
"12156638873931618554171829126792193045421052652279363021382169897324752428276" "7077479683546002997211712695946002074877511277312570035766170199895071832130",
], "10093483419865920389913245021038182291233451549023025229112148274109565435465"
[ ],
"7898200236362823042373859371574133993780991612861777490112507062703164551277", [
"7074218545237549455313236346927434013100842096812539264420499035217050630853" "4595479056700221319381530156280926371456704509942304414423590385166031118820",
] "19831328484489333784475432780421641293929726139240675179672856274388269393268"
], ],
[ [
[ "11934129596455521040620786944827826205713621633706285934057045369193958244500",
"7077479683546002997211712695946002074877511277312570035766170199895071832130", "8037395052364110730298837004334506829870972346962140206007064471173334027475"
"10093483419865920389913245021038182291233451549023025229112148274109565435465" ]
], ]
[ ],
"4595479056700221319381530156280926371456704509942304414423590385166031118820", "IC": [
"19831328484489333784475432780421641293929726139240675179672856274388269393268" [
], "19490069286251317200471893224761952280235157078692599655063040494106083015102",
[ "15613730057977833735664106983317680013118142165231654768046521650638333652991",
"11934129596455521040620786944827826205713621633706285934057045369193958244500", "1"
"8037395052364110730298837004334506829870972346962140206007064471173334027475" ],
] [
] "1563543155852853229359605494188815884199915022658219002707722789976065966419",
], "858819375930654753672617171465307097688802650498051619587167586479724200799",
"IC": [ "1"
[ ],
"19490069286251317200471893224761952280235157078692599655063040494106083015102", [
"15613730057977833735664106983317680013118142165231654768046521650638333652991", "3808889614445935800597561392085733302718838702771107544944545050886958022904",
"1" "13293649293049947010793838294353767499934999769633605908974566715226392122400",
], "1"
[ ]
"1563543155852853229359605494188815884199915022658219002707722789976065966419", ]
"858819375930654753672617171465307097688802650498051619587167586479724200799", }
"1"
],
[
"3808889614445935800597561392085733302718838702771107544944545050886958022904",
"13293649293049947010793838294353767499934999769633605908974566715226392122400",
"1"
]
]
}

View File

@@ -4,17 +4,10 @@ import { vitePreprocess } from '@sveltejs/kit/vite';
/** @type {import('@sveltejs/kit').Config} */ /** @type {import('@sveltejs/kit').Config} */
const config = { const config = {
// Consult https://kit.svelte.dev/docs/integrations#preprocessors
// for more information about preprocessors
preprocess: [vitePreprocess({})],
preprocess: [vitePreprocess({})],
kit: { kit: {
// adapter-auto only supports some environments, see https://kit.svelte.dev/docs/adapter-auto for a list.
// If your environment is not supported or you settled on a specific environment, switch out the adapter.
// See https://kit.svelte.dev/docs/adapters for more information about adapters.
adapter: adapter({ adapter: adapter({
// default options are shown. On some platforms
// these options are set automatically — see below
pages: 'build', pages: 'build',
assets: 'build', assets: 'build',
fallback: 'index.html', fallback: 'index.html',
@@ -25,5 +18,3 @@ const config = {
}; };
export default config; export default config;

View File

@@ -1,10 +1,11 @@
import { join } from 'path'; import { join } from 'path';
import type { Config } from 'tailwindcss'; import type { Config } from 'tailwindcss';
import { discreetlyTheme } from './theme';
import { skeleton } from '@skeletonlabs/tw-plugin';
import forms from '@tailwindcss/forms'; import forms from '@tailwindcss/forms';
import typography from '@tailwindcss/typography';
import { skeleton } from '@skeletonlabs/tw-plugin';
import { discreetlyTheme } from './theme';
const config = { export default {
darkMode: 'class', darkMode: 'class',
content: [ content: [
'./src/**/*.{html,js,svelte,ts}', './src/**/*.{html,js,svelte,ts}',
@@ -14,11 +15,11 @@ const config = {
extend: {} extend: {}
}, },
plugins: [ plugins: [
forms,
typography,
skeleton({ skeleton({
themes: { custom: [discreetlyTheme] } themes: { custom: [discreetlyTheme] }
}), }),
forms forms
] ]
} satisfies Config; } satisfies Config;
export default config;

28
tests/app.test.ts Normal file
View File

@@ -0,0 +1,28 @@
import { expect, test } from '@playwright/test';
import { getLocalStorage } from './utils';
test.describe('/', () => {
test.beforeEach(async ({ page }) => {
await page.goto('/');
});
test('mount', async ({ page }) => {
await expect(page.getByRole('heading', { name: 'Welcome to Discreetly' })).toBeVisible();
});
test('default servers should be set', async ({ page }) => {
const localStorage = await getLocalStorage(page);
localStorage.forEach((object) => {
switch (object.name) {
case 'selectedServer':
expect(object.value).toBe('"https://server.discreetly.chat/"');
break;
case 'serverData':
expect(object.value).toBe(
'{"https://server.discreetly.chat/":{"name":"Discreetly Server","url":"https://server.discreetly.chat/"}}'
);
break;
}
});
});
});

View File

@@ -1,6 +0,0 @@
import { expect, test } from '@playwright/test';
test('index page has expected h1', async ({ page }) => {
await page.goto('/');
await expect(page.getByRole('heading', { name: 'Welcome to SvelteKit' })).toBeVisible();
});

6
tests/utils.ts Normal file
View File

@@ -0,0 +1,6 @@
import type { Page } from '@playwright/test';
export async function getLocalStorage(page: Page) {
const state = await page.context().storageState();
return state.origins[0].localStorage;
}

View File

@@ -1,17 +1,10 @@
import { purgeCss } from 'vite-plugin-tailwind-purgecss';
import { sveltekit } from '@sveltejs/kit/vite'; import { sveltekit } from '@sveltejs/kit/vite';
import { defineConfig } from 'vite'; import { defineConfig } from 'vitest/config';
import sizes from 'rollup-plugin-sizes';
export default defineConfig({ export default defineConfig({
plugins: [sveltekit()], plugins: [sveltekit(), purgeCss()],
build: { test: {
minify: 'terser', include: ['src/**/*.{test,spec}.{js,ts}']
cssMinify: 'lightningcss',
rollupOptions: {
plugins: [sizes()],
output: {
compact: true
}
}
} }
}); });