safety-number-view

pull/1279/head
Vincent 4 years ago
parent 134df7e389
commit d823e2a758

@ -304,6 +304,10 @@
}
}
},
"verificationKeysLoadFail": {
"message": "Failed to load verification keys",
"description": "Displayed on `Show Safety Number` option in conversation screen"
},
"youMarkedAsVerified": {
"message": "You marked your Safety Number with $name$ as verified",
"description": "Shown in the conversation history when the user marks a contact as verified.",
@ -439,10 +443,10 @@
"description": "Label for a button to accept a new safety number"
},
"verify": {
"message": "Mark as verified"
"message": "Mark As Verified"
},
"unverify": {
"message": "Mark as not verified"
"message": "Mark As Not Verified"
},
"isVerified": {
"message": "You have verified your safety number with $name$.",
@ -1688,6 +1692,10 @@
"message": "Play audio notification",
"description": "Description for audio notification setting"
},
"safetyNumber": {
"message": "Safety Number",
"description": "Title to the safety number view"
},
"safetyNumberChanged": {
"message": "Safety Number has changed",
"description": "A notification shown in the conversation when a contact reinstalls"

@ -186,37 +186,6 @@
</div>
</script>
<script type='text/x-tmpl-mustache' id='key-verification'>
<div class='container'>
{{ ^hasTheirKey }}
<div class='placeholder'>{{ theirKeyUnknown }}</div>
{{ /hasTheirKey }}
{{ #hasTheirKey }}
<label> {{ yourSafetyNumberWith }} </label>
<!--<div class='qr'></div>-->
<div class='key'>
{{ #chunks }} <span>{{ . }}</span> {{ /chunks }}
</div>
{{ /hasTheirKey }}
{{ verifyHelp }}
<p> {{> link_to_support }} </p>
<div class='summary'>
{{ #isVerified }}
<span class='icon verified'></span>
{{ /isVerified }}
{{ ^isVerified }}
<span class='icon shield'></span>
{{ /isVerified }}
{{ verifiedStatus }}
</div>
<div class='verify'>
<button class='verify grey'>
{{ verifyButton }}
</button>
</div>
</div>
</script>
<script type='text/x-tmpl-mustache' id='clear-data'>
{{#isStep1}}
<div class='step'>
@ -477,9 +446,7 @@
<script type='text/javascript' src='js/views/session_confirm_view.js'></script>
<script type='text/javascript' src='js/views/file_input_view.js'></script>
<script type='text/javascript' src='js/views/list_view.js'></script>
<script type='text/javascript' src='js/views/contact_list_view.js'></script>
<script type='text/javascript' src='js/views/message_view.js'></script>
<script type='text/javascript' src='js/views/key_verification_view.js'></script>
<script type='text/javascript' src='js/views/message_list_view.js'></script>
<script type='text/javascript' src='js/views/member_list_view.js'></script>
<script type='text/javascript' src='js/views/bulk_edit_view.js'></script>

@ -186,37 +186,6 @@
</div>
</script>
<script type='text/x-tmpl-mustache' id='key-verification'>
<div class='container'>
{{ ^hasTheirKey }}
<div class='placeholder'>{{ theirKeyUnknown }}</div>
{{ /hasTheirKey }}
{{ #hasTheirKey }}
<label> {{ yourSafetyNumberWith }} </label>
<!--<div class='qr'></div>-->
<div class='key'>
{{ #chunks }} <span>{{ . }}</span> {{ /chunks }}
</div>
{{ /hasTheirKey }}
{{ verifyHelp }}
<p> {{> link_to_support }} </p>
<div class='summary'>
{{ #isVerified }}
<span class='icon verified'></span>
{{ /isVerified }}
{{ ^isVerified }}
<span class='icon shield'></span>
{{ /isVerified }}
{{ verifiedStatus }}
</div>
<div class='verify'>
<button class='verify grey'>
{{ verifyButton }}
</button>
</div>
</div>
</script>
<script type='text/x-tmpl-mustache' id='clear-data'>
{{#isStep1}}
<div class='step'>
@ -477,9 +446,7 @@
<script type='text/javascript' src='js/views/session_confirm_view.js'></script>
<script type='text/javascript' src='js/views/file_input_view.js'></script>
<script type='text/javascript' src='js/views/list_view.js'></script>
<script type='text/javascript' src='js/views/contact_list_view.js'></script>
<script type='text/javascript' src='js/views/message_view.js'></script>
<script type='text/javascript' src='js/views/key_verification_view.js'></script>
<script type='text/javascript' src='js/views/message_list_view.js'></script>
<script type='text/javascript' src='js/views/member_list_view.js'></script>
<script type='text/javascript' src='js/views/bulk_edit_view.js'></script>

@ -0,0 +1,49 @@
<?xml version="1.0" encoding="iso-8859-1"?>
<!-- Generator: Adobe Illustrator 19.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 512 512" style="enable-background:new 0 0 512 512;" xml:space="preserve">
<path style="fill:#7C8287;" d="M380.942,124.942v109.475h-74.598V124.403c0-27.768-22.586-50.354-50.344-50.354
s-50.354,22.586-50.354,50.354v110.015h-74.588V124.942C131.058,56.046,187.104,0,256,0S380.942,56.046,380.942,124.942z"/>
<path style="fill:#596C76;" d="M380.942,124.942v109.475h-74.598V124.403c0-27.768-22.586-50.354-50.344-50.354V0
C324.896,0,380.942,56.046,380.942,124.942z"/>
<path style="fill:#FF5B5B;" d="M420.543,254.198v188.986c0,37.943-30.874,68.817-68.816,68.817H160.264
c-37.943,0-68.807-30.874-68.807-68.816V254.198c0-19.161,15.597-34.758,34.758-34.758h259.56
C404.946,219.44,420.543,235.037,420.543,254.198z"/>
<path style="fill:#FF193D;" d="M420.543,254.198v188.986c0,37.943-30.874,68.817-68.816,68.817H256V219.44h129.775
C404.946,219.44,420.543,235.037,420.543,254.198z"/>
<path style="fill:#D20041;" d="M297.747,387.572c5.851,5.851,5.851,15.337,0,21.188c-2.926,2.926-6.76,4.383-10.584,4.383
c-3.834,0-7.668-1.458-10.594-4.383l-20.899-20.899l-20.24,20.23c-2.926,2.926-6.76,4.393-10.594,4.393
c-3.824,0-7.658-1.468-10.584-4.393c-5.851-5.841-5.851-15.327,0-21.178l20.24-20.24l-21.388-21.378
c-5.851-5.851-5.851-15.337,0-21.178c5.851-5.851,15.337-5.851,21.178,0l21.388,21.378l22.047-22.047
c5.841-5.851,15.327-5.851,21.178,0c5.851,5.851,5.851,15.337,0,21.188l-22.047,22.037L297.747,387.572z"/>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

@ -0,0 +1,57 @@
<?xml version="1.0" encoding="iso-8859-1"?>
<!-- Generator: Adobe Illustrator 19.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 512 512" style="enable-background:new 0 0 512 512;" xml:space="preserve">
<path style="fill:#7C8287;" d="M380.942,124.942v109.475h-74.598V124.403c0-27.768-22.586-50.354-50.344-50.354
s-50.354,22.586-50.354,50.354v110.015h-74.588V124.942C131.058,56.046,187.104,0,256,0S380.942,56.046,380.942,124.942z"/>
<path style="fill:#596C76;" d="M380.942,124.942v109.475h-74.598V124.403c0-27.768-22.586-50.354-50.344-50.354V0
C324.896,0,380.942,56.046,380.942,124.942z"/>
<path style="fill:#00EE84;" d="M420.543,254.198v188.986c0,37.943-30.874,68.817-68.816,68.817H160.264
c-37.943,0-68.807-30.874-68.807-68.816V254.198c0-19.161,15.597-34.758,34.758-34.758h259.56
C404.946,219.44,420.543,235.037,420.543,254.198z"/>
<path style="fill:#00DD7B;" d="M420.543,254.198v188.986c0,37.943-30.874,68.817-68.816,68.817H256V219.44h129.775
C404.946,219.44,420.543,235.037,420.543,254.198z"/>
<path style="fill:#00AB5E;" d="M311.776,342.245L256,393.858l-5.382,4.983c-0.12,0.11-0.26,0.21-0.379,0.32
c-0.18,0.16-0.359,0.31-0.549,0.459c-0.2,0.15-0.399,0.3-0.609,0.449c-0.19,0.14-0.389,0.27-0.579,0.399
c-0.22,0.14-0.439,0.26-0.659,0.389c-0.2,0.11-0.389,0.23-0.589,0.329c-0.24,0.12-0.479,0.22-0.719,0.329
c-0.19,0.09-0.389,0.18-0.579,0.26c-0.26,0.1-0.519,0.18-0.779,0.27c-0.19,0.06-0.369,0.13-0.559,0.18
c-0.29,0.09-0.579,0.15-0.859,0.21c-0.18,0.04-0.339,0.09-0.519,0.12c-0.32,0.07-0.649,0.11-0.979,0.15
c-0.14,0.01-0.27,0.04-0.409,0.05c-0.469,0.05-0.939,0.07-1.398,0.07c0,0,0,0-0.01,0c-0.489,0-0.979-0.03-1.468-0.08
c-0.15-0.01-0.29-0.04-0.429-0.06c-0.349-0.04-0.689-0.09-1.038-0.15c-0.17-0.04-0.339-0.09-0.519-0.13
c-0.31-0.07-0.609-0.15-0.919-0.24c-0.19-0.06-0.369-0.13-0.559-0.2c-0.28-0.09-0.559-0.19-0.839-0.3
c-0.2-0.09-0.389-0.19-0.589-0.28c-0.26-0.12-0.509-0.23-0.759-0.369c-0.2-0.11-0.399-0.24-0.599-0.359
c-0.24-0.14-0.469-0.27-0.689-0.429c-0.2-0.14-0.399-0.29-0.599-0.439c-0.21-0.16-0.419-0.32-0.629-0.489
c-0.19-0.16-0.379-0.339-0.559-0.509c-0.13-0.12-0.27-0.23-0.389-0.349l-26.73-26.73c-5.841-5.851-5.841-15.327,0-21.178
c5.851-5.851,15.337-5.851,21.188,0l16.535,16.535L256,353.049l35.437-32.791c6.071-5.622,15.547-5.252,21.158,0.819
C318.216,327.148,317.847,336.624,311.776,342.245z"/>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.6 KiB

@ -793,6 +793,7 @@
closeTheme: params.closeTheme || undefined,
cancelText: params.cancelText || undefined,
hideCancel: params.hideCancel || false,
centeredText: params.centeredText || false,
});
confirmDialog.render();

@ -61,8 +61,7 @@
window.getConversationByKey = key => {
// Key is pubkey or public chat name
const conversation =
conversations.models.find(conv => conv.id === key);
const conversation = conversations.models.find(conv => conv.id === key);
return conversation;
};

@ -1,4 +1,5 @@
interface ConversationAttributes {
profileName?: string;
members: Array<string>;
left: boolean;
expireTimer: number;
@ -24,11 +25,15 @@ export interface ConversationModel
options: object
) => void;
isPrivate: () => boolean;
isVerified: () => boolean;
toggleVerified: () => Promise<void>;
getProfile: (id: string) => Promise<any>;
getProfiles: () => Promise<any>;
setProfileKey: (key: string) => void;
isMe: () => boolean;
getRecipients: () => Array<string>;
getTitle: () => string;
onReadMessage: (message: MessageModel) => void;
updateTextInputState: () => void;
lastMessage: string;
}

@ -622,8 +622,6 @@
onClick: () => this.trigger('select', this),
onBlockContact: () => this.block(),
onUnblockContact: () => this.unblock(),
onChangeNickname: () => this.changeNickname(),
onClearNickname: () => this.setNickname(null),
onCopyPublicKey: () => this.copyPublicKey(),
onDeleteContact: () => this.deleteContact(),
onDeleteMessages: () => this.deleteMessages(),
@ -2136,8 +2134,6 @@
})
);
console.log(`[vince][unread] Read:`, read);
// Some messages we're marking read are local notifications with no sender
read = _.filter(read, m => Boolean(m.sender));
unreadMessages = unreadMessages.filter(m => Boolean(m.isIncoming()));

@ -1,58 +0,0 @@
/* global Whisper: false */
/* global textsecure: false */
// eslint-disable-next-line func-names
(function() {
'use strict';
window.Whisper = window.Whisper || {};
Whisper.ContactListView = Whisper.ListView.extend({
tagName: 'div',
itemView: Whisper.View.extend({
tagName: 'div',
className: 'contact',
templateName: 'contact',
initialize(options) {
this.ourNumber = textsecure.storage.user.getNumber();
this.listenBack = options.listenBack;
this.listenTo(this.model, 'change', this.render);
},
render() {
if (this.contactView) {
this.contactView.remove();
this.contactView = null;
}
const isMe = this.ourNumber === this.model.id;
this.contactView = new Whisper.ReactWrapperView({
className: 'contact-wrapper',
Component: window.Signal.Components.ContactListItem,
props: {
isMe,
color: this.model.getColor(),
avatarPath: this.model.getAvatarPath(),
phoneNumber: this.model.getNumber(),
name: this.model.getName(),
profileName: this.model.getProfileName(),
verified: this.model.isVerified(),
onClick: this.showIdentity.bind(this),
},
});
this.$el.append(this.contactView.el);
return this;
},
showIdentity() {
if (this.model.id === this.ourNumber) {
return;
}
const view = new Whisper.KeyVerificationPanelView({
model: this.model,
});
this.listenBack(view);
},
}),
});
})();

@ -222,12 +222,6 @@
onUnblockUser: () => {
this.model.unblock();
},
onChangeNickname: () => {
this.model.changeNickname();
},
onClearNickname: () => {
this.model.setNickname(null);
},
onCopyPublicKey: () => {
this.model.copyPublicKey();
},
@ -235,9 +229,6 @@
this.unload('archive');
this.model.setArchived(true);
},
onMoveToInbox: () => {
this.model.setArchived(false);
},
onLeaveGroup: () => {
window.Whisper.events.trigger('leaveGroup', this.model);
},
@ -1264,22 +1255,6 @@
});
},
showSafetyNumber(providedModel) {
let model = providedModel;
if (!model && this.model.isPrivate()) {
// eslint-disable-next-line prefer-destructuring
model = this.model;
}
if (model) {
const view = new Whisper.KeyVerificationPanelView({
model,
});
this.listenBack(view);
this.updateHeader();
}
},
// THIS DOES NOT DOWNLOAD ANYTHING!
downloadAttachment({ attachment, message, isDangerous }) {
if (isDangerous) {

@ -1,138 +0,0 @@
/* global Whisper, textsecure, QRCode, dcodeIO, libsignal, i18n, _ */
/* eslint-disable more/no-then */
// eslint-disable-next-line func-names
(function() {
'use strict';
window.Whisper = window.Whisper || {};
Whisper.KeyVerificationPanelView = Whisper.View.extend({
className: 'key-verification panel',
templateName: 'key-verification',
events: {
'click button.verify': 'toggleVerified',
},
initialize(options) {
this.ourNumber = textsecure.storage.user.getNumber();
if (options.newKey) {
this.theirKey = options.newKey;
}
this.loadKeys().then(() => {
this.listenTo(this.model, 'change', this.render);
});
},
loadKeys() {
return Promise.all([this.loadTheirKey(), this.loadOurKey()])
.then(this.generateSecurityNumber.bind(this))
.then(this.render.bind(this));
// .then(this.makeQRCode.bind(this));
},
makeQRCode() {
// Per Lilia: We can't turn this on until it generates a Latin1 string, as is
// required by the mobile clients.
new QRCode(this.$('.qr')[0]).makeCode(
dcodeIO.ByteBuffer.wrap(this.ourKey).toString('base64')
);
},
loadTheirKey() {
return textsecure.storage.protocol
.loadIdentityKey(this.model.id)
.then(theirKey => {
this.theirKey = theirKey;
});
},
loadOurKey() {
return textsecure.storage.protocol
.loadIdentityKey(this.ourNumber)
.then(ourKey => {
this.ourKey = ourKey;
});
},
generateSecurityNumber() {
return new libsignal.FingerprintGenerator(5200)
.createFor(this.ourNumber, this.ourKey, this.model.id, this.theirKey)
.then(securityNumber => {
this.securityNumber = securityNumber;
});
},
onSafetyNumberChanged() {
this.model.getProfiles().then(this.loadKeys.bind(this));
window.confirmationDialog({
title: i18n('changedSinceVerifiedTitle'),
message: i18n('changedRightAfterVerify', [
this.model.getTitle(),
this.model.getTitle(),
]),
hideCancel: true,
});
},
toggleVerified() {
this.$('button.verify').attr('disabled', true);
this.model
.toggleVerified()
.catch(result => {
if (result instanceof Error) {
if (result.name === 'OutgoingIdentityKeyError') {
this.onSafetyNumberChanged();
} else {
window.log.error(
'failed to toggle verified:',
result && result.stack ? result.stack : result
);
}
} else {
const keyError = _.some(
result.errors,
error => error.name === 'OutgoingIdentityKeyError'
);
if (keyError) {
this.onSafetyNumberChanged();
} else {
_.forEach(result.errors, error => {
window.log.error(
'failed to toggle verified:',
error && error.stack ? error.stack : error
);
});
}
}
})
.then(() => {
this.$('button.verify').removeAttr('disabled');
});
},
render_attributes() {
const s = this.securityNumber;
const chunks = [];
for (let i = 0; i < s.length; i += 5) {
chunks.push(s.substring(i, i + 5));
}
const name = this.model.getTitle();
const yourSafetyNumberWith = i18n(
'yourSafetyNumberWith',
this.model.getTitle()
);
const isVerified = this.model.isVerified();
const verifyButton = isVerified ? i18n('unverify') : i18n('verify');
const verifiedStatus = isVerified
? i18n('isVerified', name)
: i18n('isNotVerified', name);
return {
learnMore: i18n('learnMore'),
theirKeyUnknown: i18n('theirIdentityUnknown'),
yourSafetyNumberWith,
verifyHelp: i18n('verifyHelp', this.model.getTitle()),
verifyButton,
hasTheirKey: this.theirKey !== undefined,
chunks,
isVerified,
verifiedStatus,
};
},
});
})();

@ -32,7 +32,7 @@ window.Signal = {
Components: {
SessionPasswordPrompt,
},
}
};
window.CONSTANTS = {
MAX_LOGIN_TRIES: 3,

@ -67,77 +67,6 @@
background-color: $color-white;
}
.key-verification {
label {
display: block;
margin: 10px 0;
font-size: $font-size-small;
}
.icon {
height: 1.25em;
width: 1.25em;
vertical-align: text-bottom;
display: inline-block;
&.verified {
@include color-svg('../images/verified-check.svg', $color-light-90);
}
&.shield {
@include color-svg('../images/shield.svg', $color-light-90);
}
}
.key,
.placeholder {
padding: 0 1em;
-webkit-user-select: text;
}
.key {
font-family: monospace;
padding: 10px;
margin: 20px auto 20px auto;
width: 16em;
background: $grey_l;
border: solid 1px $grey_l2;
border-radius: $border-radius;
}
.placeholder {
font-weight: bold;
}
.qr {
border-radius: 200px;
border: solid 1px $grey_l2;
width: 150px;
height: 150px;
text-align: center;
padding: 25px;
margin: 10px auto;
canvas {
display: none;
}
img {
display: inline-block;
max-width: 100%;
}
}
.summary {
margin: 30px 0 10px;
text-align: center;
}
div.verify {
text-align: center;
}
button.verify {
border-radius: 5px;
font-weight: bold;
padding: 10px;
margin: 0;
}
}
.message-container,
.message-list {
list-style: none;

@ -30,6 +30,10 @@ div.spacer-lg {
width: 100%;
}
.break-word {
word-break: break-all;
}
input,
textarea {
caret-color: $session-color-green !important;
@ -643,12 +647,6 @@ label {
}
&__secret-words {
display: flex;
flex-direction: column;
align-items: center;
background-color: $session-shade-6;
padding: $session-margin-sm $session-margin-lg;
border-radius: 3px;
margin-bottom: $session-margin-md;
}
}
@ -1626,3 +1624,75 @@ input {
pointer-events: none;
}
}
.session-info-box {
display: flex;
flex-direction: column;
align-items: center;
background-color: $session-shade-6;
padding: $session-margin-sm $session-margin-lg;
border-radius: 4px;
}
/* ************************************* */
/* KEY VERIFICATION VIEW (SAFETY NUMBER) */
/* ************************************* */
.key-verification {
display: flex;
flex-direction: column;
align-items: center;
padding-top: $session-margin-lg;
text-align: center;
&__header {
word-break: break-all;
h2 {
margin-bottom: 0px;
}
small {
margin-top: -25px;
opacity: 0.6;
}
}
&__key {
font-family: $session-font-mono;
margin: 30px 0;
width: 250px;
}
&__is-verified {
display: flex;
flex-direction: column;
align-items: center;
font-size: $session-font-md;
margin: 30px 0;
& > span {
svg {
min-width: 30px;
margin-right: 10px;
}
height: 50px;
display: inline-flex;
align-items: center;
}
.session-button {
margin: 20px 0;
width: 100%;
}
}
.session-loader {
margin-top: $session-margin-md;
}
}
/* ************************************* */
/* ************************************* */
/* ************************************* */

@ -132,12 +132,47 @@ $composition-container-height: 60px;
background-color: $session-shade-2;
}
.conversation-content {
display: flex;
flex-grow: 1;
flex-direction: column;
position: relative;
background-color: $session-background;
.conversation-messages {
display: flex;
flex-direction: column;
flex-grow: 1;
position: absolute;
height: 100%;
width: 100%;
background-color: inherit;
}
.conversation-info-panel {
position: absolute;
justify-content: flex-start;
flex-direction: column;
align-items: center;
height: 100%;
width: 100%;
z-index: 10;
background-color: inherit;
display: none;
padding: 20px;
&.show {
display: flex;
}
}
}
.messages-wrapper {
display: flex;
flex-grow: 1;
flex-direction: column;
position: relative;
height: 0px;
height: 100%;
&--blocking-overlay {
background-color: rgba(0, 0, 0, 0.8);
@ -213,10 +248,12 @@ $composition-container-height: 60px;
}
.send-message-input {
cursor: text;
display: flex;
align-items: center;
flex-grow: 1;
min-height: $composition-container-height;
padding: $session-margin-md 0;
textarea {
font-family: $session-font-default;
@ -467,7 +504,7 @@ $rhap_font-family: inherit !default;
transition: fill $session-transition-duration;
&:hover path {
fill: #FFFFFF;
fill: #ffffff;
}
}
}
@ -533,7 +570,7 @@ $rhap_font-family: inherit !default;
margin-left: -10px;
background: $session-color-green;
box-shadow: none;
box-shadow: rgba($rhap_theme-color, .5) 0 0 5px;
box-shadow: rgba($rhap_theme-color, 0.5) 0 0 5px;
}
.rhap_controls-section {

@ -77,13 +77,7 @@
padding-top: 20px;
&__secret-words {
display: flex;
flex-direction: column;
align-items: center;
background-color: $session-shade-6;
padding: $session-margin-sm $session-margin-lg;
border-radius: 8px;
margin-bottom: 0px;
label {
margin-bottom: 5px;

@ -15,24 +15,6 @@ body.dark-theme {
background-color: $color-gray-95;
}
.key-verification {
.key {
color: $color-dark-05;
background: $color-dark-85;
border: solid 1px $color-dark-60;
border-radius: $border-radius;
}
.icon {
&.verified {
@include color-svg('../images/verified-check.svg', $color-dark-05);
}
&.shield {
@include color-svg('../images/shield.svg', $color-dark-05);
}
}
}
.bottom-bar {
form.send {
&.video-attachment {
@ -729,9 +711,9 @@ body.dark-theme {
}
.module-message__text {
color: #FFFFFF;
color: #ffffff;
a {
color: #FFFFFF;
color: #ffffff;
}
}

@ -203,37 +203,6 @@
</div>
</script>
<script type="text/x-tmpl-mustache" id="key-verification">
<div class="container">
{{ ^hasTheirKey }}
<div class="placeholder">{{ theirKeyUnknown }}</div>
{{ /hasTheirKey }}
{{ #hasTheirKey }}
<label> {{ yourSafetyNumberWith }} </label>
<!--<div class="qr"></div>-->
<div class="key">
{{ #chunks }} <span>{{ . }}</span> {{ /chunks }}
</div>
{{ /hasTheirKey }}
{{ verifyHelp }}
<p> {{> link_to_support }} </p>
<div class="summary">
{{ #isVerified }}
<span class="icon verified"></span>
{{ /isVerified }}
{{ ^isVerified }}
<span class="icon shield"></span>
{{ /isVerified }}
{{ verifiedStatus }}
</div>
<div class="verify">
<button class="verify grey">
{{ verifyButton }}
</button>
</div>
</div>
</script>
<script type="text/x-tmpl-mustache" id="clear-data">
{{#isStep1}}
<div class="step">
@ -525,9 +494,7 @@
<script type="text/javascript" src="../js/views/session_confirm_view.js"></script>
<script type="text/javascript" src="../js/views/file_input_view.js"></script>
<script type="text/javascript" src="../js/views/list_view.js"></script>
<script type="text/javascript" src="../js/views/contact_list_view.js"></script>
<script type="text/javascript" src="../js/views/message_view.js"></script>
<script type="text/javascript" src="../js/views/key_verification_view.js"></script>
<script type="text/javascript" src="../js/views/message_list_view.js"></script>
<script type="text/javascript" src="../js/views/member_list_view.js"></script>
<script type="text/javascript" src="../js/views/bulk_edit_view.js"></script>

@ -52,8 +52,6 @@ type PropsHousekeeping = {
onDeleteMessages?: () => void;
onDeleteContact?: () => void;
onBlockContact?: () => void;
onChangeNickname?: () => void;
onClearNickname?: () => void;
onCopyPublicKey?: () => void;
onUnblockContact?: () => void;
};
@ -166,8 +164,6 @@ export class ConversationListItem extends React.PureComponent<Props> {
onDeleteContact,
onDeleteMessages,
onBlockContact,
onChangeNickname,
onClearNickname,
onCopyPublicKey,
onUnblockContact,
} = this.props;
@ -180,14 +176,6 @@ export class ConversationListItem extends React.PureComponent<Props> {
{!isPublic && !isRss && !isMe ? (
<MenuItem onClick={blockHandler}>{blockTitle}</MenuItem>
) : null}
{/* {!isPublic && !isRss && !isMe ? (
<MenuItem onClick={onChangeNickname}>
{i18n('changeNickname')}
</MenuItem>
) : null} */}
{!isPublic && !isRss && !isMe && hasNickname ? (
<MenuItem onClick={onClearNickname}>{i18n('clearNickname')}</MenuItem>
) : null}
{!isPublic && !isRss ? (
<MenuItem onClick={onCopyPublicKey}>{i18n('copyPublicKey')}</MenuItem>
) : null}

@ -4,6 +4,7 @@ import { QRCode } from 'react-qr-svg';
import { SessionModal } from './session/SessionModal';
import { SessionButton, SessionButtonColor } from './session/SessionButton';
import { SessionSpinner } from './session/SessionSpinner';
import classNames from 'classnames';
interface Props {
onClose: any;
@ -119,7 +120,12 @@ export class DevicePairingDialog extends React.Component<Props, State> {
</h4>
{this.renderErrors()}
<div className="device-pairing-dialog__secret-words">
<div
className={classNames(
'device-pairing-dialog__secret-words',
'session-info-box'
)}
>
<label>{window.i18n('secretWords')}</label>
<div className="subtle">{secretWords}</div>
</div>

@ -73,7 +73,6 @@ interface Props {
onCloseOverlay: () => void;
onDeleteSelectedMessages: () => void;
onMoveToInbox: () => void;
onShowSafetyNumber: () => void;
onShowAllMedia: () => void;
onShowGroupMembers: () => void;
@ -82,9 +81,6 @@ interface Props {
onBlockUser: () => void;
onUnblockUser: () => void;
onClearNickname: () => void;
onChangeNickname: () => void;
onCopyPublicKey: () => void;
onLeaveGroup: () => void;

@ -103,7 +103,9 @@ export class CreateGroupDialog extends React.Component<Props, State> {
>
<div className="spacer-lg" />
{this.state.errorDisplayed && <p className={errorMessageClasses}>{this.state.errorMessage}</p>}
{this.state.errorDisplayed && (
<p className={errorMessageClasses}>{this.state.errorMessage}</p>
)}
<input
type="text"
id="group-name"

@ -449,7 +449,12 @@ export class RegistrationTabs extends React.Component<{}, State> {
</div>
{this.renderEnterSessionID(!this.state.secretWords)}
{this.state.secretWords && (
<div className="session-registration__content__secret-words">
<div
className={classNames(
'session-registration__content__secret-words',
'session-info-box'
)}
>
<label>Secret words</label>
<div className="subtle">{this.state.secretWords}</div>
</div>

@ -1,6 +1,7 @@
import React from 'react';
import { SessionModal } from './SessionModal';
import { SessionButton, SessionButtonColor } from './SessionButton';
import classNames from 'classnames';
interface Props {
message: string;
@ -15,6 +16,7 @@ interface Props {
hideCancel: boolean;
okTheme: SessionButtonColor;
closeTheme: SessionButtonColor;
centeredText?: boolean;
}
export class SessionConfirm extends React.Component<Props> {
@ -40,6 +42,7 @@ export class SessionConfirm extends React.Component<Props> {
onClickOk,
onClickClose,
hideCancel,
centeredText,
} = this.props;
const okText = this.props.okText || window.i18n('ok');
@ -63,7 +66,12 @@ export class SessionConfirm extends React.Component<Props> {
<div className="session-modal__centered">
<span className={messageSubText}>{message}</span>
{messageSub && (
<span className="session-confirm-sub-message subtle">
<span
className={classNames(
'session-confirm-sub-message subtle',
centeredText && 'text-center'
)}
>
{messageSub}
</span>
)}

@ -0,0 +1,248 @@
import React from 'react';
import * as _ from 'lodash';
import {
SessionButton,
SessionButtonColor,
SessionButtonType,
} from './SessionButton';
import { UserUtil } from '../../util';
import { MultiDeviceProtocol } from '../../session/protocols';
import { PubKey } from '../../session/types';
import { ConversationModel } from '../../../js/models/conversations';
import { SessionSpinner } from './SessionSpinner';
import classNames from 'classnames';
import { SessionIcon, SessionIconSize, SessionIconType } from './icon';
interface Props {
conversation: ConversationModel;
}
interface State {
loading: boolean;
error?: 'verificationKeysLoadFail';
securityNumber?: string;
isVerified?: boolean;
}
export class SessionKeyVerification extends React.Component<Props, State> {
constructor(props: any) {
super(props);
this.state = {
loading: true,
error: undefined,
securityNumber: undefined,
isVerified: this.props.conversation.isVerified(),
};
this.toggleVerification = this.toggleVerification.bind(this);
this.onSafetyNumberChanged = this.onSafetyNumberChanged.bind(this);
}
public async componentWillMount() {
const securityNumber = await this.generateSecurityNumber();
if (!securityNumber) {
this.setState({
error: 'verificationKeysLoadFail',
});
return;
}
// Finished loading
this.setState({
loading: false,
securityNumber,
});
}
public render() {
const theirName = this.props.conversation.attributes.profileName;
const theirPubkey = this.props.conversation.id;
const isVerified = this.props.conversation.isVerified();
if (this.state.loading) {
return (
<div className="key-verification">
<SessionSpinner loading={this.state.loading} />
</div>
);
}
const verificationIconColor = isVerified ? '#00f782' : '#ff453a';
const verificationButtonColor = isVerified
? SessionButtonColor.Warning
: SessionButtonColor.Success;
const verificationButton = (
<SessionButton
buttonType={SessionButtonType.DefaultOutline}
buttonColor={verificationButtonColor}
onClick={this.toggleVerification}
>
{window.i18n(isVerified ? 'unverify' : 'verify')}
</SessionButton>
);
return (
<div className="key-verification">
{this.state.error ? (
<h3>{window.i18n(this.state.error)}</h3>
) : (
<>
<div className={classNames('key-verification__header')}>
<h2>{window.i18n('safetyNumber')}</h2>
<small>{theirPubkey}</small>
</div>
<div
className={classNames(
'key-verification__key',
'session-info-box'
)}
>
{this.renderSecurityNumber()}
</div>
<div className="key-verification__help">
{window.i18n('verifyHelp', theirName)}
</div>
<div className="key-verification__is-verified">
<span>
<SessionIcon
iconType={SessionIconType.Lock}
iconSize={SessionIconSize.Large}
iconColor={verificationIconColor}
/>
{window.i18n(
isVerified ? 'isVerified' : 'isNotVerified',
theirName
)}
</span>
{verificationButton}
</div>
</>
)}
</div>
);
}
public async onSafetyNumberChanged() {
const conversationModel = this.props.conversation;
await conversationModel.getProfiles();
const securityNumber = await this.generateSecurityNumber();
this.setState({ securityNumber });
window.confirmationDialog({
title: window.i18n('changedSinceVerifiedTitle'),
message: window.i18n('changedRightAfterVerify', [
conversationModel.attributes.profileName,
conversationModel.attributes.profileName,
]),
hideCancel: true,
centeredText: true,
});
}
private async generateSecurityNumber(): Promise<string | undefined> {
const ourDeviceKey = await UserUtil.getCurrentDevicePubKey();
if (!ourDeviceKey) {
this.setState({
error: 'verificationKeysLoadFail',
});
return;
}
const conversationId = this.props.conversation.id;
const ourPrimaryKey = (
await MultiDeviceProtocol.getPrimaryDevice(PubKey.cast(ourDeviceKey))
).key;
// Grab identity keys
const ourIdentityKey = await window.textsecure.storage.protocol.loadIdentityKey(
ourPrimaryKey
);
const theirIdentityKey = await window.textsecure.storage.protocol.loadIdentityKey(
this.props.conversation.id
);
if (!ourIdentityKey || !theirIdentityKey) {
return;
}
// Generate security number
const fingerprintGenerator = new window.libsignal.FingerprintGenerator(
5200
);
return fingerprintGenerator.createFor(
ourPrimaryKey,
ourIdentityKey,
conversationId,
theirIdentityKey
);
}
private async toggleVerification() {
const conversationModel = this.props.conversation;
try {
await conversationModel.toggleVerified();
this.setState({ isVerified: !this.state.isVerified });
await conversationModel.getProfiles();
} catch (e) {
if (e instanceof Error) {
if (e.name === 'OutgoingIdentityKeyError') {
await this.onSafetyNumberChanged();
} else {
window.log.error(
'failed to toggle verified:',
e && e.stack ? e.stack : e
);
}
} else {
const keyError = _.some(
e.errors,
error => error.name === 'OutgoingIdentityKeyError'
);
if (keyError) {
await this.onSafetyNumberChanged();
} else {
_.forEach(e.errors, error => {
window.log.error(
'failed to toggle verified:',
error && error.stack ? error.stack : error
);
});
}
}
}
}
private renderSecurityNumber(): Array<JSX.Element> | undefined {
// Turns 32813902154726601686003948952478 ...
// into 32813 90215 47266 ...
const { loading, securityNumber } = this.state;
if (loading) {
return;
}
const securityNumberChunks = _.chunk(
Array.from(securityNumber ?? []),
5
).map(chunk => chunk.join(''));
const securityNumberLines = _.chunk(securityNumberChunks, 4).map(chunk =>
chunk.join(' ')
);
const securityNumberElement = securityNumberLines.map(line => (
<div key={line}>{line}</div>
));
return securityNumberElement;
}
}

@ -178,7 +178,11 @@ export class SessionCompositionBox extends React.Component<Props, State> {
onClick={this.onLoadVoiceNoteView}
/>
<div className="send-message-input" role="main" onClick={this.focusCompositionBox}>
<div
className="send-message-input"
role="main"
onClick={this.focusCompositionBox}
>
<TextareaAutosize
rows={1}
maxRows={3}
@ -414,5 +418,4 @@ export class SessionCompositionBox extends React.Component<Props, State> {
// Focus the textarea when user clicks anywhere in the composition box
this.textarea.current?.focus();
}
}

@ -18,10 +18,11 @@ import { SessionGroupSettings } from './SessionGroupSettings';
import { ResetSessionNotification } from '../../conversation/ResetSessionNotification';
import { Constants, getMessageQueue } from '../../../session';
import { MessageQueue } from '../../../session/sending';
import { SessionKeyVerification } from '../SessionKeyVerification';
interface State {
conversationKey: string;
// Message sending progress
messageProgressVisible: boolean;
sendingProgress: number;
@ -45,6 +46,9 @@ interface State {
showOptionsPane: boolean;
showScrollButton: boolean;
// For displaying `More Info` on messages, and `Safety Number`, etc.
infoViewState?: 'safetyNumber' | 'messageDetails';
// dropZoneFiles?: FileList
dropZoneFiles: any;
}
@ -82,6 +86,8 @@ export class SessionConversation extends React.Component<any, State> {
showOptionsPane: false,
showScrollButton: false,
infoViewState: undefined,
dropZoneFiles: undefined, // <-- FileList or something else?
};
@ -184,6 +190,9 @@ export class SessionConversation extends React.Component<any, State> {
!conversationModel.isPrivate() && !conversationModel.isRss();
const groupSettingsProps = this.getGroupSettingsProps();
const showSafetyNumber = this.state.infoViewState === 'safetyNumber';
const showMessageDetails = this.state.infoViewState === 'messageDetails';
return (
<>
<div
@ -205,38 +214,54 @@ export class SessionConversation extends React.Component<any, State> {
resetProgress={this.resetSendingProgress}
/> */}
<div className="messages-wrapper">
{loading && <div className="messages-container__loading" />}
<div className="conversation-content">
<div
className="messages-container"
onScroll={this.handleScroll}
ref={this.messageContainerRef}
className={classNames(
'conversation-info-panel',
this.state.infoViewState && 'show'
)}
>
{this.renderMessages()}
<div ref={this.messagesEndRef} />
{showSafetyNumber && (
<SessionKeyVerification conversation={conversationModel} />
)}
{showMessageDetails && <>&nbsp</>}
</div>
<SessionScrollButton
show={showScrollButton}
onClick={this.scrollToBottom}
/>
{showRecordingView && (
<div className="messages-wrapper--blocking-overlay" />
)}
<div className="conversation-messages">
<div className="messages-wrapper">
{loading && <div className="messages-container__loading" />}
<div
className="messages-container"
onScroll={this.handleScroll}
ref={this.messageContainerRef}
>
{this.renderMessages()}
<div ref={this.messagesEndRef} />
</div>
<SessionScrollButton
show={showScrollButton}
onClick={this.scrollToBottom}
/>
{showRecordingView && (
<div className="messages-wrapper--blocking-overlay" />
)}
</div>
{!isRss && (
<SessionCompositionBox
sendMessage={sendMessageFn}
dropZoneFiles={this.state.dropZoneFiles}
onMessageSending={this.onMessageSending}
onMessageSuccess={this.onMessageSuccess}
onMessageFailure={this.onMessageFailure}
onLoadVoiceNoteView={this.onLoadVoiceNoteView}
onExitVoiceNoteView={this.onExitVoiceNoteView}
/>
)}
</div>
</div>
{!isRss && (
<SessionCompositionBox
sendMessage={sendMessageFn}
dropZoneFiles={this.state.dropZoneFiles}
onMessageSending={this.onMessageSending}
onMessageSuccess={this.onMessageSuccess}
onMessageFailure={this.onMessageFailure}
onLoadVoiceNoteView={this.onLoadVoiceNoteView}
onExitVoiceNoteView={this.onExitVoiceNoteView}
/>
)}
</div>
{shouldRenderGroupSettings && (
@ -311,55 +336,7 @@ export class SessionConversation extends React.Component<any, State> {
public renderHeader() {
const headerProps = this.getHeaderProps();
return (
<ConversationHeader
id={headerProps.id}
name={headerProps.name}
phoneNumber={headerProps.phoneNumber}
profileName={headerProps.profileName}
avatarPath={headerProps.avatarPath}
isVerified={headerProps.isVerified}
isMe={headerProps.isMe}
isClosable={headerProps.isClosable}
isGroup={headerProps.isGroup}
isPublic={headerProps.isPublic}
isRss={headerProps.isRss}
amMod={headerProps.amMod}
members={headerProps.members}
subscriberCount={headerProps.subscriberCount}
expirationSettingName={headerProps.expirationSettingName}
showBackButton={headerProps.showBackButton}
timerOptions={headerProps.timerOptions}
hasNickname={headerProps.hasNickname}
isBlocked={headerProps.isBlocked}
isOnline={headerProps.isOnline}
selectedMessages={headerProps.selectedMessages}
isKickedFromGroup={headerProps.isKickedFromGroup}
onInviteContacts={headerProps.onInviteContacts}
onSetDisappearingMessages={headerProps.onSetDisappearingMessages}
onDeleteMessages={headerProps.onDeleteMessages}
onDeleteContact={headerProps.onDeleteContact}
onResetSession={headerProps.onResetSession}
onCloseOverlay={headerProps.onCloseOverlay}
onDeleteSelectedMessages={headerProps.onDeleteSelectedMessages}
onMoveToInbox={headerProps.onMoveToInbox}
onShowSafetyNumber={headerProps.onShowSafetyNumber}
onShowAllMedia={headerProps.onShowAllMedia}
onShowGroupMembers={headerProps.onShowGroupMembers}
onGoBack={headerProps.onGoBack}
onBlockUser={headerProps.onBlockUser}
onUnblockUser={headerProps.onUnblockUser}
onClearNickname={headerProps.onClearNickname}
onChangeNickname={headerProps.onChangeNickname}
onCopyPublicKey={headerProps.onCopyPublicKey}
onLeaveGroup={headerProps.onLeaveGroup}
onAddModerators={headerProps.onAddModerators}
onRemoveModerators={headerProps.onRemoveModerators}
onAvatarClick={headerProps.onAvatarClick}
onUpdateGroupName={headerProps.onUpdateGroupName}
i18n={window.i18n}
/>
);
return <ConversationHeader {...headerProps} />;
}
public renderMessage(
@ -454,6 +431,7 @@ export class SessionConversation extends React.Component<any, State> {
const members = conversation.get('members') || [];
const headerProps = {
i18n: window.i18n,
id: conversation.id,
name: conversation.getName(),
phoneNumber: conversation.getNumber(),
@ -476,9 +454,7 @@ export class SessionConversation extends React.Component<any, State> {
selectedMessages: this.state.selectedMessages,
isKickedFromGroup: conversation.get('isKickedFromGroup'),
expirationSettingName,
showBackButton: Boolean(
conversation.panels && conversation.panels.length
),
showBackButton: Boolean(this.state.infoViewState),
timerOptions: window.Whisper.ExpirationTimerOptions.map((item: any) => ({
name: item.getName(),
value: item.get('seconds'),
@ -495,11 +471,14 @@ export class SessionConversation extends React.Component<any, State> {
conversation.endSession();
},
// These are view only and don't update the Conversation model, so they
// need a manual update call.
onShowSafetyNumber: () => {
conversation.showSafetyNumber();
this.setState({ infoViewState: 'safetyNumber' });
},
onGoBack: () => {
this.setState({ infoViewState: undefined });
},
onShowAllMedia: async () => {
conversation.updateHeader();
},
@ -507,11 +486,7 @@ export class SessionConversation extends React.Component<any, State> {
conversation.onUpdateGroupName();
},
onShowGroupMembers: async () => {
await conversation.showMembers();
conversation.updateHeader();
},
onGoBack: () => {
conversation.resetPanel();
window.Whisper.events.trigger('updateGroupMembers', conversation);
conversation.updateHeader();
},
@ -521,24 +496,14 @@ export class SessionConversation extends React.Component<any, State> {
onUnblockUser: () => {
conversation.unblock();
},
onChangeNickname: () => {
conversation.changeNickname();
},
onClearNickname: () => {
conversation.setNickname(null);
},
onCopyPublicKey: () => {
conversation.copyPublicKey();
},
onMoveToInbox: () => {
conversation.setArchived(false);
},
onLeaveGroup: () => {
window.Whisper.events.trigger('leaveGroup', conversation);
},
onInviteContacts: () => {
// VINCE TODO: Inviting contacts ⚡️
return;
window.Whisper.events.trigger('inviteContacts', conversation);
},
onAddModerators: () => {
@ -654,7 +619,6 @@ export class SessionConversation extends React.Component<any, State> {
public onMessageSuccess() {
this.updateSendingProgress(100, 2);
}
public onMessageFailure() {

@ -27,9 +27,7 @@ export class SessionEmojiPanel extends React.Component<Props, State> {
return (
<div className={classNames('session-emoji-panel', show && 'show')}>
<Picker
backgroundImageFn={() =>
'./images/emoji/emoji-sheet-twitter-32.png'
}
backgroundImageFn={() => './images/emoji/emoji-sheet-twitter-32.png'}
set={'twitter'}
sheetSize={32}
darkMode={true}

@ -417,8 +417,6 @@ export class SessionRecording extends React.Component<Props, State> {
}
private onSendVoiceMessage() {
console.log(`[vince][mic] Sending voice message to composition box1`);
const audioBlob = this.state.mediaBlob.data;
if (!audioBlob) {
return;
@ -711,10 +709,9 @@ export class SessionRecording extends React.Component<Props, State> {
});
}
private onKeyDown(event: any) {
private async onKeyDown(event: any) {
if (event.key === 'Escape') {
// FIXME VINCE: Add SessionConfirm
this.onDeleteVoiceMessage();
await this.onDeleteVoiceMessage();
}
}
}

@ -1,4 +1,3 @@
export const reducer = (state: any, action: any) => {
console.log(`[vince][redux] Action: `, action);
export const reducer = (state: any, _action: any) => {
return state;
};

@ -381,15 +381,6 @@
"updated": "2018-09-19T21:59:32.770Z",
"reasonDetail": "Protected from arbitrary input"
},
{
"rule": "jQuery-append(",
"path": "js/views/contact_list_view.js",
"line": " this.$el.append(this.contactView.el);",
"lineNumber": 44,
"reasonCategory": "usageTrusted",
"updated": "2018-10-02T21:18:39.026Z",
"reasonDetail": "Operating on previously-existing DOM elements"
},
{
"rule": "jQuery-$(",
"path": "js/views/debug_log_view.js",
@ -740,50 +731,6 @@
"updated": "2018-09-19T21:59:32.770Z",
"reasonDetail": "Protected from arbitrary input"
},
{
"rule": "jQuery-$(",
"path": "js/views/key_verification_view.js",
"line": " new QRCode(this.$('.qr')[0]).makeCode(",
"lineNumber": 36,
"reasonCategory": "usageTrusted",
"updated": "2018-09-19T21:59:32.770Z",
"reasonDetail": "Protected from arbitrary input"
},
{
"rule": "jQuery-wrap(",
"path": "js/views/key_verification_view.js",
"line": " dcodeIO.ByteBuffer.wrap(this.ourKey).toString('base64')",
"lineNumber": 37,
"reasonCategory": "falseMatch",
"updated": "2018-09-19T18:13:29.628Z"
},
{
"rule": "jQuery-insertBefore(",
"path": "js/views/key_verification_view.js",
"line": " dialog.$el.insertBefore(this.el);",
"lineNumber": 72,
"reasonCategory": "usageTrusted",
"updated": "2018-09-19T18:13:29.628Z",
"reasonDetail": "Interacting with already-existing DOM nodes"
},
{
"rule": "jQuery-$(",
"path": "js/views/key_verification_view.js",
"line": " this.$('button.verify').attr('disabled', true);",
"lineNumber": 76,
"reasonCategory": "usageTrusted",
"updated": "2018-09-19T21:59:32.770Z",
"reasonDetail": "Protected from arbitrary input"
},
{
"rule": "jQuery-$(",
"path": "js/views/key_verification_view.js",
"line": " this.$('button.verify').removeAttr('disabled');",
"lineNumber": 107,
"reasonCategory": "usageTrusted",
"updated": "2018-09-19T21:59:32.770Z",
"reasonDetail": "Protected from arbitrary input"
},
{
"rule": "jQuery-append(",
"path": "js/views/list_view.js",

Loading…
Cancel
Save