Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,6 @@
"source.organizeImports": "explicit"
},
"typescript.preferences.importModuleSpecifier": "non-relative",
"editor.defaultFormatter": "esbenp.prettier-vscode"
"editor.defaultFormatter": "esbenp.prettier-vscode",
"js/ts.tsdk.path": "node_modules/typescript/lib"
}
11 changes: 6 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,8 @@
"@lightninglabs/lnc-web": "^0.3.1-alpha",
"@noble/ciphers": "^2.2.0",
"@noble/curves": "^2.2.0",
"@noble/secp256k1": "^3.1.0",
"@noble/hashes": "^2.2.0",
"@noble/secp256k1": "^3.1.0",
"@popicons/react": "^0.0.27",
"@scure/base": "^2.2.0",
"@scure/bip32": "^2.2.0",
Expand All @@ -53,13 +53,13 @@
"bitcoinjs-lib": "^7.0.1",
"bolt11-signet": "1.4.1",
"crypto-js": "^4.2.0",
"dexie": "^4.4.3",
"dayjs": "^1.11.21",
"dexie": "^4.4.3",
"elliptic": "^6.6.1",
"events": "^3.3.0",
"html5-qrcode": "^2.3.8",
"i18next-browser-languagedetector": "^8.2.1",
"i18next": "^25.10.10",
"i18next-browser-languagedetector": "^8.2.1",
"liquidjs-lib": "^6.0.2-liquid.29",
"lodash.merge": "^4.6.2",
"lodash.pick": "^4.4.0",
Expand Down Expand Up @@ -96,6 +96,7 @@
"@types/chrome": "^0.0.248",
"@types/crypto-js": "^4.1.3",
"@types/elliptic": "^6.4.16",
"@types/jest": "^29.5.14",
"@types/lodash.merge": "^4.6.8",
"@types/lodash.pick": "^4.4.8",
"@types/lodash.snakecase": "^4.1.8",
Expand All @@ -104,7 +105,6 @@
"@types/react-modal": "^3.16.2",
"@types/uuid": "^9.0.6",
"@types/webextension-polyfill": "^0.10.5",
"typescript-eslint": "^8.34.0",
"@webbtc/webln-types": "^3.0.0",
"autoprefixer": "^10.4.16",
"buffer": "^6.0.3",
Expand All @@ -118,9 +118,9 @@
"eslint-config-prettier": "^9.0.0",
"eslint-plugin-react": "^7.37.0",
"eslint-plugin-react-hooks": "^5.2.0",
"globals": "^16.2.0",
"fake-indexeddb": "^4.0.1",
"filemanager-webpack-plugin": "^8.0.0",
"globals": "^16.2.0",
"html-webpack-plugin": "^5.5.3",
"husky": "^8.0.3",
"jest": "^29.7.0",
Expand All @@ -141,6 +141,7 @@
"terser-webpack-plugin": "^5.3.9",
"tsconfig-paths-webpack-plugin": "^4.1.0",
"typescript": "^5.2.2",
"typescript-eslint": "^8.34.0",
"webpack": "^5.105.0",
"webpack-bundle-analyzer": "^4.9.1",
"webpack-cli": "^5.1.4",
Expand Down
4 changes: 2 additions & 2 deletions src/app/components/PaymentSummary/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,13 +35,13 @@ const PaymentSummary: FC<Props> = ({ amount, description, fiatAmount }) => {
);
};

