Skip to content

Commit

Permalink
main: make error messages more accurate
Browse files Browse the repository at this point in the history
  • Loading branch information
nonfungible-human committed Nov 28, 2023
1 parent 30c2d97 commit 696df24
Show file tree
Hide file tree
Showing 5 changed files with 148 additions and 11 deletions.
2 changes: 1 addition & 1 deletion app/src/__tests__/components/auth/AuthPage.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,6 @@ describe('AuthPage ', () => {
const input = getByLabelText('Enter your password in the field above');
fireEvent.change(input, { target: { value: 'test-pw' } });
fireEvent.click(getByText('Submit'));
expect(await findByText('oops, that password is incorrect')).toBeInTheDocument();
expect(await findByText('failed to connect')).toBeInTheDocument();
});
});
4 changes: 1 addition & 3 deletions app/src/__tests__/store/authStore.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,7 @@ describe('AuthStore', () => {
if (desc.methodName === 'GetInfo') throw new Error('test-err');
return undefined as any;
});
await expect(store.login('test-pw')).rejects.toThrow(
'oops, that password is incorrect',
);
await expect(store.login('test-pw')).rejects.toThrow('failed to connect');
expect(store.credentials).toBe('');
});

Expand Down
66 changes: 62 additions & 4 deletions app/src/components/auth/AuthPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,14 @@ import styled from '@emotion/styled';
import { ReactComponent as LogoImage } from 'assets/images/logo.svg';
import { usePrefixedTranslation } from 'hooks';
import { useStore } from 'store';
import { Background, Button, HeaderOne, Input } from 'components/base';
import {
Background,
Button,
ChevronDown,
ChevronUp,
HeaderOne,
Input,
} from 'components/base';

