143 lines
3.2 KiB
TypeScript
143 lines
3.2 KiB
TypeScript
|
|
|
|
export type UserEntry = {
|
|
id: string,
|
|
username: string,
|
|
display_name?: string,
|
|
biography?: string,
|
|
joined_at?: string,
|
|
age: number, // actually not just that
|
|
badges?: string[]
|
|
type?: 'user',
|
|
karma?: number
|
|
};
|
|
|
|
export type GuildEntry = {
|
|
type?: 'guild',
|
|
id: string,
|
|
name: string,
|
|
display_name?: string,
|
|
description?: string,
|
|
created_at?: string,
|
|
subscriber_count?: number,
|
|
post_count?: number
|
|
};
|
|
|
|
export type PostEntry = {
|
|
id: string,
|
|
url: string,
|
|
title: string,
|
|
created_at: string,
|
|
author?: UserEntry | null,
|
|
content?: string,
|
|
to: UserEntry | GuildEntry ,
|
|
privacy?: number
|
|
};
|
|
|
|
export type ServerHealth = {
|
|
version: string,
|
|
name: string,
|
|
post_count: number,
|
|
user_count: number,
|
|
me: string | null,
|
|
csrf_token?: string,
|
|
color_theme?: number
|
|
};
|
|
|
|
|
|
// 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';
|
|
|
|
constructor () {
|
|
}
|
|
|
|
async fetch(url: string, options?: RequestInit) {
|
|
return await fetch(`${Backend.ENDPOINT_BASE}/${url.replace(/^\/+/, '')}`, options);
|
|
}
|
|
|
|
withEvent(event: any) {
|
|
return new EventBackend(event);
|
|
}
|
|
}
|
|
|
|
class EventBackend extends Backend {
|
|
event: any | null;
|
|
|
|
constructor (event: any | null) {
|
|
super();
|
|
this.event = event;
|
|
}
|
|
|
|
async oath (): Promise<CsrfEventBackend> {
|
|
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);
|
|
}
|
|
return new CsrfEventBackend(this.event, csrfToken);
|
|
}
|
|
|
|
async fetch(url: string, options?: RequestInit): Promise<Response> {
|
|
console.debug(`fetch ${Backend.ENDPOINT_BASE}/${url.replace(/^\/+/, '')}`);
|
|
return await (this.event?.fetch||fetch)(`${Backend.ENDPOINT_BASE}/${url.replace(/^\/+/, '')}`, options);
|
|
}
|
|
}
|
|
|
|
class CsrfEventBackend extends EventBackend {
|
|
csrfToken: string | null;
|
|
|
|
constructor(event: EventBackend | null, csrfToken: string | null) {
|
|
super(event);
|
|
this.csrfToken = csrfToken;
|
|
}
|
|
|
|
async submitJson(url: string, data: object) {
|
|
return await this.fetch(url, {
|
|
method: 'POST',
|
|
body: JSON.stringify(data),
|
|
headers: {
|
|
'content-type': 'application/json',
|
|
'x-csrftoken': this.csrfToken || ""
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
export const backend = new Backend();
|
|
|