remove regionCode and PhoneNumber.ts

pull/1459/head
Audric Ackermann 4 years ago
parent 2b92386399
commit 02fb5783a4
No known key found for this signature in database
GPG Key ID: 999F434D76324AD4

@ -120,7 +120,6 @@
accountManager = new textsecure.AccountManager(USERNAME, PASSWORD); accountManager = new textsecure.AccountManager(USERNAME, PASSWORD);
accountManager.addEventListener('registration', () => { accountManager.addEventListener('registration', () => {
const user = { const user = {
regionCode: window.storage.get('regionCode'),
ourNumber: textsecure.storage.user.getNumber(), ourNumber: textsecure.storage.user.getNumber(),
ourPrimary: window.textsecure.storage.get('primaryDevicePubKey'), ourPrimary: window.textsecure.storage.get('primaryDevicePubKey'),
}; };

@ -22,7 +22,7 @@
window.Whisper = window.Whisper || {}; window.Whisper = window.Whisper || {};
const { Contact, Conversation, Message, PhoneNumber } = window.Signal.Types; const { Contact, Conversation, Message } = window.Signal.Types;
const { const {
upgradeMessageSchema, upgradeMessageSchema,
loadAttachmentData, loadAttachmentData,
@ -352,8 +352,6 @@
return this.get('groupAdmins') || this.get('moderators'); return this.get('groupAdmins') || this.get('moderators');
}, },
getProps() { getProps() {
const { format } = PhoneNumber;
const regionCode = storage.get('regionCode');
const typingKeys = Object.keys(this.contactTypingTimers || {}); const typingKeys = Object.keys(this.contactTypingTimers || {});
const groupAdmins = this.getGroupAdmins(); const groupAdmins = this.getGroupAdmins();
@ -377,9 +375,6 @@
unreadCount: this.get('unreadCount') || 0, unreadCount: this.get('unreadCount') || 0,
mentionedUs: this.get('mentionedUs') || false, mentionedUs: this.get('mentionedUs') || false,
isBlocked: this.isBlocked(), isBlocked: this.isBlocked(),
phoneNumber: format(this.id, {
ourRegionCode: regionCode,
}),
lastMessage: { lastMessage: {
status: this.get('lastMessageStatus'), status: this.get('lastMessageStatus'),
text: this.get('lastMessage'), text: this.get('lastMessage'),

@ -20,7 +20,7 @@
window.Whisper = window.Whisper || {}; window.Whisper = window.Whisper || {};
const { Message: TypedMessage, Contact, PhoneNumber } = Signal.Types; const { Message: TypedMessage, Contact } = Signal.Types;
const { const {
deleteExternalMessageFiles, deleteExternalMessageFiles,
@ -326,9 +326,6 @@
return window.getConversationController().get(phoneNumber); return window.getConversationController().get(phoneNumber);
}, },
findAndFormatContact(phoneNumber) { findAndFormatContact(phoneNumber) {
const { format } = PhoneNumber;
const regionCode = storage.get('regionCode');
const contactModel = this.findContact(phoneNumber); const contactModel = this.findContact(phoneNumber);
let profileName; let profileName;
if (phoneNumber === window.storage.get('primaryDevicePubKey')) { if (phoneNumber === window.storage.get('primaryDevicePubKey')) {
@ -338,9 +335,7 @@
} }
return { return {
phoneNumber: format(phoneNumber, { phoneNumber,
ourRegionCode: regionCode,
}),
color: null, color: null,
avatarPath: contactModel ? contactModel.getAvatarPath() : null, avatarPath: contactModel ? contactModel.getAvatarPath() : null,
name: contactModel ? contactModel.getName() : null, name: contactModel ? contactModel.getName() : null,
@ -520,7 +515,6 @@
timestamp: this.get('sent_at'), timestamp: this.get('sent_at'),
serverTimestamp: this.get('serverTimestamp'), serverTimestamp: this.get('serverTimestamp'),
status: this.getMessagePropStatus(), status: this.getMessagePropStatus(),
contact: this.getPropsForEmbeddedContact(),
authorName: contact.name, authorName: contact.name,
authorProfileName: contact.profileName, authorProfileName: contact.profileName,
authorPhoneNumber: contact.phoneNumber, authorPhoneNumber: contact.phoneNumber,
@ -567,38 +561,6 @@
return `${start}${newSpaces}${end}`; return `${start}${newSpaces}${end}`;
}); });
}, },
getPropsForEmbeddedContact() {
const regionCode = storage.get('regionCode');
const { contactSelector } = Contact;
const contacts = this.get('contact');
if (!contacts || !contacts.length) {
return null;
}
const contact = contacts[0];
const firstNumber =
contact.number && contact.number[0] && contact.number[0].value;
const onSendMessage = firstNumber
? () => {
this.trigger('open-conversation', firstNumber);
}
: null;
const onClick = async () => {
// First let's be sure that the signal account check is complete.
this.trigger('show-contact-detail', {
contact,
});
};
return contactSelector(contact, {
regionCode,
getAbsoluteAttachmentPath,
onSendMessage,
onClick,
});
},
processQuoteAttachment(attachment) { processQuoteAttachment(attachment) {
const { thumbnail } = attachment; const { thumbnail } = attachment;
const path = const path =
@ -652,15 +614,9 @@
return null; return null;
} }
const { format } = PhoneNumber;
const regionCode = storage.get('regionCode');
const { author, id, referencedMessageNotFound } = quote; const { author, id, referencedMessageNotFound } = quote;
const contact = author && window.getConversationController().get(author); const contact = author && window.getConversationController().get(author);
const authorPhoneNumber = format(author, {
ourRegionCode: regionCode,
});
const authorName = contact ? contact.getName() : null; const authorName = contact ? contact.getName() : null;
const isFromMe = contact const isFromMe = contact
? contact.id === textsecure.storage.user.getNumber() ? contact.id === textsecure.storage.user.getNumber()
@ -684,7 +640,7 @@
? this.processQuoteAttachment(firstAttachment) ? this.processQuoteAttachment(firstAttachment)
: null, : null,
isFromMe, isFromMe,
authorPhoneNumber, authorPhoneNumber: author,
messageId: id, messageId: id,
authorName, authorName,
onClick, onClick,

@ -101,7 +101,6 @@ const Errors = require('./types/errors');
const MediaGalleryMessage = require('../../ts/components/conversation/media-gallery/types/Message'); const MediaGalleryMessage = require('../../ts/components/conversation/media-gallery/types/Message');
const MessageType = require('./types/message'); const MessageType = require('./types/message');
const MIME = require('../../ts/types/MIME'); const MIME = require('../../ts/types/MIME');
const PhoneNumber = require('../../ts/types/PhoneNumber');
const SettingsType = require('../../ts/types/Settings'); const SettingsType = require('../../ts/types/Settings');
// Views // Views
@ -113,7 +112,6 @@ const MessageDataMigrator = require('./messages_data_migrator');
function initializeMigrations({ function initializeMigrations({
userDataPath, userDataPath,
getRegionCode,
Attachments, Attachments,
Type, Type,
VisualType, VisualType,
@ -178,7 +176,6 @@ function initializeMigrations({
return MessageType.upgradeSchema(message, { return MessageType.upgradeSchema(message, {
writeNewAttachmentData, writeNewAttachmentData,
getRegionCode,
getAbsoluteAttachmentPath, getAbsoluteAttachmentPath,
makeObjectUrl, makeObjectUrl,
revokeObjectUrl, revokeObjectUrl,
@ -200,13 +197,12 @@ function initializeMigrations({
} }
exports.setup = (options = {}) => { exports.setup = (options = {}) => {
const { Attachments, userDataPath, getRegionCode, logger } = options; const { Attachments, userDataPath, logger } = options;
Data.init(); Data.init();
const Migrations = initializeMigrations({ const Migrations = initializeMigrations({
userDataPath, userDataPath,
getRegionCode,
Attachments, Attachments,
Type: AttachmentType, Type: AttachmentType,
VisualType: VisualAttachment, VisualType: VisualAttachment,
@ -261,7 +257,6 @@ exports.setup = (options = {}) => {
Errors, Errors,
Message: MessageType, Message: MessageType,
MIME, MIME,
PhoneNumber,
Settings: SettingsType, Settings: SettingsType,
VisualAttachment, VisualAttachment,
}; };

@ -1,155 +0,0 @@
const { omit, compact, map } = require('lodash');
const { toLogFormat } = require('./errors');
const { SignalService } = require('../../../ts/protobuf');
const { parse: parsePhoneNumber } = require('../../../ts/types/PhoneNumber');
const DEFAULT_PHONE_TYPE = SignalService.DataMessage.Contact.Phone.Type.HOME;
const DEFAULT_EMAIL_TYPE = SignalService.DataMessage.Contact.Email.Type.HOME;
const DEFAULT_ADDRESS_TYPE =
SignalService.DataMessage.Contact.PostalAddress.Type.HOME;
exports.parseAndWriteAvatar = upgradeAttachment => async (
contact,
context = {}
) => {
const { message, regionCode, logger } = context;
const { avatar } = contact;
// This is to ensure that an omit() call doesn't pull in prototype props/methods
const contactShallowCopy = Object.assign({}, contact);
const contactWithUpdatedAvatar =
avatar && avatar.avatar
? Object.assign({}, contactShallowCopy, {
avatar: Object.assign({}, avatar, {
avatar: await upgradeAttachment(avatar.avatar, context),
}),
})
: omit(contactShallowCopy, ['avatar']);
// eliminates empty numbers, emails, and addresses; adds type if not provided
const parsedContact = parseContact(contactWithUpdatedAvatar, { regionCode });
const error = exports._validate(parsedContact, {
messageId: idForLogging(message),
});
if (error) {
logger.error(
'Contact.parseAndWriteAvatar: contact was malformed.',
toLogFormat(error)
);
}
return parsedContact;
};
function parseContact(contact, options = {}) {
const { regionCode } = options;
const boundParsePhone = phoneNumber =>
parsePhoneItem(phoneNumber, { regionCode });
return Object.assign(
{},
omit(contact, ['avatar', 'number', 'email', 'address']),
parseAvatar(contact.avatar),
createArrayKey('number', compact(map(contact.number, boundParsePhone))),
createArrayKey('email', compact(map(contact.email, parseEmailItem))),
createArrayKey('address', compact(map(contact.address, parseAddress)))
);
}
function idForLogging(message) {
return `${message.source}.${message.sourceDevice} ${message.sent_at}`;
}
exports._validate = (contact, options = {}) => {
const { messageId } = options;
const { name, number, email, address, organization } = contact;
if ((!name || !name.displayName) && !organization) {
return new Error(
`Message ${messageId}: Contact had neither 'displayName' nor 'organization'`
);
}
if (
(!number || !number.length) &&
(!email || !email.length) &&
(!address || !address.length)
) {
return new Error(
`Message ${messageId}: Contact had no included numbers, email or addresses`
);
}
return null;
};
function parsePhoneItem(item, options = {}) {
const { regionCode } = options;
if (!item.value) {
return null;
}
return Object.assign({}, item, {
type: item.type || DEFAULT_PHONE_TYPE,
value: parsePhoneNumber(item.value, { regionCode }),
});
}
function parseEmailItem(item) {
if (!item.value) {
return null;
}
return Object.assign({}, item, {
type: item.type || DEFAULT_EMAIL_TYPE,
});
}
function parseAddress(address) {
if (!address) {
return null;
}
if (
!address.street &&
!address.pobox &&
!address.neighborhood &&
!address.city &&
!address.region &&
!address.postcode &&
!address.country
) {
return null;
}
return Object.assign({}, address, {
type: address.type || DEFAULT_ADDRESS_TYPE,
});
}
function parseAvatar(avatar) {
if (!avatar) {
return null;
}
return {
avatar: Object.assign({}, avatar, {
isProfile: avatar.isProfile || false,
}),
};
}
function createArrayKey(key, array) {
if (!array || !array.length) {
return null;
}
return {
[key]: array,
};
}

@ -1,6 +1,5 @@
const { isFunction, isObject, isString, omit } = require('lodash'); const { isFunction, isObject, isString, omit } = require('lodash');
const Contact = require('./contact');
const Attachment = require('./attachment'); const Attachment = require('./attachment');
const Errors = require('./errors'); const Errors = require('./errors');
const SchemaVersion = require('./schema_version'); const SchemaVersion = require('./schema_version');
@ -283,12 +282,6 @@ const toVersion5 = exports._withSchemaVersion({
schemaVersion: 5, schemaVersion: 5,
upgrade: initializeAttachmentMetadata, upgrade: initializeAttachmentMetadata,
}); });
const toVersion6 = exports._withSchemaVersion({
schemaVersion: 6,
upgrade: exports._mapContact(
Contact.parseAndWriteAvatar(Attachment.migrateDataToFileSystem)
),
});
// IMPORTANT: Weve updated our definition of `initializeAttachmentMetadata`, so // IMPORTANT: Weve updated our definition of `initializeAttachmentMetadata`, so
// we need to run it again on existing items that have previously been incorrectly // we need to run it again on existing items that have previously been incorrectly
// classified: // classified:
@ -318,7 +311,6 @@ const VERSIONS = [
toVersion3, toVersion3,
toVersion4, toVersion4,
toVersion5, toVersion5,
toVersion6,
toVersion7, toVersion7,
toVersion8, toVersion8,
toVersion9, toVersion9,
@ -334,7 +326,6 @@ exports.upgradeSchema = async (
rawMessage, rawMessage,
{ {
writeNewAttachmentData, writeNewAttachmentData,
getRegionCode,
getAbsoluteAttachmentPath, getAbsoluteAttachmentPath,
makeObjectUrl, makeObjectUrl,
revokeObjectUrl, revokeObjectUrl,
@ -348,9 +339,6 @@ exports.upgradeSchema = async (
if (!isFunction(writeNewAttachmentData)) { if (!isFunction(writeNewAttachmentData)) {
throw new TypeError('context.writeNewAttachmentData is required'); throw new TypeError('context.writeNewAttachmentData is required');
} }
if (!isFunction(getRegionCode)) {
throw new TypeError('context.getRegionCode is required');
}
if (!isFunction(getAbsoluteAttachmentPath)) { if (!isFunction(getAbsoluteAttachmentPath)) {
throw new TypeError('context.getAbsoluteAttachmentPath is required'); throw new TypeError('context.getAbsoluteAttachmentPath is required');
} }
@ -386,7 +374,6 @@ exports.upgradeSchema = async (
// eslint-disable-next-line no-await-in-loop // eslint-disable-next-line no-await-in-loop
message = await currentVersion(message, { message = await currentVersion(message, {
writeNewAttachmentData, writeNewAttachmentData,
regionCode: getRegionCode(),
getAbsoluteAttachmentPath, getAbsoluteAttachmentPath,
makeObjectUrl, makeObjectUrl,
revokeObjectUrl, revokeObjectUrl,

@ -129,7 +129,6 @@
await textsecure.storage.put('typing-indicators-setting', Boolean(true)); await textsecure.storage.put('typing-indicators-setting', Boolean(true));
await textsecure.storage.user.setNumberAndDeviceId(pubKeyString, 1); await textsecure.storage.user.setNumberAndDeviceId(pubKeyString, 1);
await textsecure.storage.put('regionCode', null);
}, },
async clearSessionsAndPreKeys() { async clearSessionsAndPreKeys() {
const store = textsecure.storage.protocol; const store = textsecure.storage.protocol;

@ -407,7 +407,6 @@ window.moment.locale(locale);
window.Signal = Signal.setup({ window.Signal = Signal.setup({
Attachments, Attachments,
userDataPath: app.getPath('userData'), userDataPath: app.getPath('userData'),
getRegionCode: () => window.storage.get('regionCode'),
logger: window.log, logger: window.log,
}); });

@ -1,404 +0,0 @@
const { assert } = require('chai');
const sinon = require('sinon');
const Contact = require('../../../js/modules/types/contact');
const {
stringToArrayBuffer,
} = require('../../../js/modules/string_to_array_buffer');
describe('Contact', () => {
const NUMBER = '+12025550099';
const logger = {
error: () => null,
};
describe('parseAndWriteAvatar', () => {
it('handles message with no avatar in contact', async () => {
const upgradeAttachment = sinon
.stub()
.throws(new Error("Shouldn't be called"));
const upgradeVersion = Contact.parseAndWriteAvatar(upgradeAttachment);
const message = {
body: 'hey there!',
contact: [
{
name: {
displayName: 'Someone Somewhere',
},
number: [
{
type: 1,
value: NUMBER,
},
],
},
],
};
const result = await upgradeVersion(message.contact[0], {
message,
logger,
});
assert.deepEqual(result, message.contact[0]);
});
// LOKI: Phone number stays the same
it('turns phone numbers to e164 format', async () => {
const upgradeAttachment = sinon
.stub()
.throws(new Error("Shouldn't be called"));
const upgradeVersion = Contact.parseAndWriteAvatar(upgradeAttachment);
const message = {
body: 'hey there!',
contact: [
{
name: {
displayName: 'Someone Somewhere',
},
number: [
{
type: 1,
value: '(202) 555-0099',
},
],
},
],
};
const expected = {
name: {
displayName: 'Someone Somewhere',
},
number: [
{
type: 1,
value: '(202) 555-0099',
},
],
};
const result = await upgradeVersion(message.contact[0], {
message,
regionCode: 'US',
logger,
});
assert.deepEqual(result, expected);
});
it('removes contact avatar if it has no sub-avatar', async () => {
const upgradeAttachment = sinon
.stub()
.throws(new Error("Shouldn't be called"));
const upgradeVersion = Contact.parseAndWriteAvatar(upgradeAttachment);
const message = {
body: 'hey there!',
contact: [
{
name: {
displayName: 'Someone Somewhere',
},
number: [
{
type: 1,
value: NUMBER,
},
],
avatar: {
isProfile: true,
},
},
],
};
const expected = {
name: {
displayName: 'Someone Somewhere',
},
number: [
{
type: 1,
value: NUMBER,
},
],
};
const result = await upgradeVersion(message.contact[0], {
message,
logger,
});
assert.deepEqual(result, expected);
});
it('writes avatar to disk', async () => {
const upgradeAttachment = async () => {
return {
path: 'abc/abcdefg',
};
};
const upgradeVersion = Contact.parseAndWriteAvatar(upgradeAttachment);
const message = {
body: 'hey there!',
contact: [
{
name: {
displayName: 'Someone Somewhere',
},
number: [
{
type: 1,
value: NUMBER,
},
],
email: [
{
type: 2,
value: 'someone@somewhere.com',
},
],
address: [
{
type: 1,
street: '5 Somewhere Ave.',
},
],
avatar: {
otherKey: 'otherValue',
avatar: {
contentType: 'image/png',
data: stringToArrayBuffer('Its easy if you try'),
},
},
},
],
};
const expected = {
name: {
displayName: 'Someone Somewhere',
},
number: [
{
type: 1,
value: NUMBER,
},
],
email: [
{
type: 2,
value: 'someone@somewhere.com',
},
],
address: [
{
type: 1,
street: '5 Somewhere Ave.',
},
],
avatar: {
otherKey: 'otherValue',
isProfile: false,
avatar: {
path: 'abc/abcdefg',
},
},
};
const result = await upgradeVersion(message.contact[0], {
message,
logger,
});
assert.deepEqual(result, expected);
});
it('removes number element if it ends up with no value', async () => {
const upgradeAttachment = sinon
.stub()
.throws(new Error("Shouldn't be called"));
const upgradeVersion = Contact.parseAndWriteAvatar(upgradeAttachment);
const message = {
body: 'hey there!',
contact: [
{
name: {
displayName: 'Someone Somewhere',
},
number: [
{
type: 1,
},
],
email: [
{
value: 'someone@somewhere.com',
},
],
},
],
};
const expected = {
name: {
displayName: 'Someone Somewhere',
},
email: [
{
type: 1,
value: 'someone@somewhere.com',
},
],
};
const result = await upgradeVersion(message.contact[0], {
message,
logger,
});
assert.deepEqual(result, expected);
});
it('drops address if it has no real values', async () => {
const upgradeAttachment = sinon
.stub()
.throws(new Error("Shouldn't be called"));
const upgradeVersion = Contact.parseAndWriteAvatar(upgradeAttachment);
const message = {
body: 'hey there!',
contact: [
{
name: {
displayName: 'Someone Somewhere',
},
number: [
{
value: NUMBER,
},
],
address: [
{
type: 1,
},
],
},
],
};
const expected = {
name: {
displayName: 'Someone Somewhere',
},
number: [
{
value: NUMBER,
type: 1,
},
],
};
const result = await upgradeVersion(message.contact[0], {
message,
logger,
});
assert.deepEqual(result, expected);
});
it('removes invalid elements if no values remain in contact', async () => {
const upgradeAttachment = sinon
.stub()
.throws(new Error("Shouldn't be called"));
const upgradeVersion = Contact.parseAndWriteAvatar(upgradeAttachment);
const message = {
body: 'hey there!',
source: NUMBER,
sourceDevice: '1',
sent_at: 1232132,
contact: [
{
name: {
displayName: 'Someone Somewhere',
},
number: [
{
type: 1,
},
],
email: [
{
type: 1,
},
],
},
],
};
const expected = {
name: {
displayName: 'Someone Somewhere',
},
};
const result = await upgradeVersion(message.contact[0], {
message,
logger,
});
assert.deepEqual(result, expected);
});
it('handles a contact with just organization', async () => {
const upgradeAttachment = sinon
.stub()
.throws(new Error("Shouldn't be called"));
const upgradeVersion = Contact.parseAndWriteAvatar(upgradeAttachment);
const message = {
contact: [
{
organization: 'Somewhere Consulting',
number: [
{
type: 1,
value: NUMBER,
},
],
},
],
};
const result = await upgradeVersion(message.contact[0], {
message,
logger,
});
assert.deepEqual(result, message.contact[0]);
});
});
describe('_validate', () => {
it('returns error if contact has no name.displayName or organization', () => {
const messageId = 'the-message-id';
const contact = {
name: {
name: 'Someone',
},
number: [
{
type: 1,
value: NUMBER,
},
],
};
const expected =
"Message the-message-id: Contact had neither 'displayName' nor 'organization'";
const result = Contact._validate(contact, { messageId });
assert.deepEqual(result.message, expected);
});
it('logs if no values remain in contact', async () => {
const messageId = 'the-message-id';
const contact = {
name: {
displayName: 'Someone Somewhere',
},
number: [],
email: [],
};
const expected =
'Message the-message-id: Contact had no included numbers, email or addresses';
const result = Contact._validate(contact, { messageId });
assert.deepEqual(result.message, expected);
});
});
});

@ -299,7 +299,6 @@ describe('Message', () => {
assert.deepEqual(attachmentData, expectedAttachmentData); assert.deepEqual(attachmentData, expectedAttachmentData);
return 'abc/abcdefg'; return 'abc/abcdefg';
}, },
getRegionCode: () => 'US',
getAbsoluteAttachmentPath: () => 'some/path/on/disk', getAbsoluteAttachmentPath: () => 'some/path/on/disk',
makeObjectUrl: () => 'blob://FAKE', makeObjectUrl: () => 'blob://FAKE',
revokeObjectUrl: () => null, revokeObjectUrl: () => null,

@ -15,7 +15,6 @@ export type SearchResultsProps = {
conversations: Array<ConversationListItemProps>; conversations: Array<ConversationListItemProps>;
hideMessagesHeader: boolean; hideMessagesHeader: boolean;
messages: Array<MessageSearchResultProps>; messages: Array<MessageSearchResultProps>;
regionCode: string;
searchTerm: string; searchTerm: string;
}; };

@ -6,7 +6,6 @@ import { PubKey } from '../session/types';
export type Props = { export type Props = {
contacts: Array<ConversationListItemProps>; contacts: Array<ConversationListItemProps>;
regionCode: string;
searchTerm: string; searchTerm: string;
selectedContact: number; selectedContact: number;
onContactSelected: any; onContactSelected: any;

@ -24,24 +24,6 @@ const TypingBubbleContainer = styled.div<TypingBubbleProps>`
`; `;
export const TypingBubble = (props: TypingBubbleProps) => { export const TypingBubble = (props: TypingBubbleProps) => {
// const renderAvatar = () => {
// const { avatarPath, displayedName, conversationType, phoneNumber } = props;
// if (conversationType !== 'group') {
// return;
// }
// return (
// <div className="module-message__author-avatar">
// <Avatar
// avatarPath={avatarPath}
// name={displayedName || phoneNumber}
// size={36}
// pubkey={phoneNumber}
// />
// </div>
// );
// };
if (props.conversationType === 'group') { if (props.conversationType === 'group') {
return <></>; return <></>;

@ -1,14 +1,13 @@
import React from 'react'; import React from 'react';
import { ConversationListItemWithDetails } from '../ConversationListItem'; import { ConversationListItemWithDetails } from '../ConversationListItem';
import { LeftPane, RowRendererParamsType } from '../LeftPane'; import { RowRendererParamsType } from '../LeftPane';
import { import {
SessionButton, SessionButton,
SessionButtonColor, SessionButtonColor,
SessionButtonType, SessionButtonType,
} from './SessionButton'; } from './SessionButton';
import { AutoSizer, List } from 'react-virtualized'; import { AutoSizer, List } from 'react-virtualized';
import { validateNumber } from '../../types/PhoneNumber';
import { ConversationType } from '../../state/ducks/conversations'; import { ConversationType } from '../../state/ducks/conversations';
import { import {
SessionClosableOverlay, SessionClosableOverlay,
@ -18,6 +17,7 @@ import { ToastUtils } from '../../session/utils';
import { DefaultTheme } from 'styled-components'; import { DefaultTheme } from 'styled-components';
import { LeftPaneSectionHeader } from './LeftPaneSectionHeader'; import { LeftPaneSectionHeader } from './LeftPaneSectionHeader';
import { ConversationController } from '../../session/conversations'; import { ConversationController } from '../../session/conversations';
import { PubKey } from '../../session/types';
export interface Props { export interface Props {
directContacts: Array<ConversationType>; directContacts: Array<ConversationType>;
@ -122,7 +122,7 @@ export class LeftPaneContactSection extends React.Component<Props, State> {
private handleOnAddContact() { private handleOnAddContact() {
const sessionID = this.state.addContactRecipientID.trim(); const sessionID = this.state.addContactRecipientID.trim();
const error = validateNumber(sessionID, window.i18n); const error = PubKey.validateWithError(sessionID, window.i18n);
if (error) { if (error) {
ToastUtils.pushToastError('addContact', error); ToastUtils.pushToastError('addContact', error);

@ -13,8 +13,7 @@ import { SessionSearchInput } from './SessionSearchInput';
import { debounce } from 'lodash'; import { debounce } from 'lodash';
import { cleanSearchTerm } from '../../util/cleanSearchTerm'; import { cleanSearchTerm } from '../../util/cleanSearchTerm';
import { SearchOptions } from '../../types/Search'; import { SearchOptions } from '../../types/Search';
import { validateNumber } from '../../types/PhoneNumber'; import { RowRendererParamsType } from '../LeftPane';
import { LeftPane, RowRendererParamsType } from '../LeftPane';
import { import {
SessionClosableOverlay, SessionClosableOverlay,
SessionClosableOverlayType, SessionClosableOverlayType,
@ -26,7 +25,7 @@ import {
SessionButtonColor, SessionButtonColor,
SessionButtonType, SessionButtonType,
} from './SessionButton'; } from './SessionButton';
import { OpenGroup } from '../../session/types'; import { OpenGroup, PubKey } from '../../session/types';
import { ToastUtils } from '../../session/utils'; import { ToastUtils } from '../../session/utils';
import { DefaultTheme } from 'styled-components'; import { DefaultTheme } from 'styled-components';
import { LeftPaneSectionHeader } from './LeftPaneSectionHeader'; import { LeftPaneSectionHeader } from './LeftPaneSectionHeader';
@ -266,7 +265,6 @@ export class LeftPaneMessageSection extends React.Component<Props, State> {
search(searchTerm, { search(searchTerm, {
noteToSelf: window.i18n('noteToSelf').toLowerCase(), noteToSelf: window.i18n('noteToSelf').toLowerCase(),
ourNumber: window.textsecure.storage.user.getNumber(), ourNumber: window.textsecure.storage.user.getNumber(),
regionCode: '',
}); });
} }
} }
@ -392,7 +390,7 @@ export class LeftPaneMessageSection extends React.Component<Props, State> {
pubkey = this.state.valuePasted || this.props.searchTerm; pubkey = this.state.valuePasted || this.props.searchTerm;
pubkey = pubkey.trim(); pubkey = pubkey.trim();
const error = validateNumber(pubkey); const error = PubKey.validateWithError(pubkey);
if (!error) { if (!error) {
await ConversationController.getInstance().getOrCreateAndWait( await ConversationController.getInstance().getOrCreateAndWait(
pubkey, pubkey,

@ -191,7 +191,6 @@ export class SessionInboxView extends React.Component<Props, State> {
), ),
}, },
user: { user: {
regionCode: window.storage.get('regionCode'),
ourPrimary: window.storage.get('primaryDevicePubKey'), ourPrimary: window.storage.get('primaryDevicePubKey'),
ourNumber: ourNumber:
window.storage.get('primaryDevicePubKey') || window.storage.get('primaryDevicePubKey') ||

@ -1,3 +1,4 @@
import { LocalizerType } from '../../types/Util';
import { fromHexToArray } from '../utils/String'; import { fromHexToArray } from '../utils/String';
export class PubKey { export class PubKey {
@ -58,6 +59,10 @@ export class PubKey {
const valAny = value as PubKey; const valAny = value as PubKey;
const pk = value instanceof PubKey ? valAny.key : value; const pk = value instanceof PubKey ? valAny.key : value;
if (!pk) {
throw new Error('PubkKey.shorten was given an invalid PubKey to shorten.')
}
return `(...${pk.substring(pk.length - 6)})`; return `(...${pk.substring(pk.length - 6)})`;
} }
@ -76,10 +81,40 @@ export class PubKey {
return undefined; return undefined;
} }
/**
* Returns the pubkey as a string if it's valid, or undefined
*/
public static normalize(pubkeyString: string): string | undefined {
// Returns a new instance if the pubkey is valid
if (PubKey.validate(pubkeyString)) {
return pubkeyString;
}
return undefined;
}
public static validate(pubkeyString: string): boolean { public static validate(pubkeyString: string): boolean {
return this.regex.test(pubkeyString); return this.regex.test(pubkeyString);
} }
/**
* Returns a localized string of the error, or undefined in the given pubkey is valid.
*/
public static validateWithError(pubkey: string, i18n: LocalizerType = window.i18n): string | undefined {
// Check if it's hex
const isHex = pubkey.replace(/[\s]*/g, '').match(/^[0-9a-fA-F]+$/);
if (!isHex) {
return i18n('invalidSessionId');
}
// Check if the pubkey length is 33 and leading with 05 or of length 32
const len = pubkey.length;
if ((len !== 33 * 2 || !/^05/.test(pubkey)) && len !== 32 * 2) {
return i18n('invalidPubkeyFormat');
}
return undefined;
}
/** /**
* This removes the 05 prefix from a Pubkey which have it and have a length of 66 * This removes the 05 prefix from a Pubkey which have it and have a length of 66
* @param keyWithOrWithoutPrefix the key with or without the prefix * @param keyWithOrWithoutPrefix the key with or without the prefix

@ -1,6 +1,5 @@
import { omit, reject } from 'lodash'; import { omit, reject } from 'lodash';
import { normalize } from '../../types/PhoneNumber';
import { AdvancedSearchOptions, SearchOptions } from '../../types/Search'; import { AdvancedSearchOptions, SearchOptions } from '../../types/Search';
import { getMessageModel } from '../../shims/Whisper'; import { getMessageModel } from '../../shims/Whisper';
import { cleanSearchTerm } from '../../util/cleanSearchTerm'; import { cleanSearchTerm } from '../../util/cleanSearchTerm';
@ -14,6 +13,7 @@ import {
RemoveAllConversationsActionType, RemoveAllConversationsActionType,
SelectedConversationChangedActionType, SelectedConversationChangedActionType,
} from './conversations'; } from './conversations';
import { PubKey } from '../../session/types';
// State // State
@ -89,8 +89,6 @@ async function doSearch(
query: string, query: string,
options: SearchOptions options: SearchOptions
): Promise<SearchResultsPayloadType> { ): Promise<SearchResultsPayloadType> {
const { regionCode } = options;
const advancedSearchOptions = getAdvancedSearchOptionsFromQuery(query); const advancedSearchOptions = getAdvancedSearchOptionsFromQuery(query);
const processedQuery = advancedSearchOptions.query; const processedQuery = advancedSearchOptions.query;
const isAdvancedQuery = query !== processedQuery; const isAdvancedQuery = query !== processedQuery;
@ -120,7 +118,7 @@ async function doSearch(
return { return {
query, query,
normalizedPhoneNumber: normalize(query, { regionCode }), normalizedPhoneNumber: PubKey.normalize(query),
conversations, conversations,
contacts, contacts,
messages: getMessageProps(filteredMessages) || [], messages: getMessageProps(filteredMessages) || [],

@ -4,7 +4,6 @@ import { LocalizerType } from '../../types/Util';
export type UserStateType = { export type UserStateType = {
ourNumber: string; ourNumber: string;
regionCode: string;
i18n: LocalizerType; i18n: LocalizerType;
}; };
@ -15,7 +14,6 @@ type UserChangedActionType = {
payload: { payload: {
ourNumber: string; ourNumber: string;
ourPrimary: string; ourPrimary: string;
regionCode: string;
}; };
}; };
@ -30,7 +28,6 @@ export const actions = {
function userChanged(attributes: { function userChanged(attributes: {
ourNumber: string; ourNumber: string;
ourPrimary: string; ourPrimary: string;
regionCode: string;
}): UserChangedActionType { }): UserChangedActionType {
return { return {
type: 'USER_CHANGED', type: 'USER_CHANGED',
@ -43,7 +40,6 @@ function userChanged(attributes: {
function getEmptyState(): UserStateType { function getEmptyState(): UserStateType {
return { return {
ourNumber: 'missing', ourNumber: 'missing',
regionCode: 'missing',
i18n: () => 'missing', i18n: () => 'missing',
}; };
} }

@ -1,5 +1,4 @@
import { createSelector } from 'reselect'; import { createSelector } from 'reselect';
import { format } from '../../types/PhoneNumber';
import { LocalizerType } from '../../types/Util'; import { LocalizerType } from '../../types/Util';
import { StateType } from '../reducer'; import { StateType } from '../reducer';
@ -10,7 +9,7 @@ import {
MessageTypeInConvo, MessageTypeInConvo,
} from '../ducks/conversations'; } from '../ducks/conversations';
import { getIntl, getOurNumber, getRegionCode } from './user'; import { getIntl, getOurNumber } from './user';
import { BlockedNumberController } from '../../util'; import { BlockedNumberController } from '../../util';
export const getConversations = (state: StateType): ConversationsStateType => export const getConversations = (state: StateType): ConversationsStateType =>
@ -50,27 +49,21 @@ export const getMessagesOfSelectedConversation = createSelector(
(state: ConversationsStateType): Array<MessageTypeInConvo> => state.messages (state: ConversationsStateType): Array<MessageTypeInConvo> => state.messages
); );
function getConversationTitle( function getConversationTitle(conversation: ConversationType): string {
conversation: ConversationType,
options: { i18n: LocalizerType; ourRegionCode: string }
): string {
if (conversation.name) { if (conversation.name) {
return conversation.name; return conversation.name;
} }
if (conversation.type === 'group') { if (conversation.type === 'group') {
const { i18n } = options; const { i18n } = window;
return i18n('unknown'); return i18n('unknown');
} }
return format(conversation.phoneNumber, options); return conversation.id;
} }
const collator = new Intl.Collator(); const collator = new Intl.Collator();
export const _getConversationComparator = ( export const _getConversationComparator = (i18n: LocalizerType) => {
i18n: LocalizerType,
ourRegionCode: string
) => {
return (left: ConversationType, right: ConversationType): number => { return (left: ConversationType, right: ConversationType): number => {
const leftTimestamp = left.timestamp; const leftTimestamp = left.timestamp;
const rightTimestamp = right.timestamp; const rightTimestamp = right.timestamp;
@ -83,21 +76,14 @@ export const _getConversationComparator = (
if (leftTimestamp && rightTimestamp && leftTimestamp !== rightTimestamp) { if (leftTimestamp && rightTimestamp && leftTimestamp !== rightTimestamp) {
return rightTimestamp - leftTimestamp; return rightTimestamp - leftTimestamp;
} }
const leftTitle = getConversationTitle(left, { const leftTitle = getConversationTitle(left).toLowerCase();
i18n, const rightTitle = getConversationTitle(right).toLowerCase();
ourRegionCode,
}).toLowerCase();
const rightTitle = getConversationTitle(right, {
i18n,
ourRegionCode,
}).toLowerCase();
return collator.compare(leftTitle, rightTitle); return collator.compare(leftTitle, rightTitle);
}; };
}; };
export const getConversationComparator = createSelector( export const getConversationComparator = createSelector(
getIntl, getIntl,
getRegionCode,
_getConversationComparator _getConversationComparator
); );

@ -11,8 +11,6 @@ import {
} from './conversations'; } from './conversations';
import { ConversationLookupType } from '../ducks/conversations'; import { ConversationLookupType } from '../ducks/conversations';
import { getRegionCode } from './user';
export const getSearch = (state: StateType): SearchStateType => state.search; export const getSearch = (state: StateType): SearchStateType => state.search;
export const getQuery = createSelector( export const getQuery = createSelector(
@ -37,14 +35,12 @@ export const isSearching = createSelector(
export const getSearchResults = createSelector( export const getSearchResults = createSelector(
[ [
getSearch, getSearch,
getRegionCode,
getConversationLookup, getConversationLookup,
getSelectedConversationKey, getSelectedConversationKey,
getSelectedMessage, getSelectedMessage,
], ],
( (
state: SearchStateType, state: SearchStateType,
regionCode: string,
lookup: ConversationLookupType, lookup: ConversationLookupType,
selectedConversation?: string, selectedConversation?: string,
selectedMessage?: string selectedMessage?: string
@ -94,7 +90,6 @@ export const getSearchResults = createSelector(
return message; return message;
}), }),
regionCode: regionCode,
searchTerm: state.query, searchTerm: state.query,
}; };
} }

@ -12,11 +12,6 @@ export const getOurNumber = createSelector(
(state: UserStateType): string => state.ourNumber (state: UserStateType): string => state.ourNumber
); );
export const getRegionCode = createSelector(
getUser,
(state: UserStateType): string => state.regionCode
);
export const getIntl = createSelector( export const getIntl = createSelector(
getUser, getUser,
(state: UserStateType): LocalizerType => state.i18n (state: UserStateType): LocalizerType => state.i18n

@ -3,7 +3,7 @@ import { LeftPane } from '../../components/LeftPane';
import { StateType } from '../reducer'; import { StateType } from '../reducer';
import { getQuery, getSearchResults, isSearching } from '../selectors/search'; import { getQuery, getSearchResults, isSearching } from '../selectors/search';
import { getIntl, getOurNumber, getRegionCode } from '../selectors/user'; import { getIntl, getOurNumber } from '../selectors/user';
import { import {
getLeftPaneLists, getLeftPaneLists,
getOurPrimaryConversation, getOurPrimaryConversation,
@ -25,7 +25,6 @@ const mapStateToProps = (state: StateType) => {
...lists, ...lists,
ourPrimaryConversation: getOurPrimaryConversation(state), // used in actionPanel ourPrimaryConversation: getOurPrimaryConversation(state), // used in actionPanel
searchTerm: getQuery(state), searchTerm: getQuery(state),
regionCode: getRegionCode(state),
ourNumber: getOurNumber(state), ourNumber: getOurNumber(state),
searchResults, searchResults,
i18n: getIntl(state), i18n: getIntl(state),

@ -10,7 +10,6 @@ describe('state/selectors/conversations', () => {
describe('#getLeftPaneList', () => { describe('#getLeftPaneList', () => {
it('sorts conversations based on timestamp then by intl-friendly title', () => { it('sorts conversations based on timestamp then by intl-friendly title', () => {
const i18n = (key: string) => key; const i18n = (key: string) => key;
const regionCode = 'US';
const data: ConversationLookupType = { const data: ConversationLookupType = {
id1: { id1: {
id: 'id1', id: 'id1',
@ -101,7 +100,7 @@ describe('state/selectors/conversations', () => {
left: false, left: false,
}, },
}; };
const comparator = _getConversationComparator(i18n, regionCode); const comparator = _getConversationComparator(i18n);
const { conversations } = _getLeftPaneLists(data, comparator); const { conversations } = _getLeftPaneLists(data, comparator);
assert.strictEqual(conversations[0].name, 'First!'); assert.strictEqual(conversations[0].name, 'First!');

@ -1,189 +0,0 @@
import { assert } from 'chai';
import { contactSelector, getName } from '../../types/Contact';
describe('Contact', () => {
describe('getName', () => {
it('returns displayName if provided', () => {
const contact = {
name: {
displayName: 'displayName',
givenName: 'givenName',
familyName: 'familyName',
},
organization: 'Somewhere, Inc.',
};
const expected = 'displayName';
const actual = getName(contact);
assert.strictEqual(actual, expected);
});
it('returns organization if no displayName', () => {
const contact = {
name: {
givenName: 'givenName',
familyName: 'familyName',
},
organization: 'Somewhere, Inc.',
};
const expected = 'Somewhere, Inc.';
const actual = getName(contact);
assert.strictEqual(actual, expected);
});
it('returns givenName + familyName if no displayName or organization', () => {
const contact = {
name: {
givenName: 'givenName',
familyName: 'familyName',
},
};
const expected = 'givenName familyName';
const actual = getName(contact);
assert.strictEqual(actual, expected);
});
it('returns just givenName', () => {
const contact = {
name: {
givenName: 'givenName',
},
};
const expected = 'givenName';
const actual = getName(contact);
assert.strictEqual(actual, expected);
});
it('returns just familyName', () => {
const contact = {
name: {
familyName: 'familyName',
},
};
const expected = 'familyName';
const actual = getName(contact);
assert.strictEqual(actual, expected);
});
});
describe('contactSelector', () => {
const regionCode = '1';
const getAbsoluteAttachmentPath = (path: string) => `absolute:${path}`;
const onSendMessage = () => null;
const onClick = () => null;
it('eliminates avatar if it has had an attachment download error', () => {
const contact = {
name: {
displayName: 'displayName',
givenName: 'givenName',
familyName: 'familyName',
},
organization: 'Somewhere, Inc.',
avatar: {
isProfile: true,
avatar: {
error: true,
},
},
};
const expected = {
name: {
displayName: 'displayName',
givenName: 'givenName',
familyName: 'familyName',
},
organization: 'Somewhere, Inc.',
avatar: undefined,
onSendMessage,
onClick,
number: undefined,
};
const actual = contactSelector(contact, {
regionCode,
getAbsoluteAttachmentPath,
onSendMessage,
onClick,
});
assert.deepEqual(actual, expected);
});
it('does not calculate absolute path if avatar is pending', () => {
const contact = {
name: {
displayName: 'displayName',
givenName: 'givenName',
familyName: 'familyName',
},
organization: 'Somewhere, Inc.',
avatar: {
isProfile: true,
avatar: {
pending: true,
},
},
};
const expected = {
name: {
displayName: 'displayName',
givenName: 'givenName',
familyName: 'familyName',
},
organization: 'Somewhere, Inc.',
avatar: {
isProfile: true,
avatar: {
pending: true,
path: undefined,
},
},
onSendMessage,
onClick,
number: undefined,
};
const actual = contactSelector(contact, {
regionCode,
getAbsoluteAttachmentPath,
onSendMessage,
onClick,
});
assert.deepEqual(actual, expected);
});
it('calculates absolute path', () => {
const contact = {
name: {
displayName: 'displayName',
givenName: 'givenName',
familyName: 'familyName',
},
organization: 'Somewhere, Inc.',
avatar: {
isProfile: true,
avatar: {
path: 'somewhere',
},
},
};
const expected = {
name: {
displayName: 'displayName',
givenName: 'givenName',
familyName: 'familyName',
},
organization: 'Somewhere, Inc.',
avatar: {
isProfile: true,
avatar: {
path: 'absolute:somewhere',
},
},
onSendMessage,
onClick,
number: undefined,
};
const actual = contactSelector(contact, {
regionCode,
getAbsoluteAttachmentPath,
onSendMessage,
onClick,
});
assert.deepEqual(actual, expected);
});
});
});

@ -1,12 +1,7 @@
// @ts-ignore // @ts-ignore
import Attachments from '../../app/attachments';
import { format as formatPhoneNumber } from '../types/PhoneNumber';
export interface Contact { export interface Contact {
name?: Name; name?: Name;
number?: Array<Phone>; number?: Array<Phone>;
email?: Array<Email>;
address?: Array<PostalAddress>;
avatar?: Avatar; avatar?: Avatar;
organization?: string; organization?: string;
} }
@ -39,24 +34,6 @@ export interface Phone {
label?: string; label?: string;
} }
export interface Email {
value: string;
type: ContactType;
label?: string;
}
export interface PostalAddress {
type: AddressType;
label?: string;
street?: string;
pobox?: string;
neighborhood?: string;
city?: string;
region?: string;
postcode?: string;
country?: string;
}
interface Avatar { interface Avatar {
avatar: Attachment; avatar: Attachment;
isProfile: boolean; isProfile: boolean;
@ -68,55 +45,6 @@ interface Attachment {
pending?: boolean; pending?: boolean;
} }
export function contactSelector(
contact: Contact,
options: {
regionCode: string;
getAbsoluteAttachmentPath: (path: string) => string;
onSendMessage: () => void;
onClick: () => void;
}
) {
const {
getAbsoluteAttachmentPath,
onClick,
onSendMessage,
regionCode,
} = options;
let { avatar } = contact;
if (avatar && avatar.avatar) {
if (avatar.avatar.error) {
avatar = undefined;
} else {
avatar = {
...avatar,
avatar: {
...avatar.avatar,
path: avatar.avatar.path
? getAbsoluteAttachmentPath(avatar.avatar.path)
: undefined,
},
};
}
}
return {
...contact,
onSendMessage,
onClick,
avatar,
number:
contact.number &&
contact.number.map(item => ({
...item,
value: formatPhoneNumber(item.value, {
ourRegionCode: regionCode,
}),
})),
};
}
export function getName(contact: Contact): string | undefined { export function getName(contact: Contact): string | undefined {
const { name, organization } = contact; const { name, organization } = contact;
const displayName = (name && name.displayName) || undefined; const displayName = (name && name.displayName) || undefined;

@ -38,7 +38,7 @@ type ExpirationTimerUpdate = Partial<
expirationTimerUpdate: Readonly<{ expirationTimerUpdate: Readonly<{
expireTimer: number; expireTimer: number;
fromSync: boolean; fromSync: boolean;
source: string; // PhoneNumber source: string;
}>; }>;
}> }>
>; >;

@ -1,65 +0,0 @@
import { LocalizerType } from './Util';
export function format(
phoneNumber: string,
_options: {
ourRegionCode: string;
}
) {
return phoneNumber;
}
export function parse(
phoneNumber: string,
_options: {
regionCode: string;
}
): string {
return phoneNumber;
}
export function normalize(
phoneNumber: string,
_options: { regionCode: string }
): string | undefined {
try {
if (isValidNumber(phoneNumber)) {
return phoneNumber;
}
return;
} catch (error) {
return;
}
}
function validate(number: string) {
// Check if it's hex
const isHex = number.replace(/[\s]*/g, '').match(/^[0-9a-fA-F]+$/);
if (!isHex) {
return 'invalidSessionId';
}
// Check if the pubkey length is 33 and leading with 05 or of length 32
const len = number.length;
if ((len !== 33 * 2 || !/^05/.test(number)) && len !== 32 * 2) {
return 'invalidPubkeyFormat';
}
return null;
}
function isValidNumber(number: string) {
const error = validate(number);
return !error;
}
export function validateNumber(
number: string,
i18n: LocalizerType = window.i18n
) {
const error = validate(number);
return error && i18n(error);
}

@ -1,5 +1,4 @@
export type SearchOptions = { export type SearchOptions = {
regionCode: string;
ourNumber: string; ourNumber: string;
noteToSelf: string; noteToSelf: string;
}; };

@ -496,15 +496,6 @@
"updated": "2018-09-19T21:59:32.770Z", "updated": "2018-09-19T21:59:32.770Z",
"reasonDetail": "Protected from arbitrary input" "reasonDetail": "Protected from arbitrary input"
}, },
{
"rule": "jQuery-$(",
"path": "js/views/phone-input-view.js",
"line": " const regionCode = this.$('li.active')",
"lineNumber": 22,
"reasonCategory": "usageTrusted",
"updated": "2018-09-19T21:59:32.770Z",
"reasonDetail": "Protected from arbitrary input"
},
{ {
"rule": "jQuery-$(", "rule": "jQuery-$(",
"path": "js/views/phone-input-view.js", "path": "js/views/phone-input-view.js",

Loading…
Cancel
Save