Support new 'requiredProtocolVersion' in DataMessage

* Add new requiredProtocolVersion field to DataMessage

* Message.requiredProtocolVersion, warning if version mot supported

* Update strings; limit width; new left pane preview text
This commit is contained in:
Scott Nonnenberg 2019-06-10 14:40:02 -07:00 committed by Ken Powers
parent dd98477479
commit 9fd867fdd1
13 changed files with 355 additions and 24 deletions

View file

@ -1740,6 +1740,11 @@
"description": "description":
"Shown in notifications and in the left pane instead of sticker image." "Shown in notifications and in the left pane instead of sticker image."
}, },
"message--getDescription--unsupported-message": {
"message": "Unsupported message",
"description":
"Shown in notifications and in the left pane when a message has features too new for this signal install."
},
"stickers--toast--InstallFailed": { "stickers--toast--InstallFailed": {
"message": "Sticker pack could not be installed", "message": "Sticker pack could not be installed",
"description": "description":
@ -1863,5 +1868,38 @@
"confirmation-dialog--Cancel": { "confirmation-dialog--Cancel": {
"message": "Cancel", "message": "Cancel",
"description": "Appears on the cancel button in confirmation dialogs." "description": "Appears on the cancel button in confirmation dialogs."
},
"Message--unsupported-message": {
"message":
"$contact$ sent you a message that can't be processed or displayed because it uses a new Signal feature.",
"placeholders": {
"contact": {
"content": "$1",
"example": "Alice"
}
}
},
"Message--unsupported-message-ask-to-resend": {
"message":
"You can ask $contact$ to re-send this message now that you are using an up-to-date version of Signal.",
"placeholders": {
"contact": {
"content": "$1",
"example": "Alice"
}
}
},
"Message--from-me-unsupported-message": {
"message":
"One of your devices sent a message that can't be processed or displayed because it uses a new Signal feature."
},
"Message--from-me-unsupported-message-ask-to-resend": {
"message":
"Youve updated to the latest version of Signal and will now receive this message type on your device."
},
"Message--update-signal": {
"message": "Update Signal",
"description":
"Text for a button which will take user to Signal download page"
} }
} }

View file

