add testing to web components
This commit is contained in:
parent
716da3246b
commit
6d06b52605
@ -37,6 +37,8 @@ echo compile
|
|||||||
build_css &
|
build_css &
|
||||||
build_js src/main.ts &
|
build_js src/main.ts &
|
||||||
build_js src/main.test.ts &
|
build_js src/main.test.ts &
|
||||||
|
for file in $(find src/ -name test.ts); do build_js $file; done &
|
||||||
|
wait -n
|
||||||
wait -n
|
wait -n
|
||||||
wait -n
|
wait -n
|
||||||
wait -n
|
wait -n
|
||||||
|
@ -9,12 +9,14 @@
|
|||||||
"dev": "./dev.mjs",
|
"dev": "./dev.mjs",
|
||||||
"prepare": "pnpm compile",
|
"prepare": "pnpm compile",
|
||||||
"lint": "eslint 'src/**/*.{js,jsx,ts,tsx}'",
|
"lint": "eslint 'src/**/*.{js,jsx,ts,tsx}'",
|
||||||
"test": "mocha --enable-source-maps 'dist/**/*.test.js'",
|
"test": "mocha --enable-source-maps 'dist/**/*test.js'",
|
||||||
"pretty": "prettier --write src"
|
"pretty": "prettier --write src"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@gnu-taler/anastasis-core": "workspace:*",
|
"@gnu-taler/anastasis-core": "workspace:*",
|
||||||
"@gnu-taler/taler-util": "workspace:*",
|
"@gnu-taler/taler-util": "workspace:*",
|
||||||
|
"@types/chai": "^4.3.0",
|
||||||
|
"chai": "^4.3.6",
|
||||||
"date-fns": "2.28.0",
|
"date-fns": "2.28.0",
|
||||||
"jed": "1.1.1",
|
"jed": "1.1.1",
|
||||||
"preact": "^10.5.15",
|
"preact": "^10.5.15",
|
||||||
|
21
packages/anastasis-webui/src/components/InvalidState.tsx
Normal file
21
packages/anastasis-webui/src/components/InvalidState.tsx
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
/*
|
||||||
|
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 <http://www.gnu.org/licenses/>
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { h, VNode } from "preact";
|
||||||
|
|
||||||
|
export default function InvalidState(): VNode {
|
||||||
|
return <div>invalid state</div>;
|
||||||
|
}
|
21
packages/anastasis-webui/src/components/NoReducer.tsx
Normal file
21
packages/anastasis-webui/src/components/NoReducer.tsx
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
/*
|
||||||
|
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 <http://www.gnu.org/licenses/>
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { h, VNode } from "preact";
|
||||||
|
|
||||||
|
export default function NoReducer(): VNode {
|
||||||
|
return <div>no reducer</div>;
|
||||||
|
}
|
@ -1,61 +0,0 @@
|
|||||||
/*
|
|
||||||
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 <http://www.gnu.org/licenses/>
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @author Sebastian Javier Marchano (sebasjm)
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { ReducerState } from "@gnu-taler/anastasis-core";
|
|
||||||
import { createExample, reducerStatesExample } from "../../utils/index.js";
|
|
||||||
import { AddingProviderScreen as TestedComponent } from "./AddingProviderScreen.js";
|
|
||||||
|
|
||||||
export default {
|
|
||||||
title: "Pages/ManageProvider",
|
|
||||||
component: TestedComponent,
|
|
||||||
args: {
|
|
||||||
order: 1,
|
|
||||||
},
|
|
||||||
argTypes: {
|
|
||||||
onUpdate: { action: "onUpdate" },
|
|
||||||
onBack: { action: "onBack" },
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export const NewProvider = createExample(TestedComponent, {
|
|
||||||
...reducerStatesExample.authEditing,
|
|
||||||
} as ReducerState);
|
|
||||||
|
|
||||||
export const NewProviderWithoutProviderList = createExample(TestedComponent, {
|
|
||||||
...reducerStatesExample.authEditing,
|
|
||||||
authentication_providers: {},
|
|
||||||
} as ReducerState);
|
|
||||||
|
|
||||||
export const NewSmsProvider = createExample(
|
|
||||||
TestedComponent,
|
|
||||||
{
|
|
||||||
...reducerStatesExample.authEditing,
|
|
||||||
} as ReducerState,
|
|
||||||
{ providerType: "sms" },
|
|
||||||
);
|
|
||||||
|
|
||||||
export const NewIBANProvider = createExample(
|
|
||||||
TestedComponent,
|
|
||||||
{
|
|
||||||
...reducerStatesExample.authEditing,
|
|
||||||
} as ReducerState,
|
|
||||||
{ providerType: "iban" },
|
|
||||||
);
|
|
@ -1,367 +0,0 @@
|
|||||||
/*
|
|
||||||
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 <http://www.gnu.org/licenses/>
|
|
||||||
*/
|
|
||||||
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<void> {
|
|
||||||
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<string | undefined>();
|
|
||||||
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<number | undefined>(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<void> {
|
|
||||||
await reducer?.transition("add_provider", { provider_url });
|
|
||||||
onCancel();
|
|
||||||
}
|
|
||||||
function deleteProvider(provider_url: string): void {
|
|
||||||
reducer?.transition("delete_provider", { provider_url });
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!reducer) {
|
|
||||||
return <div>no reducer in context</div>;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
!reducer.currentReducerState ||
|
|
||||||
!("authentication_providers" in reducer.currentReducerState)
|
|
||||||
) {
|
|
||||||
return <div>invalid state</div>;
|
|
||||||
}
|
|
||||||
|
|
||||||
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 (
|
|
||||||
<AnastasisClientFrame
|
|
||||||
hideNav
|
|
||||||
title="Backup: Manage providers"
|
|
||||||
hideNext={errors}
|
|
||||||
>
|
|
||||||
<div>
|
|
||||||
{!providerLabel ? (
|
|
||||||
<p>Add a provider url</p>
|
|
||||||
) : (
|
|
||||||
<p>Add a provider url for a {providerLabel} service</p>
|
|
||||||
)}
|
|
||||||
<div class="container">
|
|
||||||
<TextInput
|
|
||||||
label="Provider URL"
|
|
||||||
placeholder="https://provider.com"
|
|
||||||
grabFocus
|
|
||||||
error={errors}
|
|
||||||
bind={[providerURL, setProviderURL]}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<p class="block">Example: https://kudos.demo.anastasis.lu</p>
|
|
||||||
{testing && <p class="has-text-info">Testing</p>}
|
|
||||||
|
|
||||||
<div
|
|
||||||
class="block"
|
|
||||||
style={{
|
|
||||||
marginTop: "2em",
|
|
||||||
display: "flex",
|
|
||||||
justifyContent: "space-between",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<button class="button" onClick={onCancel}>
|
|
||||||
Cancel
|
|
||||||
</button>
|
|
||||||
<span data-tooltip={errors}>
|
|
||||||
<button
|
|
||||||
class="button is-info"
|
|
||||||
disabled={error !== "" || testing}
|
|
||||||
onClick={() => addProvider(url!)}
|
|
||||||
>
|
|
||||||
Add
|
|
||||||
</button>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{authProviders.length > 0 ? (
|
|
||||||
!providerLabel ? (
|
|
||||||
<p class="subtitle">Current providers</p>
|
|
||||||
) : (
|
|
||||||
<p class="subtitle">
|
|
||||||
Current providers for {providerLabel} service
|
|
||||||
</p>
|
|
||||||
)
|
|
||||||
) : !providerLabel ? (
|
|
||||||
<p class="subtitle">No known providers, add one.</p>
|
|
||||||
) : (
|
|
||||||
<p class="subtitle">No known providers for {providerLabel} service</p>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{authProviders.map((k) => {
|
|
||||||
const p = allAuthProviders[k] as AuthenticationProviderStatusOk;
|
|
||||||
return (
|
|
||||||
<TableRow key={k} url={k} info={p} onDelete={deleteProvider} />
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
{authProvidersByStatus["error"]?.map((k) => {
|
|
||||||
const p = k as AuthenticationProviderStatusError;
|
|
||||||
return (
|
|
||||||
<TableRowError
|
|
||||||
key={k}
|
|
||||||
url={k.url}
|
|
||||||
info={p}
|
|
||||||
onDelete={deleteProvider}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
</AnastasisClientFrame>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
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 (
|
|
||||||
<div
|
|
||||||
class="box"
|
|
||||||
style={{ display: "flex", justifyContent: "space-between" }}
|
|
||||||
>
|
|
||||||
<div>
|
|
||||||
<div class="subtitle">{url}</div>
|
|
||||||
<dl>
|
|
||||||
<dt>
|
|
||||||
<b>Business Name</b>
|
|
||||||
</dt>
|
|
||||||
<dd>{info.business_name}</dd>
|
|
||||||
<dt>
|
|
||||||
<b>Supported methods</b>
|
|
||||||
</dt>
|
|
||||||
<dd>{info.methods.map((m) => m.type).join(",")}</dd>
|
|
||||||
<dt>
|
|
||||||
<b>Maximum storage</b>
|
|
||||||
</dt>
|
|
||||||
<dd>{info.storage_limit_in_megabytes} Mb</dd>
|
|
||||||
<dt>
|
|
||||||
<b>Status</b>
|
|
||||||
</dt>
|
|
||||||
<dd>{status}</dd>
|
|
||||||
</dl>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
class="block"
|
|
||||||
style={{
|
|
||||||
marginTop: "auto",
|
|
||||||
marginBottom: "auto",
|
|
||||||
display: "flex",
|
|
||||||
justifyContent: "space-between",
|
|
||||||
flexDirection: "column",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<button class="button is-danger" onClick={() => onDelete(url)}>
|
|
||||||
Remove
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
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 (
|
|
||||||
<div
|
|
||||||
class="box"
|
|
||||||
style={{ display: "flex", justifyContent: "space-between" }}
|
|
||||||
>
|
|
||||||
<div>
|
|
||||||
<div class="subtitle">{url}</div>
|
|
||||||
<dl>
|
|
||||||
<dt>
|
|
||||||
<b>Error</b>
|
|
||||||
</dt>
|
|
||||||
<dd>{info.hint}</dd>
|
|
||||||
<dt>
|
|
||||||
<b>Code</b>
|
|
||||||
</dt>
|
|
||||||
<dd>{info.code}</dd>
|
|
||||||
<dt>
|
|
||||||
<b>Status</b>
|
|
||||||
</dt>
|
|
||||||
<dd>{status}</dd>
|
|
||||||
</dl>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
class="block"
|
|
||||||
style={{
|
|
||||||
marginTop: "auto",
|
|
||||||
marginBottom: "auto",
|
|
||||||
display: "flex",
|
|
||||||
justifyContent: "space-between",
|
|
||||||
flexDirection: "column",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<button class="button is-danger" onClick={() => onDelete(url)}>
|
|
||||||
Remove
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
@ -0,0 +1,102 @@
|
|||||||
|
/*
|
||||||
|
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 <http://www.gnu.org/licenses/>
|
||||||
|
*/
|
||||||
|
import { AuthenticationProviderStatus } from "@gnu-taler/anastasis-core";
|
||||||
|
import InvalidState from "../../../components/InvalidState.js";
|
||||||
|
import NoReducer from "../../../components/NoReducer.js";
|
||||||
|
import { compose, StateViewMap } from "../../../utils/index.js";
|
||||||
|
import useComponentState from "./state.js";
|
||||||
|
import { WithoutProviderType, WithProviderType } from "./views.js";
|
||||||
|
|
||||||
|
export type AuthProvByStatusMap = Record<
|
||||||
|
AuthenticationProviderStatus["status"],
|
||||||
|
(AuthenticationProviderStatus & { url: string })[]
|
||||||
|
>
|
||||||
|
|
||||||
|
export type State = NoReducer | InvalidState | WithType | WithoutType;
|
||||||
|
|
||||||
|
export interface NoReducer {
|
||||||
|
status: "no-reducer";
|
||||||
|
}
|
||||||
|
export interface InvalidState {
|
||||||
|
status: "invalid-state";
|
||||||
|
}
|
||||||
|
|
||||||
|
interface CommonProps {
|
||||||
|
addProvider?: () => Promise<void>;
|
||||||
|
deleteProvider: (url: string) => Promise<void>;
|
||||||
|
authProvidersByStatus: AuthProvByStatusMap;
|
||||||
|
error: string | undefined;
|
||||||
|
onCancel: () => Promise<void>;
|
||||||
|
testing: boolean;
|
||||||
|
setProviderURL: (url: string) => Promise<void>;
|
||||||
|
providerURL: string;
|
||||||
|
errors: string | undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface WithType extends CommonProps {
|
||||||
|
status: "with-type";
|
||||||
|
providerLabel: string;
|
||||||
|
}
|
||||||
|
export interface WithoutType extends CommonProps {
|
||||||
|
status: "without-type";
|
||||||
|
}
|
||||||
|
|
||||||
|
const map: StateViewMap<State> = {
|
||||||
|
"no-reducer": NoReducer,
|
||||||
|
"invalid-state": InvalidState,
|
||||||
|
"with-type": WithProviderType,
|
||||||
|
"without-type": WithoutProviderType,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default compose("AddingProviderScreen", useComponentState, map)
|
||||||
|
|
||||||
|
|
||||||
|
export async function testProvider(
|
||||||
|
url: string,
|
||||||
|
expectedMethodType?: string,
|
||||||
|
): Promise<void> {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,147 @@
|
|||||||
|
/*
|
||||||
|
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 <http://www.gnu.org/licenses/>
|
||||||
|
*/
|
||||||
|
import { useEffect, useRef, useState } from "preact/hooks";
|
||||||
|
import { useAnastasisContext } from "../../../context/anastasis.js";
|
||||||
|
import { authMethods, KnownAuthMethods } from "../authMethod/index.jsx";
|
||||||
|
import { AuthProvByStatusMap, State, testProvider } from "./index.js";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
providerType?: KnownAuthMethods;
|
||||||
|
onCancel: () => Promise<void>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function useComponentState({ providerType, onCancel }: Props): State {
|
||||||
|
const reducer = useAnastasisContext();
|
||||||
|
|
||||||
|
const [providerURL, setProviderURL] = useState("");
|
||||||
|
|
||||||
|
const [error, setError] = useState<string | undefined>();
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
prev[p.status].push({ ...p, url });
|
||||||
|
return prev;
|
||||||
|
},
|
||||||
|
{ "not-contacted": [], disabled: [], error: [], ok: [] } as AuthProvByStatusMap,
|
||||||
|
);
|
||||||
|
const authProviders = authProvidersByStatus["ok"].map((p) => p.url);
|
||||||
|
|
||||||
|
//FIXME: move this timeout logic into a hook
|
||||||
|
const timeout = useRef<ReturnType<typeof setTimeout> | undefined>(undefined);
|
||||||
|
useEffect(() => {
|
||||||
|
if (timeout.current) clearTimeout(timeout.current);
|
||||||
|
timeout.current = 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]);
|
||||||
|
|
||||||
|
if (!reducer) {
|
||||||
|
return {
|
||||||
|
status: "no-reducer",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
!reducer.currentReducerState ||
|
||||||
|
!("authentication_providers" in reducer.currentReducerState)
|
||||||
|
) {
|
||||||
|
return {
|
||||||
|
status: "invalid-state",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const addProvider = async (provider_url: string): Promise<void> => {
|
||||||
|
await reducer.transition("add_provider", { provider_url });
|
||||||
|
onCancel();
|
||||||
|
}
|
||||||
|
const deleteProvider = async (provider_url: string): Promise<void> => {
|
||||||
|
reducer.transition("delete_provider", { provider_url });
|
||||||
|
}
|
||||||
|
|
||||||
|
let errors = !providerURL ? "Add provider URL" : undefined;
|
||||||
|
let url: string | undefined;
|
||||||
|
try {
|
||||||
|
url = new URL("", providerURL).href;
|
||||||
|
} catch {
|
||||||
|
errors = "Check the URL";
|
||||||
|
}
|
||||||
|
const _url = url
|
||||||
|
|
||||||
|
if (!!error && !errors) {
|
||||||
|
errors = error;
|
||||||
|
}
|
||||||
|
if (!errors && authProviders.includes(url!)) {
|
||||||
|
errors = "That provider is already known";
|
||||||
|
}
|
||||||
|
|
||||||
|
const commonState = {
|
||||||
|
addProvider: !_url ? undefined : async () => addProvider(_url),
|
||||||
|
deleteProvider: async (url: string) => deleteProvider(url),
|
||||||
|
allAuthProviders,
|
||||||
|
authProvidersByStatus,
|
||||||
|
onCancel,
|
||||||
|
providerURL,
|
||||||
|
testing,
|
||||||
|
setProviderURL: async (s: string) => setProviderURL(s),
|
||||||
|
errors,
|
||||||
|
error,
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!providerLabel) {
|
||||||
|
return {
|
||||||
|
status: "without-type",
|
||||||
|
...commonState
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return {
|
||||||
|
status: "with-type",
|
||||||
|
providerLabel,
|
||||||
|
...commonState
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,89 @@
|
|||||||
|
/*
|
||||||
|
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 <http://www.gnu.org/licenses/>
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @author Sebastian Javier Marchano (sebasjm)
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { AuthenticationProviderStatusOk } from "@gnu-taler/anastasis-core";
|
||||||
|
import { createExampleWithoutAnastasis } from "../../../utils/index.jsx";
|
||||||
|
import { WithoutProviderType, WithProviderType } from "./views.jsx";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
title: "Pages/ManageProvider",
|
||||||
|
args: {
|
||||||
|
order: 1,
|
||||||
|
},
|
||||||
|
argTypes: {
|
||||||
|
onUpdate: { action: "onUpdate" },
|
||||||
|
onBack: { action: "onBack" },
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const NewProvider = createExampleWithoutAnastasis(WithoutProviderType, {
|
||||||
|
authProvidersByStatus: {
|
||||||
|
ok: [
|
||||||
|
{
|
||||||
|
business_name: "X provider",
|
||||||
|
status: "ok",
|
||||||
|
storage_limit_in_megabytes: 5,
|
||||||
|
methods: [
|
||||||
|
{
|
||||||
|
type: "question",
|
||||||
|
usage_fee: "KUDOS:1",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
url: "",
|
||||||
|
} as AuthenticationProviderStatusOk & { url: string },
|
||||||
|
],
|
||||||
|
"not-contacted": [],
|
||||||
|
disabled: [],
|
||||||
|
error: [],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export const NewProviderWithoutProviderList = createExampleWithoutAnastasis(
|
||||||
|
WithoutProviderType,
|
||||||
|
{
|
||||||
|
authProvidersByStatus: {
|
||||||
|
ok: [],
|
||||||
|
"not-contacted": [],
|
||||||
|
disabled: [],
|
||||||
|
error: [],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
export const NewSmsProvider = createExampleWithoutAnastasis(WithProviderType, {
|
||||||
|
authProvidersByStatus: {
|
||||||
|
ok: [],
|
||||||
|
"not-contacted": [],
|
||||||
|
disabled: [],
|
||||||
|
error: [],
|
||||||
|
},
|
||||||
|
providerLabel: "sms",
|
||||||
|
});
|
||||||
|
|
||||||
|
export const NewIBANProvider = createExampleWithoutAnastasis(WithProviderType, {
|
||||||
|
authProvidersByStatus: {
|
||||||
|
ok: [],
|
||||||
|
"not-contacted": [],
|
||||||
|
disabled: [],
|
||||||
|
error: [],
|
||||||
|
},
|
||||||
|
providerLabel: "IBAN",
|
||||||
|
});
|
@ -0,0 +1,42 @@
|
|||||||
|
/*
|
||||||
|
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 <http://www.gnu.org/licenses/>
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @author Sebastian Javier Marchano (sebasjm)
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { expect } from "chai";
|
||||||
|
import { mountHook } from "../../../test-utils.js";
|
||||||
|
import useComponentState from "./state.js";
|
||||||
|
|
||||||
|
describe("AddingProviderScreen states", () => {
|
||||||
|
it("should have status 'no-balance' when balance is empty", async () => {
|
||||||
|
const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } =
|
||||||
|
mountHook(() =>
|
||||||
|
useComponentState({ onCancel: async () => { null } }),
|
||||||
|
);
|
||||||
|
|
||||||
|
{
|
||||||
|
const { status } = getLastResultOrThrow();
|
||||||
|
expect(status).equal("no-reducer");
|
||||||
|
}
|
||||||
|
|
||||||
|
await assertNoPendingUpdate();
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
@ -0,0 +1,304 @@
|
|||||||
|
/*
|
||||||
|
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 <http://www.gnu.org/licenses/>
|
||||||
|
*/
|
||||||
|
import {
|
||||||
|
AuthenticationProviderStatusError,
|
||||||
|
AuthenticationProviderStatusOk,
|
||||||
|
} from "@gnu-taler/anastasis-core";
|
||||||
|
import { h, VNode } from "preact";
|
||||||
|
import { useEffect, useState } from "preact/hooks";
|
||||||
|
import { TextInput } from "../../../components/fields/TextInput.js";
|
||||||
|
import { AnastasisClientFrame } from "../index.js";
|
||||||
|
import { testProvider, WithoutType, WithType } from "./index.js";
|
||||||
|
|
||||||
|
export function WithProviderType(props: WithType): VNode {
|
||||||
|
return (
|
||||||
|
<AnastasisClientFrame
|
||||||
|
hideNav
|
||||||
|
title="Backup: Manage providers1"
|
||||||
|
hideNext={props.errors}
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
<p>Add a provider url for a {props.providerLabel} service</p>
|
||||||
|
<div class="container">
|
||||||
|
<TextInput
|
||||||
|
label="Provider URL"
|
||||||
|
placeholder="https://provider.com"
|
||||||
|
grabFocus
|
||||||
|
error={props.errors}
|
||||||
|
bind={[props.providerURL, props.setProviderURL]}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<p class="block">Example: https://kudos.demo.anastasis.lu</p>
|
||||||
|
{props.testing && <p class="has-text-info">Testing</p>}
|
||||||
|
|
||||||
|
<div
|
||||||
|
class="block"
|
||||||
|
style={{
|
||||||
|
marginTop: "2em",
|
||||||
|
display: "flex",
|
||||||
|
justifyContent: "space-between",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<button class="button" onClick={props.onCancel}>
|
||||||
|
Cancel
|
||||||
|
</button>
|
||||||
|
<span data-tooltip={props.errors}>
|
||||||
|
<button
|
||||||
|
class="button is-info"
|
||||||
|
disabled={props.error !== "" || props.testing}
|
||||||
|
onClick={props.addProvider}
|
||||||
|
>
|
||||||
|
Add
|
||||||
|
</button>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{props.authProvidersByStatus["ok"].length > 0 ? (
|
||||||
|
<p class="subtitle">
|
||||||
|
Current providers for {props.providerLabel} service
|
||||||
|
</p>
|
||||||
|
) : (
|
||||||
|
<p class="subtitle">
|
||||||
|
No known providers for {props.providerLabel} service
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{props.authProvidersByStatus["ok"].map((k, i) => {
|
||||||
|
const p = k as AuthenticationProviderStatusOk;
|
||||||
|
return (
|
||||||
|
<TableRow
|
||||||
|
key={i}
|
||||||
|
url={k.url}
|
||||||
|
info={p}
|
||||||
|
onDelete={props.deleteProvider}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
<p class="subtitle">Providers with errors</p>
|
||||||
|
{props.authProvidersByStatus["error"].map((k, i) => {
|
||||||
|
const p = k as AuthenticationProviderStatusError;
|
||||||
|
return (
|
||||||
|
<TableRowError
|
||||||
|
key={i}
|
||||||
|
url={k.url}
|
||||||
|
info={p}
|
||||||
|
onDelete={props.deleteProvider}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</AnastasisClientFrame>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function WithoutProviderType(props: WithoutType): VNode {
|
||||||
|
return (
|
||||||
|
<AnastasisClientFrame
|
||||||
|
hideNav
|
||||||
|
title="Backup: Manage providers2"
|
||||||
|
hideNext={props.errors}
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
<p>Add a provider url</p>
|
||||||
|
<div class="container">
|
||||||
|
<TextInput
|
||||||
|
label="Provider URL"
|
||||||
|
placeholder="https://provider.com"
|
||||||
|
grabFocus
|
||||||
|
error={props.errors}
|
||||||
|
bind={[props.providerURL, props.setProviderURL]}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<p class="block">Example: https://kudos.demo.anastasis.lu</p>
|
||||||
|
{props.testing && <p class="has-text-info">Testing</p>}
|
||||||
|
|
||||||
|
<div
|
||||||
|
class="block"
|
||||||
|
style={{
|
||||||
|
marginTop: "2em",
|
||||||
|
display: "flex",
|
||||||
|
justifyContent: "space-between",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<button class="button" onClick={props.onCancel}>
|
||||||
|
Cancel
|
||||||
|
</button>
|
||||||
|
<span data-tooltip={props.errors}>
|
||||||
|
<button
|
||||||
|
class="button is-info"
|
||||||
|
disabled={props.error !== "" || props.testing}
|
||||||
|
onClick={props.addProvider}
|
||||||
|
>
|
||||||
|
Add
|
||||||
|
</button>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{props.authProvidersByStatus["ok"].length > 0 ? (
|
||||||
|
<p class="subtitle">Current providers</p>
|
||||||
|
) : (
|
||||||
|
<p class="subtitle">No known providers, add one.</p>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{props.authProvidersByStatus["ok"].map((k, i) => {
|
||||||
|
const p = k as AuthenticationProviderStatusOk;
|
||||||
|
return (
|
||||||
|
<TableRow
|
||||||
|
key={i}
|
||||||
|
url={k.url}
|
||||||
|
info={p}
|
||||||
|
onDelete={props.deleteProvider}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
<p class="subtitle">Providers with errors</p>
|
||||||
|
{props.authProvidersByStatus["error"].map((k, i) => {
|
||||||
|
const p = k as AuthenticationProviderStatusError;
|
||||||
|
return (
|
||||||
|
<TableRowError
|
||||||
|
key={i}
|
||||||
|
url={k.url}
|
||||||
|
info={p}
|
||||||
|
onDelete={props.deleteProvider}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</AnastasisClientFrame>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function TableRow({
|
||||||
|
url,
|
||||||
|
info,
|
||||||
|
onDelete,
|
||||||
|
}: {
|
||||||
|
onDelete: (s: string) => Promise<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 (
|
||||||
|
<div
|
||||||
|
class="box"
|
||||||
|
style={{ display: "flex", justifyContent: "space-between" }}
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
<div class="subtitle">{url}</div>
|
||||||
|
<dl>
|
||||||
|
<dt>
|
||||||
|
<b>Business Name</b>
|
||||||
|
</dt>
|
||||||
|
<dd>{info.business_name}</dd>
|
||||||
|
<dt>
|
||||||
|
<b>Supported methods</b>
|
||||||
|
</dt>
|
||||||
|
<dd>{info.methods.map((m) => m.type).join(",")}</dd>
|
||||||
|
<dt>
|
||||||
|
<b>Maximum storage</b>
|
||||||
|
</dt>
|
||||||
|
<dd>{info.storage_limit_in_megabytes} Mb</dd>
|
||||||
|
<dt>
|
||||||
|
<b>Status</b>
|
||||||
|
</dt>
|
||||||
|
<dd>{status}</dd>
|
||||||
|
</dl>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="block"
|
||||||
|
style={{
|
||||||
|
marginTop: "auto",
|
||||||
|
marginBottom: "auto",
|
||||||
|
display: "flex",
|
||||||
|
justifyContent: "space-between",
|
||||||
|
flexDirection: "column",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<button class="button is-danger" onClick={() => onDelete(url)}>
|
||||||
|
Remove
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
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 (
|
||||||
|
<div
|
||||||
|
class="box"
|
||||||
|
style={{ display: "flex", justifyContent: "space-between" }}
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
<div class="subtitle">{url}</div>
|
||||||
|
<dl>
|
||||||
|
<dt>
|
||||||
|
<b>Error</b>
|
||||||
|
</dt>
|
||||||
|
<dd>{info.hint}</dd>
|
||||||
|
<dt>
|
||||||
|
<b>Code</b>
|
||||||
|
</dt>
|
||||||
|
<dd>{info.code}</dd>
|
||||||
|
<dt>
|
||||||
|
<b>Status</b>
|
||||||
|
</dt>
|
||||||
|
<dd>{status}</dd>
|
||||||
|
</dl>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="block"
|
||||||
|
style={{
|
||||||
|
marginTop: "auto",
|
||||||
|
marginBottom: "auto",
|
||||||
|
display: "flex",
|
||||||
|
justifyContent: "space-between",
|
||||||
|
flexDirection: "column",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<button class="button is-danger" onClick={() => onDelete(url)}>
|
||||||
|
Remove
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
@ -17,7 +17,7 @@ import { AuthMethod, ReducerStateBackup } from "@gnu-taler/anastasis-core";
|
|||||||
import { Fragment, h, VNode } from "preact";
|
import { Fragment, h, VNode } from "preact";
|
||||||
import { useState } from "preact/hooks";
|
import { useState } from "preact/hooks";
|
||||||
import { useAnastasisContext } from "../../context/anastasis.js";
|
import { useAnastasisContext } from "../../context/anastasis.js";
|
||||||
import { AddingProviderScreen } from "./AddingProviderScreen.js";
|
import AddingProviderScreen from "./AddingProviderScreen/index.js";
|
||||||
import {
|
import {
|
||||||
authMethods,
|
authMethods,
|
||||||
AuthMethodSetupProps,
|
AuthMethodSetupProps,
|
||||||
@ -84,7 +84,7 @@ export function AuthenticationEditorScreen(): VNode {
|
|||||||
if (manageProvider !== undefined) {
|
if (manageProvider !== undefined) {
|
||||||
return (
|
return (
|
||||||
<AddingProviderScreen
|
<AddingProviderScreen
|
||||||
onCancel={() => setManageProvider(undefined)}
|
onCancel={async () => setManageProvider(undefined)}
|
||||||
providerType={
|
providerType={
|
||||||
isKnownAuthMethods(manageProvider) ? manageProvider : undefined
|
isKnownAuthMethods(manageProvider) ? manageProvider : undefined
|
||||||
}
|
}
|
||||||
|
@ -22,7 +22,7 @@ import { useEffect, useState } from "preact/hooks";
|
|||||||
import { AsyncButton } from "../../components/AsyncButton.js";
|
import { AsyncButton } from "../../components/AsyncButton.js";
|
||||||
import { PhoneNumberInput } from "../../components/fields/NumberInput.js";
|
import { PhoneNumberInput } from "../../components/fields/NumberInput.js";
|
||||||
import { useAnastasisContext } from "../../context/anastasis.js";
|
import { useAnastasisContext } from "../../context/anastasis.js";
|
||||||
import { AddingProviderScreen } from "./AddingProviderScreen.js";
|
import AddingProviderScreen from "./AddingProviderScreen/index.js";
|
||||||
import { AnastasisClientFrame } from "./index.js";
|
import { AnastasisClientFrame } from "./index.js";
|
||||||
|
|
||||||
export function SecretSelectionScreen(): VNode {
|
export function SecretSelectionScreen(): VNode {
|
||||||
@ -54,7 +54,9 @@ export function SecretSelectionScreen(): VNode {
|
|||||||
const recoveryDocument = reducer.currentReducerState.recovery_document;
|
const recoveryDocument = reducer.currentReducerState.recovery_document;
|
||||||
|
|
||||||
if (manageProvider) {
|
if (manageProvider) {
|
||||||
return <AddingProviderScreen onCancel={() => setManageProvider(false)} />;
|
return (
|
||||||
|
<AddingProviderScreen onCancel={async () => setManageProvider(false)} />
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (reducer.discoveryState.state === "none") {
|
if (reducer.discoveryState.state === "none") {
|
||||||
@ -220,7 +222,9 @@ export function OldSecretSelectionScreen(): VNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (manageProvider) {
|
if (manageProvider) {
|
||||||
return <AddingProviderScreen onCancel={() => setManageProvider(false)} />;
|
return (
|
||||||
|
<AddingProviderScreen onCancel={async () => setManageProvider(false)} />
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const providerInfo = provs[
|
const providerInfo = provs[
|
||||||
|
@ -19,7 +19,7 @@
|
|||||||
* @author Sebastian Javier Marchano (sebasjm)
|
* @author Sebastian Javier Marchano (sebasjm)
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import * as a23 from "./AddingProviderScreen.stories.js";
|
import * as a23 from "./AddingProviderScreen/stories.js";
|
||||||
import * as a28 from "./AttributeEntryScreen.stories.js";
|
import * as a28 from "./AttributeEntryScreen.stories.js";
|
||||||
import * as a18 from "./AuthenticationEditorScreen.stories.js";
|
import * as a18 from "./AuthenticationEditorScreen.stories.js";
|
||||||
import * as a8 from "./authMethod/AuthMethodEmailSetup.stories.js";
|
import * as a8 from "./authMethod/AuthMethodEmailSetup.stories.js";
|
||||||
|
@ -168,9 +168,9 @@ export function AnastasisClientFrame(props: AnastasisClientFrameProps): VNode {
|
|||||||
window.removeEventListener("popstate", browserOnBackButton);
|
window.removeEventListener("popstate", browserOnBackButton);
|
||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
if (!reducer) {
|
// if (!reducer) {
|
||||||
return <p>Fatal: Reducer must be in context.</p>;
|
// return <p>Fatal: Reducer must be in context.</p>;
|
||||||
}
|
// }
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Fragment>
|
<Fragment>
|
||||||
|
@ -21,13 +21,26 @@ import {
|
|||||||
ReducerState,
|
ReducerState,
|
||||||
ReducerStateRecovery,
|
ReducerStateRecovery,
|
||||||
} from "@gnu-taler/anastasis-core";
|
} from "@gnu-taler/anastasis-core";
|
||||||
import { FunctionalComponent, h, VNode } from "preact";
|
import { ComponentChildren, FunctionalComponent, h, VNode } from "preact";
|
||||||
import { AnastasisProvider } from "../context/anastasis.js";
|
import { AnastasisProvider } from "../context/anastasis.js";
|
||||||
|
|
||||||
const noop = async (): Promise<void> => {
|
const noop = async (): Promise<void> => {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export function createExampleWithoutAnastasis<Props>(
|
||||||
|
Component: FunctionalComponent<Props>,
|
||||||
|
props: Partial<Props> | (() => Partial<Props>),
|
||||||
|
): ComponentChildren {
|
||||||
|
//FIXME: props are evaluated on build time
|
||||||
|
// in some cases we want to evaluated the props on render time so we can get some relative timestamp
|
||||||
|
// check how we can build evaluatedProps in render time
|
||||||
|
const evaluatedProps = typeof props === "function" ? props() : props;
|
||||||
|
const Render = (args: any): VNode => h(Component, args);
|
||||||
|
Render.args = evaluatedProps;
|
||||||
|
return Render;
|
||||||
|
}
|
||||||
|
|
||||||
export function createExample<Props>(
|
export function createExample<Props>(
|
||||||
Component: FunctionalComponent<Props>,
|
Component: FunctionalComponent<Props>,
|
||||||
currentReducerState?: ReducerState,
|
currentReducerState?: ReducerState,
|
||||||
@ -293,3 +306,24 @@ export const reducerStatesExample = {
|
|||||||
backup_state: BackupStates.TruthsPaying,
|
backup_state: BackupStates.TruthsPaying,
|
||||||
} as ReducerState,
|
} as ReducerState,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type StateFunc<S> = (p: S) => VNode;
|
||||||
|
|
||||||
|
export type StateViewMap<StateType extends { status: string }> = {
|
||||||
|
[S in StateType as S["status"]]: StateFunc<S>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function compose<SType extends { status: string }, PType>(
|
||||||
|
name: string,
|
||||||
|
hook: (p: PType) => SType,
|
||||||
|
vs: StateViewMap<SType>,
|
||||||
|
): (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<SType>;
|
||||||
|
return c(state);
|
||||||
|
};
|
||||||
|
Component.name = `${name}`;
|
||||||
|
return Component;
|
||||||
|
}
|
||||||
|
@ -1,4 +1,19 @@
|
|||||||
#!/usr/bin/env node
|
#!/usr/bin/env node
|
||||||
|
/*
|
||||||
|
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 <http://www.gnu.org/licenses/>
|
||||||
|
*/
|
||||||
|
|
||||||
import linaria from '@linaria/esbuild'
|
import linaria from '@linaria/esbuild'
|
||||||
import esbuild from 'esbuild'
|
import esbuild from 'esbuild'
|
||||||
@ -61,7 +76,7 @@ export const buildConfig = {
|
|||||||
],
|
],
|
||||||
format: 'iife',
|
format: 'iife',
|
||||||
platform: 'browser',
|
platform: 'browser',
|
||||||
sourcemap: true,
|
sourcemap: true,
|
||||||
jsxFactory: 'h',
|
jsxFactory: 'h',
|
||||||
jsxFragment: 'Fragment',
|
jsxFragment: 'Fragment',
|
||||||
// define: {
|
// define: {
|
||||||
|
@ -56,10 +56,12 @@ importers:
|
|||||||
'@creativebulma/bulma-tooltip': ^1.2.0
|
'@creativebulma/bulma-tooltip': ^1.2.0
|
||||||
'@gnu-taler/anastasis-core': workspace:*
|
'@gnu-taler/anastasis-core': workspace:*
|
||||||
'@gnu-taler/taler-util': workspace:*
|
'@gnu-taler/taler-util': workspace:*
|
||||||
|
'@types/chai': ^4.3.0
|
||||||
'@types/mocha': ^9.0.0
|
'@types/mocha': ^9.0.0
|
||||||
bulma: ^0.9.3
|
bulma: ^0.9.3
|
||||||
bulma-checkbox: ^1.1.1
|
bulma-checkbox: ^1.1.1
|
||||||
bulma-radio: ^1.1.1
|
bulma-radio: ^1.1.1
|
||||||
|
chai: ^4.3.6
|
||||||
chokidar: ^3.5.3
|
chokidar: ^3.5.3
|
||||||
date-fns: 2.28.0
|
date-fns: 2.28.0
|
||||||
eslint-plugin-header: ^3.1.1
|
eslint-plugin-header: ^3.1.1
|
||||||
@ -76,6 +78,8 @@ importers:
|
|||||||
dependencies:
|
dependencies:
|
||||||
'@gnu-taler/anastasis-core': link:../anastasis-core
|
'@gnu-taler/anastasis-core': link:../anastasis-core
|
||||||
'@gnu-taler/taler-util': link:../taler-util
|
'@gnu-taler/taler-util': link:../taler-util
|
||||||
|
'@types/chai': 4.3.0
|
||||||
|
chai: 4.3.6
|
||||||
date-fns: 2.28.0
|
date-fns: 2.28.0
|
||||||
jed: 1.1.1
|
jed: 1.1.1
|
||||||
preact: 10.6.5
|
preact: 10.6.5
|
||||||
@ -3488,7 +3492,6 @@ packages:
|
|||||||
|
|
||||||
/@types/chai/4.3.0:
|
/@types/chai/4.3.0:
|
||||||
resolution: {integrity: sha512-/ceqdqeRraGolFTcfoXNiqjyQhZzbINDngeoAq9GoHa8PPK1yNzTaxWjA6BFWp5Ua9JpXEMSS4s5i9tS0hOJtw==}
|
resolution: {integrity: sha512-/ceqdqeRraGolFTcfoXNiqjyQhZzbINDngeoAq9GoHa8PPK1yNzTaxWjA6BFWp5Ua9JpXEMSS4s5i9tS0hOJtw==}
|
||||||
dev: true
|
|
||||||
|
|
||||||
/@types/chrome/0.0.176:
|
/@types/chrome/0.0.176:
|
||||||
resolution: {integrity: sha512-LOveFOMIUhMJjvRzZv5whGBpncP/gdJ4hcxeAqg94wGi6CyKaCmLgFSofgItf85GuLTl/0BQ6J/Y1e8BqZWfEg==}
|
resolution: {integrity: sha512-LOveFOMIUhMJjvRzZv5whGBpncP/gdJ4hcxeAqg94wGi6CyKaCmLgFSofgItf85GuLTl/0BQ6J/Y1e8BqZWfEg==}
|
||||||
@ -4330,7 +4333,6 @@ packages:
|
|||||||
|
|
||||||
/assertion-error/1.1.0:
|
/assertion-error/1.1.0:
|
||||||
resolution: {integrity: sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==}
|
resolution: {integrity: sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==}
|
||||||
dev: true
|
|
||||||
|
|
||||||
/assign-symbols/1.0.0:
|
/assign-symbols/1.0.0:
|
||||||
resolution: {integrity: sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=}
|
resolution: {integrity: sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=}
|
||||||
@ -5208,7 +5210,6 @@ packages:
|
|||||||
loupe: 2.3.4
|
loupe: 2.3.4
|
||||||
pathval: 1.1.1
|
pathval: 1.1.1
|
||||||
type-detect: 4.0.8
|
type-detect: 4.0.8
|
||||||
dev: true
|
|
||||||
|
|
||||||
/chalk/0.4.0:
|
/chalk/0.4.0:
|
||||||
resolution: {integrity: sha1-UZmj3c0MHv4jvAjBsCewYXbgxk8=}
|
resolution: {integrity: sha1-UZmj3c0MHv4jvAjBsCewYXbgxk8=}
|
||||||
@ -5264,8 +5265,7 @@ packages:
|
|||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/check-error/1.0.2:
|
/check-error/1.0.2:
|
||||||
resolution: {integrity: sha1-V00xLt2Iu13YkS6Sht1sCu1KrII=}
|
resolution: {integrity: sha512-BrgHpW9NURQgzoNyjfq0Wu6VFO6D7IZEmJNdtgNqpzGG8RuNFHt2jQxWlAs4HMe119chBnv+34syEZtc6IhLtA==}
|
||||||
dev: true
|
|
||||||
|
|
||||||
/chokidar/2.1.8:
|
/chokidar/2.1.8:
|
||||||
resolution: {integrity: sha512-ZmZUazfOzf0Nve7duiCKD23PFSCs4JPoYyccjUFF3aQkQadqBhfzhjkwBH2mNOG9cTBwhamM37EIsIkZw3nRgg==}
|
resolution: {integrity: sha512-ZmZUazfOzf0Nve7duiCKD23PFSCs4JPoYyccjUFF3aQkQadqBhfzhjkwBH2mNOG9cTBwhamM37EIsIkZw3nRgg==}
|
||||||
@ -6242,7 +6242,6 @@ packages:
|
|||||||
engines: {node: '>=0.12'}
|
engines: {node: '>=0.12'}
|
||||||
dependencies:
|
dependencies:
|
||||||
type-detect: 4.0.8
|
type-detect: 4.0.8
|
||||||
dev: true
|
|
||||||
|
|
||||||
/deep-equal/1.1.1:
|
/deep-equal/1.1.1:
|
||||||
resolution: {integrity: sha512-yd9c5AdiqVcR+JjcwUQb9DkhJc8ngNr0MahEBGvDiJw8puWab2yZlh+nkasOnZP+EGTAP6rRp2JzJhJZzvNF8g==}
|
resolution: {integrity: sha512-yd9c5AdiqVcR+JjcwUQb9DkhJc8ngNr0MahEBGvDiJw8puWab2yZlh+nkasOnZP+EGTAP6rRp2JzJhJZzvNF8g==}
|
||||||
@ -7773,8 +7772,7 @@ packages:
|
|||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/get-func-name/2.0.0:
|
/get-func-name/2.0.0:
|
||||||
resolution: {integrity: sha1-6td0q+5y4gQJQzoGY2YCPdaIekE=}
|
resolution: {integrity: sha512-Hm0ixYtaSZ/V7C8FJrtZIuBBI+iSgL+1Aq82zSu8VQNB4S3Gk8e7Qs3VwBDJAhmRZcFqkl3tQu36g/Foh5I5ig==}
|
||||||
dev: true
|
|
||||||
|
|
||||||
/get-intrinsic/1.1.1:
|
/get-intrinsic/1.1.1:
|
||||||
resolution: {integrity: sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==}
|
resolution: {integrity: sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==}
|
||||||
@ -9312,7 +9310,6 @@ packages:
|
|||||||
resolution: {integrity: sha512-OvKfgCC2Ndby6aSTREl5aCCPTNIzlDfQZvZxNUrBrihDhL3xcrYegTblhmEiCrg2kKQz4XsFIaemE5BF4ybSaQ==}
|
resolution: {integrity: sha512-OvKfgCC2Ndby6aSTREl5aCCPTNIzlDfQZvZxNUrBrihDhL3xcrYegTblhmEiCrg2kKQz4XsFIaemE5BF4ybSaQ==}
|
||||||
dependencies:
|
dependencies:
|
||||||
get-func-name: 2.0.0
|
get-func-name: 2.0.0
|
||||||
dev: true
|
|
||||||
|
|
||||||
/lower-case/1.1.4:
|
/lower-case/1.1.4:
|
||||||
resolution: {integrity: sha1-miyr0bno4K6ZOkv31YdcOcQujqw=}
|
resolution: {integrity: sha1-miyr0bno4K6ZOkv31YdcOcQujqw=}
|
||||||
@ -10453,7 +10450,6 @@ packages:
|
|||||||
|
|
||||||
/pathval/1.1.1:
|
/pathval/1.1.1:
|
||||||
resolution: {integrity: sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==}
|
resolution: {integrity: sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==}
|
||||||
dev: true
|
|
||||||
|
|
||||||
/pbkdf2/3.1.2:
|
/pbkdf2/3.1.2:
|
||||||
resolution: {integrity: sha512-iuh7L6jA7JEGu2WxDwtQP1ddOpaJNC4KlDEFfdQajSGgGPNi4OyDc2R7QnbY2bR9QjBVGwgvTdNJZoE7RaxUMA==}
|
resolution: {integrity: sha512-iuh7L6jA7JEGu2WxDwtQP1ddOpaJNC4KlDEFfdQajSGgGPNi4OyDc2R7QnbY2bR9QjBVGwgvTdNJZoE7RaxUMA==}
|
||||||
@ -13165,7 +13161,6 @@ packages:
|
|||||||
/type-detect/4.0.8:
|
/type-detect/4.0.8:
|
||||||
resolution: {integrity: sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==}
|
resolution: {integrity: sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==}
|
||||||
engines: {node: '>=4'}
|
engines: {node: '>=4'}
|
||||||
dev: true
|
|
||||||
|
|
||||||
/type-fest/0.13.1:
|
/type-fest/0.13.1:
|
||||||
resolution: {integrity: sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg==}
|
resolution: {integrity: sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg==}
|
||||||
|
Loading…
Reference in New Issue
Block a user