Merge branch 'clearnet' into registration-progress-banner

pull/1846/head
Warrick Corfe-Tan 4 years ago
commit ebb37b44b6

@ -340,7 +340,7 @@
"createAccount": "Create account",
"signIn": "Sign In",
"yourUniqueSessionID": "Say hello to your Session ID",
"allUsersAreRandomly...": "Your Session ID is the unique address people can use to contact you on Session. With no connection to your real identity, your Session ID is totally anonymous and private by design.",
"signupSessionIDBlurb": "Your Session ID is the unique address people can use to contact you on Session. With no connection to your real identity, your Session ID is totally anonymous and private by design.",
"getStarted": "Get started",
"createSessionID": "Create Session ID",
"recoveryPhrase": "Recovery Phrase",

@ -149,7 +149,7 @@
color: themed('textColor');
}
font-weight: bold;
padding: 12px;
padding: 20px;
}
&__welcome-session {
@ -278,7 +278,7 @@
&-description-long,
&-signin-device-pairing-header {
padding-top: 10px;
padding-top: 0;
padding-bottom: 20px;
@include themify($themes) {
@include session-color-subtle(themed('textColor'));

@ -9,7 +9,7 @@ import { SessionButton, SessionButtonColor, SessionButtonType } from '../session
import { SessionIconButton, SessionIconSize, SessionIconType } from '../session/icon';
import { PillDivider } from '../session/PillDivider';
import { SyncUtils, ToastUtils, UserUtils } from '../../session/utils';
import { MAX_USERNAME_LENGTH } from '../session/registration/RegistrationTabs';
import { MAX_USERNAME_LENGTH } from '../session/registration/RegistrationStages';
import { SessionSpinner } from '../session/SessionSpinner';
import { ConversationModel, ConversationTypeEnum } from '../../models/conversation';

@ -1,7 +1,7 @@
import React, { useEffect } from 'react';
import { AccentText } from './AccentText';
import { RegistrationTabs } from './registration/RegistrationTabs';
import { RegistrationStages } from './registration/RegistrationStages';
import { SessionIconButton, SessionIconSize, SessionIconType } from './icon';
import { SessionToastContainer } from './SessionToastContainer';
import { lightTheme, SessionTheme } from '../../state/ducks/SessionTheme';
@ -36,7 +36,7 @@ export const SessionRegistrationView = () => {
<AccentText />
</div>
<div className="session-content-registration">
<RegistrationTabs theme={lightTheme} />
<RegistrationStages />
</div>
</div>
</div>

@ -160,11 +160,15 @@ export enum RegistrationPhase {
interface RegistrationPhaseContext {
registrationPhase: RegistrationPhase;
setRegistrationPhase: (phase: RegistrationPhase) => void;
generatedRecoveryPhrase: string;
hexGeneratedPubKey: string;
}
export const RegistrationContext = createContext<RegistrationPhaseContext>({
registrationPhase: RegistrationPhase.Start,
setRegistrationPhase: () => undefined,
generatedRecoveryPhrase: '',
hexGeneratedPubKey: '',
});
export const RegistrationStages = () => {
@ -199,14 +203,16 @@ export const RegistrationStages = () => {
return (
<div className="session-registration-container">
<RegistrationContext.Provider value={{ registrationPhase, setRegistrationPhase }}>
<RegistrationContext.Provider
value={{
registrationPhase,
setRegistrationPhase,
generatedRecoveryPhrase,
hexGeneratedPubKey,
}}
>
{(registrationPhase === RegistrationPhase.Start ||
registrationPhase === RegistrationPhase.SignUp) && (
<SignUpTab
generatedRecoveryPhrase={generatedRecoveryPhrase}
hexGeneratedPubKey={hexGeneratedPubKey}
/>
)}
registrationPhase === RegistrationPhase.SignUp) && <SignUpTab />}
{(registrationPhase === RegistrationPhase.Start ||
registrationPhase === RegistrationPhase.SignIn) && <SignInTab />}
</RegistrationContext.Provider>

@ -1,239 +0,0 @@
import React from 'react';
import { PromiseUtils, StringUtils, ToastUtils, UserUtils } from '../../../session/utils';
import { getConversationController } from '../../../session/conversations';
import { createOrUpdateItem, removeAll } from '../../../data/data';
import { SignUpTab } from './SignUpTab';
import { SignInTab } from './SignInTab';
import { TabLabel, TabType } from './TabLabel';
import { trigger } from '../../../shims/events';
import {
generateMnemonic,
registerSingleDevice,
sessionGenerateKeyPair,
signInByLinkingDevice,
} from '../../../util/accountManager';
import { fromHex } from '../../../session/utils/String';
import { TaskTimedOutError } from '../../../session/utils/Promise';
import { mn_decode } from '../../../session/crypto/mnemonic';
import { getSwarmPollingInstance } from '../../../session/snode_api/swarmPolling';
export const MAX_USERNAME_LENGTH = 20;
// tslint:disable: use-simple-attributes
interface State {
selectedTab: TabType;
generatedRecoveryPhrase: string;
hexGeneratedPubKey: string;
}
export async function resetRegistration() {
await removeAll();
await window.storage.reset();
await window.storage.fetch();
getConversationController().reset();
await getConversationController().load();
}
/**
* Returns undefined if an error happened, or the trim userName.
*
* Be sure to use the trimmed userName for creating the account.
*/
const displayNameIsValid = (displayName: string): undefined | string => {
const trimName = displayName.trim();
if (!trimName) {
window?.log?.warn('invalid trimmed name for registration');
ToastUtils.pushToastError('invalidDisplayName', window.i18n('displayNameEmpty'));
return undefined;
}
return trimName;
};
export async function signUp(signUpDetails: {
displayName: string;
generatedRecoveryPhrase: string;
}) {
const { displayName, generatedRecoveryPhrase } = signUpDetails;
window?.log?.info('SIGNING UP');
const trimName = displayNameIsValid(displayName);
// shows toast to user about the error
if (!trimName) {
return;
}
try {
await resetRegistration();
await registerSingleDevice(generatedRecoveryPhrase, 'english', trimName);
await createOrUpdateItem({
id: 'hasSyncedInitialConfigurationItem',
value: true,
});
trigger('openInbox');
} catch (e) {
await resetRegistration();
ToastUtils.pushToastError('registrationError', `Error: ${e.message || 'Something went wrong'}`);
window?.log?.warn('exception during registration:', e);
}
}
/**
* Sign in/restore from seed.
* Ask for a display name, as we will drop incoming ConfigurationMessages if any are saved on the swarm.
* We will handle a ConfigurationMessage
*/
export async function signInWithRecovery(signInDetails: {
displayName: string;
userRecoveryPhrase: string;
}) {
const { displayName, userRecoveryPhrase } = signInDetails;
window?.log?.info('RESTORING FROM SEED');
const trimName = displayNameIsValid(displayName);
// shows toast to user about the error
if (!trimName) {
return;
}
try {
await resetRegistration();
await registerSingleDevice(userRecoveryPhrase, 'english', trimName);
trigger('openInbox');
} catch (e) {
await resetRegistration();
ToastUtils.pushToastError('registrationError', `Error: ${e.message || 'Something went wrong'}`);
window?.log?.warn('exception during registration:', e);
}
}
/**
* This is will try to sign in with the user recovery phrase.
* If no ConfigurationMessage is received in 60seconds, the loading will be canceled.
*/
export async function signInWithLinking(signInDetails: { userRecoveryPhrase: string }) {
const { userRecoveryPhrase } = signInDetails;
window?.log?.info('LINKING DEVICE');
try {
await resetRegistration();
await signInByLinkingDevice(userRecoveryPhrase, 'english');
let displayNameFromNetwork = '';
await getSwarmPollingInstance().start();
await PromiseUtils.waitForTask(done => {
window.Whisper.events.on('configurationMessageReceived', (displayName: string) => {
window.Whisper.events.off('configurationMessageReceived');
UserUtils.setSignInByLinking(false);
done(displayName);
displayNameFromNetwork = displayName;
});
}, 60000);
if (displayNameFromNetwork.length) {
// display name, avatars, groups and contacts should already be handled when this event was triggered.
window?.log?.info('We got a displayName from network: ');
} else {
window?.log?.info('Got a config message from network but without a displayName...');
throw new Error('Got a config message from network but without a displayName...');
}
// Do not set the lastProfileUpdateTimestamp.
// We expect to get a display name from a configuration message while we are loading messages of this user
trigger('openInbox');
} catch (e) {
await resetRegistration();
if (e instanceof TaskTimedOutError) {
ToastUtils.pushToastError(
'registrationError',
'Could not find your display name. Please Sign In by Restoring Your Account instead.'
);
} else {
ToastUtils.pushToastError(
'registrationError',
`Error: ${e.message || 'Something went wrong'}`
);
}
window?.log?.warn('exception during registration:', e);
}
}
export class RegistrationTabs extends React.Component<any, State> {
constructor(props: any) {
super(props);
this.state = {
selectedTab: TabType.SignUp,
generatedRecoveryPhrase: '',
hexGeneratedPubKey: '',
};
}
public componentDidMount() {
void this.generateMnemonicAndKeyPair();
void resetRegistration();
}
public render() {
const { selectedTab } = this.state;
return (
<div className="session-registration-container">
<div className="session-registration__tab-container">
<TabLabel
type={TabType.SignUp}
isSelected={selectedTab === TabType.SignUp}
onSelect={this.handleTabSelect}
/>
<TabLabel
type={TabType.SignIn}
isSelected={selectedTab === TabType.SignIn}
onSelect={this.handleTabSelect}
/>
</div>
{this.renderSections()}
</div>
);
}
private async generateMnemonicAndKeyPair() {
if (this.state.generatedRecoveryPhrase === '') {
const mnemonic = await generateMnemonic();
let seedHex = mn_decode(mnemonic);
// handle shorter than 32 bytes seeds
const privKeyHexLength = 32 * 2;
if (seedHex.length !== privKeyHexLength) {
seedHex = seedHex.concat('0'.repeat(32));
seedHex = seedHex.substring(0, privKeyHexLength);
}
const seed = fromHex(seedHex);
const keyPair = await sessionGenerateKeyPair(seed);
const hexGeneratedPubKey = StringUtils.decode(keyPair.pubKey, 'hex');
this.setState({
generatedRecoveryPhrase: mnemonic,
hexGeneratedPubKey, // our 'frontend' sessionID
});
}
}
private readonly handleTabSelect = (tabType: TabType): void => {
this.setState({
selectedTab: tabType,
});
};
private renderSections() {
const { selectedTab, generatedRecoveryPhrase, hexGeneratedPubKey } = this.state;
if (selectedTab === TabType.SignUp) {
return (
<SignUpTab
generatedRecoveryPhrase={generatedRecoveryPhrase}
hexGeneratedPubKey={hexGeneratedPubKey}
/>
);
}
return <SignInTab />;
}
}

@ -2,7 +2,7 @@ import classNames from 'classnames';
import React from 'react';
import { lightTheme } from '../../../state/ducks/SessionTheme';
import { SessionInput } from '../SessionInput';
import { MAX_USERNAME_LENGTH } from './RegistrationTabs';
import { MAX_USERNAME_LENGTH } from './RegistrationStages';
const DisplayNameInput = (props: {
stealAutoFocus?: boolean;

@ -1,9 +1,14 @@
import React, { useState } from 'react';
import React, { useContext, useState } from 'react';
import { Flex } from '../../basic/Flex';
import { SpacerLG } from '../../basic/Text';
import { SessionButton, SessionButtonColor, SessionButtonType } from '../SessionButton';
import { SessionSpinner } from '../SessionSpinner';
import { signInWithLinking, signInWithRecovery } from './RegistrationTabs';
import {
RegistrationContext,
RegistrationPhase,
signInWithLinking,
signInWithRecovery,
} from './RegistrationStages';
import { RegistrationUserDetails } from './RegistrationUserDetails';
import { TermsAndConditions } from './TermsAndConditions';
@ -80,14 +85,14 @@ const SignInButtons = (props: {
<div>
<RestoreUsingRecoveryPhraseButton onRecoveryButtonClicked={props.onRecoveryButtonClicked} />
<SpacerLG />
<div className="or">{window.i18n('or')}</div>
<SpacerLG />
<LinkDeviceButton onLinkDeviceButtonClicked={props.onLinkDeviceButtonClicked} />
</div>
);
};
export const SignInTab = () => {
const { setRegistrationPhase } = useContext(RegistrationContext);
const [signInMode, setSignInMode] = useState(SignInMode.Default);
const [recoveryPhrase, setRecoveryPhrase] = useState('');
const [recoveryPhraseError, setRecoveryPhraseError] = useState(undefined as string | undefined);
@ -153,12 +158,14 @@ export const SignInTab = () => {
<SignInButtons
signInMode={signInMode}
onRecoveryButtonClicked={() => {
setRegistrationPhase(RegistrationPhase.SignIn);
setSignInMode(SignInMode.UsingRecoveryPhrase);
setRecoveryPhrase('');
setDisplayName('');
setIsLoading(false);
}}
onLinkDeviceButtonClicked={() => {
setRegistrationPhase(RegistrationPhase.SignIn);
setSignInMode(SignInMode.LinkDevice);
setRecoveryPhrase('');
setDisplayName('');

@ -1,7 +1,7 @@
import React, { useEffect, useState } from 'react';
import React, { useContext, useEffect, useState } from 'react';
import { SessionButton, SessionButtonColor, SessionButtonType } from '../SessionButton';
import { SessionIdEditable } from '../SessionIdEditable';
import { signUp } from './RegistrationTabs';
import { RegistrationContext, RegistrationPhase, signUp } from './RegistrationStages';
import { RegistrationUserDetails } from './RegistrationUserDetails';
import { TermsAndConditions } from './TermsAndConditions';
@ -11,12 +11,6 @@ export enum SignUpMode {
EnterDetails,
}
export interface Props {
// tslint:disable: react-unused-props-and-state
generatedRecoveryPhrase: string;
hexGeneratedPubKey: string;
}
const CreateSessionIdButton = ({ createSessionID }: { createSessionID: any }) => {
return (
<SessionButton
@ -40,10 +34,8 @@ const ContinueSignUpButton = ({ continueSignUp }: { continueSignUp: any }) => {
};
const SignUpDefault = (props: { createSessionID: () => void }) => {
const allUsersAreRandomly = window.i18n('allUsersAreRandomly...');
return (
<div className="session-registration__content">
<div className="session-description-long">{allUsersAreRandomly}</div>
<CreateSessionIdButton createSessionID={props.createSessionID} />
</div>
);
@ -55,23 +47,26 @@ const SignUpSessionIDShown = (props: { continueSignUp: () => void }) => {
<div className="session-registration__unique-session-id">
{window.i18n('yourUniqueSessionID')}
</div>
<SessionIdEditable editable={false} placeholder={undefined} />
<div className="session-description-long">{window.i18n('signupSessionIDBlurb')}</div>
<ContinueSignUpButton continueSignUp={props.continueSignUp} />
<TermsAndConditions />
</div>
);
};
export const SignUpTab = (props: Props) => {
const [signUpMode, setSignUpMode] = useState(SignUpMode.Default);
export const SignUpTab = () => {
const { setRegistrationPhase, generatedRecoveryPhrase, hexGeneratedPubKey } = useContext(
RegistrationContext
);
const [signUpMode, setSignUpMode] = useState(SignUpMode.Default);
const [displayName, setDisplayName] = useState('');
const [displayNameError, setDisplayNameError] = useState('');
useEffect(() => {
if (signUpMode === SignUpMode.SessionIDShown) {
window.Session.setNewSessionID(props.hexGeneratedPubKey);
window.Session.setNewSessionID(hexGeneratedPubKey);
}
}, [signUpMode]);
@ -80,6 +75,7 @@ export const SignUpTab = (props: Props) => {
<SignUpDefault
createSessionID={() => {
setSignUpMode(SignUpMode.SessionIDShown);
setRegistrationPhase(RegistrationPhase.SignUp);
}}
/>
);
@ -104,7 +100,7 @@ export const SignUpTab = (props: Props) => {
const signUpWithDetails = async () => {
await signUp({
displayName,
generatedRecoveryPhrase: props.generatedRecoveryPhrase,
generatedRecoveryPhrase: generatedRecoveryPhrase,
});
};

@ -94,7 +94,10 @@ export class MessageSentHandler {
window?.log?.warn('Should send PN notify but no wrapped envelope set.');
} else {
// we do not really care about the retsult.
await PnServer.notifyPnServer(wrappedEnvelope, sentMessage.device);
const pnNotifyRet = await PnServer.notifyPnServer(wrappedEnvelope, sentMessage.device);
if (!pnNotifyRet) {
window?.log?.warn('Push notification server request returned false');
}
}
}

Loading…
Cancel
Save