diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 650a4416a..c95c54b3b 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -14,7 +14,7 @@ Remember, you can preview this before saving it. ### Contributor checklist: * [ ] My commits are in nice logical chunks with [good commit messages](http://chris.beams.io/posts/git-commit/) -* [ ] My changes are [rebased](https://medium.freecodecamp.org/git-rebase-and-the-golden-rule-explained-70715eccc372) on the latest [`clearnet`](https://github.com/loki-project/loki-messenger/tree/development) branch +* [ ] My changes are [rebased](https://blog.axosoft.com/golden-rule-of-rebasing-in-git/) on the latest [`clearnet`](https://github.com/loki-project/loki-messenger/tree/clearnet) branch * [ ] A `yarn ready` run passes successfully ([more about tests here](https://github.com/loki-project/loki-messenger/blob/master/CONTRIBUTING.md#tests)) * [ ] My changes are ready to be shipped to users diff --git a/js/background.js b/js/background.js index 8db812708..e494311ed 100644 --- a/js/background.js +++ b/js/background.js @@ -2115,7 +2115,7 @@ const shouldSendReceipt = !isError && data.unidentifiedDeliveryReceived && - !data.isFriendRequest && + !data.friendRequest && !isGroup; // Send the receipt async and hope that it succeeds diff --git a/js/views/create_group_dialog_view.js b/js/views/create_group_dialog_view.js index 8a5cb9e30..a2bc8d78e 100644 --- a/js/views/create_group_dialog_view.js +++ b/js/views/create_group_dialog_view.js @@ -165,9 +165,9 @@ props: { titleText: this.titleText, groupName: this.groupName, - okText: this.okText, + okText: i18n('ok'), + cancelText: i18n('cancel'), isPublic: this.isPublic, - cancelText: this.cancelText, existingMembers: this.existingMembers, friendList: this.friendsAndMembers, isAdmin: this.isAdmin, diff --git a/main.js b/main.js index 1990461fa..66ac9c46f 100644 --- a/main.js +++ b/main.js @@ -198,10 +198,10 @@ function captureClicks(window) { window.webContents.on('new-window', handleUrl); } -const DEFAULT_WIDTH = 800; +const DEFAULT_WIDTH = 880; const DEFAULT_HEIGHT = 720; const MIN_WIDTH = 880; -const MIN_HEIGHT = 580; +const MIN_HEIGHT = 720; const BOUNDS_BUFFER = 100; function isVisible(window, bounds) { @@ -1132,7 +1132,7 @@ ipc.on('set-auto-update-setting', (event, enabled) => { function getThemeFromMainWindow() { return new Promise(resolve => { - ipc.once(`get-success-theme-setting`, (_event, value) => resolve(value)); - mainWindow.webContents.send(`get-theme-setting`); + ipc.once('get-success-theme-setting', (_event, value) => resolve(value)); + mainWindow.webContents.send('get-theme-setting'); }); } diff --git a/stylesheets/_mentions.scss b/stylesheets/_mentions.scss index 104164dea..eded63c8b 100644 --- a/stylesheets/_mentions.scss +++ b/stylesheets/_mentions.scss @@ -82,7 +82,7 @@ .error-faded { opacity: 0; - margin-top: -20px; + margin-top: -5px; transition: all 100ms linear; } @@ -96,8 +96,6 @@ max-height: 240px; overflow-y: auto; margin: 4px; - border-top: 1px solid #2f2f2f; - border-bottom: 1px solid #2f2f2f; .check-mark { float: right; diff --git a/stylesheets/_password.scss b/stylesheets/_password.scss deleted file mode 100644 index 46a9f0dd0..000000000 --- a/stylesheets/_password.scss +++ /dev/null @@ -1,50 +0,0 @@ -.password { - .content-wrapper { - display: flex; - align-items: center; - justify-content: center; - color: $color-dark-05; - width: 100%; - height: 100%; - } - - .content { - margin: 3em; - } - - .inputs { - display: flex; - flex-direction: column; - } - - input { - width: 30em; - } - - .error { - font-weight: bold; - font-size: 16px; - margin-top: 1em; - } - - .reset { - font-size: 15px; - margin-top: 1em; - cursor: pointer; - user-select: none; - - a { - color: #78be20; - font-weight: bold; - } - } - - .overlay { - color: $color-dark-05; - background: $color-dark-85; - - .step { - padding: 0; - } - } -} diff --git a/stylesheets/_session.scss b/stylesheets/_session.scss index c9110eb91..a70be1e63 100644 --- a/stylesheets/_session.scss +++ b/stylesheets/_session.scss @@ -140,11 +140,11 @@ div.spacer-lg { transition: filter 0.1s; } -.text-subtle { +.subtle { opacity: 0.6; } -.text-soft { +.soft { opacity: 0.4; } @@ -848,6 +848,12 @@ label { .friend-selection-list { width: unset; } + + .create-group-dialog__member-count { + text-align: center; + margin-top: -25px; + opacity: 0.6; + } } .session-confirm { @@ -963,7 +969,7 @@ label { list-style: none; padding: 0px; margin: 0px; - max-height: 450px; + max-height: 40vh; overflow-y: auto; } @@ -1636,92 +1642,6 @@ input { } } -.clear-data, -.password-prompt { - &-wrapper { - display: flex; - justify-content: center; - align-items: center; - - background-color: $session-color-black; - - width: 100%; - height: 100%; - - padding: 3 * $session-margin-lg; - } - - &-error-section { - width: 100%; - color: $session-color-white; - margin: -$session-margin-sm 0px 2 * $session-margin-lg 0px; - - .session-label { - &.primary { - background-color: rgba($session-color-primary, 0.3); - } - padding: $session-margin-xs $session-margin-sm; - font-size: $session-font-xs; - text-align: center; - } - } - - &-container { - font-family: 'SF Pro Text'; - color: $session-color-white; - display: inline-flex; - flex-direction: column; - align-items: center; - justify-content: center; - - width: 600px; - min-width: 420px; - padding: 3 * $session-margin-lg 2 * $session-margin-lg; - box-sizing: border-box; - background-color: $session-shade-4; - border: 1px solid $session-shade-8; - border-radius: 2px; - - .warning-info-area, - .password-info-area { - display: inline-flex; - justify-content: center; - align-items: center; - - h1 { - color: $session-color-white; - } - svg { - margin-right: $session-margin-lg; - } - } - - p, - input { - margin: $session-margin-lg 0px; - } - - .button-group { - display: inline-flex; - } - - #password-prompt-input { - width: 100%; - color: #fff; - background-color: #2e2e2e; - margin-top: 2 * $session-margin-lg; - padding: $session-margin-xs $session-margin-lg; - outline: none; - border: none; - border-radius: 2px; - text-align: center; - font-size: 24px; - letter-spacing: 5px; - font-family: 'SF Pro Text'; - } - } -} - .onboarding-message-section { display: flex; flex-grow: 1; @@ -1845,6 +1765,13 @@ input { justify-content: space-between; transition: $session-transition-duration; + &:first-child { + border-top: 1px solid rgba($session-shade-8, 0.6); + } + &:last-child { + border-bottom: 1px solid rgba($session-shade-8, 0.6); + } + &.selected { background-color: $session-shade-4; } @@ -1872,6 +1799,10 @@ input { margin-left: 5px; opacity: 0.8; } + + &__avatar > div { + margin-bottom: 0px !important; + } } .invite-friends-container { diff --git a/stylesheets/_session_password.scss b/stylesheets/_session_password.scss new file mode 100644 index 000000000..98d548277 --- /dev/null +++ b/stylesheets/_session_password.scss @@ -0,0 +1,89 @@ +.password { + height: 100vh; + + .clear-data, + .password-prompt { + &-wrapper { + display: flex; + justify-content: center; + align-items: center; + + background-color: $session-color-black; + + width: 100%; + height: 100%; + + padding: 3 * $session-margin-lg; + } + + &-error-section { + width: 100%; + color: $session-color-white; + margin: -$session-margin-sm 0px 2 * $session-margin-lg 0px; + + .session-label { + &.primary { + background-color: rgba($session-color-primary, 0.3); + } + padding: $session-margin-xs $session-margin-sm; + font-size: $session-font-xs; + text-align: center; + } + } + + &-container { + font-family: 'SF Pro Text'; + color: $session-color-white; + display: inline-flex; + flex-direction: column; + align-items: center; + justify-content: center; + + width: 600px; + min-width: 420px; + padding: 3 * $session-margin-lg 2 * $session-margin-lg; + box-sizing: border-box; + background-color: $session-shade-4; + border: 1px solid $session-shade-8; + border-radius: 2px; + + .warning-info-area, + .password-info-area { + display: inline-flex; + justify-content: center; + align-items: center; + + h1 { + color: $session-color-white; + } + svg { + margin-right: $session-margin-lg; + } + } + + p, + input { + margin: $session-margin-lg 0px; + } + + .button-group { + display: inline-flex; + } + + #password-prompt-input { + width: 100%; + color: #fff; + background-color: #2e2e2e; + margin-top: 2 * $session-margin-lg; + padding: $session-margin-xs $session-margin-lg; + outline: none; + border: none; + border-radius: 2px; + text-align: center; + font-size: 24px; + letter-spacing: 5px; + font-family: 'SF Pro Text'; + } + } + } +} diff --git a/stylesheets/manifest.scss b/stylesheets/manifest.scss index d4657b295..4961f65a3 100644 --- a/stylesheets/manifest.scss +++ b/stylesheets/manifest.scss @@ -12,7 +12,6 @@ @import 'emoji'; @import 'mentions'; @import 'settings'; -@import 'password'; // Build the main view @import 'index'; @@ -22,10 +21,16 @@ @import 'ios'; @import 'theme_dark'; -// Session +// /////////////////// // +// ///// Session ///// // +// /////////////////// // @import 'modules'; @import 'session'; + +// Separate screens @import 'session_signin'; +@import 'session_password'; + @import 'session_theme'; @import 'session_left_pane'; @import 'session_group_panel'; diff --git a/ts/components/DevicePairingDialog.tsx b/ts/components/DevicePairingDialog.tsx index 1cbb71131..b87d43b6e 100644 --- a/ts/components/DevicePairingDialog.tsx +++ b/ts/components/DevicePairingDialog.tsx @@ -120,7 +120,7 @@ export class DevicePairingDialog extends React.Component {
-
{secretWords}
+
{secretWords}
@@ -148,9 +148,7 @@ export class DevicePairingDialog extends React.Component {
{this.renderErrors()}

{window.i18n('waitingForDeviceToRegister')}

- - {window.i18n('pairNewDevicePrompt')} - + {window.i18n('pairNewDevicePrompt')}
@@ -199,7 +197,7 @@ export class DevicePairingDialog extends React.Component {

{window.i18n('confirmUnpairingTitle')}
- {description} + {description}

diff --git a/ts/components/conversation/UpdateGroupMembersDialog.tsx b/ts/components/conversation/UpdateGroupMembersDialog.tsx index 215c52ca7..00b51ddea 100644 --- a/ts/components/conversation/UpdateGroupMembersDialog.tsx +++ b/ts/components/conversation/UpdateGroupMembersDialog.tsx @@ -93,7 +93,7 @@ export class UpdateGroupMembersDialog extends React.Component { noFriendsClasses = classNames('no-friends', 'hidden'); } else { // private group - titleText = `${this.props.titleText} (Members: ${checkMarkedCount})`; + titleText = this.props.titleText; noFriendsClasses = this.state.friendList.length === 0 ? 'no-friends' @@ -114,6 +114,16 @@ export class UpdateGroupMembersDialog extends React.Component { onOk={() => null} >
+ + {!this.props.isPublic && ( + <> + + {`${checkMarkedCount} members`} + +
+ + )} +

{errorMsg}

@@ -124,6 +134,8 @@ export class UpdateGroupMembersDialog extends React.Component { 'noMembersInThisGroup' )})`}

+
+
diff --git a/ts/components/session/RegistrationTabs.tsx b/ts/components/session/RegistrationTabs.tsx index 0b5f34fd0..a7fb044db 100644 --- a/ts/components/session/RegistrationTabs.tsx +++ b/ts/components/session/RegistrationTabs.tsx @@ -39,6 +39,7 @@ interface State { passwordErrorString: string; passwordFieldsMatch: boolean; mnemonicSeed: string; + generatedMnemonicSeed: string; hexGeneratedPubKey: string; primaryDevicePubKey: string; mnemonicError: string | undefined; @@ -113,6 +114,7 @@ export class RegistrationTabs extends React.Component<{}, State> { passwordErrorString: '', passwordFieldsMatch: false, mnemonicSeed: '', + generatedMnemonicSeed: '', hexGeneratedPubKey: '', primaryDevicePubKey: '', mnemonicError: undefined, @@ -125,39 +127,11 @@ export class RegistrationTabs extends React.Component<{}, State> { window.textsecure.storage.remove('secondaryDeviceStatus'); } - public render() { + public componentDidMount() { this.generateMnemonicAndKeyPair().ignore(); - - return this.renderTabs(); - } - - private async generateMnemonicAndKeyPair() { - if (this.state.mnemonicSeed === '') { - const language = 'english'; - const mnemonic = await this.accountManager.generateMnemonic(language); - - let seedHex = window.mnemonic.mn_decode(mnemonic, language); - // handle shorter than 32 bytes seeds - const privKeyHexLength = 32 * 2; - if (seedHex.length !== privKeyHexLength) { - seedHex = seedHex.concat(seedHex); - seedHex = seedHex.substring(0, privKeyHexLength); - } - const seed = window.dcodeIO.ByteBuffer.wrap( - seedHex, - 'hex' - ).toArrayBuffer(); - const keyPair = await window.libsignal.Curve.async.createKeyPair(seed); - const hexGeneratedPubKey = Buffer.from(keyPair.pubKey).toString('hex'); - - this.setState({ - mnemonicSeed: mnemonic, - hexGeneratedPubKey, // our 'frontend' sessionID - }); - } } - private renderTabs() { + public render() { const { selectedTab } = this.state; const createAccount = window.i18n('createAccount'); @@ -186,6 +160,32 @@ export class RegistrationTabs extends React.Component<{}, State> { ); } + private async generateMnemonicAndKeyPair() { + if (this.state.generatedMnemonicSeed === '') { + const language = 'english'; + const mnemonic = await this.accountManager.generateMnemonic(language); + + let seedHex = window.mnemonic.mn_decode(mnemonic, language); + // handle shorter than 32 bytes seeds + const privKeyHexLength = 32 * 2; + if (seedHex.length !== privKeyHexLength) { + seedHex = seedHex.concat(seedHex); + seedHex = seedHex.substring(0, privKeyHexLength); + } + const seed = window.dcodeIO.ByteBuffer.wrap( + seedHex, + 'hex' + ).toArrayBuffer(); + const keyPair = await window.libsignal.Curve.async.createKeyPair(seed); + const hexGeneratedPubKey = Buffer.from(keyPair.pubKey).toString('hex'); + + this.setState({ + generatedMnemonicSeed: mnemonic, + hexGeneratedPubKey, // our 'frontend' sessionID + }); + } + } + private readonly handleTabSelect = (tabType: TabType): void => { if (tabType !== TabType.SignIn) { this.cancelSecondaryDevice().ignore(); @@ -200,7 +200,6 @@ export class RegistrationTabs extends React.Component<{}, State> { passwordErrorString: '', passwordFieldsMatch: false, mnemonicSeed: '', - hexGeneratedPubKey: '', primaryDevicePubKey: '', mnemonicError: undefined, displayNameError: undefined, @@ -731,15 +730,19 @@ export class RegistrationTabs extends React.Component<{}, State> { const { password, mnemonicSeed, + generatedMnemonicSeed, + signInMode, displayName, passwordErrorString, passwordFieldsMatch, } = this.state; // Make sure the password is valid + window.log.info('starting registration'); const trimName = displayName.trim(); if (!trimName) { + window.log.warn('invalid trimmed name for registration'); window.pushToast({ title: window.i18n('displayNameEmpty'), type: 'error', @@ -750,6 +753,7 @@ export class RegistrationTabs extends React.Component<{}, State> { } if (passwordErrorString) { + window.log.warn('invalid password for registration'); window.pushToast({ title: window.i18n('invalidPassword'), type: 'error', @@ -760,6 +764,8 @@ export class RegistrationTabs extends React.Component<{}, State> { } if (!!password && !passwordFieldsMatch) { + window.log.warn('passwords does not match for registration'); + window.pushToast({ title: window.i18n('passwordsDoNotMatch'), type: 'error', @@ -769,28 +775,48 @@ export class RegistrationTabs extends React.Component<{}, State> { return; } - if (!mnemonicSeed) { + if (signInMode === SignInMode.UsingSeed && !mnemonicSeed) { + window.log.warn('empty mnemonic seed passed in seed restoration mode'); + + return; + } else if (!generatedMnemonicSeed) { + window.log.warn('empty generated seed'); + return; } // Ensure we clear the secondary device registration status window.textsecure.storage.remove('secondaryDeviceStatus'); + const seedToUse = + signInMode === SignInMode.UsingSeed + ? mnemonicSeed + : generatedMnemonicSeed; + try { await this.resetRegistration(); await window.setPassword(password); await this.accountManager.registerSingleDevice( - mnemonicSeed, + seedToUse, language, trimName ); trigger('openInbox'); } catch (e) { - if (typeof e === 'string') { - //this.showToast(e); + window.pushToast({ + title: `Error: ${e.message || 'Something went wrong'}`, + type: 'error', + id: 'registrationError', + }); + let exmsg = ''; + if (e.message) { + exmsg += e.message; + } + if (e.stack) { + exmsg += ` | stack: + ${e.stack}`; } - //this.log(e); + window.log.warn('exception during registration:', exmsg); } } @@ -804,8 +830,12 @@ export class RegistrationTabs extends React.Component<{}, State> { } private async registerSecondaryDevice() { + window.log.warn('starting registerSecondaryDevice'); + // tslint:disable-next-line: no-backbone-get-set-outside-model if (window.textsecure.storage.get('secondaryDeviceStatus') === 'ongoing') { + window.log.warn('registering secondary device already ongoing'); + return; } this.setState({ diff --git a/ts/components/session/SessionConfirm.tsx b/ts/components/session/SessionConfirm.tsx index 1e3595499..2b43cc543 100644 --- a/ts/components/session/SessionConfirm.tsx +++ b/ts/components/session/SessionConfirm.tsx @@ -48,7 +48,7 @@ export class SessionConfirm extends React.Component { const messageSubText = messageSub ? 'session-confirm-main-message' - : 'text-subtle'; + : 'subtle'; return ( {
{message} {messageSub && ( - + {messageSub} )} diff --git a/ts/components/session/SessionGroupSettings.tsx b/ts/components/session/SessionGroupSettings.tsx index 123f17d23..919cf0876 100644 --- a/ts/components/session/SessionGroupSettings.tsx +++ b/ts/components/session/SessionGroupSettings.tsx @@ -240,7 +240,7 @@ export class SessionGroupSettings extends React.Component { {showMemberCount && ( <>
-
+
{window.i18n('members', memberCount)}
diff --git a/ts/components/session/SessionPasswordPrompt.tsx b/ts/components/session/SessionPasswordPrompt.tsx index 4ef1ae48d..01bb0f9e1 100644 --- a/ts/components/session/SessionPasswordPrompt.tsx +++ b/ts/components/session/SessionPasswordPrompt.tsx @@ -15,6 +15,8 @@ interface State { } export class SessionPasswordPrompt extends React.PureComponent<{}, State> { + private readonly inputRef: React.RefObject; + constructor(props: any) { super(props); @@ -29,10 +31,12 @@ export class SessionPasswordPrompt extends React.PureComponent<{}, State> { this.initLogin = this.initLogin.bind(this); this.initClearDataView = this.initClearDataView.bind(this); + + this.inputRef = React.createRef(); } public componentDidMount() { - setTimeout(() => $('#password-prompt-input').focus(), 100); + (this.inputRef.current as HTMLInputElement).focus(); } public render() { @@ -65,6 +69,7 @@ export class SessionPasswordPrompt extends React.PureComponent<{}, State> { onKeyUp={this.onKeyUp} maxLength={window.CONSTANTS.MAX_PASSWORD_LENGTH} onPaste={this.onPaste} + ref={this.inputRef} /> ); const infoIcon = this.state.clearDataView ? ( @@ -137,15 +142,15 @@ export class SessionPasswordPrompt extends React.PureComponent<{}, State> { }); } - // Prevent pating into input + // Prevent pasting into input return false; } public async onLogin(passPhrase: string) { - const trimmed = passPhrase ? passPhrase.trim() : passPhrase; + const passPhraseTrimmed = passPhrase.trim(); try { - await window.onLogin(trimmed); + await window.onLogin(passPhraseTrimmed); } catch (error) { // Increment the error counter and show the button if necessary this.setState({ @@ -157,7 +162,9 @@ export class SessionPasswordPrompt extends React.PureComponent<{}, State> { } private async initLogin() { - const passPhrase = String($('#password-prompt-input').val()); + const passPhrase = String( + (this.inputRef.current as HTMLInputElement).value + ); await this.onLogin(passPhrase); } diff --git a/ts/components/session/SessionQRModal.tsx b/ts/components/session/SessionQRModal.tsx index 97f613c8d..dc71ee4cd 100644 --- a/ts/components/session/SessionQRModal.tsx +++ b/ts/components/session/SessionQRModal.tsx @@ -29,7 +29,7 @@ export class SessionQRModal extends React.Component { >
-
+
diff --git a/ts/components/session/SessionSeedModal.tsx b/ts/components/session/SessionSeedModal.tsx index e9d461bd9..ed5fdb242 100644 --- a/ts/components/session/SessionSeedModal.tsx +++ b/ts/components/session/SessionSeedModal.tsx @@ -121,7 +121,7 @@ export class SessionSeedModal extends React.Component {

{i18n('seedSavePromptMain')}
- {i18n('seedSavePromptAlt')} + {i18n('seedSavePromptAlt')}