Passive UUID support
Co-authored-by: Scott Nonnenberg <scott@signal.org>
This commit is contained in:
parent
f64ca0ed21
commit
a90246cbe5
49 changed files with 2226 additions and 776 deletions
310
app/sql.js
310
app/sql.js
|
@ -1,6 +1,7 @@
|
||||||
const { join } = require('path');
|
const { join } = require('path');
|
||||||
const mkdirp = require('mkdirp');
|
const mkdirp = require('mkdirp');
|
||||||
const rimraf = require('rimraf');
|
const rimraf = require('rimraf');
|
||||||
|
const Queue = require('p-queue').default;
|
||||||
const sql = require('@journeyapps/sqlcipher');
|
const sql = require('@journeyapps/sqlcipher');
|
||||||
const { app, dialog, clipboard } = require('electron');
|
const { app, dialog, clipboard } = require('electron');
|
||||||
const { redactAll } = require('../js/modules/privacy');
|
const { redactAll } = require('../js/modules/privacy');
|
||||||
|
@ -15,6 +16,7 @@ const {
|
||||||
isNumber,
|
isNumber,
|
||||||
isObject,
|
isObject,
|
||||||
isString,
|
isString,
|
||||||
|
keyBy,
|
||||||
last,
|
last,
|
||||||
map,
|
map,
|
||||||
pick,
|
pick,
|
||||||
|
@ -57,10 +59,10 @@ module.exports = {
|
||||||
createOrUpdateSession,
|
createOrUpdateSession,
|
||||||
createOrUpdateSessions,
|
createOrUpdateSessions,
|
||||||
getSessionById,
|
getSessionById,
|
||||||
getSessionsByNumber,
|
getSessionsById,
|
||||||
bulkAddSessions,
|
bulkAddSessions,
|
||||||
removeSessionById,
|
removeSessionById,
|
||||||
removeSessionsByNumber,
|
removeSessionsById,
|
||||||
removeAllSessions,
|
removeAllSessions,
|
||||||
getAllSessions,
|
getAllSessions,
|
||||||
|
|
||||||
|
@ -1181,6 +1183,7 @@ async function updateToSchemaVersion18(currentVersion, instance) {
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function updateToSchemaVersion19(currentVersion, instance) {
|
async function updateToSchemaVersion19(currentVersion, instance) {
|
||||||
if (currentVersion >= 19) {
|
if (currentVersion >= 19) {
|
||||||
return;
|
return;
|
||||||
|
@ -1210,6 +1213,230 @@ async function updateToSchemaVersion19(currentVersion, instance) {
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function updateToSchemaVersion20(currentVersion, instance) {
|
||||||
|
if (currentVersion >= 20) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('updateToSchemaVersion20: starting...');
|
||||||
|
await instance.run('BEGIN TRANSACTION;');
|
||||||
|
|
||||||
|
try {
|
||||||
|
const migrationJobQueue = new Queue({ concurrency: 10 });
|
||||||
|
// The triggers on the messages table slow down this migration
|
||||||
|
// significantly, so we drop them and recreate them later.
|
||||||
|
// Drop triggers
|
||||||
|
const triggers = await instance.all(
|
||||||
|
'SELECT * FROM sqlite_master WHERE type = "trigger" AND tbl_name = "messages"'
|
||||||
|
);
|
||||||
|
|
||||||
|
// eslint-disable-next-line no-restricted-syntax
|
||||||
|
for (const trigger of triggers) {
|
||||||
|
// eslint-disable-next-line no-await-in-loop
|
||||||
|
await instance.run(`DROP TRIGGER ${trigger.name}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create new columns and indices
|
||||||
|
await instance.run('ALTER TABLE conversations ADD COLUMN e164 TEXT;');
|
||||||
|
await instance.run('ALTER TABLE conversations ADD COLUMN uuid TEXT;');
|
||||||
|
await instance.run('ALTER TABLE conversations ADD COLUMN groupId TEXT;');
|
||||||
|
await instance.run('ALTER TABLE messages ADD COLUMN sourceUuid TEXT;');
|
||||||
|
await instance.run(
|
||||||
|
'ALTER TABLE sessions RENAME COLUMN number TO conversationId;'
|
||||||
|
);
|
||||||
|
await instance.run(
|
||||||
|
'CREATE INDEX conversations_e164 ON conversations(e164);'
|
||||||
|
);
|
||||||
|
await instance.run(
|
||||||
|
'CREATE INDEX conversations_uuid ON conversations(uuid);'
|
||||||
|
);
|
||||||
|
await instance.run(
|
||||||
|
'CREATE INDEX conversations_groupId ON conversations(groupId);'
|
||||||
|
);
|
||||||
|
await instance.run(
|
||||||
|
'CREATE INDEX messages_sourceUuid on messages(sourceUuid);'
|
||||||
|
);
|
||||||
|
|
||||||
|
// Migrate existing IDs
|
||||||
|
await instance.run(
|
||||||
|
"UPDATE conversations SET e164 = '+' || id WHERE type = 'private';"
|
||||||
|
);
|
||||||
|
await instance.run(
|
||||||
|
"UPDATE conversations SET groupId = id WHERE type = 'group';"
|
||||||
|
);
|
||||||
|
|
||||||
|
// Drop invalid groups and any associated messages
|
||||||
|
const maybeInvalidGroups = await instance.all(
|
||||||
|
"SELECT * FROM conversations WHERE type = 'group' AND members IS NULL;"
|
||||||
|
);
|
||||||
|
// eslint-disable-next-line no-restricted-syntax
|
||||||
|
for (const group of maybeInvalidGroups) {
|
||||||
|
const json = JSON.parse(group.json);
|
||||||
|
if (!json.members || !json.members.length) {
|
||||||
|
// eslint-disable-next-line no-await-in-loop
|
||||||
|
await instance.run('DELETE FROM conversations WHERE id = $id;', {
|
||||||
|
$id: json.id,
|
||||||
|
});
|
||||||
|
// eslint-disable-next-line no-await-in-loop
|
||||||
|
await instance.run('DELETE FROM messages WHERE conversationId = $id;', {
|
||||||
|
$id: json.id,
|
||||||
|
});
|
||||||
|
// eslint-disable-next-line no-await-in-loop
|
||||||
|
// await instance.run('DELETE FROM sessions WHERE conversationId = $id;', {
|
||||||
|
// $id: json.id,
|
||||||
|
// });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate new IDs and alter data
|
||||||
|
const allConversations = await instance.all('SELECT * FROM conversations;');
|
||||||
|
const allConversationsByOldId = keyBy(allConversations, 'id');
|
||||||
|
|
||||||
|
// eslint-disable-next-line no-restricted-syntax
|
||||||
|
for (const row of allConversations) {
|
||||||
|
const oldId = row.id;
|
||||||
|
const newId = generateUUID();
|
||||||
|
allConversationsByOldId[oldId].id = newId;
|
||||||
|
const patchObj = { id: newId };
|
||||||
|
if (row.type === 'private') {
|
||||||
|
patchObj.e164 = `+${oldId}`;
|
||||||
|
} else if (row.type === 'group') {
|
||||||
|
patchObj.groupId = oldId;
|
||||||
|
}
|
||||||
|
const patch = JSON.stringify(patchObj);
|
||||||
|
// eslint-disable-next-line no-await-in-loop
|
||||||
|
await instance.run(
|
||||||
|
'UPDATE conversations SET id = $newId, json = JSON_PATCH(json, $patch) WHERE id = $oldId',
|
||||||
|
{
|
||||||
|
$newId: newId,
|
||||||
|
$oldId: oldId,
|
||||||
|
$patch: patch,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
const messagePatch = JSON.stringify({ conversationId: newId });
|
||||||
|
// eslint-disable-next-line no-await-in-loop
|
||||||
|
await instance.run(
|
||||||
|
'UPDATE messages SET conversationId = $newId, json = JSON_PATCH(json, $patch) WHERE conversationId = $oldId',
|
||||||
|
{ $newId: newId, $oldId: oldId, $patch: messagePatch }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const groupConverations = await instance.all(
|
||||||
|
"SELECT * FROM conversations WHERE type = 'group';"
|
||||||
|
);
|
||||||
|
|
||||||
|
// Update group conversations, point members at new conversation ids
|
||||||
|
migrationJobQueue.addAll(
|
||||||
|
groupConverations.map(groupRow => async () => {
|
||||||
|
const members = groupRow.members.split(/\s?\+/).filter(Boolean);
|
||||||
|
const newMembers = [];
|
||||||
|
// eslint-disable-next-line no-restricted-syntax
|
||||||
|
for (const m of members) {
|
||||||
|
const memberRow = allConversationsByOldId[m];
|
||||||
|
|
||||||
|
if (memberRow) {
|
||||||
|
newMembers.push(memberRow.id);
|
||||||
|
} else {
|
||||||
|
// We didn't previously have a private conversation for this member,
|
||||||
|
// we need to create one
|
||||||
|
const id = generateUUID();
|
||||||
|
// eslint-disable-next-line no-await-in-loop
|
||||||
|
await saveConversation(
|
||||||
|
{
|
||||||
|
id,
|
||||||
|
e164: m,
|
||||||
|
type: 'private',
|
||||||
|
version: 2,
|
||||||
|
unreadCount: 0,
|
||||||
|
verified: 0,
|
||||||
|
},
|
||||||
|
instance
|
||||||
|
);
|
||||||
|
|
||||||
|
newMembers.push(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const json = { ...jsonToObject(groupRow.json), members: newMembers };
|
||||||
|
const newMembersValue = newMembers.join(' ');
|
||||||
|
await instance.run(
|
||||||
|
'UPDATE conversations SET members = $newMembersValue, json = $newJsonValue WHERE id = $id',
|
||||||
|
{
|
||||||
|
$id: groupRow.id,
|
||||||
|
$newMembersValue: newMembersValue,
|
||||||
|
$newJsonValue: objectToJSON(json),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
// Wait for group conversation updates to finish
|
||||||
|
await migrationJobQueue.onEmpty();
|
||||||
|
|
||||||
|
// Update sessions to stable IDs
|
||||||
|
const allSessions = await instance.all('SELECT * FROM sessions;');
|
||||||
|
// eslint-disable-next-line no-restricted-syntax
|
||||||
|
for (const session of allSessions) {
|
||||||
|
// Not using patch here so we can explicitly delete a property rather than
|
||||||
|
// implicitly delete via null
|
||||||
|
const newJson = JSON.parse(session.json);
|
||||||
|
const conversation = allConversationsByOldId[newJson.number.substr(1)];
|
||||||
|
if (conversation) {
|
||||||
|
newJson.conversationId = conversation.id;
|
||||||
|
newJson.id = `${newJson.conversationId}.${newJson.deviceId}`;
|
||||||
|
}
|
||||||
|
delete newJson.number;
|
||||||
|
// eslint-disable-next-line no-await-in-loop
|
||||||
|
await instance.run(
|
||||||
|
`
|
||||||
|
UPDATE sessions
|
||||||
|
SET id = $newId, json = $newJson, conversationId = $newConversationId
|
||||||
|
WHERE id = $oldId
|
||||||
|
`,
|
||||||
|
{
|
||||||
|
$newId: newJson.id,
|
||||||
|
$newJson: objectToJSON(newJson),
|
||||||
|
$oldId: session.id,
|
||||||
|
$newConversationId: newJson.conversationId,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update identity keys to stable IDs
|
||||||
|
const allIdentityKeys = await instance.all('SELECT * FROM identityKeys;');
|
||||||
|
// eslint-disable-next-line no-restricted-syntax
|
||||||
|
for (const identityKey of allIdentityKeys) {
|
||||||
|
const newJson = JSON.parse(identityKey.json);
|
||||||
|
newJson.id = allConversationsByOldId[newJson.id];
|
||||||
|
// eslint-disable-next-line no-await-in-loop
|
||||||
|
await instance.run(
|
||||||
|
`
|
||||||
|
UPDATE identityKeys
|
||||||
|
SET id = $newId, json = $newJson
|
||||||
|
WHERE id = $oldId
|
||||||
|
`,
|
||||||
|
{
|
||||||
|
$newId: newJson.id,
|
||||||
|
$newJson: objectToJSON(newJson),
|
||||||
|
$oldId: identityKey.id,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Recreate triggers
|
||||||
|
// eslint-disable-next-line no-restricted-syntax
|
||||||
|
for (const trigger of triggers) {
|
||||||
|
// eslint-disable-next-line no-await-in-loop
|
||||||
|
await instance.run(trigger.sql);
|
||||||
|
}
|
||||||
|
|
||||||
|
await instance.run('PRAGMA user_version = 20;');
|
||||||
|
await instance.run('COMMIT TRANSACTION;');
|
||||||
|
} catch (error) {
|
||||||
|
await instance.run('ROLLBACK;');
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const SCHEMA_VERSIONS = [
|
const SCHEMA_VERSIONS = [
|
||||||
updateToSchemaVersion1,
|
updateToSchemaVersion1,
|
||||||
updateToSchemaVersion2,
|
updateToSchemaVersion2,
|
||||||
|
@ -1230,6 +1457,7 @@ const SCHEMA_VERSIONS = [
|
||||||
updateToSchemaVersion17,
|
updateToSchemaVersion17,
|
||||||
updateToSchemaVersion18,
|
updateToSchemaVersion18,
|
||||||
updateToSchemaVersion19,
|
updateToSchemaVersion19,
|
||||||
|
updateToSchemaVersion20,
|
||||||
];
|
];
|
||||||
|
|
||||||
async function updateSchema(instance) {
|
async function updateSchema(instance) {
|
||||||
|
@ -1479,31 +1707,31 @@ async function removeAllItems() {
|
||||||
|
|
||||||
const SESSIONS_TABLE = 'sessions';
|
const SESSIONS_TABLE = 'sessions';
|
||||||
async function createOrUpdateSession(data) {
|
async function createOrUpdateSession(data) {
|
||||||
const { id, number } = data;
|
const { id, conversationId } = data;
|
||||||
if (!id) {
|
if (!id) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
'createOrUpdateSession: Provided data did not have a truthy id'
|
'createOrUpdateSession: Provided data did not have a truthy id'
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (!number) {
|
if (!conversationId) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
'createOrUpdateSession: Provided data did not have a truthy number'
|
'createOrUpdateSession: Provided data did not have a truthy conversationId'
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
await db.run(
|
await db.run(
|
||||||
`INSERT OR REPLACE INTO sessions (
|
`INSERT OR REPLACE INTO sessions (
|
||||||
id,
|
id,
|
||||||
number,
|
conversationId,
|
||||||
json
|
json
|
||||||
) values (
|
) values (
|
||||||
$id,
|
$id,
|
||||||
$number,
|
$conversationId,
|
||||||
$json
|
$json
|
||||||
)`,
|
)`,
|
||||||
{
|
{
|
||||||
$id: id,
|
$id: id,
|
||||||
$number: number,
|
$conversationId: conversationId,
|
||||||
$json: objectToJSON(data),
|
$json: objectToJSON(data),
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
@ -1524,10 +1752,13 @@ createOrUpdateSessions.needsSerial = true;
|
||||||
async function getSessionById(id) {
|
async function getSessionById(id) {
|
||||||
return getById(SESSIONS_TABLE, id);
|
return getById(SESSIONS_TABLE, id);
|
||||||
}
|
}
|
||||||
async function getSessionsByNumber(number) {
|
async function getSessionsById(id) {
|
||||||
const rows = await db.all('SELECT * FROM sessions WHERE number = $number;', {
|
const rows = await db.all(
|
||||||
$number: number,
|
'SELECT * FROM sessions WHERE conversationId = $id;',
|
||||||
});
|
{
|
||||||
|
$id: id,
|
||||||
|
}
|
||||||
|
);
|
||||||
return map(rows, row => jsonToObject(row.json));
|
return map(rows, row => jsonToObject(row.json));
|
||||||
}
|
}
|
||||||
async function bulkAddSessions(array) {
|
async function bulkAddSessions(array) {
|
||||||
|
@ -1536,9 +1767,9 @@ async function bulkAddSessions(array) {
|
||||||
async function removeSessionById(id) {
|
async function removeSessionById(id) {
|
||||||
return removeById(SESSIONS_TABLE, id);
|
return removeById(SESSIONS_TABLE, id);
|
||||||
}
|
}
|
||||||
async function removeSessionsByNumber(number) {
|
async function removeSessionsById(id) {
|
||||||
await db.run('DELETE FROM sessions WHERE number = $number;', {
|
await db.run('DELETE FROM sessions WHERE conversationId = $id;', {
|
||||||
$number: number,
|
$id: id,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
async function removeAllSessions() {
|
async function removeAllSessions() {
|
||||||
|
@ -1634,23 +1865,30 @@ async function getConversationCount() {
|
||||||
return row['count(*)'];
|
return row['count(*)'];
|
||||||
}
|
}
|
||||||
|
|
||||||
async function saveConversation(data) {
|
async function saveConversation(data, instance = db) {
|
||||||
const {
|
const {
|
||||||
id,
|
|
||||||
// eslint-disable-next-line camelcase
|
// eslint-disable-next-line camelcase
|
||||||
active_at,
|
active_at,
|
||||||
type,
|
e164,
|
||||||
|
groupId,
|
||||||
|
id,
|
||||||
members,
|
members,
|
||||||
name,
|
name,
|
||||||
profileName,
|
|
||||||
profileFamilyName,
|
profileFamilyName,
|
||||||
|
profileName,
|
||||||
|
type,
|
||||||
|
uuid,
|
||||||
} = data;
|
} = data;
|
||||||
|
|
||||||
await db.run(
|
await instance.run(
|
||||||
`INSERT INTO conversations (
|
`INSERT INTO conversations (
|
||||||
id,
|
id,
|
||||||
json,
|
json,
|
||||||
|
|
||||||
|
e164,
|
||||||
|
uuid,
|
||||||
|
groupId,
|
||||||
|
|
||||||
active_at,
|
active_at,
|
||||||
type,
|
type,
|
||||||
members,
|
members,
|
||||||
|
@ -1662,6 +1900,10 @@ async function saveConversation(data) {
|
||||||
$id,
|
$id,
|
||||||
$json,
|
$json,
|
||||||
|
|
||||||
|
$e164,
|
||||||
|
$uuid,
|
||||||
|
$groupId,
|
||||||
|
|
||||||
$active_at,
|
$active_at,
|
||||||
$type,
|
$type,
|
||||||
$members,
|
$members,
|
||||||
|
@ -1674,6 +1916,10 @@ async function saveConversation(data) {
|
||||||
$id: id,
|
$id: id,
|
||||||
$json: objectToJSON(data),
|
$json: objectToJSON(data),
|
||||||
|
|
||||||
|
$e164: e164,
|
||||||
|
$uuid: uuid,
|
||||||
|
$groupId: groupId,
|
||||||
|
|
||||||
$active_at: active_at,
|
$active_at: active_at,
|
||||||
$type: type,
|
$type: type,
|
||||||
$members: members ? members.join(' ') : null,
|
$members: members ? members.join(' ') : null,
|
||||||
|
@ -1713,12 +1959,17 @@ async function updateConversation(data) {
|
||||||
name,
|
name,
|
||||||
profileName,
|
profileName,
|
||||||
profileFamilyName,
|
profileFamilyName,
|
||||||
|
e164,
|
||||||
|
uuid,
|
||||||
} = data;
|
} = data;
|
||||||
|
|
||||||
await db.run(
|
await db.run(
|
||||||
`UPDATE conversations SET
|
`UPDATE conversations SET
|
||||||
json = $json,
|
json = $json,
|
||||||
|
|
||||||
|
e164 = $e164,
|
||||||
|
uuid = $uuid,
|
||||||
|
|
||||||
active_at = $active_at,
|
active_at = $active_at,
|
||||||
type = $type,
|
type = $type,
|
||||||
members = $members,
|
members = $members,
|
||||||
|
@ -1731,6 +1982,9 @@ async function updateConversation(data) {
|
||||||
$id: id,
|
$id: id,
|
||||||
$json: objectToJSON(data),
|
$json: objectToJSON(data),
|
||||||
|
|
||||||
|
$e164: e164,
|
||||||
|
$uuid: uuid,
|
||||||
|
|
||||||
$active_at: active_at,
|
$active_at: active_at,
|
||||||
$type: type,
|
$type: type,
|
||||||
$members: members ? members.join(' ') : null,
|
$members: members ? members.join(' ') : null,
|
||||||
|
@ -1920,6 +2174,7 @@ async function saveMessage(data, { forceSave } = {}) {
|
||||||
// eslint-disable-next-line camelcase
|
// eslint-disable-next-line camelcase
|
||||||
sent_at,
|
sent_at,
|
||||||
source,
|
source,
|
||||||
|
sourceUuid,
|
||||||
sourceDevice,
|
sourceDevice,
|
||||||
type,
|
type,
|
||||||
unread,
|
unread,
|
||||||
|
@ -1945,6 +2200,7 @@ async function saveMessage(data, { forceSave } = {}) {
|
||||||
$schemaVersion: schemaVersion,
|
$schemaVersion: schemaVersion,
|
||||||
$sent_at: sent_at,
|
$sent_at: sent_at,
|
||||||
$source: source,
|
$source: source,
|
||||||
|
$sourceUuid: sourceUuid,
|
||||||
$sourceDevice: sourceDevice,
|
$sourceDevice: sourceDevice,
|
||||||
$type: type,
|
$type: type,
|
||||||
$unread: unread,
|
$unread: unread,
|
||||||
|
@ -1970,6 +2226,7 @@ async function saveMessage(data, { forceSave } = {}) {
|
||||||
schemaVersion = $schemaVersion,
|
schemaVersion = $schemaVersion,
|
||||||
sent_at = $sent_at,
|
sent_at = $sent_at,
|
||||||
source = $source,
|
source = $source,
|
||||||
|
sourceUuid = $sourceUuid,
|
||||||
sourceDevice = $sourceDevice,
|
sourceDevice = $sourceDevice,
|
||||||
type = $type,
|
type = $type,
|
||||||
unread = $unread
|
unread = $unread
|
||||||
|
@ -2004,6 +2261,7 @@ async function saveMessage(data, { forceSave } = {}) {
|
||||||
schemaVersion,
|
schemaVersion,
|
||||||
sent_at,
|
sent_at,
|
||||||
source,
|
source,
|
||||||
|
sourceUuid,
|
||||||
sourceDevice,
|
sourceDevice,
|
||||||
type,
|
type,
|
||||||
unread
|
unread
|
||||||
|
@ -2025,6 +2283,7 @@ async function saveMessage(data, { forceSave } = {}) {
|
||||||
$schemaVersion,
|
$schemaVersion,
|
||||||
$sent_at,
|
$sent_at,
|
||||||
$source,
|
$source,
|
||||||
|
$sourceUuid,
|
||||||
$sourceDevice,
|
$sourceDevice,
|
||||||
$type,
|
$type,
|
||||||
$unread
|
$unread
|
||||||
|
@ -2095,14 +2354,21 @@ async function getAllMessageIds() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line camelcase
|
// eslint-disable-next-line camelcase
|
||||||
async function getMessageBySender({ source, sourceDevice, sent_at }) {
|
async function getMessageBySender({
|
||||||
|
source,
|
||||||
|
sourceUuid,
|
||||||
|
sourceDevice,
|
||||||
|
// eslint-disable-next-line camelcase
|
||||||
|
sent_at,
|
||||||
|
}) {
|
||||||
const rows = await db.all(
|
const rows = await db.all(
|
||||||
`SELECT json FROM messages WHERE
|
`SELECT json FROM messages WHERE
|
||||||
source = $source AND
|
(source = $source OR sourceUuid = $sourceUuid) AND
|
||||||
sourceDevice = $sourceDevice AND
|
sourceDevice = $sourceDevice AND
|
||||||
sent_at = $sent_at;`,
|
sent_at = $sent_at;`,
|
||||||
{
|
{
|
||||||
$source: source,
|
$source: source,
|
||||||
|
$sourceUuid: sourceUuid,
|
||||||
$sourceDevice: sourceDevice,
|
$sourceDevice: sourceDevice,
|
||||||
$sent_at: sent_at,
|
$sent_at: sent_at,
|
||||||
}
|
}
|
||||||
|
|
253
js/background.js
253
js/background.js
|
@ -25,7 +25,7 @@
|
||||||
wait: 500,
|
wait: 500,
|
||||||
maxSize: 500,
|
maxSize: 500,
|
||||||
processBatch: async items => {
|
processBatch: async items => {
|
||||||
const bySource = _.groupBy(items, item => item.source);
|
const bySource = _.groupBy(items, item => item.source || item.sourceUuid);
|
||||||
const sources = Object.keys(bySource);
|
const sources = Object.keys(bySource);
|
||||||
|
|
||||||
for (let i = 0, max = sources.length; i < max; i += 1) {
|
for (let i = 0, max = sources.length; i < max; i += 1) {
|
||||||
|
@ -33,13 +33,15 @@
|
||||||
const timestamps = bySource[source].map(item => item.timestamp);
|
const timestamps = bySource[source].map(item => item.timestamp);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
const c = ConversationController.get(source);
|
||||||
const { wrap, sendOptions } = ConversationController.prepareForSend(
|
const { wrap, sendOptions } = ConversationController.prepareForSend(
|
||||||
source
|
c.get('id')
|
||||||
);
|
);
|
||||||
// eslint-disable-next-line no-await-in-loop
|
// eslint-disable-next-line no-await-in-loop
|
||||||
await wrap(
|
await wrap(
|
||||||
textsecure.messaging.sendDeliveryReceipt(
|
textsecure.messaging.sendDeliveryReceipt(
|
||||||
source,
|
c.get('e164'),
|
||||||
|
c.get('uuid'),
|
||||||
timestamps,
|
timestamps,
|
||||||
sendOptions
|
sendOptions
|
||||||
)
|
)
|
||||||
|
@ -234,13 +236,23 @@
|
||||||
let accountManager;
|
let accountManager;
|
||||||
window.getAccountManager = () => {
|
window.getAccountManager = () => {
|
||||||
if (!accountManager) {
|
if (!accountManager) {
|
||||||
const USERNAME = storage.get('number_id');
|
const OLD_USERNAME = storage.get('number_id');
|
||||||
|
const USERNAME = storage.get('uuid_id');
|
||||||
const PASSWORD = storage.get('password');
|
const PASSWORD = storage.get('password');
|
||||||
accountManager = new textsecure.AccountManager(USERNAME, PASSWORD);
|
accountManager = new textsecure.AccountManager(
|
||||||
|
USERNAME || OLD_USERNAME,
|
||||||
|
PASSWORD
|
||||||
|
);
|
||||||
accountManager.addEventListener('registration', () => {
|
accountManager.addEventListener('registration', () => {
|
||||||
|
const ourNumber = textsecure.storage.user.getNumber();
|
||||||
|
const ourUuid = textsecure.storage.user.getUuid();
|
||||||
const user = {
|
const user = {
|
||||||
regionCode: window.storage.get('regionCode'),
|
regionCode: window.storage.get('regionCode'),
|
||||||
ourNumber: textsecure.storage.user.getNumber(),
|
ourNumber,
|
||||||
|
ourUuid,
|
||||||
|
ourConversationId: ConversationController.getConversationId(
|
||||||
|
ourNumber || ourUuid
|
||||||
|
),
|
||||||
};
|
};
|
||||||
Whisper.events.trigger('userChanged', user);
|
Whisper.events.trigger('userChanged', user);
|
||||||
|
|
||||||
|
@ -580,6 +592,11 @@
|
||||||
const conversations = convoCollection.map(
|
const conversations = convoCollection.map(
|
||||||
conversation => conversation.cachedProps
|
conversation => conversation.cachedProps
|
||||||
);
|
);
|
||||||
|
const ourNumber = textsecure.storage.user.getNumber();
|
||||||
|
const ourUuid = textsecure.storage.user.getUuid();
|
||||||
|
const ourConversationId = ConversationController.getConversationId(
|
||||||
|
ourNumber || ourUuid
|
||||||
|
);
|
||||||
const initialState = {
|
const initialState = {
|
||||||
conversations: {
|
conversations: {
|
||||||
conversationLookup: Signal.Util.makeLookup(conversations, 'id'),
|
conversationLookup: Signal.Util.makeLookup(conversations, 'id'),
|
||||||
|
@ -598,7 +615,9 @@
|
||||||
stickersPath: window.baseStickersPath,
|
stickersPath: window.baseStickersPath,
|
||||||
tempPath: window.baseTempPath,
|
tempPath: window.baseTempPath,
|
||||||
regionCode: window.storage.get('regionCode'),
|
regionCode: window.storage.get('regionCode'),
|
||||||
ourNumber: textsecure.storage.user.getNumber(),
|
ourConversationId,
|
||||||
|
ourNumber,
|
||||||
|
ourUuid,
|
||||||
platform: window.platform,
|
platform: window.platform,
|
||||||
i18n: window.i18n,
|
i18n: window.i18n,
|
||||||
interactionMode: window.getInteractionMode(),
|
interactionMode: window.getInteractionMode(),
|
||||||
|
@ -1508,7 +1527,8 @@
|
||||||
messageReceiver = null;
|
messageReceiver = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const USERNAME = storage.get('number_id');
|
const OLD_USERNAME = storage.get('number_id');
|
||||||
|
const USERNAME = storage.get('uuid_id');
|
||||||
const PASSWORD = storage.get('password');
|
const PASSWORD = storage.get('password');
|
||||||
const mySignalingKey = storage.get('signaling_key');
|
const mySignalingKey = storage.get('signaling_key');
|
||||||
|
|
||||||
|
@ -1524,6 +1544,7 @@
|
||||||
// initialize the socket and start listening for messages
|
// initialize the socket and start listening for messages
|
||||||
window.log.info('Initializing socket and listening for messages');
|
window.log.info('Initializing socket and listening for messages');
|
||||||
messageReceiver = new textsecure.MessageReceiver(
|
messageReceiver = new textsecure.MessageReceiver(
|
||||||
|
OLD_USERNAME,
|
||||||
USERNAME,
|
USERNAME,
|
||||||
PASSWORD,
|
PASSWORD,
|
||||||
mySignalingKey,
|
mySignalingKey,
|
||||||
|
@ -1569,7 +1590,7 @@
|
||||||
});
|
});
|
||||||
|
|
||||||
window.textsecure.messaging = new textsecure.MessageSender(
|
window.textsecure.messaging = new textsecure.MessageSender(
|
||||||
USERNAME,
|
USERNAME || OLD_USERNAME,
|
||||||
PASSWORD
|
PASSWORD
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -1605,7 +1626,10 @@
|
||||||
|
|
||||||
const udSupportKey = 'hasRegisterSupportForUnauthenticatedDelivery';
|
const udSupportKey = 'hasRegisterSupportForUnauthenticatedDelivery';
|
||||||
if (!storage.get(udSupportKey)) {
|
if (!storage.get(udSupportKey)) {
|
||||||
const server = WebAPI.connect({ username: USERNAME, password: PASSWORD });
|
const server = WebAPI.connect({
|
||||||
|
username: USERNAME || OLD_USERNAME,
|
||||||
|
password: PASSWORD,
|
||||||
|
});
|
||||||
try {
|
try {
|
||||||
await server.registerSupportForUnauthenticatedDelivery();
|
await server.registerSupportForUnauthenticatedDelivery();
|
||||||
storage.put(udSupportKey, true);
|
storage.put(udSupportKey, true);
|
||||||
|
@ -1617,7 +1641,50 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const hasRegisteredUuidSupportKey = 'hasRegisteredUuidSupport';
|
||||||
|
if (
|
||||||
|
!storage.get(hasRegisteredUuidSupportKey) &&
|
||||||
|
textsecure.storage.user.getUuid()
|
||||||
|
) {
|
||||||
|
const server = WebAPI.connect({
|
||||||
|
username: USERNAME || OLD_USERNAME,
|
||||||
|
password: PASSWORD,
|
||||||
|
});
|
||||||
|
try {
|
||||||
|
await server.registerCapabilities({ uuid: true });
|
||||||
|
storage.put(hasRegisteredUuidSupportKey, true);
|
||||||
|
} catch (error) {
|
||||||
|
window.log.error(
|
||||||
|
'Error: Unable to register support for UUID messages.',
|
||||||
|
error && error.stack ? error.stack : error
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const deviceId = textsecure.storage.user.getDeviceId();
|
const deviceId = textsecure.storage.user.getDeviceId();
|
||||||
|
|
||||||
|
if (!textsecure.storage.user.getUuid()) {
|
||||||
|
const server = WebAPI.connect({
|
||||||
|
username: OLD_USERNAME,
|
||||||
|
password: PASSWORD,
|
||||||
|
});
|
||||||
|
try {
|
||||||
|
const { uuid } = await server.whoami();
|
||||||
|
textsecure.storage.user.setUuidAndDeviceId(uuid, deviceId);
|
||||||
|
const ourNumber = textsecure.storage.user.getNumber();
|
||||||
|
const me = await ConversationController.getOrCreateAndWait(
|
||||||
|
ourNumber,
|
||||||
|
'private'
|
||||||
|
);
|
||||||
|
me.updateUuid(uuid);
|
||||||
|
} catch (error) {
|
||||||
|
window.log.error(
|
||||||
|
'Error: Unable to retrieve UUID from service.',
|
||||||
|
error && error.stack ? error.stack : error
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (firstRun === true && deviceId !== '1') {
|
if (firstRun === true && deviceId !== '1') {
|
||||||
const hasThemeSetting = Boolean(storage.get('theme-setting'));
|
const hasThemeSetting = Boolean(storage.get('theme-setting'));
|
||||||
if (!hasThemeSetting && textsecure.storage.get('userAgent') === 'OWI') {
|
if (!hasThemeSetting && textsecure.storage.get('userAgent') === 'OWI') {
|
||||||
|
@ -1639,9 +1706,10 @@
|
||||||
Whisper.events.trigger('contactsync');
|
Whisper.events.trigger('contactsync');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const ourUuid = textsecure.storage.user.getUuid();
|
||||||
const ourNumber = textsecure.storage.user.getNumber();
|
const ourNumber = textsecure.storage.user.getNumber();
|
||||||
const { wrap, sendOptions } = ConversationController.prepareForSend(
|
const { wrap, sendOptions } = ConversationController.prepareForSend(
|
||||||
ourNumber,
|
ourNumber || ourUuid,
|
||||||
{
|
{
|
||||||
syncMessage: true,
|
syncMessage: true,
|
||||||
}
|
}
|
||||||
|
@ -1766,7 +1834,7 @@
|
||||||
function onTyping(ev) {
|
function onTyping(ev) {
|
||||||
// Note: this type of message is automatically removed from cache in MessageReceiver
|
// Note: this type of message is automatically removed from cache in MessageReceiver
|
||||||
|
|
||||||
const { typing, sender, senderDevice } = ev;
|
const { typing, sender, senderUuid, senderDevice } = ev;
|
||||||
const { groupId, started } = typing || {};
|
const { groupId, started } = typing || {};
|
||||||
|
|
||||||
// We don't do anything with incoming typing messages if the setting is disabled
|
// We don't do anything with incoming typing messages if the setting is disabled
|
||||||
|
@ -1774,12 +1842,18 @@
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const conversation = ConversationController.get(groupId || sender);
|
const conversation = ConversationController.get(
|
||||||
|
groupId || sender || senderUuid
|
||||||
|
);
|
||||||
|
const ourUuid = textsecure.storage.user.getUuid();
|
||||||
const ourNumber = textsecure.storage.user.getNumber();
|
const ourNumber = textsecure.storage.user.getNumber();
|
||||||
|
|
||||||
if (conversation) {
|
if (conversation) {
|
||||||
// We drop typing notifications in groups we're not a part of
|
// We drop typing notifications in groups we're not a part of
|
||||||
if (!conversation.isPrivate() && !conversation.hasMember(ourNumber)) {
|
if (
|
||||||
|
!conversation.isPrivate() &&
|
||||||
|
!conversation.hasMember(ourNumber || ourUuid)
|
||||||
|
) {
|
||||||
window.log.warn(
|
window.log.warn(
|
||||||
`Received typing indicator for group ${conversation.idForLogging()}, which we're not a part of. Dropping.`
|
`Received typing indicator for group ${conversation.idForLogging()}, which we're not a part of. Dropping.`
|
||||||
);
|
);
|
||||||
|
@ -1789,6 +1863,7 @@
|
||||||
conversation.notifyTyping({
|
conversation.notifyTyping({
|
||||||
isTyping: started,
|
isTyping: started,
|
||||||
sender,
|
sender,
|
||||||
|
senderUuid,
|
||||||
senderDevice,
|
senderDevice,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -1833,9 +1908,10 @@
|
||||||
async function onContactReceived(ev) {
|
async function onContactReceived(ev) {
|
||||||
const details = ev.contactDetails;
|
const details = ev.contactDetails;
|
||||||
|
|
||||||
const id = details.number;
|
if (
|
||||||
|
details.number === textsecure.storage.user.getNumber() ||
|
||||||
if (id === textsecure.storage.user.getNumber()) {
|
details.uuid === textsecure.storage.user.getUuid()
|
||||||
|
) {
|
||||||
// special case for syncing details about ourselves
|
// special case for syncing details about ourselves
|
||||||
if (details.profileKey) {
|
if (details.profileKey) {
|
||||||
window.log.info('Got sync message with our own profile key');
|
window.log.info('Got sync message with our own profile key');
|
||||||
|
@ -1844,9 +1920,11 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
const c = new Whisper.Conversation({
|
const c = new Whisper.Conversation({
|
||||||
id,
|
e164: details.number,
|
||||||
|
uuid: details.uuid,
|
||||||
|
type: 'private',
|
||||||
});
|
});
|
||||||
const validationError = c.validateNumber();
|
const validationError = c.validate();
|
||||||
if (validationError) {
|
if (validationError) {
|
||||||
window.log.error(
|
window.log.error(
|
||||||
'Invalid contact received:',
|
'Invalid contact received:',
|
||||||
|
@ -1857,7 +1935,7 @@
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const conversation = await ConversationController.getOrCreateAndWait(
|
const conversation = await ConversationController.getOrCreateAndWait(
|
||||||
id,
|
details.number || details.uuid,
|
||||||
'private'
|
'private'
|
||||||
);
|
);
|
||||||
let activeAt = conversation.get('active_at');
|
let activeAt = conversation.get('active_at');
|
||||||
|
@ -1878,10 +1956,18 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
if (typeof details.blocked !== 'undefined') {
|
if (typeof details.blocked !== 'undefined') {
|
||||||
if (details.blocked) {
|
const e164 = conversation.get('e164');
|
||||||
storage.addBlockedNumber(id);
|
if (details.blocked && e164) {
|
||||||
|
storage.addBlockedNumber(e164);
|
||||||
} else {
|
} else {
|
||||||
storage.removeBlockedNumber(id);
|
storage.removeBlockedNumber(e164);
|
||||||
|
}
|
||||||
|
|
||||||
|
const uuid = conversation.get('uuid');
|
||||||
|
if (details.blocked && uuid) {
|
||||||
|
storage.addBlockedUuid(uuid);
|
||||||
|
} else {
|
||||||
|
storage.removeBlockedUuid(uuid);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1912,17 +1998,21 @@
|
||||||
conversation.set({ avatar: null });
|
conversation.set({ avatar: null });
|
||||||
}
|
}
|
||||||
|
|
||||||
window.Signal.Data.updateConversation(id, conversation.attributes);
|
window.Signal.Data.updateConversation(
|
||||||
|
details.number || details.uuid,
|
||||||
|
conversation.attributes
|
||||||
|
);
|
||||||
|
|
||||||
const { expireTimer } = details;
|
const { expireTimer } = details;
|
||||||
const isValidExpireTimer = typeof expireTimer === 'number';
|
const isValidExpireTimer = typeof expireTimer === 'number';
|
||||||
if (isValidExpireTimer) {
|
if (isValidExpireTimer) {
|
||||||
const source = textsecure.storage.user.getNumber();
|
const sourceE164 = textsecure.storage.user.getNumber();
|
||||||
|
const sourceUuid = textsecure.storage.user.getUuid();
|
||||||
const receivedAt = Date.now();
|
const receivedAt = Date.now();
|
||||||
|
|
||||||
await conversation.updateExpirationTimer(
|
await conversation.updateExpirationTimer(
|
||||||
expireTimer,
|
expireTimer,
|
||||||
source,
|
sourceE164 || sourceUuid,
|
||||||
receivedAt,
|
receivedAt,
|
||||||
{ fromSync: true }
|
{ fromSync: true }
|
||||||
);
|
);
|
||||||
|
@ -1934,6 +2024,7 @@
|
||||||
verifiedEvent.verified = {
|
verifiedEvent.verified = {
|
||||||
state: verified.state,
|
state: verified.state,
|
||||||
destination: verified.destination,
|
destination: verified.destination,
|
||||||
|
destinationUuid: verified.destinationUuid,
|
||||||
identityKey: verified.identityKey.toArrayBuffer(),
|
identityKey: verified.identityKey.toArrayBuffer(),
|
||||||
};
|
};
|
||||||
verifiedEvent.viaContactSync = true;
|
verifiedEvent.viaContactSync = true;
|
||||||
|
@ -1953,9 +2044,23 @@
|
||||||
'group'
|
'group'
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const memberConversations = await Promise.all(
|
||||||
|
(details.members || details.membersE164).map(member => {
|
||||||
|
if (member.e164 || member.uuid) {
|
||||||
|
return ConversationController.getOrCreateAndWait(
|
||||||
|
member.e164 || member.uuid,
|
||||||
|
'private'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return ConversationController.getOrCreateAndWait(member, 'private');
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
const members = memberConversations.map(c => c.get('id'));
|
||||||
|
|
||||||
const updates = {
|
const updates = {
|
||||||
name: details.name,
|
name: details.name,
|
||||||
members: details.members,
|
members,
|
||||||
color: details.color,
|
color: details.color,
|
||||||
type: 'group',
|
type: 'group',
|
||||||
};
|
};
|
||||||
|
@ -2004,11 +2109,17 @@
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const source = textsecure.storage.user.getNumber();
|
const sourceE164 = textsecure.storage.user.getNumber();
|
||||||
|
const sourceUuid = textsecure.storage.user.getUuid();
|
||||||
const receivedAt = Date.now();
|
const receivedAt = Date.now();
|
||||||
await conversation.updateExpirationTimer(expireTimer, source, receivedAt, {
|
await conversation.updateExpirationTimer(
|
||||||
fromSync: true,
|
expireTimer,
|
||||||
});
|
sourceE164 || sourceUuid,
|
||||||
|
receivedAt,
|
||||||
|
{
|
||||||
|
fromSync: true,
|
||||||
|
}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Descriptors
|
// Descriptors
|
||||||
|
@ -2024,10 +2135,10 @@
|
||||||
: { type: Message.PRIVATE, id: destination };
|
: { type: Message.PRIVATE, id: destination };
|
||||||
|
|
||||||
// Matches event data from `libtextsecure` `MessageReceiver::handleDataMessage`:
|
// Matches event data from `libtextsecure` `MessageReceiver::handleDataMessage`:
|
||||||
const getDescriptorForReceived = ({ message, source }) =>
|
const getDescriptorForReceived = ({ message, source, sourceUuid }) =>
|
||||||
message.group
|
message.group
|
||||||
? getGroupDescriptor(message.group)
|
? getGroupDescriptor(message.group)
|
||||||
: { type: Message.PRIVATE, id: source };
|
: { type: Message.PRIVATE, id: source || sourceUuid };
|
||||||
|
|
||||||
// Received:
|
// Received:
|
||||||
async function handleMessageReceivedProfileUpdate({
|
async function handleMessageReceivedProfileUpdate({
|
||||||
|
@ -2069,11 +2180,18 @@
|
||||||
|
|
||||||
const message = await initIncomingMessage(data);
|
const message = await initIncomingMessage(data);
|
||||||
|
|
||||||
await ConversationController.getOrCreateAndWait(
|
const result = await ConversationController.getOrCreateAndWait(
|
||||||
messageDescriptor.id,
|
messageDescriptor.id,
|
||||||
messageDescriptor.type
|
messageDescriptor.type
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (messageDescriptor.type === 'private') {
|
||||||
|
result.updateE164(data.source);
|
||||||
|
if (data.sourceUuid) {
|
||||||
|
result.updateUuid(data.sourceUuid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (data.message.reaction) {
|
if (data.message.reaction) {
|
||||||
const { reaction } = data.message;
|
const { reaction } = data.message;
|
||||||
const reactionModel = Whisper.Reactions.add({
|
const reactionModel = Whisper.Reactions.add({
|
||||||
|
@ -2083,7 +2201,7 @@
|
||||||
targetAuthorUuid: reaction.targetAuthorUuid,
|
targetAuthorUuid: reaction.targetAuthorUuid,
|
||||||
targetTimestamp: reaction.targetTimestamp.toNumber(),
|
targetTimestamp: reaction.targetTimestamp.toNumber(),
|
||||||
timestamp: Date.now(),
|
timestamp: Date.now(),
|
||||||
fromId: data.source,
|
fromId: data.source || data.sourceUuid,
|
||||||
});
|
});
|
||||||
// Note: We do not wait for completion here
|
// Note: We do not wait for completion here
|
||||||
Whisper.Reactions.onReaction(reactionModel);
|
Whisper.Reactions.onReaction(reactionModel);
|
||||||
|
@ -2112,8 +2230,12 @@
|
||||||
|
|
||||||
// Then we update our own profileKey if it's different from what we have
|
// Then we update our own profileKey if it's different from what we have
|
||||||
const ourNumber = textsecure.storage.user.getNumber();
|
const ourNumber = textsecure.storage.user.getNumber();
|
||||||
|
const ourUuid = textsecure.storage.user.getUuid();
|
||||||
const profileKey = data.message.profileKey.toString('base64');
|
const profileKey = data.message.profileKey.toString('base64');
|
||||||
const me = await ConversationController.getOrCreate(ourNumber, 'private');
|
const me = await ConversationController.getOrCreate(
|
||||||
|
ourNumber || ourUuid,
|
||||||
|
'private'
|
||||||
|
);
|
||||||
|
|
||||||
// Will do the save for us if needed
|
// Will do the save for us if needed
|
||||||
await me.setProfileKey(profileKey);
|
await me.setProfileKey(profileKey);
|
||||||
|
@ -2136,6 +2258,7 @@
|
||||||
|
|
||||||
return new Whisper.Message({
|
return new Whisper.Message({
|
||||||
source: textsecure.storage.user.getNumber(),
|
source: textsecure.storage.user.getNumber(),
|
||||||
|
sourceUuid: textsecure.storage.user.getUuid(),
|
||||||
sourceDevice: data.device,
|
sourceDevice: data.device,
|
||||||
sent_at: data.timestamp,
|
sent_at: data.timestamp,
|
||||||
sent_to: sentTo,
|
sent_to: sentTo,
|
||||||
|
@ -2176,6 +2299,7 @@
|
||||||
if (data.message.reaction) {
|
if (data.message.reaction) {
|
||||||
const { reaction } = data.message;
|
const { reaction } = data.message;
|
||||||
const ourNumber = textsecure.storage.user.getNumber();
|
const ourNumber = textsecure.storage.user.getNumber();
|
||||||
|
const ourUuid = textsecure.storage.user.getUuid();
|
||||||
const reactionModel = Whisper.Reactions.add({
|
const reactionModel = Whisper.Reactions.add({
|
||||||
emoji: reaction.emoji,
|
emoji: reaction.emoji,
|
||||||
remove: reaction.remove,
|
remove: reaction.remove,
|
||||||
|
@ -2183,7 +2307,7 @@
|
||||||
targetAuthorUuid: reaction.targetAuthorUuid,
|
targetAuthorUuid: reaction.targetAuthorUuid,
|
||||||
targetTimestamp: reaction.targetTimestamp.toNumber(),
|
targetTimestamp: reaction.targetTimestamp.toNumber(),
|
||||||
timestamp: Date.now(),
|
timestamp: Date.now(),
|
||||||
fromId: ourNumber,
|
fromId: ourNumber || ourUuid,
|
||||||
fromSync: true,
|
fromSync: true,
|
||||||
});
|
});
|
||||||
// Note: We do not wait for completion here
|
// Note: We do not wait for completion here
|
||||||
|
@ -2197,20 +2321,25 @@
|
||||||
messageDescriptor.id,
|
messageDescriptor.id,
|
||||||
messageDescriptor.type
|
messageDescriptor.type
|
||||||
);
|
);
|
||||||
|
|
||||||
// Don't wait for handleDataMessage, as it has its own per-conversation queueing
|
// Don't wait for handleDataMessage, as it has its own per-conversation queueing
|
||||||
|
|
||||||
message.handleDataMessage(data.message, event.confirm, {
|
message.handleDataMessage(data.message, event.confirm, {
|
||||||
data,
|
data,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async function initIncomingMessage(data) {
|
async function initIncomingMessage(data) {
|
||||||
|
const targetId = data.source || data.sourceUuid;
|
||||||
|
const conversation = ConversationController.get(targetId);
|
||||||
|
const conversationId = conversation ? conversation.id : targetId;
|
||||||
|
|
||||||
return new Whisper.Message({
|
return new Whisper.Message({
|
||||||
source: data.source,
|
source: data.source,
|
||||||
|
sourceUuid: data.sourceUuid,
|
||||||
sourceDevice: data.sourceDevice,
|
sourceDevice: data.sourceDevice,
|
||||||
sent_at: data.timestamp,
|
sent_at: data.timestamp,
|
||||||
received_at: data.receivedAt || Date.now(),
|
received_at: data.receivedAt || Date.now(),
|
||||||
conversationId: data.source,
|
conversationId,
|
||||||
unidentifiedDeliveryReceived: data.unidentifiedDeliveryReceived,
|
unidentifiedDeliveryReceived: data.unidentifiedDeliveryReceived,
|
||||||
type: 'incoming',
|
type: 'incoming',
|
||||||
unread: 1,
|
unread: 1,
|
||||||
|
@ -2384,11 +2513,16 @@
|
||||||
async function onViewSync(ev) {
|
async function onViewSync(ev) {
|
||||||
ev.confirm();
|
ev.confirm();
|
||||||
|
|
||||||
const { source, timestamp } = ev;
|
const { source, sourceUuid, timestamp } = ev;
|
||||||
window.log.info(`view sync ${source} ${timestamp}`);
|
window.log.info(`view sync ${source} ${timestamp}`);
|
||||||
|
const conversationId = ConversationController.getConversationId(
|
||||||
|
source || sourceUuid
|
||||||
|
);
|
||||||
|
|
||||||
const sync = Whisper.ViewSyncs.add({
|
const sync = Whisper.ViewSyncs.add({
|
||||||
source,
|
source,
|
||||||
|
sourceUuid,
|
||||||
|
conversationId,
|
||||||
timestamp,
|
timestamp,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -2398,12 +2532,12 @@
|
||||||
function onReadReceipt(ev) {
|
function onReadReceipt(ev) {
|
||||||
const readAt = ev.timestamp;
|
const readAt = ev.timestamp;
|
||||||
const { timestamp } = ev.read;
|
const { timestamp } = ev.read;
|
||||||
const { reader } = ev.read;
|
const reader = ConversationController.getConversationId(ev.read.reader);
|
||||||
window.log.info('read receipt', reader, timestamp);
|
window.log.info('read receipt', reader, timestamp);
|
||||||
|
|
||||||
ev.confirm();
|
ev.confirm();
|
||||||
|
|
||||||
if (!storage.get('read-receipt-setting')) {
|
if (!storage.get('read-receipt-setting') || !reader) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2420,11 +2554,12 @@
|
||||||
function onReadSync(ev) {
|
function onReadSync(ev) {
|
||||||
const readAt = ev.timestamp;
|
const readAt = ev.timestamp;
|
||||||
const { timestamp } = ev.read;
|
const { timestamp } = ev.read;
|
||||||
const { sender } = ev.read;
|
const { sender, senderUuid } = ev.read;
|
||||||
window.log.info('read sync', sender, timestamp);
|
window.log.info('read sync', sender, senderUuid, timestamp);
|
||||||
|
|
||||||
const receipt = Whisper.ReadSyncs.add({
|
const receipt = Whisper.ReadSyncs.add({
|
||||||
sender,
|
sender,
|
||||||
|
senderUuid,
|
||||||
timestamp,
|
timestamp,
|
||||||
read_at: readAt,
|
read_at: readAt,
|
||||||
});
|
});
|
||||||
|
@ -2437,7 +2572,8 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
async function onVerified(ev) {
|
async function onVerified(ev) {
|
||||||
const number = ev.verified.destination;
|
const e164 = ev.verified.destination;
|
||||||
|
const uuid = ev.verified.destinationUuid;
|
||||||
const key = ev.verified.identityKey;
|
const key = ev.verified.identityKey;
|
||||||
let state;
|
let state;
|
||||||
|
|
||||||
|
@ -2446,12 +2582,16 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
const c = new Whisper.Conversation({
|
const c = new Whisper.Conversation({
|
||||||
id: number,
|
e164,
|
||||||
|
uuid,
|
||||||
|
type: 'private',
|
||||||
});
|
});
|
||||||
const error = c.validateNumber();
|
const error = c.validate();
|
||||||
if (error) {
|
if (error) {
|
||||||
window.log.error(
|
window.log.error(
|
||||||
'Invalid verified sync received:',
|
'Invalid verified sync received:',
|
||||||
|
e164,
|
||||||
|
uuid,
|
||||||
Errors.toLogFormat(error)
|
Errors.toLogFormat(error)
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
|
@ -2473,13 +2613,14 @@
|
||||||
|
|
||||||
window.log.info(
|
window.log.info(
|
||||||
'got verified sync for',
|
'got verified sync for',
|
||||||
number,
|
e164,
|
||||||
|
uuid,
|
||||||
state,
|
state,
|
||||||
ev.viaContactSync ? 'via contact sync' : ''
|
ev.viaContactSync ? 'via contact sync' : ''
|
||||||
);
|
);
|
||||||
|
|
||||||
const contact = await ConversationController.getOrCreateAndWait(
|
const contact = await ConversationController.getOrCreateAndWait(
|
||||||
number,
|
e164 || uuid,
|
||||||
'private'
|
'private'
|
||||||
);
|
);
|
||||||
const options = {
|
const options = {
|
||||||
|
@ -2499,17 +2640,27 @@
|
||||||
|
|
||||||
function onDeliveryReceipt(ev) {
|
function onDeliveryReceipt(ev) {
|
||||||
const { deliveryReceipt } = ev;
|
const { deliveryReceipt } = ev;
|
||||||
|
const { sourceUuid, source } = deliveryReceipt;
|
||||||
|
const identifier = source || sourceUuid;
|
||||||
|
|
||||||
window.log.info(
|
window.log.info(
|
||||||
'delivery receipt from',
|
'delivery receipt from',
|
||||||
`${deliveryReceipt.source}.${deliveryReceipt.sourceDevice}`,
|
`${identifier}.${deliveryReceipt.sourceDevice}`,
|
||||||
deliveryReceipt.timestamp
|
deliveryReceipt.timestamp
|
||||||
);
|
);
|
||||||
|
|
||||||
ev.confirm();
|
ev.confirm();
|
||||||
|
|
||||||
|
const deliveredTo = ConversationController.getConversationId(identifier);
|
||||||
|
|
||||||
|
if (!deliveredTo) {
|
||||||
|
window.log.info('no conversation for identifier', identifier);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const receipt = Whisper.DeliveryReceipts.add({
|
const receipt = Whisper.DeliveryReceipts.add({
|
||||||
timestamp: deliveryReceipt.timestamp,
|
timestamp: deliveryReceipt.timestamp,
|
||||||
source: deliveryReceipt.source,
|
deliveredTo,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Note: We don't wait for completion here
|
// Note: We don't wait for completion here
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
/* global _, Whisper, Backbone, storage */
|
/* global _, Whisper, Backbone, storage, textsecure */
|
||||||
|
|
||||||
/* eslint-disable more/no-then */
|
/* eslint-disable more/no-then */
|
||||||
|
|
||||||
|
@ -67,8 +67,8 @@
|
||||||
dangerouslyCreateAndAdd(attributes) {
|
dangerouslyCreateAndAdd(attributes) {
|
||||||
return conversations.add(attributes);
|
return conversations.add(attributes);
|
||||||
},
|
},
|
||||||
getOrCreate(id, type) {
|
getOrCreate(identifier, type) {
|
||||||
if (typeof id !== 'string') {
|
if (typeof identifier !== 'string') {
|
||||||
throw new TypeError("'id' must be a string");
|
throw new TypeError("'id' must be a string");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -84,16 +84,41 @@
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
let conversation = conversations.get(id);
|
let conversation = conversations.get(identifier);
|
||||||
if (conversation) {
|
if (conversation) {
|
||||||
return conversation;
|
return conversation;
|
||||||
}
|
}
|
||||||
|
|
||||||
conversation = conversations.add({
|
const id = window.getGuid();
|
||||||
id,
|
|
||||||
type,
|
if (type === 'group') {
|
||||||
version: 2,
|
conversation = conversations.add({
|
||||||
});
|
id,
|
||||||
|
uuid: null,
|
||||||
|
e164: null,
|
||||||
|
groupId: identifier,
|
||||||
|
type,
|
||||||
|
version: 2,
|
||||||
|
});
|
||||||
|
} else if (window.isValidGuid(identifier)) {
|
||||||
|
conversation = conversations.add({
|
||||||
|
id,
|
||||||
|
uuid: identifier,
|
||||||
|
e164: null,
|
||||||
|
groupId: null,
|
||||||
|
type,
|
||||||
|
version: 2,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
conversation = conversations.add({
|
||||||
|
id,
|
||||||
|
uuid: null,
|
||||||
|
e164: identifier,
|
||||||
|
groupId: null,
|
||||||
|
type,
|
||||||
|
version: 2,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const create = async () => {
|
const create = async () => {
|
||||||
if (!conversation.isValid()) {
|
if (!conversation.isValid()) {
|
||||||
|
@ -114,7 +139,7 @@
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
window.log.error(
|
window.log.error(
|
||||||
'Conversation save failed! ',
|
'Conversation save failed! ',
|
||||||
id,
|
identifier,
|
||||||
type,
|
type,
|
||||||
'Error:',
|
'Error:',
|
||||||
error && error.stack ? error.stack : error
|
error && error.stack ? error.stack : error
|
||||||
|
@ -142,8 +167,22 @@
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
getConversationId(address) {
|
||||||
|
if (!address) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const [id] = textsecure.utils.unencodeNumber(address);
|
||||||
|
const conv = this.get(id);
|
||||||
|
|
||||||
|
if (conv) {
|
||||||
|
return conv.get('id');
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
},
|
||||||
prepareForSend(id, options) {
|
prepareForSend(id, options) {
|
||||||
// id is either a group id or an individual user's id
|
// id is any valid conversation identifier
|
||||||
const conversation = this.get(id);
|
const conversation = this.get(id);
|
||||||
const sendOptions = conversation
|
const sendOptions = conversation
|
||||||
? conversation.getSendOptions(options)
|
? conversation.getSendOptions(options)
|
||||||
|
|
|
@ -25,7 +25,7 @@
|
||||||
const receipts = this.filter(
|
const receipts = this.filter(
|
||||||
receipt =>
|
receipt =>
|
||||||
receipt.get('timestamp') === message.get('sent_at') &&
|
receipt.get('timestamp') === message.get('sent_at') &&
|
||||||
recipients.indexOf(receipt.get('source')) > -1
|
recipients.indexOf(receipt.get('deliveredTo')) > -1
|
||||||
);
|
);
|
||||||
this.remove(receipts);
|
this.remove(receipts);
|
||||||
return receipts;
|
return receipts;
|
||||||
|
@ -34,19 +34,23 @@
|
||||||
if (messages.length === 0) {
|
if (messages.length === 0) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
const sourceId = ConversationController.getConversationId(source);
|
||||||
const message = messages.find(
|
const message = messages.find(
|
||||||
item => !item.isIncoming() && source === item.get('conversationId')
|
item => !item.isIncoming() && sourceId === item.get('conversationId')
|
||||||
);
|
);
|
||||||
if (message) {
|
if (message) {
|
||||||
return MessageController.register(message.id, message);
|
return MessageController.register(message.id, message);
|
||||||
}
|
}
|
||||||
|
|
||||||
const groups = await window.Signal.Data.getAllGroupsInvolvingId(source, {
|
const groups = await window.Signal.Data.getAllGroupsInvolvingId(
|
||||||
ConversationCollection: Whisper.ConversationCollection,
|
sourceId,
|
||||||
});
|
{
|
||||||
|
ConversationCollection: Whisper.ConversationCollection,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
const ids = groups.pluck('id');
|
const ids = groups.pluck('id');
|
||||||
ids.push(source);
|
ids.push(sourceId);
|
||||||
|
|
||||||
const target = messages.find(
|
const target = messages.find(
|
||||||
item =>
|
item =>
|
||||||
|
@ -68,25 +72,25 @@
|
||||||
);
|
);
|
||||||
|
|
||||||
const message = await this.getTargetMessage(
|
const message = await this.getTargetMessage(
|
||||||
receipt.get('source'),
|
receipt.get('deliveredTo'),
|
||||||
messages
|
messages
|
||||||
);
|
);
|
||||||
if (!message) {
|
if (!message) {
|
||||||
window.log.info(
|
window.log.info(
|
||||||
'No message for delivery receipt',
|
'No message for delivery receipt',
|
||||||
receipt.get('source'),
|
receipt.get('deliveredTo'),
|
||||||
receipt.get('timestamp')
|
receipt.get('timestamp')
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const deliveries = message.get('delivered') || 0;
|
const deliveries = message.get('delivered') || 0;
|
||||||
const deliveredTo = message.get('delivered_to') || [];
|
const deliveredTo = message.get('deliveredTo') || [];
|
||||||
const expirationStartTimestamp = message.get(
|
const expirationStartTimestamp = message.get(
|
||||||
'expirationStartTimestamp'
|
'expirationStartTimestamp'
|
||||||
);
|
);
|
||||||
message.set({
|
message.set({
|
||||||
delivered_to: _.union(deliveredTo, [receipt.get('source')]),
|
delivered_to: _.union(deliveredTo, [receipt.get('deliveredTo')]),
|
||||||
delivered: deliveries + 1,
|
delivered: deliveries + 1,
|
||||||
expirationStartTimestamp: expirationStartTimestamp || Date.now(),
|
expirationStartTimestamp: expirationStartTimestamp || Date.now(),
|
||||||
sent: true,
|
sent: true,
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
const BLOCKED_NUMBERS_ID = 'blocked';
|
const BLOCKED_NUMBERS_ID = 'blocked';
|
||||||
|
const BLOCKED_UUIDS_ID = 'blocked-uuids';
|
||||||
const BLOCKED_GROUPS_ID = 'blocked-groups';
|
const BLOCKED_GROUPS_ID = 'blocked-groups';
|
||||||
|
|
||||||
storage.isBlocked = number => {
|
storage.isBlocked = number => {
|
||||||
|
@ -31,6 +32,30 @@
|
||||||
storage.put(BLOCKED_NUMBERS_ID, _.without(numbers, number));
|
storage.put(BLOCKED_NUMBERS_ID, _.without(numbers, number));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
storage.isUuidBlocked = uuid => {
|
||||||
|
const uuids = storage.get(BLOCKED_UUIDS_ID, []);
|
||||||
|
|
||||||
|
return _.include(uuids, uuid);
|
||||||
|
};
|
||||||
|
storage.addBlockedUuid = uuid => {
|
||||||
|
const uuids = storage.get(BLOCKED_UUIDS_ID, []);
|
||||||
|
if (_.include(uuids, uuid)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
window.log.info('adding', uuid, 'to blocked list');
|
||||||
|
storage.put(BLOCKED_UUIDS_ID, uuids.concat(uuid));
|
||||||
|
};
|
||||||
|
storage.removeBlockedUuid = uuid => {
|
||||||
|
const numbers = storage.get(BLOCKED_UUIDS_ID, []);
|
||||||
|
if (!_.include(numbers, uuid)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
window.log.info('removing', uuid, 'from blocked list');
|
||||||
|
storage.put(BLOCKED_NUMBERS_ID, _.without(numbers, uuid));
|
||||||
|
};
|
||||||
|
|
||||||
storage.isGroupBlocked = groupId => {
|
storage.isGroupBlocked = groupId => {
|
||||||
const groupIds = storage.get(BLOCKED_GROUPS_ID, []);
|
const groupIds = storage.get(BLOCKED_GROUPS_ID, []);
|
||||||
|
|
||||||
|
|
|
@ -27,7 +27,7 @@
|
||||||
};
|
};
|
||||||
|
|
||||||
const { Util } = window.Signal;
|
const { Util } = window.Signal;
|
||||||
const { Conversation, Contact, Message, PhoneNumber } = window.Signal.Types;
|
const { Conversation, Contact, Message } = window.Signal.Types;
|
||||||
const {
|
const {
|
||||||
deleteAttachmentData,
|
deleteAttachmentData,
|
||||||
doesAttachmentExist,
|
doesAttachmentExist,
|
||||||
|
@ -85,8 +85,13 @@
|
||||||
return collection;
|
return collection;
|
||||||
},
|
},
|
||||||
|
|
||||||
initialize() {
|
initialize(attributes) {
|
||||||
|
if (window.isValidE164(attributes.id)) {
|
||||||
|
this.set({ id: window.getGuid(), e164: attributes.id });
|
||||||
|
}
|
||||||
|
|
||||||
this.ourNumber = textsecure.storage.user.getNumber();
|
this.ourNumber = textsecure.storage.user.getNumber();
|
||||||
|
this.ourUuid = textsecure.storage.user.getUuid();
|
||||||
this.verifiedEnum = textsecure.storage.protocol.VerifiedStatus;
|
this.verifiedEnum = textsecure.storage.protocol.VerifiedStatus;
|
||||||
|
|
||||||
// This may be overridden by ConversationController.getOrCreate, and signify
|
// This may be overridden by ConversationController.getOrCreate, and signify
|
||||||
|
@ -148,7 +153,11 @@
|
||||||
},
|
},
|
||||||
|
|
||||||
isMe() {
|
isMe() {
|
||||||
return this.id === this.ourNumber;
|
const e164 = this.get('e164');
|
||||||
|
const uuid = this.get('uuid');
|
||||||
|
return (
|
||||||
|
(e164 && e164 === this.ourNumber) || (uuid && uuid === this.ourUuid)
|
||||||
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
hasDraft() {
|
hasDraft() {
|
||||||
|
@ -241,11 +250,17 @@
|
||||||
},
|
},
|
||||||
|
|
||||||
sendTypingMessage(isTyping) {
|
sendTypingMessage(isTyping) {
|
||||||
const groupId = !this.isPrivate() ? this.id : null;
|
if (!textsecure.messaging) {
|
||||||
const recipientId = this.isPrivate() ? this.id : null;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const groupId = !this.isPrivate() ? this.get('groupId') : null;
|
||||||
|
const maybeRecipientId = this.get('uuid') || this.get('e164');
|
||||||
|
const recipientId = this.isPrivate() ? maybeRecipientId : null;
|
||||||
const groupNumbers = this.getRecipients();
|
const groupNumbers = this.getRecipients();
|
||||||
|
|
||||||
const sendOptions = this.getSendOptions();
|
const sendOptions = this.getSendOptions();
|
||||||
|
|
||||||
this.wrapSend(
|
this.wrapSend(
|
||||||
textsecure.messaging.sendTypingMessage(
|
textsecure.messaging.sendTypingMessage(
|
||||||
{
|
{
|
||||||
|
@ -356,8 +371,6 @@
|
||||||
return this.cachedProps;
|
return this.cachedProps;
|
||||||
},
|
},
|
||||||
getProps() {
|
getProps() {
|
||||||
const { format } = PhoneNumber;
|
|
||||||
const regionCode = storage.get('regionCode');
|
|
||||||
const color = this.getColor();
|
const color = this.getColor();
|
||||||
|
|
||||||
const typingValues = _.values(this.contactTypingTimers || {});
|
const typingValues = _.values(this.contactTypingTimers || {});
|
||||||
|
@ -394,9 +407,7 @@
|
||||||
draftPreview,
|
draftPreview,
|
||||||
draftText,
|
draftText,
|
||||||
|
|
||||||
phoneNumber: format(this.id, {
|
phoneNumber: this.getNumber(),
|
||||||
ourRegionCode: regionCode,
|
|
||||||
}),
|
|
||||||
lastMessage: {
|
lastMessage: {
|
||||||
status: this.get('lastMessageStatus'),
|
status: this.get('lastMessageStatus'),
|
||||||
text: this.get('lastMessage'),
|
text: this.get('lastMessage'),
|
||||||
|
@ -406,6 +417,31 @@
|
||||||
return result;
|
return result;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
updateE164(e164) {
|
||||||
|
const oldValue = this.get('e164');
|
||||||
|
if (e164 !== oldValue) {
|
||||||
|
this.set('e164', e164);
|
||||||
|
window.Signal.Data.updateConversation(this.id, this.attributes);
|
||||||
|
this.trigger('idUpdated', this, 'e164', oldValue);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
updateUuid(uuid) {
|
||||||
|
const oldValue = this.get('uuid');
|
||||||
|
if (uuid !== oldValue) {
|
||||||
|
this.set('uuid', uuid);
|
||||||
|
window.Signal.Data.updateConversation(this.id, this.attributes);
|
||||||
|
this.trigger('idUpdated', this, 'uuid', oldValue);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
updateGroupId(groupId) {
|
||||||
|
const oldValue = this.get('groupId');
|
||||||
|
if (groupId !== oldValue) {
|
||||||
|
this.set('groupId', groupId);
|
||||||
|
window.Signal.Data.updateConversation(this.id, this.attributes);
|
||||||
|
this.trigger('idUpdated', this, 'groupId', oldValue);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
onMessageError() {
|
onMessageError() {
|
||||||
this.updateVerified();
|
this.updateVerified();
|
||||||
},
|
},
|
||||||
|
@ -506,24 +542,28 @@
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (!options.viaSyncMessage) {
|
if (!options.viaSyncMessage) {
|
||||||
await this.sendVerifySyncMessage(this.id, verified);
|
await this.sendVerifySyncMessage(
|
||||||
|
this.get('e164'),
|
||||||
|
this.get('uuid'),
|
||||||
|
verified
|
||||||
|
);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
sendVerifySyncMessage(number, state) {
|
sendVerifySyncMessage(e164, uuid, state) {
|
||||||
// Because syncVerification sends a (null) message to the target of the verify and
|
// Because syncVerification sends a (null) message to the target of the verify and
|
||||||
// a sync message to our own devices, we need to send the accessKeys down for both
|
// a sync message to our own devices, we need to send the accessKeys down for both
|
||||||
// contacts. So we merge their sendOptions.
|
// contacts. So we merge their sendOptions.
|
||||||
const { sendOptions } = ConversationController.prepareForSend(
|
const { sendOptions } = ConversationController.prepareForSend(
|
||||||
this.ourNumber,
|
this.ourNumber || this.ourUuid,
|
||||||
{ syncMessage: true }
|
{ syncMessage: true }
|
||||||
);
|
);
|
||||||
const contactSendOptions = this.getSendOptions();
|
const contactSendOptions = this.getSendOptions();
|
||||||
const options = Object.assign({}, sendOptions, contactSendOptions);
|
const options = Object.assign({}, sendOptions, contactSendOptions);
|
||||||
|
|
||||||
const promise = textsecure.storage.protocol.loadIdentityKey(number);
|
const promise = textsecure.storage.protocol.loadIdentityKey(e164);
|
||||||
return promise.then(key =>
|
return promise.then(key =>
|
||||||
this.wrapSend(
|
this.wrapSend(
|
||||||
textsecure.messaging.syncVerification(number, state, key, options)
|
textsecure.messaging.syncVerification(e164, uuid, state, key, options)
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
@ -764,8 +804,8 @@
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
validate(attributes) {
|
validate(attributes = this.attributes) {
|
||||||
const required = ['id', 'type'];
|
const required = ['type'];
|
||||||
const missing = _.filter(required, attr => !attributes[attr]);
|
const missing = _.filter(required, attr => !attributes[attr]);
|
||||||
if (missing.length) {
|
if (missing.length) {
|
||||||
return `Conversation must have ${missing}`;
|
return `Conversation must have ${missing}`;
|
||||||
|
@ -775,7 +815,16 @@
|
||||||
return `Invalid conversation type: ${attributes.type}`;
|
return `Invalid conversation type: ${attributes.type}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
const error = this.validateNumber();
|
const atLeastOneOf = ['e164', 'uuid', 'groupId'];
|
||||||
|
const hasAtLeastOneOf =
|
||||||
|
_.filter(atLeastOneOf, attr => attributes[attr]).length > 0;
|
||||||
|
|
||||||
|
if (!hasAtLeastOneOf) {
|
||||||
|
return 'Missing one of e164, uuid, or groupId';
|
||||||
|
}
|
||||||
|
|
||||||
|
const error = this.validateNumber() || this.validateUuid();
|
||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
return error;
|
return error;
|
||||||
}
|
}
|
||||||
|
@ -784,11 +833,14 @@
|
||||||
},
|
},
|
||||||
|
|
||||||
validateNumber() {
|
validateNumber() {
|
||||||
if (this.isPrivate()) {
|
if (this.isPrivate() && this.get('e164')) {
|
||||||
const regionCode = storage.get('regionCode');
|
const regionCode = storage.get('regionCode');
|
||||||
const number = libphonenumber.util.parseNumber(this.id, regionCode);
|
const number = libphonenumber.util.parseNumber(
|
||||||
|
this.get('e164'),
|
||||||
|
regionCode
|
||||||
|
);
|
||||||
if (number.isValidNumber) {
|
if (number.isValidNumber) {
|
||||||
this.set({ id: number.e164 });
|
this.set({ e164: number.e164 });
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -798,6 +850,18 @@
|
||||||
return null;
|
return null;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
validateUuid() {
|
||||||
|
if (this.isPrivate() && this.get('uuid')) {
|
||||||
|
if (window.isValidGuid(this.get('uuid'))) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 'Invalid UUID';
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
|
||||||
queueJob(callback) {
|
queueJob(callback) {
|
||||||
this.jobQueue = this.jobQueue || new window.PQueue({ concurrency: 1 });
|
this.jobQueue = this.jobQueue || new window.PQueue({ concurrency: 1 });
|
||||||
|
|
||||||
|
@ -811,10 +875,15 @@
|
||||||
|
|
||||||
getRecipients() {
|
getRecipients() {
|
||||||
if (this.isPrivate()) {
|
if (this.isPrivate()) {
|
||||||
return [this.id];
|
return [this.get('uuid') || this.get('e164')];
|
||||||
}
|
}
|
||||||
const me = textsecure.storage.user.getNumber();
|
const me = ConversationController.getConversationId(
|
||||||
return _.without(this.get('members'), me);
|
textsecure.storage.user.getUuid() || textsecure.storage.user.getNumber()
|
||||||
|
);
|
||||||
|
return _.without(this.get('members'), me).map(memberId => {
|
||||||
|
const c = ConversationController.get(memberId);
|
||||||
|
return c.get('uuid') || c.get('e164');
|
||||||
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
async getQuoteAttachment(attachments, preview, sticker) {
|
async getQuoteAttachment(attachments, preview, sticker) {
|
||||||
|
@ -908,7 +977,8 @@
|
||||||
: '';
|
: '';
|
||||||
|
|
||||||
return {
|
return {
|
||||||
author: contact.id,
|
author: contact.get('e164'),
|
||||||
|
authorUuid: contact.get('uuid'),
|
||||||
id: quotedMessage.get('sent_at'),
|
id: quotedMessage.get('sent_at'),
|
||||||
text: body || embeddedContactName,
|
text: body || embeddedContactName,
|
||||||
attachments: quotedMessage.isTapToView()
|
attachments: quotedMessage.isTapToView()
|
||||||
|
@ -955,7 +1025,9 @@
|
||||||
* @param {boolean} [reaction.remove] - Set to `true` if we are removing a
|
* @param {boolean} [reaction.remove] - Set to `true` if we are removing a
|
||||||
* reaction with the given emoji
|
* reaction with the given emoji
|
||||||
* @param {object} target - The target of the reaction
|
* @param {object} target - The target of the reaction
|
||||||
* @param {string} target.targetAuthorE164 - The E164 address of the target
|
* @param {string} [target.targetAuthorE164] - The E164 address of the target
|
||||||
|
* message's author
|
||||||
|
* @param {string} [target.targetAuthorUuid] - The UUID address of the target
|
||||||
* message's author
|
* message's author
|
||||||
* @param {number} target.targetTimestamp - The sent_at timestamp of the
|
* @param {number} target.targetTimestamp - The sent_at timestamp of the
|
||||||
* target message
|
* target message
|
||||||
|
@ -965,13 +1037,17 @@
|
||||||
const outgoingReaction = { ...reaction, ...target };
|
const outgoingReaction = { ...reaction, ...target };
|
||||||
const reactionModel = Whisper.Reactions.add({
|
const reactionModel = Whisper.Reactions.add({
|
||||||
...outgoingReaction,
|
...outgoingReaction,
|
||||||
fromId: this.ourNumber || textsecure.storage.user.getNumber(),
|
fromId:
|
||||||
|
this.ourNumber ||
|
||||||
|
this.ourUuid ||
|
||||||
|
textsecure.storage.user.getNumber() ||
|
||||||
|
textsecure.storage.user.getUuid(),
|
||||||
timestamp,
|
timestamp,
|
||||||
fromSync: true,
|
fromSync: true,
|
||||||
});
|
});
|
||||||
Whisper.Reactions.onReaction(reactionModel);
|
Whisper.Reactions.onReaction(reactionModel);
|
||||||
|
|
||||||
const destination = this.id;
|
const destination = this.get('e164');
|
||||||
const recipients = this.getRecipients();
|
const recipients = this.getRecipients();
|
||||||
|
|
||||||
let profileKey;
|
let profileKey;
|
||||||
|
@ -987,11 +1063,10 @@
|
||||||
timestamp
|
timestamp
|
||||||
);
|
);
|
||||||
|
|
||||||
// Here we move attachments to disk
|
|
||||||
const attributes = {
|
const attributes = {
|
||||||
id: window.getGuid(),
|
id: window.getGuid(),
|
||||||
type: 'outgoing',
|
type: 'outgoing',
|
||||||
conversationId: destination,
|
conversationId: this.get('id'),
|
||||||
sent_at: timestamp,
|
sent_at: timestamp,
|
||||||
received_at: timestamp,
|
received_at: timestamp,
|
||||||
recipients,
|
recipients,
|
||||||
|
@ -1029,11 +1104,10 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
const options = this.getSendOptions();
|
const options = this.getSendOptions();
|
||||||
const groupNumbers = this.getRecipients();
|
|
||||||
|
|
||||||
const promise = (() => {
|
const promise = (() => {
|
||||||
if (this.isPrivate()) {
|
if (this.isPrivate()) {
|
||||||
return textsecure.messaging.sendMessageToNumber(
|
return textsecure.messaging.sendMessageToIdentifier(
|
||||||
destination,
|
destination,
|
||||||
null,
|
null,
|
||||||
null,
|
null,
|
||||||
|
@ -1049,8 +1123,8 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
return textsecure.messaging.sendMessageToGroup(
|
return textsecure.messaging.sendMessageToGroup(
|
||||||
destination,
|
this.get('groupId'),
|
||||||
groupNumbers,
|
this.getRecipients(),
|
||||||
null,
|
null,
|
||||||
null,
|
null,
|
||||||
null,
|
null,
|
||||||
|
@ -1082,7 +1156,7 @@
|
||||||
const { clearUnreadMetrics } = window.reduxActions.conversations;
|
const { clearUnreadMetrics } = window.reduxActions.conversations;
|
||||||
clearUnreadMetrics(this.id);
|
clearUnreadMetrics(this.id);
|
||||||
|
|
||||||
const destination = this.id;
|
const destination = this.get('uuid') || this.get('e164');
|
||||||
const expireTimer = this.get('expireTimer');
|
const expireTimer = this.get('expireTimer');
|
||||||
const recipients = this.getRecipients();
|
const recipients = this.getRecipients();
|
||||||
|
|
||||||
|
@ -1105,7 +1179,7 @@
|
||||||
const messageWithSchema = await upgradeMessageSchema({
|
const messageWithSchema = await upgradeMessageSchema({
|
||||||
type: 'outgoing',
|
type: 'outgoing',
|
||||||
body,
|
body,
|
||||||
conversationId: destination,
|
conversationId: this.id,
|
||||||
quote,
|
quote,
|
||||||
preview,
|
preview,
|
||||||
attachments,
|
attachments,
|
||||||
|
@ -1147,10 +1221,13 @@
|
||||||
|
|
||||||
// We're offline!
|
// We're offline!
|
||||||
if (!textsecure.messaging) {
|
if (!textsecure.messaging) {
|
||||||
const errors = this.contactCollection.map(contact => {
|
const errors = (this.contactCollection.length
|
||||||
|
? this.contactCollection
|
||||||
|
: [this]
|
||||||
|
).map(contact => {
|
||||||
const error = new Error('Network is not available');
|
const error = new Error('Network is not available');
|
||||||
error.name = 'SendMessageNetworkError';
|
error.name = 'SendMessageNetworkError';
|
||||||
error.number = contact.id;
|
error.number = contact.get('uuid') || contact.get('e164');
|
||||||
return error;
|
return error;
|
||||||
});
|
});
|
||||||
await message.saveErrors(errors);
|
await message.saveErrors(errors);
|
||||||
|
@ -1189,12 +1266,11 @@
|
||||||
|
|
||||||
const conversationType = this.get('type');
|
const conversationType = this.get('type');
|
||||||
const options = this.getSendOptions();
|
const options = this.getSendOptions();
|
||||||
const groupNumbers = this.getRecipients();
|
|
||||||
|
|
||||||
const promise = (() => {
|
const promise = (() => {
|
||||||
switch (conversationType) {
|
switch (conversationType) {
|
||||||
case Message.PRIVATE:
|
case Message.PRIVATE:
|
||||||
return textsecure.messaging.sendMessageToNumber(
|
return textsecure.messaging.sendMessageToIdentifier(
|
||||||
destination,
|
destination,
|
||||||
messageBody,
|
messageBody,
|
||||||
finalAttachments,
|
finalAttachments,
|
||||||
|
@ -1209,8 +1285,8 @@
|
||||||
);
|
);
|
||||||
case Message.GROUP:
|
case Message.GROUP:
|
||||||
return textsecure.messaging.sendMessageToGroup(
|
return textsecure.messaging.sendMessageToGroup(
|
||||||
destination,
|
this.get('groupId'),
|
||||||
groupNumbers,
|
this.getRecipients(),
|
||||||
messageBody,
|
messageBody,
|
||||||
finalAttachments,
|
finalAttachments,
|
||||||
quote,
|
quote,
|
||||||
|
@ -1239,7 +1315,7 @@
|
||||||
// success
|
// success
|
||||||
if (result) {
|
if (result) {
|
||||||
await this.handleMessageSendResult(
|
await this.handleMessageSendResult(
|
||||||
result.failoverNumbers,
|
result.failoverIdentifiers,
|
||||||
result.unidentifiedDeliveries
|
result.unidentifiedDeliveries
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1249,7 +1325,7 @@
|
||||||
// failure
|
// failure
|
||||||
if (result) {
|
if (result) {
|
||||||
await this.handleMessageSendResult(
|
await this.handleMessageSendResult(
|
||||||
result.failoverNumbers,
|
result.failoverIdentifiers,
|
||||||
result.unidentifiedDeliveries
|
result.unidentifiedDeliveries
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1258,9 +1334,9 @@
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
async handleMessageSendResult(failoverNumbers, unidentifiedDeliveries) {
|
async handleMessageSendResult(failoverIdentifiers, unidentifiedDeliveries) {
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
(failoverNumbers || []).map(async number => {
|
(failoverIdentifiers || []).map(async number => {
|
||||||
const conversation = ConversationController.get(number);
|
const conversation = ConversationController.get(number);
|
||||||
|
|
||||||
if (
|
if (
|
||||||
|
@ -1315,25 +1391,36 @@
|
||||||
|
|
||||||
getSendOptions(options = {}) {
|
getSendOptions(options = {}) {
|
||||||
const senderCertificate = storage.get('senderCertificate');
|
const senderCertificate = storage.get('senderCertificate');
|
||||||
const numberInfo = this.getNumberInfo(options);
|
const senderCertificateWithUuid = storage.get(
|
||||||
|
'senderCertificateWithUuid'
|
||||||
|
);
|
||||||
|
const sendMetadata = this.getSendMetadata(options);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
senderCertificate,
|
senderCertificate,
|
||||||
numberInfo,
|
senderCertificateWithUuid,
|
||||||
|
sendMetadata,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
getNumberInfo(options = {}) {
|
getUuidCapable() {
|
||||||
|
return Boolean(_.property('uuid')(this.get('capabilities')));
|
||||||
|
},
|
||||||
|
|
||||||
|
getSendMetadata(options = {}) {
|
||||||
const { syncMessage, disableMeCheck } = options;
|
const { syncMessage, disableMeCheck } = options;
|
||||||
|
|
||||||
if (!this.ourNumber) {
|
if (!this.ourNumber && !this.ourUuid) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// START: this code has an Expiration date of ~2018/11/21
|
// START: this code has an Expiration date of ~2018/11/21
|
||||||
// We don't want to enable unidentified delivery for send unless it is
|
// We don't want to enable unidentified delivery for send unless it is
|
||||||
// also enabled for our own account.
|
// also enabled for our own account.
|
||||||
const me = ConversationController.getOrCreate(this.ourNumber, 'private');
|
const me = ConversationController.getOrCreate(
|
||||||
|
this.ourNumber || this.ourUuid,
|
||||||
|
'private'
|
||||||
|
);
|
||||||
if (
|
if (
|
||||||
!disableMeCheck &&
|
!disableMeCheck &&
|
||||||
me.get('sealedSender') === SEALED_SENDER.DISABLED
|
me.get('sealedSender') === SEALED_SENDER.DISABLED
|
||||||
|
@ -1344,29 +1431,36 @@
|
||||||
|
|
||||||
if (!this.isPrivate()) {
|
if (!this.isPrivate()) {
|
||||||
const infoArray = this.contactCollection.map(conversation =>
|
const infoArray = this.contactCollection.map(conversation =>
|
||||||
conversation.getNumberInfo(options)
|
conversation.getSendMetadata(options)
|
||||||
);
|
);
|
||||||
return Object.assign({}, ...infoArray);
|
return Object.assign({}, ...infoArray);
|
||||||
}
|
}
|
||||||
|
|
||||||
const accessKey = this.get('accessKey');
|
const accessKey = this.get('accessKey');
|
||||||
const sealedSender = this.get('sealedSender');
|
const sealedSender = this.get('sealedSender');
|
||||||
|
const uuidCapable = this.getUuidCapable();
|
||||||
|
|
||||||
// We never send sync messages as sealed sender
|
// We never send sync messages as sealed sender
|
||||||
if (syncMessage && this.id === this.ourNumber) {
|
if (syncMessage && this.isMe()) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const e164 = this.get('e164');
|
||||||
|
const uuid = this.get('uuid');
|
||||||
|
|
||||||
// If we've never fetched user's profile, we default to what we have
|
// If we've never fetched user's profile, we default to what we have
|
||||||
if (sealedSender === SEALED_SENDER.UNKNOWN) {
|
if (sealedSender === SEALED_SENDER.UNKNOWN) {
|
||||||
|
const info = {
|
||||||
|
accessKey:
|
||||||
|
accessKey ||
|
||||||
|
window.Signal.Crypto.arrayBufferToBase64(
|
||||||
|
window.Signal.Crypto.getRandomBytes(16)
|
||||||
|
),
|
||||||
|
useUuidSenderCert: uuidCapable,
|
||||||
|
};
|
||||||
return {
|
return {
|
||||||
[this.id]: {
|
...(e164 ? { [e164]: info } : {}),
|
||||||
accessKey:
|
...(uuid ? { [uuid]: info } : {}),
|
||||||
accessKey ||
|
|
||||||
window.Signal.Crypto.arrayBufferToBase64(
|
|
||||||
window.Signal.Crypto.getRandomBytes(16)
|
|
||||||
),
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1374,15 +1468,19 @@
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const info = {
|
||||||
|
accessKey:
|
||||||
|
accessKey && sealedSender === SEALED_SENDER.ENABLED
|
||||||
|
? accessKey
|
||||||
|
: window.Signal.Crypto.arrayBufferToBase64(
|
||||||
|
window.Signal.Crypto.getRandomBytes(16)
|
||||||
|
),
|
||||||
|
useUuidSenderCert: uuidCapable,
|
||||||
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
[this.id]: {
|
...(e164 ? { [e164]: info } : {}),
|
||||||
accessKey:
|
...(uuid ? { [uuid]: info } : {}),
|
||||||
accessKey && sealedSender === SEALED_SENDER.ENABLED
|
|
||||||
? accessKey
|
|
||||||
: window.Signal.Crypto.arrayBufferToBase64(
|
|
||||||
window.Signal.Crypto.getRandomBytes(16)
|
|
||||||
),
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -1465,7 +1563,10 @@
|
||||||
source,
|
source,
|
||||||
});
|
});
|
||||||
|
|
||||||
source = source || textsecure.storage.user.getNumber();
|
source =
|
||||||
|
source ||
|
||||||
|
textsecure.storage.user.getNumber() ||
|
||||||
|
textsecure.storage.user.getUuid();
|
||||||
|
|
||||||
// When we add a disappearing messages notification to the conversation, we want it
|
// When we add a disappearing messages notification to the conversation, we want it
|
||||||
// to be above the message that initiated that change, hence the subtraction.
|
// to be above the message that initiated that change, hence the subtraction.
|
||||||
|
@ -1492,7 +1593,7 @@
|
||||||
});
|
});
|
||||||
|
|
||||||
if (this.isPrivate()) {
|
if (this.isPrivate()) {
|
||||||
model.set({ destination: this.id });
|
model.set({ destination: this.get('uuid') || this.get('e164') });
|
||||||
}
|
}
|
||||||
if (model.isOutgoing()) {
|
if (model.isOutgoing()) {
|
||||||
model.set({ recipients: this.getRecipients() });
|
model.set({ recipients: this.getRecipients() });
|
||||||
|
@ -1522,7 +1623,7 @@
|
||||||
const flags =
|
const flags =
|
||||||
textsecure.protobuf.DataMessage.Flags.EXPIRATION_TIMER_UPDATE;
|
textsecure.protobuf.DataMessage.Flags.EXPIRATION_TIMER_UPDATE;
|
||||||
const dataMessage = await textsecure.messaging.getMessageProto(
|
const dataMessage = await textsecure.messaging.getMessageProto(
|
||||||
this.get('id'),
|
this.get('uuid') || this.get('e164'),
|
||||||
null,
|
null,
|
||||||
[],
|
[],
|
||||||
null,
|
null,
|
||||||
|
@ -1538,8 +1639,8 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.get('type') === 'private') {
|
if (this.get('type') === 'private') {
|
||||||
promise = textsecure.messaging.sendExpirationTimerUpdateToNumber(
|
promise = textsecure.messaging.sendExpirationTimerUpdateToIdentifier(
|
||||||
this.get('id'),
|
this.get('uuid') || this.get('e164'),
|
||||||
expireTimer,
|
expireTimer,
|
||||||
message.get('sent_at'),
|
message.get('sent_at'),
|
||||||
profileKey,
|
profileKey,
|
||||||
|
@ -1547,7 +1648,7 @@
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
promise = textsecure.messaging.sendExpirationTimerUpdateToGroup(
|
promise = textsecure.messaging.sendExpirationTimerUpdateToGroup(
|
||||||
this.get('id'),
|
this.get('groupId'),
|
||||||
this.getRecipients(),
|
this.getRecipients(),
|
||||||
expireTimer,
|
expireTimer,
|
||||||
message.get('sent_at'),
|
message.get('sent_at'),
|
||||||
|
@ -1573,7 +1674,8 @@
|
||||||
type: 'outgoing',
|
type: 'outgoing',
|
||||||
sent_at: now,
|
sent_at: now,
|
||||||
received_at: now,
|
received_at: now,
|
||||||
destination: this.id,
|
destination: this.get('e164'),
|
||||||
|
destinationUuid: this.get('uuid'),
|
||||||
recipients: this.getRecipients(),
|
recipients: this.getRecipients(),
|
||||||
flags: textsecure.protobuf.DataMessage.Flags.END_SESSION,
|
flags: textsecure.protobuf.DataMessage.Flags.END_SESSION,
|
||||||
});
|
});
|
||||||
|
@ -1589,7 +1691,11 @@
|
||||||
const options = this.getSendOptions();
|
const options = this.getSendOptions();
|
||||||
message.send(
|
message.send(
|
||||||
this.wrapSend(
|
this.wrapSend(
|
||||||
textsecure.messaging.resetSession(this.id, now, options)
|
textsecure.messaging.resetSession(
|
||||||
|
this.get('uuid') || this.get('e164'),
|
||||||
|
now,
|
||||||
|
options
|
||||||
|
)
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1720,7 +1826,7 @@
|
||||||
// Because syncReadMessages sends to our other devices, and sendReadReceipts goes
|
// Because syncReadMessages sends to our other devices, and sendReadReceipts goes
|
||||||
// to a contact, we need accessKeys for both.
|
// to a contact, we need accessKeys for both.
|
||||||
const { sendOptions } = ConversationController.prepareForSend(
|
const { sendOptions } = ConversationController.prepareForSend(
|
||||||
this.ourNumber,
|
this.ourUuid || this.ourNumber,
|
||||||
{ syncMessage: true }
|
{ syncMessage: true }
|
||||||
);
|
);
|
||||||
await this.wrapSend(
|
await this.wrapSend(
|
||||||
|
@ -1731,11 +1837,13 @@
|
||||||
const convoSendOptions = this.getSendOptions();
|
const convoSendOptions = this.getSendOptions();
|
||||||
|
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
_.map(_.groupBy(read, 'sender'), async (receipts, sender) => {
|
_.map(_.groupBy(read, 'sender'), async (receipts, identifier) => {
|
||||||
const timestamps = _.map(receipts, 'timestamp');
|
const timestamps = _.map(receipts, 'timestamp');
|
||||||
|
const c = ConversationController.get(identifier);
|
||||||
await this.wrapSend(
|
await this.wrapSend(
|
||||||
textsecure.messaging.sendReadReceipts(
|
textsecure.messaging.sendReadReceipts(
|
||||||
sender,
|
c.get('e164'),
|
||||||
|
c.get('uuid'),
|
||||||
timestamps,
|
timestamps,
|
||||||
convoSendOptions
|
convoSendOptions
|
||||||
)
|
)
|
||||||
|
@ -1756,9 +1864,14 @@
|
||||||
// request all conversation members' keys
|
// request all conversation members' keys
|
||||||
let ids = [];
|
let ids = [];
|
||||||
if (this.isPrivate()) {
|
if (this.isPrivate()) {
|
||||||
ids = [this.id];
|
ids = [this.get('uuid') || this.get('e164')];
|
||||||
} else {
|
} else {
|
||||||
ids = this.get('members');
|
ids = this.get('members')
|
||||||
|
.map(id => {
|
||||||
|
const c = ConversationController.get(id);
|
||||||
|
return c ? c.get('uuid') || c.get('e164') : null;
|
||||||
|
})
|
||||||
|
.filter(Boolean);
|
||||||
}
|
}
|
||||||
return Promise.all(_.map(ids, this.getProfile));
|
return Promise.all(_.map(ids, this.getProfile));
|
||||||
},
|
},
|
||||||
|
@ -1780,8 +1893,8 @@
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await c.deriveAccessKeyIfNeeded();
|
await c.deriveAccessKeyIfNeeded();
|
||||||
const numberInfo = c.getNumberInfo({ disableMeCheck: true }) || {};
|
const sendMetadata = c.getSendMetadata({ disableMeCheck: true }) || {};
|
||||||
const getInfo = numberInfo[c.id] || {};
|
const getInfo = sendMetadata[c.id] || {};
|
||||||
|
|
||||||
if (getInfo.accessKey) {
|
if (getInfo.accessKey) {
|
||||||
try {
|
try {
|
||||||
|
@ -1863,6 +1976,10 @@
|
||||||
sealedSender: SEALED_SENDER.DISABLED,
|
sealedSender: SEALED_SENDER.DISABLED,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (profile.capabilities) {
|
||||||
|
c.set({ capabilities: profile.capabilities });
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error.code !== 403 && error.code !== 404) {
|
if (error.code !== 403 && error.code !== 404) {
|
||||||
window.log.error(
|
window.log.error(
|
||||||
|
@ -2027,8 +2144,9 @@
|
||||||
this.set({ accessKey });
|
this.set({ accessKey });
|
||||||
},
|
},
|
||||||
|
|
||||||
hasMember(number) {
|
hasMember(identifier) {
|
||||||
return _.contains(this.get('members'), number);
|
const cid = ConversationController.getConversationId(identifier);
|
||||||
|
return cid && _.contains(this.get('members'), cid);
|
||||||
},
|
},
|
||||||
fetchContacts() {
|
fetchContacts() {
|
||||||
if (this.isPrivate()) {
|
if (this.isPrivate()) {
|
||||||
|
@ -2114,7 +2232,7 @@
|
||||||
if (!this.isPrivate()) {
|
if (!this.isPrivate()) {
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
const number = this.id;
|
const number = this.get('e164');
|
||||||
try {
|
try {
|
||||||
const parsedNumber = libphonenumber.parse(number);
|
const parsedNumber = libphonenumber.parse(number);
|
||||||
const regionCode = libphonenumber.getRegionCodeForNumber(parsedNumber);
|
const regionCode = libphonenumber.getRegionCodeForNumber(parsedNumber);
|
||||||
|
@ -2230,10 +2348,10 @@
|
||||||
},
|
},
|
||||||
|
|
||||||
notifyTyping(options = {}) {
|
notifyTyping(options = {}) {
|
||||||
const { isTyping, sender, senderDevice } = options;
|
const { isTyping, sender, senderUuid, senderDevice } = options;
|
||||||
|
|
||||||
// We don't do anything with typing messages from our other devices
|
// We don't do anything with typing messages from our other devices
|
||||||
if (sender === this.ourNumber) {
|
if (sender === this.ourNumber || senderUuid === this.ourUuid) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2289,6 +2407,83 @@
|
||||||
Whisper.ConversationCollection = Backbone.Collection.extend({
|
Whisper.ConversationCollection = Backbone.Collection.extend({
|
||||||
model: Whisper.Conversation,
|
model: Whisper.Conversation,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Backbone defines a `_byId` field. Here we set up additional `_byE164`,
|
||||||
|
* `_byUuid`, and `_byGroupId` fields so we can track conversations by more
|
||||||
|
* than just their id.
|
||||||
|
*/
|
||||||
|
initialize() {
|
||||||
|
this._byE164 = {};
|
||||||
|
this._byUuid = {};
|
||||||
|
this._byGroupId = {};
|
||||||
|
this.on('idUpdated', (model, idProp, oldValue) => {
|
||||||
|
if (oldValue) {
|
||||||
|
if (idProp === 'e164') {
|
||||||
|
delete this._byE164[oldValue];
|
||||||
|
}
|
||||||
|
if (idProp === 'uuid') {
|
||||||
|
delete this._byUuid[oldValue];
|
||||||
|
}
|
||||||
|
if (idProp === 'groupId') {
|
||||||
|
delete this._byGroupid[oldValue];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (model.get('e164')) {
|
||||||
|
this._byE164[model.get('e164')] = model;
|
||||||
|
}
|
||||||
|
if (model.get('uuid')) {
|
||||||
|
this._byUuid[model.get('uuid')] = model;
|
||||||
|
}
|
||||||
|
if (model.get('groupId')) {
|
||||||
|
this._byGroupid[model.get('groupId')] = model;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
reset(...args) {
|
||||||
|
Backbone.Collection.prototype.reset.apply(this, args);
|
||||||
|
this._byE164 = {};
|
||||||
|
this._byUuid = {};
|
||||||
|
this._byGroupId = {};
|
||||||
|
},
|
||||||
|
|
||||||
|
add(...models) {
|
||||||
|
const res = Backbone.Collection.prototype.add.apply(this, models);
|
||||||
|
[].concat(res).forEach(model => {
|
||||||
|
const e164 = model.get('e164');
|
||||||
|
if (e164) {
|
||||||
|
this._byE164[e164] = model;
|
||||||
|
}
|
||||||
|
|
||||||
|
const uuid = model.get('uuid');
|
||||||
|
if (uuid) {
|
||||||
|
this._byUuid[uuid] = model;
|
||||||
|
}
|
||||||
|
|
||||||
|
const groupId = model.get('groupId');
|
||||||
|
if (groupId) {
|
||||||
|
this._byGroupId[groupId] = model;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return res;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Backbone collections have a `_byId` field that `get` defers to. Here, we
|
||||||
|
* override `get` to first access our custom `_byE164`, `_byUuid`, and
|
||||||
|
* `_byGroupId` functions, followed by falling back to the original
|
||||||
|
* Backbone implementation.
|
||||||
|
*/
|
||||||
|
get(id) {
|
||||||
|
return (
|
||||||
|
this._byE164[id] ||
|
||||||
|
this._byE164[`+${id}`] ||
|
||||||
|
this._byUuid[id] ||
|
||||||
|
this._byGroupId[id] ||
|
||||||
|
Backbone.Collection.prototype.get.call(this, id)
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
comparator(m) {
|
comparator(m) {
|
||||||
return -m.get('timestamp');
|
return -m.get('timestamp');
|
||||||
},
|
},
|
||||||
|
|
|
@ -78,6 +78,9 @@
|
||||||
window.AccountCache[number] !== undefined;
|
window.AccountCache[number] !== undefined;
|
||||||
window.hasSignalAccount = number => window.AccountCache[number];
|
window.hasSignalAccount = number => window.AccountCache[number];
|
||||||
|
|
||||||
|
const includesAny = (haystack, ...needles) =>
|
||||||
|
needles.some(needle => haystack.includes(needle));
|
||||||
|
|
||||||
window.Whisper.Message = Backbone.Model.extend({
|
window.Whisper.Message = Backbone.Model.extend({
|
||||||
initialize(attributes) {
|
initialize(attributes) {
|
||||||
if (_.isObject(attributes)) {
|
if (_.isObject(attributes)) {
|
||||||
|
@ -94,6 +97,7 @@
|
||||||
this.INITIAL_PROTOCOL_VERSION =
|
this.INITIAL_PROTOCOL_VERSION =
|
||||||
textsecure.protobuf.DataMessage.ProtocolVersion.INITIAL;
|
textsecure.protobuf.DataMessage.ProtocolVersion.INITIAL;
|
||||||
this.OUR_NUMBER = textsecure.storage.user.getNumber();
|
this.OUR_NUMBER = textsecure.storage.user.getNumber();
|
||||||
|
this.OUR_UUID = textsecure.storage.user.getUuid();
|
||||||
|
|
||||||
this.on('destroy', this.onDestroy);
|
this.on('destroy', this.onDestroy);
|
||||||
this.on('change:expirationStartTimestamp', this.setToExpire);
|
this.on('change:expirationStartTimestamp', this.setToExpire);
|
||||||
|
@ -178,24 +182,32 @@
|
||||||
|
|
||||||
// Other top-level prop-generation
|
// Other top-level prop-generation
|
||||||
getPropsForSearchResult() {
|
getPropsForSearchResult() {
|
||||||
const fromNumber = this.getSource();
|
const sourceE164 = this.getSource();
|
||||||
const from = this.findAndFormatContact(fromNumber);
|
const sourceUuid = this.getSourceUuid();
|
||||||
if (fromNumber === this.OUR_NUMBER) {
|
const fromContact = this.findAndFormatContact(sourceE164 || sourceUuid);
|
||||||
from.isMe = true;
|
|
||||||
|
if (
|
||||||
|
(sourceE164 && sourceE164 === this.OUR_NUMBER) ||
|
||||||
|
(sourceUuid && sourceUuid === this.OUR_UUID)
|
||||||
|
) {
|
||||||
|
fromContact.isMe = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
const toNumber = this.get('conversationId');
|
const conversation = this.getConversation();
|
||||||
let to = this.findAndFormatContact(toNumber);
|
let to = this.findAndFormatContact(conversation.get('id'));
|
||||||
if (toNumber === this.OUR_NUMBER) {
|
if (conversation.isMe()) {
|
||||||
to.isMe = true;
|
to.isMe = true;
|
||||||
} else if (fromNumber === toNumber) {
|
} else if (
|
||||||
|
sourceE164 === conversation.get('e164') ||
|
||||||
|
sourceUuid === conversation.get('uuid')
|
||||||
|
) {
|
||||||
to = {
|
to = {
|
||||||
isMe: true,
|
isMe: true,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
from,
|
from: fromContact,
|
||||||
to,
|
to,
|
||||||
|
|
||||||
isSelected: this.isSelected,
|
isSelected: this.isSelected,
|
||||||
|
@ -221,11 +233,15 @@
|
||||||
// We include numbers we didn't successfully send to so we can display errors.
|
// We include numbers we didn't successfully send to so we can display errors.
|
||||||
// Older messages don't have the recipients included on the message, so we fall
|
// Older messages don't have the recipients included on the message, so we fall
|
||||||
// back to the conversation's current recipients
|
// back to the conversation's current recipients
|
||||||
const phoneNumbers = this.isIncoming()
|
const conversationIds = this.isIncoming()
|
||||||
? [this.get('source')]
|
? [this.getConversation().get('id')]
|
||||||
: _.union(
|
: _.union(
|
||||||
this.get('sent_to') || [],
|
(this.get('sent_to') || []).map(id =>
|
||||||
this.get('recipients') || this.getConversation().getRecipients()
|
ConversationController.getConversationId(id)
|
||||||
|
),
|
||||||
|
(
|
||||||
|
this.get('recipients') || this.getConversation().getRecipients()
|
||||||
|
).map(id => ConversationController.getConversationId(id))
|
||||||
);
|
);
|
||||||
|
|
||||||
// This will make the error message for outgoing key errors a bit nicer
|
// This will make the error message for outgoing key errors a bit nicer
|
||||||
|
@ -242,7 +258,7 @@
|
||||||
// that contact. Otherwise, it will be a standalone entry.
|
// that contact. Otherwise, it will be a standalone entry.
|
||||||
const errors = _.reject(allErrors, error => Boolean(error.number));
|
const errors = _.reject(allErrors, error => Boolean(error.number));
|
||||||
const errorsGroupedById = _.groupBy(allErrors, 'number');
|
const errorsGroupedById = _.groupBy(allErrors, 'number');
|
||||||
const finalContacts = (phoneNumbers || []).map(id => {
|
const finalContacts = (conversationIds || []).map(id => {
|
||||||
const errorsForContact = errorsGroupedById[id];
|
const errorsForContact = errorsGroupedById[id];
|
||||||
const isOutgoingKeyError = Boolean(
|
const isOutgoingKeyError = Boolean(
|
||||||
_.find(errorsForContact, error => error.name === OUTGOING_KEY_ERROR)
|
_.find(errorsForContact, error => error.name === OUTGOING_KEY_ERROR)
|
||||||
|
@ -353,7 +369,7 @@
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { expireTimer, fromSync, source } = timerUpdate;
|
const { expireTimer, fromSync, source, sourceUuid } = timerUpdate;
|
||||||
const timespan = Whisper.ExpirationTimerOptions.getName(expireTimer || 0);
|
const timespan = Whisper.ExpirationTimerOptions.getName(expireTimer || 0);
|
||||||
const disabled = !expireTimer;
|
const disabled = !expireTimer;
|
||||||
|
|
||||||
|
@ -369,7 +385,7 @@
|
||||||
...basicProps,
|
...basicProps,
|
||||||
type: 'fromSync',
|
type: 'fromSync',
|
||||||
};
|
};
|
||||||
} else if (source === this.OUR_NUMBER) {
|
} else if (source === this.OUR_NUMBER || sourceUuid === this.OUR_UUID) {
|
||||||
return {
|
return {
|
||||||
...basicProps,
|
...basicProps,
|
||||||
type: 'fromMe',
|
type: 'fromMe',
|
||||||
|
@ -477,9 +493,10 @@
|
||||||
.map(attachment => this.getPropsForAttachment(attachment));
|
.map(attachment => this.getPropsForAttachment(attachment));
|
||||||
},
|
},
|
||||||
getPropsForMessage() {
|
getPropsForMessage() {
|
||||||
const phoneNumber = this.getSource();
|
const sourceE164 = this.getSource();
|
||||||
const contact = this.findAndFormatContact(phoneNumber);
|
const sourceUuid = this.getSourceUuid();
|
||||||
const contactModel = this.findContact(phoneNumber);
|
const contact = this.findAndFormatContact(sourceE164 || sourceUuid);
|
||||||
|
const contactModel = this.findContact(sourceE164 || sourceUuid);
|
||||||
|
|
||||||
const authorColor = contactModel ? contactModel.getColor() : null;
|
const authorColor = contactModel ? contactModel.getColor() : null;
|
||||||
const authorAvatarPath = contactModel
|
const authorAvatarPath = contactModel
|
||||||
|
@ -558,8 +575,8 @@
|
||||||
},
|
},
|
||||||
|
|
||||||
// Dependencies of prop-generation functions
|
// Dependencies of prop-generation functions
|
||||||
findAndFormatContact(phoneNumber) {
|
findAndFormatContact(identifier) {
|
||||||
const contactModel = this.findContact(phoneNumber);
|
const contactModel = this.findContact(identifier);
|
||||||
if (contactModel) {
|
if (contactModel) {
|
||||||
return contactModel.format();
|
return contactModel.format();
|
||||||
}
|
}
|
||||||
|
@ -567,13 +584,13 @@
|
||||||
const { format } = PhoneNumber;
|
const { format } = PhoneNumber;
|
||||||
const regionCode = storage.get('regionCode');
|
const regionCode = storage.get('regionCode');
|
||||||
return {
|
return {
|
||||||
phoneNumber: format(phoneNumber, {
|
phoneNumber: format(identifier, {
|
||||||
ourRegionCode: regionCode,
|
ourRegionCode: regionCode,
|
||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
findContact(phoneNumber) {
|
findContact(identifier) {
|
||||||
return ConversationController.get(phoneNumber);
|
return ConversationController.get(identifier);
|
||||||
},
|
},
|
||||||
getConversation() {
|
getConversation() {
|
||||||
// This needs to be an unsafe call, because this method is called during
|
// This needs to be an unsafe call, because this method is called during
|
||||||
|
@ -700,8 +717,14 @@
|
||||||
const { format } = PhoneNumber;
|
const { format } = PhoneNumber;
|
||||||
const regionCode = storage.get('regionCode');
|
const regionCode = storage.get('regionCode');
|
||||||
|
|
||||||
const { author, id: sentAt, referencedMessageNotFound } = quote;
|
const {
|
||||||
const contact = author && ConversationController.get(author);
|
author,
|
||||||
|
authorUuid,
|
||||||
|
id: sentAt,
|
||||||
|
referencedMessageNotFound,
|
||||||
|
} = quote;
|
||||||
|
const contact =
|
||||||
|
author && ConversationController.get(author || authorUuid);
|
||||||
const authorColor = contact ? contact.getColor() : 'grey';
|
const authorColor = contact ? contact.getColor() : 'grey';
|
||||||
|
|
||||||
const authorPhoneNumber = format(author, {
|
const authorPhoneNumber = format(author, {
|
||||||
|
@ -709,7 +732,7 @@
|
||||||
});
|
});
|
||||||
const authorProfileName = contact ? contact.getProfileName() : null;
|
const authorProfileName = contact ? contact.getProfileName() : null;
|
||||||
const authorName = contact ? contact.getName() : null;
|
const authorName = contact ? contact.getName() : null;
|
||||||
const isFromMe = contact ? contact.id === this.OUR_NUMBER : false;
|
const isFromMe = contact ? contact.isMe() : false;
|
||||||
const firstAttachment = quote.attachments && quote.attachments[0];
|
const firstAttachment = quote.attachments && quote.attachments[0];
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
@ -728,17 +751,26 @@
|
||||||
onClick: () => this.trigger('scroll-to-message'),
|
onClick: () => this.trigger('scroll-to-message'),
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
getStatus(number) {
|
getStatus(identifier) {
|
||||||
|
const conversation = ConversationController.get(identifier);
|
||||||
|
|
||||||
|
if (!conversation) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const e164 = conversation.get('e164');
|
||||||
|
const uuid = conversation.get('uuid');
|
||||||
|
|
||||||
const readBy = this.get('read_by') || [];
|
const readBy = this.get('read_by') || [];
|
||||||
if (readBy.indexOf(number) >= 0) {
|
if (includesAny(readBy, identifier, e164, uuid)) {
|
||||||
return 'read';
|
return 'read';
|
||||||
}
|
}
|
||||||
const deliveredTo = this.get('delivered_to') || [];
|
const deliveredTo = this.get('delivered_to') || [];
|
||||||
if (deliveredTo.indexOf(number) >= 0) {
|
if (includesAny(deliveredTo, identifier, e164, uuid)) {
|
||||||
return 'delivered';
|
return 'delivered';
|
||||||
}
|
}
|
||||||
const sentTo = this.get('sent_to') || [];
|
const sentTo = this.get('sent_to') || [];
|
||||||
if (sentTo.indexOf(number) >= 0) {
|
if (includesAny(sentTo, identifier, e164, uuid)) {
|
||||||
return 'sent';
|
return 'sent';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -982,17 +1014,24 @@
|
||||||
|
|
||||||
if (!fromSync) {
|
if (!fromSync) {
|
||||||
const sender = this.getSource();
|
const sender = this.getSource();
|
||||||
|
const senderUuid = this.getSourceUuid();
|
||||||
const timestamp = this.get('sent_at');
|
const timestamp = this.get('sent_at');
|
||||||
const ourNumber = textsecure.storage.user.getNumber();
|
const ourNumber = textsecure.storage.user.getNumber();
|
||||||
|
const ourUuid = textsecure.storage.user.getUuid();
|
||||||
const { wrap, sendOptions } = ConversationController.prepareForSend(
|
const { wrap, sendOptions } = ConversationController.prepareForSend(
|
||||||
ourNumber,
|
ourNumber || ourUuid,
|
||||||
{
|
{
|
||||||
syncMessage: true,
|
syncMessage: true,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
await wrap(
|
await wrap(
|
||||||
textsecure.messaging.syncViewOnceOpen(sender, timestamp, sendOptions)
|
textsecure.messaging.syncViewOnceOpen(
|
||||||
|
sender,
|
||||||
|
senderUuid,
|
||||||
|
timestamp,
|
||||||
|
sendOptions
|
||||||
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -1052,14 +1091,25 @@
|
||||||
|
|
||||||
return this.OUR_NUMBER;
|
return this.OUR_NUMBER;
|
||||||
},
|
},
|
||||||
|
getSourceUuid() {
|
||||||
|
if (this.isIncoming()) {
|
||||||
|
return this.get('sourceUuid');
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.OUR_UUID;
|
||||||
|
},
|
||||||
getContact() {
|
getContact() {
|
||||||
const source = this.getSource();
|
const source = this.getSource();
|
||||||
|
const sourceUuid = this.getSourceUuid();
|
||||||
|
|
||||||
if (!source) {
|
if (!source && !sourceUuid) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return ConversationController.getOrCreate(source, 'private');
|
return ConversationController.getOrCreate(
|
||||||
|
source || sourceUuid,
|
||||||
|
'private'
|
||||||
|
);
|
||||||
},
|
},
|
||||||
isOutgoing() {
|
isOutgoing() {
|
||||||
return this.get('type') === 'outgoing';
|
return this.get('type') === 'outgoing';
|
||||||
|
@ -1237,9 +1287,9 @@
|
||||||
|
|
||||||
// Special-case the self-send case - we send only a sync message
|
// Special-case the self-send case - we send only a sync message
|
||||||
if (recipients.length === 1 && recipients[0] === this.OUR_NUMBER) {
|
if (recipients.length === 1 && recipients[0] === this.OUR_NUMBER) {
|
||||||
const [number] = recipients;
|
const [identifier] = recipients;
|
||||||
const dataMessage = await textsecure.messaging.getMessageProto(
|
const dataMessage = await textsecure.messaging.getMessageProto(
|
||||||
number,
|
identifier,
|
||||||
body,
|
body,
|
||||||
attachments,
|
attachments,
|
||||||
quoteWithData,
|
quoteWithData,
|
||||||
|
@ -1257,9 +1307,9 @@
|
||||||
const options = conversation.getSendOptions();
|
const options = conversation.getSendOptions();
|
||||||
|
|
||||||
if (conversation.isPrivate()) {
|
if (conversation.isPrivate()) {
|
||||||
const [number] = recipients;
|
const [identifer] = recipients;
|
||||||
promise = textsecure.messaging.sendMessageToNumber(
|
promise = textsecure.messaging.sendMessageToIdentifier(
|
||||||
number,
|
identifer,
|
||||||
body,
|
body,
|
||||||
attachments,
|
attachments,
|
||||||
quoteWithData,
|
quoteWithData,
|
||||||
|
@ -1327,8 +1377,8 @@
|
||||||
|
|
||||||
// Called when the user ran into an error with a specific user, wants to send to them
|
// Called when the user ran into an error with a specific user, wants to send to them
|
||||||
// One caller today: ConversationView.forceSend()
|
// One caller today: ConversationView.forceSend()
|
||||||
async resend(number) {
|
async resend(identifier) {
|
||||||
const error = this.removeOutgoingErrors(number);
|
const error = this.removeOutgoingErrors(identifier);
|
||||||
if (!error) {
|
if (!error) {
|
||||||
window.log.warn('resend: requested number was not present in errors');
|
window.log.warn('resend: requested number was not present in errors');
|
||||||
return null;
|
return null;
|
||||||
|
@ -1349,9 +1399,9 @@
|
||||||
const stickerWithData = await loadStickerData(this.get('sticker'));
|
const stickerWithData = await loadStickerData(this.get('sticker'));
|
||||||
|
|
||||||
// Special-case the self-send case - we send only a sync message
|
// Special-case the self-send case - we send only a sync message
|
||||||
if (number === this.OUR_NUMBER) {
|
if (identifier === this.OUR_NUMBER || identifier === this.OUR_UUID) {
|
||||||
const dataMessage = await textsecure.messaging.getMessageProto(
|
const dataMessage = await textsecure.messaging.getMessageProto(
|
||||||
number,
|
identifier,
|
||||||
body,
|
body,
|
||||||
attachments,
|
attachments,
|
||||||
quoteWithData,
|
quoteWithData,
|
||||||
|
@ -1366,10 +1416,10 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
const { wrap, sendOptions } = ConversationController.prepareForSend(
|
const { wrap, sendOptions } = ConversationController.prepareForSend(
|
||||||
number
|
identifier
|
||||||
);
|
);
|
||||||
const promise = textsecure.messaging.sendMessageToNumber(
|
const promise = textsecure.messaging.sendMessageToNumber(
|
||||||
number,
|
identifier,
|
||||||
body,
|
body,
|
||||||
attachments,
|
attachments,
|
||||||
quoteWithData,
|
quoteWithData,
|
||||||
|
@ -1411,7 +1461,7 @@
|
||||||
|
|
||||||
const sentTo = this.get('sent_to') || [];
|
const sentTo = this.get('sent_to') || [];
|
||||||
this.set({
|
this.set({
|
||||||
sent_to: _.union(sentTo, result.successfulNumbers),
|
sent_to: _.union(sentTo, result.successfulIdentifiers),
|
||||||
sent: true,
|
sent: true,
|
||||||
expirationStartTimestamp: Date.now(),
|
expirationStartTimestamp: Date.now(),
|
||||||
unidentifiedDeliveries: result.unidentifiedDeliveries,
|
unidentifiedDeliveries: result.unidentifiedDeliveries,
|
||||||
|
@ -1442,7 +1492,7 @@
|
||||||
promises.push(c.getProfiles());
|
promises.push(c.getProfiles());
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (result.successfulNumbers.length > 0) {
|
if (result.successfulIdentifiers.length > 0) {
|
||||||
const sentTo = this.get('sent_to') || [];
|
const sentTo = this.get('sent_to') || [];
|
||||||
|
|
||||||
// In groups, we don't treat unregistered users as a user-visible
|
// In groups, we don't treat unregistered users as a user-visible
|
||||||
|
@ -1462,7 +1512,7 @@
|
||||||
this.saveErrors(filteredErrors);
|
this.saveErrors(filteredErrors);
|
||||||
|
|
||||||
this.set({
|
this.set({
|
||||||
sent_to: _.union(sentTo, result.successfulNumbers),
|
sent_to: _.union(sentTo, result.successfulIdentifiers),
|
||||||
sent: true,
|
sent: true,
|
||||||
expirationStartTimestamp,
|
expirationStartTimestamp,
|
||||||
unidentifiedDeliveries: result.unidentifiedDeliveries,
|
unidentifiedDeliveries: result.unidentifiedDeliveries,
|
||||||
|
@ -1488,12 +1538,13 @@
|
||||||
},
|
},
|
||||||
|
|
||||||
async sendSyncMessageOnly(dataMessage) {
|
async sendSyncMessageOnly(dataMessage) {
|
||||||
|
const conv = this.getConversation();
|
||||||
this.set({ dataMessage });
|
this.set({ dataMessage });
|
||||||
|
|
||||||
try {
|
try {
|
||||||
this.set({
|
this.set({
|
||||||
// These are the same as a normal send()
|
// These are the same as a normal send()
|
||||||
sent_to: [this.OUR_NUMBER],
|
sent_to: [conv.get('uuid') || conv.get('e164')],
|
||||||
sent: true,
|
sent: true,
|
||||||
expirationStartTimestamp: Date.now(),
|
expirationStartTimestamp: Date.now(),
|
||||||
});
|
});
|
||||||
|
@ -1503,8 +1554,8 @@
|
||||||
unidentifiedDeliveries: result ? result.unidentifiedDeliveries : null,
|
unidentifiedDeliveries: result ? result.unidentifiedDeliveries : null,
|
||||||
|
|
||||||
// These are unique to a Note to Self message - immediately read/delivered
|
// These are unique to a Note to Self message - immediately read/delivered
|
||||||
delivered_to: [this.OUR_NUMBER],
|
delivered_to: [this.OUR_UUID || this.OUR_NUMBER],
|
||||||
read_by: [this.OUR_NUMBER],
|
read_by: [this.OUR_UUID || this.OUR_NUMBER],
|
||||||
});
|
});
|
||||||
} catch (result) {
|
} catch (result) {
|
||||||
const errors = (result && result.errors) || [
|
const errors = (result && result.errors) || [
|
||||||
|
@ -1528,8 +1579,9 @@
|
||||||
|
|
||||||
sendSyncMessage() {
|
sendSyncMessage() {
|
||||||
const ourNumber = textsecure.storage.user.getNumber();
|
const ourNumber = textsecure.storage.user.getNumber();
|
||||||
|
const ourUuid = textsecure.storage.user.getUuid();
|
||||||
const { wrap, sendOptions } = ConversationController.prepareForSend(
|
const { wrap, sendOptions } = ConversationController.prepareForSend(
|
||||||
ourNumber,
|
ourUuid || ourNumber,
|
||||||
{
|
{
|
||||||
syncMessage: true,
|
syncMessage: true,
|
||||||
}
|
}
|
||||||
|
@ -1542,12 +1594,14 @@
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
}
|
}
|
||||||
const isUpdate = Boolean(this.get('synced'));
|
const isUpdate = Boolean(this.get('synced'));
|
||||||
|
const conv = this.getConversation();
|
||||||
|
|
||||||
return wrap(
|
return wrap(
|
||||||
textsecure.messaging.sendSyncMessage(
|
textsecure.messaging.sendSyncMessage(
|
||||||
dataMessage,
|
dataMessage,
|
||||||
this.get('sent_at'),
|
this.get('sent_at'),
|
||||||
this.get('destination'),
|
conv.get('e164'),
|
||||||
|
conv.get('uuid'),
|
||||||
this.get('expirationStartTimestamp'),
|
this.get('expirationStartTimestamp'),
|
||||||
this.get('sent_to'),
|
this.get('sent_to'),
|
||||||
this.get('unidentifiedDeliveries'),
|
this.get('unidentifiedDeliveries'),
|
||||||
|
@ -1773,7 +1827,11 @@
|
||||||
const found = collection.find(item => {
|
const found = collection.find(item => {
|
||||||
const messageAuthor = item.getContact();
|
const messageAuthor = item.getContact();
|
||||||
|
|
||||||
return messageAuthor && author === messageAuthor.id;
|
return (
|
||||||
|
messageAuthor &&
|
||||||
|
ConversationController.getConversationId(author) ===
|
||||||
|
messageAuthor.get('id')
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!found) {
|
if (!found) {
|
||||||
|
@ -1873,6 +1931,7 @@
|
||||||
// still go through one of the previous two codepaths
|
// still go through one of the previous two codepaths
|
||||||
const message = this;
|
const message = this;
|
||||||
const source = message.get('source');
|
const source = message.get('source');
|
||||||
|
const sourceUuid = message.get('sourceUuid');
|
||||||
const type = message.get('type');
|
const type = message.get('type');
|
||||||
let conversationId = message.get('conversationId');
|
let conversationId = message.get('conversationId');
|
||||||
if (initialMessage.group) {
|
if (initialMessage.group) {
|
||||||
|
@ -1952,6 +2011,7 @@
|
||||||
|
|
||||||
// We drop incoming messages for groups we already know about, which we're not a
|
// We drop incoming messages for groups we already know about, which we're not a
|
||||||
// part of, except for group updates.
|
// part of, except for group updates.
|
||||||
|
const ourUuid = textsecure.storage.user.getUuid();
|
||||||
const ourNumber = textsecure.storage.user.getNumber();
|
const ourNumber = textsecure.storage.user.getNumber();
|
||||||
const isGroupUpdate =
|
const isGroupUpdate =
|
||||||
initialMessage.group &&
|
initialMessage.group &&
|
||||||
|
@ -1960,7 +2020,7 @@
|
||||||
if (
|
if (
|
||||||
type === 'incoming' &&
|
type === 'incoming' &&
|
||||||
!conversation.isPrivate() &&
|
!conversation.isPrivate() &&
|
||||||
!conversation.hasMember(ourNumber) &&
|
!conversation.hasMember(ourNumber || ourUuid) &&
|
||||||
!isGroupUpdate
|
!isGroupUpdate
|
||||||
) {
|
) {
|
||||||
window.log.warn(
|
window.log.warn(
|
||||||
|
@ -1982,6 +2042,7 @@
|
||||||
Whisper.deliveryReceiptQueue.add(() => {
|
Whisper.deliveryReceiptQueue.add(() => {
|
||||||
Whisper.deliveryReceiptBatcher.add({
|
Whisper.deliveryReceiptBatcher.add({
|
||||||
source,
|
source,
|
||||||
|
sourceUuid,
|
||||||
timestamp: this.get('sent_at'),
|
timestamp: this.get('sent_at'),
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -2044,6 +2105,20 @@
|
||||||
};
|
};
|
||||||
if (dataMessage.group) {
|
if (dataMessage.group) {
|
||||||
let groupUpdate = null;
|
let groupUpdate = null;
|
||||||
|
const memberConversations = await Promise.all(
|
||||||
|
(
|
||||||
|
dataMessage.group.members || dataMessage.group.membersE164
|
||||||
|
).map(member => {
|
||||||
|
if (member.e164 || member.uuid) {
|
||||||
|
return ConversationController.getOrCreateAndWait(
|
||||||
|
member.e164 || member.uuid,
|
||||||
|
'private'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return ConversationController.getOrCreateAndWait(member);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
const members = memberConversations.map(c => c.get('id'));
|
||||||
attributes = {
|
attributes = {
|
||||||
...attributes,
|
...attributes,
|
||||||
type: 'group',
|
type: 'group',
|
||||||
|
@ -2053,10 +2128,7 @@
|
||||||
attributes = {
|
attributes = {
|
||||||
...attributes,
|
...attributes,
|
||||||
name: dataMessage.group.name,
|
name: dataMessage.group.name,
|
||||||
members: _.union(
|
members: _.union(members, conversation.get('members')),
|
||||||
dataMessage.group.members,
|
|
||||||
conversation.get('members')
|
|
||||||
),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
groupUpdate =
|
groupUpdate =
|
||||||
|
@ -2065,7 +2137,7 @@
|
||||||
) || {};
|
) || {};
|
||||||
|
|
||||||
const difference = _.difference(
|
const difference = _.difference(
|
||||||
attributes.members,
|
members,
|
||||||
conversation.get('members')
|
conversation.get('members')
|
||||||
);
|
);
|
||||||
if (difference.length > 0) {
|
if (difference.length > 0) {
|
||||||
|
@ -2076,15 +2148,22 @@
|
||||||
attributes.left = false;
|
attributes.left = false;
|
||||||
}
|
}
|
||||||
} else if (dataMessage.group.type === GROUP_TYPES.QUIT) {
|
} else if (dataMessage.group.type === GROUP_TYPES.QUIT) {
|
||||||
if (source === textsecure.storage.user.getNumber()) {
|
if (
|
||||||
|
source === textsecure.storage.user.getNumber() ||
|
||||||
|
sourceUuid === textsecure.storage.user.getUuid()
|
||||||
|
) {
|
||||||
attributes.left = true;
|
attributes.left = true;
|
||||||
groupUpdate = { left: 'You' };
|
groupUpdate = { left: 'You' };
|
||||||
} else {
|
} else {
|
||||||
groupUpdate = { left: source };
|
const myConversation = ConversationController.get(
|
||||||
|
source || sourceUuid
|
||||||
|
);
|
||||||
|
groupUpdate = { left: myConversation.get('id') };
|
||||||
}
|
}
|
||||||
attributes.members = _.without(
|
attributes.members = _.without(
|
||||||
conversation.get('members'),
|
conversation.get('members'),
|
||||||
source
|
source,
|
||||||
|
sourceUuid
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2102,7 +2181,7 @@
|
||||||
message.set({
|
message.set({
|
||||||
delivered: (message.get('delivered') || 0) + 1,
|
delivered: (message.get('delivered') || 0) + 1,
|
||||||
delivered_to: _.union(message.get('delivered_to') || [], [
|
delivered_to: _.union(message.get('delivered_to') || [], [
|
||||||
receipt.get('source'),
|
receipt.get('deliveredTo'),
|
||||||
]),
|
]),
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
@ -2216,13 +2295,16 @@
|
||||||
|
|
||||||
if (dataMessage.profileKey) {
|
if (dataMessage.profileKey) {
|
||||||
const profileKey = dataMessage.profileKey.toString('base64');
|
const profileKey = dataMessage.profileKey.toString('base64');
|
||||||
if (source === textsecure.storage.user.getNumber()) {
|
if (
|
||||||
|
source === textsecure.storage.user.getNumber() ||
|
||||||
|
sourceUuid === textsecure.storage.user.getUuid()
|
||||||
|
) {
|
||||||
conversation.set({ profileSharing: true });
|
conversation.set({ profileSharing: true });
|
||||||
} else if (conversation.isPrivate()) {
|
} else if (conversation.isPrivate()) {
|
||||||
conversation.setProfileKey(profileKey);
|
conversation.setProfileKey(profileKey);
|
||||||
} else {
|
} else {
|
||||||
ConversationController.getOrCreateAndWait(
|
ConversationController.getOrCreateAndWait(
|
||||||
source,
|
source || sourceUuid,
|
||||||
'private'
|
'private'
|
||||||
).then(sender => {
|
).then(sender => {
|
||||||
sender.setProfileKey(profileKey);
|
sender.setProfileKey(profileKey);
|
||||||
|
|
|
@ -1118,8 +1118,14 @@ async function importConversations(dir, options) {
|
||||||
|
|
||||||
function getMessageKey(message) {
|
function getMessageKey(message) {
|
||||||
const ourNumber = textsecure.storage.user.getNumber();
|
const ourNumber = textsecure.storage.user.getNumber();
|
||||||
|
const ourUuid = textsecure.storage.user.getUuid();
|
||||||
const source = message.source || ourNumber;
|
const source = message.source || ourNumber;
|
||||||
if (source === ourNumber) {
|
const sourceUuid = message.sourceUuid || ourUuid;
|
||||||
|
|
||||||
|
if (
|
||||||
|
(source && source === ourNumber) ||
|
||||||
|
(sourceUuid && sourceUuid === ourUuid)
|
||||||
|
) {
|
||||||
return `${source} ${message.timestamp}`;
|
return `${source} ${message.timestamp}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
/* global window, setTimeout, IDBKeyRange */
|
/* global window, setTimeout, IDBKeyRange, ConversationController */
|
||||||
|
|
||||||
const electron = require('electron');
|
const electron = require('electron');
|
||||||
|
|
||||||
|
@ -84,10 +84,10 @@ module.exports = {
|
||||||
createOrUpdateSession,
|
createOrUpdateSession,
|
||||||
createOrUpdateSessions,
|
createOrUpdateSessions,
|
||||||
getSessionById,
|
getSessionById,
|
||||||
getSessionsByNumber,
|
getSessionsById,
|
||||||
bulkAddSessions,
|
bulkAddSessions,
|
||||||
removeSessionById,
|
removeSessionById,
|
||||||
removeSessionsByNumber,
|
removeSessionsById,
|
||||||
removeAllSessions,
|
removeAllSessions,
|
||||||
getAllSessions,
|
getAllSessions,
|
||||||
|
|
||||||
|
@ -431,10 +431,14 @@ async function removeIndexedDBFiles() {
|
||||||
|
|
||||||
const IDENTITY_KEY_KEYS = ['publicKey'];
|
const IDENTITY_KEY_KEYS = ['publicKey'];
|
||||||
async function createOrUpdateIdentityKey(data) {
|
async function createOrUpdateIdentityKey(data) {
|
||||||
const updated = keysFromArrayBuffer(IDENTITY_KEY_KEYS, data);
|
const updated = keysFromArrayBuffer(IDENTITY_KEY_KEYS, {
|
||||||
|
...data,
|
||||||
|
id: ConversationController.getConversationId(data.id),
|
||||||
|
});
|
||||||
await channels.createOrUpdateIdentityKey(updated);
|
await channels.createOrUpdateIdentityKey(updated);
|
||||||
}
|
}
|
||||||
async function getIdentityKeyById(id) {
|
async function getIdentityKeyById(identifier) {
|
||||||
|
const id = ConversationController.getConversationId(identifier);
|
||||||
const data = await channels.getIdentityKeyById(id);
|
const data = await channels.getIdentityKeyById(id);
|
||||||
return keysToArrayBuffer(IDENTITY_KEY_KEYS, data);
|
return keysToArrayBuffer(IDENTITY_KEY_KEYS, data);
|
||||||
}
|
}
|
||||||
|
@ -444,7 +448,8 @@ async function bulkAddIdentityKeys(array) {
|
||||||
);
|
);
|
||||||
await channels.bulkAddIdentityKeys(updated);
|
await channels.bulkAddIdentityKeys(updated);
|
||||||
}
|
}
|
||||||
async function removeIdentityKeyById(id) {
|
async function removeIdentityKeyById(identifier) {
|
||||||
|
const id = ConversationController.getConversationId(identifier);
|
||||||
await channels.removeIdentityKeyById(id);
|
await channels.removeIdentityKeyById(id);
|
||||||
}
|
}
|
||||||
async function removeAllIdentityKeys() {
|
async function removeAllIdentityKeys() {
|
||||||
|
@ -515,6 +520,11 @@ const ITEM_KEYS = {
|
||||||
'value.signature',
|
'value.signature',
|
||||||
'value.serialized',
|
'value.serialized',
|
||||||
],
|
],
|
||||||
|
senderCertificateWithUuid: [
|
||||||
|
'value.certificate',
|
||||||
|
'value.signature',
|
||||||
|
'value.serialized',
|
||||||
|
],
|
||||||
signaling_key: ['value'],
|
signaling_key: ['value'],
|
||||||
profileKey: ['value'],
|
profileKey: ['value'],
|
||||||
};
|
};
|
||||||
|
@ -572,8 +582,8 @@ async function getSessionById(id) {
|
||||||
const session = await channels.getSessionById(id);
|
const session = await channels.getSessionById(id);
|
||||||
return session;
|
return session;
|
||||||
}
|
}
|
||||||
async function getSessionsByNumber(number) {
|
async function getSessionsById(id) {
|
||||||
const sessions = await channels.getSessionsByNumber(number);
|
const sessions = await channels.getSessionsById(id);
|
||||||
return sessions;
|
return sessions;
|
||||||
}
|
}
|
||||||
async function bulkAddSessions(array) {
|
async function bulkAddSessions(array) {
|
||||||
|
@ -582,8 +592,8 @@ async function bulkAddSessions(array) {
|
||||||
async function removeSessionById(id) {
|
async function removeSessionById(id) {
|
||||||
await channels.removeSessionById(id);
|
await channels.removeSessionById(id);
|
||||||
}
|
}
|
||||||
async function removeSessionsByNumber(number) {
|
async function removeSessionsById(id) {
|
||||||
await channels.removeSessionsByNumber(number);
|
await channels.removeSessionsById(id);
|
||||||
}
|
}
|
||||||
async function removeAllSessions(id) {
|
async function removeAllSessions(id) {
|
||||||
await channels.removeAllSessions(id);
|
await channels.removeAllSessions(id);
|
||||||
|
@ -799,11 +809,12 @@ async function getAllMessageIds() {
|
||||||
|
|
||||||
async function getMessageBySender(
|
async function getMessageBySender(
|
||||||
// eslint-disable-next-line camelcase
|
// eslint-disable-next-line camelcase
|
||||||
{ source, sourceDevice, sent_at },
|
{ source, sourceUuid, sourceDevice, sent_at },
|
||||||
{ Message }
|
{ Message }
|
||||||
) {
|
) {
|
||||||
const messages = await channels.getMessageBySender({
|
const messages = await channels.getMessageBySender({
|
||||||
source,
|
source,
|
||||||
|
sourceUuid,
|
||||||
sourceDevice,
|
sourceDevice,
|
||||||
sent_at,
|
sent_at,
|
||||||
});
|
});
|
||||||
|
|
|
@ -117,13 +117,14 @@ function _createSenderCertificateFromBuffer(serialized) {
|
||||||
!certificate.identityKey ||
|
!certificate.identityKey ||
|
||||||
!certificate.senderDevice ||
|
!certificate.senderDevice ||
|
||||||
!certificate.expires ||
|
!certificate.expires ||
|
||||||
!certificate.sender
|
!(certificate.sender || certificate.senderUuid)
|
||||||
) {
|
) {
|
||||||
throw new Error('Missing fields');
|
throw new Error('Missing fields');
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
sender: certificate.sender,
|
sender: certificate.sender,
|
||||||
|
senderUuid: certificate.senderUuid,
|
||||||
senderDevice: certificate.senderDevice,
|
senderDevice: certificate.senderDevice,
|
||||||
expires: certificate.expires.toNumber(),
|
expires: certificate.expires.toNumber(),
|
||||||
identityKey: certificate.identityKey.toArrayBuffer(),
|
identityKey: certificate.identityKey.toArrayBuffer(),
|
||||||
|
@ -344,7 +345,7 @@ SecretSessionCipher.prototype = {
|
||||||
|
|
||||||
// public Pair<SignalProtocolAddress, byte[]> decrypt(
|
// public Pair<SignalProtocolAddress, byte[]> decrypt(
|
||||||
// CertificateValidator validator, byte[] ciphertext, long timestamp)
|
// CertificateValidator validator, byte[] ciphertext, long timestamp)
|
||||||
async decrypt(validator, ciphertext, timestamp, me) {
|
async decrypt(validator, ciphertext, timestamp, me = {}) {
|
||||||
// Capture this.xxx variables to replicate Java's implicit this syntax
|
// Capture this.xxx variables to replicate Java's implicit this syntax
|
||||||
const signalProtocolStore = this.storage;
|
const signalProtocolStore = this.storage;
|
||||||
const _calculateEphemeralKeys = this._calculateEphemeralKeys.bind(this);
|
const _calculateEphemeralKeys = this._calculateEphemeralKeys.bind(this);
|
||||||
|
@ -401,18 +402,29 @@ SecretSessionCipher.prototype = {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const { sender, senderDevice } = content.senderCertificate;
|
const { sender, senderUuid, senderDevice } = content.senderCertificate;
|
||||||
const { number, deviceId } = me || {};
|
if (
|
||||||
if (sender === number && senderDevice === deviceId) {
|
((sender && me.number && sender === me.number) ||
|
||||||
|
(senderUuid && me.uuid && senderUuid === me.uuid)) &&
|
||||||
|
senderDevice === me.deviceId
|
||||||
|
) {
|
||||||
return {
|
return {
|
||||||
isMe: true,
|
isMe: true,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
const address = new libsignal.SignalProtocolAddress(sender, senderDevice);
|
const addressE164 =
|
||||||
|
sender && new libsignal.SignalProtocolAddress(sender, senderDevice);
|
||||||
|
const addressUuid =
|
||||||
|
senderUuid &&
|
||||||
|
new libsignal.SignalProtocolAddress(
|
||||||
|
senderUuid.toLowerCase(),
|
||||||
|
senderDevice
|
||||||
|
);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
return {
|
return {
|
||||||
sender: address,
|
sender: addressE164,
|
||||||
|
senderUuid: addressUuid,
|
||||||
content: await _decryptWithUnidentifiedSenderMessage(content),
|
content: await _decryptWithUnidentifiedSenderMessage(content),
|
||||||
};
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
@ -421,7 +433,8 @@ SecretSessionCipher.prototype = {
|
||||||
error = new Error('Decryption error was falsey!');
|
error = new Error('Decryption error was falsey!');
|
||||||
}
|
}
|
||||||
|
|
||||||
error.sender = address;
|
error.sender = addressE164;
|
||||||
|
error.senderUuid = addressUuid;
|
||||||
|
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
|
@ -504,7 +517,7 @@ SecretSessionCipher.prototype = {
|
||||||
const signalProtocolStore = this.storage;
|
const signalProtocolStore = this.storage;
|
||||||
|
|
||||||
const sender = new libsignal.SignalProtocolAddress(
|
const sender = new libsignal.SignalProtocolAddress(
|
||||||
message.senderCertificate.sender,
|
message.senderCertificate.sender || message.senderCertificate.senderUuid,
|
||||||
message.senderCertificate.senderDevice
|
message.senderCertificate.senderDevice
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -8,6 +8,7 @@ const { escapeRegExp } = require('lodash');
|
||||||
|
|
||||||
const APP_ROOT_PATH = path.join(__dirname, '..', '..', '..');
|
const APP_ROOT_PATH = path.join(__dirname, '..', '..', '..');
|
||||||
const PHONE_NUMBER_PATTERN = /\+\d{7,12}(\d{3})/g;
|
const PHONE_NUMBER_PATTERN = /\+\d{7,12}(\d{3})/g;
|
||||||
|
const UUID_PATTERN = /[0-9A-F]{8}-[0-9A-F]{4}-4[0-9A-F]{3}-[89AB][0-9A-F]{3}-[0-9A-F]{10}([0-9A-F]{2})/gi;
|
||||||
const GROUP_ID_PATTERN = /(group\()([^)]+)(\))/g;
|
const GROUP_ID_PATTERN = /(group\()([^)]+)(\))/g;
|
||||||
const REDACTION_PLACEHOLDER = '[REDACTED]';
|
const REDACTION_PLACEHOLDER = '[REDACTED]';
|
||||||
|
|
||||||
|
@ -64,6 +65,15 @@ exports.redactPhoneNumbers = text => {
|
||||||
return text.replace(PHONE_NUMBER_PATTERN, `+${REDACTION_PLACEHOLDER}$1`);
|
return text.replace(PHONE_NUMBER_PATTERN, `+${REDACTION_PLACEHOLDER}$1`);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// redactUuids :: String -> String
|
||||||
|
exports.redactUuids = text => {
|
||||||
|
if (!is.string(text)) {
|
||||||
|
throw new TypeError("'text' must be a string");
|
||||||
|
}
|
||||||
|
|
||||||
|
return text.replace(UUID_PATTERN, `${REDACTION_PLACEHOLDER}$1`);
|
||||||
|
};
|
||||||
|
|
||||||
// redactGroupIds :: String -> String
|
// redactGroupIds :: String -> String
|
||||||
exports.redactGroupIds = text => {
|
exports.redactGroupIds = text => {
|
||||||
if (!is.string(text)) {
|
if (!is.string(text)) {
|
||||||
|
@ -84,7 +94,8 @@ exports.redactSensitivePaths = exports._redactPath(APP_ROOT_PATH);
|
||||||
exports.redactAll = compose(
|
exports.redactAll = compose(
|
||||||
exports.redactSensitivePaths,
|
exports.redactSensitivePaths,
|
||||||
exports.redactGroupIds,
|
exports.redactGroupIds,
|
||||||
exports.redactPhoneNumbers
|
exports.redactPhoneNumbers,
|
||||||
|
exports.redactUuids
|
||||||
);
|
);
|
||||||
|
|
||||||
const removeNewlines = text => text.replace(/\r?\n|\r/g, '');
|
const removeNewlines = text => text.replace(/\r?\n|\r/g, '');
|
||||||
|
|
|
@ -16,7 +16,15 @@ let scheduleNext = null;
|
||||||
function refreshOurProfile() {
|
function refreshOurProfile() {
|
||||||
window.log.info('refreshOurProfile');
|
window.log.info('refreshOurProfile');
|
||||||
const ourNumber = textsecure.storage.user.getNumber();
|
const ourNumber = textsecure.storage.user.getNumber();
|
||||||
const conversation = ConversationController.getOrCreate(ourNumber, 'private');
|
const ourUuid = textsecure.storage.user.getUuid();
|
||||||
|
const conversation = ConversationController.getOrCreate(
|
||||||
|
// This is explicitly ourNumber first in order to avoid creating new
|
||||||
|
// conversations when an old one exists
|
||||||
|
ourNumber || ourUuid,
|
||||||
|
'private'
|
||||||
|
);
|
||||||
|
conversation.updateUuid(ourUuid);
|
||||||
|
conversation.updateE164(ourNumber);
|
||||||
conversation.getProfiles();
|
conversation.getProfiles();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -66,21 +74,36 @@ function initialize({ events, storage, navigator, logger }) {
|
||||||
async function run() {
|
async function run() {
|
||||||
logger.info('refreshSenderCertificate: Getting new certificate...');
|
logger.info('refreshSenderCertificate: Getting new certificate...');
|
||||||
try {
|
try {
|
||||||
const username = storage.get('number_id');
|
const OLD_USERNAME = storage.get('number_id');
|
||||||
const password = storage.get('password');
|
const USERNAME = storage.get('uuid_id');
|
||||||
const server = WebAPI.connect({ username, password });
|
const PASSWORD = storage.get('password');
|
||||||
|
const server = WebAPI.connect({
|
||||||
|
username: USERNAME || OLD_USERNAME,
|
||||||
|
password: PASSWORD,
|
||||||
|
});
|
||||||
|
|
||||||
const { certificate } = await server.getSenderCertificate();
|
await Promise.all(
|
||||||
const arrayBuffer = window.Signal.Crypto.base64ToArrayBuffer(certificate);
|
[false, true].map(async withUuid => {
|
||||||
const decoded = textsecure.protobuf.SenderCertificate.decode(arrayBuffer);
|
const { certificate } = await server.getSenderCertificate(withUuid);
|
||||||
|
const arrayBuffer = window.Signal.Crypto.base64ToArrayBuffer(
|
||||||
|
certificate
|
||||||
|
);
|
||||||
|
const decoded = textsecure.protobuf.SenderCertificate.decode(
|
||||||
|
arrayBuffer
|
||||||
|
);
|
||||||
|
|
||||||
decoded.certificate = decoded.certificate.toArrayBuffer();
|
decoded.certificate = decoded.certificate.toArrayBuffer();
|
||||||
decoded.signature = decoded.signature.toArrayBuffer();
|
decoded.signature = decoded.signature.toArrayBuffer();
|
||||||
decoded.serialized = arrayBuffer;
|
decoded.serialized = arrayBuffer;
|
||||||
|
|
||||||
|
storage.put(
|
||||||
|
`senderCertificate${withUuid ? 'WithUuid' : ''}`,
|
||||||
|
decoded
|
||||||
|
);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
storage.put('senderCertificate', decoded);
|
|
||||||
scheduledTime = null;
|
scheduledTime = null;
|
||||||
|
|
||||||
scheduleNextRotation();
|
scheduleNextRotation();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error(
|
logger.error(
|
||||||
|
|
|
@ -394,12 +394,14 @@ const URL_CALLS = {
|
||||||
attachmentId: 'v2/attachments/form/upload',
|
attachmentId: 'v2/attachments/form/upload',
|
||||||
deliveryCert: 'v1/certificate/delivery',
|
deliveryCert: 'v1/certificate/delivery',
|
||||||
supportUnauthenticatedDelivery: 'v1/devices/unauthenticated_delivery',
|
supportUnauthenticatedDelivery: 'v1/devices/unauthenticated_delivery',
|
||||||
|
registerCapabilities: 'v1/devices/capabilities',
|
||||||
devices: 'v1/devices',
|
devices: 'v1/devices',
|
||||||
keys: 'v2/keys',
|
keys: 'v2/keys',
|
||||||
messages: 'v1/messages',
|
messages: 'v1/messages',
|
||||||
profile: 'v1/profile',
|
profile: 'v1/profile',
|
||||||
signed: 'v2/keys/signed',
|
signed: 'v2/keys/signed',
|
||||||
getStickerPackUpload: 'v1/sticker/pack/form',
|
getStickerPackUpload: 'v1/sticker/pack/form',
|
||||||
|
whoami: 'v1/accounts/whoami',
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
|
@ -451,8 +453,8 @@ function initialize({
|
||||||
getAttachment,
|
getAttachment,
|
||||||
getAvatar,
|
getAvatar,
|
||||||
getDevices,
|
getDevices,
|
||||||
getKeysForNumber,
|
getKeysForIdentifier,
|
||||||
getKeysForNumberUnauth,
|
getKeysForIdentifierUnauth,
|
||||||
getMessageSocket,
|
getMessageSocket,
|
||||||
getMyKeys,
|
getMyKeys,
|
||||||
getProfile,
|
getProfile,
|
||||||
|
@ -463,6 +465,7 @@ function initialize({
|
||||||
getStickerPackManifest,
|
getStickerPackManifest,
|
||||||
makeProxiedRequest,
|
makeProxiedRequest,
|
||||||
putAttachment,
|
putAttachment,
|
||||||
|
registerCapabilities,
|
||||||
putStickers,
|
putStickers,
|
||||||
registerKeys,
|
registerKeys,
|
||||||
registerSupportForUnauthenticatedDelivery,
|
registerSupportForUnauthenticatedDelivery,
|
||||||
|
@ -473,6 +476,7 @@ function initialize({
|
||||||
sendMessagesUnauth,
|
sendMessagesUnauth,
|
||||||
setSignedPreKey,
|
setSignedPreKey,
|
||||||
updateDeviceName,
|
updateDeviceName,
|
||||||
|
whoami,
|
||||||
};
|
};
|
||||||
|
|
||||||
function _ajax(param) {
|
function _ajax(param) {
|
||||||
|
@ -535,12 +539,21 @@ function initialize({
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function getSenderCertificate() {
|
function whoami() {
|
||||||
|
return _ajax({
|
||||||
|
call: 'whoami',
|
||||||
|
httpType: 'GET',
|
||||||
|
responseType: 'json',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function getSenderCertificate(withUuid = false) {
|
||||||
return _ajax({
|
return _ajax({
|
||||||
call: 'deliveryCert',
|
call: 'deliveryCert',
|
||||||
httpType: 'GET',
|
httpType: 'GET',
|
||||||
responseType: 'json',
|
responseType: 'json',
|
||||||
schema: { certificate: 'string' },
|
schema: { certificate: 'string' },
|
||||||
|
urlParameters: withUuid ? '?includeUuid=true' : undefined,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -552,19 +565,27 @@ function initialize({
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function getProfile(number) {
|
function registerCapabilities(capabilities) {
|
||||||
|
return _ajax({
|
||||||
|
call: 'registerCapabilities',
|
||||||
|
httpType: 'PUT',
|
||||||
|
jsonData: { capabilities },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function getProfile(identifier) {
|
||||||
return _ajax({
|
return _ajax({
|
||||||
call: 'profile',
|
call: 'profile',
|
||||||
httpType: 'GET',
|
httpType: 'GET',
|
||||||
urlParameters: `/${number}`,
|
urlParameters: `/${identifier}`,
|
||||||
responseType: 'json',
|
responseType: 'json',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
function getProfileUnauth(number, { accessKey } = {}) {
|
function getProfileUnauth(identifier, { accessKey } = {}) {
|
||||||
return _ajax({
|
return _ajax({
|
||||||
call: 'profile',
|
call: 'profile',
|
||||||
httpType: 'GET',
|
httpType: 'GET',
|
||||||
urlParameters: `/${number}`,
|
urlParameters: `/${identifier}`,
|
||||||
responseType: 'json',
|
responseType: 'json',
|
||||||
unauthenticated: true,
|
unauthenticated: true,
|
||||||
accessKey,
|
accessKey,
|
||||||
|
@ -623,17 +644,17 @@ function initialize({
|
||||||
let call;
|
let call;
|
||||||
let urlPrefix;
|
let urlPrefix;
|
||||||
let schema;
|
let schema;
|
||||||
let responseType;
|
|
||||||
|
|
||||||
if (deviceName) {
|
if (deviceName) {
|
||||||
jsonData.name = deviceName;
|
jsonData.name = deviceName;
|
||||||
call = 'devices';
|
call = 'devices';
|
||||||
urlPrefix = '/';
|
urlPrefix = '/';
|
||||||
schema = { deviceId: 'number' };
|
|
||||||
responseType = 'json';
|
|
||||||
} else {
|
} else {
|
||||||
call = 'accounts';
|
call = 'accounts';
|
||||||
urlPrefix = '/code/';
|
urlPrefix = '/code/';
|
||||||
|
jsonData.capabilities = {
|
||||||
|
uuid: true,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// We update our saved username and password, since we're creating a new account
|
// We update our saved username and password, since we're creating a new account
|
||||||
|
@ -643,14 +664,14 @@ function initialize({
|
||||||
const response = await _ajax({
|
const response = await _ajax({
|
||||||
call,
|
call,
|
||||||
httpType: 'PUT',
|
httpType: 'PUT',
|
||||||
|
responseType: 'json',
|
||||||
urlParameters: urlPrefix + code,
|
urlParameters: urlPrefix + code,
|
||||||
jsonData,
|
jsonData,
|
||||||
responseType,
|
|
||||||
validateResponse: schema,
|
validateResponse: schema,
|
||||||
});
|
});
|
||||||
|
|
||||||
// From here on out, our username will be our phone number combined with device
|
// From here on out, our username will be our UUID or E164 combined with device
|
||||||
username = `${number}.${response.deviceId || 1}`;
|
username = `${response.uuid || number}.${response.deviceId || 1}`;
|
||||||
|
|
||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
@ -768,25 +789,25 @@ function initialize({
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getKeysForNumber(number, deviceId = '*') {
|
function getKeysForIdentifier(identifier, deviceId = '*') {
|
||||||
return _ajax({
|
return _ajax({
|
||||||
call: 'keys',
|
call: 'keys',
|
||||||
httpType: 'GET',
|
httpType: 'GET',
|
||||||
urlParameters: `/${number}/${deviceId}`,
|
urlParameters: `/${identifier}/${deviceId}`,
|
||||||
responseType: 'json',
|
responseType: 'json',
|
||||||
validateResponse: { identityKey: 'string', devices: 'object' },
|
validateResponse: { identityKey: 'string', devices: 'object' },
|
||||||
}).then(handleKeys);
|
}).then(handleKeys);
|
||||||
}
|
}
|
||||||
|
|
||||||
function getKeysForNumberUnauth(
|
function getKeysForIdentifierUnauth(
|
||||||
number,
|
identifier,
|
||||||
deviceId = '*',
|
deviceId = '*',
|
||||||
{ accessKey } = {}
|
{ accessKey } = {}
|
||||||
) {
|
) {
|
||||||
return _ajax({
|
return _ajax({
|
||||||
call: 'keys',
|
call: 'keys',
|
||||||
httpType: 'GET',
|
httpType: 'GET',
|
||||||
urlParameters: `/${number}/${deviceId}`,
|
urlParameters: `/${identifier}/${deviceId}`,
|
||||||
responseType: 'json',
|
responseType: 'json',
|
||||||
validateResponse: { identityKey: 'string', devices: 'object' },
|
validateResponse: { identityKey: 'string', devices: 'object' },
|
||||||
unauthenticated: true,
|
unauthenticated: true,
|
||||||
|
|
|
@ -36,7 +36,9 @@
|
||||||
|
|
||||||
const found = messages.find(
|
const found = messages.find(
|
||||||
item =>
|
item =>
|
||||||
item.isIncoming() && item.get('source') === receipt.get('sender')
|
item.isIncoming() &&
|
||||||
|
(item.get('source') === receipt.get('sender') ||
|
||||||
|
item.get('sourceUuid') === receipt.get('senderUuid'))
|
||||||
);
|
);
|
||||||
const notificationForMessage = found
|
const notificationForMessage = found
|
||||||
? Whisper.Notifications.findWhere({ messageId: found.id })
|
? Whisper.Notifications.findWhere({ messageId: found.id })
|
||||||
|
@ -47,6 +49,7 @@
|
||||||
window.log.info(
|
window.log.info(
|
||||||
'No message for read sync',
|
'No message for read sync',
|
||||||
receipt.get('sender'),
|
receipt.get('sender'),
|
||||||
|
receipt.get('senderUuid'),
|
||||||
receipt.get('timestamp')
|
receipt.get('timestamp')
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
|
|
|
@ -147,6 +147,24 @@
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
async function normalizeEncodedAddress(encodedAddress) {
|
||||||
|
const [identifier, deviceId] = textsecure.utils.unencodeNumber(
|
||||||
|
encodedAddress
|
||||||
|
);
|
||||||
|
try {
|
||||||
|
const conv = await ConversationController.getOrCreateAndWait(
|
||||||
|
identifier,
|
||||||
|
'private'
|
||||||
|
);
|
||||||
|
return `${conv.get('id')}.${deviceId}`;
|
||||||
|
} catch (e) {
|
||||||
|
window.log.error(
|
||||||
|
`could not get conversation for identifier ${identifier}`
|
||||||
|
);
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function SignalProtocolStore() {
|
function SignalProtocolStore() {
|
||||||
this.sessionUpdateBatcher = window.Signal.Util.createBatcher({
|
this.sessionUpdateBatcher = window.Signal.Util.createBatcher({
|
||||||
wait: 500,
|
wait: 500,
|
||||||
|
@ -322,66 +340,98 @@
|
||||||
|
|
||||||
// Sessions
|
// Sessions
|
||||||
|
|
||||||
async loadSession(encodedNumber) {
|
async loadSession(encodedAddress) {
|
||||||
if (encodedNumber === null || encodedNumber === undefined) {
|
if (encodedAddress === null || encodedAddress === undefined) {
|
||||||
throw new Error('Tried to get session for undefined/null number');
|
throw new Error('Tried to get session for undefined/null number');
|
||||||
}
|
}
|
||||||
|
|
||||||
const session = this.sessions[encodedNumber];
|
try {
|
||||||
if (session) {
|
const id = await normalizeEncodedAddress(encodedAddress);
|
||||||
return session.record;
|
const session = this.sessions[id];
|
||||||
|
|
||||||
|
if (session) {
|
||||||
|
return session.record;
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
window.log.error(`could not load session ${encodedAddress}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
return undefined;
|
return undefined;
|
||||||
},
|
},
|
||||||
async storeSession(encodedNumber, record) {
|
async storeSession(encodedAddress, record) {
|
||||||
if (encodedNumber === null || encodedNumber === undefined) {
|
if (encodedAddress === null || encodedAddress === undefined) {
|
||||||
throw new Error('Tried to put session for undefined/null number');
|
throw new Error('Tried to put session for undefined/null number');
|
||||||
}
|
}
|
||||||
const unencoded = textsecure.utils.unencodeNumber(encodedNumber);
|
const unencoded = textsecure.utils.unencodeNumber(encodedAddress);
|
||||||
const number = unencoded[0];
|
|
||||||
const deviceId = parseInt(unencoded[1], 10);
|
const deviceId = parseInt(unencoded[1], 10);
|
||||||
|
|
||||||
const data = {
|
try {
|
||||||
id: encodedNumber,
|
const id = await normalizeEncodedAddress(encodedAddress);
|
||||||
number,
|
|
||||||
deviceId,
|
|
||||||
record,
|
|
||||||
};
|
|
||||||
|
|
||||||
this.sessions[encodedNumber] = data;
|
const data = {
|
||||||
|
id,
|
||||||
|
conversationId: textsecure.utils.unencodeNumber(id)[0],
|
||||||
|
deviceId,
|
||||||
|
record,
|
||||||
|
};
|
||||||
|
|
||||||
// Note: Because these are cached in memory, we batch and make these database
|
this.sessions[id] = data;
|
||||||
// updates out of band.
|
|
||||||
this.sessionUpdateBatcher.add(data);
|
// Note: Because these are cached in memory, we batch and make these database
|
||||||
|
// updates out of band.
|
||||||
|
this.sessionUpdateBatcher.add(data);
|
||||||
|
} catch (e) {
|
||||||
|
window.log.error(`could not store session for ${encodedAddress}`);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
async getDeviceIds(number) {
|
async getDeviceIds(identifier) {
|
||||||
if (number === null || number === undefined) {
|
if (identifier === null || identifier === undefined) {
|
||||||
throw new Error('Tried to get device ids for undefined/null number');
|
throw new Error('Tried to get device ids for undefined/null number');
|
||||||
}
|
}
|
||||||
|
|
||||||
const allSessions = Object.values(this.sessions);
|
try {
|
||||||
const sessions = allSessions.filter(session => session.number === number);
|
const id = ConversationController.getConversationId(identifier);
|
||||||
return _.pluck(sessions, 'deviceId');
|
const allSessions = Object.values(this.sessions);
|
||||||
|
const sessions = allSessions.filter(
|
||||||
|
session => session.conversationId === id
|
||||||
|
);
|
||||||
|
|
||||||
|
return _.pluck(sessions, 'deviceId');
|
||||||
|
} catch (e) {
|
||||||
|
window.log.error(
|
||||||
|
`could not get device ids for identifier ${identifier}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return [];
|
||||||
},
|
},
|
||||||
async removeSession(encodedNumber) {
|
async removeSession(encodedAddress) {
|
||||||
window.log.info('deleting session for ', encodedNumber);
|
window.log.info('deleting session for ', encodedAddress);
|
||||||
delete this.sessions[encodedNumber];
|
try {
|
||||||
await window.Signal.Data.removeSessionById(encodedNumber);
|
const id = await normalizeEncodedAddress(encodedAddress);
|
||||||
|
delete this.sessions[id];
|
||||||
|
await window.Signal.Data.removeSessionById(id);
|
||||||
|
} catch (e) {
|
||||||
|
window.log.error(`could not delete session for ${encodedAddress}`);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
async removeAllSessions(number) {
|
async removeAllSessions(identifier) {
|
||||||
if (number === null || number === undefined) {
|
if (identifier === null || identifier === undefined) {
|
||||||
throw new Error('Tried to remove sessions for undefined/null number');
|
throw new Error('Tried to remove sessions for undefined/null number');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const id = ConversationController.getConversationId(identifier);
|
||||||
|
|
||||||
const allSessions = Object.values(this.sessions);
|
const allSessions = Object.values(this.sessions);
|
||||||
|
|
||||||
for (let i = 0, max = allSessions.length; i < max; i += 1) {
|
for (let i = 0, max = allSessions.length; i < max; i += 1) {
|
||||||
const session = allSessions[i];
|
const session = allSessions[i];
|
||||||
if (session.number === number) {
|
if (session.conversationId === id) {
|
||||||
delete this.sessions[session.id];
|
delete this.sessions[session.id];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
await window.Signal.Data.removeSessionsByNumber(number);
|
|
||||||
|
await window.Signal.Data.removeSessionsById(identifier);
|
||||||
},
|
},
|
||||||
async archiveSiblingSessions(identifier) {
|
async archiveSiblingSessions(identifier) {
|
||||||
const address = libsignal.SignalProtocolAddress.fromString(identifier);
|
const address = libsignal.SignalProtocolAddress.fromString(identifier);
|
||||||
|
@ -404,12 +454,15 @@
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
async archiveAllSessions(number) {
|
async archiveAllSessions(identifier) {
|
||||||
const deviceIds = await this.getDeviceIds(number);
|
const deviceIds = await this.getDeviceIds(identifier);
|
||||||
|
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
deviceIds.map(async deviceId => {
|
deviceIds.map(async deviceId => {
|
||||||
const address = new libsignal.SignalProtocolAddress(number, deviceId);
|
const address = new libsignal.SignalProtocolAddress(
|
||||||
|
identifier,
|
||||||
|
deviceId
|
||||||
|
);
|
||||||
window.log.info('closing session for', address.toString());
|
window.log.info('closing session for', address.toString());
|
||||||
const sessionCipher = new libsignal.SessionCipher(
|
const sessionCipher = new libsignal.SessionCipher(
|
||||||
textsecure.storage.protocol,
|
textsecure.storage.protocol,
|
||||||
|
@ -426,16 +479,35 @@
|
||||||
|
|
||||||
// Identity Keys
|
// Identity Keys
|
||||||
|
|
||||||
async isTrustedIdentity(identifier, publicKey, direction) {
|
getIdentityRecord(identifier) {
|
||||||
if (identifier === null || identifier === undefined) {
|
try {
|
||||||
|
const id = ConversationController.getConversationId(identifier);
|
||||||
|
const record = this.identityKeys[id];
|
||||||
|
|
||||||
|
if (record) {
|
||||||
|
return record;
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
window.log.error(
|
||||||
|
`could not get identity record for identifier ${identifier}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return undefined;
|
||||||
|
},
|
||||||
|
|
||||||
|
async isTrustedIdentity(encodedAddress, publicKey, direction) {
|
||||||
|
if (encodedAddress === null || encodedAddress === undefined) {
|
||||||
throw new Error('Tried to get identity key for undefined/null key');
|
throw new Error('Tried to get identity key for undefined/null key');
|
||||||
}
|
}
|
||||||
const number = textsecure.utils.unencodeNumber(identifier)[0];
|
const identifier = textsecure.utils.unencodeNumber(encodedAddress)[0];
|
||||||
const isOurNumber = number === textsecure.storage.user.getNumber();
|
const isOurIdentifier =
|
||||||
|
identifier === textsecure.storage.user.getNumber() ||
|
||||||
|
identifier === textsecure.storage.user.getUuid();
|
||||||
|
|
||||||
const identityRecord = this.identityKeys[number];
|
const identityRecord = this.getIdentityRecord(identifier);
|
||||||
|
|
||||||
if (isOurNumber) {
|
if (isOurIdentifier) {
|
||||||
const existing = identityRecord ? identityRecord.publicKey : null;
|
const existing = identityRecord ? identityRecord.publicKey : null;
|
||||||
return equalArrayBuffers(existing, publicKey);
|
return equalArrayBuffers(existing, publicKey);
|
||||||
}
|
}
|
||||||
|
@ -482,8 +554,8 @@
|
||||||
if (identifier === null || identifier === undefined) {
|
if (identifier === null || identifier === undefined) {
|
||||||
throw new Error('Tried to get identity key for undefined/null key');
|
throw new Error('Tried to get identity key for undefined/null key');
|
||||||
}
|
}
|
||||||
const number = textsecure.utils.unencodeNumber(identifier)[0];
|
const id = textsecure.utils.unencodeNumber(identifier)[0];
|
||||||
const identityRecord = this.identityKeys[number];
|
const identityRecord = this.getIdentityRecord(id);
|
||||||
|
|
||||||
if (identityRecord) {
|
if (identityRecord) {
|
||||||
return identityRecord.publicKey;
|
return identityRecord.publicKey;
|
||||||
|
@ -496,8 +568,8 @@
|
||||||
this.identityKeys[id] = data;
|
this.identityKeys[id] = data;
|
||||||
await window.Signal.Data.createOrUpdateIdentityKey(data);
|
await window.Signal.Data.createOrUpdateIdentityKey(data);
|
||||||
},
|
},
|
||||||
async saveIdentity(identifier, publicKey, nonblockingApproval) {
|
async saveIdentity(encodedAddress, publicKey, nonblockingApproval) {
|
||||||
if (identifier === null || identifier === undefined) {
|
if (encodedAddress === null || encodedAddress === undefined) {
|
||||||
throw new Error('Tried to put identity key for undefined/null key');
|
throw new Error('Tried to put identity key for undefined/null key');
|
||||||
}
|
}
|
||||||
if (!(publicKey instanceof ArrayBuffer)) {
|
if (!(publicKey instanceof ArrayBuffer)) {
|
||||||
|
@ -509,14 +581,15 @@
|
||||||
nonblockingApproval = false;
|
nonblockingApproval = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const number = textsecure.utils.unencodeNumber(identifier)[0];
|
const identifer = textsecure.utils.unencodeNumber(encodedAddress)[0];
|
||||||
const identityRecord = this.identityKeys[number];
|
const identityRecord = this.getIdentityRecord(identifer);
|
||||||
|
const id = ConversationController.getConversationId(identifer);
|
||||||
|
|
||||||
if (!identityRecord || !identityRecord.publicKey) {
|
if (!identityRecord || !identityRecord.publicKey) {
|
||||||
// Lookup failed, or the current key was removed, so save this one.
|
// Lookup failed, or the current key was removed, so save this one.
|
||||||
window.log.info('Saving new identity...');
|
window.log.info('Saving new identity...');
|
||||||
await this._saveIdentityKey({
|
await this._saveIdentityKey({
|
||||||
id: number,
|
id,
|
||||||
publicKey,
|
publicKey,
|
||||||
firstUse: true,
|
firstUse: true,
|
||||||
timestamp: Date.now(),
|
timestamp: Date.now(),
|
||||||
|
@ -542,7 +615,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
await this._saveIdentityKey({
|
await this._saveIdentityKey({
|
||||||
id: number,
|
id,
|
||||||
publicKey,
|
publicKey,
|
||||||
firstUse: false,
|
firstUse: false,
|
||||||
timestamp: Date.now(),
|
timestamp: Date.now(),
|
||||||
|
@ -551,14 +624,14 @@
|
||||||
});
|
});
|
||||||
|
|
||||||
try {
|
try {
|
||||||
this.trigger('keychange', number);
|
this.trigger('keychange', identifer);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
window.log.error(
|
window.log.error(
|
||||||
'saveIdentity error triggering keychange:',
|
'saveIdentity error triggering keychange:',
|
||||||
error && error.stack ? error.stack : error
|
error && error.stack ? error.stack : error
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
await this.archiveSiblingSessions(identifier);
|
await this.archiveSiblingSessions(encodedAddress);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
} else if (this.isNonBlockingApprovalRequired(identityRecord)) {
|
} else if (this.isNonBlockingApprovalRequired(identityRecord)) {
|
||||||
|
@ -579,16 +652,21 @@
|
||||||
!identityRecord.nonblockingApproval
|
!identityRecord.nonblockingApproval
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
async saveIdentityWithAttributes(identifier, attributes) {
|
async saveIdentityWithAttributes(encodedAddress, attributes) {
|
||||||
if (identifier === null || identifier === undefined) {
|
if (encodedAddress === null || encodedAddress === undefined) {
|
||||||
throw new Error('Tried to put identity key for undefined/null key');
|
throw new Error('Tried to put identity key for undefined/null key');
|
||||||
}
|
}
|
||||||
|
|
||||||
const number = textsecure.utils.unencodeNumber(identifier)[0];
|
const identifier = textsecure.utils.unencodeNumber(encodedAddress)[0];
|
||||||
const identityRecord = this.identityKeys[number];
|
const identityRecord = this.getIdentityRecord(identifier);
|
||||||
|
const conv = await ConversationController.getOrCreateAndWait(
|
||||||
|
identifier,
|
||||||
|
'private'
|
||||||
|
);
|
||||||
|
const id = conv.get('id');
|
||||||
|
|
||||||
const updates = {
|
const updates = {
|
||||||
id: number,
|
id,
|
||||||
...identityRecord,
|
...identityRecord,
|
||||||
...attributes,
|
...attributes,
|
||||||
};
|
};
|
||||||
|
@ -600,26 +678,26 @@
|
||||||
throw model.validationError;
|
throw model.validationError;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
async setApproval(identifier, nonblockingApproval) {
|
async setApproval(encodedAddress, nonblockingApproval) {
|
||||||
if (identifier === null || identifier === undefined) {
|
if (encodedAddress === null || encodedAddress === undefined) {
|
||||||
throw new Error('Tried to set approval for undefined/null identifier');
|
throw new Error('Tried to set approval for undefined/null identifier');
|
||||||
}
|
}
|
||||||
if (typeof nonblockingApproval !== 'boolean') {
|
if (typeof nonblockingApproval !== 'boolean') {
|
||||||
throw new Error('Invalid approval status');
|
throw new Error('Invalid approval status');
|
||||||
}
|
}
|
||||||
|
|
||||||
const number = textsecure.utils.unencodeNumber(identifier)[0];
|
const identifier = textsecure.utils.unencodeNumber(encodedAddress)[0];
|
||||||
const identityRecord = this.identityKeys[number];
|
const identityRecord = this.getIdentityRecord(identifier);
|
||||||
|
|
||||||
if (!identityRecord) {
|
if (!identityRecord) {
|
||||||
throw new Error(`No identity record for ${number}`);
|
throw new Error(`No identity record for ${identifier}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
identityRecord.nonblockingApproval = nonblockingApproval;
|
identityRecord.nonblockingApproval = nonblockingApproval;
|
||||||
await this._saveIdentityKey(identityRecord);
|
await this._saveIdentityKey(identityRecord);
|
||||||
},
|
},
|
||||||
async setVerified(number, verifiedStatus, publicKey) {
|
async setVerified(encodedAddress, verifiedStatus, publicKey) {
|
||||||
if (number === null || number === undefined) {
|
if (encodedAddress === null || encodedAddress === undefined) {
|
||||||
throw new Error('Tried to set verified for undefined/null key');
|
throw new Error('Tried to set verified for undefined/null key');
|
||||||
}
|
}
|
||||||
if (!validateVerifiedStatus(verifiedStatus)) {
|
if (!validateVerifiedStatus(verifiedStatus)) {
|
||||||
|
@ -629,9 +707,10 @@
|
||||||
throw new Error('Invalid public key');
|
throw new Error('Invalid public key');
|
||||||
}
|
}
|
||||||
|
|
||||||
const identityRecord = this.identityKeys[number];
|
const identityRecord = this.getIdentityRecord(encodedAddress);
|
||||||
|
|
||||||
if (!identityRecord) {
|
if (!identityRecord) {
|
||||||
throw new Error(`No identity record for ${number}`);
|
throw new Error(`No identity record for ${encodedAddress}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
|
@ -650,14 +729,14 @@
|
||||||
window.log.info('No identity record for specified publicKey');
|
window.log.info('No identity record for specified publicKey');
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
async getVerified(number) {
|
async getVerified(identifier) {
|
||||||
if (number === null || number === undefined) {
|
if (identifier === null || identifier === undefined) {
|
||||||
throw new Error('Tried to set verified for undefined/null key');
|
throw new Error('Tried to set verified for undefined/null key');
|
||||||
}
|
}
|
||||||
|
|
||||||
const identityRecord = this.identityKeys[number];
|
const identityRecord = this.getIdentityRecord(identifier);
|
||||||
if (!identityRecord) {
|
if (!identityRecord) {
|
||||||
throw new Error(`No identity record for ${number}`);
|
throw new Error(`No identity record for ${identifier}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const verifiedStatus = identityRecord.verified;
|
const verifiedStatus = identityRecord.verified;
|
||||||
|
@ -681,15 +760,16 @@
|
||||||
// This function encapsulates the non-Java behavior, since the mobile apps don't
|
// This function encapsulates the non-Java behavior, since the mobile apps don't
|
||||||
// currently receive contact syncs and therefore will see a verify sync with
|
// currently receive contact syncs and therefore will see a verify sync with
|
||||||
// UNVERIFIED status
|
// UNVERIFIED status
|
||||||
async processUnverifiedMessage(number, verifiedStatus, publicKey) {
|
async processUnverifiedMessage(identifier, verifiedStatus, publicKey) {
|
||||||
if (number === null || number === undefined) {
|
if (identifier === null || identifier === undefined) {
|
||||||
throw new Error('Tried to set verified for undefined/null key');
|
throw new Error('Tried to set verified for undefined/null key');
|
||||||
}
|
}
|
||||||
if (publicKey !== undefined && !(publicKey instanceof ArrayBuffer)) {
|
if (publicKey !== undefined && !(publicKey instanceof ArrayBuffer)) {
|
||||||
throw new Error('Invalid public key');
|
throw new Error('Invalid public key');
|
||||||
}
|
}
|
||||||
|
|
||||||
const identityRecord = this.identityKeys[number];
|
const identityRecord = this.getIdentityRecord(identifier);
|
||||||
|
|
||||||
const isPresent = Boolean(identityRecord);
|
const isPresent = Boolean(identityRecord);
|
||||||
let isEqual = false;
|
let isEqual = false;
|
||||||
|
|
||||||
|
@ -703,7 +783,7 @@
|
||||||
identityRecord.verified !== VerifiedStatus.UNVERIFIED
|
identityRecord.verified !== VerifiedStatus.UNVERIFIED
|
||||||
) {
|
) {
|
||||||
await textsecure.storage.protocol.setVerified(
|
await textsecure.storage.protocol.setVerified(
|
||||||
number,
|
identifier,
|
||||||
verifiedStatus,
|
verifiedStatus,
|
||||||
publicKey
|
publicKey
|
||||||
);
|
);
|
||||||
|
@ -711,17 +791,20 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!isPresent || !isEqual) {
|
if (!isPresent || !isEqual) {
|
||||||
await textsecure.storage.protocol.saveIdentityWithAttributes(number, {
|
await textsecure.storage.protocol.saveIdentityWithAttributes(
|
||||||
publicKey,
|
identifier,
|
||||||
verified: verifiedStatus,
|
{
|
||||||
firstUse: false,
|
publicKey,
|
||||||
timestamp: Date.now(),
|
verified: verifiedStatus,
|
||||||
nonblockingApproval: true,
|
firstUse: false,
|
||||||
});
|
timestamp: Date.now(),
|
||||||
|
nonblockingApproval: true,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
if (isPresent && !isEqual) {
|
if (isPresent && !isEqual) {
|
||||||
try {
|
try {
|
||||||
this.trigger('keychange', number);
|
this.trigger('keychange', identifier);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
window.log.error(
|
window.log.error(
|
||||||
'processUnverifiedMessage error triggering keychange:',
|
'processUnverifiedMessage error triggering keychange:',
|
||||||
|
@ -729,7 +812,7 @@
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.archiveAllSessions(number);
|
await this.archiveAllSessions(identifier);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -743,8 +826,8 @@
|
||||||
},
|
},
|
||||||
// This matches the Java method as of
|
// This matches the Java method as of
|
||||||
// https://github.com/signalapp/Signal-Android/blob/d0bb68e1378f689e4d10ac6a46014164992ca4e4/src/org/thoughtcrime/securesms/util/IdentityUtil.java#L188
|
// https://github.com/signalapp/Signal-Android/blob/d0bb68e1378f689e4d10ac6a46014164992ca4e4/src/org/thoughtcrime/securesms/util/IdentityUtil.java#L188
|
||||||
async processVerifiedMessage(number, verifiedStatus, publicKey) {
|
async processVerifiedMessage(identifier, verifiedStatus, publicKey) {
|
||||||
if (number === null || number === undefined) {
|
if (identifier === null || identifier === undefined) {
|
||||||
throw new Error('Tried to set verified for undefined/null key');
|
throw new Error('Tried to set verified for undefined/null key');
|
||||||
}
|
}
|
||||||
if (!validateVerifiedStatus(verifiedStatus)) {
|
if (!validateVerifiedStatus(verifiedStatus)) {
|
||||||
|
@ -754,7 +837,7 @@
|
||||||
throw new Error('Invalid public key');
|
throw new Error('Invalid public key');
|
||||||
}
|
}
|
||||||
|
|
||||||
const identityRecord = this.identityKeys[number];
|
const identityRecord = this.getIdentityRecord(identifier);
|
||||||
|
|
||||||
const isPresent = Boolean(identityRecord);
|
const isPresent = Boolean(identityRecord);
|
||||||
let isEqual = false;
|
let isEqual = false;
|
||||||
|
@ -775,7 +858,7 @@
|
||||||
verifiedStatus === VerifiedStatus.DEFAULT
|
verifiedStatus === VerifiedStatus.DEFAULT
|
||||||
) {
|
) {
|
||||||
await textsecure.storage.protocol.setVerified(
|
await textsecure.storage.protocol.setVerified(
|
||||||
number,
|
identifier,
|
||||||
verifiedStatus,
|
verifiedStatus,
|
||||||
publicKey
|
publicKey
|
||||||
);
|
);
|
||||||
|
@ -788,17 +871,20 @@
|
||||||
(isPresent && !isEqual) ||
|
(isPresent && !isEqual) ||
|
||||||
(isPresent && identityRecord.verified !== VerifiedStatus.VERIFIED))
|
(isPresent && identityRecord.verified !== VerifiedStatus.VERIFIED))
|
||||||
) {
|
) {
|
||||||
await textsecure.storage.protocol.saveIdentityWithAttributes(number, {
|
await textsecure.storage.protocol.saveIdentityWithAttributes(
|
||||||
publicKey,
|
identifier,
|
||||||
verified: verifiedStatus,
|
{
|
||||||
firstUse: false,
|
publicKey,
|
||||||
timestamp: Date.now(),
|
verified: verifiedStatus,
|
||||||
nonblockingApproval: true,
|
firstUse: false,
|
||||||
});
|
timestamp: Date.now(),
|
||||||
|
nonblockingApproval: true,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
if (isPresent && !isEqual) {
|
if (isPresent && !isEqual) {
|
||||||
try {
|
try {
|
||||||
this.trigger('keychange', number);
|
this.trigger('keychange', identifier);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
window.log.error(
|
window.log.error(
|
||||||
'processVerifiedMessage error triggering keychange:',
|
'processVerifiedMessage error triggering keychange:',
|
||||||
|
@ -806,7 +892,7 @@
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.archiveAllSessions(number);
|
await this.archiveAllSessions(identifier);
|
||||||
|
|
||||||
// true signifies that we overwrote a previous key with a new one
|
// true signifies that we overwrote a previous key with a new one
|
||||||
return true;
|
return true;
|
||||||
|
@ -818,14 +904,14 @@
|
||||||
// state we had before.
|
// state we had before.
|
||||||
return false;
|
return false;
|
||||||
},
|
},
|
||||||
async isUntrusted(number) {
|
async isUntrusted(identifier) {
|
||||||
if (number === null || number === undefined) {
|
if (identifier === null || identifier === undefined) {
|
||||||
throw new Error('Tried to set verified for undefined/null key');
|
throw new Error('Tried to set verified for undefined/null key');
|
||||||
}
|
}
|
||||||
|
|
||||||
const identityRecord = this.identityKeys[number];
|
const identityRecord = this.getIdentityRecord(identifier);
|
||||||
if (!identityRecord) {
|
if (!identityRecord) {
|
||||||
throw new Error(`No identity record for ${number}`);
|
throw new Error(`No identity record for ${identifier}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
|
@ -838,10 +924,13 @@
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
},
|
},
|
||||||
async removeIdentityKey(number) {
|
async removeIdentityKey(identifier) {
|
||||||
delete this.identityKeys[number];
|
const id = ConversationController.getConversationId(identifier);
|
||||||
await window.Signal.Data.removeIdentityKeyById(number);
|
if (id) {
|
||||||
await textsecure.storage.protocol.removeAllSessions(number);
|
delete this.identityKeys[id];
|
||||||
|
await window.Signal.Data.removeIdentityKeyById(id);
|
||||||
|
await textsecure.storage.protocol.removeAllSessions(id);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
// Not yet processed messages - for resiliency
|
// Not yet processed messages - for resiliency
|
||||||
|
|
|
@ -14,7 +14,7 @@
|
||||||
Whisper.ViewSyncs = new (Backbone.Collection.extend({
|
Whisper.ViewSyncs = new (Backbone.Collection.extend({
|
||||||
forMessage(message) {
|
forMessage(message) {
|
||||||
const sync = this.findWhere({
|
const sync = this.findWhere({
|
||||||
source: message.get('source'),
|
conversationId: message.get('conversationId'),
|
||||||
timestamp: message.get('sent_at'),
|
timestamp: message.get('sent_at'),
|
||||||
});
|
});
|
||||||
if (sync) {
|
if (sync) {
|
||||||
|
@ -35,13 +35,15 @@
|
||||||
);
|
);
|
||||||
|
|
||||||
const found = messages.find(
|
const found = messages.find(
|
||||||
item => item.get('source') === sync.get('source')
|
item => item.get('conversationId') === sync.get('conversationId')
|
||||||
);
|
);
|
||||||
const syncSource = sync.get('source');
|
const syncSource = sync.get('source');
|
||||||
|
const syncSourceUuid = sync.get('sourceUuid');
|
||||||
const syncTimestamp = sync.get('timestamp');
|
const syncTimestamp = sync.get('timestamp');
|
||||||
const wasMessageFound = Boolean(found);
|
const wasMessageFound = Boolean(found);
|
||||||
window.log.info('Receive view sync:', {
|
window.log.info('Receive view sync:', {
|
||||||
syncSource,
|
syncSource,
|
||||||
|
syncSourceUuid,
|
||||||
syncTimestamp,
|
syncTimestamp,
|
||||||
wasMessageFound,
|
wasMessageFound,
|
||||||
});
|
});
|
||||||
|
|
|
@ -26,7 +26,7 @@
|
||||||
this.contactView = null;
|
this.contactView = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const isMe = this.ourNumber === this.model.id;
|
const isMe = this.model.isMe();
|
||||||
|
|
||||||
this.contactView = new Whisper.ReactWrapperView({
|
this.contactView = new Whisper.ReactWrapperView({
|
||||||
className: 'contact-wrapper',
|
className: 'contact-wrapper',
|
||||||
|
@ -47,7 +47,7 @@
|
||||||
return this;
|
return this;
|
||||||
},
|
},
|
||||||
showIdentity() {
|
showIdentity() {
|
||||||
if (this.model.id === this.ourNumber || this.loading) {
|
if (this.model.isMe() || this.loading) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2004,7 +2004,7 @@
|
||||||
await contact.setApproved();
|
await contact.setApproved();
|
||||||
}
|
}
|
||||||
|
|
||||||
message.resend(contact.id);
|
message.resend(contact.get('e164'), contact.get('uuid'));
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -2483,6 +2483,7 @@
|
||||||
try {
|
try {
|
||||||
await this.model.sendReactionMessage(reaction, {
|
await this.model.sendReactionMessage(reaction, {
|
||||||
targetAuthorE164: messageModel.getSource(),
|
targetAuthorE164: messageModel.getSource(),
|
||||||
|
targetAuthorUuid: messageModel.getSourceUuid(),
|
||||||
targetTimestamp: messageModel.get('sent_at'),
|
targetTimestamp: messageModel.get('sent_at'),
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
@ -2668,10 +2669,17 @@
|
||||||
if (window.reduxStore.getState().expiration.hasExpired) {
|
if (window.reduxStore.getState().expiration.hasExpired) {
|
||||||
ToastView = Whisper.ExpiredToast;
|
ToastView = Whisper.ExpiredToast;
|
||||||
}
|
}
|
||||||
if (this.model.isPrivate() && storage.isBlocked(this.model.id)) {
|
if (
|
||||||
|
this.model.isPrivate() &&
|
||||||
|
(storage.isBlocked(this.model.get('e164')) ||
|
||||||
|
storage.isUuidBlocked(this.model.get('uuid')))
|
||||||
|
) {
|
||||||
ToastView = Whisper.BlockedToast;
|
ToastView = Whisper.BlockedToast;
|
||||||
}
|
}
|
||||||
if (!this.model.isPrivate() && storage.isGroupBlocked(this.model.id)) {
|
if (
|
||||||
|
!this.model.isPrivate() &&
|
||||||
|
storage.isGroupBlocked(this.model.get('groupId'))
|
||||||
|
) {
|
||||||
ToastView = Whisper.BlockedGroupToast;
|
ToastView = Whisper.BlockedGroupToast;
|
||||||
}
|
}
|
||||||
if (!this.model.isPrivate() && this.model.get('left')) {
|
if (!this.model.isPrivate() && this.model.get('left')) {
|
||||||
|
|
|
@ -16,6 +16,7 @@
|
||||||
},
|
},
|
||||||
initialize(options) {
|
initialize(options) {
|
||||||
this.ourNumber = textsecure.storage.user.getNumber();
|
this.ourNumber = textsecure.storage.user.getNumber();
|
||||||
|
this.ourUuid = textsecure.storage.user.getUuid();
|
||||||
if (options.newKey) {
|
if (options.newKey) {
|
||||||
this.theirKey = options.newKey;
|
this.theirKey = options.newKey;
|
||||||
}
|
}
|
||||||
|
@ -44,16 +45,29 @@
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
loadTheirKey() {
|
loadTheirKey() {
|
||||||
const item = textsecure.storage.protocol.identityKeys[this.model.id];
|
const item = textsecure.storage.protocol.getIdentityRecord(
|
||||||
|
this.model.get('id')
|
||||||
|
);
|
||||||
this.theirKey = item ? item.publicKey : null;
|
this.theirKey = item ? item.publicKey : null;
|
||||||
},
|
},
|
||||||
loadOurKey() {
|
loadOurKey() {
|
||||||
const item = textsecure.storage.protocol.identityKeys[this.ourNumber];
|
const item = textsecure.storage.protocol.getIdentityRecord(
|
||||||
|
this.ourUuid || this.ourNumber
|
||||||
|
);
|
||||||
this.ourKey = item ? item.publicKey : null;
|
this.ourKey = item ? item.publicKey : null;
|
||||||
},
|
},
|
||||||
generateSecurityNumber() {
|
generateSecurityNumber() {
|
||||||
return new libsignal.FingerprintGenerator(5200)
|
return new libsignal.FingerprintGenerator(5200)
|
||||||
.createFor(this.ourNumber, this.ourKey, this.model.id, this.theirKey)
|
.createFor(
|
||||||
|
// TODO: we cannot use UUIDs for safety numbers yet
|
||||||
|
// this.ourUuid || this.ourNumber,
|
||||||
|
this.ourNumber,
|
||||||
|
this.ourKey,
|
||||||
|
// TODO: we cannot use UUIDs for safety numbers yet
|
||||||
|
// this.model.get('uuid') || this.model.get('e164'),
|
||||||
|
this.model.get('e164'),
|
||||||
|
this.theirKey
|
||||||
|
)
|
||||||
.then(securityNumber => {
|
.then(securityNumber => {
|
||||||
this.securityNumber = securityNumber;
|
this.securityNumber = securityNumber;
|
||||||
});
|
});
|
||||||
|
|
|
@ -24,14 +24,14 @@
|
||||||
this.pending = Promise.resolve();
|
this.pending = Promise.resolve();
|
||||||
}
|
}
|
||||||
|
|
||||||
function getNumber(numberId) {
|
function getIdentifier(id) {
|
||||||
if (!numberId || !numberId.length) {
|
if (!id || !id.length) {
|
||||||
return numberId;
|
return id;
|
||||||
}
|
}
|
||||||
|
|
||||||
const parts = numberId.split('.');
|
const parts = id.split('.');
|
||||||
if (!parts.length) {
|
if (!parts.length) {
|
||||||
return numberId;
|
return id;
|
||||||
}
|
}
|
||||||
|
|
||||||
return parts[0];
|
return parts[0];
|
||||||
|
@ -136,7 +136,7 @@
|
||||||
.then(clearSessionsAndPreKeys)
|
.then(clearSessionsAndPreKeys)
|
||||||
.then(generateKeys)
|
.then(generateKeys)
|
||||||
.then(keys => registerKeys(keys).then(() => confirmKeys(keys)))
|
.then(keys => registerKeys(keys).then(() => confirmKeys(keys)))
|
||||||
.then(() => registrationDone(number));
|
.then(() => registrationDone({ number }));
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
@ -212,7 +212,8 @@
|
||||||
provisionMessage.profileKey,
|
provisionMessage.profileKey,
|
||||||
deviceName,
|
deviceName,
|
||||||
provisionMessage.userAgent,
|
provisionMessage.userAgent,
|
||||||
provisionMessage.readReceipts
|
provisionMessage.readReceipts,
|
||||||
|
{ uuid: provisionMessage.uuid }
|
||||||
)
|
)
|
||||||
.then(clearSessionsAndPreKeys)
|
.then(clearSessionsAndPreKeys)
|
||||||
.then(generateKeys)
|
.then(generateKeys)
|
||||||
|
@ -221,9 +222,7 @@
|
||||||
confirmKeys(keys)
|
confirmKeys(keys)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
.then(() =>
|
.then(() => registrationDone(provisionMessage));
|
||||||
registrationDone(provisionMessage.number)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
@ -414,7 +413,8 @@
|
||||||
password = password.substring(0, password.length - 2);
|
password = password.substring(0, password.length - 2);
|
||||||
const registrationId = libsignal.KeyHelper.generateRegistrationId();
|
const registrationId = libsignal.KeyHelper.generateRegistrationId();
|
||||||
|
|
||||||
const previousNumber = getNumber(textsecure.storage.get('number_id'));
|
const previousNumber = getIdentifier(textsecure.storage.get('number_id'));
|
||||||
|
const previousUuid = getIdentifier(textsecure.storage.get('uuid_id'));
|
||||||
|
|
||||||
const encryptedDeviceName = await this.encryptDeviceName(
|
const encryptedDeviceName = await this.encryptDeviceName(
|
||||||
deviceName,
|
deviceName,
|
||||||
|
@ -437,10 +437,21 @@
|
||||||
{ accessKey }
|
{ accessKey }
|
||||||
);
|
);
|
||||||
|
|
||||||
if (previousNumber && previousNumber !== number) {
|
const numberChanged = previousNumber && previousNumber !== number;
|
||||||
window.log.warn(
|
const uuidChanged =
|
||||||
'New number is different from old number; deleting all previous data'
|
previousUuid && response.uuid && previousUuid !== response.uuid;
|
||||||
);
|
|
||||||
|
if (numberChanged || uuidChanged) {
|
||||||
|
if (numberChanged) {
|
||||||
|
window.log.warn(
|
||||||
|
'New number is different from old number; deleting all previous data'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (uuidChanged) {
|
||||||
|
window.log.warn(
|
||||||
|
'New uuid is different from old uuid; deleting all previous data'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await textsecure.storage.protocol.removeAllData();
|
await textsecure.storage.protocol.removeAllData();
|
||||||
|
@ -465,10 +476,29 @@
|
||||||
textsecure.storage.remove('read-receipts-setting'),
|
textsecure.storage.remove('read-receipts-setting'),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
// `setNumberAndDeviceId` and `setUuidAndDeviceId` need to be called
|
||||||
|
// before `saveIdentifyWithAttributes` since `saveIdentityWithAttributes`
|
||||||
|
// indirectly calls `ConversationController.getConverationId()` which
|
||||||
|
// initializes the conversation for the given number (our number) which
|
||||||
|
// calls out to the user storage API to get the stored UUID and number
|
||||||
|
// information.
|
||||||
|
await textsecure.storage.user.setNumberAndDeviceId(
|
||||||
|
number,
|
||||||
|
response.deviceId || 1,
|
||||||
|
deviceName
|
||||||
|
);
|
||||||
|
|
||||||
|
const setUuid = response.uuid;
|
||||||
|
if (setUuid) {
|
||||||
|
await textsecure.storage.user.setUuidAndDeviceId(
|
||||||
|
setUuid,
|
||||||
|
response.deviceId || 1
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// update our own identity key, which may have changed
|
// update our own identity key, which may have changed
|
||||||
// if we're relinking after a reinstall on the master device
|
// if we're relinking after a reinstall on the master device
|
||||||
await textsecure.storage.protocol.saveIdentityWithAttributes(number, {
|
await textsecure.storage.protocol.saveIdentityWithAttributes(number, {
|
||||||
id: number,
|
|
||||||
publicKey: identityKeyPair.pubKey,
|
publicKey: identityKeyPair.pubKey,
|
||||||
firstUse: true,
|
firstUse: true,
|
||||||
timestamp: Date.now(),
|
timestamp: Date.now(),
|
||||||
|
@ -491,12 +521,6 @@
|
||||||
Boolean(readReceipts)
|
Boolean(readReceipts)
|
||||||
);
|
);
|
||||||
|
|
||||||
await textsecure.storage.user.setNumberAndDeviceId(
|
|
||||||
number,
|
|
||||||
response.deviceId || 1,
|
|
||||||
deviceName
|
|
||||||
);
|
|
||||||
|
|
||||||
const regionCode = libphonenumber.util.getRegionCodeForNumber(number);
|
const regionCode = libphonenumber.util.getRegionCodeForNumber(number);
|
||||||
await textsecure.storage.put('regionCode', regionCode);
|
await textsecure.storage.put('regionCode', regionCode);
|
||||||
await textsecure.storage.protocol.hydrateCaches();
|
await textsecure.storage.protocol.hydrateCaches();
|
||||||
|
@ -579,11 +603,16 @@
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
async registrationDone(number) {
|
async registrationDone({ uuid, number }) {
|
||||||
window.log.info('registration done');
|
window.log.info('registration done');
|
||||||
|
|
||||||
// Ensure that we always have a conversation for ourself
|
// Ensure that we always have a conversation for ourself
|
||||||
await ConversationController.getOrCreateAndWait(number, 'private');
|
const conversation = await ConversationController.getOrCreateAndWait(
|
||||||
|
number || uuid,
|
||||||
|
'private'
|
||||||
|
);
|
||||||
|
conversation.updateE164(number);
|
||||||
|
conversation.updateUuid(uuid);
|
||||||
|
|
||||||
window.log.info('dispatching registration event');
|
window.log.info('dispatching registration event');
|
||||||
|
|
||||||
|
|
|
@ -36,6 +36,22 @@ ProtoParser.prototype = {
|
||||||
proto.profileKey = proto.profileKey.toArrayBuffer();
|
proto.profileKey = proto.profileKey.toArrayBuffer();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (proto.uuid) {
|
||||||
|
window.normalizeUuids(
|
||||||
|
proto,
|
||||||
|
['uuid'],
|
||||||
|
'ProtoParser::next (proto.uuid)'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (proto.members) {
|
||||||
|
window.normalizeUuids(
|
||||||
|
proto,
|
||||||
|
proto.members.map((_member, i) => `members.${i}.uuid`),
|
||||||
|
'ProtoParser::next (proto.members)'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return proto;
|
return proto;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
window.log.error(
|
window.log.error(
|
||||||
|
|
|
@ -36521,7 +36521,7 @@ Internal.SessionLock.queueJobForNumber = function queueJobForNumber(number, runJ
|
||||||
})();
|
})();
|
||||||
|
|
||||||
(function() {
|
(function() {
|
||||||
var VERSION = 0;
|
var VERSION = shortToArrayBuffer(0);
|
||||||
|
|
||||||
function iterateHash(data, key, count) {
|
function iterateHash(data, key, count) {
|
||||||
data = dcodeIO.ByteBuffer.concat([data, key]).toArrayBuffer();
|
data = dcodeIO.ByteBuffer.concat([data, key]).toArrayBuffer();
|
||||||
|
@ -36551,10 +36551,21 @@ Internal.SessionLock.queueJobForNumber = function queueJobForNumber(number, runJ
|
||||||
return s;
|
return s;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function decodeUuid(uuid) {
|
||||||
|
let i = 0;
|
||||||
|
let buf = new Uint8Array(16);
|
||||||
|
|
||||||
|
uuid.replace(/[0-9A-F]{2}/ig, oct => {
|
||||||
|
buf[i++] = parseInt(oct, 16);
|
||||||
|
});
|
||||||
|
|
||||||
|
return buf;
|
||||||
|
}
|
||||||
|
|
||||||
function getDisplayStringFor(identifier, key, iterations) {
|
function getDisplayStringFor(identifier, key, iterations) {
|
||||||
var bytes = dcodeIO.ByteBuffer.concat([
|
var isUuid = /^[0-9A-F]{8}-[0-9A-F]{4}-4[0-9A-F]{3}-[89AB][0-9A-F]{3}-[0-9A-F]{12}$/i.test(identifier);
|
||||||
shortToArrayBuffer(VERSION), key, identifier
|
var encodedIdentifier = isUuid ? decodeUuid(identifier) : identifier;
|
||||||
]).toArrayBuffer();
|
var bytes = dcodeIO.ByteBuffer.concat([VERSION, key, encodedIdentifier]).toArrayBuffer();
|
||||||
return iterateHash(bytes, key, iterations).then(function(output) {
|
return iterateHash(bytes, key, iterations).then(function(output) {
|
||||||
output = new Uint8Array(output);
|
output = new Uint8Array(output);
|
||||||
return getEncodedChunk(output, 0) +
|
return getEncodedChunk(output, 0) +
|
||||||
|
|
|
@ -14,13 +14,20 @@
|
||||||
|
|
||||||
const RETRY_TIMEOUT = 2 * 60 * 1000;
|
const RETRY_TIMEOUT = 2 * 60 * 1000;
|
||||||
|
|
||||||
function MessageReceiver(username, password, signalingKey, options = {}) {
|
function MessageReceiver(
|
||||||
|
oldUsername,
|
||||||
|
username,
|
||||||
|
password,
|
||||||
|
signalingKey,
|
||||||
|
options = {}
|
||||||
|
) {
|
||||||
this.count = 0;
|
this.count = 0;
|
||||||
|
|
||||||
this.signalingKey = signalingKey;
|
this.signalingKey = signalingKey;
|
||||||
this.username = username;
|
this.username = oldUsername;
|
||||||
|
this.uuid = username;
|
||||||
this.password = password;
|
this.password = password;
|
||||||
this.server = WebAPI.connect({ username, password });
|
this.server = WebAPI.connect({ username: username || oldUsername, password });
|
||||||
|
|
||||||
if (!options.serverTrustRoot) {
|
if (!options.serverTrustRoot) {
|
||||||
throw new Error('Server trust root is required!');
|
throw new Error('Server trust root is required!');
|
||||||
|
@ -29,9 +36,12 @@ function MessageReceiver(username, password, signalingKey, options = {}) {
|
||||||
options.serverTrustRoot
|
options.serverTrustRoot
|
||||||
);
|
);
|
||||||
|
|
||||||
const address = libsignal.SignalProtocolAddress.fromString(username);
|
this.number_id = oldUsername
|
||||||
this.number = address.getName();
|
? textsecure.utils.unencodeNumber(oldUsername)[0]
|
||||||
this.deviceId = address.getDeviceId();
|
: null;
|
||||||
|
this.uuid_id = username ? textsecure.utils.unencodeNumber(username)[0] : null;
|
||||||
|
// eslint-disable-next-line prefer-destructuring
|
||||||
|
this.deviceId = textsecure.utils.unencodeNumber(username || oldUsername)[1];
|
||||||
|
|
||||||
this.incomingQueue = new window.PQueue({ concurrency: 1 });
|
this.incomingQueue = new window.PQueue({ concurrency: 1 });
|
||||||
this.pendingQueue = new window.PQueue({ concurrency: 1 });
|
this.pendingQueue = new window.PQueue({ concurrency: 1 });
|
||||||
|
@ -176,7 +186,7 @@ MessageReceiver.prototype.extend({
|
||||||
}
|
}
|
||||||
// possible 403 or network issue. Make an request to confirm
|
// possible 403 or network issue. Make an request to confirm
|
||||||
return this.server
|
return this.server
|
||||||
.getDevices(this.number)
|
.getDevices(this.number_id || this.uuid_id)
|
||||||
.then(this.connect.bind(this)) // No HTTP error? Reconnect
|
.then(this.connect.bind(this)) // No HTTP error? Reconnect
|
||||||
.catch(e => {
|
.catch(e => {
|
||||||
const event = new Event('error');
|
const event = new Event('error');
|
||||||
|
@ -213,6 +223,11 @@ MessageReceiver.prototype.extend({
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const envelope = textsecure.protobuf.Envelope.decode(plaintext);
|
const envelope = textsecure.protobuf.Envelope.decode(plaintext);
|
||||||
|
window.normalizeUuids(
|
||||||
|
envelope,
|
||||||
|
['sourceUuid'],
|
||||||
|
'message_receiver::handleRequest::job'
|
||||||
|
);
|
||||||
// After this point, decoding errors are not the server's
|
// After this point, decoding errors are not the server's
|
||||||
// fault, and we should handle them gracefully and tell the
|
// fault, and we should handle them gracefully and tell the
|
||||||
// user they received an invalid message
|
// user they received an invalid message
|
||||||
|
@ -222,6 +237,11 @@ MessageReceiver.prototype.extend({
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.isUuidBlocked(envelope.sourceUuid)) {
|
||||||
|
request.respond(200, 'OK');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
envelope.id = envelope.serverGuid || window.getGuid();
|
envelope.id = envelope.serverGuid || window.getGuid();
|
||||||
envelope.serverTimestamp = envelope.serverTimestamp
|
envelope.serverTimestamp = envelope.serverTimestamp
|
||||||
? envelope.serverTimestamp.toNumber()
|
? envelope.serverTimestamp.toNumber()
|
||||||
|
@ -333,6 +353,7 @@ MessageReceiver.prototype.extend({
|
||||||
const envelope = textsecure.protobuf.Envelope.decode(envelopePlaintext);
|
const envelope = textsecure.protobuf.Envelope.decode(envelopePlaintext);
|
||||||
envelope.id = envelope.serverGuid || item.id;
|
envelope.id = envelope.serverGuid || item.id;
|
||||||
envelope.source = envelope.source || item.source;
|
envelope.source = envelope.source || item.source;
|
||||||
|
envelope.sourceUuid = envelope.sourceUuid || item.sourceUuid;
|
||||||
envelope.sourceDevice = envelope.sourceDevice || item.sourceDevice;
|
envelope.sourceDevice = envelope.sourceDevice || item.sourceDevice;
|
||||||
envelope.serverTimestamp =
|
envelope.serverTimestamp =
|
||||||
envelope.serverTimestamp || item.serverTimestamp;
|
envelope.serverTimestamp || item.serverTimestamp;
|
||||||
|
@ -378,8 +399,8 @@ MessageReceiver.prototype.extend({
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
getEnvelopeId(envelope) {
|
getEnvelopeId(envelope) {
|
||||||
if (envelope.source) {
|
if (envelope.sourceUuid || envelope.source) {
|
||||||
return `${envelope.source}.${
|
return `${envelope.sourceUuid || envelope.source}.${
|
||||||
envelope.sourceDevice
|
envelope.sourceDevice
|
||||||
} ${envelope.timestamp.toNumber()} (${envelope.id})`;
|
} ${envelope.timestamp.toNumber()} (${envelope.id})`;
|
||||||
}
|
}
|
||||||
|
@ -485,6 +506,7 @@ MessageReceiver.prototype.extend({
|
||||||
const { id } = envelope;
|
const { id } = envelope;
|
||||||
const data = {
|
const data = {
|
||||||
source: envelope.source,
|
source: envelope.source,
|
||||||
|
sourceUuid: envelope.sourceUuid,
|
||||||
sourceDevice: envelope.sourceDevice,
|
sourceDevice: envelope.sourceDevice,
|
||||||
serverTimestamp: envelope.serverTimestamp,
|
serverTimestamp: envelope.serverTimestamp,
|
||||||
decrypted: MessageReceiver.arrayBufferToStringBase64(plaintext),
|
decrypted: MessageReceiver.arrayBufferToStringBase64(plaintext),
|
||||||
|
@ -586,6 +608,7 @@ MessageReceiver.prototype.extend({
|
||||||
ev.deliveryReceipt = {
|
ev.deliveryReceipt = {
|
||||||
timestamp: envelope.timestamp.toNumber(),
|
timestamp: envelope.timestamp.toNumber(),
|
||||||
source: envelope.source,
|
source: envelope.source,
|
||||||
|
sourceUuid: envelope.sourceUuid,
|
||||||
sourceDevice: envelope.sourceDevice,
|
sourceDevice: envelope.sourceDevice,
|
||||||
};
|
};
|
||||||
this.dispatchAndWait(ev).then(resolve, reject);
|
this.dispatchAndWait(ev).then(resolve, reject);
|
||||||
|
@ -613,16 +636,21 @@ MessageReceiver.prototype.extend({
|
||||||
|
|
||||||
let promise;
|
let promise;
|
||||||
const address = new libsignal.SignalProtocolAddress(
|
const address = new libsignal.SignalProtocolAddress(
|
||||||
envelope.source,
|
// Using source as opposed to sourceUuid allows us to get the existing
|
||||||
|
// session if we haven't yet harvested the incoming uuid
|
||||||
|
envelope.source || envelope.sourceUuid,
|
||||||
envelope.sourceDevice
|
envelope.sourceDevice
|
||||||
);
|
);
|
||||||
|
|
||||||
const ourNumber = textsecure.storage.user.getNumber();
|
const ourNumber = textsecure.storage.user.getNumber();
|
||||||
const number = address.toString().split('.')[0];
|
const ourUuid = textsecure.storage.user.getUuid();
|
||||||
const options = {};
|
const options = {};
|
||||||
|
|
||||||
// No limit on message keys if we're communicating with our other devices
|
// No limit on message keys if we're communicating with our other devices
|
||||||
if (ourNumber === number) {
|
if (
|
||||||
|
(envelope.source && ourNumber && ourNumber === envelope.source) ||
|
||||||
|
(envelope.sourceUuid && ourUuid && ourUuid === envelope.sourceUuid)
|
||||||
|
) {
|
||||||
options.messageKeysLimit = false;
|
options.messageKeysLimit = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -637,6 +665,7 @@ MessageReceiver.prototype.extend({
|
||||||
|
|
||||||
const me = {
|
const me = {
|
||||||
number: ourNumber,
|
number: ourNumber,
|
||||||
|
uuid: ourUuid,
|
||||||
deviceId: parseInt(textsecure.storage.user.getDeviceId(), 10),
|
deviceId: parseInt(textsecure.storage.user.getDeviceId(), 10),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -666,7 +695,7 @@ MessageReceiver.prototype.extend({
|
||||||
)
|
)
|
||||||
.then(
|
.then(
|
||||||
result => {
|
result => {
|
||||||
const { isMe, sender, content } = result;
|
const { isMe, sender, senderUuid, content } = result;
|
||||||
|
|
||||||
// We need to drop incoming messages from ourself since server can't
|
// We need to drop incoming messages from ourself since server can't
|
||||||
// do it for us
|
// do it for us
|
||||||
|
@ -674,7 +703,10 @@ MessageReceiver.prototype.extend({
|
||||||
return { isMe: true };
|
return { isMe: true };
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.isBlocked(sender.getName())) {
|
if (
|
||||||
|
(sender && this.isBlocked(sender.getName())) ||
|
||||||
|
(senderUuid && this.isUuidBlocked(senderUuid.getName()))
|
||||||
|
) {
|
||||||
window.log.info(
|
window.log.info(
|
||||||
'Dropping blocked message after sealed sender decryption'
|
'Dropping blocked message after sealed sender decryption'
|
||||||
);
|
);
|
||||||
|
@ -685,25 +717,41 @@ MessageReceiver.prototype.extend({
|
||||||
// to make the rest of the app work properly.
|
// to make the rest of the app work properly.
|
||||||
|
|
||||||
const originalSource = envelope.source;
|
const originalSource = envelope.source;
|
||||||
|
const originalSourceUuid = envelope.sourceUuid;
|
||||||
|
|
||||||
// eslint-disable-next-line no-param-reassign
|
// eslint-disable-next-line no-param-reassign
|
||||||
envelope.source = sender.getName();
|
envelope.source = sender && sender.getName();
|
||||||
// eslint-disable-next-line no-param-reassign
|
// eslint-disable-next-line no-param-reassign
|
||||||
envelope.sourceDevice = sender.getDeviceId();
|
envelope.sourceUuid = senderUuid && senderUuid.getName();
|
||||||
|
window.normalizeUuids(
|
||||||
|
envelope,
|
||||||
|
['sourceUuid'],
|
||||||
|
'message_receiver::decrypt::UNIDENTIFIED_SENDER'
|
||||||
|
);
|
||||||
// eslint-disable-next-line no-param-reassign
|
// eslint-disable-next-line no-param-reassign
|
||||||
envelope.unidentifiedDeliveryReceived = !originalSource;
|
envelope.sourceDevice =
|
||||||
|
(sender && sender.getDeviceId()) ||
|
||||||
|
(senderUuid && senderUuid.getDeviceId());
|
||||||
|
// eslint-disable-next-line no-param-reassign
|
||||||
|
envelope.unidentifiedDeliveryReceived = !(
|
||||||
|
originalSource || originalSourceUuid
|
||||||
|
);
|
||||||
|
|
||||||
// Return just the content because that matches the signature of the other
|
// Return just the content because that matches the signature of the other
|
||||||
// decrypt methods used above.
|
// decrypt methods used above.
|
||||||
return this.unpad(content);
|
return this.unpad(content);
|
||||||
},
|
},
|
||||||
error => {
|
error => {
|
||||||
const { sender } = error || {};
|
const { sender, senderUuid } = error || {};
|
||||||
|
|
||||||
if (sender) {
|
if (sender || senderUuid) {
|
||||||
const originalSource = envelope.source;
|
const originalSource = envelope.source;
|
||||||
|
const originalSourceUuid = envelope.sourceUuid;
|
||||||
|
|
||||||
if (this.isBlocked(sender.getName())) {
|
if (
|
||||||
|
(sender && this.isBlocked(sender.getName())) ||
|
||||||
|
(senderUuid && this.isUuidBlocked(senderUuid.getName()))
|
||||||
|
) {
|
||||||
window.log.info(
|
window.log.info(
|
||||||
'Dropping blocked message with error after sealed sender decryption'
|
'Dropping blocked message with error after sealed sender decryption'
|
||||||
);
|
);
|
||||||
|
@ -711,11 +759,23 @@ MessageReceiver.prototype.extend({
|
||||||
}
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line no-param-reassign
|
// eslint-disable-next-line no-param-reassign
|
||||||
envelope.source = sender.getName();
|
envelope.source = sender && sender.getName();
|
||||||
// eslint-disable-next-line no-param-reassign
|
// eslint-disable-next-line no-param-reassign
|
||||||
envelope.sourceDevice = sender.getDeviceId();
|
envelope.sourceUuid =
|
||||||
|
senderUuid && senderUuid.getName().toLowerCase();
|
||||||
|
window.normalizeUuids(
|
||||||
|
envelope,
|
||||||
|
['sourceUuid'],
|
||||||
|
'message_receiver::decrypt::UNIDENTIFIED_SENDER::error'
|
||||||
|
);
|
||||||
// eslint-disable-next-line no-param-reassign
|
// eslint-disable-next-line no-param-reassign
|
||||||
envelope.unidentifiedDeliveryReceived = !originalSource;
|
envelope.sourceDevice =
|
||||||
|
(sender && sender.getDeviceId()) ||
|
||||||
|
(senderUuid && senderUuid.getDeviceId());
|
||||||
|
// eslint-disable-next-line no-param-reassign
|
||||||
|
envelope.unidentifiedDeliveryReceived = !(
|
||||||
|
originalSource || originalSourceUuid
|
||||||
|
);
|
||||||
|
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
|
@ -803,7 +863,12 @@ MessageReceiver.prototype.extend({
|
||||||
this.processDecrypted(envelope, msg).then(message => {
|
this.processDecrypted(envelope, msg).then(message => {
|
||||||
const groupId = message.group && message.group.id;
|
const groupId = message.group && message.group.id;
|
||||||
const isBlocked = this.isGroupBlocked(groupId);
|
const isBlocked = this.isGroupBlocked(groupId);
|
||||||
const isMe = envelope.source === textsecure.storage.user.getNumber();
|
const { source, sourceUuid } = envelope;
|
||||||
|
const ourE164 = textsecure.storage.user.getNumber();
|
||||||
|
const ourUuid = textsecure.storage.user.getUuid();
|
||||||
|
const isMe =
|
||||||
|
(source && ourE164 && source === ourE164) ||
|
||||||
|
(sourceUuid && ourUuid && sourceUuid === ourUuid);
|
||||||
const isLeavingGroup = Boolean(
|
const isLeavingGroup = Boolean(
|
||||||
message.group &&
|
message.group &&
|
||||||
message.group.type === textsecure.protobuf.GroupContext.Type.QUIT
|
message.group.type === textsecure.protobuf.GroupContext.Type.QUIT
|
||||||
|
@ -840,13 +905,18 @@ MessageReceiver.prototype.extend({
|
||||||
let p = Promise.resolve();
|
let p = Promise.resolve();
|
||||||
// eslint-disable-next-line no-bitwise
|
// eslint-disable-next-line no-bitwise
|
||||||
if (msg.flags & textsecure.protobuf.DataMessage.Flags.END_SESSION) {
|
if (msg.flags & textsecure.protobuf.DataMessage.Flags.END_SESSION) {
|
||||||
p = this.handleEndSession(envelope.source);
|
p = this.handleEndSession(envelope.source || envelope.sourceUuid);
|
||||||
}
|
}
|
||||||
return p.then(() =>
|
return p.then(() =>
|
||||||
this.processDecrypted(envelope, msg).then(message => {
|
this.processDecrypted(envelope, msg).then(message => {
|
||||||
const groupId = message.group && message.group.id;
|
const groupId = message.group && message.group.id;
|
||||||
const isBlocked = this.isGroupBlocked(groupId);
|
const isBlocked = this.isGroupBlocked(groupId);
|
||||||
const isMe = envelope.source === textsecure.storage.user.getNumber();
|
const { source, sourceUuid } = envelope;
|
||||||
|
const ourE164 = textsecure.storage.user.getNumber();
|
||||||
|
const ourUuid = textsecure.storage.user.getUuid();
|
||||||
|
const isMe =
|
||||||
|
(source && ourE164 && source === ourE164) ||
|
||||||
|
(sourceUuid && ourUuid && sourceUuid === ourUuid);
|
||||||
const isLeavingGroup = Boolean(
|
const isLeavingGroup = Boolean(
|
||||||
message.group &&
|
message.group &&
|
||||||
message.group.type === textsecure.protobuf.GroupContext.Type.QUIT
|
message.group.type === textsecure.protobuf.GroupContext.Type.QUIT
|
||||||
|
@ -865,6 +935,7 @@ MessageReceiver.prototype.extend({
|
||||||
ev.confirm = this.removeFromCache.bind(this, envelope);
|
ev.confirm = this.removeFromCache.bind(this, envelope);
|
||||||
ev.data = {
|
ev.data = {
|
||||||
source: envelope.source,
|
source: envelope.source,
|
||||||
|
sourceUuid: envelope.sourceUuid,
|
||||||
sourceDevice: envelope.sourceDevice,
|
sourceDevice: envelope.sourceDevice,
|
||||||
timestamp: envelope.timestamp.toNumber(),
|
timestamp: envelope.timestamp.toNumber(),
|
||||||
receivedAt: envelope.receivedAt,
|
receivedAt: envelope.receivedAt,
|
||||||
|
@ -930,6 +1001,7 @@ MessageReceiver.prototype.extend({
|
||||||
ev.deliveryReceipt = {
|
ev.deliveryReceipt = {
|
||||||
timestamp: receiptMessage.timestamp[i].toNumber(),
|
timestamp: receiptMessage.timestamp[i].toNumber(),
|
||||||
source: envelope.source,
|
source: envelope.source,
|
||||||
|
sourceUuid: envelope.sourceUuid,
|
||||||
sourceDevice: envelope.sourceDevice,
|
sourceDevice: envelope.sourceDevice,
|
||||||
};
|
};
|
||||||
results.push(this.dispatchAndWait(ev));
|
results.push(this.dispatchAndWait(ev));
|
||||||
|
@ -943,7 +1015,7 @@ MessageReceiver.prototype.extend({
|
||||||
ev.timestamp = envelope.timestamp.toNumber();
|
ev.timestamp = envelope.timestamp.toNumber();
|
||||||
ev.read = {
|
ev.read = {
|
||||||
timestamp: receiptMessage.timestamp[i].toNumber(),
|
timestamp: receiptMessage.timestamp[i].toNumber(),
|
||||||
reader: envelope.source,
|
reader: envelope.source || envelope.sourceUuid,
|
||||||
};
|
};
|
||||||
results.push(this.dispatchAndWait(ev));
|
results.push(this.dispatchAndWait(ev));
|
||||||
}
|
}
|
||||||
|
@ -968,6 +1040,7 @@ MessageReceiver.prototype.extend({
|
||||||
}
|
}
|
||||||
|
|
||||||
ev.sender = envelope.source;
|
ev.sender = envelope.source;
|
||||||
|
ev.senderUuid = envelope.sourceUuid;
|
||||||
ev.senderDevice = envelope.sourceDevice;
|
ev.senderDevice = envelope.sourceDevice;
|
||||||
ev.typing = {
|
ev.typing = {
|
||||||
typingMessage,
|
typingMessage,
|
||||||
|
@ -992,7 +1065,24 @@ MessageReceiver.prototype.extend({
|
||||||
this.removeFromCache(envelope);
|
this.removeFromCache(envelope);
|
||||||
},
|
},
|
||||||
handleSyncMessage(envelope, syncMessage) {
|
handleSyncMessage(envelope, syncMessage) {
|
||||||
if (envelope.source !== this.number) {
|
const unidentified = syncMessage.sent
|
||||||
|
? syncMessage.sent.unidentifiedStatus || []
|
||||||
|
: [];
|
||||||
|
window.normalizeUuids(
|
||||||
|
syncMessage,
|
||||||
|
[
|
||||||
|
'sent.destinationUuid',
|
||||||
|
...unidentified.map(
|
||||||
|
(_el, i) => `sent.unidentifiedStatus.${i}.destinationUuid`
|
||||||
|
),
|
||||||
|
],
|
||||||
|
'message_receiver::handleSyncMessage'
|
||||||
|
);
|
||||||
|
const fromSelfSource =
|
||||||
|
envelope.source && envelope.source === this.number_id;
|
||||||
|
const fromSelfSourceUuid =
|
||||||
|
envelope.sourceUuid && envelope.sourceUuid === this.uuid_id;
|
||||||
|
if (!fromSelfSource && !fromSelfSourceUuid) {
|
||||||
throw new Error('Received sync message from another number');
|
throw new Error('Received sync message from another number');
|
||||||
}
|
}
|
||||||
// eslint-disable-next-line eqeqeq
|
// eslint-disable-next-line eqeqeq
|
||||||
|
@ -1057,8 +1147,15 @@ MessageReceiver.prototype.extend({
|
||||||
const ev = new Event('viewSync');
|
const ev = new Event('viewSync');
|
||||||
ev.confirm = this.removeFromCache.bind(this, envelope);
|
ev.confirm = this.removeFromCache.bind(this, envelope);
|
||||||
ev.source = sync.sender;
|
ev.source = sync.sender;
|
||||||
|
ev.sourceUuid = sync.senderUuid;
|
||||||
ev.timestamp = sync.timestamp ? sync.timestamp.toNumber() : null;
|
ev.timestamp = sync.timestamp ? sync.timestamp.toNumber() : null;
|
||||||
|
|
||||||
|
window.normalizeUuids(
|
||||||
|
ev,
|
||||||
|
['sourceUuid'],
|
||||||
|
'message_receiver::handleViewOnceOpen'
|
||||||
|
);
|
||||||
|
|
||||||
return this.dispatchAndWait(ev);
|
return this.dispatchAndWait(ev);
|
||||||
},
|
},
|
||||||
handleStickerPackOperation(envelope, operations) {
|
handleStickerPackOperation(envelope, operations) {
|
||||||
|
@ -1080,8 +1177,14 @@ MessageReceiver.prototype.extend({
|
||||||
ev.verified = {
|
ev.verified = {
|
||||||
state: verified.state,
|
state: verified.state,
|
||||||
destination: verified.destination,
|
destination: verified.destination,
|
||||||
|
destinationUuid: verified.destinationUuid,
|
||||||
identityKey: verified.identityKey.toArrayBuffer(),
|
identityKey: verified.identityKey.toArrayBuffer(),
|
||||||
};
|
};
|
||||||
|
window.normalizeUuids(
|
||||||
|
ev,
|
||||||
|
['verified.destinationUuid'],
|
||||||
|
'message_receiver::handleVerified'
|
||||||
|
);
|
||||||
return this.dispatchAndWait(ev);
|
return this.dispatchAndWait(ev);
|
||||||
},
|
},
|
||||||
handleRead(envelope, read) {
|
handleRead(envelope, read) {
|
||||||
|
@ -1093,7 +1196,13 @@ MessageReceiver.prototype.extend({
|
||||||
ev.read = {
|
ev.read = {
|
||||||
timestamp: read[i].timestamp.toNumber(),
|
timestamp: read[i].timestamp.toNumber(),
|
||||||
sender: read[i].sender,
|
sender: read[i].sender,
|
||||||
|
senderUuid: read[i].senderUuid,
|
||||||
};
|
};
|
||||||
|
window.normalizeUuids(
|
||||||
|
ev,
|
||||||
|
['read.senderUuid'],
|
||||||
|
'message_receiver::handleRead'
|
||||||
|
);
|
||||||
results.push(this.dispatchAndWait(ev));
|
results.push(this.dispatchAndWait(ev));
|
||||||
}
|
}
|
||||||
return Promise.all(results);
|
return Promise.all(results);
|
||||||
|
@ -1158,6 +1267,15 @@ MessageReceiver.prototype.extend({
|
||||||
handleBlocked(envelope, blocked) {
|
handleBlocked(envelope, blocked) {
|
||||||
window.log.info('Setting these numbers as blocked:', blocked.numbers);
|
window.log.info('Setting these numbers as blocked:', blocked.numbers);
|
||||||
textsecure.storage.put('blocked', blocked.numbers);
|
textsecure.storage.put('blocked', blocked.numbers);
|
||||||
|
if (blocked.uuids) {
|
||||||
|
window.normalizeUuids(
|
||||||
|
blocked,
|
||||||
|
blocked.uuids.map((_uuid, i) => `uuids.${i}`),
|
||||||
|
'message_receiver::handleBlocked'
|
||||||
|
);
|
||||||
|
window.log.info('Setting these uuids as blocked:', blocked.uuids);
|
||||||
|
textsecure.storage.put('blocked-uuids', blocked.uuids);
|
||||||
|
}
|
||||||
|
|
||||||
const groupIds = _.map(blocked.groupIds, groupId => groupId.toBinary());
|
const groupIds = _.map(blocked.groupIds, groupId => groupId.toBinary());
|
||||||
window.log.info(
|
window.log.info(
|
||||||
|
@ -1169,10 +1287,13 @@ MessageReceiver.prototype.extend({
|
||||||
return this.removeFromCache(envelope);
|
return this.removeFromCache(envelope);
|
||||||
},
|
},
|
||||||
isBlocked(number) {
|
isBlocked(number) {
|
||||||
return textsecure.storage.get('blocked', []).indexOf(number) >= 0;
|
return textsecure.storage.get('blocked', []).includes(number);
|
||||||
|
},
|
||||||
|
isUuidBlocked(uuid) {
|
||||||
|
return textsecure.storage.get('blocked-uuids', []).includes(uuid);
|
||||||
},
|
},
|
||||||
isGroupBlocked(groupId) {
|
isGroupBlocked(groupId) {
|
||||||
return textsecure.storage.get('blocked-groups', []).indexOf(groupId) >= 0;
|
return textsecure.storage.get('blocked-groups', []).includes(groupId);
|
||||||
},
|
},
|
||||||
cleanAttachment(attachment) {
|
cleanAttachment(attachment) {
|
||||||
return {
|
return {
|
||||||
|
@ -1213,13 +1334,18 @@ MessageReceiver.prototype.extend({
|
||||||
const cleaned = this.cleanAttachment(attachment);
|
const cleaned = this.cleanAttachment(attachment);
|
||||||
return this.downloadAttachment(cleaned);
|
return this.downloadAttachment(cleaned);
|
||||||
},
|
},
|
||||||
async handleEndSession(number) {
|
async handleEndSession(identifier) {
|
||||||
window.log.info('got end session');
|
window.log.info('got end session');
|
||||||
const deviceIds = await textsecure.storage.protocol.getDeviceIds(number);
|
const deviceIds = await textsecure.storage.protocol.getDeviceIds(
|
||||||
|
identifier
|
||||||
|
);
|
||||||
|
|
||||||
return Promise.all(
|
return Promise.all(
|
||||||
deviceIds.map(deviceId => {
|
deviceIds.map(deviceId => {
|
||||||
const address = new libsignal.SignalProtocolAddress(number, deviceId);
|
const address = new libsignal.SignalProtocolAddress(
|
||||||
|
identifier,
|
||||||
|
deviceId
|
||||||
|
);
|
||||||
const sessionCipher = new libsignal.SessionCipher(
|
const sessionCipher = new libsignal.SessionCipher(
|
||||||
textsecure.storage.protocol,
|
textsecure.storage.protocol,
|
||||||
address
|
address
|
||||||
|
@ -1274,8 +1400,6 @@ MessageReceiver.prototype.extend({
|
||||||
throw new Error('Unknown flags in message');
|
throw new Error('Unknown flags in message');
|
||||||
}
|
}
|
||||||
|
|
||||||
const promises = [];
|
|
||||||
|
|
||||||
if (decrypted.group !== null) {
|
if (decrypted.group !== null) {
|
||||||
decrypted.group.id = decrypted.group.id.toBinary();
|
decrypted.group.id = decrypted.group.id.toBinary();
|
||||||
|
|
||||||
|
@ -1290,6 +1414,7 @@ MessageReceiver.prototype.extend({
|
||||||
break;
|
break;
|
||||||
case textsecure.protobuf.GroupContext.Type.DELIVER:
|
case textsecure.protobuf.GroupContext.Type.DELIVER:
|
||||||
decrypted.group.name = null;
|
decrypted.group.name = null;
|
||||||
|
decrypted.group.membersE164 = [];
|
||||||
decrypted.group.members = [];
|
decrypted.group.members = [];
|
||||||
decrypted.group.avatar = null;
|
decrypted.group.avatar = null;
|
||||||
break;
|
break;
|
||||||
|
@ -1383,7 +1508,19 @@ MessageReceiver.prototype.extend({
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return Promise.all(promises).then(() => decrypted);
|
const groupMembers = decrypted.group ? decrypted.group.members || [] : [];
|
||||||
|
|
||||||
|
window.normalizeUuids(
|
||||||
|
decrypted,
|
||||||
|
[
|
||||||
|
'quote.authorUuid',
|
||||||
|
'reaction.targetAuthorUuid',
|
||||||
|
...groupMembers.map((_member, i) => `group.members.${i}.uuid`),
|
||||||
|
],
|
||||||
|
'message_receiver::processDecrypted'
|
||||||
|
);
|
||||||
|
|
||||||
|
return Promise.resolve(decrypted);
|
||||||
/* eslint-enable no-bitwise, no-param-reassign */
|
/* eslint-enable no-bitwise, no-param-reassign */
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
@ -1392,12 +1529,14 @@ window.textsecure = window.textsecure || {};
|
||||||
|
|
||||||
textsecure.MessageReceiver = function MessageReceiverWrapper(
|
textsecure.MessageReceiver = function MessageReceiverWrapper(
|
||||||
username,
|
username,
|
||||||
|
uuid,
|
||||||
password,
|
password,
|
||||||
signalingKey,
|
signalingKey,
|
||||||
options
|
options
|
||||||
) {
|
) {
|
||||||
const messageReceiver = new MessageReceiver(
|
const messageReceiver = new MessageReceiver(
|
||||||
username,
|
username,
|
||||||
|
uuid,
|
||||||
password,
|
password,
|
||||||
signalingKey,
|
signalingKey,
|
||||||
options
|
options
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
function OutgoingMessage(
|
function OutgoingMessage(
|
||||||
server,
|
server,
|
||||||
timestamp,
|
timestamp,
|
||||||
numbers,
|
identifiers,
|
||||||
message,
|
message,
|
||||||
silent,
|
silent,
|
||||||
callback,
|
callback,
|
||||||
|
@ -19,41 +19,43 @@ function OutgoingMessage(
|
||||||
}
|
}
|
||||||
this.server = server;
|
this.server = server;
|
||||||
this.timestamp = timestamp;
|
this.timestamp = timestamp;
|
||||||
this.numbers = numbers;
|
this.identifiers = identifiers;
|
||||||
this.message = message; // ContentMessage proto
|
this.message = message; // ContentMessage proto
|
||||||
this.callback = callback;
|
this.callback = callback;
|
||||||
this.silent = silent;
|
this.silent = silent;
|
||||||
|
|
||||||
this.numbersCompleted = 0;
|
this.identifiersCompleted = 0;
|
||||||
this.errors = [];
|
this.errors = [];
|
||||||
this.successfulNumbers = [];
|
this.successfulIdentifiers = [];
|
||||||
this.failoverNumbers = [];
|
this.failoverIdentifiers = [];
|
||||||
this.unidentifiedDeliveries = [];
|
this.unidentifiedDeliveries = [];
|
||||||
|
|
||||||
const { numberInfo, senderCertificate, online } = options || {};
|
const { sendMetadata, senderCertificate, senderCertificateWithUuid, online } =
|
||||||
this.numberInfo = numberInfo;
|
options || {};
|
||||||
|
this.sendMetadata = sendMetadata;
|
||||||
this.senderCertificate = senderCertificate;
|
this.senderCertificate = senderCertificate;
|
||||||
|
this.senderCertificateWithUuid = senderCertificateWithUuid;
|
||||||
this.online = online;
|
this.online = online;
|
||||||
}
|
}
|
||||||
|
|
||||||
OutgoingMessage.prototype = {
|
OutgoingMessage.prototype = {
|
||||||
constructor: OutgoingMessage,
|
constructor: OutgoingMessage,
|
||||||
numberCompleted() {
|
numberCompleted() {
|
||||||
this.numbersCompleted += 1;
|
this.identifiersCompleted += 1;
|
||||||
if (this.numbersCompleted >= this.numbers.length) {
|
if (this.identifiersCompleted >= this.identifiers.length) {
|
||||||
this.callback({
|
this.callback({
|
||||||
successfulNumbers: this.successfulNumbers,
|
successfulIdentifiers: this.successfulIdentifiers,
|
||||||
failoverNumbers: this.failoverNumbers,
|
failoverIdentifiers: this.failoverIdentifiers,
|
||||||
errors: this.errors,
|
errors: this.errors,
|
||||||
unidentifiedDeliveries: this.unidentifiedDeliveries,
|
unidentifiedDeliveries: this.unidentifiedDeliveries,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
registerError(number, reason, error) {
|
registerError(identifier, reason, error) {
|
||||||
if (!error || (error.name === 'HTTPError' && error.code !== 404)) {
|
if (!error || (error.name === 'HTTPError' && error.code !== 404)) {
|
||||||
// eslint-disable-next-line no-param-reassign
|
// eslint-disable-next-line no-param-reassign
|
||||||
error = new textsecure.OutgoingMessageError(
|
error = new textsecure.OutgoingMessageError(
|
||||||
number,
|
identifier,
|
||||||
this.message.toArrayBuffer(),
|
this.message.toArrayBuffer(),
|
||||||
this.timestamp,
|
this.timestamp,
|
||||||
error
|
error
|
||||||
|
@ -61,27 +63,27 @@ OutgoingMessage.prototype = {
|
||||||
}
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line no-param-reassign
|
// eslint-disable-next-line no-param-reassign
|
||||||
error.number = number;
|
error.number = identifier;
|
||||||
// eslint-disable-next-line no-param-reassign
|
// eslint-disable-next-line no-param-reassign
|
||||||
error.reason = reason;
|
error.reason = reason;
|
||||||
this.errors[this.errors.length] = error;
|
this.errors[this.errors.length] = error;
|
||||||
this.numberCompleted();
|
this.numberCompleted();
|
||||||
},
|
},
|
||||||
reloadDevicesAndSend(number, recurse) {
|
reloadDevicesAndSend(identifier, recurse) {
|
||||||
return () =>
|
return () =>
|
||||||
textsecure.storage.protocol.getDeviceIds(number).then(deviceIds => {
|
textsecure.storage.protocol.getDeviceIds(identifier).then(deviceIds => {
|
||||||
if (deviceIds.length === 0) {
|
if (deviceIds.length === 0) {
|
||||||
return this.registerError(
|
return this.registerError(
|
||||||
number,
|
identifier,
|
||||||
'Got empty device list when loading device keys',
|
'Got empty device list when loading device keys',
|
||||||
null
|
null
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return this.doSendMessage(number, deviceIds, recurse);
|
return this.doSendMessage(identifier, deviceIds, recurse);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
getKeysForNumber(number, updateDevices) {
|
getKeysForIdentifier(identifier, updateDevices) {
|
||||||
const handleResult = response =>
|
const handleResult = response =>
|
||||||
Promise.all(
|
Promise.all(
|
||||||
response.devices.map(device => {
|
response.devices.map(device => {
|
||||||
|
@ -92,7 +94,7 @@ OutgoingMessage.prototype = {
|
||||||
updateDevices.indexOf(device.deviceId) > -1
|
updateDevices.indexOf(device.deviceId) > -1
|
||||||
) {
|
) {
|
||||||
const address = new libsignal.SignalProtocolAddress(
|
const address = new libsignal.SignalProtocolAddress(
|
||||||
number,
|
identifier,
|
||||||
device.deviceId
|
device.deviceId
|
||||||
);
|
);
|
||||||
const builder = new libsignal.SessionBuilder(
|
const builder = new libsignal.SessionBuilder(
|
||||||
|
@ -119,27 +121,30 @@ OutgoingMessage.prototype = {
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
const { numberInfo } = this;
|
const { sendMetadata } = this;
|
||||||
const info = numberInfo && numberInfo[number] ? numberInfo[number] : {};
|
const info =
|
||||||
|
sendMetadata && sendMetadata[identifier] ? sendMetadata[identifier] : {};
|
||||||
const { accessKey } = info || {};
|
const { accessKey } = info || {};
|
||||||
|
|
||||||
if (updateDevices === undefined) {
|
if (updateDevices === undefined) {
|
||||||
if (accessKey) {
|
if (accessKey) {
|
||||||
return this.server
|
return this.server
|
||||||
.getKeysForNumberUnauth(number, '*', { accessKey })
|
.getKeysForIdentifierUnauth(identifier, '*', { accessKey })
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
if (error.code === 401 || error.code === 403) {
|
if (error.code === 401 || error.code === 403) {
|
||||||
if (this.failoverNumbers.indexOf(number) === -1) {
|
if (this.failoverIdentifiers.indexOf(identifier) === -1) {
|
||||||
this.failoverNumbers.push(number);
|
this.failoverIdentifiers.push(identifier);
|
||||||
}
|
}
|
||||||
return this.server.getKeysForNumber(number, '*');
|
return this.server.getKeysForIdentifier(identifier, '*');
|
||||||
}
|
}
|
||||||
throw error;
|
throw error;
|
||||||
})
|
})
|
||||||
.then(handleResult);
|
.then(handleResult);
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.server.getKeysForNumber(number, '*').then(handleResult);
|
return this.server
|
||||||
|
.getKeysForIdentifier(identifier, '*')
|
||||||
|
.then(handleResult);
|
||||||
}
|
}
|
||||||
|
|
||||||
let promise = Promise.resolve();
|
let promise = Promise.resolve();
|
||||||
|
@ -149,31 +154,31 @@ OutgoingMessage.prototype = {
|
||||||
|
|
||||||
if (accessKey) {
|
if (accessKey) {
|
||||||
innerPromise = this.server
|
innerPromise = this.server
|
||||||
.getKeysForNumberUnauth(number, deviceId, { accessKey })
|
.getKeysForIdentifierUnauth(identifier, deviceId, { accessKey })
|
||||||
.then(handleResult)
|
.then(handleResult)
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
if (error.code === 401 || error.code === 403) {
|
if (error.code === 401 || error.code === 403) {
|
||||||
if (this.failoverNumbers.indexOf(number) === -1) {
|
if (this.failoverIdentifiers.indexOf(identifier) === -1) {
|
||||||
this.failoverNumbers.push(number);
|
this.failoverIdentifiers.push(identifier);
|
||||||
}
|
}
|
||||||
return this.server
|
return this.server
|
||||||
.getKeysForNumber(number, deviceId)
|
.getKeysForIdentifier(identifier, deviceId)
|
||||||
.then(handleResult);
|
.then(handleResult);
|
||||||
}
|
}
|
||||||
throw error;
|
throw error;
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
innerPromise = this.server
|
innerPromise = this.server
|
||||||
.getKeysForNumber(number, deviceId)
|
.getKeysForIdentifier(identifier, deviceId)
|
||||||
.then(handleResult);
|
.then(handleResult);
|
||||||
}
|
}
|
||||||
|
|
||||||
return innerPromise.catch(e => {
|
return innerPromise.catch(e => {
|
||||||
if (e.name === 'HTTPError' && e.code === 404) {
|
if (e.name === 'HTTPError' && e.code === 404) {
|
||||||
if (deviceId !== 1) {
|
if (deviceId !== 1) {
|
||||||
return this.removeDeviceIdsForNumber(number, [deviceId]);
|
return this.removeDeviceIdsForIdentifier(identifier, [deviceId]);
|
||||||
}
|
}
|
||||||
throw new textsecure.UnregisteredUserError(number, e);
|
throw new textsecure.UnregisteredUserError(identifier, e);
|
||||||
} else {
|
} else {
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
|
@ -184,12 +189,12 @@ OutgoingMessage.prototype = {
|
||||||
return promise;
|
return promise;
|
||||||
},
|
},
|
||||||
|
|
||||||
transmitMessage(number, jsonData, timestamp, { accessKey } = {}) {
|
transmitMessage(identifier, jsonData, timestamp, { accessKey } = {}) {
|
||||||
let promise;
|
let promise;
|
||||||
|
|
||||||
if (accessKey) {
|
if (accessKey) {
|
||||||
promise = this.server.sendMessagesUnauth(
|
promise = this.server.sendMessagesUnauth(
|
||||||
number,
|
identifier,
|
||||||
jsonData,
|
jsonData,
|
||||||
timestamp,
|
timestamp,
|
||||||
this.silent,
|
this.silent,
|
||||||
|
@ -198,7 +203,7 @@ OutgoingMessage.prototype = {
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
promise = this.server.sendMessages(
|
promise = this.server.sendMessages(
|
||||||
number,
|
identifier,
|
||||||
jsonData,
|
jsonData,
|
||||||
timestamp,
|
timestamp,
|
||||||
this.silent,
|
this.silent,
|
||||||
|
@ -212,10 +217,10 @@ OutgoingMessage.prototype = {
|
||||||
// 404 should throw UnregisteredUserError
|
// 404 should throw UnregisteredUserError
|
||||||
// all other network errors can be retried later.
|
// all other network errors can be retried later.
|
||||||
if (e.code === 404) {
|
if (e.code === 404) {
|
||||||
throw new textsecure.UnregisteredUserError(number, e);
|
throw new textsecure.UnregisteredUserError(identifier, e);
|
||||||
}
|
}
|
||||||
throw new textsecure.SendMessageNetworkError(
|
throw new textsecure.SendMessageNetworkError(
|
||||||
number,
|
identifier,
|
||||||
jsonData,
|
jsonData,
|
||||||
e,
|
e,
|
||||||
timestamp
|
timestamp
|
||||||
|
@ -248,13 +253,17 @@ OutgoingMessage.prototype = {
|
||||||
return this.plaintext;
|
return this.plaintext;
|
||||||
},
|
},
|
||||||
|
|
||||||
doSendMessage(number, deviceIds, recurse) {
|
doSendMessage(identifier, deviceIds, recurse) {
|
||||||
const ciphers = {};
|
const ciphers = {};
|
||||||
const plaintext = this.getPlaintext();
|
const plaintext = this.getPlaintext();
|
||||||
|
|
||||||
const { numberInfo, senderCertificate } = this;
|
const { sendMetadata } = this;
|
||||||
const info = numberInfo && numberInfo[number] ? numberInfo[number] : {};
|
const info =
|
||||||
const { accessKey } = info || {};
|
sendMetadata && sendMetadata[identifier] ? sendMetadata[identifier] : {};
|
||||||
|
const { accessKey, useUuidSenderCert } = info || {};
|
||||||
|
const senderCertificate = useUuidSenderCert
|
||||||
|
? this.senderCertificateWithUuid
|
||||||
|
: this.senderCertificate;
|
||||||
|
|
||||||
if (accessKey && !senderCertificate) {
|
if (accessKey && !senderCertificate) {
|
||||||
window.log.warn(
|
window.log.warn(
|
||||||
|
@ -266,8 +275,9 @@ OutgoingMessage.prototype = {
|
||||||
|
|
||||||
// We don't send to ourselves if unless sealedSender is enabled
|
// We don't send to ourselves if unless sealedSender is enabled
|
||||||
const ourNumber = textsecure.storage.user.getNumber();
|
const ourNumber = textsecure.storage.user.getNumber();
|
||||||
|
const ourUuid = textsecure.storage.user.getUuid();
|
||||||
const ourDeviceId = textsecure.storage.user.getDeviceId();
|
const ourDeviceId = textsecure.storage.user.getDeviceId();
|
||||||
if (number === ourNumber && !sealedSender) {
|
if ((identifier === ourNumber || identifier === ourUuid) && !sealedSender) {
|
||||||
// eslint-disable-next-line no-param-reassign
|
// eslint-disable-next-line no-param-reassign
|
||||||
deviceIds = _.reject(
|
deviceIds = _.reject(
|
||||||
deviceIds,
|
deviceIds,
|
||||||
|
@ -279,12 +289,15 @@ OutgoingMessage.prototype = {
|
||||||
|
|
||||||
return Promise.all(
|
return Promise.all(
|
||||||
deviceIds.map(async deviceId => {
|
deviceIds.map(async deviceId => {
|
||||||
const address = new libsignal.SignalProtocolAddress(number, deviceId);
|
const address = new libsignal.SignalProtocolAddress(
|
||||||
|
identifier,
|
||||||
|
deviceId
|
||||||
|
);
|
||||||
|
|
||||||
const options = {};
|
const options = {};
|
||||||
|
|
||||||
// No limit on message keys if we're communicating with our other devices
|
// No limit on message keys if we're communicating with our other devices
|
||||||
if (ourNumber === number) {
|
if (ourNumber === identifier || ourUuid === identifier) {
|
||||||
options.messageKeysLimit = false;
|
options.messageKeysLimit = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -299,6 +312,7 @@ OutgoingMessage.prototype = {
|
||||||
senderCertificate,
|
senderCertificate,
|
||||||
plaintext
|
plaintext
|
||||||
);
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
type: textsecure.protobuf.Envelope.Type.UNIDENTIFIED_SENDER,
|
type: textsecure.protobuf.Envelope.Type.UNIDENTIFIED_SENDER,
|
||||||
destinationDeviceId: address.getDeviceId(),
|
destinationDeviceId: address.getDeviceId(),
|
||||||
|
@ -327,18 +341,18 @@ OutgoingMessage.prototype = {
|
||||||
)
|
)
|
||||||
.then(jsonData => {
|
.then(jsonData => {
|
||||||
if (sealedSender) {
|
if (sealedSender) {
|
||||||
return this.transmitMessage(number, jsonData, this.timestamp, {
|
return this.transmitMessage(identifier, jsonData, this.timestamp, {
|
||||||
accessKey,
|
accessKey,
|
||||||
}).then(
|
}).then(
|
||||||
() => {
|
() => {
|
||||||
this.unidentifiedDeliveries.push(number);
|
this.unidentifiedDeliveries.push(identifier);
|
||||||
this.successfulNumbers.push(number);
|
this.successfulIdentifiers.push(identifier);
|
||||||
this.numberCompleted();
|
this.numberCompleted();
|
||||||
},
|
},
|
||||||
error => {
|
error => {
|
||||||
if (error.code === 401 || error.code === 403) {
|
if (error.code === 401 || error.code === 403) {
|
||||||
if (this.failoverNumbers.indexOf(number) === -1) {
|
if (this.failoverIdentifiers.indexOf(identifier) === -1) {
|
||||||
this.failoverNumbers.push(number);
|
this.failoverIdentifiers.push(identifier);
|
||||||
}
|
}
|
||||||
if (info) {
|
if (info) {
|
||||||
info.accessKey = null;
|
info.accessKey = null;
|
||||||
|
@ -346,7 +360,7 @@ OutgoingMessage.prototype = {
|
||||||
|
|
||||||
// Set final parameter to true to ensure we don't hit this codepath a
|
// Set final parameter to true to ensure we don't hit this codepath a
|
||||||
// second time.
|
// second time.
|
||||||
return this.doSendMessage(number, deviceIds, recurse, true);
|
return this.doSendMessage(identifier, deviceIds, recurse, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
throw error;
|
throw error;
|
||||||
|
@ -354,9 +368,9 @@ OutgoingMessage.prototype = {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.transmitMessage(number, jsonData, this.timestamp).then(
|
return this.transmitMessage(identifier, jsonData, this.timestamp).then(
|
||||||
() => {
|
() => {
|
||||||
this.successfulNumbers.push(number);
|
this.successfulIdentifiers.push(identifier);
|
||||||
this.numberCompleted();
|
this.numberCompleted();
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
@ -369,22 +383,22 @@ OutgoingMessage.prototype = {
|
||||||
) {
|
) {
|
||||||
if (!recurse)
|
if (!recurse)
|
||||||
return this.registerError(
|
return this.registerError(
|
||||||
number,
|
identifier,
|
||||||
'Hit retry limit attempting to reload device list',
|
'Hit retry limit attempting to reload device list',
|
||||||
error
|
error
|
||||||
);
|
);
|
||||||
|
|
||||||
let p;
|
let p;
|
||||||
if (error.code === 409) {
|
if (error.code === 409) {
|
||||||
p = this.removeDeviceIdsForNumber(
|
p = this.removeDeviceIdsForIdentifier(
|
||||||
number,
|
identifier,
|
||||||
error.response.extraDevices
|
error.response.extraDevices
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
p = Promise.all(
|
p = Promise.all(
|
||||||
error.response.staleDevices.map(deviceId =>
|
error.response.staleDevices.map(deviceId =>
|
||||||
ciphers[deviceId].closeOpenSessionForDevice(
|
ciphers[deviceId].closeOpenSessionForDevice(
|
||||||
new libsignal.SignalProtocolAddress(number, deviceId)
|
new libsignal.SignalProtocolAddress(identifier, deviceId)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
@ -395,10 +409,10 @@ OutgoingMessage.prototype = {
|
||||||
error.code === 410
|
error.code === 410
|
||||||
? error.response.staleDevices
|
? error.response.staleDevices
|
||||||
: error.response.missingDevices;
|
: error.response.missingDevices;
|
||||||
return this.getKeysForNumber(number, resetDevices).then(
|
return this.getKeysForIdentifier(identifier, resetDevices).then(
|
||||||
// We continue to retry as long as the error code was 409; the assumption is
|
// We continue to retry as long as the error code was 409; the assumption is
|
||||||
// that we'll request new device info and the next request will succeed.
|
// that we'll request new device info and the next request will succeed.
|
||||||
this.reloadDevicesAndSend(number, error.code === 409)
|
this.reloadDevicesAndSend(identifier, error.code === 409)
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
} else if (error.message === 'Identity key changed') {
|
} else if (error.message === 'Identity key changed') {
|
||||||
|
@ -408,13 +422,12 @@ OutgoingMessage.prototype = {
|
||||||
error.originalMessage = this.message.toArrayBuffer();
|
error.originalMessage = this.message.toArrayBuffer();
|
||||||
window.log.error(
|
window.log.error(
|
||||||
'Got "key changed" error from encrypt - no identityKey for application layer',
|
'Got "key changed" error from encrypt - no identityKey for application layer',
|
||||||
number,
|
identifier,
|
||||||
deviceIds
|
deviceIds
|
||||||
);
|
);
|
||||||
|
|
||||||
const address = new libsignal.SignalProtocolAddress(number, 1);
|
window.log.info('closing all sessions for', identifier);
|
||||||
const identifier = address.toString();
|
const address = new libsignal.SignalProtocolAddress(identifier, 1);
|
||||||
window.log.info('closing all sessions for', number);
|
|
||||||
|
|
||||||
const sessionCipher = new libsignal.SessionCipher(
|
const sessionCipher = new libsignal.SessionCipher(
|
||||||
textsecure.storage.protocol,
|
textsecure.storage.protocol,
|
||||||
|
@ -425,7 +438,9 @@ OutgoingMessage.prototype = {
|
||||||
// Primary device
|
// Primary device
|
||||||
sessionCipher.closeOpenSessionForDevice(),
|
sessionCipher.closeOpenSessionForDevice(),
|
||||||
// The rest of their devices
|
// The rest of their devices
|
||||||
textsecure.storage.protocol.archiveSiblingSessions(identifier),
|
textsecure.storage.protocol.archiveSiblingSessions(
|
||||||
|
address.toString()
|
||||||
|
),
|
||||||
]).then(
|
]).then(
|
||||||
() => {
|
() => {
|
||||||
throw error;
|
throw error;
|
||||||
|
@ -439,65 +454,76 @@ OutgoingMessage.prototype = {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.registerError(number, 'Failed to create or send message', error);
|
this.registerError(
|
||||||
|
identifier,
|
||||||
|
'Failed to create or send message',
|
||||||
|
error
|
||||||
|
);
|
||||||
return null;
|
return null;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
getStaleDeviceIdsForNumber(number) {
|
getStaleDeviceIdsForIdentifier(identifier) {
|
||||||
return textsecure.storage.protocol.getDeviceIds(number).then(deviceIds => {
|
return textsecure.storage.protocol
|
||||||
if (deviceIds.length === 0) {
|
.getDeviceIds(identifier)
|
||||||
return [1];
|
.then(deviceIds => {
|
||||||
}
|
if (deviceIds.length === 0) {
|
||||||
const updateDevices = [];
|
return [1];
|
||||||
return Promise.all(
|
}
|
||||||
deviceIds.map(deviceId => {
|
const updateDevices = [];
|
||||||
const address = new libsignal.SignalProtocolAddress(number, deviceId);
|
return Promise.all(
|
||||||
const sessionCipher = new libsignal.SessionCipher(
|
deviceIds.map(deviceId => {
|
||||||
textsecure.storage.protocol,
|
const address = new libsignal.SignalProtocolAddress(
|
||||||
address
|
identifier,
|
||||||
);
|
deviceId
|
||||||
return sessionCipher.hasOpenSession().then(hasSession => {
|
);
|
||||||
if (!hasSession) {
|
const sessionCipher = new libsignal.SessionCipher(
|
||||||
updateDevices.push(deviceId);
|
textsecure.storage.protocol,
|
||||||
}
|
address
|
||||||
});
|
);
|
||||||
})
|
return sessionCipher.hasOpenSession().then(hasSession => {
|
||||||
).then(() => updateDevices);
|
if (!hasSession) {
|
||||||
});
|
updateDevices.push(deviceId);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
})
|
||||||
|
).then(() => updateDevices);
|
||||||
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
removeDeviceIdsForNumber(number, deviceIdsToRemove) {
|
removeDeviceIdsForIdentifier(identifier, deviceIdsToRemove) {
|
||||||
let promise = Promise.resolve();
|
let promise = Promise.resolve();
|
||||||
// eslint-disable-next-line no-restricted-syntax, guard-for-in
|
// eslint-disable-next-line no-restricted-syntax, guard-for-in
|
||||||
for (const j in deviceIdsToRemove) {
|
for (const j in deviceIdsToRemove) {
|
||||||
promise = promise.then(() => {
|
promise = promise.then(() => {
|
||||||
const encodedNumber = `${number}.${deviceIdsToRemove[j]}`;
|
const encodedAddress = `${identifier}.${deviceIdsToRemove[j]}`;
|
||||||
return textsecure.storage.protocol.removeSession(encodedNumber);
|
return textsecure.storage.protocol.removeSession(encodedAddress);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return promise;
|
return promise;
|
||||||
},
|
},
|
||||||
|
|
||||||
async sendToNumber(number) {
|
async sendToIdentifier(identifier) {
|
||||||
try {
|
try {
|
||||||
const updateDevices = await this.getStaleDeviceIdsForNumber(number);
|
const updateDevices = await this.getStaleDeviceIdsForIdentifier(
|
||||||
await this.getKeysForNumber(number, updateDevices);
|
identifier
|
||||||
await this.reloadDevicesAndSend(number, true)();
|
);
|
||||||
|
await this.getKeysForIdentifier(identifier, updateDevices);
|
||||||
|
await this.reloadDevicesAndSend(identifier, true)();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error.message === 'Identity key changed') {
|
if (error.message === 'Identity key changed') {
|
||||||
// eslint-disable-next-line no-param-reassign
|
// eslint-disable-next-line no-param-reassign
|
||||||
const newError = new textsecure.OutgoingIdentityKeyError(
|
const newError = new textsecure.OutgoingIdentityKeyError(
|
||||||
number,
|
identifier,
|
||||||
error.originalMessage,
|
error.originalMessage,
|
||||||
error.timestamp,
|
error.timestamp,
|
||||||
error.identityKey
|
error.identityKey
|
||||||
);
|
);
|
||||||
this.registerError(number, 'Identity key changed', newError);
|
this.registerError(identifier, 'Identity key changed', newError);
|
||||||
} else {
|
} else {
|
||||||
this.registerError(
|
this.registerError(
|
||||||
number,
|
identifier,
|
||||||
`Failed to retrieve new device keys for number ${number}`,
|
`Failed to retrieve new device keys for number ${identifier}`,
|
||||||
error
|
error
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
/* global _, textsecure, WebAPI, libsignal, OutgoingMessage, window, dcodeIO */
|
// eslint-disable-next-line max-len
|
||||||
|
/* global _, textsecure, WebAPI, libsignal, OutgoingMessage, window, dcodeIO, ConversationController */
|
||||||
|
|
||||||
/* eslint-disable more/no-then, no-bitwise */
|
/* eslint-disable more/no-then, no-bitwise */
|
||||||
|
|
||||||
|
@ -246,18 +247,22 @@ MessageSender.prototype = {
|
||||||
return proto;
|
return proto;
|
||||||
},
|
},
|
||||||
|
|
||||||
queueJobForNumber(number, runJob) {
|
async queueJobForIdentifier(identifier, runJob) {
|
||||||
this.pendingMessages[number] =
|
const { id } = await ConversationController.getOrCreateAndWait(
|
||||||
this.pendingMessages[number] || new window.PQueue({ concurrency: 1 });
|
identifier,
|
||||||
|
'private'
|
||||||
|
);
|
||||||
|
this.pendingMessages[id] =
|
||||||
|
this.pendingMessages[id] || new window.PQueue({ concurrency: 1 });
|
||||||
|
|
||||||
const queue = this.pendingMessages[number];
|
const queue = this.pendingMessages[id];
|
||||||
|
|
||||||
const taskWithTimeout = textsecure.createTaskWithTimeout(
|
const taskWithTimeout = textsecure.createTaskWithTimeout(
|
||||||
runJob,
|
runJob,
|
||||||
`queueJobForNumber ${number}`
|
`queueJobForIdentifier ${identifier} ${id}`
|
||||||
);
|
);
|
||||||
|
|
||||||
queue.add(taskWithTimeout);
|
return queue.add(taskWithTimeout);
|
||||||
},
|
},
|
||||||
|
|
||||||
uploadAttachments(message) {
|
uploadAttachments(message) {
|
||||||
|
@ -361,7 +366,7 @@ MessageSender.prototype = {
|
||||||
new Promise((resolve, reject) => {
|
new Promise((resolve, reject) => {
|
||||||
this.sendMessageProto(
|
this.sendMessageProto(
|
||||||
message.timestamp,
|
message.timestamp,
|
||||||
message.recipients,
|
message.recipients || [],
|
||||||
message.toProto(),
|
message.toProto(),
|
||||||
res => {
|
res => {
|
||||||
res.dataMessage = message.toArrayBuffer();
|
res.dataMessage = message.toArrayBuffer();
|
||||||
|
@ -379,8 +384,8 @@ MessageSender.prototype = {
|
||||||
},
|
},
|
||||||
sendMessageProto(
|
sendMessageProto(
|
||||||
timestamp,
|
timestamp,
|
||||||
numbers,
|
recipients,
|
||||||
message,
|
messageProto,
|
||||||
callback,
|
callback,
|
||||||
silent,
|
silent,
|
||||||
options = {}
|
options = {}
|
||||||
|
@ -388,8 +393,8 @@ MessageSender.prototype = {
|
||||||
const rejections = textsecure.storage.get('signedKeyRotationRejected', 0);
|
const rejections = textsecure.storage.get('signedKeyRotationRejected', 0);
|
||||||
if (rejections > 5) {
|
if (rejections > 5) {
|
||||||
throw new textsecure.SignedPreKeyRotationError(
|
throw new textsecure.SignedPreKeyRotationError(
|
||||||
numbers,
|
recipients,
|
||||||
message.toArrayBuffer(),
|
messageProto.toArrayBuffer(),
|
||||||
timestamp
|
timestamp
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -397,19 +402,27 @@ MessageSender.prototype = {
|
||||||
const outgoing = new OutgoingMessage(
|
const outgoing = new OutgoingMessage(
|
||||||
this.server,
|
this.server,
|
||||||
timestamp,
|
timestamp,
|
||||||
numbers,
|
recipients,
|
||||||
message,
|
messageProto,
|
||||||
silent,
|
silent,
|
||||||
callback,
|
callback,
|
||||||
options
|
options
|
||||||
);
|
);
|
||||||
|
|
||||||
numbers.forEach(number => {
|
recipients.forEach(identifier => {
|
||||||
this.queueJobForNumber(number, () => outgoing.sendToNumber(number));
|
this.queueJobForIdentifier(identifier, () =>
|
||||||
|
outgoing.sendToIdentifier(identifier)
|
||||||
|
);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
sendMessageProtoAndWait(timestamp, numbers, message, silent, options = {}) {
|
sendMessageProtoAndWait(
|
||||||
|
timestamp,
|
||||||
|
identifiers,
|
||||||
|
messageProto,
|
||||||
|
silent,
|
||||||
|
options = {}
|
||||||
|
) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
const callback = result => {
|
const callback = result => {
|
||||||
if (result && result.errors && result.errors.length > 0) {
|
if (result && result.errors && result.errors.length > 0) {
|
||||||
|
@ -421,8 +434,8 @@ MessageSender.prototype = {
|
||||||
|
|
||||||
this.sendMessageProto(
|
this.sendMessageProto(
|
||||||
timestamp,
|
timestamp,
|
||||||
numbers,
|
identifiers,
|
||||||
message,
|
messageProto,
|
||||||
callback,
|
callback,
|
||||||
silent,
|
silent,
|
||||||
options
|
options
|
||||||
|
@ -430,7 +443,7 @@ MessageSender.prototype = {
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
sendIndividualProto(number, proto, timestamp, silent, options = {}) {
|
sendIndividualProto(identifier, proto, timestamp, silent, options = {}) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
const callback = res => {
|
const callback = res => {
|
||||||
if (res && res.errors && res.errors.length > 0) {
|
if (res && res.errors && res.errors.length > 0) {
|
||||||
|
@ -441,7 +454,7 @@ MessageSender.prototype = {
|
||||||
};
|
};
|
||||||
this.sendMessageProto(
|
this.sendMessageProto(
|
||||||
timestamp,
|
timestamp,
|
||||||
[number],
|
[identifier],
|
||||||
proto,
|
proto,
|
||||||
callback,
|
callback,
|
||||||
silent,
|
silent,
|
||||||
|
@ -467,6 +480,7 @@ MessageSender.prototype = {
|
||||||
encodedDataMessage,
|
encodedDataMessage,
|
||||||
timestamp,
|
timestamp,
|
||||||
destination,
|
destination,
|
||||||
|
destinationUuid,
|
||||||
expirationStartTimestamp,
|
expirationStartTimestamp,
|
||||||
sentTo = [],
|
sentTo = [],
|
||||||
unidentifiedDeliveries = [],
|
unidentifiedDeliveries = [],
|
||||||
|
@ -474,7 +488,9 @@ MessageSender.prototype = {
|
||||||
options
|
options
|
||||||
) {
|
) {
|
||||||
const myNumber = textsecure.storage.user.getNumber();
|
const myNumber = textsecure.storage.user.getNumber();
|
||||||
|
const myUuid = textsecure.storage.user.getUuid();
|
||||||
const myDevice = textsecure.storage.user.getDeviceId();
|
const myDevice = textsecure.storage.user.getDeviceId();
|
||||||
|
|
||||||
if (myDevice === 1 || myDevice === '1') {
|
if (myDevice === 1 || myDevice === '1') {
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
}
|
}
|
||||||
|
@ -488,6 +504,9 @@ MessageSender.prototype = {
|
||||||
if (destination) {
|
if (destination) {
|
||||||
sentMessage.destination = destination;
|
sentMessage.destination = destination;
|
||||||
}
|
}
|
||||||
|
if (destinationUuid) {
|
||||||
|
sentMessage.destinationUuid = destinationUuid;
|
||||||
|
}
|
||||||
if (expirationStartTimestamp) {
|
if (expirationStartTimestamp) {
|
||||||
sentMessage.expirationStartTimestamp = expirationStartTimestamp;
|
sentMessage.expirationStartTimestamp = expirationStartTimestamp;
|
||||||
}
|
}
|
||||||
|
@ -508,10 +527,16 @@ MessageSender.prototype = {
|
||||||
// Though this field has 'unidenified' in the name, it should have entries for each
|
// Though this field has 'unidenified' in the name, it should have entries for each
|
||||||
// number we sent to.
|
// number we sent to.
|
||||||
if (sentTo && sentTo.length) {
|
if (sentTo && sentTo.length) {
|
||||||
sentMessage.unidentifiedStatus = sentTo.map(number => {
|
sentMessage.unidentifiedStatus = sentTo.map(identifier => {
|
||||||
const status = new textsecure.protobuf.SyncMessage.Sent.UnidentifiedDeliveryStatus();
|
const status = new textsecure.protobuf.SyncMessage.Sent.UnidentifiedDeliveryStatus();
|
||||||
status.destination = number;
|
const conv = ConversationController.get(identifier);
|
||||||
status.unidentified = Boolean(unidentifiedLookup[number]);
|
if (conv && conv.get('e164')) {
|
||||||
|
status.destination = conv.get('e164');
|
||||||
|
}
|
||||||
|
if (conv && conv.get('uuid')) {
|
||||||
|
status.destinationUuid = conv.get('uuid');
|
||||||
|
}
|
||||||
|
status.unidentified = Boolean(unidentifiedLookup[identifier]);
|
||||||
return status;
|
return status;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -523,7 +548,7 @@ MessageSender.prototype = {
|
||||||
|
|
||||||
const silent = true;
|
const silent = true;
|
||||||
return this.sendIndividualProto(
|
return this.sendIndividualProto(
|
||||||
myNumber,
|
myUuid || myNumber,
|
||||||
contentMessage,
|
contentMessage,
|
||||||
timestamp,
|
timestamp,
|
||||||
silent,
|
silent,
|
||||||
|
@ -552,6 +577,7 @@ MessageSender.prototype = {
|
||||||
|
|
||||||
sendRequestBlockSyncMessage(options) {
|
sendRequestBlockSyncMessage(options) {
|
||||||
const myNumber = textsecure.storage.user.getNumber();
|
const myNumber = textsecure.storage.user.getNumber();
|
||||||
|
const myUuid = textsecure.storage.user.getUuid();
|
||||||
const myDevice = textsecure.storage.user.getDeviceId();
|
const myDevice = textsecure.storage.user.getDeviceId();
|
||||||
if (myDevice !== 1 && myDevice !== '1') {
|
if (myDevice !== 1 && myDevice !== '1') {
|
||||||
const request = new textsecure.protobuf.SyncMessage.Request();
|
const request = new textsecure.protobuf.SyncMessage.Request();
|
||||||
|
@ -563,7 +589,7 @@ MessageSender.prototype = {
|
||||||
|
|
||||||
const silent = true;
|
const silent = true;
|
||||||
return this.sendIndividualProto(
|
return this.sendIndividualProto(
|
||||||
myNumber,
|
myUuid || myNumber,
|
||||||
contentMessage,
|
contentMessage,
|
||||||
Date.now(),
|
Date.now(),
|
||||||
silent,
|
silent,
|
||||||
|
@ -576,6 +602,7 @@ MessageSender.prototype = {
|
||||||
|
|
||||||
sendRequestConfigurationSyncMessage(options) {
|
sendRequestConfigurationSyncMessage(options) {
|
||||||
const myNumber = textsecure.storage.user.getNumber();
|
const myNumber = textsecure.storage.user.getNumber();
|
||||||
|
const myUuid = textsecure.storage.user.getUuid();
|
||||||
const myDevice = textsecure.storage.user.getDeviceId();
|
const myDevice = textsecure.storage.user.getDeviceId();
|
||||||
if (myDevice !== 1 && myDevice !== '1') {
|
if (myDevice !== 1 && myDevice !== '1') {
|
||||||
const request = new textsecure.protobuf.SyncMessage.Request();
|
const request = new textsecure.protobuf.SyncMessage.Request();
|
||||||
|
@ -587,7 +614,7 @@ MessageSender.prototype = {
|
||||||
|
|
||||||
const silent = true;
|
const silent = true;
|
||||||
return this.sendIndividualProto(
|
return this.sendIndividualProto(
|
||||||
myNumber,
|
myUuid || myNumber,
|
||||||
contentMessage,
|
contentMessage,
|
||||||
Date.now(),
|
Date.now(),
|
||||||
silent,
|
silent,
|
||||||
|
@ -600,6 +627,7 @@ MessageSender.prototype = {
|
||||||
|
|
||||||
sendRequestGroupSyncMessage(options) {
|
sendRequestGroupSyncMessage(options) {
|
||||||
const myNumber = textsecure.storage.user.getNumber();
|
const myNumber = textsecure.storage.user.getNumber();
|
||||||
|
const myUuid = textsecure.storage.user.getUuid();
|
||||||
const myDevice = textsecure.storage.user.getDeviceId();
|
const myDevice = textsecure.storage.user.getDeviceId();
|
||||||
if (myDevice !== 1 && myDevice !== '1') {
|
if (myDevice !== 1 && myDevice !== '1') {
|
||||||
const request = new textsecure.protobuf.SyncMessage.Request();
|
const request = new textsecure.protobuf.SyncMessage.Request();
|
||||||
|
@ -611,7 +639,7 @@ MessageSender.prototype = {
|
||||||
|
|
||||||
const silent = true;
|
const silent = true;
|
||||||
return this.sendIndividualProto(
|
return this.sendIndividualProto(
|
||||||
myNumber,
|
myUuid || myNumber,
|
||||||
contentMessage,
|
contentMessage,
|
||||||
Date.now(),
|
Date.now(),
|
||||||
silent,
|
silent,
|
||||||
|
@ -624,6 +652,8 @@ MessageSender.prototype = {
|
||||||
|
|
||||||
sendRequestContactSyncMessage(options) {
|
sendRequestContactSyncMessage(options) {
|
||||||
const myNumber = textsecure.storage.user.getNumber();
|
const myNumber = textsecure.storage.user.getNumber();
|
||||||
|
const myUuid = textsecure.storage.user.getUuid();
|
||||||
|
|
||||||
const myDevice = textsecure.storage.user.getDeviceId();
|
const myDevice = textsecure.storage.user.getDeviceId();
|
||||||
if (myDevice !== 1 && myDevice !== '1') {
|
if (myDevice !== 1 && myDevice !== '1') {
|
||||||
const request = new textsecure.protobuf.SyncMessage.Request();
|
const request = new textsecure.protobuf.SyncMessage.Request();
|
||||||
|
@ -635,7 +665,7 @@ MessageSender.prototype = {
|
||||||
|
|
||||||
const silent = true;
|
const silent = true;
|
||||||
return this.sendIndividualProto(
|
return this.sendIndividualProto(
|
||||||
myNumber,
|
myUuid || myNumber,
|
||||||
contentMessage,
|
contentMessage,
|
||||||
Date.now(),
|
Date.now(),
|
||||||
silent,
|
silent,
|
||||||
|
@ -653,7 +683,8 @@ MessageSender.prototype = {
|
||||||
// We don't want to send typing messages to our other devices, but we will
|
// We don't want to send typing messages to our other devices, but we will
|
||||||
// in the group case.
|
// in the group case.
|
||||||
const myNumber = textsecure.storage.user.getNumber();
|
const myNumber = textsecure.storage.user.getNumber();
|
||||||
if (recipientId && myNumber === recipientId) {
|
const myUuid = textsecure.storage.user.getUuid();
|
||||||
|
if (recipientId && (myNumber === recipientId || myUuid === recipientId)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -662,7 +693,7 @@ MessageSender.prototype = {
|
||||||
}
|
}
|
||||||
|
|
||||||
const recipients = groupId
|
const recipients = groupId
|
||||||
? _.without(groupNumbers, myNumber)
|
? _.without(groupNumbers, myNumber, myUuid)
|
||||||
: [recipientId];
|
: [recipientId];
|
||||||
const groupIdBuffer = groupId
|
const groupIdBuffer = groupId
|
||||||
? window.Signal.Crypto.fromEncodedBinaryToArrayBuffer(groupId)
|
? window.Signal.Crypto.fromEncodedBinaryToArrayBuffer(groupId)
|
||||||
|
@ -694,10 +725,14 @@ MessageSender.prototype = {
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
sendDeliveryReceipt(recipientId, timestamps, options) {
|
sendDeliveryReceipt(recipientE164, recipientUuid, timestamps, options) {
|
||||||
const myNumber = textsecure.storage.user.getNumber();
|
const myNumber = textsecure.storage.user.getNumber();
|
||||||
|
const myUuid = textsecure.storage.user.getUuid();
|
||||||
const myDevice = textsecure.storage.user.getDeviceId();
|
const myDevice = textsecure.storage.user.getDeviceId();
|
||||||
if (myNumber === recipientId && (myDevice === 1 || myDevice === '1')) {
|
if (
|
||||||
|
(myNumber === recipientE164 || myUuid === recipientUuid) &&
|
||||||
|
(myDevice === 1 || myDevice === '1')
|
||||||
|
) {
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -710,7 +745,7 @@ MessageSender.prototype = {
|
||||||
|
|
||||||
const silent = true;
|
const silent = true;
|
||||||
return this.sendIndividualProto(
|
return this.sendIndividualProto(
|
||||||
recipientId,
|
recipientUuid || recipientE164,
|
||||||
contentMessage,
|
contentMessage,
|
||||||
Date.now(),
|
Date.now(),
|
||||||
silent,
|
silent,
|
||||||
|
@ -718,7 +753,7 @@ MessageSender.prototype = {
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
sendReadReceipts(sender, timestamps, options) {
|
sendReadReceipts(senderE164, senderUuid, timestamps, options) {
|
||||||
const receiptMessage = new textsecure.protobuf.ReceiptMessage();
|
const receiptMessage = new textsecure.protobuf.ReceiptMessage();
|
||||||
receiptMessage.type = textsecure.protobuf.ReceiptMessage.Type.READ;
|
receiptMessage.type = textsecure.protobuf.ReceiptMessage.Type.READ;
|
||||||
receiptMessage.timestamp = timestamps;
|
receiptMessage.timestamp = timestamps;
|
||||||
|
@ -728,7 +763,7 @@ MessageSender.prototype = {
|
||||||
|
|
||||||
const silent = true;
|
const silent = true;
|
||||||
return this.sendIndividualProto(
|
return this.sendIndividualProto(
|
||||||
sender,
|
senderUuid || senderE164,
|
||||||
contentMessage,
|
contentMessage,
|
||||||
Date.now(),
|
Date.now(),
|
||||||
silent,
|
silent,
|
||||||
|
@ -737,6 +772,7 @@ MessageSender.prototype = {
|
||||||
},
|
},
|
||||||
syncReadMessages(reads, options) {
|
syncReadMessages(reads, options) {
|
||||||
const myNumber = textsecure.storage.user.getNumber();
|
const myNumber = textsecure.storage.user.getNumber();
|
||||||
|
const myUuid = textsecure.storage.user.getUuid();
|
||||||
const myDevice = textsecure.storage.user.getDeviceId();
|
const myDevice = textsecure.storage.user.getDeviceId();
|
||||||
if (myDevice !== 1 && myDevice !== '1') {
|
if (myDevice !== 1 && myDevice !== '1') {
|
||||||
const syncMessage = this.createSyncMessage();
|
const syncMessage = this.createSyncMessage();
|
||||||
|
@ -745,6 +781,7 @@ MessageSender.prototype = {
|
||||||
const read = new textsecure.protobuf.SyncMessage.Read();
|
const read = new textsecure.protobuf.SyncMessage.Read();
|
||||||
read.timestamp = reads[i].timestamp;
|
read.timestamp = reads[i].timestamp;
|
||||||
read.sender = reads[i].sender;
|
read.sender = reads[i].sender;
|
||||||
|
|
||||||
syncMessage.read.push(read);
|
syncMessage.read.push(read);
|
||||||
}
|
}
|
||||||
const contentMessage = new textsecure.protobuf.Content();
|
const contentMessage = new textsecure.protobuf.Content();
|
||||||
|
@ -752,7 +789,7 @@ MessageSender.prototype = {
|
||||||
|
|
||||||
const silent = true;
|
const silent = true;
|
||||||
return this.sendIndividualProto(
|
return this.sendIndividualProto(
|
||||||
myNumber,
|
myUuid || myNumber,
|
||||||
contentMessage,
|
contentMessage,
|
||||||
Date.now(),
|
Date.now(),
|
||||||
silent,
|
silent,
|
||||||
|
@ -763,8 +800,9 @@ MessageSender.prototype = {
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
},
|
},
|
||||||
|
|
||||||
async syncViewOnceOpen(sender, timestamp, options) {
|
async syncViewOnceOpen(sender, senderUuid, timestamp, options) {
|
||||||
const myNumber = textsecure.storage.user.getNumber();
|
const myNumber = textsecure.storage.user.getNumber();
|
||||||
|
const myUuid = textsecure.storage.user.getUuid();
|
||||||
const myDevice = textsecure.storage.user.getDeviceId();
|
const myDevice = textsecure.storage.user.getDeviceId();
|
||||||
if (myDevice === 1 || myDevice === '1') {
|
if (myDevice === 1 || myDevice === '1') {
|
||||||
return null;
|
return null;
|
||||||
|
@ -774,6 +812,7 @@ MessageSender.prototype = {
|
||||||
|
|
||||||
const viewOnceOpen = new textsecure.protobuf.SyncMessage.ViewOnceOpen();
|
const viewOnceOpen = new textsecure.protobuf.SyncMessage.ViewOnceOpen();
|
||||||
viewOnceOpen.sender = sender;
|
viewOnceOpen.sender = sender;
|
||||||
|
viewOnceOpen.senderUuid = senderUuid;
|
||||||
viewOnceOpen.timestamp = timestamp;
|
viewOnceOpen.timestamp = timestamp;
|
||||||
syncMessage.viewOnceOpen = viewOnceOpen;
|
syncMessage.viewOnceOpen = viewOnceOpen;
|
||||||
|
|
||||||
|
@ -782,7 +821,7 @@ MessageSender.prototype = {
|
||||||
|
|
||||||
const silent = true;
|
const silent = true;
|
||||||
return this.sendIndividualProto(
|
return this.sendIndividualProto(
|
||||||
myNumber,
|
myUuid || myNumber,
|
||||||
contentMessage,
|
contentMessage,
|
||||||
Date.now(),
|
Date.now(),
|
||||||
silent,
|
silent,
|
||||||
|
@ -797,6 +836,7 @@ MessageSender.prototype = {
|
||||||
}
|
}
|
||||||
|
|
||||||
const myNumber = textsecure.storage.user.getNumber();
|
const myNumber = textsecure.storage.user.getNumber();
|
||||||
|
const myUuid = textsecure.storage.user.getUuid();
|
||||||
const ENUM = textsecure.protobuf.SyncMessage.StickerPackOperation.Type;
|
const ENUM = textsecure.protobuf.SyncMessage.StickerPackOperation.Type;
|
||||||
|
|
||||||
const packOperations = operations.map(item => {
|
const packOperations = operations.map(item => {
|
||||||
|
@ -818,15 +858,23 @@ MessageSender.prototype = {
|
||||||
|
|
||||||
const silent = true;
|
const silent = true;
|
||||||
return this.sendIndividualProto(
|
return this.sendIndividualProto(
|
||||||
myNumber,
|
myUuid || myNumber,
|
||||||
contentMessage,
|
contentMessage,
|
||||||
Date.now(),
|
Date.now(),
|
||||||
silent,
|
silent,
|
||||||
options
|
options
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
syncVerification(destination, state, identityKey, options) {
|
|
||||||
|
syncVerification(
|
||||||
|
destinationE164,
|
||||||
|
destinationUuid,
|
||||||
|
state,
|
||||||
|
identityKey,
|
||||||
|
options
|
||||||
|
) {
|
||||||
const myNumber = textsecure.storage.user.getNumber();
|
const myNumber = textsecure.storage.user.getNumber();
|
||||||
|
const myUuid = textsecure.storage.user.getUuid();
|
||||||
const myDevice = textsecure.storage.user.getDeviceId();
|
const myDevice = textsecure.storage.user.getDeviceId();
|
||||||
const now = Date.now();
|
const now = Date.now();
|
||||||
|
|
||||||
|
@ -850,7 +898,7 @@ MessageSender.prototype = {
|
||||||
// We want the NullMessage to look like a normal outgoing message; not silent
|
// We want the NullMessage to look like a normal outgoing message; not silent
|
||||||
const silent = false;
|
const silent = false;
|
||||||
const promise = this.sendIndividualProto(
|
const promise = this.sendIndividualProto(
|
||||||
destination,
|
destinationUuid || destinationE164,
|
||||||
contentMessage,
|
contentMessage,
|
||||||
now,
|
now,
|
||||||
silent,
|
silent,
|
||||||
|
@ -860,7 +908,12 @@ MessageSender.prototype = {
|
||||||
return promise.then(() => {
|
return promise.then(() => {
|
||||||
const verified = new textsecure.protobuf.Verified();
|
const verified = new textsecure.protobuf.Verified();
|
||||||
verified.state = state;
|
verified.state = state;
|
||||||
verified.destination = destination;
|
if (destinationE164) {
|
||||||
|
verified.destination = destinationE164;
|
||||||
|
}
|
||||||
|
if (destinationUuid) {
|
||||||
|
verified.destinationUuid = destinationUuid;
|
||||||
|
}
|
||||||
verified.identityKey = identityKey;
|
verified.identityKey = identityKey;
|
||||||
verified.nullMessage = nullMessage.padding;
|
verified.nullMessage = nullMessage.padding;
|
||||||
|
|
||||||
|
@ -872,7 +925,7 @@ MessageSender.prototype = {
|
||||||
|
|
||||||
const innerSilent = true;
|
const innerSilent = true;
|
||||||
return this.sendIndividualProto(
|
return this.sendIndividualProto(
|
||||||
myNumber,
|
myUuid || myNumber,
|
||||||
secondMessage,
|
secondMessage,
|
||||||
now,
|
now,
|
||||||
innerSilent,
|
innerSilent,
|
||||||
|
@ -881,13 +934,22 @@ MessageSender.prototype = {
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
sendGroupProto(providedNumbers, proto, timestamp = Date.now(), options = {}) {
|
sendGroupProto(
|
||||||
const me = textsecure.storage.user.getNumber();
|
providedIdentifiers,
|
||||||
const numbers = providedNumbers.filter(number => number !== me);
|
proto,
|
||||||
if (numbers.length === 0) {
|
timestamp = Date.now(),
|
||||||
|
options = {}
|
||||||
|
) {
|
||||||
|
const myE164 = textsecure.storage.user.getNumber();
|
||||||
|
const myUuid = textsecure.storage.user.getUuid();
|
||||||
|
const identifiers = providedIdentifiers.filter(
|
||||||
|
id => id !== myE164 && id !== myUuid
|
||||||
|
);
|
||||||
|
|
||||||
|
if (identifiers.length === 0) {
|
||||||
return Promise.resolve({
|
return Promise.resolve({
|
||||||
successfulNumbers: [],
|
successfulIdentifiers: [],
|
||||||
failoverNumbers: [],
|
failoverIdentifiers: [],
|
||||||
errors: [],
|
errors: [],
|
||||||
unidentifiedDeliveries: [],
|
unidentifiedDeliveries: [],
|
||||||
dataMessage: proto.toArrayBuffer(),
|
dataMessage: proto.toArrayBuffer(),
|
||||||
|
@ -907,7 +969,7 @@ MessageSender.prototype = {
|
||||||
|
|
||||||
this.sendMessageProto(
|
this.sendMessageProto(
|
||||||
timestamp,
|
timestamp,
|
||||||
numbers,
|
providedIdentifiers,
|
||||||
proto,
|
proto,
|
||||||
callback,
|
callback,
|
||||||
silent,
|
silent,
|
||||||
|
@ -917,7 +979,7 @@ MessageSender.prototype = {
|
||||||
},
|
},
|
||||||
|
|
||||||
async getMessageProto(
|
async getMessageProto(
|
||||||
number,
|
destination,
|
||||||
body,
|
body,
|
||||||
attachments,
|
attachments,
|
||||||
quote,
|
quote,
|
||||||
|
@ -930,7 +992,8 @@ MessageSender.prototype = {
|
||||||
flags
|
flags
|
||||||
) {
|
) {
|
||||||
const attributes = {
|
const attributes = {
|
||||||
recipients: [number],
|
recipients: [destination],
|
||||||
|
destination,
|
||||||
body,
|
body,
|
||||||
timestamp,
|
timestamp,
|
||||||
attachments,
|
attachments,
|
||||||
|
@ -958,8 +1021,8 @@ MessageSender.prototype = {
|
||||||
return message.toArrayBuffer();
|
return message.toArrayBuffer();
|
||||||
},
|
},
|
||||||
|
|
||||||
sendMessageToNumber(
|
sendMessageToIdentifier(
|
||||||
number,
|
identifier,
|
||||||
messageText,
|
messageText,
|
||||||
attachments,
|
attachments,
|
||||||
quote,
|
quote,
|
||||||
|
@ -973,7 +1036,7 @@ MessageSender.prototype = {
|
||||||
) {
|
) {
|
||||||
return this.sendMessage(
|
return this.sendMessage(
|
||||||
{
|
{
|
||||||
recipients: [number],
|
recipients: [identifier],
|
||||||
body: messageText,
|
body: messageText,
|
||||||
timestamp,
|
timestamp,
|
||||||
attachments,
|
attachments,
|
||||||
|
@ -988,7 +1051,7 @@ MessageSender.prototype = {
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
resetSession(number, timestamp, options) {
|
resetSession(identifier, timestamp, options) {
|
||||||
window.log.info('resetting secure session');
|
window.log.info('resetting secure session');
|
||||||
const silent = false;
|
const silent = false;
|
||||||
const proto = new textsecure.protobuf.DataMessage();
|
const proto = new textsecure.protobuf.DataMessage();
|
||||||
|
@ -1017,14 +1080,14 @@ MessageSender.prototype = {
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
const sendToContactPromise = deleteAllSessions(number)
|
const sendToContactPromise = deleteAllSessions(identifier)
|
||||||
.catch(logError('resetSession/deleteAllSessions1 error:'))
|
.catch(logError('resetSession/deleteAllSessions1 error:'))
|
||||||
.then(() => {
|
.then(() => {
|
||||||
window.log.info(
|
window.log.info(
|
||||||
'finished closing local sessions, now sending to contact'
|
'finished closing local sessions, now sending to contact'
|
||||||
);
|
);
|
||||||
return this.sendIndividualProto(
|
return this.sendIndividualProto(
|
||||||
number,
|
identifier,
|
||||||
proto,
|
proto,
|
||||||
timestamp,
|
timestamp,
|
||||||
silent,
|
silent,
|
||||||
|
@ -1032,14 +1095,15 @@ MessageSender.prototype = {
|
||||||
).catch(logError('resetSession/sendToContact error:'));
|
).catch(logError('resetSession/sendToContact error:'));
|
||||||
})
|
})
|
||||||
.then(() =>
|
.then(() =>
|
||||||
deleteAllSessions(number).catch(
|
deleteAllSessions(identifier).catch(
|
||||||
logError('resetSession/deleteAllSessions2 error:')
|
logError('resetSession/deleteAllSessions2 error:')
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
const myNumber = textsecure.storage.user.getNumber();
|
const myNumber = textsecure.storage.user.getNumber();
|
||||||
|
const myUuid = textsecure.storage.user.getUuid();
|
||||||
// We already sent the reset session to our other devices in the code above!
|
// We already sent the reset session to our other devices in the code above!
|
||||||
if (number === myNumber) {
|
if (identifier === myNumber || identifier === myUuid) {
|
||||||
return sendToContactPromise;
|
return sendToContactPromise;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1047,7 +1111,7 @@ MessageSender.prototype = {
|
||||||
const sendSyncPromise = this.sendSyncMessage(
|
const sendSyncPromise = this.sendSyncMessage(
|
||||||
buffer,
|
buffer,
|
||||||
timestamp,
|
timestamp,
|
||||||
number,
|
identifier,
|
||||||
null,
|
null,
|
||||||
[],
|
[],
|
||||||
[],
|
[],
|
||||||
|
@ -1059,7 +1123,7 @@ MessageSender.prototype = {
|
||||||
|
|
||||||
async sendMessageToGroup(
|
async sendMessageToGroup(
|
||||||
groupId,
|
groupId,
|
||||||
groupNumbers,
|
recipients,
|
||||||
messageText,
|
messageText,
|
||||||
attachments,
|
attachments,
|
||||||
quote,
|
quote,
|
||||||
|
@ -1071,10 +1135,10 @@ MessageSender.prototype = {
|
||||||
profileKey,
|
profileKey,
|
||||||
options
|
options
|
||||||
) {
|
) {
|
||||||
const me = textsecure.storage.user.getNumber();
|
const myE164 = textsecure.storage.user.getNumber();
|
||||||
const numbers = groupNumbers.filter(number => number !== me);
|
const myUuid = textsecure.storage.user.getNumber();
|
||||||
const attrs = {
|
const attrs = {
|
||||||
recipients: numbers,
|
recipients: recipients.filter(r => r !== myE164 && r !== myUuid),
|
||||||
body: messageText,
|
body: messageText,
|
||||||
timestamp,
|
timestamp,
|
||||||
attachments,
|
attachments,
|
||||||
|
@ -1090,10 +1154,10 @@ MessageSender.prototype = {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
if (numbers.length === 0) {
|
if (recipients.length === 0) {
|
||||||
return Promise.resolve({
|
return Promise.resolve({
|
||||||
successfulNumbers: [],
|
successfulIdentifiers: [],
|
||||||
failoverNumbers: [],
|
failoverIdentifiers: [],
|
||||||
errors: [],
|
errors: [],
|
||||||
unidentifiedDeliveries: [],
|
unidentifiedDeliveries: [],
|
||||||
dataMessage: await this.getMessageProtoObj(attrs),
|
dataMessage: await this.getMessageProtoObj(attrs),
|
||||||
|
@ -1103,19 +1167,20 @@ MessageSender.prototype = {
|
||||||
return this.sendMessage(attrs, options);
|
return this.sendMessage(attrs, options);
|
||||||
},
|
},
|
||||||
|
|
||||||
createGroup(targetNumbers, id, name, avatar, options) {
|
createGroup(targetIdentifiers, id, name, avatar, options) {
|
||||||
const proto = new textsecure.protobuf.DataMessage();
|
const proto = new textsecure.protobuf.DataMessage();
|
||||||
proto.group = new textsecure.protobuf.GroupContext();
|
proto.group = new textsecure.protobuf.GroupContext();
|
||||||
proto.group.id = stringToArrayBuffer(id);
|
proto.group.id = stringToArrayBuffer(id);
|
||||||
|
|
||||||
proto.group.type = textsecure.protobuf.GroupContext.Type.UPDATE;
|
proto.group.type = textsecure.protobuf.GroupContext.Type.UPDATE;
|
||||||
proto.group.members = targetNumbers;
|
// TODO
|
||||||
|
proto.group.members = targetIdentifiers;
|
||||||
proto.group.name = name;
|
proto.group.name = name;
|
||||||
|
|
||||||
return this.makeAttachmentPointer(avatar).then(attachment => {
|
return this.makeAttachmentPointer(avatar).then(attachment => {
|
||||||
proto.group.avatar = attachment;
|
proto.group.avatar = attachment;
|
||||||
return this.sendGroupProto(
|
return this.sendGroupProto(
|
||||||
targetNumbers,
|
targetIdentifiers,
|
||||||
proto,
|
proto,
|
||||||
Date.now(),
|
Date.now(),
|
||||||
options
|
options
|
||||||
|
@ -1123,19 +1188,19 @@ MessageSender.prototype = {
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
updateGroup(groupId, name, avatar, targetNumbers, options) {
|
updateGroup(groupId, name, avatar, targetIdentifiers, options) {
|
||||||
const proto = new textsecure.protobuf.DataMessage();
|
const proto = new textsecure.protobuf.DataMessage();
|
||||||
proto.group = new textsecure.protobuf.GroupContext();
|
proto.group = new textsecure.protobuf.GroupContext();
|
||||||
|
|
||||||
proto.group.id = stringToArrayBuffer(groupId);
|
proto.group.id = stringToArrayBuffer(groupId);
|
||||||
proto.group.type = textsecure.protobuf.GroupContext.Type.UPDATE;
|
proto.group.type = textsecure.protobuf.GroupContext.Type.UPDATE;
|
||||||
proto.group.name = name;
|
proto.group.name = name;
|
||||||
proto.group.members = targetNumbers;
|
proto.group.members = targetIdentifiers;
|
||||||
|
|
||||||
return this.makeAttachmentPointer(avatar).then(attachment => {
|
return this.makeAttachmentPointer(avatar).then(attachment => {
|
||||||
proto.group.avatar = attachment;
|
proto.group.avatar = attachment;
|
||||||
return this.sendGroupProto(
|
return this.sendGroupProto(
|
||||||
targetNumbers,
|
targetIdentifiers,
|
||||||
proto,
|
proto,
|
||||||
Date.now(),
|
Date.now(),
|
||||||
options
|
options
|
||||||
|
@ -1143,58 +1208,61 @@ MessageSender.prototype = {
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
addNumberToGroup(groupId, newNumbers, options) {
|
addIdentifierToGroup(groupId, newIdentifiers, options) {
|
||||||
const proto = new textsecure.protobuf.DataMessage();
|
const proto = new textsecure.protobuf.DataMessage();
|
||||||
proto.group = new textsecure.protobuf.GroupContext();
|
proto.group = new textsecure.protobuf.GroupContext();
|
||||||
proto.group.id = stringToArrayBuffer(groupId);
|
proto.group.id = stringToArrayBuffer(groupId);
|
||||||
proto.group.type = textsecure.protobuf.GroupContext.Type.UPDATE;
|
proto.group.type = textsecure.protobuf.GroupContext.Type.UPDATE;
|
||||||
proto.group.members = newNumbers;
|
proto.group.members = newIdentifiers;
|
||||||
return this.sendGroupProto(newNumbers, proto, Date.now(), options);
|
return this.sendGroupProto(newIdentifiers, proto, Date.now(), options);
|
||||||
},
|
},
|
||||||
|
|
||||||
setGroupName(groupId, name, groupNumbers, options) {
|
setGroupName(groupId, name, groupIdentifiers, options) {
|
||||||
const proto = new textsecure.protobuf.DataMessage();
|
const proto = new textsecure.protobuf.DataMessage();
|
||||||
proto.group = new textsecure.protobuf.GroupContext();
|
proto.group = new textsecure.protobuf.GroupContext();
|
||||||
proto.group.id = stringToArrayBuffer(groupId);
|
proto.group.id = stringToArrayBuffer(groupId);
|
||||||
proto.group.type = textsecure.protobuf.GroupContext.Type.UPDATE;
|
proto.group.type = textsecure.protobuf.GroupContext.Type.UPDATE;
|
||||||
proto.group.name = name;
|
proto.group.name = name;
|
||||||
proto.group.members = groupNumbers;
|
proto.group.members = groupIdentifiers;
|
||||||
|
|
||||||
return this.sendGroupProto(groupNumbers, proto, Date.now(), options);
|
return this.sendGroupProto(groupIdentifiers, proto, Date.now(), options);
|
||||||
},
|
},
|
||||||
|
|
||||||
setGroupAvatar(groupId, avatar, groupNumbers, options) {
|
setGroupAvatar(groupId, avatar, groupIdentifiers, options) {
|
||||||
const proto = new textsecure.protobuf.DataMessage();
|
const proto = new textsecure.protobuf.DataMessage();
|
||||||
proto.group = new textsecure.protobuf.GroupContext();
|
proto.group = new textsecure.protobuf.GroupContext();
|
||||||
proto.group.id = stringToArrayBuffer(groupId);
|
proto.group.id = stringToArrayBuffer(groupId);
|
||||||
proto.group.type = textsecure.protobuf.GroupContext.Type.UPDATE;
|
proto.group.type = textsecure.protobuf.GroupContext.Type.UPDATE;
|
||||||
proto.group.members = groupNumbers;
|
proto.group.members = groupIdentifiers;
|
||||||
|
|
||||||
return this.makeAttachmentPointer(avatar).then(attachment => {
|
return this.makeAttachmentPointer(avatar).then(attachment => {
|
||||||
proto.group.avatar = attachment;
|
proto.group.avatar = attachment;
|
||||||
return this.sendGroupProto(groupNumbers, proto, Date.now(), options);
|
return this.sendGroupProto(groupIdentifiers, proto, Date.now(), options);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
leaveGroup(groupId, groupNumbers, options) {
|
leaveGroup(groupId, groupIdentifiers, options) {
|
||||||
const proto = new textsecure.protobuf.DataMessage();
|
const proto = new textsecure.protobuf.DataMessage();
|
||||||
proto.group = new textsecure.protobuf.GroupContext();
|
proto.group = new textsecure.protobuf.GroupContext();
|
||||||
proto.group.id = stringToArrayBuffer(groupId);
|
proto.group.id = stringToArrayBuffer(groupId);
|
||||||
proto.group.type = textsecure.protobuf.GroupContext.Type.QUIT;
|
proto.group.type = textsecure.protobuf.GroupContext.Type.QUIT;
|
||||||
return this.sendGroupProto(groupNumbers, proto, Date.now(), options);
|
return this.sendGroupProto(groupIdentifiers, proto, Date.now(), options);
|
||||||
},
|
},
|
||||||
async sendExpirationTimerUpdateToGroup(
|
async sendExpirationTimerUpdateToGroup(
|
||||||
groupId,
|
groupId,
|
||||||
groupNumbers,
|
groupIdentifiers,
|
||||||
expireTimer,
|
expireTimer,
|
||||||
timestamp,
|
timestamp,
|
||||||
profileKey,
|
profileKey,
|
||||||
options
|
options
|
||||||
) {
|
) {
|
||||||
const me = textsecure.storage.user.getNumber();
|
const myNumber = textsecure.storage.user.getNumber();
|
||||||
const numbers = groupNumbers.filter(number => number !== me);
|
const myUuid = textsecure.storage.user.getUuid();
|
||||||
|
const recipients = groupIdentifiers.filter(
|
||||||
|
identifier => identifier !== myNumber && identifier !== myUuid
|
||||||
|
);
|
||||||
const attrs = {
|
const attrs = {
|
||||||
recipients: numbers,
|
recipients,
|
||||||
timestamp,
|
timestamp,
|
||||||
expireTimer,
|
expireTimer,
|
||||||
profileKey,
|
profileKey,
|
||||||
|
@ -1205,10 +1273,10 @@ MessageSender.prototype = {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
if (numbers.length === 0) {
|
if (recipients.length === 0) {
|
||||||
return Promise.resolve({
|
return Promise.resolve({
|
||||||
successfulNumbers: [],
|
successfulIdentifiers: [],
|
||||||
failoverNumbers: [],
|
failoverIdentifiers: [],
|
||||||
errors: [],
|
errors: [],
|
||||||
unidentifiedDeliveries: [],
|
unidentifiedDeliveries: [],
|
||||||
dataMessage: await this.getMessageProtoObj(attrs),
|
dataMessage: await this.getMessageProtoObj(attrs),
|
||||||
|
@ -1217,8 +1285,8 @@ MessageSender.prototype = {
|
||||||
|
|
||||||
return this.sendMessage(attrs, options);
|
return this.sendMessage(attrs, options);
|
||||||
},
|
},
|
||||||
sendExpirationTimerUpdateToNumber(
|
sendExpirationTimerUpdateToIdentifier(
|
||||||
number,
|
identifier,
|
||||||
expireTimer,
|
expireTimer,
|
||||||
timestamp,
|
timestamp,
|
||||||
profileKey,
|
profileKey,
|
||||||
|
@ -1226,7 +1294,7 @@ MessageSender.prototype = {
|
||||||
) {
|
) {
|
||||||
return this.sendMessage(
|
return this.sendMessage(
|
||||||
{
|
{
|
||||||
recipients: [number],
|
recipients: [identifier],
|
||||||
timestamp,
|
timestamp,
|
||||||
expireTimer,
|
expireTimer,
|
||||||
profileKey,
|
profileKey,
|
||||||
|
@ -1245,7 +1313,7 @@ window.textsecure = window.textsecure || {};
|
||||||
textsecure.MessageSender = function MessageSenderWrapper(username, password) {
|
textsecure.MessageSender = function MessageSenderWrapper(username, password) {
|
||||||
const sender = new MessageSender(username, password);
|
const sender = new MessageSender(username, password);
|
||||||
|
|
||||||
this.sendExpirationTimerUpdateToNumber = sender.sendExpirationTimerUpdateToNumber.bind(
|
this.sendExpirationTimerUpdateToIdentifier = sender.sendExpirationTimerUpdateToIdentifier.bind(
|
||||||
sender
|
sender
|
||||||
);
|
);
|
||||||
this.sendExpirationTimerUpdateToGroup = sender.sendExpirationTimerUpdateToGroup.bind(
|
this.sendExpirationTimerUpdateToGroup = sender.sendExpirationTimerUpdateToGroup.bind(
|
||||||
|
@ -1264,14 +1332,14 @@ textsecure.MessageSender = function MessageSenderWrapper(username, password) {
|
||||||
sender
|
sender
|
||||||
);
|
);
|
||||||
|
|
||||||
this.sendMessageToNumber = sender.sendMessageToNumber.bind(sender);
|
this.sendMessageToIdentifier = sender.sendMessageToIdentifier.bind(sender);
|
||||||
this.sendMessage = sender.sendMessage.bind(sender);
|
this.sendMessage = sender.sendMessage.bind(sender);
|
||||||
this.resetSession = sender.resetSession.bind(sender);
|
this.resetSession = sender.resetSession.bind(sender);
|
||||||
this.sendMessageToGroup = sender.sendMessageToGroup.bind(sender);
|
this.sendMessageToGroup = sender.sendMessageToGroup.bind(sender);
|
||||||
this.sendTypingMessage = sender.sendTypingMessage.bind(sender);
|
this.sendTypingMessage = sender.sendTypingMessage.bind(sender);
|
||||||
this.createGroup = sender.createGroup.bind(sender);
|
this.createGroup = sender.createGroup.bind(sender);
|
||||||
this.updateGroup = sender.updateGroup.bind(sender);
|
this.updateGroup = sender.updateGroup.bind(sender);
|
||||||
this.addNumberToGroup = sender.addNumberToGroup.bind(sender);
|
this.addIdentifierToGroup = sender.addIdentifierToGroup.bind(sender);
|
||||||
this.setGroupName = sender.setGroupName.bind(sender);
|
this.setGroupName = sender.setGroupName.bind(sender);
|
||||||
this.setGroupAvatar = sender.setGroupAvatar.bind(sender);
|
this.setGroupAvatar = sender.setGroupAvatar.bind(sender);
|
||||||
this.leaveGroup = sender.leaveGroup.bind(sender);
|
this.leaveGroup = sender.leaveGroup.bind(sender);
|
||||||
|
|
|
@ -16,13 +16,33 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
setUuidAndDeviceId(uuid, deviceId) {
|
||||||
|
textsecure.storage.put('uuid_id', `${uuid}.${deviceId}`);
|
||||||
|
},
|
||||||
|
|
||||||
getNumber() {
|
getNumber() {
|
||||||
const numberId = textsecure.storage.get('number_id');
|
const numberId = textsecure.storage.get('number_id');
|
||||||
if (numberId === undefined) return undefined;
|
if (numberId === undefined) return undefined;
|
||||||
return textsecure.utils.unencodeNumber(numberId)[0];
|
return textsecure.utils.unencodeNumber(numberId)[0];
|
||||||
},
|
},
|
||||||
|
|
||||||
|
getUuid() {
|
||||||
|
const uuid = textsecure.storage.get('uuid_id');
|
||||||
|
if (uuid === undefined) return undefined;
|
||||||
|
return textsecure.utils.unencodeNumber(uuid)[0];
|
||||||
|
},
|
||||||
|
|
||||||
getDeviceId() {
|
getDeviceId() {
|
||||||
|
return this._getDeviceIdFromUuid() || this._getDeviceIdFromNumber();
|
||||||
|
},
|
||||||
|
|
||||||
|
_getDeviceIdFromUuid() {
|
||||||
|
const uuid = textsecure.storage.get('uuid_id');
|
||||||
|
if (uuid === undefined) return undefined;
|
||||||
|
return textsecure.utils.unencodeNumber(uuid)[1];
|
||||||
|
},
|
||||||
|
|
||||||
|
_getDeviceIdFromNumber() {
|
||||||
const numberId = textsecure.storage.get('number_id');
|
const numberId = textsecure.storage.get('number_id');
|
||||||
if (numberId === undefined) return undefined;
|
if (numberId === undefined) return undefined;
|
||||||
return textsecure.utils.unencodeNumber(numberId)[1];
|
return textsecure.utils.unencodeNumber(numberId)[1];
|
||||||
|
|
|
@ -13,6 +13,7 @@ describe('ContactBuffer', () => {
|
||||||
const contactInfo = new textsecure.protobuf.ContactDetails({
|
const contactInfo = new textsecure.protobuf.ContactDetails({
|
||||||
name: 'Zero Cool',
|
name: 'Zero Cool',
|
||||||
number: '+10000000000',
|
number: '+10000000000',
|
||||||
|
uuid: '7198E1BD-1293-452A-A098-F982FF201902',
|
||||||
avatar: { contentType: 'image/jpeg', length: avatarLen },
|
avatar: { contentType: 'image/jpeg', length: avatarLen },
|
||||||
});
|
});
|
||||||
const contactInfoBuffer = contactInfo.encode().toArrayBuffer();
|
const contactInfoBuffer = contactInfo.encode().toArrayBuffer();
|
||||||
|
@ -37,6 +38,7 @@ describe('ContactBuffer', () => {
|
||||||
count += 1;
|
count += 1;
|
||||||
assert.strictEqual(contact.name, 'Zero Cool');
|
assert.strictEqual(contact.name, 'Zero Cool');
|
||||||
assert.strictEqual(contact.number, '+10000000000');
|
assert.strictEqual(contact.number, '+10000000000');
|
||||||
|
assert.strictEqual(contact.uuid, '7198e1bd-1293-452a-a098-f982ff201902');
|
||||||
assert.strictEqual(contact.avatar.contentType, 'image/jpeg');
|
assert.strictEqual(contact.avatar.contentType, 'image/jpeg');
|
||||||
assert.strictEqual(contact.avatar.length, 255);
|
assert.strictEqual(contact.avatar.length, 255);
|
||||||
assert.strictEqual(contact.avatar.data.byteLength, 255);
|
assert.strictEqual(contact.avatar.data.byteLength, 255);
|
||||||
|
@ -63,7 +65,13 @@ describe('GroupBuffer', () => {
|
||||||
const groupInfo = new textsecure.protobuf.GroupDetails({
|
const groupInfo = new textsecure.protobuf.GroupDetails({
|
||||||
id: new Uint8Array([1, 3, 3, 7]).buffer,
|
id: new Uint8Array([1, 3, 3, 7]).buffer,
|
||||||
name: 'Hackers',
|
name: 'Hackers',
|
||||||
members: ['cereal', 'burn', 'phreak', 'joey'],
|
membersE164: ['cereal', 'burn', 'phreak', 'joey'],
|
||||||
|
members: [
|
||||||
|
{ uuid: '3EA23646-92E8-4604-8833-6388861971C1', e164: 'cereal' },
|
||||||
|
{ uuid: 'B8414169-7149-4736-8E3B-477191931301', e164: 'burn' },
|
||||||
|
{ uuid: '64C97B95-A782-4E1E-BBCC-5A4ACE8d71f6', e164: 'phreak' },
|
||||||
|
{ uuid: 'CA334652-C35B-4FDC-9CC7-5F2060C771EE', e164: 'joey' },
|
||||||
|
],
|
||||||
avatar: { contentType: 'image/jpeg', length: avatarLen },
|
avatar: { contentType: 'image/jpeg', length: avatarLen },
|
||||||
});
|
});
|
||||||
const groupInfoBuffer = groupInfo.encode().toArrayBuffer();
|
const groupInfoBuffer = groupInfo.encode().toArrayBuffer();
|
||||||
|
@ -91,7 +99,21 @@ describe('GroupBuffer', () => {
|
||||||
group.id.toArrayBuffer(),
|
group.id.toArrayBuffer(),
|
||||||
new Uint8Array([1, 3, 3, 7]).buffer
|
new Uint8Array([1, 3, 3, 7]).buffer
|
||||||
);
|
);
|
||||||
assert.sameMembers(group.members, ['cereal', 'burn', 'phreak', 'joey']);
|
assert.sameMembers(group.membersE164, [
|
||||||
|
'cereal',
|
||||||
|
'burn',
|
||||||
|
'phreak',
|
||||||
|
'joey',
|
||||||
|
]);
|
||||||
|
assert.sameDeepMembers(
|
||||||
|
group.members.map(({ uuid, e164 }) => ({ uuid, e164 })),
|
||||||
|
[
|
||||||
|
{ uuid: '3ea23646-92e8-4604-8833-6388861971c1', e164: 'cereal' },
|
||||||
|
{ uuid: 'b8414169-7149-4736-8e3b-477191931301', e164: 'burn' },
|
||||||
|
{ uuid: '64c97b95-a782-4e1e-bbcc-5a4ace8d71f6', e164: 'phreak' },
|
||||||
|
{ uuid: 'ca334652-c35b-4fdc-9cc7-5f2060c771ee', e164: 'joey' },
|
||||||
|
]
|
||||||
|
);
|
||||||
assert.strictEqual(group.avatar.contentType, 'image/jpeg');
|
assert.strictEqual(group.avatar.contentType, 'image/jpeg');
|
||||||
assert.strictEqual(group.avatar.length, 255);
|
assert.strictEqual(group.avatar.length, 255);
|
||||||
assert.strictEqual(group.avatar.data.byteLength, 255);
|
assert.strictEqual(group.avatar.data.byteLength, 255);
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
window.setImmediate = window.nodeSetImmediate;
|
window.setImmediate = window.nodeSetImmediate;
|
||||||
|
|
||||||
const getKeysForNumberMap = {};
|
const getKeysForIdentifierMap = {};
|
||||||
const messagesSentMap = {};
|
const messagesSentMap = {};
|
||||||
|
|
||||||
const fakeCall = () => Promise.resolve();
|
const fakeCall = () => Promise.resolve();
|
||||||
|
@ -10,7 +10,7 @@ const fakeAPI = {
|
||||||
getAttachment: fakeCall,
|
getAttachment: fakeCall,
|
||||||
getAvatar: fakeCall,
|
getAvatar: fakeCall,
|
||||||
getDevices: fakeCall,
|
getDevices: fakeCall,
|
||||||
// getKeysForNumber: fakeCall,
|
// getKeysForIdentifier : fakeCall,
|
||||||
getMessageSocket: fakeCall,
|
getMessageSocket: fakeCall,
|
||||||
getMyKeys: fakeCall,
|
getMyKeys: fakeCall,
|
||||||
getProfile: fakeCall,
|
getProfile: fakeCall,
|
||||||
|
@ -22,13 +22,13 @@ const fakeAPI = {
|
||||||
// sendMessages: fakeCall,
|
// sendMessages: fakeCall,
|
||||||
setSignedPreKey: fakeCall,
|
setSignedPreKey: fakeCall,
|
||||||
|
|
||||||
getKeysForNumber(number) {
|
getKeysForIdentifier(number) {
|
||||||
const res = getKeysForNumberMap[number];
|
const res = getKeysForIdentifierMap[number];
|
||||||
if (res !== undefined) {
|
if (res !== undefined) {
|
||||||
delete getKeysForNumberMap[number];
|
delete getKeysForIdentifierMap[number];
|
||||||
return Promise.resolve(res);
|
return Promise.resolve(res);
|
||||||
}
|
}
|
||||||
throw new Error('getKeysForNumber of unknown/used number');
|
throw new Error('getKeysForIdentfier of unknown/used number');
|
||||||
},
|
},
|
||||||
|
|
||||||
sendMessages(destination, messageArray) {
|
sendMessages(destination, messageArray) {
|
||||||
|
|
|
@ -4,6 +4,12 @@ function SignalProtocolStore() {
|
||||||
|
|
||||||
SignalProtocolStore.prototype = {
|
SignalProtocolStore.prototype = {
|
||||||
Direction: { SENDING: 1, RECEIVING: 2 },
|
Direction: { SENDING: 1, RECEIVING: 2 },
|
||||||
|
VerifiedStatus: {
|
||||||
|
DEFAULT: 0,
|
||||||
|
VERIFIED: 1,
|
||||||
|
UNVERIFIED: 2,
|
||||||
|
},
|
||||||
|
|
||||||
getIdentityKeyPair() {
|
getIdentityKeyPair() {
|
||||||
return Promise.resolve(this.get('identityKey'));
|
return Promise.resolve(this.get('identityKey'));
|
||||||
},
|
},
|
||||||
|
|
|
@ -23,7 +23,6 @@
|
||||||
<script type="text/javascript" src="../protobufs.js" data-cover></script>
|
<script type="text/javascript" src="../protobufs.js" data-cover></script>
|
||||||
<script type="text/javascript" src="../errors.js" data-cover></script>
|
<script type="text/javascript" src="../errors.js" data-cover></script>
|
||||||
<script type="text/javascript" src="../storage.js" data-cover></script>
|
<script type="text/javascript" src="../storage.js" data-cover></script>
|
||||||
<script type="text/javascript" src="../protocol_wrapper.js" data-cover></script>
|
|
||||||
|
|
||||||
<script type="text/javascript" src="../event_target.js" data-cover></script>
|
<script type="text/javascript" src="../event_target.js" data-cover></script>
|
||||||
<script type="text/javascript" src="../websocket-resources.js" data-cover></script>
|
<script type="text/javascript" src="../websocket-resources.js" data-cover></script>
|
||||||
|
@ -34,6 +33,16 @@
|
||||||
<script type="text/javascript" src="../account_manager.js" data-cover></script>
|
<script type="text/javascript" src="../account_manager.js" data-cover></script>
|
||||||
<script type="text/javascript" src="../contacts_parser.js" data-cover></script>
|
<script type="text/javascript" src="../contacts_parser.js" data-cover></script>
|
||||||
<script type="text/javascript" src="../task_with_timeout.js" data-cover></script>
|
<script type="text/javascript" src="../task_with_timeout.js" data-cover></script>
|
||||||
|
<script type="text/javascript" src="../storage/user.js" data-cover></script>
|
||||||
|
|
||||||
|
<script type="text/javascript" src="../protocol_wrapper.js" data-cover></script>
|
||||||
|
<script type="text/javascript" src="../../js/libphonenumber-util.js"></script>
|
||||||
|
<script type="text/javascript" src="../../js/components.js" data-cover></script>
|
||||||
|
<script type="text/javascript" src="../../js/signal_protocol_store.js" data-cover></script>
|
||||||
|
<script type="text/javascript" src="../../js/storage.js" data-cover></script>
|
||||||
|
<script type="text/javascript" src="../../js/models/messages.js" data-cover></script>
|
||||||
|
<script type="text/javascript" src="../../js/models/conversations.js" data-cover></script>
|
||||||
|
<script type="text/javascript" src="../../js/conversation_controller.js" data-cover></script>
|
||||||
|
|
||||||
<script type="text/javascript" src="errors_test.js"></script>
|
<script type="text/javascript" src="errors_test.js"></script>
|
||||||
<script type="text/javascript" src="helpers_test.js"></script>
|
<script type="text/javascript" src="helpers_test.js"></script>
|
||||||
|
|
|
@ -4,12 +4,14 @@ describe('MessageReceiver', () => {
|
||||||
textsecure.storage.impl = new SignalProtocolStore();
|
textsecure.storage.impl = new SignalProtocolStore();
|
||||||
const { WebSocket } = window;
|
const { WebSocket } = window;
|
||||||
const number = '+19999999999';
|
const number = '+19999999999';
|
||||||
|
const uuid = 'AAAAAAAA-BBBB-4CCC-9DDD-EEEEEEEEEEEE';
|
||||||
const deviceId = 1;
|
const deviceId = 1;
|
||||||
const signalingKey = libsignal.crypto.getRandomBytes(32 + 20);
|
const signalingKey = libsignal.crypto.getRandomBytes(32 + 20);
|
||||||
|
|
||||||
before(() => {
|
before(() => {
|
||||||
window.WebSocket = MockSocket;
|
window.WebSocket = MockSocket;
|
||||||
textsecure.storage.user.setNumberAndDeviceId(number, deviceId, 'name');
|
textsecure.storage.user.setNumberAndDeviceId(number, deviceId, 'name');
|
||||||
|
textsecure.storage.user.setUuidAndDeviceId(uuid, deviceId);
|
||||||
textsecure.storage.put('password', 'password');
|
textsecure.storage.put('password', 'password');
|
||||||
textsecure.storage.put('signaling_key', signalingKey);
|
textsecure.storage.put('signaling_key', signalingKey);
|
||||||
});
|
});
|
||||||
|
@ -21,6 +23,7 @@ describe('MessageReceiver', () => {
|
||||||
const attrs = {
|
const attrs = {
|
||||||
type: textsecure.protobuf.Envelope.Type.CIPHERTEXT,
|
type: textsecure.protobuf.Envelope.Type.CIPHERTEXT,
|
||||||
source: number,
|
source: number,
|
||||||
|
sourceUuid: uuid,
|
||||||
sourceDevice: deviceId,
|
sourceDevice: deviceId,
|
||||||
timestamp: Date.now(),
|
timestamp: Date.now(),
|
||||||
};
|
};
|
||||||
|
@ -72,7 +75,7 @@ describe('MessageReceiver', () => {
|
||||||
it('connects', done => {
|
it('connects', done => {
|
||||||
const mockServer = new MockServer(
|
const mockServer = new MockServer(
|
||||||
`ws://localhost:8080/v1/websocket/?login=${encodeURIComponent(
|
`ws://localhost:8080/v1/websocket/?login=${encodeURIComponent(
|
||||||
number
|
uuid
|
||||||
)}.1&password=password`
|
)}.1&password=password`
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -1,9 +1,7 @@
|
||||||
/* global libsignal, textsecure */
|
/* global libsignal, textsecure, storage, ConversationController */
|
||||||
|
|
||||||
describe('SignalProtocolStore', () => {
|
describe('SignalProtocolStore', () => {
|
||||||
before(() => {
|
// debugger;
|
||||||
localStorage.clear();
|
|
||||||
});
|
|
||||||
const store = textsecure.storage.protocol;
|
const store = textsecure.storage.protocol;
|
||||||
const identifier = '+5558675309';
|
const identifier = '+5558675309';
|
||||||
const identityKey = {
|
const identityKey = {
|
||||||
|
@ -14,6 +12,14 @@ describe('SignalProtocolStore', () => {
|
||||||
pubKey: libsignal.crypto.getRandomBytes(33),
|
pubKey: libsignal.crypto.getRandomBytes(33),
|
||||||
privKey: libsignal.crypto.getRandomBytes(32),
|
privKey: libsignal.crypto.getRandomBytes(32),
|
||||||
};
|
};
|
||||||
|
before(async () => {
|
||||||
|
localStorage.clear();
|
||||||
|
ConversationController.reset();
|
||||||
|
// store.hydrateCaches();
|
||||||
|
await storage.fetch();
|
||||||
|
await ConversationController.load();
|
||||||
|
await ConversationController.getOrCreateAndWait(identifier, 'private');
|
||||||
|
});
|
||||||
it('retrieves my registration id', async () => {
|
it('retrieves my registration id', async () => {
|
||||||
store.put('registrationId', 1337);
|
store.put('registrationId', 1337);
|
||||||
|
|
||||||
|
|
3
main.js
3
main.js
|
@ -68,7 +68,8 @@ const userConfig = require('./app/user_config');
|
||||||
const importMode =
|
const importMode =
|
||||||
process.argv.some(arg => arg === '--import') || config.get('import');
|
process.argv.some(arg => arg === '--import') || config.get('import');
|
||||||
|
|
||||||
const development = config.environment === 'development';
|
const development =
|
||||||
|
config.environment === 'development' || config.environment === 'staging';
|
||||||
|
|
||||||
// We generally want to pull in our own modules after this point, after the user
|
// We generally want to pull in our own modules after this point, after the user
|
||||||
// data directory has been set.
|
// data directory has been set.
|
||||||
|
|
25
preload.js
25
preload.js
|
@ -6,6 +6,7 @@ try {
|
||||||
const electron = require('electron');
|
const electron = require('electron');
|
||||||
const semver = require('semver');
|
const semver = require('semver');
|
||||||
const curve = require('curve25519-n');
|
const curve = require('curve25519-n');
|
||||||
|
const _ = require('lodash');
|
||||||
const { installGetter, installSetter } = require('./preload_utils');
|
const { installGetter, installSetter } = require('./preload_utils');
|
||||||
|
|
||||||
const { deferredToPromise } = require('./js/modules/deferred_to_promise');
|
const { deferredToPromise } = require('./js/modules/deferred_to_promise');
|
||||||
|
@ -254,6 +255,30 @@ try {
|
||||||
window.loadImage = require('blueimp-load-image');
|
window.loadImage = require('blueimp-load-image');
|
||||||
window.getGuid = require('uuid/v4');
|
window.getGuid = require('uuid/v4');
|
||||||
|
|
||||||
|
window.isValidGuid = maybeGuid =>
|
||||||
|
/^[0-9A-F]{8}-[0-9A-F]{4}-4[0-9A-F]{3}-[89AB][0-9A-F]{3}-[0-9A-F]{12}$/i.test(
|
||||||
|
maybeGuid
|
||||||
|
);
|
||||||
|
// https://stackoverflow.com/a/23299989
|
||||||
|
window.isValidE164 = maybeE164 => /^\+?[1-9]\d{1,14}$/.test(maybeE164);
|
||||||
|
|
||||||
|
window.normalizeUuids = (obj, paths, context) => {
|
||||||
|
if (!obj) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
paths.forEach(path => {
|
||||||
|
const val = _.get(obj, path);
|
||||||
|
if (val) {
|
||||||
|
if (!window.isValidGuid(val)) {
|
||||||
|
window.log.warn(
|
||||||
|
`Normalizing invalid uuid: ${val} at path ${path} in context "${context}"`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
_.set(obj, path, val.toLowerCase());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
window.React = require('react');
|
window.React = require('react');
|
||||||
window.ReactDOM = require('react-dom');
|
window.ReactDOM = require('react-dom');
|
||||||
window.moment = require('moment');
|
window.moment = require('moment');
|
||||||
|
|
|
@ -11,10 +11,20 @@ message ProvisionEnvelope {
|
||||||
}
|
}
|
||||||
|
|
||||||
message ProvisionMessage {
|
message ProvisionMessage {
|
||||||
optional bytes identityKeyPrivate = 2;
|
optional bytes identityKeyPrivate = 2;
|
||||||
optional string number = 3;
|
optional string number = 3;
|
||||||
optional string provisioningCode = 4;
|
optional string uuid = 8;
|
||||||
optional string userAgent = 5;
|
optional string provisioningCode = 4;
|
||||||
optional bytes profileKey = 6;
|
optional string userAgent = 5;
|
||||||
optional bool readReceipts = 7;
|
optional bytes profileKey = 6;
|
||||||
|
optional bool readReceipts = 7;
|
||||||
|
optional uint32 ProvisioningVersion = 9;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum ProvisioningVersion {
|
||||||
|
option allow_alias = true;
|
||||||
|
|
||||||
|
INITIAL = 0;
|
||||||
|
TABLET_SUPPORT = 1;
|
||||||
|
CURRENT = 1;
|
||||||
|
}
|
|
@ -16,6 +16,7 @@ message Envelope {
|
||||||
|
|
||||||
optional Type type = 1;
|
optional Type type = 1;
|
||||||
optional string source = 2;
|
optional string source = 2;
|
||||||
|
optional string sourceUuid = 11;
|
||||||
optional uint32 sourceDevice = 7;
|
optional uint32 sourceDevice = 7;
|
||||||
optional string relay = 3;
|
optional string relay = 3;
|
||||||
optional uint64 timestamp = 5;
|
optional uint64 timestamp = 5;
|
||||||
|
@ -85,6 +86,7 @@ message DataMessage {
|
||||||
|
|
||||||
optional uint64 id = 1;
|
optional uint64 id = 1;
|
||||||
optional string author = 2;
|
optional string author = 2;
|
||||||
|
optional string authorUuid = 5;
|
||||||
optional string text = 3;
|
optional string text = 3;
|
||||||
repeated QuotedAttachment attachments = 4;
|
repeated QuotedAttachment attachments = 4;
|
||||||
}
|
}
|
||||||
|
@ -236,20 +238,23 @@ message Verified {
|
||||||
UNVERIFIED = 2;
|
UNVERIFIED = 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
optional string destination = 1;
|
optional string destination = 1;
|
||||||
optional bytes identityKey = 2;
|
optional string destinationUuid = 5;
|
||||||
optional State state = 3;
|
optional bytes identityKey = 2;
|
||||||
optional bytes nullMessage = 4;
|
optional State state = 3;
|
||||||
|
optional bytes nullMessage = 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
message SyncMessage {
|
message SyncMessage {
|
||||||
message Sent {
|
message Sent {
|
||||||
message UnidentifiedDeliveryStatus {
|
message UnidentifiedDeliveryStatus {
|
||||||
optional string destination = 1;
|
optional string destination = 1;
|
||||||
optional bool unidentified = 2;
|
optional string destinationUuid = 3;
|
||||||
|
optional bool unidentified = 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
optional string destination = 1;
|
optional string destination = 1;
|
||||||
|
optional string destinationUuid = 7;
|
||||||
optional uint64 timestamp = 2;
|
optional uint64 timestamp = 2;
|
||||||
optional DataMessage message = 3;
|
optional DataMessage message = 3;
|
||||||
optional uint64 expirationStartTimestamp = 4;
|
optional uint64 expirationStartTimestamp = 4;
|
||||||
|
@ -268,6 +273,7 @@ message SyncMessage {
|
||||||
|
|
||||||
message Blocked {
|
message Blocked {
|
||||||
repeated string numbers = 1;
|
repeated string numbers = 1;
|
||||||
|
repeated string uuids = 3;
|
||||||
repeated bytes groupIds = 2;
|
repeated bytes groupIds = 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -284,8 +290,9 @@ message SyncMessage {
|
||||||
}
|
}
|
||||||
|
|
||||||
message Read {
|
message Read {
|
||||||
optional string sender = 1;
|
optional string sender = 1;
|
||||||
optional uint64 timestamp = 2;
|
optional string senderUuid = 3;
|
||||||
|
optional uint64 timestamp = 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
message Configuration {
|
message Configuration {
|
||||||
|
@ -306,8 +313,9 @@ message SyncMessage {
|
||||||
}
|
}
|
||||||
|
|
||||||
message ViewOnceOpen {
|
message ViewOnceOpen {
|
||||||
optional string sender = 1;
|
optional string sender = 1;
|
||||||
optional uint64 timestamp = 2;
|
optional string senderUuid = 3;
|
||||||
|
optional uint64 timestamp = 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
optional Sent sent = 1;
|
optional Sent sent = 1;
|
||||||
|
@ -349,11 +357,18 @@ message GroupContext {
|
||||||
QUIT = 3;
|
QUIT = 3;
|
||||||
REQUEST_INFO = 4;
|
REQUEST_INFO = 4;
|
||||||
}
|
}
|
||||||
optional bytes id = 1;
|
|
||||||
optional Type type = 2;
|
message Member {
|
||||||
optional string name = 3;
|
optional string uuid = 1;
|
||||||
repeated string members = 4;
|
optional string e164 = 2;
|
||||||
optional AttachmentPointer avatar = 5;
|
}
|
||||||
|
|
||||||
|
optional bytes id = 1;
|
||||||
|
optional Type type = 2;
|
||||||
|
optional string name = 3;
|
||||||
|
repeated string membersE164 = 4;
|
||||||
|
repeated Member members = 6;
|
||||||
|
optional AttachmentPointer avatar = 5;
|
||||||
}
|
}
|
||||||
|
|
||||||
message ContactDetails {
|
message ContactDetails {
|
||||||
|
@ -363,6 +378,7 @@ message ContactDetails {
|
||||||
}
|
}
|
||||||
|
|
||||||
optional string number = 1;
|
optional string number = 1;
|
||||||
|
optional string uuid = 9;
|
||||||
optional string name = 2;
|
optional string name = 2;
|
||||||
optional Avatar avatar = 3;
|
optional Avatar avatar = 3;
|
||||||
optional string color = 4;
|
optional string color = 4;
|
||||||
|
@ -378,9 +394,15 @@ message GroupDetails {
|
||||||
optional uint32 length = 2;
|
optional uint32 length = 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
message Member {
|
||||||
|
optional string uuid = 1;
|
||||||
|
optional string e164 = 2;
|
||||||
|
}
|
||||||
|
|
||||||
optional bytes id = 1;
|
optional bytes id = 1;
|
||||||
optional string name = 2;
|
optional string name = 2;
|
||||||
repeated string members = 3;
|
repeated string membersE164 = 3;
|
||||||
|
repeated Member members = 9;
|
||||||
optional Avatar avatar = 4;
|
optional Avatar avatar = 4;
|
||||||
optional bool active = 5 [default = true];
|
optional bool active = 5 [default = true];
|
||||||
optional uint32 expireTimer = 6;
|
optional uint32 expireTimer = 6;
|
||||||
|
|
|
@ -15,11 +15,12 @@ message ServerCertificate {
|
||||||
|
|
||||||
message SenderCertificate {
|
message SenderCertificate {
|
||||||
message Certificate {
|
message Certificate {
|
||||||
optional string sender = 1;
|
optional string sender = 1;
|
||||||
optional uint32 senderDevice = 2;
|
optional string senderUuid = 6;
|
||||||
optional fixed64 expires = 3;
|
optional uint32 senderDevice = 2;
|
||||||
optional bytes identityKey = 4;
|
optional fixed64 expires = 3;
|
||||||
optional ServerCertificate signer = 5;
|
optional bytes identityKey = 4;
|
||||||
|
optional ServerCertificate signer = 5;
|
||||||
}
|
}
|
||||||
|
|
||||||
optional bytes certificate = 1;
|
optional bytes certificate = 1;
|
||||||
|
|
|
@ -69,10 +69,11 @@ window.encryptAndUpload = async (
|
||||||
cover,
|
cover,
|
||||||
onProgress = noop
|
onProgress = noop
|
||||||
) => {
|
) => {
|
||||||
const usernameItem = await window.Signal.Data.getItemById('number_id');
|
const usernameItem = await window.Signal.Data.getItemById('uuid_id');
|
||||||
|
const oldUsernameItem = await window.Signal.Data.getItemById('number_id');
|
||||||
const passwordItem = await window.Signal.Data.getItemById('password');
|
const passwordItem = await window.Signal.Data.getItemById('password');
|
||||||
|
|
||||||
if (!usernameItem || !passwordItem) {
|
if (!oldUsernameItem || !passwordItem) {
|
||||||
const { message } = window.localeMessages[
|
const { message } = window.localeMessages[
|
||||||
'StickerCreator--Authentication--error'
|
'StickerCreator--Authentication--error'
|
||||||
];
|
];
|
||||||
|
@ -86,13 +87,17 @@ window.encryptAndUpload = async (
|
||||||
}
|
}
|
||||||
|
|
||||||
const { value: username } = usernameItem;
|
const { value: username } = usernameItem;
|
||||||
|
const { value: oldUsername } = oldUsernameItem;
|
||||||
const { value: password } = passwordItem;
|
const { value: password } = passwordItem;
|
||||||
|
|
||||||
const packKey = window.libsignal.crypto.getRandomBytes(32);
|
const packKey = window.libsignal.crypto.getRandomBytes(32);
|
||||||
const encryptionKey = await deriveStickerPackKey(packKey);
|
const encryptionKey = await deriveStickerPackKey(packKey);
|
||||||
const iv = window.libsignal.crypto.getRandomBytes(16);
|
const iv = window.libsignal.crypto.getRandomBytes(16);
|
||||||
|
|
||||||
const server = WebAPI.connect({ username, password });
|
const server = WebAPI.connect({
|
||||||
|
username: username || oldUsername,
|
||||||
|
password,
|
||||||
|
});
|
||||||
|
|
||||||
const uniqueStickers = uniqBy([...stickers, { webp: cover }], 'webp');
|
const uniqueStickers = uniqBy([...stickers, { webp: cover }], 'webp');
|
||||||
|
|
||||||
|
|
|
@ -261,6 +261,8 @@ describe('Backup', () => {
|
||||||
const CONTACT_ONE_NUMBER = '+12025550001';
|
const CONTACT_ONE_NUMBER = '+12025550001';
|
||||||
const CONTACT_TWO_NUMBER = '+12025550002';
|
const CONTACT_TWO_NUMBER = '+12025550002';
|
||||||
|
|
||||||
|
const CONVERSATION_ID = 'bdaa7f4f-e9bd-493e-ab0d-8331ad604269';
|
||||||
|
|
||||||
const toArrayBuffer = nodeBuffer =>
|
const toArrayBuffer = nodeBuffer =>
|
||||||
nodeBuffer.buffer.slice(
|
nodeBuffer.buffer.slice(
|
||||||
nodeBuffer.byteOffset,
|
nodeBuffer.byteOffset,
|
||||||
|
@ -405,7 +407,7 @@ describe('Backup', () => {
|
||||||
const CONVERSATION_COUNT = 1;
|
const CONVERSATION_COUNT = 1;
|
||||||
|
|
||||||
const messageWithAttachments = {
|
const messageWithAttachments = {
|
||||||
conversationId: CONTACT_ONE_NUMBER,
|
conversationId: CONVERSATION_ID,
|
||||||
body: 'Totally!',
|
body: 'Totally!',
|
||||||
source: OUR_NUMBER,
|
source: OUR_NUMBER,
|
||||||
received_at: 1524185933350,
|
received_at: 1524185933350,
|
||||||
|
@ -493,7 +495,7 @@ describe('Backup', () => {
|
||||||
active_at: 1524185933350,
|
active_at: 1524185933350,
|
||||||
color: 'orange',
|
color: 'orange',
|
||||||
expireTimer: 0,
|
expireTimer: 0,
|
||||||
id: CONTACT_ONE_NUMBER,
|
id: CONVERSATION_ID,
|
||||||
name: 'Someone Somewhere',
|
name: 'Someone Somewhere',
|
||||||
profileAvatar: {
|
profileAvatar: {
|
||||||
contentType: 'image/jpeg',
|
contentType: 'image/jpeg',
|
||||||
|
|
|
@ -21,6 +21,20 @@ describe('Privacy', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('redactUuids', () => {
|
||||||
|
it('should redact all uuids', () => {
|
||||||
|
const text =
|
||||||
|
'This is a log line with a uuid 9e420799-acdf-4bf4-8dee-353d7e2096b4\n' +
|
||||||
|
'and another one IN ALL UPPERCASE 340727FB-E43A-413B-941B-AADA033B6CA3';
|
||||||
|
|
||||||
|
const actual = Privacy.redactUuids(text);
|
||||||
|
const expected =
|
||||||
|
'This is a log line with a uuid [REDACTED]b4\n' +
|
||||||
|
'and another one IN ALL UPPERCASE [REDACTED]A3';
|
||||||
|
assert.equal(actual, expected);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('redactGroupIds', () => {
|
describe('redactGroupIds', () => {
|
||||||
it('should redact all group IDs', () => {
|
it('should redact all group IDs', () => {
|
||||||
const text =
|
const text =
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
/* global _, textsecure, libsignal, storage */
|
/* global _, textsecure, libsignal, storage, ConversationController */
|
||||||
|
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
|
@ -8,7 +8,7 @@ describe('SignalProtocolStore', () => {
|
||||||
let identityKey;
|
let identityKey;
|
||||||
let testKey;
|
let testKey;
|
||||||
|
|
||||||
before(done => {
|
before(async () => {
|
||||||
store = textsecure.storage.protocol;
|
store = textsecure.storage.protocol;
|
||||||
store.hydrateCaches();
|
store.hydrateCaches();
|
||||||
identityKey = {
|
identityKey = {
|
||||||
|
@ -22,7 +22,10 @@ describe('SignalProtocolStore', () => {
|
||||||
|
|
||||||
storage.put('registrationId', 1337);
|
storage.put('registrationId', 1337);
|
||||||
storage.put('identityKey', identityKey);
|
storage.put('identityKey', identityKey);
|
||||||
storage.fetch().then(done, done);
|
await storage.fetch();
|
||||||
|
ConversationController.reset();
|
||||||
|
await ConversationController.load();
|
||||||
|
await ConversationController.getOrCreateAndWait(number, 'private');
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('getLocalRegistrationId', () => {
|
describe('getLocalRegistrationId', () => {
|
||||||
|
|
|
@ -16,6 +16,8 @@ export interface PropsType {
|
||||||
startSearchCounter: number;
|
startSearchCounter: number;
|
||||||
|
|
||||||
// To be used as an ID
|
// To be used as an ID
|
||||||
|
ourConversationId: string;
|
||||||
|
ourUuid: string;
|
||||||
ourNumber: string;
|
ourNumber: string;
|
||||||
regionCode: string;
|
regionCode: string;
|
||||||
|
|
||||||
|
@ -40,7 +42,9 @@ export interface PropsType {
|
||||||
searchDiscussions: (
|
searchDiscussions: (
|
||||||
query: string,
|
query: string,
|
||||||
options: {
|
options: {
|
||||||
|
ourConversationId: string;
|
||||||
ourNumber: string;
|
ourNumber: string;
|
||||||
|
ourUuid: string;
|
||||||
noteToSelf: string;
|
noteToSelf: string;
|
||||||
}
|
}
|
||||||
) => void;
|
) => void;
|
||||||
|
@ -147,7 +151,9 @@ export class MainHeader extends React.Component<PropsType, StateType> {
|
||||||
public search = debounce((searchTerm: string) => {
|
public search = debounce((searchTerm: string) => {
|
||||||
const {
|
const {
|
||||||
i18n,
|
i18n,
|
||||||
|
ourConversationId,
|
||||||
ourNumber,
|
ourNumber,
|
||||||
|
ourUuid,
|
||||||
regionCode,
|
regionCode,
|
||||||
searchDiscussions,
|
searchDiscussions,
|
||||||
searchMessages,
|
searchMessages,
|
||||||
|
@ -157,7 +163,9 @@ export class MainHeader extends React.Component<PropsType, StateType> {
|
||||||
if (searchDiscussions && !searchConversationId) {
|
if (searchDiscussions && !searchConversationId) {
|
||||||
searchDiscussions(searchTerm, {
|
searchDiscussions(searchTerm, {
|
||||||
noteToSelf: i18n('noteToSelf').toLowerCase(),
|
noteToSelf: i18n('noteToSelf').toLowerCase(),
|
||||||
|
ourConversationId,
|
||||||
ourNumber,
|
ourNumber,
|
||||||
|
ourUuid,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -148,7 +148,7 @@ function searchMessages(
|
||||||
function searchDiscussions(
|
function searchDiscussions(
|
||||||
query: string,
|
query: string,
|
||||||
options: {
|
options: {
|
||||||
ourNumber: string;
|
ourConversationId: string;
|
||||||
noteToSelf: string;
|
noteToSelf: string;
|
||||||
}
|
}
|
||||||
): SearchDiscussionsResultsKickoffActionType {
|
): SearchDiscussionsResultsKickoffActionType {
|
||||||
|
@ -180,15 +180,15 @@ async function doSearchMessages(
|
||||||
async function doSearchDiscussions(
|
async function doSearchDiscussions(
|
||||||
query: string,
|
query: string,
|
||||||
options: {
|
options: {
|
||||||
ourNumber: string;
|
ourConversationId: string;
|
||||||
noteToSelf: string;
|
noteToSelf: string;
|
||||||
}
|
}
|
||||||
): Promise<SearchDiscussionsResultsPayloadType> {
|
): Promise<SearchDiscussionsResultsPayloadType> {
|
||||||
const { ourNumber, noteToSelf } = options;
|
const { ourConversationId, noteToSelf } = options;
|
||||||
const { conversations, contacts } = await queryConversationsAndContacts(
|
const { conversations, contacts } = await queryConversationsAndContacts(
|
||||||
query,
|
query,
|
||||||
{
|
{
|
||||||
ourNumber,
|
ourConversationId,
|
||||||
noteToSelf,
|
noteToSelf,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
@ -271,9 +271,12 @@ async function queryMessages(query: string, searchConversationId?: string) {
|
||||||
|
|
||||||
async function queryConversationsAndContacts(
|
async function queryConversationsAndContacts(
|
||||||
providedQuery: string,
|
providedQuery: string,
|
||||||
options: { ourNumber: string; noteToSelf: string }
|
options: {
|
||||||
|
ourConversationId: string;
|
||||||
|
noteToSelf: string;
|
||||||
|
}
|
||||||
) {
|
) {
|
||||||
const { ourNumber, noteToSelf } = options;
|
const { ourConversationId, noteToSelf } = options;
|
||||||
const query = providedQuery.replace(/[+-.()]*/g, '');
|
const query = providedQuery.replace(/[+-.()]*/g, '');
|
||||||
|
|
||||||
const searchResults: Array<DBConversationType> = await dataSearchConversations(
|
const searchResults: Array<DBConversationType> = await dataSearchConversations(
|
||||||
|
@ -294,13 +297,20 @@ async function queryConversationsAndContacts(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// // @ts-ignore
|
||||||
|
// console._log(
|
||||||
|
// '%cqueryConversationsAndContacts',
|
||||||
|
// 'background:black;color:red;',
|
||||||
|
// { searchResults, conversations, ourNumber, ourUuid }
|
||||||
|
// );
|
||||||
|
|
||||||
// Inject synthetic Note to Self entry if query matches localized 'Note to Self'
|
// Inject synthetic Note to Self entry if query matches localized 'Note to Self'
|
||||||
if (noteToSelf.indexOf(providedQuery.toLowerCase()) !== -1) {
|
if (noteToSelf.indexOf(providedQuery.toLowerCase()) !== -1) {
|
||||||
// ensure that we don't have duplicates in our results
|
// ensure that we don't have duplicates in our results
|
||||||
contacts = contacts.filter(id => id !== ourNumber);
|
contacts = contacts.filter(id => id !== ourConversationId);
|
||||||
conversations = conversations.filter(id => id !== ourNumber);
|
conversations = conversations.filter(id => id !== ourConversationId);
|
||||||
|
|
||||||
contacts.unshift(ourNumber);
|
contacts.unshift(ourConversationId);
|
||||||
}
|
}
|
||||||
|
|
||||||
return { conversations, contacts };
|
return { conversations, contacts };
|
||||||
|
|
|
@ -6,6 +6,8 @@ export type UserStateType = {
|
||||||
attachmentsPath: string;
|
attachmentsPath: string;
|
||||||
stickersPath: string;
|
stickersPath: string;
|
||||||
tempPath: string;
|
tempPath: string;
|
||||||
|
ourConversationId: string;
|
||||||
|
ourUuid: string;
|
||||||
ourNumber: string;
|
ourNumber: string;
|
||||||
platform: string;
|
platform: string;
|
||||||
regionCode: string;
|
regionCode: string;
|
||||||
|
@ -18,6 +20,8 @@ export type UserStateType = {
|
||||||
type UserChangedActionType = {
|
type UserChangedActionType = {
|
||||||
type: 'USER_CHANGED';
|
type: 'USER_CHANGED';
|
||||||
payload: {
|
payload: {
|
||||||
|
ourConversationId?: string;
|
||||||
|
ourUuid?: string;
|
||||||
ourNumber?: string;
|
ourNumber?: string;
|
||||||
regionCode?: string;
|
regionCode?: string;
|
||||||
interactionMode?: 'mouse' | 'keyboard';
|
interactionMode?: 'mouse' | 'keyboard';
|
||||||
|
@ -34,7 +38,9 @@ export const actions = {
|
||||||
|
|
||||||
function userChanged(attributes: {
|
function userChanged(attributes: {
|
||||||
interactionMode?: 'mouse' | 'keyboard';
|
interactionMode?: 'mouse' | 'keyboard';
|
||||||
|
ourConversationId: string;
|
||||||
ourNumber: string;
|
ourNumber: string;
|
||||||
|
ourUuid: string;
|
||||||
regionCode: string;
|
regionCode: string;
|
||||||
}): UserChangedActionType {
|
}): UserChangedActionType {
|
||||||
return {
|
return {
|
||||||
|
@ -50,6 +56,8 @@ function getEmptyState(): UserStateType {
|
||||||
attachmentsPath: 'missing',
|
attachmentsPath: 'missing',
|
||||||
stickersPath: 'missing',
|
stickersPath: 'missing',
|
||||||
tempPath: 'missing',
|
tempPath: 'missing',
|
||||||
|
ourConversationId: 'missing',
|
||||||
|
ourUuid: 'missing',
|
||||||
ourNumber: 'missing',
|
ourNumber: 'missing',
|
||||||
regionCode: 'missing',
|
regionCode: 'missing',
|
||||||
platform: 'missing',
|
platform: 'missing',
|
||||||
|
|
|
@ -22,6 +22,7 @@ import {
|
||||||
getInteractionMode,
|
getInteractionMode,
|
||||||
getIntl,
|
getIntl,
|
||||||
getRegionCode,
|
getRegionCode,
|
||||||
|
getUserConversationId,
|
||||||
getUserNumber,
|
getUserNumber,
|
||||||
} from './user';
|
} from './user';
|
||||||
|
|
||||||
|
@ -181,9 +182,12 @@ export const getLeftPaneLists = createSelector(
|
||||||
);
|
);
|
||||||
|
|
||||||
export const getMe = createSelector(
|
export const getMe = createSelector(
|
||||||
[getConversationLookup, getUserNumber],
|
[getConversationLookup, getUserConversationId],
|
||||||
(lookup: ConversationLookupType, ourNumber: string): ConversationType => {
|
(
|
||||||
return lookup[ourNumber];
|
lookup: ConversationLookupType,
|
||||||
|
ourConversationId: string
|
||||||
|
): ConversationType => {
|
||||||
|
return lookup[ourConversationId];
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -149,6 +149,7 @@ export const getSearchResults = createSelector(
|
||||||
});
|
});
|
||||||
contacts.forEach(id => {
|
contacts.forEach(id => {
|
||||||
const data = lookup[id];
|
const data = lookup[id];
|
||||||
|
|
||||||
items.push({
|
items.push({
|
||||||
type: 'contact',
|
type: 'contact',
|
||||||
data: {
|
data: {
|
||||||
|
|
|
@ -17,6 +17,16 @@ export const getRegionCode = createSelector(
|
||||||
(state: UserStateType): string => state.regionCode
|
(state: UserStateType): string => state.regionCode
|
||||||
);
|
);
|
||||||
|
|
||||||
|
export const getUserConversationId = createSelector(
|
||||||
|
getUser,
|
||||||
|
(state: UserStateType): string => state.ourConversationId
|
||||||
|
);
|
||||||
|
|
||||||
|
export const getUserUuid = createSelector(
|
||||||
|
getUser,
|
||||||
|
(state: UserStateType): string => state.ourUuid
|
||||||
|
);
|
||||||
|
|
||||||
export const getIntl = createSelector(
|
export const getIntl = createSelector(
|
||||||
getUser,
|
getUser,
|
||||||
(state: UserStateType): LocalizerType => state.i18n
|
(state: UserStateType): LocalizerType => state.i18n
|
||||||
|
|
|
@ -10,7 +10,13 @@ import {
|
||||||
getSearchConversationName,
|
getSearchConversationName,
|
||||||
getStartSearchCounter,
|
getStartSearchCounter,
|
||||||
} from '../selectors/search';
|
} from '../selectors/search';
|
||||||
import { getIntl, getRegionCode, getUserNumber } from '../selectors/user';
|
import {
|
||||||
|
getIntl,
|
||||||
|
getRegionCode,
|
||||||
|
getUserConversationId,
|
||||||
|
getUserNumber,
|
||||||
|
getUserUuid,
|
||||||
|
} from '../selectors/user';
|
||||||
import { getMe } from '../selectors/conversations';
|
import { getMe } from '../selectors/conversations';
|
||||||
|
|
||||||
const mapStateToProps = (state: StateType) => {
|
const mapStateToProps = (state: StateType) => {
|
||||||
|
@ -20,7 +26,9 @@ const mapStateToProps = (state: StateType) => {
|
||||||
searchConversationName: getSearchConversationName(state),
|
searchConversationName: getSearchConversationName(state),
|
||||||
startSearchCounter: getStartSearchCounter(state),
|
startSearchCounter: getStartSearchCounter(state),
|
||||||
regionCode: getRegionCode(state),
|
regionCode: getRegionCode(state),
|
||||||
|
ourConversationId: getUserConversationId(state),
|
||||||
ourNumber: getUserNumber(state),
|
ourNumber: getUserNumber(state),
|
||||||
|
ourUuid: getUserUuid(state),
|
||||||
...getMe(state),
|
...getMe(state),
|
||||||
i18n: getIntl(state),
|
i18n: getIntl(state),
|
||||||
};
|
};
|
||||||
|
|
|
@ -164,17 +164,17 @@
|
||||||
"rule": "jQuery-load(",
|
"rule": "jQuery-load(",
|
||||||
"path": "js/conversation_controller.js",
|
"path": "js/conversation_controller.js",
|
||||||
"line": " async load() {",
|
"line": " async load() {",
|
||||||
"lineNumber": 171,
|
"lineNumber": 210,
|
||||||
"reasonCategory": "falseMatch",
|
"reasonCategory": "falseMatch",
|
||||||
"updated": "2019-07-31T00:19:18.696Z"
|
"updated": "2020-02-14T20:02:37.507Z"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"rule": "jQuery-load(",
|
"rule": "jQuery-load(",
|
||||||
"path": "js/conversation_controller.js",
|
"path": "js/conversation_controller.js",
|
||||||
"line": " this._initialPromise = load();",
|
"line": " this._initialPromise = load();",
|
||||||
"lineNumber": 216,
|
"lineNumber": 255,
|
||||||
"reasonCategory": "falseMatch",
|
"reasonCategory": "falseMatch",
|
||||||
"updated": "2019-07-31T00:19:18.696Z"
|
"updated": "2020-02-14T20:02:37.507Z"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"rule": "jQuery-$(",
|
"rule": "jQuery-$(",
|
||||||
|
@ -301,9 +301,9 @@
|
||||||
"rule": "jQuery-load(",
|
"rule": "jQuery-load(",
|
||||||
"path": "js/signal_protocol_store.js",
|
"path": "js/signal_protocol_store.js",
|
||||||
"line": " await ConversationController.load();",
|
"line": " await ConversationController.load();",
|
||||||
"lineNumber": 894,
|
"lineNumber": 983,
|
||||||
"reasonCategory": "falseMatch",
|
"reasonCategory": "falseMatch",
|
||||||
"updated": "2018-09-15T00:38:04.183Z"
|
"updated": "2020-02-28T18:14:42.951Z"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"rule": "DOM-innerHTML",
|
"rule": "DOM-innerHTML",
|
||||||
|
@ -669,44 +669,44 @@
|
||||||
"rule": "jQuery-$(",
|
"rule": "jQuery-$(",
|
||||||
"path": "js/views/key_verification_view.js",
|
"path": "js/views/key_verification_view.js",
|
||||||
"line": " new QRCode(this.$('.qr')[0]).makeCode(",
|
"line": " new QRCode(this.$('.qr')[0]).makeCode(",
|
||||||
"lineNumber": 42,
|
"lineNumber": 43,
|
||||||
"reasonCategory": "usageTrusted",
|
"reasonCategory": "usageTrusted",
|
||||||
"updated": "2019-07-31T00:19:18.696Z",
|
"updated": "2020-02-14T20:02:37.507Z",
|
||||||
"reasonDetail": "Hardcoded selector"
|
"reasonDetail": "Hardcoded selector"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"rule": "jQuery-wrap(",
|
"rule": "jQuery-wrap(",
|
||||||
"path": "js/views/key_verification_view.js",
|
"path": "js/views/key_verification_view.js",
|
||||||
"line": " dcodeIO.ByteBuffer.wrap(this.ourKey).toString('base64')",
|
"line": " dcodeIO.ByteBuffer.wrap(this.ourKey).toString('base64')",
|
||||||
"lineNumber": 43,
|
"lineNumber": 44,
|
||||||
"reasonCategory": "falseMatch",
|
"reasonCategory": "falseMatch",
|
||||||
"updated": "2019-07-31T00:19:18.696Z"
|
"updated": "2020-02-14T20:02:37.507Z"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"rule": "jQuery-insertBefore(",
|
"rule": "jQuery-insertBefore(",
|
||||||
"path": "js/views/key_verification_view.js",
|
"path": "js/views/key_verification_view.js",
|
||||||
"line": " dialog.$el.insertBefore(this.el);",
|
"line": " dialog.$el.insertBefore(this.el);",
|
||||||
"lineNumber": 72,
|
"lineNumber": 86,
|
||||||
"reasonCategory": "usageTrusted",
|
"reasonCategory": "usageTrusted",
|
||||||
"updated": "2019-07-31T00:19:18.696Z",
|
"updated": "2020-02-14T20:02:37.507Z",
|
||||||
"reasonDetail": "Known DOM elements"
|
"reasonDetail": "Known DOM elements"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"rule": "jQuery-$(",
|
"rule": "jQuery-$(",
|
||||||
"path": "js/views/key_verification_view.js",
|
"path": "js/views/key_verification_view.js",
|
||||||
"line": " this.$('button.verify').attr('disabled', true);",
|
"line": " this.$('button.verify').attr('disabled', true);",
|
||||||
"lineNumber": 76,
|
"lineNumber": 90,
|
||||||
"reasonCategory": "usageTrusted",
|
"reasonCategory": "usageTrusted",
|
||||||
"updated": "2019-07-31T00:19:18.696Z",
|
"updated": "2020-02-14T20:02:37.507Z",
|
||||||
"reasonDetail": "Hardcoded selector"
|
"reasonDetail": "Hardcoded selector"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"rule": "jQuery-$(",
|
"rule": "jQuery-$(",
|
||||||
"path": "js/views/key_verification_view.js",
|
"path": "js/views/key_verification_view.js",
|
||||||
"line": " this.$('button.verify').removeAttr('disabled');",
|
"line": " this.$('button.verify').removeAttr('disabled');",
|
||||||
"lineNumber": 107,
|
"lineNumber": 121,
|
||||||
"reasonCategory": "usageTrusted",
|
"reasonCategory": "usageTrusted",
|
||||||
"updated": "2019-07-31T00:19:18.696Z",
|
"updated": "2020-02-14T20:02:37.507Z",
|
||||||
"reasonDetail": "Hardcoded selector"
|
"reasonDetail": "Hardcoded selector"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -1203,65 +1203,65 @@
|
||||||
"rule": "jQuery-wrap(",
|
"rule": "jQuery-wrap(",
|
||||||
"path": "libtextsecure/message_receiver.js",
|
"path": "libtextsecure/message_receiver.js",
|
||||||
"line": " dcodeIO.ByteBuffer.wrap(string, 'binary').toArrayBuffer();",
|
"line": " dcodeIO.ByteBuffer.wrap(string, 'binary').toArrayBuffer();",
|
||||||
"lineNumber": 62,
|
"lineNumber": 72,
|
||||||
"reasonCategory": "falseMatch",
|
"reasonCategory": "falseMatch",
|
||||||
"updated": "2019-09-20T18:36:19.909Z"
|
"updated": "2020-02-14T20:02:37.507Z"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"rule": "jQuery-wrap(",
|
"rule": "jQuery-wrap(",
|
||||||
"path": "libtextsecure/message_receiver.js",
|
"path": "libtextsecure/message_receiver.js",
|
||||||
"line": " dcodeIO.ByteBuffer.wrap(arrayBuffer).toString('binary');",
|
"line": " dcodeIO.ByteBuffer.wrap(arrayBuffer).toString('binary');",
|
||||||
"lineNumber": 64,
|
"lineNumber": 74,
|
||||||
"reasonCategory": "falseMatch",
|
"reasonCategory": "falseMatch",
|
||||||
"updated": "2019-09-20T18:36:19.909Z"
|
"updated": "2020-02-14T20:02:37.507Z"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"rule": "jQuery-wrap(",
|
"rule": "jQuery-wrap(",
|
||||||
"path": "libtextsecure/message_receiver.js",
|
"path": "libtextsecure/message_receiver.js",
|
||||||
"line": " dcodeIO.ByteBuffer.wrap(string, 'base64').toArrayBuffer();",
|
"line": " dcodeIO.ByteBuffer.wrap(string, 'base64').toArrayBuffer();",
|
||||||
"lineNumber": 66,
|
"lineNumber": 76,
|
||||||
"reasonCategory": "falseMatch",
|
"reasonCategory": "falseMatch",
|
||||||
"updated": "2019-09-20T18:36:19.909Z"
|
"updated": "2020-02-14T20:02:37.507Z"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"rule": "jQuery-wrap(",
|
"rule": "jQuery-wrap(",
|
||||||
"path": "libtextsecure/message_receiver.js",
|
"path": "libtextsecure/message_receiver.js",
|
||||||
"line": " dcodeIO.ByteBuffer.wrap(arrayBuffer).toString('base64');",
|
"line": " dcodeIO.ByteBuffer.wrap(arrayBuffer).toString('base64');",
|
||||||
"lineNumber": 68,
|
"lineNumber": 78,
|
||||||
"reasonCategory": "falseMatch",
|
"reasonCategory": "falseMatch",
|
||||||
"updated": "2019-09-20T18:36:19.909Z"
|
"updated": "2020-02-14T20:02:37.507Z"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"rule": "jQuery-wrap(",
|
"rule": "jQuery-wrap(",
|
||||||
"path": "libtextsecure/message_receiver.js",
|
"path": "libtextsecure/message_receiver.js",
|
||||||
"line": " const buffer = dcodeIO.ByteBuffer.wrap(ciphertext);",
|
"line": " const buffer = dcodeIO.ByteBuffer.wrap(ciphertext);",
|
||||||
"lineNumber": 752,
|
"lineNumber": 812,
|
||||||
"reasonCategory": "falseMatch",
|
"reasonCategory": "falseMatch",
|
||||||
"updated": "2019-10-21T22:30:15.622Z"
|
"updated": "2020-03-04T21:24:23.269Z"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"rule": "jQuery-wrap(",
|
"rule": "jQuery-wrap(",
|
||||||
"path": "libtextsecure/message_receiver.js",
|
"path": "libtextsecure/message_receiver.js",
|
||||||
"line": " const buffer = dcodeIO.ByteBuffer.wrap(ciphertext);",
|
"line": " const buffer = dcodeIO.ByteBuffer.wrap(ciphertext);",
|
||||||
"lineNumber": 777,
|
"lineNumber": 837,
|
||||||
"reasonCategory": "falseMatch",
|
"reasonCategory": "falseMatch",
|
||||||
"updated": "2019-10-21T22:30:15.622Z"
|
"updated": "2020-03-04T21:24:23.269Z"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"rule": "jQuery-wrap(",
|
"rule": "jQuery-wrap(",
|
||||||
"path": "libtextsecure/sendmessage.js",
|
"path": "libtextsecure/sendmessage.js",
|
||||||
"line": " return dcodeIO.ByteBuffer.wrap(string, 'hex').toArrayBuffer();",
|
"line": " return dcodeIO.ByteBuffer.wrap(string, 'hex').toArrayBuffer();",
|
||||||
"lineNumber": 17,
|
"lineNumber": 18,
|
||||||
"reasonCategory": "falseMatch",
|
"reasonCategory": "falseMatch",
|
||||||
"updated": "2019-04-26T17:48:30.675Z"
|
"updated": "2020-02-14T20:02:37.507Z"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"rule": "jQuery-wrap(",
|
"rule": "jQuery-wrap(",
|
||||||
"path": "libtextsecure/sendmessage.js",
|
"path": "libtextsecure/sendmessage.js",
|
||||||
"line": " return dcodeIO.ByteBuffer.wrap(string, 'base64').toArrayBuffer();",
|
"line": " return dcodeIO.ByteBuffer.wrap(string, 'base64').toArrayBuffer();",
|
||||||
"lineNumber": 20,
|
"lineNumber": 21,
|
||||||
"reasonCategory": "falseMatch",
|
"reasonCategory": "falseMatch",
|
||||||
"updated": "2019-04-26T17:48:30.675Z"
|
"updated": "2020-02-14T20:02:37.507Z"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"rule": "jQuery-wrap(",
|
"rule": "jQuery-wrap(",
|
||||||
|
@ -11663,18 +11663,18 @@
|
||||||
"rule": "React-createRef",
|
"rule": "React-createRef",
|
||||||
"path": "ts/components/MainHeader.js",
|
"path": "ts/components/MainHeader.js",
|
||||||
"line": " this.inputRef = react_1.default.createRef();",
|
"line": " this.inputRef = react_1.default.createRef();",
|
||||||
"lineNumber": 144,
|
"lineNumber": 146,
|
||||||
"reasonCategory": "usageTrusted",
|
"reasonCategory": "usageTrusted",
|
||||||
"updated": "2019-08-09T21:17:57.798Z",
|
"updated": "2020-02-14T20:02:37.507Z",
|
||||||
"reasonDetail": "Used only to set focus"
|
"reasonDetail": "Used only to set focus"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"rule": "React-createRef",
|
"rule": "React-createRef",
|
||||||
"path": "ts/components/MainHeader.tsx",
|
"path": "ts/components/MainHeader.tsx",
|
||||||
"line": " this.inputRef = React.createRef();",
|
"line": " this.inputRef = React.createRef();",
|
||||||
"lineNumber": 65,
|
"lineNumber": 69,
|
||||||
"reasonCategory": "usageTrusted",
|
"reasonCategory": "usageTrusted",
|
||||||
"updated": "2019-08-09T21:17:57.798Z",
|
"updated": "2020-02-14T20:02:37.507Z",
|
||||||
"reasonDetail": "Used only to set focus"
|
"reasonDetail": "Used only to set focus"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -11816,4 +11816,4 @@
|
||||||
"reasonCategory": "falseMatch",
|
"reasonCategory": "falseMatch",
|
||||||
"updated": "2020-02-07T19:52:28.522Z"
|
"updated": "2020-02-07T19:52:28.522Z"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
Loading…
Add table
Reference in a new issue