diff --git a/_locales/en/messages.json b/_locales/en/messages.json
index c94edff84..ca1001bb6 100644
--- a/_locales/en/messages.json
+++ b/_locales/en/messages.json
@@ -1376,7 +1376,7 @@
"description": "Description of the media permission description"
},
"clearDataHeader": {
- "message": "Clear Data",
+ "message": "Clear All Local Data",
"description":
"Header in the settings dialog for the section dealing with data deletion"
},
@@ -2281,7 +2281,7 @@
},
"passwordViewTitle": {
- "message": "Type in your password",
+ "message": "Type In Your Password",
"description":
"The title shown when user needs to type in a password to unlock the messenger"
},
@@ -2309,6 +2309,9 @@
"message": "Remove Password",
"description": "Button action that the user can click to remove a password"
},
+ "maxPasswordAttempts": {
+ "message": "Invalid Password. Would you like to reset the database?"
+ },
"typeInOldPassword": {
"message": "Please type in your old password"
},
@@ -2316,7 +2319,7 @@
"message": "Old password is invalid"
},
"invalidPassword": {
- "message": "Invalid password"
+ "message": "Invalid Password"
},
"noGivenPassword": {
"message": "Please enter your password"
diff --git a/index.html b/index.html
index 18d7ba8e7..2d781b911 100644
--- a/index.html
+++ b/index.html
@@ -4,7 +4,7 @@
-
Signal
+ Session
diff --git a/js/modules/signal.js b/js/modules/signal.js
index e44df7149..7075c5782 100644
--- a/js/modules/signal.js
+++ b/js/modules/signal.js
@@ -74,6 +74,9 @@ const {
const {
SessionPasswordModal,
} = require('../../ts/components/session/SessionPasswordModal');
+const {
+ SessionPasswordPrompt,
+} = require('../../ts/components/session/SessionPasswordPrompt');
const {
SessionConfirm,
@@ -293,6 +296,7 @@ exports.setup = (options = {}) => {
SessionQRModal,
SessionSeedModal,
SessionPasswordModal,
+ SessionPasswordPrompt,
SessionDropdown,
SessionScrollButton,
MediaGallery,
diff --git a/js/views/clear_data_view.js b/js/views/clear_data_view.js
deleted file mode 100644
index 7af25d893..000000000
--- a/js/views/clear_data_view.js
+++ /dev/null
@@ -1,72 +0,0 @@
-/* global i18n: false */
-/* global Whisper: false */
-
-/* eslint-disable no-new */
-
-// eslint-disable-next-line func-names
-(function() {
- 'use strict';
-
- window.Whisper = window.Whisper || {};
- const { Logs } = window.Signal;
-
- const CLEAR_DATA_STEPS = {
- CHOICE: 1,
- DELETING: 2,
- };
- window.Whisper.ClearDataView = Whisper.View.extend({
- templateName: 'clear-data',
- className: 'full-screen-flow overlay',
- events: {
- 'click .cancel': 'onCancel',
- 'click .delete-all-data': 'onDeleteAllData',
- },
- initialize(onClear = null) {
- this.step = CLEAR_DATA_STEPS.CHOICE;
- this.onClear = onClear;
- },
- onCancel() {
- this.remove();
- },
- async onDeleteAllData() {
- window.log.info('Deleting everything!');
- this.step = CLEAR_DATA_STEPS.DELETING;
- this.render();
-
- await this.clearAllData();
- },
- async clearAllData() {
- if (this.onClear) {
- this.onClear();
- } else {
- try {
- await Logs.deleteAll();
-
- await window.Signal.Data.removeAll();
- await window.Signal.Data.close();
- await window.Signal.Data.removeDB();
-
- await window.Signal.Data.removeOtherData();
- } catch (error) {
- window.log.error(
- 'Something went wrong deleting all data:',
- error && error.stack ? error.stack : error
- );
- }
- window.restart();
- }
- },
- render_attributes() {
- return {
- isStep1: this.step === CLEAR_DATA_STEPS.CHOICE,
- header: i18n('deleteAllDataHeader'),
- body: i18n('deleteAllDataBody'),
- cancelButton: i18n('cancel'),
- deleteButton: i18n('deleteAllDataButton'),
-
- isStep2: this.step === CLEAR_DATA_STEPS.DELETING,
- deleting: i18n('deleteAllDataProgress'),
- };
- },
- });
-})();
diff --git a/js/views/password_view.js b/js/views/password_view.js
index a2ed42a26..f88ffbafa 100644
--- a/js/views/password_view.js
+++ b/js/views/password_view.js
@@ -1,7 +1,4 @@
-/* global i18n: false */
-/* global Whisper: false */
-
-/* eslint-disable no-new */
+/* global Whisper */
// eslint-disable-next-line func-names
(function() {
@@ -9,63 +6,20 @@
window.Whisper = window.Whisper || {};
- const MIN_LOGIN_TRIES = 3;
-
Whisper.PasswordView = Whisper.View.extend({
- className: 'password full-screen-flow standalone-fullscreen',
- templateName: 'password',
- events: {
- keyup: 'onKeyup',
- 'click #unlock-button': 'onLogin',
- 'click #reset-button': 'onReset',
- },
initialize() {
- this.errorCount = 0;
this.render();
},
- render_attributes() {
- return {
- title: i18n('passwordViewTitle'),
- buttonText: i18n('unlock'),
- resetText: i18n('resetDatabase'),
- showReset: this.errorCount >= MIN_LOGIN_TRIES,
- };
- },
- onKeyup(event) {
- switch (event.key) {
- case 'Enter':
- this.onLogin();
- break;
- default:
- return;
- }
- event.preventDefault();
- },
- async onLogin() {
- const passPhrase = this.$('#passPhrase').val();
- const trimmed = passPhrase ? passPhrase.trim() : passPhrase;
- this.setError('');
- try {
- await window.onLogin(trimmed);
- } catch (e) {
- // Increment the error counter and show the button if necessary
- this.errorCount += 1;
- if (this.errorCount >= MIN_LOGIN_TRIES) {
- this.render();
- }
- this.setError(`Error: ${e}`);
- }
- },
- setError(string) {
- this.$('.error').text(string);
- },
- onReset() {
- const clearDataView = new window.Whisper.ClearDataView(() => {
- window.resetDatabase();
+ render() {
+ this.passwordView = new window.Whisper.ReactWrapperView({
+ className: 'password overlay',
+ Component: window.Signal.Components.SessionPasswordPrompt,
+ props: {},
});
- clearDataView.render();
- this.$el.append(clearDataView.el);
+
+ this.$el.append(this.passwordView.el);
+ return this;
},
});
})();
diff --git a/password.html b/password.html
index 05f5078b7..05478651e 100644
--- a/password.html
+++ b/password.html
@@ -16,63 +16,12 @@
-
-
-
+
+
-
diff --git a/password_preload.js b/password_preload.js
index 8d8a5b7de..1144878ea 100644
--- a/password_preload.js
+++ b/password_preload.js
@@ -8,6 +8,9 @@ const config = url.parse(window.location.toString(), true).query;
const { locale } = config;
const localeMessages = ipcRenderer.sendSync('locale-data');
+window.React = require('react');
+window.ReactDOM = require('react-dom');
+
window.theme = config.theme;
window.i18n = i18n.setup(locale, localeMessages);
@@ -17,6 +20,9 @@ window.getAppInstance = () => config.appInstance;
// So far we're only using this for Signal.Types
const Signal = require('./js/modules/signal');
+const electron = require('electron');
+
+const ipc = electron.ipcRenderer;
window.Signal = Signal.setup({
Attachments: null,
@@ -24,12 +30,32 @@ window.Signal = Signal.setup({
getRegionCode: () => null,
});
+window.Signal.Logs = require('./js/modules/logs');
+
+window.CONSTANTS = {
+ MAX_LOGIN_TRIES: 3,
+ MAX_PASSWORD_LENGTH: 32,
+ MAX_USERNAME_LENGTH: 20,
+};
+
window.passwordUtil = require('./app/password_util');
+window.Signal.Logs = require('./js/modules/logs');
window.resetDatabase = () => {
window.log.info('reset database');
ipcRenderer.send('resetDatabase');
};
+
+window.restart = () => {
+ window.log.info('restart');
+ ipc.send('restart');
+};
+
+window.clearLocalData = async () => {
+ window.resetDatabase();
+ window.restart();
+};
+
window.onLogin = passPhrase =>
new Promise((resolve, reject) => {
ipcRenderer.once('password-window-login-response', (event, error) => {
diff --git a/preload.js b/preload.js
index 338143a47..2445648c0 100644
--- a/preload.js
+++ b/preload.js
@@ -60,8 +60,9 @@ window.isBeforeVersion = (toCheck, baseVersion) => {
};
window.CONSTANTS = {
- maxPasswordLength: 32,
- maxUsernameLength: 20,
+ MAX_LOGIN_TRIES: 3,
+ MAX_PASSWORD_LENGTH: 32,
+ MAX_USERNAME_LENGTH: 20,
};
window.versionInfo = {
@@ -134,6 +135,11 @@ window.restart = () => {
ipc.send('restart');
};
+window.resetDatabase = () => {
+ window.log.info('reset database');
+ ipc.send('resetDatabase');
+};
+
// Events for updating block number states across different windows.
// In this case we need these to update the blocked number
// collection on the main window from the settings window.
diff --git a/stylesheets/_session.scss b/stylesheets/_session.scss
index 9bf8a4372..2fee6682c 100644
--- a/stylesheets/_session.scss
+++ b/stylesheets/_session.scss
@@ -225,6 +225,7 @@ $session_message-container-border-radius: 5px;
}
.button-group > div {
+ display: inline-flex;
margin-left: 5px;
margin-right: 5px;
}
@@ -258,6 +259,10 @@ $session_message-container-border-radius: 5px;
&.brand {
color: $session-color-white;
+ &:hover {
+ filter: brightness(90%);
+ }
+
&.green,
&.white,
&.primary,
@@ -1438,3 +1443,89 @@ 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';
+ }
+ }
+}
diff --git a/ts/components/EditProfileDialog.tsx b/ts/components/EditProfileDialog.tsx
index 959601298..555986cdc 100644
--- a/ts/components/EditProfileDialog.tsx
+++ b/ts/components/EditProfileDialog.tsx
@@ -208,7 +208,7 @@ export class EditProfileDialog extends React.Component
{
value={this.state.profileName}
placeholder={placeholderText}
onChange={this.onNameEdited}
- maxLength={window.CONSTANTS.maxUsernameLength}
+ maxLength={window.CONSTANTS.MAX_USERNAME_LENGTH}
tabIndex={0}
required={true}
aria-required={true}
@@ -302,7 +302,10 @@ export class EditProfileDialog extends React.Component {
private onClickOK() {
const newName = this.state.profileName.trim();
- if (newName.length === 0 || newName.length > window.CONSTANTS.maxUsernameLength) {
+ if (
+ newName.length === 0 ||
+ newName.length > window.CONSTANTS.MAX_USERNAME_LENGTH
+ ) {
return;
}
diff --git a/ts/components/session/ActionsPanel.tsx b/ts/components/session/ActionsPanel.tsx
index 073d69d23..cc2601893 100644
--- a/ts/components/session/ActionsPanel.tsx
+++ b/ts/components/session/ActionsPanel.tsx
@@ -64,7 +64,8 @@ export class ActionsPanel extends React.Component {
const handleClick = onSelect
? () => {
type === SectionType.Profile
- ? this.editProfileHandle()
+ ? /* tslint:disable-next-line:no-void-expression */
+ this.editProfileHandle()
: /* tslint:disable-next-line:no-void-expression */
onSelect(type);
}
diff --git a/ts/components/session/RegistrationTabs.tsx b/ts/components/session/RegistrationTabs.tsx
index cd1adc7e7..1f8bc5c31 100644
--- a/ts/components/session/RegistrationTabs.tsx
+++ b/ts/components/session/RegistrationTabs.tsx
@@ -449,7 +449,7 @@ export class RegistrationTabs extends React.Component<{}, State> {
type="text"
placeholder={window.i18n('enterDisplayName')}
value={this.state.displayName}
- maxLength={window.CONSTANTS.maxUsernameLength}
+ maxLength={window.CONSTANTS.MAX_USERNAME_LENGTH}
onValueChanged={(val: string) => {
this.onDisplayNameChanged(val);
}}
@@ -463,7 +463,7 @@ export class RegistrationTabs extends React.Component<{}, State> {
error={this.state.passwordErrorString}
type="password"
placeholder={window.i18n('enterOptionalPassword')}
- maxLength={window.CONSTANTS.maxPasswordLength}
+ maxLength={window.CONSTANTS.MAX_PASSWORD_LENGTH}
onValueChanged={(val: string) => {
this.onPasswordChanged(val);
}}
@@ -477,7 +477,7 @@ export class RegistrationTabs extends React.Component<{}, State> {
error={passwordsDoNotMatch}
type="password"
placeholder={window.i18n('optionalPassword')}
- maxLength={window.CONSTANTS.maxPasswordLength}
+ maxLength={window.CONSTANTS.MAX_PASSWORD_LENGTH}
onValueChanged={(val: string) => {
this.onPasswordVerifyChanged(val);
}}
diff --git a/ts/components/session/SessionInput.tsx b/ts/components/session/SessionInput.tsx
index ab892a5d0..11432e80a 100644
--- a/ts/components/session/SessionInput.tsx
+++ b/ts/components/session/SessionInput.tsx
@@ -34,7 +34,14 @@ export class SessionInput extends React.PureComponent {
}
public render() {
- const { placeholder, type, value, maxLength, enableShowHide, error } = this.props;
+ const {
+ placeholder,
+ type,
+ value,
+ maxLength,
+ enableShowHide,
+ error,
+ } = this.props;
const { forceShow } = this.state;
const correctType = forceShow ? 'text' : type;
diff --git a/ts/components/session/SessionPasswordModal.tsx b/ts/components/session/SessionPasswordModal.tsx
index c939cbfcb..fe626db39 100644
--- a/ts/components/session/SessionPasswordModal.tsx
+++ b/ts/components/session/SessionPasswordModal.tsx
@@ -58,14 +58,14 @@ export class SessionPasswordModal extends React.Component {
type="password"
id="password-modal-input"
placeholder={placeholders[0]}
- maxLength={window.CONSTANTS.maxPasswordLength}
+ maxLength={window.CONSTANTS.MAX_PASSWORD_LENGTH}
/>
{action !== PasswordAction.Remove && (
)}
@@ -120,7 +120,7 @@ export class SessionPasswordModal extends React.Component {
$('#password-modal-input-confirm').val()
);
- if (enteredPassword.length === 0 || enteredPasswordConfirm.length === 0){
+ if (enteredPassword.length === 0 || enteredPasswordConfirm.length === 0) {
return;
}
diff --git a/ts/components/session/SessionPasswordPrompt.tsx b/ts/components/session/SessionPasswordPrompt.tsx
new file mode 100644
index 000000000..a48af57ce
--- /dev/null
+++ b/ts/components/session/SessionPasswordPrompt.tsx
@@ -0,0 +1,196 @@
+import React from 'react';
+import classNames from 'classnames';
+
+import { SessionIcon, SessionIconType } from './icon';
+import {
+ SessionButton,
+ SessionButtonColor,
+ SessionButtonType,
+} from './SessionButton';
+
+interface State {
+ error: string;
+ errorCount: number;
+ clearDataView: boolean;
+}
+
+export class SessionPasswordPrompt extends React.PureComponent<{}, State> {
+ constructor(props: any) {
+ super(props);
+
+ this.state = {
+ error: '',
+ errorCount: 0,
+ clearDataView: false,
+ };
+
+ this.onKeyUp = this.onKeyUp.bind(this);
+ this.initLogin = this.initLogin.bind(this);
+ this.initClearDataView = this.initClearDataView.bind(this);
+
+ window.addEventListener('keyup', this.onKeyUp);
+ }
+
+ public render() {
+ const showResetElements =
+ this.state.errorCount >= window.CONSTANTS.MAX_LOGIN_TRIES;
+
+ const wrapperClass = this.state.clearDataView
+ ? 'clear-data-wrapper'
+ : 'password-prompt-wrapper';
+ const containerClass = this.state.clearDataView
+ ? 'clear-data-container'
+ : 'password-prompt-container';
+ const infoAreaClass = this.state.clearDataView
+ ? 'warning-info-area'
+ : 'password-info-area';
+ const infoTitle = this.state.clearDataView
+ ? window.i18n('clearDataHeader')
+ : window.i18n('passwordViewTitle');
+ const buttonGroup = this.state.clearDataView
+ ? this.renderClearDataViewButtons()
+ : this.renderPasswordViewButtons();
+ const featureElement = this.state.clearDataView ? (
+ {window.i18n('clearDataExplanation')}
+ ) : (
+
+ );
+ const infoIcon = this.state.clearDataView ? (
+
+ ) : (
+
+ );
+ const errorSection = !this.state.clearDataView && (
+
+ {this.state.error && (
+ <>
+ {showResetElements ? (
+
+ {window.i18n('maxPasswordAttempts')}
+
+ ) : (
+
{this.state.error}
+ )}
+ >
+ )}
+
+ );
+
+ return (
+
+
+
+ {infoIcon}
+
+
{infoTitle}
+
+
+ {featureElement}
+ {errorSection}
+ {buttonGroup}
+
+
+ );
+ }
+
+ public async onKeyUp(event: any) {
+ switch (event.key) {
+ case 'Enter':
+ await this.initLogin();
+ break;
+ default:
+ }
+ event.preventDefault();
+ }
+
+ public async onLogin(passPhrase: string) {
+ const trimmed = passPhrase ? passPhrase.trim() : passPhrase;
+
+ try {
+ await window.onLogin(trimmed);
+ } catch (e) {
+ // Increment the error counter and show the button if necessary
+ this.setState({
+ errorCount: this.state.errorCount + 1,
+ });
+
+ this.setState({ error: e });
+ }
+ }
+
+ private async initLogin() {
+ const passPhrase = String($('#password-prompt-input').val());
+ await this.onLogin(passPhrase);
+ }
+
+ private initClearDataView() {
+ this.setState({
+ error: '',
+ errorCount: 0,
+ clearDataView: true,
+ });
+ }
+
+ private renderPasswordViewButtons(): JSX.Element {
+ const showResetElements =
+ this.state.errorCount >= window.CONSTANTS.MAX_LOGIN_TRIES;
+
+ return (
+
+ {showResetElements && (
+ <>
+
+ >
+ )}
+
+
+ );
+ }
+
+ private renderClearDataViewButtons(): JSX.Element {
+ return (
+
+ {
+ this.setState({ clearDataView: false });
+ }}
+ />
+
+
+
+ );
+ }
+}
diff --git a/ts/components/session/SessionScrollButton.tsx b/ts/components/session/SessionScrollButton.tsx
index 39644e0d6..6c6950611 100644
--- a/ts/components/session/SessionScrollButton.tsx
+++ b/ts/components/session/SessionScrollButton.tsx
@@ -1,12 +1,8 @@
import React from 'react';
-import { SessionIconButton, SessionIconType, SessionIconSize } from './icon';
+import { SessionIconButton, SessionIconSize, SessionIconType } from './icon';
-interface Props {
- count: number;
-}
-
-export class SessionScrollButton extends React.PureComponent {
+export class SessionScrollButton extends React.PureComponent {
constructor(props: any) {
super(props);
}
diff --git a/ts/components/session/icon/SessionIcon.tsx b/ts/components/session/icon/SessionIcon.tsx
index 6f1bb4b0b..de8e7c43c 100644
--- a/ts/components/session/icon/SessionIcon.tsx
+++ b/ts/components/session/icon/SessionIcon.tsx
@@ -5,7 +5,7 @@ import { icons, SessionIconSize, SessionIconType } from '../icon';
export interface Props {
iconType: SessionIconType;
- iconSize: SessionIconSize;
+ iconSize: SessionIconSize | number;
iconColor: string;
iconPadded: boolean;
iconRotation: number;
@@ -33,21 +33,25 @@ export class SessionIcon extends React.PureComponent {
} = this.props;
let iconDimensions;
- switch (iconSize) {
- case SessionIconSize.Small:
- iconDimensions = '15';
- break;
- case SessionIconSize.Medium:
- iconDimensions = '20';
- break;
- case SessionIconSize.Large:
- iconDimensions = '25';
- break;
- case SessionIconSize.Huge:
- iconDimensions = '30';
- break;
- default:
- iconDimensions = '20';
+ if (typeof iconSize === 'number') {
+ iconDimensions = iconSize;
+ } else {
+ switch (iconSize) {
+ case SessionIconSize.Small:
+ iconDimensions = '15';
+ break;
+ case SessionIconSize.Medium:
+ iconDimensions = '20';
+ break;
+ case SessionIconSize.Large:
+ iconDimensions = '25';
+ break;
+ case SessionIconSize.Huge:
+ iconDimensions = '30';
+ break;
+ default:
+ iconDimensions = '20';
+ }
}
const iconDef = icons[iconType];
diff --git a/ts/global.d.ts b/ts/global.d.ts
index 0a426177f..dda00f796 100644
--- a/ts/global.d.ts
+++ b/ts/global.d.ts
@@ -4,6 +4,7 @@ interface Window {
Events: any;
deleteAllData: any;
+ clearLocalData: any;
getAccountManager: any;
mnemonic: any;
clipboard: any;
@@ -23,6 +24,7 @@ interface Window {
// Following function needs to be written in background.js
// getMemberList: any;
+ onLogin: any;
setPassword: any;
textsecure: any;
Session: any;
@@ -50,6 +52,8 @@ interface Window {
getSettingValue: any;
setSettingValue: any;
lokiFeatureFlags: any;
+
+ resetDatabase: any;
}
interface Promise {