gnunet-svn
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[taler-wallet-core] branch master updated: preact routing on the wallet


From: gnunet
Subject: [taler-wallet-core] branch master updated: preact routing on the wallet
Date: Fri, 07 May 2021 23:16:39 +0200

This is an automated email from the git hooks/post-receive script.

sebasjm pushed a commit to branch master
in repository wallet-core.

The following commit(s) were added to refs/heads/master by this push:
     new 4ed4535b preact routing on the wallet
4ed4535b is described below

commit 4ed4535bc090acf3e5a3b7781ba458d077aac751
Author: Sebastian <sebasjm@gmail.com>
AuthorDate: Fri May 7 18:10:27 2021 -0300

    preact routing on the wallet
---
 packages/taler-wallet-webextension/package.json    |   5 +-
 .../taler-wallet-webextension/src/Application.tsx  |  99 ++++++++++++++
 .../src/pageEntryPoint.ts                          |  39 +-----
 .../taler-wallet-webextension/src/pages/pay.tsx    |  13 +-
 .../taler-wallet-webextension/src/pages/popup.tsx  | 150 ++++++---------------
 .../taler-wallet-webextension/src/pages/refund.tsx |  18 ++-
 .../taler-wallet-webextension/src/pages/tip.tsx    |  11 +-
 .../src/pages/welcome.tsx                          |  67 ++++-----
 .../src/pages/withdraw.tsx                         |  15 ++-
 .../taler-wallet-webextension/src/renderHtml.tsx   |   2 +-
 .../taler-wallet-webextension/src/wxBackend.ts     |  10 +-
 .../static/add-auditor.html                        |  33 -----
 .../taler-wallet-webextension/static/auditors.html |  35 -----
 .../static/benchmark.html                          |  16 ---
 packages/taler-wallet-webextension/static/pay.html |  73 ----------
 .../taler-wallet-webextension/static/payback.html  |  34 -----
 .../taler-wallet-webextension/static/refund.html   |  19 ---
 .../static/reset-required.html                     |  25 ----
 .../static/style/popup.css                         |  57 +++++++-
 packages/taler-wallet-webextension/static/tip.html |  19 ---
 .../taler-wallet-webextension/static/welcome.html  |  24 ----
 .../taler-wallet-webextension/static/withdraw.html |  22 ---
 pnpm-lock.yaml                                     |  35 +++++
 23 files changed, 321 insertions(+), 500 deletions(-)

