find taler action in clipboard and withdraw with mobile

This commit is contained in:
Sebastian 2022-09-09 12:22:26 -03:00
parent 9b2d6d766f
commit dda90b51f6
No known key found for this signature in database
GPG Key ID: BE4FF68352439FC1
10 changed files with 127 additions and 43 deletions

View File

@ -25,9 +25,11 @@ import { platform } from "../platform/api.js";
interface Type { interface Type {
findTalerUriInActiveTab: () => Promise<string | undefined>; findTalerUriInActiveTab: () => Promise<string | undefined>;
findTalerUriInClipboard: () => Promise<string | undefined>;
} }
const Context = createContext<Type>({ const Context = createContext<Type>({
findTalerUriInActiveTab: async () => undefined, findTalerUriInActiveTab: async () => undefined,
findTalerUriInClipboard: async () => undefined,
}); });
/** /**
@ -56,7 +58,10 @@ export const IoCProviderForRuntime = ({
children: any; children: any;
}): VNode => { }): VNode => {
return h(Context.Provider, { return h(Context.Provider, {
value: { findTalerUriInActiveTab: platform.findTalerUriInActiveTab }, value: {
findTalerUriInActiveTab: platform.findTalerUriInActiveTab,
findTalerUriInClipboard: platform.findTalerUriInClipboard,
},
children, children,
}); });
}; };

View File

@ -122,13 +122,13 @@ export function ReadyView({
}} }}
> >
<i18n.Translate>Exchange</i18n.Translate> <i18n.Translate>Exchange</i18n.Translate>
<Link> {/* <Link>
<SvgIcon <SvgIcon
title="Edit" title="Edit"
dangerouslySetInnerHTML={{ __html: editIcon }} dangerouslySetInnerHTML={{ __html: editIcon }}
color="black" color="black"
/> />
</Link> </Link> */}
</div> </div>
} }
text={<ExchangeDetails exchange={exchangeUrl} />} text={<ExchangeDetails exchange={exchangeUrl} />}

View File

@ -85,6 +85,7 @@ export namespace State {
ageRestriction?: SelectFieldHandler; ageRestriction?: SelectFieldHandler;
talerWithdrawUri?: string;
cancel: () => Promise<void>; cancel: () => Promise<void>;
}; };
} }

View File

