Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: lnurl-auth onboarding #2754

Merged
merged 16 commits into from
Oct 5, 2023
Merged
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
140 changes: 140 additions & 0 deletions src/app/components/LNURLAuth/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
import Button from "@components/Button";
import ConfirmOrCancel from "@components/ConfirmOrCancel";
import Container from "@components/Container";
import ContentMessage from "@components/ContentMessage";
import PublisherCard from "@components/PublisherCard";
import ResultCard from "@components/ResultCard";
import React, { useState } from "react";
import { useTranslation } from "react-i18next";
import { useNavigate } from "react-router-dom";
import ScreenHeader from "~/app/components/ScreenHeader";
import { useNavigationState } from "~/app/hooks/useNavigationState";
import { USER_REJECTED_ERROR } from "~/common/constants";
import api from "~/common/lib/api";
import msg from "~/common/lib/msg";
import type { LNURLAuthServiceResponse } from "~/types";

function LNURLAuthComponent() {
const { t } = useTranslation("translation", { keyPrefix: "lnurlauth" });
const { t: tCommon } = useTranslation("common");

const navigate = useNavigate();
const navState = useNavigationState();

const details = navState.args?.lnurlDetails as LNURLAuthServiceResponse;
const origin = navState.origin;

const [successMessage, setSuccessMessage] = useState("");
const [errorMessage, setErrorMessage] = useState("");
const [loading, setLoading] = useState(false);

async function confirm() {
try {
setLoading(true);
const response = await api.lnurlAuth({
origin,
lnurlDetails: details,
});

if (navState.isPrompt && origin?.host) {
const allowance = await api.getAllowance(origin.host);

if (allowance.lnurlAuth === false) {
await msg.request("updateAllowance", {
id: allowance.id,
lnurlAuth: true,
});
}
}

if (response.success) {
setSuccessMessage(
t("success", { name: origin ? origin.name : details.domain })
);
// ATTENTION: if this LNURL is called through `webln.lnurl` then we immediately return and return the response. This closes the window which means the user will NOT see the above successAction.
// We assume this is OK when it is called through webln.
if (navState.isPrompt) {
msg.reply(response);
}
} else {
setErrorMessage(t("errors.status"));
}
} catch (e) {
console.error(e);
if (e instanceof Error) {
setErrorMessage(`Error: ${e.message}`);
}
} finally {
setLoading(false);
}
}

function reject(e: React.MouseEvent<HTMLButtonElement>) {
e.preventDefault();
if (navState.isPrompt) {
msg.error(USER_REJECTED_ERROR);
} else {
navigate(-1);
}
}

function close(e: React.MouseEvent<HTMLButtonElement>) {
// will never be reached via prompt
e.preventDefault();
navigate(-1);
}

return (
<div className="h-full flex flex-col overflow-y-auto no-scrollbar">
<ScreenHeader title={t("title")} />
{!successMessage ? (
<>
<Container justifyBetween maxWidth="sm">
<div>
{origin ? (
<PublisherCard
title={origin.name}
image={origin.icon}
url={details.domain}
/>
) : (
<PublisherCard title={details.domain} />
)}

<ContentMessage
heading={`${t("content_message.heading")} ${
origin ? origin.name : details.domain
}?`}
content={details.domain}
/>

{errorMessage && (
<p className="my-2 mx-5 text-red-500">{errorMessage}</p>
)}
</div>
<ConfirmOrCancel
label={t("submit")}
onConfirm={confirm}
onCancel={reject}
disabled={loading}
loading={loading}
/>
</Container>
</>
) : (
<Container justifyBetween maxWidth="sm">
<ResultCard isSuccess message={successMessage} />
<div className="mt-4">
<Button
onClick={close}
label={tCommon("actions.close")}
fullWidth
/>
</div>
</Container>
)}
</div>
);
}

export default LNURLAuthComponent;
158 changes: 28 additions & 130 deletions src/app/screens/LNURLAuth/index.tsx
Original file line number Diff line number Diff line change
@@ -1,140 +1,38 @@
import Button from "@components/Button";
import ConfirmOrCancel from "@components/ConfirmOrCancel";
import Container from "@components/Container";
import ContentMessage from "@components/ContentMessage";
import PublisherCard from "@components/PublisherCard";
import ResultCard from "@components/ResultCard";
import React, { useState } from "react";
import { useTranslation } from "react-i18next";
import { useNavigate } from "react-router-dom";
import ScreenHeader from "~/app/components/ScreenHeader";
import { useNavigationState } from "~/app/hooks/useNavigationState";
import { USER_REJECTED_ERROR } from "~/common/constants";
import { useEffect, useState } from "react";
import LNURLAuthComponent from "~/app/components/LNURLAuth";
import Onboard from "~/app/components/onboard";
import { useAccount } from "~/app/context/AccountContext";
import { isAlbyOAuthAccount } from "~/app/utils";
import api from "~/common/lib/api";
import msg from "~/common/lib/msg";
import type { LNURLAuthServiceResponse } from "~/types";

