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
resolution-mode=highest
engine-strict=true

5812
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,17 +1,21 @@
{
"name": "discreetly",
"version": "0.0.1",
"version": "0.3.0",
"private": true,
"license": "GPL-3.0-or-later",
"scripts": {
"dev": "vite dev",
"build": "vite build",
"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: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 .",
"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": {
"@faker-js/faker": "^8.0.2",
@@ -22,36 +26,45 @@
"@sveltejs/adapter-cloudflare": "^2.3.0",
"@sveltejs/adapter-static": "^2.0.3",
"@sveltejs/kit": "^1.25.0",
"@sveltejs/vite-plugin-svelte": "^2.4.6",
"@tailwindcss/forms": "^0.5.6",
"@tailwindcss/typography": "0.5.10",
"@testing-library/svelte": "^4.0.3",
"@types/dompurify": "^3.0.2",
"@types/node": "^20.6.3",
"@types/qrcode": "^1.5.1",
"@typescript-eslint/eslint-plugin": "^5.45.0",
"@typescript-eslint/parser": "^5.45.0",
"@vitest/ui": "^0.34.6",
"autoprefixer": "^10.4.14",
"eslint": "^8.28.0",
"eslint-config-prettier": "^8.5.0",
"eslint-plugin-svelte": "^2.30.0",
"eslint": "^8.28.0",
"jsdom": "^22.1.0",
"lightningcss": "^1.21.7",
"postcss": "^8.4.24",
"postcss-load-config": "^4.0.1",
"prettier": "^2.8.0",
"postcss": "^8.4.24",
"prettier-plugin-svelte": "^2.10.1",
"rollup-plugin-sizes": "^1.0.5",
"svelte": "^4.0.5",
"prettier": "^2.8.0",
"svelte-check": "^3.4.5",
"svelte-kit": "^1.2.0",
"svelte": "^4.0.5",
"tailwindcss": "^3.3.2",
"terser": "^5.19.2",
"tslib": "^2.4.1",
"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",
"dependencies": {
"@ethersproject/bytes": "^5.7.0",
"@ethersproject/keccak256": "^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/identity": "^3.10.1",
"autolinker": "^4.0.0",
@@ -65,6 +78,7 @@
"qr-scanner": "^1.4.2",
"qrcode": "^1.5.3",
"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');
const autoprefixer = require('autoprefixer');
const config = {
plugins: [
//Some plugins, like tailwindcss/nesting, need to run before Tailwind,
tailwindcss(),
//But others, like autoprefixer, need to run after,
autoprefixer
]
module.exports = {
plugins: {
tailwindcss: {},
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%
</head>
<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>
</html>

View File

@@ -91,3 +91,7 @@ input:focus-visible {
font-family: 'Nippo';
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
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 });
}

View File

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

View File

@@ -1,27 +1,41 @@
<script lang="ts">
import { getIdentityBackup } from '$lib/utils/';
$: id = getIdentityBackup();
$: identityBackupExists = id ? true : false;
$: encodedIdentity = 'data:text/json;charset=utf-8,' + encodeURIComponent(id!);
let revealIdentity = false;
let id = '';
function reveal() {
id = getIdentityBackup();
if (revealIdentity == false) {
revealIdentity = true;
setTimeout(() => {
revealIdentity = false;
id = '';
}, 60000);
if (id === undefined || id === null) {
id = 'No Identity Backup Found';
} else {
revealIdentity = false;
if (revealIdentity == false) {
revealIdentity = true;
setTimeout(() => {
revealIdentity = false;
id = '';
}, 60000);
} else {
revealIdentity = false;
}
}
}
</script>
<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(getIdentityBackup())}
download="Discreetly_Identity.json">Download Identity Backup as JSON</a
>
{#if identityBackupExists}
<a class="btn variant-ghost-success" href={encodedIdentity} 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}
<div class="btn variant-ghost-success" on:click={reveal}>Show Identity</div>
{:else}

View File

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

View File

@@ -7,21 +7,23 @@
import { getModalStore, type ModalSettings } from '@skeletonlabs/skeleton';
import { deriveKey, hashPassword } from '$lib/crypto/crypto';
import { onMount } from 'svelte';
import { setPassword } from '$lib/utils';
import { alertAll, setPassword } from '$lib/utils';
const modalStore = getModalStore();
export let cls: string = '';
let minPasswordLength = 3;
function setPasswordModal() {
const modal: ModalSettings = {
type: 'prompt',
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: '',
valueAttr: { type: 'password', minlength: 3, required: true },
valueAttr: { type: 'password', minlength: minPasswordLength, required: true },
response: async (r: string) => {
setPassword(r);
if (r != '' && r != null && r != undefined && r.length >= minPasswordLength) {
setPassword(r);
}
}
};
modalStore.trigger(modal);
@@ -33,13 +35,14 @@
title: 'Unlock',
body: 'Enter your password to unlock your keystores',
value: '',
valueAttr: { type: 'password', minlength: 3, required: true },
valueAttr: { type: 'password', minlength: 4, required: true },
response: async (r: string) => {
if (r != 'false') {
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;
}
}
@@ -53,7 +56,7 @@
}
onMount(() => {
console.debug(
'PasswordLock: ',
'PadLock:',
$passwordSet ? 'password set,' : 'password not set,',
$keyStore !== null && $keyStore !== undefined ? 'unlocked' : 'locked'
);
@@ -64,14 +67,14 @@
{#if $passwordSet}
{#if $keyStore instanceof CryptoKey}
<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>
{: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}
{:else}
<div on:click={setPasswordModal} title="Password not set">
<NoPassword class="text-primary-500" />
<NoPassword class="w-full text-error-500" />
</div>
{/if}
</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 = {
signUpStatus: {
inviteAccepted: false,
completedSignup: 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[]>;
}
interface CreateInviteData {
numCodes: number;
expiresAt?: number;
usesLeft?: number;
roomIds?: string[];
all?: boolean;
}
export async function createInvite(
serverUrl: string,
username: string,
@@ -32,7 +40,7 @@ export async function createInvite(
expiresAt?: number,
usesLeft?: number
) {
const data = { numCodes, expiresAt, usesLeft };
const data: CreateInviteData = { numCodes, expiresAt, usesLeft };
if (roomIds.length > 0) {
data['roomIds'] = roomIds;
} else {

View File

@@ -129,3 +129,14 @@ export const identityStore = storable({} as IdentityStoreI, 'identity');
* @description Identity store, this is the user's identity ENCRYPTED
*/
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 {
'NO_IDENTITY',
'localStorage',
'localStorageEncrypted',
'cryptKeeper',
'PCDPass'
}

View File

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

View File

@@ -30,3 +30,12 @@ export const clearConsoleMessages = () => {
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() {
const id = get(identityKeyStore) as IdentityStoreI;
const id_old = get(identityStore);
const id_ = get(identityStore);
if (id !== null && id !== undefined) {
return id._commitment;
}
if (id_old !== null && id_old !== undefined) {
if (id_ !== null && id_ !== undefined) {
console.warn('PLEASE ADD A PASSWORD!');
return id_old._commitment;
return id_._commitment;
}
}
export function getIdentityBackup() {
const id = get(identityKeyStore);
const id_old = get(identityStore);
const id_ = get(identityStore);
if (id !== null && id !== undefined) {
return JSON.stringify(id);
}
if (id_old !== null && id_old !== undefined) {
if (id_ !== null && id_ !== undefined) {
console.warn('PLEASE ADD A PASSWORD!');
return JSON.stringify(id_old);
return JSON.stringify(id_);
}
}
export function doesIdentityExist(): 'safe' | 'unsafe' | 'none' {
const id = get(identityKeyStore);
const id_old = get(identityStore);
const id_ = get(identityStore);
if (id._commitment !== null && id._commitment !== undefined) {
return 'safe';
}
if (id_old._commitment !== null && id_old._commitment !== undefined) {
if (id_._commitment !== null && id_._commitment !== undefined) {
console.warn('PLEASE ADD A PASSWORD');
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 { selectedServer, configStore } from '$lib/stores';
import { get } from 'svelte/store';
@@ -10,6 +10,11 @@ export async function inviteCode(newCode: string) {
const server = get(selectedServer);
try {
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, {
code: newCode.toLowerCase(),
idc
@@ -20,7 +25,7 @@ export async function inviteCode(newCode: string) {
acceptedRoomNames = await updateRooms(server, result.roomIds);
console.log(`Added to rooms: ${acceptedRoomNames}`);
configStore.update((store) => {
store['signUpStatus']['inviteAccepted'] = true;
store['signUpStatus']['completedSignup'] = true;
store['signUpStatus']['inviteCode'] = '';
return store;
});

View File

@@ -20,9 +20,11 @@ export function getEpochFromTimestamp(
let relative = '';
try {
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';
console.debug(`${err.message}: ${epoch} * ${ratelimit} = ${timestamp}`);
console.debug(`${message}: ${epoch} * ${ratelimit} = ${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[]> {
if (roomIds.length < 1) {
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 roomIds;

View File

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

View File

@@ -1,19 +1,23 @@
<script lang="ts">
import { AppShell, Modal, initializeStores } from '@skeletonlabs/skeleton';
import { Modal, initializeStores } from '@skeletonlabs/skeleton';
import { Toast } from '@skeletonlabs/skeleton';
import '../app.postcss';
import { onMount } from 'svelte';
import AppHeader from './AppHeader.svelte';
import Loading from '$lib/components/loading.svelte';
import { selectedServer } from '$lib/stores';
import { getServerList, setDefaultServers } from '$lib/utils/';
import { getServerList, isInputFieldFocused, setDefaultServers } 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 SelectRoom from '$lib/components/SelectRoom.svelte';
import Console from './console/Console.svelte';
import Sidebar from './Sidebar.svelte';
import AppFooter from './AppFooter.svelte';
initializeStores();
const drawerStore = getDrawerStore();
// Hack to get BigInt <-> JSON compatibility
(BigInt.prototype as any).toJSON = function () {
return this.toString();
@@ -25,19 +29,48 @@
setDefaultServers();
}
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>
<Modal />
<Toast position="t" background="variant-filled-primary" />
<Drawer position="top" padding="p-4" rounded="rounded-token">
<SelectServer />
<SelectRoom />
{#if $drawerStore.id === 'roomselect'}
<SelectServer />
<SelectRoom />
{:else if $drawerStore.id === 'console'}
<Console />
{/if}
</Drawer>
<AppShell>
<svelte:fragment slot="header"><AppHeader /></svelte:fragment>
<slot>
<Loading />
</slot>
</AppShell>
<div class="w-full h-screen flex flex-col overflow-hidden">
<div class="flex-none z-10"><AppHeader /></div>
<div class="grid grid-cols-[1fr,auto] h-full min-w-full justify-between">
<main class="flex flex-col justify-between">
<slot class="flex flex-col justify-center">
<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">
import Button from '$lib/components/button.svelte';
import { identityStore } from '$lib/stores';
$: identityExists = !!$identityStore._commitment;
import Welcome from '$lib/components/Welcome.svelte';
console.info(
'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]">
<h2 class="h2 mb-5">Welcome to Discreetly!</h2>
{#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}
<Button link="/chat" cls="variant-ghost-success">Go Chat</Button>
{/if}
<Welcome />
<slot />
</div>

View File

@@ -1,27 +1,97 @@
<script lang="ts">
import { identityStore } from '$lib/stores';
import { AppBar } from '@skeletonlabs/skeleton';
$: identityExists = !!$identityStore._commitment;
import { page } from '$app/stores';
import { passwordSet, configStore, keyStore } from '$lib/stores';
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>
<AppBar class="hidden md:block" padding="py-3 px-8">
<svelte:fragment slot="lead">
<a href="/">Home</a>
{#if identityExists}
<a href="/chat">Chat</a>
{:else}
<a href="/signup">Sign Up</a>
{/if}
<a href="/about">About</a>
</svelte:fragment>
<svelte:fragment slot="trail">
<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>
<TabGroup
justify="justify-center"
active="variant-filled-primary"
hover="hover:variant-soft-primary"
flex="flex-1 lg:flex-none"
class="bg-surface-100-800-token w-full"
>
<TabAnchor href="/about" selected={$page.url.pathname === '/about'} title="About">
<svelte:fragment slot="lead"><Information class="rail-icon" /></svelte:fragment>
<span>About</span>
</TabAnchor>
<style>
a {
margin-right: 1.5rem;
}
</style>
<TabAnchor href="/chat" selected={$page.url.pathname === '/chat'} title="Chat">
<svelte:fragment slot="lead"><Chat class="rail-icon" /></svelte:fragment>
<span>Chat</span>
</TabAnchor>
{#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">
import { AppBar } from '@skeletonlabs/skeleton';
import { LightSwitch } from '@skeletonlabs/skeleton';
import { page } from '$app/stores';
import { configStore, currentSelectedRoom, identityStore, keyStore } from '$lib/stores';
import { AppBar, type DrawerSettings } from '@skeletonlabs/skeleton';
import { currentSelectedRoom, identityExists } from '$lib/stores';
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';
const drawerStore = getDrawerStore();
const settings: DrawerSettings = { id: 'roomselect' };
// Open the drawer:
function drawerOpen(): void {
drawerStore.open();
drawerStore.open(settings);
}
</script>
@@ -29,43 +25,43 @@
>
<svelte:fragment slot="lead">
<h1 class="h4 text-primary-500">
{#if identityExists}
<a href="/chat"><img class="max-h-7" src="/logo-text.png" alt="discreetly" /></a>
{#if $identityExists}
<a href="/chat" role="button" tabindex="0"
><img class="max-h-7" src="/logo-text.png" alt="discreetly" /></a
>
{: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}
</h1>
</svelte:fragment>
<a href="/about" class="btn btn-sm variant-ringed-secondary hidden sm:inline">About</a>
{#if identityExists}
<a href="/chat" class="hidden btn btn-sm variant-ringed-secondary sm:inline">Chat</a>
{#if $identityExists}
<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
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}
<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}
<a
class="btn btn-sm variant-ringed-secondary font-medium text-sm hidden sm:inline"
href="/console">Console</a
>
<svelte:fragment slot="trail">
<a href="/about" class="hidden sm:inline"><Information size="1.2em" /></a>
{#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>
<div class="hidden sm:inline text-primary-500">Alpha Version!</div>
</svelte:fragment>
</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">
import { goto } from '$app/navigation';
import { identityStore, serverStore } from '$lib/stores';
import { identityExists, serverStore } from '$lib/stores';
import { updateServer } from '$lib/utils';
import { onMount } from 'svelte';
$: identityExists = !!$identityStore._commitment;
onMount(() => {
if (!identityExists) {
if (!$identityExists) {
goto('/signup');
}
if (!Object.keys($serverStore).length) {

View File

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

View File

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

View File

@@ -17,7 +17,6 @@
addConsoleMessage('`/clear` Clears the console');
addConsoleMessage('`/join invite-code` Joins a room via invite code, Example:');
addConsoleMessage('`/password Password`');
addConsoleMessage('`/clearPassword`');
addConsoleMessage('`/unlock Password`');
addConsoleMessage('`/lock`');
addConsoleMessage('`/backup`');
@@ -75,10 +74,6 @@
addConsoleMessage('/password OLDPASSWORD NEWPASSWORD', 'warning');
}
break;
case '/clearPassword':
$configStore.hashedPwd = null;
addConsoleMessage('Password Cleared');
break;
case '/lock':
$keyStore = null;
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="mx-auto mt-10 max-w-[80ch]">
<h2 class="h2 mb-8 text-center">Manage Your Identity</h2>
<slot />
</div>
<div class="px-2 sm:px-5 mt-3 sm:mt-8 overflow-scroll h-100">
<slot />
</div>

View File

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

View File

@@ -32,7 +32,7 @@
});
</script>
<div class="card variant-ghost-error mb-5">
<div class="card variant-ghost-error">
<header class="card-header">
<h3 class="h4">Delete Your Identity & Reset Application</h3>
</header>
@@ -42,7 +42,7 @@
>I promise I backed up my identity, or I really want to destroy it forever.</span
>
</section>
<footer class="card-footer text-center mb-2">
<footer class="card-footer text-center">
{#if !isButtonDisabled}
<button
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 { configStore } from '$lib/stores';
import { ActionRepresentationE } from '$lib/types';
import { RangeSlider } from '@skeletonlabs/skeleton';
import { max } from 'date-fns';
$: if ($configStore.actionRepresentation == undefined) {
$configStore.actionRepresentation = ActionRepresentationE.AP;
}
@@ -12,7 +14,7 @@
<div class="card variant-soft-secondary">
<header class="card-header">
<h4 class="h4">Action Representation</h4>
<h3 class="h4">Action Representation</h3>
</header>
<section class="p-2 mb-2 sm:p-4 sm:mb-4">
<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">
<h6 class="h6">Demo</h6>
<AP {health} {maxHealth} />
<label
><div>Health: {health}</div>
<input type="range" min="0" max={maxHealth} bind:value={health} />
</label>
<label
><div>Max Health: {maxHealth}</div>
<input type="range" min="1" max={10} bind:value={maxHealth} />
</label>
<RangeSlider name="range-slider" bind:value={health} max={maxHealth} step={1} ticked>
<div class="flex justify-between items-center">
<div class="font-bold">Health</div>
<div class="text-xs">{health} / {maxHealth}</div>
</div>
</RangeSlider>
<RangeSlider name="range-slider" bind:value={maxHealth} max={10} step={1} ticked>
<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>
</section>

View File

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

View File

@@ -1,16 +1,21 @@
# Installing Webfonts
Follow these simple Steps.
## 1.
Put `nippo/` Folder into a Folder called `fonts/`.
## 2.
Put `nippo.css` into your `css/` Folder.
## 3. (Optional)
You may adapt the `url('path')` in `nippo.css` depends on your Website Filesystem.
## 4.
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.
```
font-family: 'Nippo-Variable';
font-family: 'Nippo-Extralight';
@@ -28,4 +32,3 @@ font-family: 'Nippo-Regular';
font-family: 'Nippo-Medium';
font-family: 'Nippo-Bold';
```

View File

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

View File

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

View File

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

View File

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

View File

@@ -4,17 +4,10 @@ import { vitePreprocess } from '@sveltejs/kit/vite';
/** @type {import('@sveltejs/kit').Config} */
const config = {
// Consult https://kit.svelte.dev/docs/integrations#preprocessors
// for more information about preprocessors
preprocess: [vitePreprocess({})],
preprocess: [vitePreprocess({})],
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({
// default options are shown. On some platforms
// these options are set automatically — see below
pages: 'build',
assets: 'build',
fallback: 'index.html',
@@ -25,5 +18,3 @@ const config = {
};
export default config;

View File

@@ -1,10 +1,11 @@
import { join } from 'path';
import type { Config } from 'tailwindcss';
import { discreetlyTheme } from './theme';
import { skeleton } from '@skeletonlabs/tw-plugin';
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',
content: [
'./src/**/*.{html,js,svelte,ts}',
@@ -14,11 +15,11 @@ const config = {
extend: {}
},
plugins: [
forms,
typography,
skeleton({
themes: { custom: [discreetlyTheme] }
}),
forms
]
} 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 { defineConfig } from 'vite';
import sizes from 'rollup-plugin-sizes';
import { defineConfig } from 'vitest/config';
export default defineConfig({
plugins: [sveltekit()],
build: {
minify: 'terser',
cssMinify: 'lightningcss',
rollupOptions: {
plugins: [sizes()],
output: {
compact: true
}
}
plugins: [sveltekit(), purgeCss()],
test: {
include: ['src/**/*.{test,spec}.{js,ts}']
}
});