diff --git a/src/lib/AsideCard.svelte b/src/lib/AsideCard.svelte
index 4e7fb2e..4e6029c 100644
--- a/src/lib/AsideCard.svelte
+++ b/src/lib/AsideCard.svelte
@@ -25,7 +25,7 @@ let { children, title = null } = $props();
position: relative;
font: inherit;
- a {
+ :global(a) {
color: inherit;
text-decoration: underline;
}
diff --git a/src/lib/GuildSelect.svelte b/src/lib/GuildSelect.svelte
index 12c07c4..ddf41ae 100644
--- a/src/lib/GuildSelect.svelte
+++ b/src/lib/GuildSelect.svelte
@@ -1,29 +1,46 @@
@@ -46,7 +63,7 @@ let suggestions: GuildEntry[] = $state([]);
border-bottom: 1px solid var(--border);
}
- :not(:focus) + ul {
+ :not(:focus) + ul:not(:active) {
display: none;
}
diff --git a/src/lib/backend.ts b/src/lib/backend.ts
index e36d983..92cbfdb 100644
--- a/src/lib/backend.ts
+++ b/src/lib/backend.ts
@@ -44,6 +44,32 @@ export type ServerHealth = {
};
+// caches an oath (CSRF token)
+class OathKeeper {
+ lastOath : number;
+ csrfToken: string | null;
+
+ constructor () {
+ this.lastOath = 0;
+ this.csrfToken = null;
+ }
+
+ set (tok: string) {
+ this.lastOath = +new Date;
+ this.csrfToken = tok;
+ }
+
+ get () {
+ if (this.lastOath === 0 || +new Date() - this.lastOath > 1800000) {
+ // 30 minutes decay
+ return null;
+ }
+ return this.csrfToken;
+ }
+}
+
+const oathKeeper = new OathKeeper();
+
export class Backend {
static ENDPOINT_BASE = '/v1';
@@ -60,33 +86,42 @@ export class Backend {
}
class EventBackend extends Backend {
- event: any;
+ event: any | null;
- constructor (event: any) {
+ constructor (event: any | null) {
super();
this.event = event;
}
async oath (): Promise {
- const resp = await this.fetch("oath");
- if (resp.status !== 200) {
- throw new Error();
+ let csrfToken = oathKeeper.get();
+ if (!csrfToken) {
+ const resp = await this.fetch("oath");
+ if (resp.status !== 200) {
+ throw new Error();
+ }
+ const respJ = await resp.json();
+ csrfToken = respJ.csrf_token;
+ }
+ if (!csrfToken) {
+ console.warn("csrf_token is null");
+ }
+ else {
+ oathKeeper.set(csrfToken);
}
- const respJ = await resp.json();
- const { csrfToken } = respJ;
return new CsrfEventBackend(this.event, csrfToken);
}
async fetch(url: string, options?: RequestInit): Promise {
console.debug(`fetch ${Backend.ENDPOINT_BASE}/${url.replace(/^\/+/, '')}`);
- return await this.event.fetch(`${Backend.ENDPOINT_BASE}/${url.replace(/^\/+/, '')}`, options);
+ return await (this.event?.fetch||fetch)(`${Backend.ENDPOINT_BASE}/${url.replace(/^\/+/, '')}`, options);
}
}
class CsrfEventBackend extends EventBackend {
- csrfToken: string;
+ csrfToken: string | null;
- constructor(event:EventBackend, csrfToken: string) {
+ constructor(event: EventBackend | null, csrfToken: string | null) {
super(event);
this.csrfToken = csrfToken;
}
@@ -97,7 +132,7 @@ class CsrfEventBackend extends EventBackend {
body: JSON.stringify(data),
headers: {
'content-type': 'application/json',
- 'x-csrftoken': this.csrfToken
+ 'x-csrftoken': this.csrfToken || ""
}
});
}