const Dt = ({ children }: { children: React.ReactNode }) => (
export const Dt = ({ children }: { children: React.ReactNode }) => (
<dt className="text-sm font-medium text-gray-800 dark:text-neutral-200">
{children}
</dt>
);

const Dd = ({ children }: { children: React.ReactNode }) => (
export const Dd = ({ children }: { children: React.ReactNode }) => (
<dd className="text-lg text-gray-600 dark:text-neutral-400 break-words">
{children}
</dd>
Expand Down
133 changes: 110 additions & 23 deletions src/app/screens/ConfirmRequestPermission/index.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
import Alert from "@components/Alert";
import BudgetControl from "@components/BudgetControl";
import ConfirmOrCancel from "@components/ConfirmOrCancel";
import Container from "@components/Container";
import PaymentSummary, { Dd, Dt } from "@components/PaymentSummary";
import PublisherCard from "@components/PublisherCard";
import Checkbox from "@components/form/Checkbox";
import { FC, useState } from "react";
import { FC, useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import ScreenHeader from "~/app/components/ScreenHeader";
import { useSettings } from "~/app/context/SettingsContext";
import { useNavigationState } from "~/app/hooks/useNavigationState";
import { USER_REJECTED_ERROR } from "~/common/constants";
import msg from "~/common/lib/msg";
Expand All @@ -17,16 +21,60 @@ const ConfirmRequestPermission: FC = () => {
keyPrefix: "confirm_request_permission",
});
const { t: tCommon } = useTranslation("common");
const { t: tPayment } = useTranslation("translation", {
keyPrefix: "confirm_payment",
});
const { t: tPermissions } = useTranslation("permissions");
const {
isLoading: isLoadingSettings,
settings,
getFormattedFiat,
} = useSettings();
const showFiat = !isLoadingSettings && settings.showFiat;

const navState = useNavigationState();
const origin = navState.origin as OriginData;
const requestMethod = navState.args?.requestPermission?.method;
const description = navState.args?.requestPermission?.description;

const enable = () => {
const { method, description, params, showBudgetControl, alwaysConfirm } =
navState.args?.requestPermission ?? {};

const { amount: rawAmount, ...rest } = params ?? {};
const amount = rawAmount != null ? Number(rawAmount) : undefined;
const otherParams = params ? Object.entries(rest) : [];

const [rememberMe, setRememberMe] = useState(false);
const [budget, setBudget] = useState(((amount || 0) * 10).toString());
const [fiatAmount, setFiatAmount] = useState("");
const [fiatBudgetAmount, setFiatBudgetAmount] = useState("");

useEffect(() => {
(async () => {
if (showFiat && amount) {
setFiatAmount(await getFormattedFiat(amount));
}
})();
}, [amount, showFiat, getFormattedFiat]);

useEffect(() => {
(async () => {
if (showFiat && budget) {
setFiatBudgetAmount(await getFormattedFiat(budget));
}
})();
}, [budget, showFiat, getFormattedFiat]);

const enable = async () => {
if (showBudgetControl && rememberMe && budget) {
await msg.request("addAllowance", {
totalBudget: parseInt(budget),
host: origin.host,
name: origin.name,
imageURL: origin.icon,
});
}

msg.reply({
enabled: alwaysAllow,
enabled: showBudgetControl ? rememberMe : alwaysAllow,
blocked: false,
});
};
Expand All @@ -53,10 +101,10 @@ const ConfirmRequestPermission: FC = () => {
url={origin.host}
isSmall={false}
/>
<div className="dark:text-white pt-4">
<p className="mb-4">{t("allow")}</p>
<div className="mb-6 center dark:text-white">
<p className="font-semibold">{requestMethod}</p>
<div className="flex flex-col gap-4 dark:text-white py-4">
<p>{t("allow")}</p>
<div className="center dark:text-white">
<p className="font-semibold">{method}</p>
{description && (
<p className="text-sm text-gray-700 dark:text-neutral-500">
{tPermissions(
Expand All @@ -65,25 +113,64 @@ const ConfirmRequestPermission: FC = () => {
</p>
)}
</div>
{amount !== undefined && (
<div className="p-4 shadow bg-white dark:bg-surface-02dp rounded-lg">
<PaymentSummary amount={amount} fiatAmount={fiatAmount} />
</div>
)}
{otherParams.length > 0 && (
<div className="p-4 shadow bg-white dark:bg-surface-02dp rounded-lg">
<dl className="space-y-4">
{otherParams.map(([key, value]) => (
<div key={key}>
<Dt>{t(`params.${key}`, { defaultValue: key })}</Dt>
<Dd>{value}</Dd>
</div>
))}
</dl>
</div>
)}
{alwaysConfirm && (
<Alert type="warn">
<p className="text-sm">{t("always_confirm_warning")}</p>
</Alert>
)}
</div>
</div>
<div className="text-center flex flex-col">
<div className="flex items-center mb-4">
<Checkbox
id="always_allow"
name="always_allow"
checked={alwaysAllow}
onChange={() => setAlwaysAllow((prev) => !prev)}
{showBudgetControl && (
<BudgetControl
fiatAmount={fiatBudgetAmount}
remember={rememberMe}
onRememberChange={(event) => {
setRememberMe(event.target.checked);
}}
budget={budget}
onBudgetChange={(event) => setBudget(event.target.value)}
/>
<label
htmlFor="always_allow"
className="cursor-pointer pl-2 block text-sm text-gray-900 font-medium dark:text-white"
>
{t("always_allow")}
</label>
</div>
)}
{!alwaysConfirm && !showBudgetControl && (
<div className="flex items-center mb-4">
<Checkbox
id="always_allow"
name="always_allow"
checked={alwaysAllow}
onChange={() => setAlwaysAllow((prev) => !prev)}
/>
<label
htmlFor="always_allow"
className="cursor-pointer pl-2 block text-sm text-gray-900 font-medium dark:text-white"
>
{t("always_allow")}
</label>
</div>
)}
<ConfirmOrCancel
label={tCommon("actions.confirm")}
label={
amount !== undefined
? tPayment("actions.pay_now")
: tCommon("actions.confirm")
}
onCancel={reject}
/>
</div>
Expand Down
24 changes: 24 additions & 0 deletions src/common/utils/helpers.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import * as secp256k1 from "@noble/secp256k1";
import { bech32 } from "bech32";
import snakeCase from "lodash.snakecase";
import { ConnectorTransaction } from "~/extension/background-script/connectors/connector.interface";
import { Sender } from "~/types";

Expand Down Expand Up @@ -73,3 +74,26 @@ export function mergeTransactions(

return mergedTransactions;
}

// recursively converts every object key to snake_case, leaving already
// snake_case keys untouched. Used to read RPC params/results regardless of
// which casing convention the caller (or connector) used.
export function snakeCaseObjectDeep(value: FixMe): FixMe {
if (Array.isArray(value)) {
return value.map(snakeCaseObjectDeep);
}

if (value && typeof value === "object" && value.constructor === Object) {
const obj = {} as FixMe;
const keys = Object.keys(value);
const len = keys.length;

for (let i = 0; i < len; i += 1) {
obj[snakeCase(keys[i])] = snakeCaseObjectDeep(value[keys[i]]);
}

return obj;
}

return value;
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,14 @@ const ConnectorClass = jest.fn().mockImplementation(() => {
jest.mock("~/extension/background-script/state", () => ({
getState: () => ({
getConnector: jest.fn(() => Promise.resolve(new ConnectorClass())),
getAccount: () => ({ connector: "lnd" }),
currentAccountId: "8b7f1dc6-ab87-4c6c-bca5-19fa8632731e",
}),
}));

const allowanceInDB = {
enabled: true,
enabledFor: ["webln"],
host: "getalby.com",
id: 1,
imageURL: "https://getalby.com/favicon.ico",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { RequestMethodHandler, RequestParams } from "./types";

const addholdinvoice: RequestMethodHandler = {
alwaysConfirm: true,
getParams(rawParams): RequestParams {
const hash = rawParams.hash;

if (typeof hash === "string" && hash) {
return { hash };
}
return {};
},
};

export default addholdinvoice;
19 changes: 19 additions & 0 deletions src/extension/background-script/actions/ln/request/connectpeer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { RequestMethodHandler, RequestParams } from "./types";

const connectpeer: RequestMethodHandler = {
alwaysConfirm: true,
getParams(rawParams): RequestParams {
const addr = (rawParams.addr ?? {}) as {
pubkey?: unknown;
pub_key?: unknown;
};
const pubkey = addr.pubkey ?? addr.pub_key;

if (typeof pubkey === "string" && pubkey) {
return { pubkey };
}
return {};
},
};

export default connectpeer;
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { RequestMethodHandler, RequestParams } from "./types";

const disconnectpeer: RequestMethodHandler = {
alwaysConfirm: true,
getParams(rawParams): RequestParams {
// LND REST is snake_case; LNC gRPC-web is camelCase
const pubkey = rawParams.pub_key ?? rawParams.pubKey;

if (typeof pubkey === "string" && pubkey) {
return { pubkey };
}
return {};
},
};

export default disconnectpeer;
Loading
Loading