@ -87,6 +87,10 @@
); );
} }
this.CURRENT_PROTOCOL_VERSION =
textsecure.protobuf.DataMessage.ProtocolVersion.CURRENT;
this.INITIAL_PROTOCOL_VERSION =
textsecure.protobuf.DataMessage.ProtocolVersion.INITIAL;
this.OUR_NUMBER = textsecure.storage.user.getNumber(); this.OUR_NUMBER = textsecure.storage.user.getNumber();
this.on('destroy', this.onDestroy); this.on('destroy', this.onDestroy);
@ -122,7 +126,12 @@
// Top-level prop generation for the message bubble // Top-level prop generation for the message bubble
generateProps() { generateProps() {
if (this.isExpirationTimerUpdate()) { if (this.isUnsupportedMessage()) {
this.props = {
type: 'unsupportedMessage',
data: this.getPropsForUnsupportedMessage(),
};
} else if (this.isExpirationTimerUpdate()) {
this.props = { this.props = {
type: 'timerNotification', type: 'timerNotification',
data: this.getPropsForTimerNotification(), data: this.getPropsForTimerNotification(),
@ -267,6 +276,16 @@
}, },
// Bucketing messages // Bucketing messages
isUnsupportedMessage() {
const versionAtReceive = this.get('supportedVersionAtReceive');
const requiredVersion = this.get('requiredProtocolVersion');
return (
_.isNumber(versionAtReceive) &&
_.isNumber(requiredVersion) &&
versionAtReceive < requiredVersion
);
},
isExpirationTimerUpdate() { isExpirationTimerUpdate() {
const flag = const flag =
textsecure.protobuf.DataMessage.Flags.EXPIRATION_TIMER_UPDATE; textsecure.protobuf.DataMessage.Flags.EXPIRATION_TIMER_UPDATE;
@ -289,6 +308,16 @@
}, },
// Props for each message type // Props for each message type
getPropsForUnsupportedMessage() {
const requiredVersion = this.get('requiredProtocolVersion');
const canProcessNow = this.CURRENT_PROTOCOL_VERSION >= requiredVersion;
const phoneNumber = this.getSource();
return {
canProcessNow,
contact: this.findAndFormatContact(phoneNumber),
};
},
getPropsForTimerNotification() { getPropsForTimerNotification() {
const timerUpdate = this.get('expirationTimerUpdate'); const timerUpdate = this.get('expirationTimerUpdate');
if (!timerUpdate) { if (!timerUpdate) {
@ -479,6 +508,7 @@
this.trigger('download', downloadOptions), this.trigger('download', downloadOptions),
openLink: url => this.trigger('navigate-to', url), openLink: url => this.trigger('navigate-to', url),
downloadNewVersion: () => this.trigger('download-new-version'),
scrollToMessage: scrollOptions => scrollToMessage: scrollOptions =>
this.trigger('scroll-to-message', scrollOptions), this.trigger('scroll-to-message', scrollOptions),
}; };
@ -694,6 +724,9 @@
// More display logic // More display logic
getDescription() { getDescription() {
if (this.isUnsupportedMessage()) {
return i18n('message--getDescription--unsupported-message');
}
if (this.isGroupUpdate()) { if (this.isGroupUpdate()) {
const groupUpdate = this.get('group_update'); const groupUpdate = this.get('group_update');
if (groupUpdate.left === 'You') { if (groupUpdate.left === 'You') {
@ -1733,6 +1766,10 @@
hasFileAttachments: dataMessage.hasFileAttachments, hasFileAttachments: dataMessage.hasFileAttachments,
hasVisualMediaAttachments: dataMessage.hasVisualMediaAttachments, hasVisualMediaAttachments: dataMessage.hasVisualMediaAttachments,
preview, preview,
requiredProtocolVersion:
dataMessage.requiredProtocolVersion ||
this.INITIAL_PROTOCOL_VERSION,
supportedVersionAtReceive: this.CURRENT_PROTOCOL_VERSION,
quote: dataMessage.quote, quote: dataMessage.quote,
schemaVersion: dataMessage.schemaVersion, schemaVersion: dataMessage.schemaVersion,
sticker: dataMessage.sticker, sticker: dataMessage.sticker,
@ -1888,10 +1925,12 @@
message.set({ id }); message.set({ id });
MessageController.register(message.id, message); MessageController.register(message.id, message);
// Note that this can save the message again, if jobs were queued. We need to if (!message.isUnsupportedMessage()) {
// call it after we have an id for this message, because the jobs refer back // Note that this can save the message again, if jobs were queued. We need to
// to their source message. // call it after we have an id for this message, because the jobs refer back
await message.queueAttachmentDownloads(); // to their source message.
await message.queueAttachmentDownloads();
}
await window.Signal.Data.updateConversation( await window.Signal.Data.updateConversation(
conversationId, conversationId,

View file

@ -65,6 +65,9 @@ const {
const { const {
TypingBubble, TypingBubble,
} = require('../../ts/components/conversation/TypingBubble'); } = require('../../ts/components/conversation/TypingBubble');
const {
UnsupportedMessage,
} = require('../../ts/components/conversation/UnsupportedMessage');
const { const {
VerificationNotification, VerificationNotification,
} = require('../../ts/components/conversation/VerificationNotification'); } = require('../../ts/components/conversation/VerificationNotification');
@ -277,6 +280,7 @@ exports.setup = (options = {}) => {
Message: MediaGalleryMessage, Message: MediaGalleryMessage,
}, },
TypingBubble, TypingBubble,
UnsupportedMessage,
VerificationNotification, VerificationNotification,
}; };

View file

@ -144,6 +144,13 @@
this.listenTo(this.model.messageCollection, 'navigate-to', url => { this.listenTo(this.model.messageCollection, 'navigate-to', url => {
window.location = url; window.location = url;
}); });
this.listenTo(
this.model.messageCollection,
'download-new-version',
() => {
window.location = 'https://signal.org/download';
}
);
this.lazyUpdateVerified = _.debounce( this.lazyUpdateVerified = _.debounce(
this.model.updateVerified.bind(this.model), this.model.updateVerified.bind(this.model),

View file

@ -51,7 +51,12 @@
const { Components } = window.Signal; const { Components } = window.Signal;
const { type, data: props } = this.model.props; const { type, data: props } = this.model.props;
if (type === 'timerNotification') { if (type === 'unsupportedMessage') {
return {
Component: Components.UnsupportedMessage,
props,
};
} else if (type === 'timerNotification') {
return { return {
Component: Components.TimerNotification, Component: Components.TimerNotification,
props, props,

View file

@ -169,17 +169,25 @@ message DataMessage {
optional AttachmentPointer data = 4; optional AttachmentPointer data = 4;
} }
optional string body = 1; enum ProtocolVersion {
repeated AttachmentPointer attachments = 2; option allow_alias = true;
optional GroupContext group = 3;
optional uint32 flags = 4; INITIAL = 0;
optional uint32 expireTimer = 5; CURRENT = 0;
optional bytes profileKey = 6; }
optional uint64 timestamp = 7;
optional Quote quote = 8; optional string body = 1;
repeated Contact contact = 9; repeated AttachmentPointer attachments = 2;
repeated Preview preview = 10; optional GroupContext group = 3;
optional Sticker sticker = 11; optional uint32 flags = 4;
optional uint32 expireTimer = 5;
optional bytes profileKey = 6;
optional uint64 timestamp = 7;
optional Quote quote = 8;
repeated Contact contact = 9;
repeated Preview preview = 10;
optional Sticker sticker = 11;
optional uint32 requiredProtocolVersion = 12;
} }
message NullMessage { message NullMessage {

View file

@ -1200,7 +1200,7 @@
font-weight: 300; font-weight: 300;
} }
.module-verification-notification__button { .module-safety-number-notification__button {
margin-top: 5px; margin-top: 5px;
display: inline-block; display: inline-block;
cursor: pointer; cursor: pointer;
@ -4448,6 +4448,79 @@
} }
} }
// Module: Unsupported Message
.module-unsupported-message {
margin-top: 14px;
text-align: center;
}
.module-unsupported-message__icon {
height: 24px;
width: 24px;
margin-left: auto;
margin-right: auto;
margin-bottom: 7px;
@include light-theme {
@include color-svg('../images/error.svg', $color-gray-60);
}
@include dark-theme {
@include color-svg('../images/error.svg', $color-dark-30);
}
}
.module-unsupported-message__icon--can-process {
@include light-theme {
@include color-svg('../images/check-circle-outline.svg', $color-gray-60);
}
@include dark-theme {
@include color-svg('../images/check-circle-outline.svg', $color-dark-30);
}
}
.module-unsupported-message__text {
font-size: 14px;
line-height: 20px;
letter-spacing: 0.3px;
max-width: 396px;
margin-left: auto;
margin-right: auto;
@include light-theme {
color: $color-gray-60;
}
@include dark-theme {
color: $color-dark-30;
}
}
.module-unsupported-message__contact {
font-weight: 300;
}
.module-unsupported-message__button {
margin-top: 5px;
display: inline-block;
cursor: pointer;
font-size: 13px;
font-weight: 300;
line-height: 18px;
padding: 12px;
border-radius: 4px;
@include light-theme {
color: $color-signal-blue;
background-color: $color-light-02;
}
@include dark-theme {
color: $color-signal-blue;
background-color: $color-gray-75;
}
}
// Third-party module: react-contextmenu // Third-party module: react-contextmenu
.react-contextmenu { .react-contextmenu {

View file

@ -1023,7 +1023,7 @@ body.dark-theme {
color: $color-dark-30; color: $color-dark-30;
} }
.module-verification-notification__button { .module-safety-number-notification__button {
color: $color-signal-blue; color: $color-signal-blue;
background-color: $color-gray-75; background-color: $color-gray-75;
} }

View file

@ -1,6 +1,6 @@
### In group conversation ### In group conversation
```js ```jsx
<util.ConversationContext theme={util.theme}> <util.ConversationContext theme={util.theme}>
<SafetyNumberNotification <SafetyNumberNotification
i18n={util.i18n} i18n={util.i18n}
@ -13,7 +13,7 @@
### In one-on-one conversation ### In one-on-one conversation
```js ```jsx
<util.ConversationContext theme={util.theme}> <util.ConversationContext theme={util.theme}>
<SafetyNumberNotification <SafetyNumberNotification
i18n={util.i18n} i18n={util.i18n}

View file

@ -50,7 +50,7 @@ export class SafetyNumberNotification extends React.Component<Props> {
name={contact.name} name={contact.name}
profileName={contact.profileName} profileName={contact.profileName}
phoneNumber={contact.phoneNumber} phoneNumber={contact.phoneNumber}
module="module-verification-notification__contact" module="module-safety-number-notification__contact"
/> />
</span>, </span>,
]} ]}
@ -62,7 +62,7 @@ export class SafetyNumberNotification extends React.Component<Props> {
onClick={() => { onClick={() => {
showIdentity(contact.id); showIdentity(contact.id);
}} }}
className="module-verification-notification__button" className="module-safety-number-notification__button"
> >
{i18n('verifyNewNumber')} {i18n('verifyNewNumber')}
</div> </div>

View file

@ -0,0 +1,69 @@
### From someone in your contacts
```jsx
<util.ConversationContext theme={util.theme}>
<UnsupportedMessage
i18n={util.i18n}
contact={{ phoneNumber: '(202) 500-1000', name: 'Alice' }}
downloadNewVersion={() => console.log('downloadNewVersion')}
/>
</util.ConversationContext>
```
### After you upgrade
```jsx
<util.ConversationContext theme={util.theme}>
<UnsupportedMessage
i18n={util.i18n}
canProcessNow={true}
contact={{ phoneNumber: '(202) 500-1000', name: 'Alice' }}
downloadNewVersion={() => console.log('downloadNewVersion')}
/>
</util.ConversationContext>
```
### No name, just profile
```jsx
<util.ConversationContext theme={util.theme}>
<UnsupportedMessage
i18n={util.i18n}
contact={{ phoneNumber: '(202) 500-1000', profileName: 'Mr. Fire' }}
downloadNewVersion={() => console.log('downloadNewVersion')}
/>
</util.ConversationContext>
```
### From yourself
```jsx
<util.ConversationContext theme={util.theme}>
<UnsupportedMessage
i18n={util.i18n}
contact={{
isMe: true,
phoneNumber: '(202) 500-1000',
profileName: 'Mr. Fire',
}}
downloadNewVersion={() => console.log('downloadNewVersion')}
/>
</util.ConversationContext>
```
### From yourself, after you upgrade
```jsx
<util.ConversationContext theme={util.theme}>
<UnsupportedMessage
i18n={util.i18n}
canProcessNow={true}
contact={{
isMe: true,
phoneNumber: '(202) 500-1000',
profileName: 'Mr. Fire',
}}
downloadNewVersion={() => console.log('downloadNewVersion')}
/>
</util.ConversationContext>
```

View file

@ -0,0 +1,88 @@
import React from 'react';
import classNames from 'classnames';
import { ContactName } from './ContactName';
import { Intl } from '../Intl';
import { LocalizerType } from '../../types/Util';
interface ContactType {
id: string;
phoneNumber: string;
profileName?: string;
name?: string;
isMe: boolean;
}
export type PropsData = {
canProcessNow: boolean;
contact: ContactType;
};
type PropsHousekeeping = {
i18n: LocalizerType;
};
export type PropsActions = {
downloadNewVersion: () => unknown;
};
type Props = PropsData & PropsHousekeeping & PropsActions;
export class UnsupportedMessage extends React.Component<Props> {
public render() {
const { canProcessNow, contact, i18n, downloadNewVersion } = this.props;
const { isMe } = contact;
const otherStringId = canProcessNow
? 'Message--unsupported-message-ask-to-resend'
: 'Message--unsupported-message';
const meStringId = canProcessNow
? 'Message--from-me-unsupported-message-ask-to-resend'
: 'Message--from-me-unsupported-message';
const stringId = isMe ? meStringId : otherStringId;
return (
<div className="module-unsupported-message">
<div
className={classNames(
'module-unsupported-message__icon',
canProcessNow
? 'module-unsupported-message__icon--can-process'
: null
)}
/>
<div className="module-unsupported-message__text">
<Intl
id={stringId}
components={[
<span
key="external-1"
className="module-unsupported-message__contact"
>
<ContactName
i18n={i18n}
name={contact.name}
profileName={contact.profileName}
phoneNumber={contact.phoneNumber}
module="module-unsupported-message__contact"
/>
</span>,
]}
i18n={i18n}
/>
</div>
{canProcessNow ? null : (
<div
role="button"
onClick={() => {
downloadNewVersion();
}}
className="module-unsupported-message__button"
>
{i18n('Message--update-signal')}
</div>
)}
</div>
);
}
}

View file

@ -872,7 +872,7 @@
"rule": "jQuery-append(", "rule": "jQuery-append(",
"path": "js/views/message_view.js", "path": "js/views/message_view.js",
"line": " this.$el.append(this.childView.el);", "line": " this.$el.append(this.childView.el);",
"lineNumber": 139, "lineNumber": 144,
"reasonCategory": "usageTrusted", "reasonCategory": "usageTrusted",
"updated": "2018-09-19T18:13:29.628Z", "updated": "2018-09-19T18:13:29.628Z",
"reasonDetail": "Interacting with already-existing DOM nodes" "reasonDetail": "Interacting with already-existing DOM nodes"