@ -410,6 +410,7 @@ export function useComponentStateFromURI(
toBeReceived, toBeReceived,
withdrawalFee, withdrawalFee,
chosenAmount, chosenAmount,
talerWithdrawUri,
ageRestriction, ageRestriction,
doWithdrawal: { doWithdrawal: {
onClick: onClick:

View File

@ -23,6 +23,7 @@ import { SelectList } from "../../components/SelectList.js";
import { import {
Input, Input,
Link, Link,
LinkSuccess,
SubTitle, SubTitle,
SuccessBox, SuccessBox,
SvgIcon, SvgIcon,
@ -35,6 +36,8 @@ import { TermsOfServiceSection } from "../TermsOfServiceSection.js";
import { State } from "./index.js"; import { State } from "./index.js";
import editIcon from "../../svg/edit_24px.svg"; import editIcon from "../../svg/edit_24px.svg";
import { Amount } from "../../components/Amount.js"; import { Amount } from "../../components/Amount.js";
import { QR } from "../../components/QR.js";
import { useState } from "preact/hooks";
export function LoadingUriView({ error }: State.LoadingUriError): VNode { export function LoadingUriView({ error }: State.LoadingUriError): VNode {
const { i18n } = useTranslationContext(); const { i18n } = useTranslationContext();
@ -126,13 +129,13 @@ export function SuccessView(state: State.Success): VNode {
}} }}
> >
<i18n.Translate>Exchange</i18n.Translate> <i18n.Translate>Exchange</i18n.Translate>
<Link> {/* <Link>
<SvgIcon <SvgIcon
title="Edit" title="Edit"
dangerouslySetInnerHTML={{ __html: editIcon }} dangerouslySetInnerHTML={{ __html: editIcon }}
color="black" color="black"
/> />
</Link> </Link> */}
</div> </div>
} }
text={<ExchangeDetails exchange={state.exchangeUrl} />} text={<ExchangeDetails exchange={state.exchangeUrl} />}
@ -164,31 +167,36 @@ export function SuccessView(state: State.Success): VNode {
</section> </section>
{state.tosProps && <TermsOfServiceSection {...state.tosProps} />} {state.tosProps && <TermsOfServiceSection {...state.tosProps} />}
{state.tosProps ? ( {state.tosProps ? (
<section> <Fragment>
{(state.tosProps.terms.status === "accepted" || <section>
(state.mustAcceptFirst && state.tosProps.reviewed)) && ( {(state.tosProps.terms.status === "accepted" ||
<Button (state.mustAcceptFirst && state.tosProps.reviewed)) && (
variant="contained" <Button
color="success" variant="contained"
disabled={!state.doWithdrawal.onClick} color="success"
onClick={state.doWithdrawal.onClick} disabled={!state.doWithdrawal.onClick}
> onClick={state.doWithdrawal.onClick}
<i18n.Translate> >
Withdraw &nbsp; <Amount value={state.toBeReceived} /> <i18n.Translate>
</i18n.Translate> Withdraw &nbsp; <Amount value={state.toBeReceived} />
</Button> </i18n.Translate>
)} </Button>
{state.tosProps.terms.status === "notfound" && ( )}
<Button {state.tosProps.terms.status === "notfound" && (
variant="contained" <Button
color="warning" variant="contained"
disabled={!state.doWithdrawal.onClick} color="warning"
onClick={state.doWithdrawal.onClick} disabled={!state.doWithdrawal.onClick}
> onClick={state.doWithdrawal.onClick}
<i18n.Translate>Withdraw anyway</i18n.Translate> >
</Button> <i18n.Translate>Withdraw anyway</i18n.Translate>
)} </Button>
</section> )}
</section>
{state.talerWithdrawUri ? (
<WithdrawWithMobile talerWithdrawUri={state.talerWithdrawUri} />
) : undefined}
</Fragment>
) : ( ) : (
<section> <section>
<i18n.Translate>Loading terms of service...</i18n.Translate> <i18n.Translate>Loading terms of service...</i18n.Translate>
@ -202,3 +210,35 @@ export function SuccessView(state: State.Success): VNode {
</WalletAction> </WalletAction>
); );
} }
function WithdrawWithMobile({
talerWithdrawUri,
}: {
talerWithdrawUri: string;
}): VNode {
const { i18n } = useTranslationContext();
const [showQR, setShowQR] = useState<boolean>(false);
return (
<section>
<LinkSuccess upperCased onClick={() => setShowQR((qr) => !qr)}>
{!showQR ? (
<i18n.Translate>Withdraw to a mobile phone</i18n.Translate>
) : (
<i18n.Translate>Hide QR</i18n.Translate>
)}
</LinkSuccess>
{showQR && (
<div>
<QR text={talerWithdrawUri} />
<i18n.Translate>
Scan the QR code or &nbsp;
<a href={talerWithdrawUri}>
<i18n.Translate>click here</i18n.Translate>
</a>
</i18n.Translate>
</div>
)}
</section>
);
}

View File

@ -17,20 +17,39 @@
import { useEffect, useState } from "preact/hooks"; import { useEffect, useState } from "preact/hooks";
import { useIocContext } from "../context/iocContext.js"; import { useIocContext } from "../context/iocContext.js";
export interface UriLocation {
uri: string;
location: "clipboard" | "activeTab"
}
export function useTalerActionURL(): [ export function useTalerActionURL(): [
string | undefined, UriLocation | undefined,
(s: boolean) => void, (s: boolean) => void,
] { ] {
const [talerActionUrl, setTalerActionUrl] = useState<string | undefined>( const [talerActionUrl, setTalerActionUrl] = useState<UriLocation | undefined>(
undefined, undefined,
); );
const [dismissed, setDismissed] = useState(false); const [dismissed, setDismissed] = useState(false);
const { findTalerUriInActiveTab } = useIocContext(); const { findTalerUriInActiveTab, findTalerUriInClipboard } = useIocContext();
useEffect(() => { useEffect(() => {
async function check(): Promise<void> { async function check(): Promise<void> {
const talerUri = await findTalerUriInActiveTab(); const clipUri = await findTalerUriInClipboard();
setTalerActionUrl(talerUri); if (clipUri) {
setTalerActionUrl({
location: "clipboard",
uri: clipUri
});
return;
}
const tabUri = await findTalerUriInActiveTab();
if (tabUri) {
setTalerActionUrl({
location: "activeTab",
uri: tabUri
});
return;
}
} }
check(); check();
}); });

View File

@ -163,13 +163,22 @@ export interface PlatformAPI {
findTalerUriInActiveTab(): Promise<string | undefined>; findTalerUriInActiveTab(): Promise<string | undefined>;
/** /**
* Used from the frontend to send commands to the wallet * Popup API
* *
* @param operation * Read the current tab html and try to find any Taler URI or QR code present.
* @param payload
* *
* @return response from the backend * @return Taler URI if found
*/ */
findTalerUriInClipboard(): Promise<string | undefined>;
/**
* Used from the frontend to send commands to the wallet
*
* @param operation
* @param payload
*
* @return response from the backend
*/
sendMessageToWalletBackground( sendMessageToWalletBackground(
operation: string, operation: string,
payload: any, payload: any,

View File

@ -30,6 +30,7 @@ import {
const api: PlatformAPI = { const api: PlatformAPI = {
isFirefox, isFirefox,
findTalerUriInActiveTab, findTalerUriInActiveTab,
findTalerUriInClipboard,
getPermissionsApi, getPermissionsApi,
getWalletWebExVersion, getWalletWebExVersion,
listenToWalletBackground, listenToWalletBackground,
@ -689,6 +690,11 @@ async function findTalerUriInTab(tabId: number): Promise<string | undefined> {
} }
} }
async function findTalerUriInClipboard(): Promise<string | undefined> {
const textInClipboard = await window.navigator.clipboard.readText();
return textInClipboard.startsWith("taler://") || textInClipboard.startsWith("taler+http://") ? textInClipboard : undefined
}
async function findTalerUriInActiveTab(): Promise<string | undefined> { async function findTalerUriInActiveTab(): Promise<string | undefined> {
const tab = await getCurrentTab(); const tab = await getCurrentTab();
if (!tab || tab.id === undefined) return; if (!tab || tab.id === undefined) return;

View File

@ -23,6 +23,7 @@ const api: PlatformAPI = {
isFirefox: () => false, isFirefox: () => false,
keepAlive: (cb: VoidFunction) => cb(), keepAlive: (cb: VoidFunction) => cb(),
findTalerUriInActiveTab: async () => undefined, findTalerUriInActiveTab: async () => undefined,
findTalerUriInClipboard: async () => undefined,
containsTalerHeaderListener: () => { containsTalerHeaderListener: () => {
return true; return true;
}, },

View File

@ -42,13 +42,15 @@ import { BalancePage } from "./BalancePage.js";
import { TalerActionFound } from "./TalerActionFound.js"; import { TalerActionFound } from "./TalerActionFound.js";
function CheckTalerActionComponent(): VNode { function CheckTalerActionComponent(): VNode {
const [talerActionUrl] = useTalerActionURL(); const [action] = useTalerActionURL();
const actionUri = action?.uri;
useEffect(() => { useEffect(() => {
if (talerActionUrl) { if (actionUri) {
route(Pages.cta({ action: encodeURIComponent(talerActionUrl) })); route(Pages.cta({ action: encodeURIComponent(actionUri) }));
} }
}, [talerActionUrl]); }, [actionUri]);
return <Fragment />; return <Fragment />;
} }