{
notification = (
);
+ } else if (item.type === 'linkNotification') {
+ notification = (
+
+
+ {i18n('messageHistoryUnsynced')}
+
+ );
} else if (item.type === 'timerNotification') {
notification = (
diff --git a/ts/shims/textsecure.ts b/ts/shims/textsecure.ts
index cc0bbf5813d2..17dd50daeea1 100644
--- a/ts/shims/textsecure.ts
+++ b/ts/shims/textsecure.ts
@@ -5,6 +5,7 @@ type TextSecureType = {
user: {
getNumber: () => string;
};
+ get: (item: string) => any;
};
messaging: {
sendStickerPackSync: (
diff --git a/ts/state/ducks/conversations.ts b/ts/state/ducks/conversations.ts
index a59903d710bd..e8da1222c6f4 100644
--- a/ts/state/ducks/conversations.ts
+++ b/ts/state/ducks/conversations.ts
@@ -27,6 +27,7 @@ export type ConversationType = {
isArchived: boolean;
activeAt?: number;
timestamp: number;
+ inboxPosition: number;
lastMessage?: {
status: 'error' | 'sending' | 'sent' | 'delivered' | 'read';
text: string;
@@ -56,7 +57,13 @@ export type MessageType = {
id: string;
conversationId: string;
source: string;
- type: 'incoming' | 'outgoing' | 'group' | 'keychange' | 'verified-change';
+ type:
+ | 'incoming'
+ | 'outgoing'
+ | 'group'
+ | 'keychange'
+ | 'verified-change'
+ | 'message-history-unsynced';
quote?: { author: string };
received_at: number;
hasSignalAccount?: boolean;
diff --git a/ts/state/selectors/conversations.ts b/ts/state/selectors/conversations.ts
index 96757da029fe..bd3f0392415d 100644
--- a/ts/state/selectors/conversations.ts
+++ b/ts/state/selectors/conversations.ts
@@ -117,6 +117,21 @@ export const _getConversationComparator = (
return rightTimestamp - leftTimestamp;
}
+ if (
+ typeof left.inboxPosition === 'number' &&
+ typeof right.inboxPosition === 'number'
+ ) {
+ return right.inboxPosition > left.inboxPosition ? -1 : 1;
+ }
+
+ if (typeof left.inboxPosition === 'number' && right.inboxPosition == null) {
+ return -1;
+ }
+
+ if (typeof right.inboxPosition === 'number' && left.inboxPosition == null) {
+ return 1;
+ }
+
const leftTitle = getConversationTitle(left, {
i18n,
ourRegionCode,
diff --git a/ts/state/selectors/search.ts b/ts/state/selectors/search.ts
index c7a384df26d6..8fe1501a6316 100644
--- a/ts/state/selectors/search.ts
+++ b/ts/state/selectors/search.ts
@@ -1,6 +1,7 @@
import memoizee from 'memoizee';
import { createSelector } from 'reselect';
import { getSearchResultsProps } from '../../shims/Whisper';
+import { instance } from '../../util/libphonenumberInstance';
import { StateType } from '../reducer';
@@ -20,7 +21,7 @@ import {
} from '../../components/SearchResults';
import { PropsDataType as MessageSearchResultPropsDataType } from '../../components/MessageSearchResult';
-import { getRegionCode, getUserNumber } from './user';
+import { getRegionCode, getUserAgent, getUserNumber } from './user';
import {
GetConversationByIdType,
getConversationLookup,
@@ -72,6 +73,7 @@ export const getSearchResults = createSelector(
[
getSearch,
getRegionCode,
+ getUserAgent,
getConversationLookup,
getSelectedConversation,
getSelectedMessage,
@@ -79,6 +81,7 @@ export const getSearchResults = createSelector(
(
state: SearchStateType,
regionCode: string,
+ userAgent: string,
lookup: ConversationLookupType,
selectedConversationId?: string,
selectedMessageId?: string
@@ -114,6 +117,17 @@ export const getSearchResults = createSelector(
type: 'start-new-conversation',
data: undefined,
});
+
+ const isIOS = userAgent === 'OWI';
+ const parsedNumber = instance.parse(state.query, regionCode);
+ const isValidNumber = instance.isValidNumber(parsedNumber);
+
+ if (!isIOS && isValidNumber) {
+ items.push({
+ type: 'sms-mms-not-supported-text',
+ data: undefined,
+ });
+ }
}
if (haveConversations) {
diff --git a/ts/state/selectors/user.ts b/ts/state/selectors/user.ts
index e7241e6e6c21..c4e2517b4ceb 100644
--- a/ts/state/selectors/user.ts
+++ b/ts/state/selectors/user.ts
@@ -4,9 +4,12 @@ import { LocalizerType } from '../../types/Util';
import { StateType } from '../reducer';
import { UserStateType } from '../ducks/user';
+import { ItemsStateType } from '../ducks/items';
export const getUser = (state: StateType): UserStateType => state.user;
+export const getItems = (state: StateType): ItemsStateType => state.items;
+
export const getUserNumber = createSelector(
getUser,
(state: UserStateType): string => state.ourNumber
@@ -27,6 +30,11 @@ export const getUserUuid = createSelector(
(state: UserStateType): string => state.ourUuid
);
+export const getUserAgent = createSelector(
+ getItems,
+ (state: ItemsStateType): string => state.userAgent
+);
+
export const getIntl = createSelector(
getUser,
(state: UserStateType): LocalizerType => state.i18n
diff --git a/ts/test/state/selectors/conversations_test.ts b/ts/test/state/selectors/conversations_test.ts
index 52cd60288a1d..25b3051d069c 100644
--- a/ts/test/state/selectors/conversations_test.ts
+++ b/ts/test/state/selectors/conversations_test.ts
@@ -17,6 +17,7 @@ describe('state/selectors/conversations', () => {
activeAt: Date.now(),
name: 'No timestamp',
timestamp: 0,
+ inboxPosition: 0,
phoneNumber: 'notused',
isArchived: false,
@@ -36,6 +37,7 @@ describe('state/selectors/conversations', () => {
activeAt: Date.now(),
name: 'B',
timestamp: 20,
+ inboxPosition: 21,
phoneNumber: 'notused',
isArchived: false,
@@ -55,6 +57,7 @@ describe('state/selectors/conversations', () => {
activeAt: Date.now(),
name: 'C',
timestamp: 20,
+ inboxPosition: 22,
phoneNumber: 'notused',
isArchived: false,
@@ -74,6 +77,7 @@ describe('state/selectors/conversations', () => {
activeAt: Date.now(),
name: 'Γ',
timestamp: 20,
+ inboxPosition: 20,
phoneNumber: 'notused',
isArchived: false,
@@ -93,6 +97,7 @@ describe('state/selectors/conversations', () => {
activeAt: Date.now(),
name: 'First!',
timestamp: 30,
+ inboxPosition: 30,
phoneNumber: 'notused',
isArchived: false,
diff --git a/ts/test/types/Conversation_test.ts b/ts/test/types/Conversation_test.ts
index a220f52bb6e6..e57b4decd707 100644
--- a/ts/test/types/Conversation_test.ts
+++ b/ts/test/types/Conversation_test.ts
@@ -3,6 +3,7 @@ import { assert } from 'chai';
import * as Conversation from '../../types/Conversation';
import {
IncomingMessage,
+ MessageHistoryUnsyncedMessage,
OutgoingMessage,
VerifiedChangeMessage,
} from '../../types/Message';
@@ -44,6 +45,30 @@ describe('Conversation', () => {
assert.deepEqual(actual, expected);
});
});
+
+ context('for message history unsynced message', () => {
+ it('should skip update', () => {
+ const input = {
+ currentTimestamp: 555,
+ lastMessage: {
+ type: 'message-history-unsynced',
+ conversationId: 'foo',
+ sent_at: 666,
+ timestamp: 666,
+ } as MessageHistoryUnsyncedMessage,
+ lastMessageNotificationText: 'xoxoxoxo',
+ };
+ const expected = {
+ lastMessage: 'xoxoxoxo',
+ lastMessageStatus: null,
+ timestamp: 555,
+ };
+
+ const actual = Conversation.createLastMessageUpdate(input);
+ assert.deepEqual(actual, expected);
+ });
+ });
+
context('for verified change message', () => {
it('should skip update', () => {
const input = {
diff --git a/ts/types/Conversation.ts b/ts/types/Conversation.ts
index 943e4caec82d..9b3b08ed2a2b 100644
--- a/ts/types/Conversation.ts
+++ b/ts/types/Conversation.ts
@@ -26,13 +26,16 @@ export const createLastMessageUpdate = ({
}
const { type, expirationTimerUpdate } = lastMessage;
+ const isMessageHistoryUnsynced = type === 'message-history-unsynced';
const isVerifiedChangeMessage = type === 'verified-change';
const isExpireTimerUpdateFromSync = Boolean(
expirationTimerUpdate && expirationTimerUpdate.fromSync
);
const shouldUpdateTimestamp = Boolean(
- !isVerifiedChangeMessage && !isExpireTimerUpdateFromSync
+ !isMessageHistoryUnsynced &&
+ !isVerifiedChangeMessage &&
+ !isExpireTimerUpdateFromSync
);
const newTimestamp = shouldUpdateTimestamp
? lastMessage.sent_at
diff --git a/ts/types/Message.ts b/ts/types/Message.ts
index 4fb2bbdf1a76..6d002a682666 100644
--- a/ts/types/Message.ts
+++ b/ts/types/Message.ts
@@ -2,7 +2,10 @@ import { Attachment } from './Attachment';
import { ContactType } from './Contact';
import { IndexableBoolean, IndexablePresence } from './IndexedDB';
-export type Message = UserMessage | VerifiedChangeMessage;
+export type Message =
+ | UserMessage
+ | VerifiedChangeMessage
+ | MessageHistoryUnsyncedMessage;
export type UserMessage = IncomingMessage | OutgoingMessage;
export type IncomingMessage = Readonly<
@@ -65,6 +68,14 @@ export type VerifiedChangeMessage = Readonly<
ExpirationTimerUpdate
>;
+export type MessageHistoryUnsyncedMessage = Readonly<
+ {
+ type: 'message-history-unsynced';
+ } & SharedMessageProperties &
+ MessageSchemaVersion5 &
+ ExpirationTimerUpdate
+>;
+
type SharedMessageProperties = Readonly<{
conversationId: string;
sent_at: number;
diff --git a/ts/types/message/initializeAttachmentMetadata.ts b/ts/types/message/initializeAttachmentMetadata.ts
index 56d19d45e8ff..f2075b71293b 100644
--- a/ts/types/message/initializeAttachmentMetadata.ts
+++ b/ts/types/message/initializeAttachmentMetadata.ts
@@ -16,6 +16,9 @@ export const initializeAttachmentMetadata = async (
if (message.type === 'verified-change') {
return message;
}
+ if (message.type === 'message-history-unsynced') {
+ return message;
+ }
if (message.messageTimer || message.isViewOnce) {
return message;
}
diff --git a/ts/util/lint/exceptions.json b/ts/util/lint/exceptions.json
index 88e0f3792e23..2e8b3ae6871b 100644
--- a/ts/util/lint/exceptions.json
+++ b/ts/util/lint/exceptions.json
@@ -570,7 +570,7 @@
"rule": "jQuery-$(",
"path": "js/views/install_view.js",
"line": " this.$('#qr img').remove();",
- "lineNumber": 135,
+ "lineNumber": 136,
"reasonCategory": "usageTrusted",
"updated": "2018-09-19T21:59:32.770Z",
"reasonDetail": "Protected from arbitrary input"
@@ -579,7 +579,7 @@
"rule": "jQuery-$(",
"path": "js/views/install_view.js",
"line": " this.$('#qr .container').show();",
- "lineNumber": 137,
+ "lineNumber": 138,
"reasonCategory": "usageTrusted",
"updated": "2018-09-19T21:59:32.770Z",
"reasonDetail": "Protected from arbitrary input"
@@ -588,7 +588,7 @@
"rule": "jQuery-$(",
"path": "js/views/install_view.js",
"line": " if ($('#qr').length === 0) {",
- "lineNumber": 141,
+ "lineNumber": 142,
"reasonCategory": "usageTrusted",
"updated": "2018-09-19T21:59:32.770Z",
"reasonDetail": "Protected from arbitrary input"
@@ -597,25 +597,25 @@
"rule": "jQuery-$(",
"path": "js/views/install_view.js",
"line": " this.$('#qr .container').hide();",
- "lineNumber": 146,
- "reasonCategory": "usageTrusted",
- "updated": "2018-09-19T21:59:32.770Z",
- "reasonDetail": "Protected from arbitrary input"
- },
- {
- "rule": "jQuery-$(",
- "path": "js/views/install_view.js",
- "line": " this.qr = new QRCode(this.$('#qr')[0]).makeCode(url);",
"lineNumber": 147,
"reasonCategory": "usageTrusted",
"updated": "2018-09-19T21:59:32.770Z",
"reasonDetail": "Protected from arbitrary input"
},
+ {
+ "rule": "jQuery-$(",
+ "path": "js/views/install_view.js",
+ "line": " this.qr = new QRCode(this.$('#qr')[0]).makeCode(url);",
+ "lineNumber": 148,
+ "reasonCategory": "usageTrusted",
+ "updated": "2018-09-19T21:59:32.770Z",
+ "reasonDetail": "Protected from arbitrary input"
+ },
{
"rule": "jQuery-$(",
"path": "js/views/install_view.js",
"line": " this.$('#qr').addClass('ready');",
- "lineNumber": 149,
+ "lineNumber": 150,
"reasonCategory": "usageTrusted",
"updated": "2018-09-19T21:59:32.770Z",
"reasonDetail": "Protected from arbitrary input"
@@ -624,7 +624,7 @@
"rule": "jQuery-$(",
"path": "js/views/install_view.js",
"line": " this.$(DEVICE_NAME_SELECTOR).val(deviceName || window.getHostName());",
- "lineNumber": 154,
+ "lineNumber": 155,
"reasonCategory": "usageTrusted",
"updated": "2018-09-19T21:59:32.770Z",
"reasonDetail": "Protected from arbitrary input"
@@ -633,7 +633,7 @@
"rule": "jQuery-$(",
"path": "js/views/install_view.js",
"line": " this.$('#link-phone').submit();",
- "lineNumber": 159,
+ "lineNumber": 160,
"reasonCategory": "usageTrusted",
"updated": "2018-09-19T21:59:32.770Z",
"reasonDetail": "Protected from arbitrary input"
@@ -642,7 +642,7 @@
"rule": "jQuery-$(",
"path": "js/views/install_view.js",
"line": " this.$('#link-phone').submit(e => {",
- "lineNumber": 169,
+ "lineNumber": 170,
"reasonCategory": "usageTrusted",
"updated": "2018-09-19T21:59:32.770Z",
"reasonDetail": "Protected from arbitrary input"
@@ -651,7 +651,7 @@
"rule": "jQuery-$(",
"path": "js/views/install_view.js",
"line": " let name = this.$(DEVICE_NAME_SELECTOR).val();",
- "lineNumber": 173,
+ "lineNumber": 174,
"reasonCategory": "usageTrusted",
"updated": "2018-09-19T21:59:32.770Z",
"reasonDetail": "Protected from arbitrary input"
@@ -660,7 +660,7 @@
"rule": "jQuery-$(",
"path": "js/views/install_view.js",
"line": " this.$(DEVICE_NAME_SELECTOR).focus();",
- "lineNumber": 176,
+ "lineNumber": 177,
"reasonCategory": "usageTrusted",
"updated": "2018-09-19T21:59:32.770Z",
"reasonDetail": "Protected from arbitrary input"
@@ -11812,7 +11812,7 @@
"rule": "jQuery-wrap(",
"path": "ts/shims/textsecure.ts",
"line": " wrap(",
- "lineNumber": 63,
+ "lineNumber": 64,
"reasonCategory": "falseMatch",
"updated": "2020-02-07T19:52:28.522Z"
}