drastically reduce permissions for Web integration
The old web integration with more permissions is still available on an opt-in basis.
This commit is contained in:
parent
3f52d293be
commit
609397d95a
@ -475,3 +475,8 @@ export interface DepositInfo {
|
||||
denomPub: string;
|
||||
denomSig: string;
|
||||
}
|
||||
|
||||
|
||||
export interface ExtendedPermissionsResponse {
|
||||
newValue: boolean;
|
||||
}
|
@ -164,6 +164,14 @@ export interface MessageMap {
|
||||
request: {};
|
||||
response: walletTypes.WalletDiagnostics;
|
||||
};
|
||||
"set-extended-permissions": {
|
||||
request: { value: boolean };
|
||||
response: walletTypes.ExtendedPermissionsResponse;
|
||||
};
|
||||
"get-extended-permissions": {
|
||||
request: { };
|
||||
response: walletTypes.ExtendedPermissionsResponse;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -24,6 +24,7 @@ import ReactDOM from "react-dom";
|
||||
import { createPopup } from "./pages/popup";
|
||||
import { createWithdrawPage } from "./pages/withdraw";
|
||||
import { createWelcomePage } from "./pages/welcome";
|
||||
import { createPayPage } from "./pages/pay";
|
||||
|
||||
function main(): void {
|
||||
try {
|
||||
@ -43,6 +44,9 @@ function main(): void {
|
||||
case "welcome.html":
|
||||
mainElement = createWelcomePage();
|
||||
break;
|
||||
case "pay.html":
|
||||
mainElement = createPayPage();
|
||||
break;
|
||||
default:
|
||||
throw Error(`page '${page}' not implemented`);
|
||||
}
|
||||
|
@ -178,7 +178,7 @@ function TalerPayDialog({ talerPayUri }: { talerPayUri: string }): JSX.Element {
|
||||
);
|
||||
}
|
||||
|
||||
export function makePayPage(): JSX.Element {
|
||||
export function createPayPage(): JSX.Element {
|
||||
const url = new URL(document.location.href);
|
||||
const talerPayUri = url.searchParams.get("talerPayUri");
|
||||
if (!talerPayUri) {
|
||||
|
@ -34,11 +34,12 @@ import { WalletBalance, WalletBalanceEntry } from "../../types/walletTypes";
|
||||
import { abbrev, renderAmount, PageLink } from "../renderHtml";
|
||||
import * as wxApi from "../wxApi";
|
||||
|
||||
import React, { Fragment } from "react";
|
||||
import React, { Fragment, useState, useEffect } from "react";
|
||||
import { HistoryEvent } from "../../types/history";
|
||||
|
||||
import moment from "moment";
|
||||
import { Timestamp } from "../../util/time";
|
||||
import { classifyTalerUri, TalerUriType } from "../../util/taleruri";
|
||||
|
||||
// FIXME: move to newer react functions
|
||||
/* eslint-disable react/no-deprecated */
|
||||
@ -761,7 +762,113 @@ function openTab(page: string) {
|
||||
};
|
||||
}
|
||||
|
||||
function makeExtensionUrlWithParams(
|
||||
url: string,
|
||||
params?: { [name: string]: string | undefined },
|
||||
): string {
|
||||
const innerUrl = new URL(chrome.extension.getURL("/" + url));
|
||||
if (params) {
|
||||
for (const key in params) {
|
||||
const p = params[key];
|
||||
if (p) {
|
||||
innerUrl.searchParams.set(key, p);
|
||||
}
|
||||
}
|
||||
}
|
||||
return innerUrl.href;
|
||||
}
|
||||
|
||||
function actionForTalerUri(talerUri: string): string | undefined {
|
||||
const uriType = classifyTalerUri(talerUri);
|
||||
switch (uriType) {
|
||||
case TalerUriType.TalerWithdraw:
|
||||
return makeExtensionUrlWithParams("withdraw.html", {
|
||||
talerWithdrawUri: talerUri,
|
||||
});
|
||||
case TalerUriType.TalerPay:
|
||||
return makeExtensionUrlWithParams("pay.html", {
|
||||
talerPayUri: talerUri,
|
||||
});
|
||||
case TalerUriType.TalerTip:
|
||||
return makeExtensionUrlWithParams("tip.html", {
|
||||
talerTipUri: talerUri,
|
||||
});
|
||||
case TalerUriType.TalerRefund:
|
||||
return makeExtensionUrlWithParams("refund.html", {
|
||||
talerRefundUri: talerUri,
|
||||
});
|
||||
case TalerUriType.TalerNotifyReserve:
|
||||
// FIXME: implement
|
||||
break;
|
||||
default:
|
||||
console.warn(
|
||||
"Response with HTTP 402 has Taler header, but header value is not a taler:// URI.",
|
||||
);
|
||||
break;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
async function findTalerUriInActiveTab(): Promise<string | undefined> {
|
||||
return new Promise((resolve, reject) => {
|
||||
chrome.tabs.executeScript(
|
||||
{
|
||||
code: `
|
||||
(() => {
|
||||
let x = document.querySelector("a[href^='taler://'");
|
||||
return x ? x.href.toString() : null;
|
||||
})();
|
||||
`,
|
||||
allFrames: false,
|
||||
},
|
||||
(result) => {
|
||||
if (chrome.runtime.lastError) {
|
||||
console.error(chrome.runtime.lastError);
|
||||
resolve(undefined);
|
||||
return;
|
||||
}
|
||||
console.log("got result", result);
|
||||
resolve(result[0]);
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
function WalletPopup(): JSX.Element {
|
||||
const [talerActionUrl, setTalerActionUrl] = useState<string | undefined>(
|
||||
undefined,
|
||||
);
|
||||
const [dismissed, setDismissed] = useState(false);
|
||||
useEffect(() => {
|
||||
async function check(): Promise<void> {
|
||||
const talerUri = await findTalerUriInActiveTab();
|
||||
if (talerUri) {
|
||||
const actionUrl = actionForTalerUri(talerUri);
|
||||
setTalerActionUrl(actionUrl);
|
||||
}
|
||||
}
|
||||
check();
|
||||
});
|
||||
if (talerActionUrl && !dismissed) {
|
||||
return (
|
||||
<div style={{ padding: "1em" }}>
|
||||
<h1>Taler Action</h1>
|
||||
<p>This page has a Taler action. </p>
|
||||
<p>
|
||||
<button
|
||||
onClick={() => {
|
||||
window.open(talerActionUrl, "_blank");
|
||||
}}
|
||||
>
|
||||
Open
|
||||
</button>
|
||||
</p>
|
||||
<p>
|
||||
<button onClick={() => setDismissed(true)}>Dismiss</button>
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<div>
|
||||
<WalletNavBar />
|
||||
@ -777,6 +884,6 @@ function WalletPopup(): JSX.Element {
|
||||
}
|
||||
|
||||
export function createPopup(): JSX.Element {
|
||||
chrome.runtime.connect({ name: "popup" });
|
||||
//chrome.runtime.connect({ name: "popup" });
|
||||
return <WalletPopup />;
|
||||
}
|
||||
|
@ -24,8 +24,9 @@ import React, { useState, useEffect } from "react";
|
||||
import { getDiagnostics } from "../wxApi";
|
||||
import { PageLink } from "../renderHtml";
|
||||
import { WalletDiagnostics } from "../../types/walletTypes";
|
||||
import * as wxApi from "../wxApi";
|
||||
|
||||
function Diagnostics(): JSX.Element {
|
||||
function Diagnostics(): JSX.Element | null {
|
||||
const [timedOut, setTimedOut] = useState(false);
|
||||
const [diagnostics, setDiagnostics] = useState<WalletDiagnostics | undefined>(
|
||||
undefined,
|
||||
@ -55,7 +56,7 @@ function Diagnostics(): JSX.Element {
|
||||
|
||||
if (diagnostics) {
|
||||
if (diagnostics.errors.length === 0) {
|
||||
return <p>Running diagnostics ... everything looks fine.</p>;
|
||||
return null;
|
||||
} else {
|
||||
return (
|
||||
<div
|
||||
@ -96,16 +97,56 @@ function Diagnostics(): JSX.Element {
|
||||
}
|
||||
|
||||
function Welcome(): JSX.Element {
|
||||
const [extendedPermissions, setExtendedPermissions] = useState(false);
|
||||
async function handleExtendedPerm(newVal: boolean): Promise<void> {
|
||||
const res = await wxApi.setExtendedPermissions(newVal);
|
||||
setExtendedPermissions(res.newValue);
|
||||
}
|
||||
useEffect(() => {
|
||||
async function getExtendedPermValue(): Promise<void> {
|
||||
const res = await wxApi.getExtendedPermissions()
|
||||
setExtendedPermissions(res.newValue);
|
||||
}
|
||||
getExtendedPermValue();
|
||||
});
|
||||
return (
|
||||
<>
|
||||
<p>Thank you for installing the wallet.</p>
|
||||
<h2>First Steps</h2>
|
||||
<p>
|
||||
Check out <a href="https://demo.taler.net/">demo.taler.net</a> for a
|
||||
demo.
|
||||
</p>
|
||||
<h2>Troubleshooting</h2>
|
||||
<Diagnostics />
|
||||
<h2>Permissions</h2>
|
||||
<div>
|
||||
<input
|
||||
checked={extendedPermissions}
|
||||
onChange={(x) => handleExtendedPerm(x.target.checked)}
|
||||
type="checkbox"
|
||||
id="checkbox-perm"
|
||||
style={{ width: "1.5em", height: "1.5em", verticalAlign: "middle" }}
|
||||
/>
|
||||
<label
|
||||
htmlFor="checkbox-perm"
|
||||
style={{ marginLeft: "0.5em", fontWeight: "bold" }}
|
||||
>
|
||||
Automatically open wallet based on page content
|
||||
</label>
|
||||
<span
|
||||
style={{
|
||||
color: "#383838",
|
||||
fontSize: "smaller",
|
||||
display: "block",
|
||||
marginLeft: "2em",
|
||||
}}
|
||||
>
|
||||
(Enabling this option below will make using the wallet faster, but
|
||||
requires more permissions from your browser.)
|
||||
</span>
|
||||
</div>
|
||||
<h2>Next Steps</h2>
|
||||
<a href="https://demo.taler.net/" style={{ display: "block" }}>
|
||||
Try the demo »
|
||||
</a>
|
||||
<a href="https://demo.taler.net/" style={{ display: "block" }}>
|
||||
Learn how to top up your wallet balance »
|
||||
</a>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
@ -160,11 +160,18 @@ function NewExchangeSelection(props: {
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h1>Digital Cash Withdrawal</h1>
|
||||
<i18n.Translate wrap="p">
|
||||
You are about to withdraw{" "}
|
||||
<strong>{renderAmount(details.bankWithdrawDetails.amount)}</strong> from
|
||||
your bank account into your wallet.
|
||||
</i18n.Translate>
|
||||
{ selectedExchange ?
|
||||
<p>
|
||||
The exchange <strong>{selectedExchange}</strong> will be used as the Taler payment service provider.
|
||||
</p> : null
|
||||
}
|
||||
|
||||
<div>
|
||||
<button
|
||||
className="pure-button button-success"
|
||||
|
@ -109,6 +109,7 @@ export class Collapsible extends React.Component<
|
||||
return (
|
||||
<h2>
|
||||
<a className="opener opener-collapsed" href="#" onClick={doOpen}>
|
||||
{" "}
|
||||
{this.props.title}
|
||||
</a>
|
||||
</h2>
|
||||
@ -118,6 +119,7 @@ export class Collapsible extends React.Component<
|
||||
<div>
|
||||
<h2>
|
||||
<a className="opener opener-open" href="#" onClick={doClose}>
|
||||
{" "}
|
||||
{this.props.title}
|
||||
</a>
|
||||
</h2>
|
||||
@ -143,7 +145,6 @@ function WireFee(props: {
|
||||
<th>Closing Fee</th>
|
||||
</tr>
|
||||
</thead>
|
||||
,
|
||||
<tbody>
|
||||
{props.rci.wireFees.feesForType[props.s].map((f) => (
|
||||
<tr key={f.sig}>
|
||||
@ -153,7 +154,6 @@ function WireFee(props: {
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
,
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
@ -41,6 +41,7 @@ import {
|
||||
WithdrawDetails,
|
||||
PreparePayResult,
|
||||
AcceptWithdrawalResponse,
|
||||
ExtendedPermissionsResponse,
|
||||
} from "../types/walletTypes";
|
||||
|
||||
import { MessageMap, MessageType } from "./messages";
|
||||
@ -324,3 +325,17 @@ export function acceptWithdrawal(
|
||||
export function getDiagnostics(): Promise<WalletDiagnostics> {
|
||||
return callBackend("get-diagnostics", {});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get diagnostics information
|
||||
*/
|
||||
export function setExtendedPermissions(value: boolean): Promise<ExtendedPermissionsResponse> {
|
||||
return callBackend("set-extended-permissions", { value });
|
||||
}
|
||||
|
||||
/**
|
||||
* Get diagnostics information
|
||||
*/
|
||||
export function getExtendedPermissions(): Promise<ExtendedPermissionsResponse> {
|
||||
return callBackend("get-extended-permissions", {});
|
||||
}
|
||||
|
@ -63,6 +63,11 @@ let outdatedDbVersion: number | undefined;
|
||||
|
||||
const walletInit: OpenedPromise<void> = openPromise<void>();
|
||||
|
||||
const extendedPermissions = {
|
||||
permissions: ["webRequest", "webRequestBlocking", "tabs"],
|
||||
origins: ["http://*/*", "https://*/*"],
|
||||
};
|
||||
|
||||
async function handleMessage(
|
||||
sender: MessageSender,
|
||||
type: MessageType,
|
||||
@ -282,6 +287,43 @@ async function handleMessage(
|
||||
}
|
||||
case "prepare-pay":
|
||||
return needsWallet().preparePayForUri(detail.talerPayUri);
|
||||
case "set-extended-permissions": {
|
||||
const newVal = detail.value;
|
||||
if (newVal) {
|
||||
const res = await new Promise((resolve, reject) => {
|
||||
chrome.permissions.request(
|
||||
extendedPermissions,
|
||||
(granted: boolean) => {
|
||||
console.log("permissions granted:", granted);
|
||||
if (chrome.runtime.lastError) {
|
||||
console.error(chrome.runtime.lastError);
|
||||
}
|
||||
resolve(granted);
|
||||
},
|
||||
);
|
||||
});
|
||||
if (res) {
|
||||
setupHeaderListener();
|
||||
}
|
||||
return { newValue: res };
|
||||
} else {
|
||||
await new Promise((resolve, reject) => {
|
||||
chrome.permissions.remove(extendedPermissions, (rem) => {
|
||||
console.log("permissions removed:", rem);
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
return { newVal: false };
|
||||
}
|
||||
}
|
||||
case "get-extended-permissions": {
|
||||
const res = await new Promise((resolve, reject) => {
|
||||
chrome.permissions.contains(extendedPermissions, (result: boolean) => {
|
||||
resolve(result);
|
||||
});
|
||||
});
|
||||
return { newValue: res };
|
||||
}
|
||||
default:
|
||||
// Exhaustiveness check.
|
||||
// See https://www.typescriptlang.org/docs/handbook/advanced-types.html
|
||||
@ -453,6 +495,91 @@ try {
|
||||
console.error(e);
|
||||
}
|
||||
|
||||
function headerListener(
|
||||
details: chrome.webRequest.WebResponseHeadersDetails,
|
||||
): chrome.webRequest.BlockingResponse | undefined {
|
||||
if (chrome.runtime.lastError) {
|
||||
console.error(chrome.runtime.lastError);
|
||||
return;
|
||||
}
|
||||
const wallet = currentWallet;
|
||||
if (!wallet) {
|
||||
console.warn("wallet not available while handling header");
|
||||
return;
|
||||
}
|
||||
if (details.statusCode === 402 || details.statusCode === 202) {
|
||||
console.log(`got 402/202 from ${details.url}`);
|
||||
for (const header of details.responseHeaders || []) {
|
||||
if (header.name.toLowerCase() === "taler") {
|
||||
const talerUri = header.value || "";
|
||||
const uriType = classifyTalerUri(talerUri);
|
||||
switch (uriType) {
|
||||
case TalerUriType.TalerWithdraw:
|
||||
return makeSyncWalletRedirect(
|
||||
"withdraw.html",
|
||||
details.tabId,
|
||||
details.url,
|
||||
{
|
||||
talerWithdrawUri: talerUri,
|
||||
},
|
||||
);
|
||||
case TalerUriType.TalerPay:
|
||||
return makeSyncWalletRedirect(
|
||||
"pay.html",
|
||||
details.tabId,
|
||||
details.url,
|
||||
{
|
||||
talerPayUri: talerUri,
|
||||
},
|
||||
);
|
||||
case TalerUriType.TalerTip:
|
||||
return makeSyncWalletRedirect(
|
||||
"tip.html",
|
||||
details.tabId,
|
||||
details.url,
|
||||
{
|
||||
talerTipUri: talerUri,
|
||||
},
|
||||
);
|
||||
case TalerUriType.TalerRefund:
|
||||
return makeSyncWalletRedirect(
|
||||
"refund.html",
|
||||
details.tabId,
|
||||
details.url,
|
||||
{
|
||||
talerRefundUri: talerUri,
|
||||
},
|
||||
);
|
||||
case TalerUriType.TalerNotifyReserve:
|
||||
Promise.resolve().then(() => {
|
||||
const w = currentWallet;
|
||||
if (!w) {
|
||||
return;
|
||||
}
|
||||
w.handleNotifyReserve();
|
||||
});
|
||||
break;
|
||||
default:
|
||||
console.warn(
|
||||
"Response with HTTP 402 has Taler header, but header value is not a taler:// URI.",
|
||||
);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
function setupHeaderListener(): void {
|
||||
// Handlers for catching HTTP requests
|
||||
chrome.webRequest.onHeadersReceived.addListener(
|
||||
headerListener,
|
||||
{ urls: ["https://*/*", "http://*/*"] },
|
||||
["responseHeaders", "blocking"],
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Main function to run for the WebExtension backend.
|
||||
*
|
||||
@ -474,79 +601,5 @@ export async function wxMain(): Promise<void> {
|
||||
return true;
|
||||
});
|
||||
|
||||
// Handlers for catching HTTP requests
|
||||
chrome.webRequest.onHeadersReceived.addListener(
|
||||
(details) => {
|
||||
const wallet = currentWallet;
|
||||
if (!wallet) {
|
||||
console.warn("wallet not available while handling header");
|
||||
return;
|
||||
}
|
||||
if (details.statusCode === 402 || details.statusCode === 202) {
|
||||
console.log(`got 402/202 from ${details.url}`);
|
||||
for (const header of details.responseHeaders || []) {
|
||||
if (header.name.toLowerCase() === "taler") {
|
||||
const talerUri = header.value || "";
|
||||
const uriType = classifyTalerUri(talerUri);
|
||||
switch (uriType) {
|
||||
case TalerUriType.TalerWithdraw:
|
||||
return makeSyncWalletRedirect(
|
||||
"withdraw.html",
|
||||
details.tabId,
|
||||
details.url,
|
||||
{
|
||||
talerWithdrawUri: talerUri,
|
||||
},
|
||||
);
|
||||
case TalerUriType.TalerPay:
|
||||
return makeSyncWalletRedirect(
|
||||
"pay.html",
|
||||
details.tabId,
|
||||
details.url,
|
||||
{
|
||||
talerPayUri: talerUri,
|
||||
},
|
||||
);
|
||||
case TalerUriType.TalerTip:
|
||||
return makeSyncWalletRedirect(
|
||||
"tip.html",
|
||||
details.tabId,
|
||||
details.url,
|
||||
{
|
||||
talerTipUri: talerUri,
|
||||
},
|
||||
);
|
||||
case TalerUriType.TalerRefund:
|
||||
return makeSyncWalletRedirect(
|
||||
"refund.html",
|
||||
details.tabId,
|
||||
details.url,
|
||||
{
|
||||
talerRefundUri: talerUri,
|
||||
},
|
||||
);
|
||||
case TalerUriType.TalerNotifyReserve:
|
||||
Promise.resolve().then(() => {
|
||||
const w = currentWallet;
|
||||
if (!w) {
|
||||
return;
|
||||
}
|
||||
w.handleNotifyReserve();
|
||||
});
|
||||
break;
|
||||
|
||||
default:
|
||||
console.warn(
|
||||
"Response with HTTP 402 has Taler header, but header value is not a taler:// URI.",
|
||||
);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return;
|
||||
},
|
||||
{ urls: ["https://*/*", "http://*/*"] },
|
||||
["responseHeaders", "blocking"],
|
||||
);
|
||||
setupHeaderListener();
|
||||
}
|
||||
|
@ -24,6 +24,10 @@
|
||||
|
||||
"permissions": [
|
||||
"storage",
|
||||
"activeTab"
|
||||
],
|
||||
|
||||
"optional_permissions": [
|
||||
"tabs",
|
||||
"webRequest",
|
||||
"webRequestBlocking",
|
||||
@ -39,16 +43,6 @@
|
||||
"default_popup": "popup.html"
|
||||
},
|
||||
|
||||
"content_scripts": [
|
||||
{
|
||||
"matches": ["*://*/*"],
|
||||
"js": [
|
||||
"contentScript.js"
|
||||
],
|
||||
"run_at": "document_start"
|
||||
}
|
||||
],
|
||||
|
||||
"background": {
|
||||
"page": "background.html",
|
||||
"persistent": true
|
||||
|
@ -1,14 +1,15 @@
|
||||
body {
|
||||
font-size: 100%;
|
||||
overflow-y: scroll;
|
||||
margin-top: 2em;
|
||||
}
|
||||
|
||||
#main {
|
||||
border: solid 1px black;
|
||||
border: solid 5px black;
|
||||
border-radius: 10px;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
margin-top: 2em;
|
||||
padding-top: 2em;
|
||||
max-width: 50%;
|
||||
padding: 2em;
|
||||
}
|
||||
@ -18,16 +19,6 @@ header {
|
||||
height: 100px;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
border-bottom: 1px solid black;
|
||||
}
|
||||
|
||||
header h1 {
|
||||
font-size: 200%;
|
||||
margin: 0;
|
||||
padding: 0 0 0 120px;
|
||||
position: relative;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
}
|
||||
|
||||
header #logo {
|
||||
@ -37,7 +28,6 @@ header #logo {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
text-align: center;
|
||||
border-right: 1px solid black;
|
||||
background-image: url(/img/logo.png);
|
||||
background-size: 100px;
|
||||
}
|
||||
@ -50,7 +40,6 @@ aside {
|
||||
section#main {
|
||||
margin: auto;
|
||||
padding: 20px;
|
||||
border-left: 1px solid black;
|
||||
height: 100%;
|
||||
max-width: 50%;
|
||||
}
|
||||
@ -61,19 +50,23 @@ section#main h1:first-child {
|
||||
|
||||
h1 {
|
||||
font-size: 160%;
|
||||
font-family: "monospace";
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: 140%;
|
||||
font-family: "monospace";
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-size: 120%;
|
||||
font-family: "monospace";
|
||||
}
|
||||
|
||||
h4,
|
||||
h5,
|
||||
h6 {
|
||||
font-family: "monospace";
|
||||
font-size: 100%;
|
||||
}
|
||||
|
||||
@ -281,3 +274,17 @@ a.opener {
|
||||
object.svg-icon.svg-baseline {
|
||||
transform: translate(0, 0.125em);
|
||||
}
|
||||
|
||||
.switch {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
width: 60px;
|
||||
height: 34px;
|
||||
}
|
||||
|
||||
/* Hide default HTML checkbox */
|
||||
.switch input {
|
||||
opacity: 0;
|
||||
width: 0;
|
||||
height: 0;
|
||||
}
|
@ -2,7 +2,7 @@
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>Taler Wallet: Withdraw</title>
|
||||
<title>Taler Wallet Installed</title>
|
||||
|
||||
<link rel="icon" href="/img/icon.png" />
|
||||
<link rel="stylesheet" type="text/css" href="/style/pure.css" />
|
||||
@ -12,7 +12,12 @@
|
||||
|
||||
<body>
|
||||
<section id="main">
|
||||
<h1>GNU Taler Wallet Installed!</h1>
|
||||
<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>
|
||||
|
@ -11,7 +11,11 @@
|
||||
|
||||
<body>
|
||||
<section id="main">
|
||||
<h1>GNU Taler Wallet</h1>
|
||||
<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>
|
||||
|
Loading…
Reference in New Issue
Block a user