import { browser } from '$app/environment'; import { base } from '$app/paths'; import { validateSignature } from './keystore'; export const canaries: Canary[] = []; export interface CanaryInterface { name: string; description: string; signer: string; url: string; keyIdentifier: string; contentType: string; upstream?: string; } export interface Canary extends CanaryInterface {} type _PreCanaryInterfaceEntries = { [T in keyof CanaryInterface]: [T, CanaryInterface[T]]; }; type CanaryInterfaceEntry = _PreCanaryInterfaceEntries[keyof _PreCanaryInterfaceEntries]; export class Canary { public constructor(canary: CanaryInterface) { const entries = Object.entries(canary) as CanaryInterfaceEntry[]; for (const a of entries) { // @ts-ignore we know the value will match in type this[a[0]] = a[1]; } canaries.push(this); } public forceContentType = false; public async getRawText() { if (!browser) throw new Error('This should only be done in a browser.'); const res = await fetch(this.url); if (res.ok) { if (!this.forceContentType) this.contentType = res.headers.get('Content-Type') || this.contentType; const text = await res.text(); return text; } else { throw new Error(`Fetching canary failed with code ${res.status} (${res.statusText}): ${await res.text().catch((e) => `Unknown (Unable to get text, ${JSON.stringify(e)})`)}`); } } public async getValidatedText( rawText: string | Promise = this.getRawText(), ) { return await validateSignature(await rawText, this.keyIdentifier); } /** Returns downloadable data url if signature passes, otherwise returns null */ public async getUrl(rawText: string | Promise = this.getRawText()) { const t = await rawText; let stripped: string; try { stripped = await this.getValidatedText(t); } catch (error) { console.warn('Failed to validate signature: ', error); return null; } return { stripped: URL.createObjectURL( new Blob([stripped], { type: this.contentType, }), ), signed: URL.createObjectURL( new Blob([t], { type: this.contentType, }), ), }; } } new Canary({ name: 'estrogen.zone', description: 'Services hosted around estrogen.zone and yuridick.gay', signer: 'memdmp', url: base + '/canaries/memdmp:estrogen.zone', keyIdentifier: 'memdmp', contentType: 'text/plain; charset=utf-8', }); new Canary({ name: 'kyun.host', description: 'The VPS provider "kyun.host"', signer: 'napatha', url: base + '/canaries/napatha:kyun.host', keyIdentifier: 'napatha', contentType: 'text/plain; charset=utf-8', upstream: 'https://files.kyun.host/canary.txt', });