diff options
Diffstat (limited to 'packages/anastasis-webui')
10 files changed, 99 insertions, 55 deletions
diff --git a/packages/anastasis-webui/package.json b/packages/anastasis-webui/package.json index c1c2925a2..108b1476e 100644 --- a/packages/anastasis-webui/package.json +++ b/packages/anastasis-webui/package.json @@ -1,7 +1,7 @@ { "private": true, "name": "@gnu-taler/anastasis-webui", - "version": "0.2.99", + "version": "0.10.7", "license": "MIT", "type": "module", "scripts": { diff --git a/packages/anastasis-webui/src/components/menu/SideBar.tsx b/packages/anastasis-webui/src/components/menu/SideBar.tsx index 3dac73e04..31bc3c7a7 100644 --- a/packages/anastasis-webui/src/components/menu/SideBar.tsx +++ b/packages/anastasis-webui/src/components/menu/SideBar.tsx @@ -29,7 +29,10 @@ interface Props { } const VERSION = typeof __VERSION__ !== "undefined" ? __VERSION__ : "dev"; -const GIT_HASH = typeof __GIT_HASH__ !== "undefined" ? __GIT_HASH__ : undefined; +const GIT_HASH = + typeof __GIT_HASH__ !== "undefined" + ? __GIT_HASH__.substring(0, 7) + : undefined; const VERSION_WITH_HASH = GIT_HASH ? `${VERSION}-${GIT_HASH}` : VERSION; export function Sidebar({ mobile }: Props): VNode { diff --git a/packages/anastasis-webui/src/hooks/use-anastasis-reducer.ts b/packages/anastasis-webui/src/hooks/use-anastasis-reducer.ts index fc8c4cf6c..fcc380775 100644 --- a/packages/anastasis-webui/src/hooks/use-anastasis-reducer.ts +++ b/packages/anastasis-webui/src/hooks/use-anastasis-reducer.ts @@ -303,7 +303,7 @@ export function useAnastasisReducer(): AnastasisReducerApi { }, }); } catch (e) { - throw Error("could not restore the state"); + throw new Error("could not restore the state"); } }, async discoverStart(): Promise<void> { @@ -399,7 +399,7 @@ export function useAnastasisReducer(): AnastasisReducerApi { } class ReducerTxImpl implements ReducerTransactionHandle { - constructor(public transactionState: ReducerState) { } + constructor(public transactionState: ReducerState) {} async transition(action: string, args: any): Promise<ReducerState> { let s: ReducerState; if (remoteReducer) { @@ -410,7 +410,7 @@ class ReducerTxImpl implements ReducerTransactionHandle { this.transactionState = s; // Abort transaction as soon as we transition into an error state. if (this.transactionState.reducer_type === "error") { - throw Error("transition resulted in error"); + throw new Error("transition resulted in error"); } return this.transactionState; } diff --git a/packages/anastasis-webui/src/index.html b/packages/anastasis-webui/src/index.html index 90a795ae3..d64b627e4 100644 --- a/packages/anastasis-webui/src/index.html +++ b/packages/anastasis-webui/src/index.html @@ -32,7 +32,7 @@ /> <link rel="shortcut icon" href="data:image/x-icon;," type="image/x-icon" /> <title>Anastasis</title> - <!-- Entry point for the demobank SPA. --> + <!-- Entry point for the SPA. --> <script type="module" src="index.js"></script> <link rel="stylesheet" href="index.css" /> </head> diff --git a/packages/anastasis-webui/src/index.ts b/packages/anastasis-webui/src/index.ts index d7b2164ab..f614e4f54 100644 --- a/packages/anastasis-webui/src/index.ts +++ b/packages/anastasis-webui/src/index.ts @@ -22,7 +22,7 @@ function main(): void { try { const container = document.getElementById("container"); if (!container) { - throw Error("container not found, can't mount page contents"); + throw new Error("container not found, can't mount page contents"); } render(h(App, {}), container); } catch (e) { diff --git a/packages/anastasis-webui/src/pages/home/AddingProviderScreen/index.ts b/packages/anastasis-webui/src/pages/home/AddingProviderScreen/index.ts index 0ab275f54..ed8301d65 100644 --- a/packages/anastasis-webui/src/pages/home/AddingProviderScreen/index.ts +++ b/packages/anastasis-webui/src/pages/home/AddingProviderScreen/index.ts @@ -24,7 +24,7 @@ import { WithoutProviderType, WithProviderType } from "./views.js"; export type AuthProvByStatusMap = Record< AuthenticationProviderStatus["status"], (AuthenticationProviderStatus & { url: string })[] -> +>; export type State = NoReducer | InvalidState | WithType | WithoutType; @@ -63,42 +63,69 @@ const map: StateViewMap<State> = { "without-type": WithoutProviderType, }; -export default compose("AddingProviderScreen", useComponentState, map) - +export default compose("AddingProviderScreen", useComponentState, map); +const providerResponseCache = new Map<string, any>(); // `any` is the return type of res.json() export async function testProvider( url: string, expectedMethodType?: string, ): Promise<void> { + const testFatalPrefix = `Encountered a fatal error whilst testing the provider ${url}`; + let configUrl = ""; 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}`, - ); - } + configUrl = new URL("config", url).href; + } catch (error) { + throw new Error(`${testFatalPrefix}: Invalid Provider URL: ${url} +Error: ${error}`); + } + // TODO: look into using core.getProviderInfo :) + const providerHasUrl = providerResponseCache.has(url); + const json = providerHasUrl + ? providerResponseCache.get(url) + : await fetch(configUrl) + .catch((error) => { + throw new Error(`${testFatalPrefix}: Could not connect: ${error} +Please check the URL.`); + }) + .then(async (response) => { + if (!response.ok) + throw new Error( + `${testFatalPrefix}: The server ${response.url} responded with a non-2xx response.`, + ); + try { + return await response.json(); + } catch (error) { + throw new Error( + `${testFatalPrefix}: The server responded with malformed JSON.\nError: ${error}`, + ); + } + }); + if (typeof json !== "object") + throw new Error( + `${testFatalPrefix}: Did not get an object after decoding.`, + ); + if (!("name" in json) || json.name !== "anastasis") { + throw new Error( + `${testFatalPrefix}: The provider does not appear to be an Anastasis provider. Please check the provider's URL.`, + ); + } + if (!("methods" in json) || !Array.isArray(json.methods)) { + throw new Error( + "This provider doesn't have authentication method. Please check the provider's URL and ensure it is properly configured.", + ); + } + if (!providerHasUrl) providerResponseCache.set(url, json); + if (!expectedMethodType) { return; - } catch (e) { - console.log("ERROR testProvider", 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; } + let found = false; + for (let i = 0; i < json.methods.length && !found; i++) { + found = json.methods[i].type === expectedMethodType; + } + if (!found) { + throw new Error( + `${testFatalPrefix}: This provider does not support authentication method ${expectedMethodType}`, + ); + } + return; } diff --git a/packages/anastasis-webui/src/pages/home/AddingProviderScreen/state.ts b/packages/anastasis-webui/src/pages/home/AddingProviderScreen/state.ts index f80f1c464..30e4d750d 100644 --- a/packages/anastasis-webui/src/pages/home/AddingProviderScreen/state.ts +++ b/packages/anastasis-webui/src/pages/home/AddingProviderScreen/state.ts @@ -76,14 +76,23 @@ export default function useComponentState({ useEffect(() => { if (timeout.current) clearTimeout(timeout.current); timeout.current = setTimeout(async () => { - const url = providerURL.endsWith("/") ? providerURL : providerURL + "/"; - if (!providerURL || authProviders.includes(url)) return; + let url = providerURL; + if (!url || authProviders.includes(url)) return; + if (url && !url.match(/^(https?:)\/\/.+\/(?:config)?$/iu)) + return setError( + "Malformed URL: Must be an HTTP(S) URL ending with a /", + ); + if (url.endsWith("/config")) url = url.substring(0, url.length - 6); try { setTesting(true); await testProvider(url, providerType); setError(""); } catch (e) { if (e instanceof Error) setError(e.message); + else + throw new Error( + `Unexpected Error Type: ${typeof e} - Cannot handle. Error: ${e}`, + ); } setTesting(false); }, 200); @@ -114,11 +123,12 @@ export default function useComponentState({ let errors = !providerURL ? "Add provider URL" : undefined; let url: string | undefined; - try { - url = new URL("", providerURL).href; - } catch { - errors = "Check the URL"; - } + // We'll validate it in testProvider & via a regex above - there's no need in this :) + // try { + // url = new URL("", providerURL).href; + // } catch { + // errors = "Check the URL"; + // } const _url = url; if (!!error && !errors) { diff --git a/packages/anastasis-webui/src/pages/home/AddingProviderScreen/views.tsx b/packages/anastasis-webui/src/pages/home/AddingProviderScreen/views.tsx index 19557a12f..00a42a949 100644 --- a/packages/anastasis-webui/src/pages/home/AddingProviderScreen/views.tsx +++ b/packages/anastasis-webui/src/pages/home/AddingProviderScreen/views.tsx @@ -121,13 +121,13 @@ export function WithoutProviderType(props: WithoutType): VNode { <div class="container"> <TextInput label="Provider URL" - placeholder="https://provider.com" + placeholder="https://provider.com/" grabFocus error={props.errors} bind={[props.providerURL, props.setProviderURL]} /> </div> - <p class="block">Example: https://kudos.demo.anastasis.lu</p> + <p class="block">Example: https://kudos.demo.anastasis.lu/</p> {props.testing && <p class="has-text-info">Testing</p>} <div diff --git a/packages/anastasis-webui/src/pages/home/AttributeEntryScreen.tsx b/packages/anastasis-webui/src/pages/home/AttributeEntryScreen.tsx index 228186a2d..1f8cea7aa 100644 --- a/packages/anastasis-webui/src/pages/home/AttributeEntryScreen.tsx +++ b/packages/anastasis-webui/src/pages/home/AttributeEntryScreen.tsx @@ -97,13 +97,11 @@ export function AttributeEntryScreen(): VNode { function saveAsPDF(): void { const printWindow = window.open("", "", "height=400,width=800"); const divContents = document.getElementById("printThis"); - const styleContents = document.getElementById("style-id"); - if (!printWindow || !divContents || !styleContents) return; + if (!printWindow || !divContents) return; printWindow.document.write( - "<html><head><title>Anastasis Recovery Document</title><style>", + `<html><head><link rel="stylesheet" href="index.css" /><title>Anastasis Recovery Document</title><style>`, ); - printWindow.document.write(styleContents.innerHTML); printWindow.document.write("</style></head><body> </body></html>"); printWindow.document.close(); printWindow.document.body.appendChild(divContents.cloneNode(true)); @@ -132,6 +130,7 @@ export function AttributeEntryScreen(): VNode { secret will be safely stored. If you forget what you have entered or if there is a misspell you will be unable to recover your secret. <p> + {/* TODO: make this actually work reliably cross-browser lol (opens about:blank for me) */} <a onClick={saveAsPDF}>Save the personal information as PDF</a> </p> </ConfirmModal> diff --git a/packages/anastasis-webui/src/pages/home/RecoveryFinishedScreen.tsx b/packages/anastasis-webui/src/pages/home/RecoveryFinishedScreen.tsx index 62ac410a2..f528bc207 100644 --- a/packages/anastasis-webui/src/pages/home/RecoveryFinishedScreen.tsx +++ b/packages/anastasis-webui/src/pages/home/RecoveryFinishedScreen.tsx @@ -58,9 +58,14 @@ export function RecoveryFinishedScreen(): VNode { const secret = bytesToString(decodeCrock(encodedSecret.value)); const plainText = encodedSecret.value.length < 1000 && encodedSecret.mime === "text/plain"; - const contentURI = !plainText - ? secret - : `data:${encodedSecret.mime},${secret}`; + + let [uri, setUri] = useState(`data:${encodedSecret.mime},${secret}`); + fetch(`data:${encodedSecret.mime},${secret}`) // TODO: look into using new Blob + .then((v) => v.blob()) + .then((blob) => URL.createObjectURL(blob)) + .then((newUri) => { + setUri(newUri); + }); return ( <AnastasisClientFrame title="Recovery Success" hideNav> <h2 class="subtitle">Your secret was recovered</h2> @@ -87,7 +92,7 @@ export function RecoveryFinishedScreen(): VNode { download={ encodedSecret.filename ? encodedSecret.filename : "secret.file" } - href={contentURI} + href={uri} > <div class="icon is-small "> <i class="mdi mdi-download" /> |