function LNURLAuth() {
const { t } = useTranslation("translation", { keyPrefix: "lnurlauth" });
const { t: tCommon } = useTranslation("common");

const navigate = useNavigate();
const navState = useNavigationState();

const details = navState.args?.lnurlDetails as LNURLAuthServiceResponse;
const origin = navState.origin;

const [successMessage, setSuccessMessage] = useState("");
const [errorMessage, setErrorMessage] = useState("");
const [loading, setLoading] = useState(false);

async function confirm() {
try {
setLoading(true);
const response = await api.lnurlAuth({
origin,
lnurlDetails: details,
});

if (navState.isPrompt && origin?.host) {
const allowance = await api.getAllowance(origin.host);

if (allowance.lnurlAuth === false) {
await msg.request("updateAllowance", {
id: allowance.id,
lnurlAuth: true,
});
export default function LNURLAuth() {
const { account } = useAccount();
const [hasMnemonic, setHasMnemonic] = useState(false);
const [albyOAuthAccount, setAlbyOAuthAccount] = useState(false);

useEffect(() => {
async function fetchAccountInfo() {
try {
const fetchedAccount = await api.getAccount();
const isOAuthAccount = isAlbyOAuthAccount(fetchedAccount.connectorType);
setAlbyOAuthAccount(isOAuthAccount);

if (fetchedAccount.hasMnemonic) {
setHasMnemonic(true);
} else {
setHasMnemonic(false);
}
} catch (e) {
console.error(e);
}

if (response.success) {
setSuccessMessage(
t("success", { name: origin ? origin.name : details.domain })
);
// ATTENTION: if this LNURL is called through `webln.lnurl` then we immediately return and return the response. This closes the window which means the user will NOT see the above successAction.
// We assume this is OK when it is called through webln.
if (navState.isPrompt) {
msg.reply(response);
}
} else {
setErrorMessage(t("errors.status"));
}
} catch (e) {
console.error(e);
if (e instanceof Error) {
setErrorMessage(`Error: ${e.message}`);
}
} finally {
setLoading(false);
}
}

function reject(e: React.MouseEvent<HTMLButtonElement>) {
e.preventDefault();
if (navState.isPrompt) {
msg.error(USER_REJECTED_ERROR);
} else {
navigate(-1);
}
}

function close(e: React.MouseEvent<HTMLButtonElement>) {
// will never be reached via prompt
e.preventDefault();
navigate(-1);
}
fetchAccountInfo();
}, [account]);

return (
<div className="h-full flex flex-col overflow-y-auto no-scrollbar">
<ScreenHeader title={t("title")} />
{!successMessage ? (
<>
<Container justifyBetween maxWidth="sm">
<div>
{origin ? (
<PublisherCard
title={origin.name}
image={origin.icon}
url={details.domain}
/>
) : (
<PublisherCard title={details.domain} />
)}

<ContentMessage
heading={`${t("content_message.heading")} ${
origin ? origin.name : details.domain
}?`}
content={details.domain}
/>

{errorMessage && (
<p className="my-2 mx-5 text-red-500">{errorMessage}</p>
)}
</div>
<ConfirmOrCancel
label={t("submit")}
onConfirm={confirm}
onCancel={reject}
disabled={loading}
loading={loading}
/>
</Container>
</>
) : (
<Container justifyBetween maxWidth="sm">
<ResultCard isSuccess message={successMessage} />
<div className="mt-4">
<Button
onClick={close}
label={tCommon("actions.close")}
fullWidth
/>
</div>
</Container>
)}
</div>
<>
{albyOAuthAccount && !hasMnemonic ? <Onboard /> : <LNURLAuthComponent />}
</>
);
}

export default LNURLAuth;
25 changes: 19 additions & 6 deletions src/extension/background-script/actions/lnurl/authOrPrompt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,12 +33,9 @@ async function authOrPrompt(
// we have the check the unlock status manually. The account can still be locked
// If it is locked we must show a prompt to unlock
const isUnlocked = await state.getState().isUnlocked();
const account = await state.getState().getAccount();

// check if there is a publisher and lnurlAuth is enabled,
// otherwise we we prompt the user
if (isUnlocked && allowance && allowance.enabled && allowance.lnurlAuth) {
return await authFunction({ lnurlDetails, origin: message.origin });
} else {
async function authPrompt() {
rolznz marked this conversation as resolved.
Show resolved Hide resolved
try {
const promptMessage = {
...message,
Expand All @@ -49,12 +46,28 @@ async function authOrPrompt(
},
};

return await utils.openPrompt<LnurlAuthResponse>(promptMessage);
const response = await utils.openPrompt<LnurlAuthResponse>(promptMessage);
return response;
} catch (e) {
// user rejected
return { error: e instanceof Error ? e.message : e };
}
}

// check if there is a publisher and lnurlAuth is enabled,
// otherwise we we prompt the user

if (
isUnlocked &&
allowance &&
allowance.enabled &&
allowance.lnurlAuth &&
(!account?.useMnemonicForLnurlAuth || account?.mnemonic)
) {
return await authFunction({ lnurlDetails, origin: message.origin });
}

return await authPrompt();
}

export default authOrPrompt;
2 changes: 1 addition & 1 deletion src/extension/background-script/actions/lnurl/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,4 +39,4 @@ async function lnurl(message: MessageWebLnLnurl, sender: Sender) {
}

export default lnurl;
export { authOrPrompt, payWithPrompt, withdrawWithPrompt, auth };
export { auth, authOrPrompt, payWithPrompt, withdrawWithPrompt };
3 changes: 0 additions & 3 deletions src/extension/background-script/actions/onboard/index.ts

This file was deleted.

19 changes: 0 additions & 19 deletions src/extension/background-script/actions/onboard/prompt.ts

This file was deleted.

Loading
Loading