diff --git a/packages/taler-wallet-webextension/package.json 
b/packages/taler-wallet-webextension/package.json
index f9756fd3..b2179a66 100644
--- a/packages/taler-wallet-webextension/package.json
+++ b/packages/taler-wallet-webextension/package.json
@@ -10,7 +10,8 @@
   "scripts": {
     "clean": "rimraf dist lib tsconfig.tsbuildinfo",
     "test": "jest ./tests",
-    "compile": "tsc && rollup -c"
+    "compile": "tsc && rollup -c",
+    "watch": "tsc --watch & rollup -w -c"
   },
   "dependencies": {
     "@gnu-taler/taler-util": "workspace:*",
@@ -29,12 +30,14 @@
     "@rollup/plugin-replace": "^2.3.4",
     "@testing-library/preact": "^2.0.1",
     "@types/chrome": "^0.0.128",
+    "@types/history": "^4.7.8",
     "@types/jest": "^26.0.23",
     "@types/node": "^14.14.22",
     "ava": "3.15.0",
     "babel-plugin-transform-react-jsx": "^6.24.1",
     "enzyme": "^3.11.0",
     "enzyme-adapter-preact-pure": "^3.1.0",
+    "history": "4.10.1",
     "jest": "^26.6.3",
     "jest-preset-preact": "^4.0.3",
     "preact-cli": "^3.0.5",
diff --git a/packages/taler-wallet-webextension/src/Application.tsx 
b/packages/taler-wallet-webextension/src/Application.tsx
new file mode 100644
index 00000000..096f6a09
--- /dev/null
+++ b/packages/taler-wallet-webextension/src/Application.tsx
@@ -0,0 +1,99 @@
+import Router, { route, Route } from "preact-router";
+import { createHashHistory } from 'history';
+import { useEffect } from "preact/hooks";
+
+import { WalletPopup } from "./pages/popup";
+import { WithdrawalDialog } from "./pages/withdraw";
+import { Welcome } from "./pages/welcome";
+import { TalerPayDialog } from "./pages/pay";
+import { RefundStatusView } from "./pages/refund";
+import { TalerTipDialog } from './pages/tip';
+
+
+export enum Pages {
+  welcome = '/welcome',
+  pay = '/pay',
+  payback = '/payback',
+  refund = '/refund',
+  reset_required = '/reset-required',
+  return_coins = '/return-coins',
+  tips = '/tips',
+  withdraw = '/withdraw',
+  popup = '/popup/:rest',
+}
+
+export function Application() {
+  const sp = new URL(document.location.href).searchParams
+  const queryParams: any = {}
+  sp.forEach((v, k) => { queryParams[k] = v; });
+
+  return <Router history={createHashHistory()} >
+    <Route path={Pages.popup} component={WalletPopup} />
+
+    <Route path={Pages.welcome} component={() => {
+      return <section id="main">
+        <div style="border-bottom: 3px dashed #aa3939; margin-bottom: 2em;">
+          <h1 style="font-family: monospace; font-size: 250%;">
+            <span style="color: #aa3939;">❰</span>Taler Wallet<span 
style="color: #aa3939;">❱</span>
+          </h1>
+        </div>
+        <h1>Browser Extension Installed!</h1>
+        <div>
+          <Welcome />
+        </div>
+      </section>
+    }} />
+
+    <Route path={Pages.pay} component={() => {
+      return <section id="main">
+        <h1>GNU Taler Wallet</h1>
+        <article class="fade">
+          <TalerPayDialog talerPayUri={queryParams.talerPayUri} />
+        </article>
+      </section>
+    }} />
+
+    <Route path={Pages.refund} component={() => {
+      return <section id="main">
+        <h1>GNU Taler Wallet</h1>
+        <article class="fade">
+          <RefundStatusView talerRefundUri={queryParams.talerRefundUri} />
+        </article>
+      </section>
+    }} />
+
+    <Route path={Pages.tips} component={() => {
+      return <section id="main">
+        <h1>GNU Taler Wallet</h1>
+        <div>
+          <TalerTipDialog talerTipUri={queryParams.talerTipUri} />
+        </div>
+      </section>
+    }} />
+    <Route path={Pages.withdraw} component={() => {
+      return <section id="main">
+        <div style="border-bottom: 3px dashed #aa3939; margin-bottom: 2em;">
+          <h1 style="font-family: monospace; font-size: 250%;">
+            <span style="color: #aa3939;">❰</span>Taler Wallet<span 
style="color: #aa3939;">❱</span>
+          </h1>
+        </div>
+        <div class="fade">
+          <WithdrawalDialog talerWithdrawUri={queryParams.talerWithdrawUri} />
+        </div>
+      </section>
+    }} />
+
+    <Route path={Pages.reset_required} component={() => <div>no yet 
implemented</div>} />
+    <Route path={Pages.payback} component={() => <div>no yet 
implemented</div>} />
+    <Route path={Pages.return_coins} component={() => <div>no yet 
implemented</div>} />
+
+    <Route default component={Redirect} to='/popup/balance' />
+  </Router>
+}
+
+export function Redirect({ to }: { to: string }): null {
+  useEffect(() => {
+    route(to, true)
+  })
+  return null
+}
diff --git a/packages/taler-wallet-webextension/src/pageEntryPoint.ts 
b/packages/taler-wallet-webextension/src/pageEntryPoint.ts
index 06dc594c..00fd5fc7 100644
--- a/packages/taler-wallet-webextension/src/pageEntryPoint.ts
+++ b/packages/taler-wallet-webextension/src/pageEntryPoint.ts
@@ -20,50 +20,17 @@
  * @author Florian Dold <dold@taler.net>
  */
 
-import {render} from "preact";
-import { createPopup } from "./pages/popup";
-import { createWithdrawPage } from "./pages/withdraw";
-import { createWelcomePage } from "./pages/welcome";
-import { createPayPage } from "./pages/pay";
-import { createRefundPage } from "./pages/refund";
+import { render } from "preact";
 import { setupI18n } from "@gnu-taler/taler-wallet-core";
-import { createTipPage } from './pages/tip';
+import { Application } from './Application';
 
 function main(): void {
   try {
-    let mainElement;
-    const m = location.pathname.match(/([^/]+)$/);
-    if (!m) {
-      throw Error("can't parse page URL");
-    }
-    const page = m[1];
-    switch (page) {
-      case "popup.html":
-        mainElement = createPopup();
-        break;
-      case "withdraw.html":
-        mainElement = createWithdrawPage();
-        break;
-      case "welcome.html":
-        mainElement = createWelcomePage();
-        break;
-      case "pay.html":
-        mainElement = createPayPage();
-        break;
-      case "refund.html":
-        mainElement = createRefundPage();
-        break;
-      case "tip.html":
-        mainElement = createTipPage();
-        break;
-      default:
-        throw Error(`page '${page}' not implemented`);
-    }
     const container = document.getElementById("container");
     if (!container) {
       throw Error("container not found, can't mount page contents");
     }
-    render(mainElement, container);
+    render(Application(), container);
   } catch (e) {
     console.error("got error", e);
     document.body.innerText = `Fatal error: "${e.message}".  Please report 
this bug at https://bugs.gnunet.org/.`;
diff --git a/packages/taler-wallet-webextension/src/pages/pay.tsx 
b/packages/taler-wallet-webextension/src/pages/pay.tsx
index 80d846d6..10f83165 100644
--- a/packages/taler-wallet-webextension/src/pages/pay.tsx
+++ b/packages/taler-wallet-webextension/src/pages/pay.tsx
@@ -41,7 +41,11 @@ import {
 } from "@gnu-taler/taler-util";
 import { JSX, VNode } from "preact";
 
-function TalerPayDialog({ talerPayUri }: { talerPayUri: string }): JSX.Element 
{
+interface Props {
+  talerPayUri?: string
+}
+
+export function TalerPayDialog({ talerPayUri }: Props): JSX.Element {
   const [payStatus, setPayStatus] = useState<PreparePayResult | 
undefined>(undefined);
   const [payResult, setPayResult] = useState<ConfirmPayResult | 
undefined>(undefined);
   const [payErrMsg, setPayErrMsg] = useState<string | undefined>("");
@@ -50,6 +54,7 @@ function TalerPayDialog({ talerPayUri }: { talerPayUri: 
string }): JSX.Element {
   let totalFees: AmountJson | undefined = undefined;
 
   useEffect(() => {
+    if (!talerPayUri) return;
     const doFetch = async (): Promise<void> => {
       const p = await wxApi.preparePay(talerPayUri);
       setPayStatus(p);
@@ -57,6 +62,10 @@ function TalerPayDialog({ talerPayUri }: { talerPayUri: 
string }): JSX.Element {
     doFetch();
   }, [numTries, talerPayUri]);
 
+  if (!talerPayUri) {
+    return <span>missing pay uri</span>
+  }
+  
   if (!payStatus) {
     return <span>Loading payment information ...</span>;
   }
@@ -83,7 +92,7 @@ function TalerPayDialog({ talerPayUri }: { talerPayUri: 
string }): JSX.Element {
       return (
         <span>
           You have already paid for this article. Click{" "}
-          <a href={fulfillmentUrl}>here</a> to view it again.
+          <a href={fulfillmentUrl} target="_bank" rel="external">here</a> to 
view it again.
         </span>
       );
     } else {
diff --git a/packages/taler-wallet-webextension/src/pages/popup.tsx 
b/packages/taler-wallet-webextension/src/pages/popup.tsx
index cf76044c..afd331c8 100644
--- a/packages/taler-wallet-webextension/src/pages/popup.tsx
+++ b/packages/taler-wallet-webextension/src/pages/popup.tsx
@@ -24,9 +24,7 @@
 /**
  * Imports.
  */
-import * as i18n from "../i18n";
-
-import {
+ import {
   AmountJson,
   Amounts,
   BalancesResponse,
@@ -40,121 +38,43 @@ import {
   Timestamp,
   amountFractionalBase,
 } from "@gnu-taler/taler-util";
-
-import { renderAmount, PageLink } from "../renderHtml";
+import { Component, ComponentChildren, JSX } from "preact";
+import { route, Route, Router } from 'preact-router';
+import { Match } from 'preact-router/match';
+import { useEffect, useState } from "preact/hooks";
+import * as i18n from "../i18n";
+import { PageLink, renderAmount } from "../renderHtml";
 import * as wxApi from "../wxApi";
-
-import { useState, useEffect } from "preact/hooks";
-
 import { PermissionsCheckbox } from "./welcome";
-import { JSXInternal } from "preact/src/jsx";
-import { Component, ComponentChild, ComponentChildren, JSX, toChildArray, 
VNode } from "preact";
-
-// FIXME: move to newer react functions
-
-class Router extends Component<any, any> {
-  static setRoute(s: string): void {
-    window.location.hash = s;
-  }
-
-  static getRoute(): string {
-    // Omit the '#' at the beginning
-    return window.location.hash.substring(1);
-  }
-
-  static onRoute(f: any): () => void {
-    Router.routeHandlers.push(f);
-    return () => {
-      const i = Router.routeHandlers.indexOf(f);
-      this.routeHandlers = this.routeHandlers.splice(i, 1);
-    };
-  }
-
-  private static routeHandlers: any[] = [];
-
-  componentWillMount(): void {
-    console.log("router mounted");
-    window.onhashchange = () => {
-      this.setState({});
-      for (const f of Router.routeHandlers) {
-        f();
-      }
-    };
-  }
-
-  render(): JSX.Element {
-    const route = window.location.hash.substring(1);
-    console.log("rendering route", route);
-    let defaultChild: ComponentChild | null = null;
-    let foundChild: ComponentChild | null = null;
-    toChildArray(this.props.children).forEach((child) => {
-      const childProps: any = (child as any).props;
-      if (!childProps) {
-        return;
-      }
-      if (childProps.default) {
-        defaultChild = child;
-      }
-      if (childProps.route === route) {
-        foundChild = child;
-      }
-    });
-    const c: ComponentChild | null = foundChild || defaultChild;
-    if (!c) {
-      throw Error("unknown route");
-    }
-    Router.setRoute((c as any).props.route);
-    return <div>{c}</div>;
-  }
-}
 
 interface TabProps {
   target: string;
+  current?: string;
   children?: ComponentChildren;
 }
 
 function Tab(props: TabProps): JSX.Element {
   let cssClass = "";
-  if (props.target === Router.getRoute()) {
+  if (props.current === props.target) {
     cssClass = "active";
   }
-  const onClick = (e: JSXInternal.TargetedMouseEvent<HTMLAnchorElement>): void 
=> {
-    Router.setRoute(props.target);
-    e.preventDefault();
-  };
   return (
-    <a onClick={onClick} href={props.target} className={cssClass}>
+    <a href={props.target} className={cssClass}>
       {props.children}
     </a>
   );
 }
 
-class WalletNavBar extends Component<any, any> {
-  private cancelSubscription: any;
-
-  componentWillMount(): void {
-    this.cancelSubscription = Router.onRoute(() => {
-      this.setState({});
-    });
-  }
-
-  componentWillUnmount(): void {
-    if (this.cancelSubscription) {
-      this.cancelSubscription();
-    }
-  }
+function WalletNavBar({ current }: { current?: string }) {
 
-  render(): JSX.Element {
-    console.log("rendering nav bar");
-    return (
-      <div className="nav" id="header">
-        <Tab target="/balance">{i18n.str`Balance`}</Tab>
-        <Tab target="/history">{i18n.str`History`}</Tab>
-        <Tab target="/settings">{i18n.str`Settings`}</Tab>
-        <Tab target="/debug">{i18n.str`Debug`}</Tab>
-      </div>
-    );
-  }
+  return (
+    <div className="nav" id="header">
+      <Tab target="/popup/balance" current={current}>{i18n.str`Balance`}</Tab>
+      <Tab target="/popup/history" current={current}>{i18n.str`History`}</Tab>
+      <Tab target="/popup/settings" 
current={current}>{i18n.str`Settings`}</Tab>
+      <Tab target="/popup/debug" current={current}>{i18n.str`Debug`}</Tab>
+    </div>
+  );
 }
 
 /**
@@ -174,7 +94,7 @@ function EmptyBalanceView(): JSX.Element {
   return (
     <i18n.Translate wrap="p">
       You have no balance to show. Need some{" "}
-      <PageLink pageName="welcome.html">help</PageLink> getting started?
+      <PageLink pageName="/welcome">help</PageLink> getting started?
     </i18n.Translate>
   );
 }
@@ -494,7 +414,7 @@ function WalletHistory(props: any): JSX.Element {
 
   return (
     <div>
-      {txs.map((tx,i) => (
+      {txs.map((tx, i) => (
         <TransactionItem key={i} tx={tx} />
       ))}
     </div>
@@ -525,7 +445,7 @@ async function confirmReset(): Promise<void> {
   if (
     confirm(
       "Do you want to IRREVOCABLY DESTROY everything inside your" +
-        " wallet and LOSE ALL YOUR COINS?",
+      " wallet and LOSE ALL YOUR COINS?",
     )
   ) {
     await wxApi.resetDb();
@@ -634,7 +554,7 @@ async function findTalerUriInActiveTab(): Promise<string | 
undefined> {
   });
 }
 
-function WalletPopup(): JSX.Element {
+export function WalletPopup(): JSX.Element {
   const [talerActionUrl, setTalerActionUrl] = useState<string | undefined>(
     undefined,
   );
@@ -671,19 +591,29 @@ function WalletPopup(): JSX.Element {
   }
   return (
     <div>
-      <WalletNavBar />
+      <Match>{({ path }: any) => <WalletNavBar current={path} />}</Match>
       <div style={{ margin: "1em" }}>
         <Router>
-          <WalletBalanceView route="/balance" default />
-          <WalletSettings route="/settings" />
-          <WalletDebug route="/debug" />
-          <WalletHistory route="/history" />
+          <Route path={Pages.balance} component={WalletBalanceView} />
+          <Route path={Pages.settings} component={WalletSettings} />
+          <Route path={Pages.debug} component={WalletDebug} />
+          <Route path={Pages.history} component={WalletHistory} />
         </Router>
       </div>
     </div>
   );
 }
 
-export function createPopup(): JSX.Element {
-  return <WalletPopup />;
+enum Pages {
+  balance = '/popup/balance',
+  settings = '/popup/settings',
+  debug = '/popup/debug',
+  history = '/popup/history',
+}
+
+export function Redirect({ to }: { to: string }): null {
+  useEffect(() => {
+    route(to, true)
+  })
+  return null
 }
diff --git a/packages/taler-wallet-webextension/src/pages/refund.tsx 
b/packages/taler-wallet-webextension/src/pages/refund.tsx
index 6525f68c..49b78160 100644
--- a/packages/taler-wallet-webextension/src/pages/refund.tsx
+++ b/packages/taler-wallet-webextension/src/pages/refund.tsx
@@ -26,18 +26,22 @@ import {
   ApplyRefundResponse,
   Amounts,
 } from "@gnu-taler/taler-util";
-// import { h } from 'preact';
 import { useEffect, useState } from "preact/hooks";
 import { JSX } from "preact/jsx-runtime";
 
-function RefundStatusView(props: { talerRefundUri: string }): JSX.Element {
-  const [applyResult, setApplyResult] = 
useState<ApplyRefundResponse|undefined>(undefined);
+interface Props {
+  talerRefundUri?: string
+}
+
+export function RefundStatusView({ talerRefundUri }: Props): JSX.Element {
+  const [applyResult, setApplyResult] = useState<ApplyRefundResponse | 
undefined>(undefined);
   const [errMsg, setErrMsg] = useState<string | undefined>(undefined);
 
   useEffect(() => {
+    if (!talerRefundUri) return;
     const doFetch = async (): Promise<void> => {
       try {
-        const result = await wxApi.applyRefund(props.talerRefundUri);
+        const result = await wxApi.applyRefund(talerRefundUri);
         setApplyResult(result);
       } catch (e) {
         console.error(e);
@@ -46,10 +50,14 @@ function RefundStatusView(props: { talerRefundUri: string 
}): JSX.Element {
       }
     };
     doFetch();
-  }, [props.talerRefundUri]);
+  }, [talerRefundUri]);
 
   console.log("rendering");
 
+  if (!talerRefundUri) {
+    return <span>missing taler refund uri</span>;
+  }
+
   if (errMsg) {
     return <span>Error: {errMsg}</span>;
   }
diff --git a/packages/taler-wallet-webextension/src/pages/tip.tsx 
b/packages/taler-wallet-webextension/src/pages/tip.tsx
index 65ddb373..8528a551 100644
--- a/packages/taler-wallet-webextension/src/pages/tip.tsx
+++ b/packages/taler-wallet-webextension/src/pages/tip.tsx
@@ -26,7 +26,11 @@ import { AmountView } from "../renderHtml";
 import * as wxApi from "../wxApi";
 import { JSX } from "preact/jsx-runtime";
 
-function TalerTipDialog({ talerTipUri }: { talerTipUri: string }): JSX.Element 
{
+interface Props { 
+  talerTipUri?: string 
+}
+
+export function TalerTipDialog({ talerTipUri }: Props): JSX.Element {
   const [updateCounter, setUpdateCounter] = useState<number>(0);
   const [prepareTipResult, setPrepareTipResult] = useState<
     PrepareTipResult | undefined
@@ -35,6 +39,7 @@ function TalerTipDialog({ talerTipUri }: { talerTipUri: 
string }): JSX.Element {
   const [tipIgnored, setTipIgnored] = useState(false);
 
   useEffect(() => {
+    if (!talerTipUri) return;
     const doFetch = async (): Promise<void> => {
       const p = await wxApi.prepareTip({ talerTipUri });
       setPrepareTipResult(p);
@@ -54,6 +59,10 @@ function TalerTipDialog({ talerTipUri }: { talerTipUri: 
string }): JSX.Element {
     setTipIgnored(true);
   };
 
+  if (!talerTipUri) {
+    return <span>missing tip uri</span>;
+  }
+
   if (tipIgnored) {
     return <span>You've ignored the tip.</span>;
   }
diff --git a/packages/taler-wallet-webextension/src/pages/welcome.tsx 
b/packages/taler-wallet-webextension/src/pages/welcome.tsx
index 54819558..61c9f036 100644
--- a/packages/taler-wallet-webextension/src/pages/welcome.tsx
+++ b/packages/taler-wallet-webextension/src/pages/welcome.tsx
@@ -87,7 +87,7 @@ function Diagnostics(): JSX.Element | null {
             <p>
               Your wallet database is outdated. Currently automatic migration 
is
               not supported. Please go{" "}
-              <PageLink pageName="reset-required.html">here</PageLink> to reset
+              <PageLink pageName="/reset-required">here</PageLink> to reset
               the wallet database.
             </p>
           ) : null}
@@ -99,38 +99,38 @@ function Diagnostics(): JSX.Element | null {
   return <p>Running diagnostics ...</p>;
 }
 
-export function PermissionsCheckbox(): JSX.Element {
-  const [extendedPermissionsEnabled, setExtendedPermissionsEnabled] = useState(
-    false,
-  );
-  async function handleExtendedPerm(): Promise<void> {
-    let nextVal: boolean | undefined;
-    if (extendedPermissionsEnabled) {
-      const granted = await new Promise<boolean>((resolve, reject) => {
-        // We set permissions here, since apparently FF wants this to be done
-        // as the result of an input event ...
-        getPermissionsApi().request(extendedPermissions, (granted: boolean) => 
{
-          if (chrome.runtime.lastError) {
-            console.error("error requesting permissions");
-            console.error(chrome.runtime.lastError);
-            reject(chrome.runtime.lastError);
-            return;
-          }
-          console.log("permissions granted:", granted);
-          resolve(granted);
-        });
+
+async function handleExtendedPerm(isEnabled: boolean, setEnable: (v:boolean) 
=> void): Promise<void> {
+  let nextVal: boolean | undefined;
+
+  if (!isEnabled) {
+    const granted = await new Promise<boolean>((resolve, reject) => {
+      // We set permissions here, since apparently FF wants this to be done
+      // as the result of an input event ...
+      getPermissionsApi().request(extendedPermissions, (granted: boolean) => {
+        if (chrome.runtime.lastError) {
+          console.error("error requesting permissions");
+          console.error(chrome.runtime.lastError);
+          reject(chrome.runtime.lastError);
+          return;
+        }
+        console.log("permissions granted:", granted);
+        resolve(granted);
       });
-      const res = await wxApi.setExtendedPermissions(granted);
-      console.log(res);
-      nextVal = res.newValue;
-    } else {
-      const res = await wxApi.setExtendedPermissions(false);
-      console.log(res);
-      nextVal = res.newValue;
-    }
-    console.log("new permissions applied:", nextVal);
-    setExtendedPermissionsEnabled(nextVal ?? false);
+    });
+    const res = await wxApi.setExtendedPermissions(granted);
+    nextVal = res.newValue;
+  } else {
+    const res = await wxApi.setExtendedPermissions(false);
+    nextVal = res.newValue;
   }
+  console.log("new permissions applied:", nextVal);
+  setEnable(nextVal ?? false);
+}
+
+export function PermissionsCheckbox(): JSX.Element {
+  const [extendedPermissionsEnabled, setExtendedPermissionsEnabled] = 
useState(false);
+
   useEffect(() => {
     async function getExtendedPermValue(): Promise<void> {
       const res = await wxApi.getExtendedPermissions();
@@ -138,11 +138,12 @@ export function PermissionsCheckbox(): JSX.Element {
     }
     getExtendedPermValue();
   });
+
   return (
     <div>
       <input
         checked={extendedPermissionsEnabled}
-        onChange={() => handleExtendedPerm()}
+        onChange={() => handleExtendedPerm(extendedPermissionsEnabled, 
setExtendedPermissionsEnabled) }
         type="checkbox"
         id="checkbox-perm"
         style={{ width: "1.5em", height: "1.5em", verticalAlign: "middle" }}
@@ -168,7 +169,7 @@ export function PermissionsCheckbox(): JSX.Element {
   );
 }
 
-function Welcome(): JSX.Element {
+export function Welcome(): JSX.Element {
   return (
     <>
       <p>Thank you for installing the wallet.</p>
diff --git a/packages/taler-wallet-webextension/src/pages/withdraw.tsx 
b/packages/taler-wallet-webextension/src/pages/withdraw.tsx
index 1d628be2..d99bcf9c 100644
--- a/packages/taler-wallet-webextension/src/pages/withdraw.tsx
+++ b/packages/taler-wallet-webextension/src/pages/withdraw.tsx
@@ -34,12 +34,14 @@ import {
 import { WithdrawUriInfoResponse } from "@gnu-taler/taler-util";
 import { JSX } from "preact/jsx-runtime";
 
-function WithdrawalDialog(props: { talerWithdrawUri: string }): JSX.Element {
+interface Props {
+  talerWithdrawUri?: string;
+}
+export function WithdrawalDialog({ talerWithdrawUri }: Props): JSX.Element {
   const [details, setDetails] = useState<WithdrawUriInfoResponse | 
undefined>(undefined);
   const [selectedExchange, setSelectedExchange] = useState<
     string | undefined
   >(undefined);
-  const talerWithdrawUri = props.talerWithdrawUri;
   const [cancelled, setCancelled] = useState(false);
   const [selecting, setSelecting] = useState(false);
   const [errMsg, setErrMsg] = useState<string | undefined>("");
@@ -52,10 +54,9 @@ function WithdrawalDialog(props: { talerWithdrawUri: string 
}): JSX.Element {
   }, []);
 
   useEffect(() => {
+    if (!talerWithdrawUri) return
     const fetchData = async (): Promise<void> => {
-      const res = await getWithdrawalDetailsForUri({
-        talerWithdrawUri: props.talerWithdrawUri,
-      });
+      const res = await getWithdrawalDetailsForUri({ talerWithdrawUri });
       setDetails(res);
       if (res.defaultExchangeBaseUrl) {
         setSelectedExchange(res.defaultExchangeBaseUrl);
@@ -64,6 +65,10 @@ function WithdrawalDialog(props: { talerWithdrawUri: string 
}): JSX.Element {
     fetchData();
   }, [selectedExchange, errMsg, selecting, talerWithdrawUri, updateCounter]);
 
+  if (!talerWithdrawUri) {
+    return <span>missing withdraw uri</span>;
+  }
+
   if (!details) {
     return <span>Loading...</span>;
   }
diff --git a/packages/taler-wallet-webextension/src/renderHtml.tsx 
b/packages/taler-wallet-webextension/src/renderHtml.tsx
index 5574e96e..2fde6f53 100644
--- a/packages/taler-wallet-webextension/src/renderHtml.tsx
+++ b/packages/taler-wallet-webextension/src/renderHtml.tsx
@@ -167,7 +167,7 @@ export function ProgressButton({isLoading, ...rest}: 
LoadingButtonProps): JSX.El
 export function PageLink(
   props: { pageName: string, children?: ComponentChildren },
 ): JSX.Element {
-  const url = chrome.extension.getURL(`/${props.pageName}`);
+  const url = chrome.extension.getURL(`/static/popup.html#/${props.pageName}`);
   return (
     <a
       className="actionLink"
diff --git a/packages/taler-wallet-webextension/src/wxBackend.ts 
b/packages/taler-wallet-webextension/src/wxBackend.ts
index 22dd2c0e..57146c0f 100644
--- a/packages/taler-wallet-webextension/src/wxBackend.ts
+++ b/packages/taler-wallet-webextension/src/wxBackend.ts
@@ -276,7 +276,7 @@ try {
   chrome.runtime.onInstalled.addListener((details) => {
     console.log("onInstalled with reason", details.reason);
     if (details.reason === "install") {
-      const url = chrome.extension.getURL("/static/welcome.html");
+      const url = chrome.extension.getURL("/static/popup.html#/welcome");
       chrome.tabs.create({ active: true, url: url });
     }
   });
@@ -311,7 +311,7 @@ function headerListener(
         switch (uriType) {
           case TalerUriType.TalerWithdraw:
             return makeSyncWalletRedirect(
-              "/static/withdraw.html",
+              "/static/popup.html#/withdraw",
               details.tabId,
               details.url,
               {
@@ -320,7 +320,7 @@ function headerListener(
             );
           case TalerUriType.TalerPay:
             return makeSyncWalletRedirect(
-              "/static/pay.html",
+              "/static/popup.html#/pay",
               details.tabId,
               details.url,
               {
@@ -329,7 +329,7 @@ function headerListener(
             );
           case TalerUriType.TalerTip:
             return makeSyncWalletRedirect(
-              "/static/tip.html",
+              "/static/popup.html#/tip",
               details.tabId,
               details.url,
               {
@@ -338,7 +338,7 @@ function headerListener(
             );
           case TalerUriType.TalerRefund:
             return makeSyncWalletRedirect(
-              "/static/refund.html",
+              "/static/popup.html#/refund",
               details.tabId,
               details.url,
               {
diff --git a/packages/taler-wallet-webextension/static/add-auditor.html 
b/packages/taler-wallet-webextension/static/add-auditor.html
deleted file mode 100644
index 47a97c07..00000000
--- a/packages/taler-wallet-webextension/static/add-auditor.html
+++ /dev/null
@@ -1,33 +0,0 @@
-<!DOCTYPE html>
-<html>
-  <head>
-    <meta charset="UTF-8" />
-
-    <title>Taler Wallet: Add Auditor</title>
-
-    <link rel="stylesheet" type="text/css" href="/style/wallet.css" />
-
-    <link rel="icon" href="/img/icon.png" />
-
-    <script src="/pageEntryPoint.js"></script>
-
-    <style>
-      .tree-item {
-        margin: 2em;
-        border-radius: 5px;
-        border: 1px solid gray;
-        padding: 1em;
-      }
-      .button-linky {
-        background: none;
-        color: black;
-        text-decoration: underline;
-        border: none;
-      }
-    </style>
-  </head>
-
-  <body>
-    <div id="container"></div>
-  </body>
-</html>
diff --git a/packages/taler-wallet-webextension/static/auditors.html 
b/packages/taler-wallet-webextension/static/auditors.html
deleted file mode 100644
index 15261290..00000000
--- a/packages/taler-wallet-webextension/static/auditors.html
+++ /dev/null
@@ -1,35 +0,0 @@
-<!DOCTYPE html>
-<html>
-  <head>
-    <meta charset="UTF-8" />
-    <title>Taler Wallet: Auditors</title>
-
-    <link rel="stylesheet" type="text/css" href="/style/wallet.css" />
-
-    <link rel="icon" href="/img/icon.png" />
-
-    <script src="/dist/webextension/pageEntryPoint.js"></script>
-
-    <style>
-      body {
-        font-size: 100%;
-      }
-      .tree-item {
-        margin: 2em;
-        border-radius: 5px;
-        border: 1px solid gray;
-        padding: 1em;
-      }
-      .button-linky {
-        background: none;
-        color: black;
-        text-decoration: underline;
-        border: none;
-      }
-    </style>
-  </head>
-
-  <body>
-    <div id="container"></div>
-  </body>
-</html>
diff --git a/packages/taler-wallet-webextension/static/benchmark.html 
b/packages/taler-wallet-webextension/static/benchmark.html
deleted file mode 100644
index d0ca32ae..00000000
--- a/packages/taler-wallet-webextension/static/benchmark.html
+++ /dev/null
@@ -1,16 +0,0 @@
-<!DOCTYPE html>
-<html>
-  <head>
-    <meta charset="UTF-8" />
-    <title>Taler Wallet: Benchmarks</title>
-    <link rel="stylesheet" type="text/css" href="/static/style/wallet.css" />
-    <link rel="icon" href="/static/img/icon.png" />
-    <script src="/dist/pageEntryPoint.js"></script>
-  </head>
-  <body>
-    <section id="main">
-      <h1>Benchmarks</h1>
-      <div id="container"></div>
-    </section>
-  </body>
-</html>
diff --git a/packages/taler-wallet-webextension/static/pay.html 
b/packages/taler-wallet-webextension/static/pay.html
deleted file mode 100644
index 12976581..00000000
--- a/packages/taler-wallet-webextension/static/pay.html
+++ /dev/null
@@ -1,73 +0,0 @@
-<!DOCTYPE html>
-<html>
-  <head>
-    <meta charset="UTF-8" />
-    <title>Taler Wallet: Confirm Contract</title>
-
-    <link rel="stylesheet" type="text/css" href="/static/style/pure.css" />
-    <link rel="stylesheet" type="text/css" href="/static/style/wallet.css" />
-    <link rel="icon" href="/static/img/icon.png" />
-    <script src="/dist/pageEntryPoint.js"></script>
-
-    <style>
-      button.accept {
-        background-color: #5757d2;
-        border: 1px solid black;
-        border-radius: 5px;
-        margin: 1em 0;
-        padding: 0.5em;
-        font-weight: bold;
-        color: white;
-      }
-      button.linky {
-        background: none !important;
-        border: none;
-        padding: 0 !important;
-
-        font-family: arial, sans-serif;
-        color: #069;
-        text-decoration: underline;
-        cursor: pointer;
-      }
-
-      input.url {
-        width: 25em;
-      }
-
-      button.accept:disabled {
-        background-color: #dedbe8;
-        border: 1px solid white;
-        border-radius: 5px;
-        margin: 1em 0;
-        padding: 0.5em;
-        font-weight: bold;
-        color: #2c2c2c;
-      }
-
-      .errorbox {
-        border: 1px solid;
-        display: inline-block;
-        margin: 1em;
-        padding: 1em;
-        font-weight: bold;
-        background: #ff8a8a;
-      }
-
-      .okaybox {
-        border: 1px solid;
-        display: inline-block;
-        margin: 1em;
-        padding: 1em;
-        font-weight: bold;
-        background: #00fa9a;
-      }
-    </style>
-  </head>
-
-  <body>
-    <section id="main">
-      <h1>GNU Taler Wallet</h1>
-      <article id="container" class="fade"></article>
-    </section>
-  </body>
-</html>
diff --git a/packages/taler-wallet-webextension/static/payback.html 
b/packages/taler-wallet-webextension/static/payback.html
deleted file mode 100644
index 7ca9dc97..00000000
--- a/packages/taler-wallet-webextension/static/payback.html
+++ /dev/null
@@ -1,34 +0,0 @@
-<!DOCTYPE html>
-<html>
-  <head>
-    <meta charset="UTF-8" />
-    <title>Taler Wallet: Payback</title>
-
-    <link rel="stylesheet" type="text/css" href="/style/pure.css" />
-    <link rel="stylesheet" type="text/css" href="/style/wallet.css" />
-    <link rel="icon" href="/img/icon.png" />
-    <script src="/pageEntryPoint.js"></script>
-
-    <style>
-      body {
-        font-size: 100%;
-      }
-      .tree-item {
-        margin: 2em;
-        border-radius: 5px;
-        border: 1px solid gray;
-        padding: 1em;
-      }
-      .button-linky {
-        background: none;
-        color: black;
-        text-decoration: underline;
-        border: none;
-      }
-    </style>
-  </head>
-
-  <body>
-    <div id="container"></div>
-  </body>
-</html>
diff --git a/packages/taler-wallet-webextension/static/refund.html 
b/packages/taler-wallet-webextension/static/refund.html
deleted file mode 100644
index 68c826bc..00000000
--- a/packages/taler-wallet-webextension/static/refund.html
+++ /dev/null
@@ -1,19 +0,0 @@
-<!DOCTYPE html>
-<html>
-  <head>
-    <meta charset="UTF-8" />
-    <title>Taler Wallet: Refund Status</title>
-
-    <link rel="icon" href="/static/img/icon.png" />
-    <link rel="stylesheet" type="text/css" href="/static/style/pure.css" />
-    <link rel="stylesheet" type="text/css" href="/static/style/wallet.css" />
-    <script src="/dist/pageEntryPoint.js"></script>
-  </head>
-
-  <body>
-    <section id="main">
-      <h1>GNU Taler Wallet</h1>
-      <article id="container" class="fade"></article>
-    </section>
-  </body>
-</html>
diff --git a/packages/taler-wallet-webextension/static/reset-required.html 
b/packages/taler-wallet-webextension/static/reset-required.html
deleted file mode 100644
index 84943fbf..00000000
--- a/packages/taler-wallet-webextension/static/reset-required.html
+++ /dev/null
@@ -1,25 +0,0 @@
-<!DOCTYPE html>
-<html>
-  <head>
-    <meta charset="UTF-8" />
-    <title>Taler Wallet: Select Taler Provider</title>
-
-    <link rel="icon" href="/img/icon.png" />
-    <link rel="stylesheet" type="text/css" href="/style/pure.css" />
-    <link rel="stylesheet" type="text/css" href="/style/wallet.css" />
-    <script src="/pageEntryPoint.js"></script>
-
-    <style>
-      body {
-        font-size: 100%;
-        overflow-y: scroll;
-      }
-    </style>
-  </head>
-
-  <body>
-    <section id="main">
-      <div id="container"></div>
-    </section>
-  </body>
-</html>
diff --git a/packages/taler-wallet-webextension/static/style/popup.css 
b/packages/taler-wallet-webextension/static/style/popup.css
index cca00239..c0201e58 100644
--- a/packages/taler-wallet-webextension/static/style/popup.css
+++ b/packages/taler-wallet-webextension/static/style/popup.css
@@ -6,7 +6,7 @@
 
 body {
   min-height: 20em;
-  width: 30em;
+  /* width: 30em; */
   margin: 0;
   padding: 0;
   max-height: 800px;
@@ -183,3 +183,58 @@ input[type="radio"] {
   text-align: center;
   padding-top: 2em;
 }
+
+/**
+ pay html
+*/
+button.accept {
+  background-color: #5757d2;
+  border: 1px solid black;
+  border-radius: 5px;
+  margin: 1em 0;
+  padding: 0.5em;
+  font-weight: bold;
+  color: white;
+}
+button.linky {
+  background: none !important;
+  border: none;
+  padding: 0 !important;
+
+  font-family: arial, sans-serif;
+  color: #069;
+  text-decoration: underline;
+  cursor: pointer;
+}
+
+input.url {
+  width: 25em;
+}
+
+button.accept:disabled {
+  background-color: #dedbe8;
+  border: 1px solid white;
+  border-radius: 5px;
+  margin: 1em 0;
+  padding: 0.5em;
+  font-weight: bold;
+  color: #2c2c2c;
+}
+
+.errorbox {
+  border: 1px solid;
+  display: inline-block;
+  margin: 1em;
+  padding: 1em;
+  font-weight: bold;
+  background: #ff8a8a;
+}
+
+.okaybox {
+  border: 1px solid;
+  display: inline-block;
+  margin: 1em;
+  padding: 1em;
+  font-weight: bold;
+  background: #00fa9a;
+}
diff --git a/packages/taler-wallet-webextension/static/tip.html 
b/packages/taler-wallet-webextension/static/tip.html
deleted file mode 100644
index 9c074e12..00000000
--- a/packages/taler-wallet-webextension/static/tip.html
+++ /dev/null
@@ -1,19 +0,0 @@
-<!DOCTYPE html>
-<html>
-  <head>
-    <meta charset="UTF-8" />
-    <title>Taler Wallet: Received Tip</title>
-
-    <link rel="icon" href="/static/img/icon.png" />
-    <link rel="stylesheet" type="text/css" href="/static/style/pure.css" />
-    <link rel="stylesheet" type="text/css" href="/static/style/wallet.css" />
-    <script src="/dist/pageEntryPoint.js"></script>
-  </head>
-
-  <body>
-    <section id="main">
-      <h1>GNU Taler Wallet</h1>
-      <div id="container"></div>
-    </section>
-  </body>
-</html>
diff --git a/packages/taler-wallet-webextension/static/welcome.html 
b/packages/taler-wallet-webextension/static/welcome.html
deleted file mode 100644
index e0b429f4..00000000
--- a/packages/taler-wallet-webextension/static/welcome.html
+++ /dev/null
@@ -1,24 +0,0 @@
-<!DOCTYPE html>
-<html>
-  <head>
-    <meta charset="UTF-8" />
-    <title>Taler Wallet Installed</title>
-
-    <link rel="icon" href="/static/img/icon.png" />
-    <link rel="stylesheet" type="text/css" href="/static/style/pure.css" />
-    <link rel="stylesheet" type="text/css" href="/static/style/wallet.css" />
-    <script src="/dist/pageEntryPoint.js"></script>
-  </head>
-
-  <body>
-    <section id="main">
-      <div style="border-bottom: 3px dashed #aa3939; margin-bottom: 2em;">
-        <h1 style="font-family: monospace; font-size: 250%;">
-          <span style="color: #aa3939;">❰</span>Taler Wallet<span 
style="color: #aa3939;">❱</span>
-        </h1>
-      </div>
-      <h1>Browser Extension Installed!</h1>
-      <div id="container">Loading...</div>
-    </section>
-  </body>
-</html>
diff --git a/packages/taler-wallet-webextension/static/withdraw.html 
b/packages/taler-wallet-webextension/static/withdraw.html
deleted file mode 100644
index b2b83765..00000000
--- a/packages/taler-wallet-webextension/static/withdraw.html
+++ /dev/null
@@ -1,22 +0,0 @@
-<!DOCTYPE html>
-<html>
-  <head>
-    <meta charset="UTF-8" />
-    <title>Taler Wallet: Withdraw</title>
-    <link rel="icon" href="/static/img/icon.png" />
-    <link rel="stylesheet" type="text/css" href="/static/style/pure.css" />
-    <link rel="stylesheet" type="text/css" href="/static/style/wallet.css" />
-    <script src="/dist/pageEntryPoint.js"></script>
-  </head>
-
-  <body>
-    <section id="main">
-      <div style="border-bottom: 3px dashed #aa3939; margin-bottom: 2em;">
-        <h1 style="font-family: monospace; font-size: 250%;">
-          <span style="color: #aa3939;">❰</span>Taler Wallet<span 
style="color: #aa3939;">❱</span>
-        </h1>
-      </div>
-      <div class="fade" id="container"></div>
-    </section>
-  </body>
-</html>
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 09b4f1c1..198562e9 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -216,12 +216,14 @@ importers:
       '@rollup/plugin-replace': ^2.3.4
       '@testing-library/preact': ^2.0.1
       '@types/chrome': ^0.0.128
+      '@types/history': ^4.7.8
       '@types/jest': ^26.0.23
       '@types/node': ^14.14.22
       ava: 3.15.0
       babel-plugin-transform-react-jsx: ^6.24.1
       enzyme: ^3.11.0
       enzyme-adapter-preact-pure: ^3.1.0
+      history: 4.10.1
       jest: ^26.6.3
       jest-preset-preact: ^4.0.3
       preact: ^10.5.13
@@ -251,12 +253,14 @@ importers:
       '@rollup/plugin-replace': 2.3.4_rollup@2.37.1
       '@testing-library/preact': 2.0.1_preact@10.5.13
       '@types/chrome': 0.0.128
+      '@types/history': 4.7.8
       '@types/jest': 26.0.23
       '@types/node': 14.14.22
       ava: 3.15.0
       babel-plugin-transform-react-jsx: 6.24.1
       enzyme: 3.11.0
       enzyme-adapter-preact-pure: 3.1.0_enzyme@3.11.0+preact@10.5.13
+      history: 4.10.1
       jest: 26.6.3
       jest-preset-preact: 4.0.3_669f037bdb6c36f0a67e918c516dafdd
       preact-cli: 3.0.5_c069246dc1d99535ac277c76f8ef56e0
@@ -2178,6 +2182,10 @@ packages:
     resolution: {integrity: 
sha512-IG8AE1m2pWtPqQ7wXhFhy6Q59bwwnLwO36v5Rit2FrbXCIp8Sk8E2PfUCreyrdo17STwFSKDAkitVuVYbpEHvQ==}
     dev: true
 
+  /@types/history/4.7.8:
+    resolution: {integrity: 
sha512-S78QIYirQcUoo6UJZx9CSP0O2ix9IaeAXwQi26Rhr/+mg7qqPy8TzaxHSUut7eGjL8WmLccT7/MXf304WjqHcA==}
+    dev: true
+
   /@types/istanbul-lib-coverage/2.0.3:
     resolution: {integrity: 
sha512-sz7iLqvVUg1gIedBOvlkxPlc8/uVzyS5OwGz1cKjXzkl3FpL3al0crU8YGU1WoHkxn0Wxbw5tyi6hvzJKNzFsw==}
     dev: true
@@ -6569,6 +6577,17 @@ packages:
     resolution: {integrity: 
sha512-l9sfDFsuqtOqKDsQdqrMRk0U85RZc0RtOR9yPI7mRVOa4FsR/BVnZ0shmQRM96Ji99kYZP/7hn1cedc1+ApsTQ==}
     dev: true
 
+  /history/4.10.1:
+    resolution: {integrity: 
sha512-36nwAD620w12kuzPAsyINPWJqlNbij+hpK1k9XRloDtym8mxzGYl2c17LnV6IAGB2Dmg4tEa7G7DlawS0+qjew==}
+    dependencies:
+      '@babel/runtime': 7.14.0
+      loose-envify: 1.4.0
+      resolve-pathname: 3.0.0
+      tiny-invariant: 1.1.0
+      tiny-warning: 1.0.3
+      value-equal: 1.0.1
+    dev: true
+
   /hmac-drbg/1.0.1:
     resolution: {integrity: sha1-0nRXAQJabHdabFRXk+1QL8DGSaE=}
     dependencies:
@@ -10818,6 +10837,10 @@ packages:
     engines: {node: '>=8'}
     dev: true
 
+  /resolve-pathname/3.0.0:
+    resolution: {integrity: 
sha512-C7rARubxI8bXFNB/hqcp/4iUeIXJhJZvFPFPiSPRnhU5UPxzMFIl+2E6yY6c4k9giDJAhtV+enfA+G89N6Csng==}
+    dev: true
+
   /resolve-url/0.2.1:
     resolution: {integrity: sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=}
     deprecated: https://github.com/lydell/resolve-url#deprecated
@@ -12097,6 +12120,14 @@ packages:
     resolution: {integrity: sha1-QFQRqOfmM5/mTbmiNN4R3DHgK9Q=}
     dev: true
 
+  /tiny-invariant/1.1.0:
+    resolution: {integrity: 
sha512-ytxQvrb1cPc9WBEI/HSeYYoGD0kWnGEOR8RY6KomWLBVhqz0RgTwVO9dLrGz7dC+nN9llyI7OKAgRq8Vq4ZBSw==}
+    dev: true
+
+  /tiny-warning/1.0.3:
+    resolution: {integrity: 
sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==}
+    dev: true
+
   /tmpl/1.0.4:
     resolution: {integrity: sha1-I2QN17QtAEM5ERQIIOXPRA5SHdE=}
     dev: true
@@ -12674,6 +12705,10 @@ packages:
     engines: {node: '>= 0.10'}
     dev: true
 
+  /value-equal/1.0.1:
+    resolution: {integrity: 
sha512-NOJ6JZCAWr0zlxZt+xqCHNTEKOsrks2HQd4MqhP1qy4z1SkbEP467eNx6TgDKXMvUOb+OENfJCZwM+16n7fRfw==}
+    dev: true
+
   /vary/1.1.2:
     resolution: {integrity: sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=}
     engines: {node: '>= 0.8'}

-- 
To stop receiving notification emails like this one, please contact
gnunet@gnunet.org.



reply via email to

[Prev in Thread] Current Thread [Next in Thread]