/* This file is part of GNU Taler (C) 2022 Taler Systems S.A. GNU Taler is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version. GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with GNU Taler; see the file COPYING. If not, see */ import { AmountJson, Amounts, GetExchangeTosResult, } from "@gnu-taler/taler-util"; import { VNode } from "preact"; function getJsonIfOk(r: Response): Promise { if (r.ok) { return r.json(); } if (r.status >= 400 && r.status < 500) { throw new Error(`URL may not be right: (${r.status}) ${r.statusText}`); } throw new Error( `Try another server: (${r.status}) ${r.statusText || "internal server error" }`, ); } export async function queryToSlashConfig(url: string): Promise { return fetch(new URL("config", url).href) .catch(() => { throw new Error(`Network error`); }) .then(getJsonIfOk); } function timeout(ms: number, promise: Promise): Promise { return new Promise((resolve, reject) => { const timer = setTimeout(() => { reject( new Error( `Timeout: the query took longer than ${Math.floor(ms / 1000)} secs`, ), ); }, ms); promise .then((value) => { clearTimeout(timer); resolve(value); }) .catch((reason) => { clearTimeout(timer); reject(reason); }); }); } export async function queryToSlashKeys(url: string): Promise { const endpoint = new URL("keys", url); const query = fetch(endpoint.href) .catch(() => { throw new Error(`Network error`); }) .then(getJsonIfOk); return timeout(3000, query); } export function buildTermsOfServiceState( tos: GetExchangeTosResult, ): TermsState { const content: TermsDocument | undefined = parseTermsOfServiceContent( tos.contentType, tos.content, ); const status: TermsStatus = buildTermsOfServiceStatus( tos.content, tos.acceptedEtag, tos.currentEtag, ); return { content, status, version: tos.currentEtag }; } export function buildTermsOfServiceStatus( content: string | undefined, acceptedVersion: string | undefined, currentVersion: string | undefined, ): TermsStatus { return !content ? "notfound" : !acceptedVersion ? "new" : acceptedVersion !== currentVersion ? "changed" : "accepted"; } function parseTermsOfServiceContent( type: string, text: string, ): TermsDocument | undefined { if (type === "text/xml") { try { const document = new DOMParser().parseFromString(text, "text/xml"); return { type: "xml", document }; } catch (e) { console.log(e); } } else if (type === "text/html") { try { const href = new URL(text); return { type: "html", href }; } catch (e) { console.log(e); } } else if (type === "text/json") { try { const data = JSON.parse(text); return { type: "json", data }; } catch (e) { console.log(e); } } else if (type === "text/pdf") { try { const location = new URL(text); return { type: "pdf", location }; } catch (e) { console.log(e); } } else if (type === "text/plain") { try { const content = text; return { type: "plain", content }; } catch (e) { console.log(e); } } return undefined; } export type TermsState = { content: TermsDocument | undefined; status: TermsStatus; version: string; }; type TermsStatus = "new" | "accepted" | "changed" | "notfound"; type TermsDocument = | TermsDocumentXml | TermsDocumentHtml | TermsDocumentPlain | TermsDocumentJson | TermsDocumentPdf; export interface TermsDocumentXml { type: "xml"; document: Document; } export interface TermsDocumentHtml { type: "html"; href: URL; } export interface TermsDocumentPlain { type: "plain"; content: string; } export interface TermsDocumentJson { type: "json"; data: any; } export interface TermsDocumentPdf { type: "pdf"; location: URL; } export type StateFunc = (p: S) => VNode; export type StateViewMap = { [S in StateType as S["status"]]: StateFunc; }; export function compose( name: string, hook: (p: PType) => SType, vs: StateViewMap, ): (p: PType) => VNode { const Component = (p: PType): VNode => { const state = hook(p); const s = state.status as unknown as SType["status"]; const c = vs[s] as unknown as StateFunc; return c(state); }; Component.name = `${name}`; return Component; }