const Styled = {
Wrapper: styled.div`
Expand Down Expand Up @@ -42,12 +49,23 @@ const Styled = {
`,
ErrMessage: styled.div`
width: 100%;
margin: 0 0 80px;
padding: 5px 0;
background-color: ${props => props.theme.colors.pink};
color: ${props => props.theme.colors.offWhite};
text-align: center;
`,
ErrDetail: styled.div`
width: 100%;
padding: 5px 0;
color: ${props => props.theme.colors.offWhite};
text-align: center;
`,
ErrDetailToggle: styled(Button)`
width: 100%;
margin: 0 0 80px;
padding: 5px 0;
background-color: transparent;
`,
Submit: styled(Button)`
background-color: transparent;
`,
Expand All @@ -58,6 +76,9 @@ const AuthPage: React.FC = () => {
const store = useStore();
const [pass, setPass] = useState('');
const [error, setError] = useState('');
const [errorDetailLit, setErrorDetailLit] = useState('');
const [errorDetailLnd, setErrorDetailLnd] = useState('');
const [errorDetailVisible, setErrorDetailVisible] = useState(false);

const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setPass(e.target.value);
Expand All @@ -70,14 +91,28 @@ const AuthPage: React.FC = () => {
await store.authStore.login(pass);
} catch (err) {
setError(err.message);
const errors = store.authStore.errors;
setErrorDetailLit(errors.litDetail);
setErrorDetailLnd(errors.lndDetail);
}
};

// don't display the login UI until the app is fully initialized this prevents
// a UI flicker while validating credentials stored in session storage
if (!store.initialized) return null;

const { Wrapper, Logo, Title, Subtitle, Form, Label, ErrMessage, Submit } = Styled;
const {
Wrapper,
Logo,
Title,
Subtitle,
Form,
Label,
ErrMessage,
ErrDetail,
ErrDetailToggle,
Submit,
} = Styled;
return (
<Background gradient>
<Wrapper>
Expand All @@ -94,7 +129,30 @@ const AuthPage: React.FC = () => {
onChange={handleChange}
/>
{error ? (
<ErrMessage>{error}</ErrMessage>
<>
<ErrMessage>{error}</ErrMessage>
{errorDetailVisible && errorDetailLit.length > 0 ? (
<ErrDetail>LiT: {errorDetailLit}</ErrDetail>
) : (
''
)}
{errorDetailVisible && errorDetailLnd.length > 0 ? (
<ErrDetail>LND: {errorDetailLnd}</ErrDetail>
) : (
''
)}
<ErrDetailToggle
ghost
borderless
compact
onClick={() => {
setErrorDetailVisible(!errorDetailVisible);
}}
>
{!errorDetailVisible ? <ChevronDown /> : <ChevronUp />}
{!errorDetailVisible ? l('showDetail') : l('hideDetail')}
</ErrDetailToggle>
</>
) : (
<Label htmlFor="auth">{l('passLabel')}</Label>
)}
Expand Down
6 changes: 5 additions & 1 deletion app/src/i18n/locales/en-US.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@
"cmps.auth.AuthPage.terminal": "Terminal",
"cmps.auth.AuthPage.subtitle": "Efficiently manage Lightning node liquidity",
"cmps.auth.AuthPage.passLabel": "Enter your password in the field above",
"cmps.auth.AuthPage.showDetail": "Show Detail",
"cmps.auth.AuthPage.hideDetail": "Hide Detail",
"cmps.auth.AuthPage.submitBtn": "Submit",
"cmps.common.Tile.maximizeTip": "Maximize",
"cmps.common.PageHeader.exportTip": "Download CSV",
Expand Down Expand Up @@ -399,6 +401,8 @@
"cmps.tour.SuccessStep.close": "Close",
"stores.authStore.emptyPassErr": "oops, password is required",
"stores.authStore.invalidPassErr": "oops, that password is incorrect",
"stores.authStore.noConnectionErr": "failed to connect",
"stores.authStore.walletLockedErr": "oops, wallet is locked",
"stores.buildSwapView.noChannelsMsg": "You cannot perform a swap without any active channels",
"stores.orderFormView.buy": "Bid",
"stores.orderFormView.sell": "Ask",
Expand All @@ -416,7 +420,7 @@
"stores.settingsStore.httpError": "url must start with 'http'",
"stores.settingsStore.keyword": "url must contain {{keyword}}",
"stores.appView.authErrorTitle": "Your session has expired",
"stores.appView.authErrorMsg": "Please enter you password to continue",
"stores.appView.authErrorMsg": "Please enter your password to continue",
"views.fundNewAccountView.amountTooLow": "must be greater than {{accountMinimum}} sats",
"views.fundNewAccountView.amountTooHigh": "must be less than wallet balance",
"views.fundNewAccountView.lowExpireBlocks": "must be greater than {{blocks}} blocks",
Expand Down
81 changes: 79 additions & 2 deletions app/src/store/stores/authStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ export default class AuthStore {
/** the password encoded to base64 */
credentials = '';

errors = { mainErr: '', litDetail: '', lndDetail: '' };

constructor(store: Store) {
makeAutoObservable(this, {}, { deep: false, autoBind: true });

Expand All @@ -31,6 +33,80 @@ export default class AuthStore {
Object.values(this._store.api).forEach(api => api.setCredentials(credentials));
}

/**
* Convert exception to error message
*/
async getErrMsg(error: string) {
// determine the main error message
const invalidPassMsg = ['expected 1 macaroon, got 0'];
for (const m in invalidPassMsg) {
const errPos = error.lastIndexOf(invalidPassMsg[m]);
if (error.length - invalidPassMsg[m].length == errPos) {
this.errors.mainErr = l('invalidPassErr');
break;
}
}

let walletLocked = false;
if (this.errors.mainErr.length == 0) {
const walletLockedMsg = [
'wallet locked, unlock it to enable full RPC access',
'proxy error with context auth: unknown macaroon to use',
];
for (const m in walletLockedMsg) {
const errPos = error.lastIndexOf(walletLockedMsg[m]);
if (error.length - walletLockedMsg[m].length == errPos) {
walletLocked = true;
this.errors.mainErr = l('walletLockedErr');
break;
}
}
}

if (this.errors.mainErr.length == 0) this.errors.mainErr = l('noConnectionErr');

// get the subserver status message
let litStatus = null;
let lndStatus = null;
try {
const serverStatus = await this._store.api.lit.listSubServerStatus();
const subServersMap = serverStatus['subServersMap'];
for (let e = 0; e < subServersMap.length; e++) {
if (subServersMap[e][0] == 'lit') litStatus = subServersMap[e];
if (subServersMap[e][0] == 'lnd') lndStatus = subServersMap[e];
}

// check for subserver error messages
if (litStatus !== null && litStatus[1].error.length > 0) {
this.errors.litDetail = litStatus[1].error;
}
if (lndStatus !== null && lndStatus[1].error.length > 0) {
this.errors.lndDetail = lndStatus[1].error;
}

// check status
if (
this.errors.litDetail.length == 0 &&
(litStatus === null || !litStatus[1].running)
) {
this.errors.litDetail = 'LiT is not running.';
if (walletLocked)
this.errors.litDetail += ' Please ensure that the wallet is unlocked.';
}
if (
this.errors.lndDetail.length == 0 &&
(lndStatus === null || !lndStatus[1].running)
) {
this.errors.lndDetail = 'LND is not running. Please start lnd and try again.';
}
} catch (e) {
this.errors.litDetail =
'Unable to connect to LiT. Please restart litd and try again.';
}

return this.errors.mainErr;
}

/**
* Validate the supplied password and save for later if successful
*/
Expand All @@ -49,8 +125,9 @@ export default class AuthStore {
} catch (error) {
// clear the credentials if incorrect
this.setCredentials('');
this._store.log.error('incorrect credentials');
throw new Error(l('invalidPassErr'));
this._store.log.error('connection failure');
this.errors = { mainErr: '', litDetail: '', lndDetail: '' };
throw new Error(await this.getErrMsg(error.message));
}
}

Expand Down

0 comments on commit 696df24

Please sign in to comment.