feat: improved error messaging when starting a new message

pull/3083/head
William Grant 12 months ago
parent 2f2c214cf5
commit 4decf22241

@ -229,6 +229,10 @@ export const SessionInput = (props: Props) => {
}, },
onKeyDown: (event: KeyboardEvent) => { onKeyDown: (event: KeyboardEvent) => {
if (event.key === 'Enter' && onEnterPressed) { if (event.key === 'Enter' && onEnterPressed) {
if (isSpecial && event.shiftKey) {
return;
}
event.preventDefault();
onEnterPressed(inputValue); onEnterPressed(inputValue);
setErrorString(''); setErrorString('');
} }

@ -13,6 +13,7 @@ import { SessionButton } from '../../basic/SessionButton';
import { SessionSpinner } from '../../loading'; import { SessionSpinner } from '../../loading';
import { ONSResolve } from '../../../session/apis/snode_api/onsResolve'; import { ONSResolve } from '../../../session/apis/snode_api/onsResolve';
import { NotFoundError, SnodeResponseError } from '../../../session/utils/errors';
import { Flex } from '../../basic/Flex'; import { Flex } from '../../basic/Flex';
import { SpacerLG, SpacerMD } from '../../basic/Text'; import { SpacerLG, SpacerMD } from '../../basic/Text';
import { YourSessionIDPill, YourSessionIDSelectable } from '../../basic/YourSessionIDPill'; import { YourSessionIDPill, YourSessionIDSelectable } from '../../basic/YourSessionIDPill';
@ -28,6 +29,21 @@ const SessionIDDescription = styled.div`
text-align: center; text-align: center;
`; `;
const StyledLeftPaneOverlay = styled(Flex)`
background: var(--background-primary-color);
overflow-y: auto;
overflow-x: hidden;
padding-top: var(--margins-md);
.session-button {
min-width: 160px;
width: fit-content;
margin-top: 1rem;
margin-bottom: 3rem;
flex-shrink: 0;
}
`;
function copyOurSessionID() { function copyOurSessionID() {
const ourSessionId = UserUtils.getOurPubKeyStrFromCache(); const ourSessionId = UserUtils.getOurPubKeyStrFromCache();
if (!ourSessionId) { if (!ourSessionId) {
@ -46,11 +62,9 @@ export const OverlayMessage = () => {
useKey('Escape', closeOverlay); useKey('Escape', closeOverlay);
const [pubkeyOrOns, setPubkeyOrOns] = useState(''); const [pubkeyOrOns, setPubkeyOrOns] = useState('');
const [pubkeyOrOnsError, setPubkeyOrOnsError] = useState<string | undefined>(undefined);
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const buttonText = window.i18n('next');
const placeholder = window.i18n('accountIdOrOnsEnter');
const disableNextButton = !pubkeyOrOns || loading; const disableNextButton = !pubkeyOrOns || loading;
async function openConvoOnceResolved(resolvedSessionID: string) { async function openConvoOnceResolved(resolvedSessionID: string) {
@ -76,41 +90,65 @@ export const OverlayMessage = () => {
} }
async function handleMessageButtonClick() { async function handleMessageButtonClick() {
setPubkeyOrOnsError(undefined);
if ((!pubkeyOrOns && !pubkeyOrOns.length) || !pubkeyOrOns.trim().length) { if ((!pubkeyOrOns && !pubkeyOrOns.length) || !pubkeyOrOns.trim().length) {
ToastUtils.pushToastError('invalidPubKey', window.i18n('onsErrorNotRecognised')); // or ons name setPubkeyOrOnsError(window.i18n('accountIdErrorInvalid'));
return; return;
} }
const pubkeyorOnsTrimmed = pubkeyOrOns.trim(); const pubkeyorOnsTrimmed = pubkeyOrOns.trim();
const validationError = PubKey.validateWithErrorNoBlinding(pubkeyorOnsTrimmed);
if (!PubKey.validateWithErrorNoBlinding(pubkeyorOnsTrimmed)) { if (!validationError) {
await openConvoOnceResolved(pubkeyorOnsTrimmed); await openConvoOnceResolved(pubkeyorOnsTrimmed);
return; return;
} }
const isPubkey = PubKey.validate(pubkeyorOnsTrimmed);
if (isPubkey && validationError) {
setPubkeyOrOnsError(validationError);
return;
}
// this might be an ONS, validate the regex first // this might be an ONS, validate the regex first
const mightBeOnsName = new RegExp(ONSResolve.onsNameRegex, 'g').test(pubkeyorOnsTrimmed); const mightBeOnsName = new RegExp(ONSResolve.onsNameRegex, 'g').test(pubkeyorOnsTrimmed);
if (!mightBeOnsName) { if (!mightBeOnsName) {
ToastUtils.pushToastError('invalidPubKey', window.i18n('onsErrorNotRecognised')); setPubkeyOrOnsError(window.i18n('onsErrorNotRecognised'));
return; return;
} }
setLoading(true); setLoading(true);
try { try {
const resolvedSessionID = await ONSResolve.getSessionIDForOnsName(pubkeyorOnsTrimmed); const resolvedSessionID = await ONSResolve.getSessionIDForOnsName(pubkeyorOnsTrimmed);
if (PubKey.validateWithErrorNoBlinding(resolvedSessionID)) { const idValidationError = PubKey.validateWithErrorNoBlinding(resolvedSessionID);
throw new Error('Got a resolved ONS but the returned entry is not a valid SessionID');
if (idValidationError) {
setPubkeyOrOnsError(window.i18n('onsErrorNotRecognised'));
return;
} }
// this is a pubkey
await openConvoOnceResolved(resolvedSessionID); await openConvoOnceResolved(resolvedSessionID);
} catch (e) { } catch (e) {
window?.log?.warn('failed to resolve ons name', pubkeyorOnsTrimmed, e); setPubkeyOrOnsError(
ToastUtils.pushToastError('invalidPubKey', window.i18n('failedResolveOns')); e instanceof SnodeResponseError
? window.i18n('onsErrorUnableToSearch')
: e instanceof NotFoundError
? window.i18n('onsErrorNotRecognised')
: window.i18n('failedResolveOns')
);
} finally { } finally {
setLoading(false); setLoading(false);
} }
} }
return ( return (
<div className="module-left-pane-overlay"> <StyledLeftPaneOverlay
container={true}
flexDirection={'column'}
flexGrow={1}
alignItems={'center'}
>
{/* TODO[epic=893] Replace everywhere and test new error handling */} {/* TODO[epic=893] Replace everywhere and test new error handling */}
{/* <SessionIdEditable {/* <SessionIdEditable
editable={!loading} editable={!loading}
@ -124,13 +162,14 @@ export const OverlayMessage = () => {
<SessionInput <SessionInput
autoFocus={true} autoFocus={true}
type="text" type="text"
placeholder={placeholder} placeholder={window.i18n('accountIdOrOnsEnter')}
value={pubkeyOrOns} value={pubkeyOrOns}
onValueChanged={setPubkeyOrOns} onValueChanged={setPubkeyOrOns}
onEnterPressed={handleMessageButtonClick} onEnterPressed={handleMessageButtonClick}
inputDataTestId="new-session-conversation" error={pubkeyOrOnsError}
isSpecial={true} isSpecial={true}
centerText={true} centerText={true}
inputDataTestId="new-session-conversation"
/> />
</div> </div>
<SpacerLG /> <SpacerLG />
@ -155,11 +194,11 @@ export const OverlayMessage = () => {
<SessionIconButton iconSize="small" iconType="copy" onClick={copyOurSessionID} /> <SessionIconButton iconSize="small" iconType="copy" onClick={copyOurSessionID} />
</Flex> </Flex>
<SessionButton <SessionButton
text={buttonText} text={window.i18n('next')}
disabled={disableNextButton} disabled={disableNextButton}
onClick={handleMessageButtonClick} onClick={handleMessageButtonClick}
dataTestId="next-new-conversation-button" dataTestId="next-new-conversation-button"
/> />
</div> </StyledLeftPaneOverlay>
); );
}; };

@ -1,5 +1,6 @@
import { isArray } from 'lodash'; import { isArray } from 'lodash';
import { Snode } from '../../../data/data'; import { Snode } from '../../../data/data';
import { SnodeResponseError } from '../../utils/errors';
import { processOnionRequestErrorAtDestination, SnodeResponse } from './onions'; import { processOnionRequestErrorAtDestination, SnodeResponse } from './onions';
import { snodeRpc } from './sessionRpc'; import { snodeRpc } from './sessionRpc';
import { import {
@ -44,7 +45,7 @@ export async function doSnodeBatchRequest(
window?.log?.warn( window?.log?.warn(
`doSnodeBatchRequest - sessionRpc could not talk to ${targetNode.ip}:${targetNode.port}` `doSnodeBatchRequest - sessionRpc could not talk to ${targetNode.ip}:${targetNode.port}`
); );
throw new Error( throw new SnodeResponseError(
`doSnodeBatchRequest - sessionRpc could not talk to ${targetNode.ip}:${targetNode.port}` `doSnodeBatchRequest - sessionRpc could not talk to ${targetNode.ip}:${targetNode.port}`
); );
} }

@ -17,6 +17,7 @@ import { toHex } from '../../utils/String';
import { Snode } from '../../../data/data'; import { Snode } from '../../../data/data';
import { callUtilsWorker } from '../../../webworker/workers/browser/util_worker_interface'; import { callUtilsWorker } from '../../../webworker/workers/browser/util_worker_interface';
import { encodeV4Request } from '../../onions/onionv4'; import { encodeV4Request } from '../../onions/onionv4';
import { SnodeResponseError } from '../../utils/errors';
import { fileServerHost } from '../file_server_api/FileServerApi'; import { fileServerHost } from '../file_server_api/FileServerApi';
import { hrefPnServerProd } from '../push_notification_api/PnServer'; import { hrefPnServerProd } from '../push_notification_api/PnServer';
import { ERROR_CODE_NO_CONNECT } from './SNodeAPI'; import { ERROR_CODE_NO_CONNECT } from './SNodeAPI';
@ -1158,7 +1159,7 @@ async function lokiOnionFetch({
window?.log?.warn('onionFetchRetryable failed ', e.message); window?.log?.warn('onionFetchRetryable failed ', e.message);
if (e?.errno === 'ENETUNREACH') { if (e?.errno === 'ENETUNREACH') {
// better handle the no connection state // better handle the no connection state
throw new Error(ERROR_CODE_NO_CONNECT); throw new SnodeResponseError(ERROR_CODE_NO_CONNECT);
} }
if (e?.message === CLOCK_OUT_OF_SYNC_MESSAGE_ERROR) { if (e?.message === CLOCK_OUT_OF_SYNC_MESSAGE_ERROR) {
window?.log?.warn('Its a clock out of sync error '); window?.log?.warn('Its a clock out of sync error ');

@ -7,6 +7,7 @@ import {
stringToUint8Array, stringToUint8Array,
toHex, toHex,
} from '../../utils/String'; } from '../../utils/String';
import { NotFoundError } from '../../utils/errors';
import { OnsResolveSubRequest } from './SnodeRequestTypes'; import { OnsResolveSubRequest } from './SnodeRequestTypes';
import { doSnodeBatchRequest } from './batchRequest'; import { doSnodeBatchRequest } from './batchRequest';
import { GetNetworkTime } from './getNetworkTime'; import { GetNetworkTime } from './getNetworkTime';
@ -57,7 +58,7 @@ async function getSessionIDForOnsName(onsNameCase: string) {
const intermediate = parsedBody?.result; const intermediate = parsedBody?.result;
if (!intermediate || !intermediate?.encrypted_value) { if (!intermediate || !intermediate?.encrypted_value) {
throw new Error('ONSresolve: no encrypted_value'); throw new NotFoundError('ONSresolve: no encrypted_value');
} }
const hexEncodedCipherText = intermediate?.encrypted_value; const hexEncodedCipherText = intermediate?.encrypted_value;

@ -8,6 +8,7 @@ import { SnodeNamespace, SnodeNamespaces } from './namespaces';
import { TTL_DEFAULT } from '../../constants'; import { TTL_DEFAULT } from '../../constants';
import { UserUtils } from '../../utils'; import { UserUtils } from '../../utils';
import { sleepFor } from '../../utils/Promise'; import { sleepFor } from '../../utils/Promise';
import { SnodeResponseError } from '../../utils/errors';
import { import {
RetrieveLegacyClosedGroupSubRequestType, RetrieveLegacyClosedGroupSubRequestType,
RetrieveSubRequestType, RetrieveSubRequestType,
@ -136,7 +137,7 @@ async function retrieveNextMessages(
window?.log?.warn( window?.log?.warn(
`_retrieveNextMessages - sessionRpc could not talk to ${targetNode.ip}:${targetNode.port}` `_retrieveNextMessages - sessionRpc could not talk to ${targetNode.ip}:${targetNode.port}`
); );
throw new Error( throw new SnodeResponseError(
`_retrieveNextMessages - sessionRpc could not talk to ${targetNode.ip}:${targetNode.port}` `_retrieveNextMessages - sessionRpc could not talk to ${targetNode.ip}:${targetNode.port}`
); );
} }

@ -145,7 +145,7 @@ export class PubKey {
// dev pubkey on testnet are now 66 chars too with the prefix, so every sessionID needs 66 chars and the prefix to be valid // dev pubkey on testnet are now 66 chars too with the prefix, so every sessionID needs 66 chars and the prefix to be valid
if (!isProdOrDevValid) { if (!isProdOrDevValid) {
return window.i18n('invalidPubkeyFormat'); return window.i18n('accountIdErrorInvalid');
} }
return undefined; return undefined;
} }

@ -66,3 +66,11 @@ export class HTTPError extends Error {
} }
} }
} }
export class SnodeResponseError extends Error {
constructor(message = 'sessionRpc could not talk to node') {
super(message);
// restore prototype chain
Object.setPrototypeOf(this, SnodeResponseError.prototype);
}
}

@ -103,6 +103,7 @@ export type LocalizerKeys =
| 'contactsHeader' | 'contactsHeader'
| 'contextMenuNoSuggestions' | 'contextMenuNoSuggestions'
| 'continue' | 'continue'
| 'conversationId'
| 'conversationsHeader' | 'conversationsHeader'
| 'conversationsNone' | 'conversationsNone'
| 'conversationsSettingsTitle' | 'conversationsSettingsTitle'

Loading…
Cancel
Save