/* This file is part of GNU Anastasis (C) 2021-2022 Anastasis SARL GNU Anastasis is free software; you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version. GNU Anastasis 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 Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with GNU Anastasis; see the file COPYING. If not, see */ import { AuthenticationProviderStatus, AuthenticationProviderStatusError, AuthenticationProviderStatusOk, } from "@gnu-taler/anastasis-core"; import { h, VNode } from "preact"; import { useEffect, useRef, useState } from "preact/hooks"; import { TextInput } from "../../components/fields/TextInput.js"; import { useAnastasisContext } from "../../context/anastasis.js"; import { authMethods, KnownAuthMethods } from "./authMethod/index.js"; import { AnastasisClientFrame } from "./index.js"; interface Props { providerType?: KnownAuthMethods; onCancel: () => void; } async function testProvider( url: string, expectedMethodType?: string, ): Promise { try { const response = await fetch(new URL("config", url).href); const json = await response.json().catch((d) => ({})); if (!("methods" in json) || !Array.isArray(json.methods)) { throw Error( "This provider doesn't have authentication method. Check the provider URL", ); } if (!expectedMethodType) { return; } let found = false; for (let i = 0; i < json.methods.length && !found; i++) { found = json.methods[i].type === expectedMethodType; } if (!found) { throw Error( `This provider does not support authentication method ${expectedMethodType}`, ); } return; } catch (e) { console.log("error", e); const error = e instanceof Error ? Error( `There was an error testing this provider, try another one. ${e.message}`, ) : Error(`There was an error testing this provider, try another one.`); throw error; } } export function AddingProviderScreen({ providerType, onCancel }: Props): VNode { const reducer = useAnastasisContext(); const [providerURL, setProviderURL] = useState(""); const [error, setError] = useState(); const [testing, setTesting] = useState(false); const providerLabel = providerType ? authMethods[providerType].label : undefined; const allAuthProviders = !reducer || !reducer.currentReducerState || reducer.currentReducerState.reducer_type === "error" || !reducer.currentReducerState.authentication_providers ? {} : reducer.currentReducerState.authentication_providers; const authProvidersByStatus = Object.keys(allAuthProviders).reduce( (prev, url) => { const p = allAuthProviders[url]; if ( providerLabel && p.status === "ok" && p.methods.findIndex((m) => m.type === providerType) !== -1 ) { return prev; } const others = prev[p.status] ? prev[p.status] : []; others.push({ ...p, url }); return { ...prev, [p.status]: others, }; }, {} as Record< AuthenticationProviderStatus["status"], (AuthenticationProviderStatus & { url: string })[] >, ); const authProviders = authProvidersByStatus["ok"].map((p) => p.url); console.log("rodos", allAuthProviders); //FIXME: move this timeout logic into a hook const timeout = useRef(undefined); useEffect(() => { if (timeout) window.clearTimeout(timeout.current); timeout.current = window.setTimeout(async () => { const url = providerURL.endsWith("/") ? providerURL : providerURL + "/"; if (!providerURL || authProviders.includes(url)) return; try { setTesting(true); await testProvider(url, providerType); setError(""); } catch (e) { if (e instanceof Error) setError(e.message); } setTesting(false); }, 200); }, [providerURL, reducer]); async function addProvider(provider_url: string): Promise { await reducer?.transition("add_provider", { provider_url }); onCancel(); } function deleteProvider(provider_url: string): void { reducer?.transition("delete_provider", { provider_url }); } if (!reducer) { return
no reducer in context
; } if ( !reducer.currentReducerState || !("authentication_providers" in reducer.currentReducerState) ) { return
invalid state
; } let errors = !providerURL ? "Add provider URL" : undefined; let url: string | undefined; try { url = new URL("", providerURL).href; } catch { errors = "Check the URL"; } if (!!error && !errors) { errors = error; } if (!errors && authProviders.includes(url!)) { errors = "That provider is already known"; } return (
{!providerLabel ? (

Add a provider url

) : (

Add a provider url for a {providerLabel} service

)}

Example: https://kudos.demo.anastasis.lu

{testing &&

Testing

}
{authProviders.length > 0 ? ( !providerLabel ? (

Current providers

) : (

Current providers for {providerLabel} service

) ) : !providerLabel ? (

No known providers, add one.

) : (

No known providers for {providerLabel} service

)} {authProviders.map((k) => { const p = allAuthProviders[k] as AuthenticationProviderStatusOk; return ( ); })} {authProvidersByStatus["error"]?.map((k) => { const p = k as AuthenticationProviderStatusError; return ( ); })}
); } function TableRow({ url, info, onDelete, }: { onDelete: (s: string) => void; url: string; info: AuthenticationProviderStatusOk; }): VNode { const [status, setStatus] = useState("checking"); useEffect(function () { testProvider(url.endsWith("/") ? url.substring(0, url.length - 1) : url) .then(function () { setStatus("responding"); }) .catch(function () { setStatus("failed to contact"); }); }); return (
{url}
Business Name
{info.business_name}
Supported methods
{info.methods.map((m) => m.type).join(",")}
Maximum storage
{info.storage_limit_in_megabytes} Mb
Status
{status}
); } function TableRowError({ url, info, onDelete, }: { onDelete: (s: string) => void; url: string; info: AuthenticationProviderStatusError; }): VNode { const [status, setStatus] = useState("checking"); useEffect(function () { testProvider(url.endsWith("/") ? url.substring(0, url.length - 1) : url) .then(function () { setStatus("responding"); }) .catch(function () { setStatus("failed to contact"); }); }); return (
{url}
Error
{info.hint}
Code
{info.code}
Status
{status}
); }