New queue for view once syncs, handleRetry improvements
This commit is contained in:
parent
571ee3cab6
commit
0a18cc50bd
12 changed files with 271 additions and 115 deletions
|
@ -1,4 +1,4 @@
|
||||||
// Copyright 2017-2021 Signal Messenger, LLC
|
// Copyright 2017-2022 Signal Messenger, LLC
|
||||||
// SPDX-License-Identifier: AGPL-3.0-only
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
/* global Whisper, window */
|
/* global Whisper, window */
|
||||||
|
@ -18,6 +18,7 @@ try {
|
||||||
|
|
||||||
// It is important to call this as early as possible
|
// It is important to call this as early as possible
|
||||||
const { SignalContext } = require('./ts/windows/context');
|
const { SignalContext } = require('./ts/windows/context');
|
||||||
|
window.i18n = SignalContext.i18n;
|
||||||
|
|
||||||
const { getEnvironment, Environment } = require('./ts/environment');
|
const { getEnvironment, Environment } = require('./ts/environment');
|
||||||
const ipc = electron.ipcRenderer;
|
const ipc = electron.ipcRenderer;
|
||||||
|
@ -421,7 +422,6 @@ try {
|
||||||
const Attachments = require('./ts/windows/attachments');
|
const Attachments = require('./ts/windows/attachments');
|
||||||
|
|
||||||
const { locale } = config;
|
const { locale } = config;
|
||||||
window.i18n = SignalContext.i18n;
|
|
||||||
window.moment.updateLocale(locale, {
|
window.moment.updateLocale(locale, {
|
||||||
relativeTime: {
|
relativeTime: {
|
||||||
s: window.i18n('timestamp_s'),
|
s: window.i18n('timestamp_s'),
|
||||||
|
|
|
@ -2968,9 +2968,9 @@ export async function startApp(): Promise<void> {
|
||||||
}
|
}
|
||||||
|
|
||||||
log.info(
|
log.info(
|
||||||
'onProfileKeyUpdate: updating profileKey',
|
'onProfileKeyUpdate: updating profileKey for',
|
||||||
data.source,
|
data.sourceUuid,
|
||||||
data.sourceUuid
|
data.source
|
||||||
);
|
);
|
||||||
|
|
||||||
await conversation.setProfileKey(data.profileKey);
|
await conversation.setProfileKey(data.profileKey);
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
// Copyright 2021 Signal Messenger, LLC
|
// Copyright 2021-2022 Signal Messenger, LLC
|
||||||
// SPDX-License-Identifier: AGPL-3.0-only
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
import { chunk } from 'lodash';
|
import { chunk } from 'lodash';
|
||||||
|
@ -12,6 +12,7 @@ import { isRecord } from '../../util/isRecord';
|
||||||
|
|
||||||
import { commonShouldJobContinue } from './commonShouldJobContinue';
|
import { commonShouldJobContinue } from './commonShouldJobContinue';
|
||||||
import { handleCommonJobRequestError } from './handleCommonJobRequestError';
|
import { handleCommonJobRequestError } from './handleCommonJobRequestError';
|
||||||
|
import { missingCaseError } from '../../util/missingCaseError';
|
||||||
|
|
||||||
const CHUNK_SIZE = 100;
|
const CHUNK_SIZE = 100;
|
||||||
|
|
||||||
|
@ -21,6 +22,11 @@ export type SyncType = {
|
||||||
senderUuid?: string;
|
senderUuid?: string;
|
||||||
timestamp: number;
|
timestamp: number;
|
||||||
};
|
};
|
||||||
|
export enum SyncTypeList {
|
||||||
|
Read = 'Read',
|
||||||
|
View = 'View',
|
||||||
|
ViewOnceOpen = 'ViewOnceOpen',
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parse what _should_ be an array of `SyncType`s.
|
* Parse what _should_ be an array of `SyncType`s.
|
||||||
|
@ -55,16 +61,16 @@ function parseOptionalString(name: string, value: unknown): undefined | string {
|
||||||
throw new Error(`${name} was not a string`);
|
throw new Error(`${name} was not a string`);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function runReadOrViewSyncJob({
|
export async function runSyncJob({
|
||||||
attempt,
|
attempt,
|
||||||
isView,
|
type,
|
||||||
log,
|
log,
|
||||||
maxRetryTime,
|
maxRetryTime,
|
||||||
syncs,
|
syncs,
|
||||||
timestamp,
|
timestamp,
|
||||||
}: Readonly<{
|
}: Readonly<{
|
||||||
attempt: number;
|
attempt: number;
|
||||||
isView: boolean;
|
type: SyncTypeList;
|
||||||
log: LoggerType;
|
log: LoggerType;
|
||||||
maxRetryTime: number;
|
maxRetryTime: number;
|
||||||
syncs: ReadonlyArray<SyncType>;
|
syncs: ReadonlyArray<SyncType>;
|
||||||
|
@ -76,10 +82,19 @@ export async function runReadOrViewSyncJob({
|
||||||
}
|
}
|
||||||
|
|
||||||
let sendType: SendTypesType;
|
let sendType: SendTypesType;
|
||||||
if (isView) {
|
switch (type) {
|
||||||
sendType = 'viewSync';
|
case SyncTypeList.View:
|
||||||
} else {
|
sendType = 'viewSync';
|
||||||
sendType = 'readSync';
|
break;
|
||||||
|
case SyncTypeList.Read:
|
||||||
|
sendType = 'readSync';
|
||||||
|
break;
|
||||||
|
case SyncTypeList.ViewOnceOpen:
|
||||||
|
sendType = 'viewOnceSync';
|
||||||
|
break;
|
||||||
|
default: {
|
||||||
|
throw missingCaseError(type);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const syncTimestamps = syncs.map(sync => sync.timestamp);
|
const syncTimestamps = syncs.map(sync => sync.timestamp);
|
||||||
|
@ -108,15 +123,27 @@ export async function runReadOrViewSyncJob({
|
||||||
|
|
||||||
let doSync:
|
let doSync:
|
||||||
| typeof window.textsecure.messaging.syncReadMessages
|
| typeof window.textsecure.messaging.syncReadMessages
|
||||||
| typeof window.textsecure.messaging.syncView;
|
| typeof window.textsecure.messaging.syncView
|
||||||
if (isView) {
|
| typeof window.textsecure.messaging.syncViewOnceOpen;
|
||||||
doSync = window.textsecure.messaging.syncView.bind(
|
switch (type) {
|
||||||
window.textsecure.messaging
|
case SyncTypeList.View:
|
||||||
);
|
doSync = window.textsecure.messaging.syncView.bind(
|
||||||
} else {
|
window.textsecure.messaging
|
||||||
doSync = window.textsecure.messaging.syncReadMessages.bind(
|
);
|
||||||
window.textsecure.messaging
|
break;
|
||||||
);
|
case SyncTypeList.Read:
|
||||||
|
doSync = window.textsecure.messaging.syncReadMessages.bind(
|
||||||
|
window.textsecure.messaging
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
case SyncTypeList.ViewOnceOpen:
|
||||||
|
doSync = window.textsecure.messaging.syncViewOnceOpen.bind(
|
||||||
|
window.textsecure.messaging
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
default: {
|
||||||
|
throw missingCaseError(type);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
|
@ -11,6 +11,7 @@ import { readSyncJobQueue } from './readSyncJobQueue';
|
||||||
import { removeStorageKeyJobQueue } from './removeStorageKeyJobQueue';
|
import { removeStorageKeyJobQueue } from './removeStorageKeyJobQueue';
|
||||||
import { reportSpamJobQueue } from './reportSpamJobQueue';
|
import { reportSpamJobQueue } from './reportSpamJobQueue';
|
||||||
import { singleProtoJobQueue } from './singleProtoJobQueue';
|
import { singleProtoJobQueue } from './singleProtoJobQueue';
|
||||||
|
import { viewOnceOpenJobQueue } from './viewOnceOpenJobQueue';
|
||||||
import { viewSyncJobQueue } from './viewSyncJobQueue';
|
import { viewSyncJobQueue } from './viewSyncJobQueue';
|
||||||
import { viewedReceiptsJobQueue } from './viewedReceiptsJobQueue';
|
import { viewedReceiptsJobQueue } from './viewedReceiptsJobQueue';
|
||||||
|
|
||||||
|
@ -24,14 +25,24 @@ export function initializeAllJobQueues({
|
||||||
}): void {
|
}): void {
|
||||||
reportSpamJobQueue.initialize({ server });
|
reportSpamJobQueue.initialize({ server });
|
||||||
|
|
||||||
deliveryReceiptsJobQueue.streamJobs();
|
// General conversation send queue
|
||||||
normalMessageSendJobQueue.streamJobs();
|
normalMessageSendJobQueue.streamJobs();
|
||||||
reactionJobQueue.streamJobs();
|
reactionJobQueue.streamJobs();
|
||||||
|
|
||||||
|
// Single proto send queue, used for a variety of one-off simple messages
|
||||||
|
singleProtoJobQueue.streamJobs();
|
||||||
|
|
||||||
|
// Syncs to others
|
||||||
|
deliveryReceiptsJobQueue.streamJobs();
|
||||||
readReceiptsJobQueue.streamJobs();
|
readReceiptsJobQueue.streamJobs();
|
||||||
|
viewedReceiptsJobQueue.streamJobs();
|
||||||
|
viewOnceOpenJobQueue.streamJobs();
|
||||||
|
|
||||||
|
// Syncs to ourselves
|
||||||
readSyncJobQueue.streamJobs();
|
readSyncJobQueue.streamJobs();
|
||||||
|
viewSyncJobQueue.streamJobs();
|
||||||
|
|
||||||
|
// Other queues
|
||||||
removeStorageKeyJobQueue.streamJobs();
|
removeStorageKeyJobQueue.streamJobs();
|
||||||
reportSpamJobQueue.streamJobs();
|
reportSpamJobQueue.streamJobs();
|
||||||
singleProtoJobQueue.streamJobs();
|
|
||||||
viewSyncJobQueue.streamJobs();
|
|
||||||
viewedReceiptsJobQueue.streamJobs();
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,14 +1,15 @@
|
||||||
// Copyright 2021 Signal Messenger, LLC
|
// Copyright 2021-2022 Signal Messenger, LLC
|
||||||
// SPDX-License-Identifier: AGPL-3.0-only
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
import * as durations from '../util/durations';
|
import * as durations from '../util/durations';
|
||||||
import type { LoggerType } from '../types/Logging';
|
import type { LoggerType } from '../types/Logging';
|
||||||
import { exponentialBackoffMaxAttempts } from '../util/exponentialBackoff';
|
import { exponentialBackoffMaxAttempts } from '../util/exponentialBackoff';
|
||||||
import type { SyncType } from './helpers/readAndViewSyncHelpers';
|
import type { SyncType } from './helpers/syncHelpers';
|
||||||
import {
|
import {
|
||||||
|
SyncTypeList,
|
||||||
parseRawSyncDataArray,
|
parseRawSyncDataArray,
|
||||||
runReadOrViewSyncJob,
|
runSyncJob,
|
||||||
} from './helpers/readAndViewSyncHelpers';
|
} from './helpers/syncHelpers';
|
||||||
import { strictAssert } from '../util/assert';
|
import { strictAssert } from '../util/assert';
|
||||||
import { isRecord } from '../util/isRecord';
|
import { isRecord } from '../util/isRecord';
|
||||||
|
|
||||||
|
@ -31,13 +32,13 @@ export class ReadSyncJobQueue extends JobQueue<ReadSyncJobData> {
|
||||||
{ data, timestamp }: Readonly<{ data: ReadSyncJobData; timestamp: number }>,
|
{ data, timestamp }: Readonly<{ data: ReadSyncJobData; timestamp: number }>,
|
||||||
{ attempt, log }: Readonly<{ attempt: number; log: LoggerType }>
|
{ attempt, log }: Readonly<{ attempt: number; log: LoggerType }>
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
await runReadOrViewSyncJob({
|
await runSyncJob({
|
||||||
attempt,
|
attempt,
|
||||||
isView: false,
|
|
||||||
log,
|
log,
|
||||||
maxRetryTime: MAX_RETRY_TIME,
|
maxRetryTime: MAX_RETRY_TIME,
|
||||||
syncs: data.readSyncs,
|
syncs: data.readSyncs,
|
||||||
timestamp,
|
timestamp,
|
||||||
|
type: SyncTypeList.Read,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
53
ts/jobs/viewOnceOpenJobQueue.ts
Normal file
53
ts/jobs/viewOnceOpenJobQueue.ts
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
// Copyright 2022 Signal Messenger, LLC
|
||||||
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
import * as durations from '../util/durations';
|
||||||
|
import type { LoggerType } from '../types/Logging';
|
||||||
|
import { exponentialBackoffMaxAttempts } from '../util/exponentialBackoff';
|
||||||
|
import type { SyncType } from './helpers/syncHelpers';
|
||||||
|
import {
|
||||||
|
SyncTypeList,
|
||||||
|
parseRawSyncDataArray,
|
||||||
|
runSyncJob,
|
||||||
|
} from './helpers/syncHelpers';
|
||||||
|
import { strictAssert } from '../util/assert';
|
||||||
|
import { isRecord } from '../util/isRecord';
|
||||||
|
|
||||||
|
import { JobQueue } from './JobQueue';
|
||||||
|
import { jobQueueDatabaseStore } from './JobQueueDatabaseStore';
|
||||||
|
|
||||||
|
const MAX_RETRY_TIME = durations.DAY;
|
||||||
|
|
||||||
|
export type ViewOnceOpenJobData = {
|
||||||
|
viewOnceOpens: Array<SyncType>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export class ViewOnceOpenJobQueue extends JobQueue<ViewOnceOpenJobData> {
|
||||||
|
protected parseData(data: unknown): ViewOnceOpenJobData {
|
||||||
|
strictAssert(isRecord(data), 'data is not an object');
|
||||||
|
return { viewOnceOpens: parseRawSyncDataArray(data.viewOnceOpens) };
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async run(
|
||||||
|
{
|
||||||
|
data,
|
||||||
|
timestamp,
|
||||||
|
}: Readonly<{ data: ViewOnceOpenJobData; timestamp: number }>,
|
||||||
|
{ attempt, log }: Readonly<{ attempt: number; log: LoggerType }>
|
||||||
|
): Promise<void> {
|
||||||
|
await runSyncJob({
|
||||||
|
attempt,
|
||||||
|
log,
|
||||||
|
maxRetryTime: MAX_RETRY_TIME,
|
||||||
|
syncs: data.viewOnceOpens,
|
||||||
|
timestamp,
|
||||||
|
type: SyncTypeList.ViewOnceOpen,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const viewOnceOpenJobQueue = new ViewOnceOpenJobQueue({
|
||||||
|
store: jobQueueDatabaseStore,
|
||||||
|
queueType: 'view once open sync',
|
||||||
|
maxAttempts: exponentialBackoffMaxAttempts(MAX_RETRY_TIME),
|
||||||
|
});
|
|
@ -1,14 +1,15 @@
|
||||||
// Copyright 2021 Signal Messenger, LLC
|
// Copyright 2021-2022 Signal Messenger, LLC
|
||||||
// SPDX-License-Identifier: AGPL-3.0-only
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
import * as durations from '../util/durations';
|
import * as durations from '../util/durations';
|
||||||
import type { LoggerType } from '../types/Logging';
|
import type { LoggerType } from '../types/Logging';
|
||||||
import { exponentialBackoffMaxAttempts } from '../util/exponentialBackoff';
|
import { exponentialBackoffMaxAttempts } from '../util/exponentialBackoff';
|
||||||
import type { SyncType } from './helpers/readAndViewSyncHelpers';
|
import type { SyncType } from './helpers/syncHelpers';
|
||||||
import {
|
import {
|
||||||
|
SyncTypeList,
|
||||||
parseRawSyncDataArray,
|
parseRawSyncDataArray,
|
||||||
runReadOrViewSyncJob,
|
runSyncJob,
|
||||||
} from './helpers/readAndViewSyncHelpers';
|
} from './helpers/syncHelpers';
|
||||||
import { strictAssert } from '../util/assert';
|
import { strictAssert } from '../util/assert';
|
||||||
import { isRecord } from '../util/isRecord';
|
import { isRecord } from '../util/isRecord';
|
||||||
|
|
||||||
|
@ -31,13 +32,13 @@ export class ViewSyncJobQueue extends JobQueue<ViewSyncJobData> {
|
||||||
{ data, timestamp }: Readonly<{ data: ViewSyncJobData; timestamp: number }>,
|
{ data, timestamp }: Readonly<{ data: ViewSyncJobData; timestamp: number }>,
|
||||||
{ attempt, log }: Readonly<{ attempt: number; log: LoggerType }>
|
{ attempt, log }: Readonly<{ attempt: number; log: LoggerType }>
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
await runReadOrViewSyncJob({
|
await runSyncJob({
|
||||||
attempt,
|
attempt,
|
||||||
isView: true,
|
|
||||||
log,
|
log,
|
||||||
maxRetryTime: MAX_RETRY_TIME,
|
maxRetryTime: MAX_RETRY_TIME,
|
||||||
syncs: data.viewSyncs,
|
syncs: data.viewSyncs,
|
||||||
timestamp,
|
timestamp,
|
||||||
|
type: SyncTypeList.View,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -143,6 +143,7 @@ import {
|
||||||
isQuoteAMatch,
|
isQuoteAMatch,
|
||||||
} from '../messages/helpers';
|
} from '../messages/helpers';
|
||||||
import type { ReplacementValuesType } from '../types/I18N';
|
import type { ReplacementValuesType } from '../types/I18N';
|
||||||
|
import { viewOnceOpenJobQueue } from '../jobs/viewOnceOpenJobQueue';
|
||||||
|
|
||||||
/* eslint-disable camelcase */
|
/* eslint-disable camelcase */
|
||||||
/* eslint-disable more/no-then */
|
/* eslint-disable more/no-then */
|
||||||
|
@ -843,20 +844,14 @@ export class MessageModel extends window.Backbone.Model<MessageAttributesType> {
|
||||||
await this.eraseContents();
|
await this.eraseContents();
|
||||||
|
|
||||||
if (!fromSync) {
|
if (!fromSync) {
|
||||||
const sender = getSource(this.attributes);
|
const senderE164 = getSource(this.attributes);
|
||||||
const senderUuid = getSourceUuid(this.attributes);
|
const senderUuid = getSourceUuid(this.attributes);
|
||||||
|
const timestamp = this.get('sent_at');
|
||||||
|
|
||||||
if (senderUuid === undefined) {
|
if (senderUuid === undefined) {
|
||||||
throw new Error('senderUuid is undefined');
|
throw new Error('markViewOnceMessageViewed: senderUuid is undefined');
|
||||||
}
|
}
|
||||||
|
|
||||||
const timestamp = this.get('sent_at');
|
|
||||||
const ourConversation =
|
|
||||||
window.ConversationController.getOurConversationOrThrow();
|
|
||||||
const sendOptions = await getSendOptions(ourConversation.attributes, {
|
|
||||||
syncMessage: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (window.ConversationController.areWePrimaryDevice()) {
|
if (window.ConversationController.areWePrimaryDevice()) {
|
||||||
log.warn(
|
log.warn(
|
||||||
'markViewOnceMessageViewed: We are primary device; not sending view once open sync'
|
'markViewOnceMessageViewed: We are primary device; not sending view once open sync'
|
||||||
|
@ -864,15 +859,22 @@ export class MessageModel extends window.Backbone.Model<MessageAttributesType> {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
await handleMessageSend(
|
try {
|
||||||
window.textsecure.messaging.syncViewOnceOpen(
|
await viewOnceOpenJobQueue.add({
|
||||||
sender,
|
viewOnceOpens: [
|
||||||
senderUuid,
|
{
|
||||||
timestamp,
|
senderE164,
|
||||||
sendOptions
|
senderUuid,
|
||||||
),
|
timestamp,
|
||||||
{ messageIds: [this.id], sendType: 'viewOnceSync' }
|
},
|
||||||
);
|
],
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
log.error(
|
||||||
|
'markViewOnceMessageViewed: Failed to queue view once open sync',
|
||||||
|
Errors.toLogFormat(error)
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
// Copyright 2021 Signal Messenger, LLC
|
// Copyright 2021-2022 Signal Messenger, LLC
|
||||||
// SPDX-License-Identifier: AGPL-3.0-only
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
import { assert } from 'chai';
|
import { assert } from 'chai';
|
||||||
|
|
||||||
import { parseRawSyncDataArray } from '../../../jobs/helpers/readAndViewSyncHelpers';
|
import { parseRawSyncDataArray } from '../../../jobs/helpers/syncHelpers';
|
||||||
|
|
||||||
describe('read and view sync helpers', () => {
|
describe('read and view sync helpers', () => {
|
||||||
describe('parseRawSyncDataArray', () => {
|
describe('parseRawSyncDataArray', () => {
|
|
@ -1363,18 +1363,31 @@ export default class MessageSender {
|
||||||
}
|
}
|
||||||
|
|
||||||
async syncViewOnceOpen(
|
async syncViewOnceOpen(
|
||||||
sender: string | undefined,
|
viewOnceOpens: ReadonlyArray<{
|
||||||
senderUuid: string,
|
senderUuid?: string;
|
||||||
timestamp: number,
|
senderE164?: string;
|
||||||
|
timestamp: number;
|
||||||
|
}>,
|
||||||
options?: Readonly<SendOptionsType>
|
options?: Readonly<SendOptionsType>
|
||||||
): Promise<CallbackResultType> {
|
): Promise<CallbackResultType> {
|
||||||
|
if (viewOnceOpens.length !== 1) {
|
||||||
|
throw new Error(
|
||||||
|
`syncViewOnceOpen: ${viewOnceOpens.length} opens provided. Can only handle one.`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
const { senderE164, senderUuid, timestamp } = viewOnceOpens[0];
|
||||||
|
|
||||||
|
if (!senderUuid) {
|
||||||
|
throw new Error('syncViewOnceOpen: Missing senderUuid');
|
||||||
|
}
|
||||||
|
|
||||||
const myUuid = window.textsecure.storage.user.getCheckedUuid();
|
const myUuid = window.textsecure.storage.user.getCheckedUuid();
|
||||||
|
|
||||||
const syncMessage = this.createSyncMessage();
|
const syncMessage = this.createSyncMessage();
|
||||||
|
|
||||||
const viewOnceOpen = new Proto.SyncMessage.ViewOnceOpen();
|
const viewOnceOpen = new Proto.SyncMessage.ViewOnceOpen();
|
||||||
if (sender !== undefined) {
|
if (senderE164 !== undefined) {
|
||||||
viewOnceOpen.sender = sender;
|
viewOnceOpen.sender = senderE164;
|
||||||
}
|
}
|
||||||
viewOnceOpen.senderUuid = senderUuid;
|
viewOnceOpen.senderUuid = senderUuid;
|
||||||
viewOnceOpen.timestamp = timestamp;
|
viewOnceOpen.timestamp = timestamp;
|
||||||
|
@ -1862,8 +1875,12 @@ export default class MessageSender {
|
||||||
}
|
}
|
||||||
|
|
||||||
async getSenderKeyDistributionMessage(
|
async getSenderKeyDistributionMessage(
|
||||||
distributionId: string
|
distributionId: string,
|
||||||
): Promise<SenderKeyDistributionMessage> {
|
{
|
||||||
|
throwIfNotInDatabase,
|
||||||
|
timestamp,
|
||||||
|
}: { throwIfNotInDatabase?: boolean; timestamp: number }
|
||||||
|
): Promise<Proto.Content> {
|
||||||
const ourUuid = window.textsecure.storage.user.getCheckedUuid();
|
const ourUuid = window.textsecure.storage.user.getCheckedUuid();
|
||||||
const ourDeviceId = parseIntOrThrow(
|
const ourDeviceId = parseIntOrThrow(
|
||||||
window.textsecure.storage.user.getDeviceId(),
|
window.textsecure.storage.user.getDeviceId(),
|
||||||
|
@ -1878,17 +1895,41 @@ export default class MessageSender {
|
||||||
ourUuid,
|
ourUuid,
|
||||||
new Address(ourUuid, ourDeviceId)
|
new Address(ourUuid, ourDeviceId)
|
||||||
);
|
);
|
||||||
const senderKeyStore = new SenderKeys({ ourUuid, zone: GLOBAL_ZONE });
|
|
||||||
|
|
||||||
return window.textsecure.storage.protocol.enqueueSenderKeyJob(
|
const senderKeyDistributionMessage =
|
||||||
address,
|
await window.textsecure.storage.protocol.enqueueSenderKeyJob(
|
||||||
async () =>
|
address,
|
||||||
SenderKeyDistributionMessage.create(
|
async () => {
|
||||||
protocolAddress,
|
const senderKeyStore = new SenderKeys({ ourUuid, zone: GLOBAL_ZONE });
|
||||||
distributionId,
|
|
||||||
senderKeyStore
|
if (throwIfNotInDatabase) {
|
||||||
)
|
const key = await senderKeyStore.getSenderKey(
|
||||||
|
protocolAddress,
|
||||||
|
distributionId
|
||||||
|
);
|
||||||
|
if (!key) {
|
||||||
|
throw new Error(
|
||||||
|
`getSenderKeyDistributionMessage: Distribution ${distributionId} was not in database as expected`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return SenderKeyDistributionMessage.create(
|
||||||
|
protocolAddress,
|
||||||
|
distributionId,
|
||||||
|
senderKeyStore
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
log.info(
|
||||||
|
`getSenderKeyDistributionMessage: Building ${distributionId} with timestamp ${timestamp}`
|
||||||
);
|
);
|
||||||
|
const contentMessage = new Proto.Content();
|
||||||
|
contentMessage.senderKeyDistributionMessage =
|
||||||
|
senderKeyDistributionMessage.serialize();
|
||||||
|
|
||||||
|
return contentMessage;
|
||||||
}
|
}
|
||||||
|
|
||||||
// The one group send exception - a message that should never be sent via sender key
|
// The one group send exception - a message that should never be sent via sender key
|
||||||
|
@ -1898,25 +1939,25 @@ export default class MessageSender {
|
||||||
distributionId,
|
distributionId,
|
||||||
groupId,
|
groupId,
|
||||||
identifiers,
|
identifiers,
|
||||||
|
throwIfNotInDatabase,
|
||||||
}: Readonly<{
|
}: Readonly<{
|
||||||
contentHint: number;
|
contentHint: number;
|
||||||
distributionId: string;
|
distributionId: string;
|
||||||
groupId: string | undefined;
|
groupId: string | undefined;
|
||||||
identifiers: ReadonlyArray<string>;
|
identifiers: ReadonlyArray<string>;
|
||||||
|
throwIfNotInDatabase?: boolean;
|
||||||
}>,
|
}>,
|
||||||
options?: Readonly<SendOptionsType>
|
options?: Readonly<SendOptionsType>
|
||||||
): Promise<CallbackResultType> {
|
): Promise<CallbackResultType> {
|
||||||
const contentMessage = new Proto.Content();
|
|
||||||
const timestamp = Date.now();
|
const timestamp = Date.now();
|
||||||
log.info(
|
const contentMessage = await this.getSenderKeyDistributionMessage(
|
||||||
`sendSenderKeyDistributionMessage: Sending ${distributionId} with timestamp ${timestamp}`
|
distributionId,
|
||||||
|
{
|
||||||
|
throwIfNotInDatabase,
|
||||||
|
timestamp,
|
||||||
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
const senderKeyDistributionMessage =
|
|
||||||
await this.getSenderKeyDistributionMessage(distributionId);
|
|
||||||
contentMessage.senderKeyDistributionMessage =
|
|
||||||
senderKeyDistributionMessage.serialize();
|
|
||||||
|
|
||||||
const sendLogCallback =
|
const sendLogCallback =
|
||||||
identifiers.length > 1
|
identifiers.length > 1
|
||||||
? this.makeSendLogCallback({
|
? this.makeSendLogCallback({
|
||||||
|
|
|
@ -7,6 +7,7 @@ import {
|
||||||
} from '@signalapp/signal-client';
|
} from '@signalapp/signal-client';
|
||||||
import { isNumber } from 'lodash';
|
import { isNumber } from 'lodash';
|
||||||
|
|
||||||
|
import * as Bytes from '../Bytes';
|
||||||
import { isProduction } from './version';
|
import { isProduction } from './version';
|
||||||
import { strictAssert } from './assert';
|
import { strictAssert } from './assert';
|
||||||
import { getSendOptions } from './getSendOptions';
|
import { getSendOptions } from './getSendOptions';
|
||||||
|
@ -31,10 +32,14 @@ import type {
|
||||||
|
|
||||||
import { SignalService as Proto } from '../protobuf';
|
import { SignalService as Proto } from '../protobuf';
|
||||||
import * as log from '../logging/log';
|
import * as log from '../logging/log';
|
||||||
import { singleProtoJobQueue } from '../jobs/singleProtoJobQueue';
|
|
||||||
|
|
||||||
const RETRY_LIMIT = 5;
|
const RETRY_LIMIT = 5;
|
||||||
|
|
||||||
|
// Note: Neither of the the two functions onRetryRequest and onDecrytionError use a job
|
||||||
|
// queue to make sure sends are reliable. That's unnecessary because these tasks are
|
||||||
|
// tied to incoming message processing queue, and will only confirm() completion on
|
||||||
|
// successful send.
|
||||||
|
|
||||||
// Entrypoints
|
// Entrypoints
|
||||||
|
|
||||||
const retryRecord = new Map<number, number>();
|
const retryRecord = new Map<number, number>();
|
||||||
|
@ -128,6 +133,7 @@ export async function onRetryRequest(event: RetryRequestEvent): Promise<void> {
|
||||||
messageIds,
|
messageIds,
|
||||||
requestGroupId,
|
requestGroupId,
|
||||||
requesterUuid,
|
requesterUuid,
|
||||||
|
timestamp,
|
||||||
});
|
});
|
||||||
|
|
||||||
const recipientConversation = window.ConversationController.getOrCreate(
|
const recipientConversation = window.ConversationController.getOrCreate(
|
||||||
|
@ -252,6 +258,7 @@ async function sendDistributionMessageOrNullMessage(
|
||||||
options: RetryRequestEventData,
|
options: RetryRequestEventData,
|
||||||
didArchive: boolean
|
didArchive: boolean
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
|
const { ContentHint } = Proto.UnidentifiedSenderMessage.Message;
|
||||||
const { groupId, requesterUuid } = options;
|
const { groupId, requesterUuid } = options;
|
||||||
let sentDistributionMessage = false;
|
let sentDistributionMessage = false;
|
||||||
log.info(`sendDistributionMessageOrNullMessage/${logId}: Starting...`);
|
log.info(`sendDistributionMessageOrNullMessage/${logId}: Starting...`);
|
||||||
|
@ -260,6 +267,7 @@ async function sendDistributionMessageOrNullMessage(
|
||||||
requesterUuid,
|
requesterUuid,
|
||||||
'private'
|
'private'
|
||||||
);
|
);
|
||||||
|
const sendOptions = await getSendOptions(conversation.attributes);
|
||||||
|
|
||||||
if (groupId) {
|
if (groupId) {
|
||||||
const group = window.ConversationController.get(groupId);
|
const group = window.ConversationController.get(groupId);
|
||||||
|
@ -277,20 +285,19 @@ async function sendDistributionMessageOrNullMessage(
|
||||||
);
|
);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const { ContentHint } = Proto.UnidentifiedSenderMessage.Message;
|
await handleMessageSend(
|
||||||
|
window.textsecure.messaging.sendSenderKeyDistributionMessage(
|
||||||
const result = await handleMessageSend(
|
{
|
||||||
window.textsecure.messaging.sendSenderKeyDistributionMessage({
|
contentHint: ContentHint.RESENDABLE,
|
||||||
contentHint: ContentHint.RESENDABLE,
|
distributionId,
|
||||||
distributionId,
|
groupId,
|
||||||
groupId,
|
identifiers: [requesterUuid],
|
||||||
identifiers: [requesterUuid],
|
throwIfNotInDatabase: true,
|
||||||
}),
|
},
|
||||||
|
sendOptions
|
||||||
|
),
|
||||||
{ messageIds: [], sendType: 'senderKeyDistributionMessage' }
|
{ messageIds: [], sendType: 'senderKeyDistributionMessage' }
|
||||||
);
|
);
|
||||||
if (result && result.errors && result.errors.length > 0) {
|
|
||||||
throw result.errors[0];
|
|
||||||
}
|
|
||||||
sentDistributionMessage = true;
|
sentDistributionMessage = true;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
log.error(
|
log.error(
|
||||||
|
@ -315,14 +322,23 @@ async function sendDistributionMessageOrNullMessage(
|
||||||
|
|
||||||
// Enqueue a null message using the newly-created session
|
// Enqueue a null message using the newly-created session
|
||||||
try {
|
try {
|
||||||
await singleProtoJobQueue.add(
|
const nullMessage = window.textsecure.messaging.getNullMessage({
|
||||||
window.textsecure.messaging.getNullMessage({
|
uuid: requesterUuid,
|
||||||
uuid: requesterUuid,
|
});
|
||||||
})
|
await handleMessageSend(
|
||||||
|
window.textsecure.messaging.sendIndividualProto({
|
||||||
|
...nullMessage,
|
||||||
|
options: sendOptions,
|
||||||
|
proto: Proto.Content.decode(
|
||||||
|
Bytes.fromBase64(nullMessage.protoBase64)
|
||||||
|
),
|
||||||
|
timestamp: Date.now(),
|
||||||
|
}),
|
||||||
|
{ messageIds: [], sendType: nullMessage.type }
|
||||||
);
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
log.error(
|
log.error(
|
||||||
'sendDistributionMessageOrNullMessage: Failed to queue null message',
|
'sendDistributionMessageOrNullMessage: Failed to send null message',
|
||||||
Errors.toLogFormat(error)
|
Errors.toLogFormat(error)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -363,12 +379,14 @@ async function maybeAddSenderKeyDistributionMessage({
|
||||||
messageIds,
|
messageIds,
|
||||||
requestGroupId,
|
requestGroupId,
|
||||||
requesterUuid,
|
requesterUuid,
|
||||||
|
timestamp,
|
||||||
}: {
|
}: {
|
||||||
contentProto: Proto.IContent;
|
contentProto: Proto.IContent;
|
||||||
logId: string;
|
logId: string;
|
||||||
messageIds: Array<string>;
|
messageIds: Array<string>;
|
||||||
requestGroupId?: string;
|
requestGroupId?: string;
|
||||||
requesterUuid: string;
|
requesterUuid: string;
|
||||||
|
timestamp: number;
|
||||||
}): Promise<{
|
}): Promise<{
|
||||||
contentProto: Proto.IContent;
|
contentProto: Proto.IContent;
|
||||||
groupId?: string;
|
groupId?: string;
|
||||||
|
@ -402,15 +420,17 @@ async function maybeAddSenderKeyDistributionMessage({
|
||||||
|
|
||||||
const senderKeyInfo = conversation.get('senderKeyInfo');
|
const senderKeyInfo = conversation.get('senderKeyInfo');
|
||||||
if (senderKeyInfo && senderKeyInfo.distributionId) {
|
if (senderKeyInfo && senderKeyInfo.distributionId) {
|
||||||
const senderKeyDistributionMessage =
|
const protoWithDistributionMessage =
|
||||||
await window.textsecure.messaging.getSenderKeyDistributionMessage(
|
await window.textsecure.messaging.getSenderKeyDistributionMessage(
|
||||||
senderKeyInfo.distributionId
|
senderKeyInfo.distributionId,
|
||||||
|
{ throwIfNotInDatabase: true, timestamp }
|
||||||
);
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
contentProto: {
|
contentProto: {
|
||||||
...contentProto,
|
...contentProto,
|
||||||
senderKeyDistributionMessage: senderKeyDistributionMessage.serialize(),
|
senderKeyDistributionMessage:
|
||||||
|
protoWithDistributionMessage.senderKeyDistributionMessage,
|
||||||
},
|
},
|
||||||
groupId: conversation.get('groupId'),
|
groupId: conversation.get('groupId'),
|
||||||
};
|
};
|
||||||
|
|
|
@ -7444,6 +7444,13 @@
|
||||||
"reasonCategory": "falseMatch",
|
"reasonCategory": "falseMatch",
|
||||||
"updated": "2020-07-21T18:34:59.251Z"
|
"updated": "2020-07-21T18:34:59.251Z"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"rule": "React-useRef",
|
||||||
|
"path": "ts/components/LeftPaneMainSearchInput.tsx",
|
||||||
|
"line": " const inputRef = useRef<HTMLInputElement | null>(null);",
|
||||||
|
"reasonCategory": "usageTrusted",
|
||||||
|
"updated": "2022-01-26T23:11:05.369Z"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"rule": "React-useRef",
|
"rule": "React-useRef",
|
||||||
"path": "ts/components/LeftPaneSearchInput.tsx",
|
"path": "ts/components/LeftPaneSearchInput.tsx",
|
||||||
|
@ -7487,13 +7494,6 @@
|
||||||
"reasonCategory": "usageTrusted",
|
"reasonCategory": "usageTrusted",
|
||||||
"updated": "2021-10-11T21:21:08.188Z"
|
"updated": "2021-10-11T21:21:08.188Z"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"rule": "React-useRef",
|
|
||||||
"path": "ts/components/LeftPaneMainSearchInput.tsx",
|
|
||||||
"line": " const inputRef = useRef<HTMLInputElement | null>(null);",
|
|
||||||
"reasonCategory": "usageTrusted",
|
|
||||||
"updated": "2022-01-26T23:11:05.369Z"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"rule": "React-useRef",
|
"rule": "React-useRef",
|
||||||
"path": "ts/components/Modal.tsx",
|
"path": "ts/components/Modal.tsx",
|
||||||
|
@ -7879,17 +7879,17 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"rule": "jQuery-load(",
|
"rule": "jQuery-load(",
|
||||||
"path": "ts/jobs/helpers/readAndViewSyncHelpers.js",
|
"path": "ts/jobs/helpers/syncHelpers.js",
|
||||||
"line": " await window.ConversationController.load();",
|
"line": " await window.ConversationController.load();",
|
||||||
"reasonCategory": "falseMatch",
|
"reasonCategory": "falseMatch",
|
||||||
"updated": "2021-12-15T19:58:28.089Z"
|
"updated": "2021-12-15T19:58:28.089Z"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"rule": "jQuery-load(",
|
"rule": "jQuery-load(",
|
||||||
"path": "ts/jobs/helpers/readAndViewSyncHelpers.ts",
|
"path": "ts/jobs/helpers/syncHelpers.ts",
|
||||||
"line": " await window.ConversationController.load();",
|
"line": " await window.ConversationController.load();",
|
||||||
"reasonCategory": "falseMatch",
|
"reasonCategory": "falseMatch",
|
||||||
"updated": "2021-12-15T19:58:28.089Z"
|
"updated": "2021-11-04T16:14:03.477Z"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"rule": "jQuery-load(",
|
"rule": "jQuery-load(",
|
||||||
|
@ -7903,7 +7903,7 @@
|
||||||
"path": "ts/jobs/normalMessageSendJobQueue.ts",
|
"path": "ts/jobs/normalMessageSendJobQueue.ts",
|
||||||
"line": " await window.ConversationController.load();",
|
"line": " await window.ConversationController.load();",
|
||||||
"reasonCategory": "falseMatch",
|
"reasonCategory": "falseMatch",
|
||||||
"updated": "2021-11-04T16:14:03.477Z"
|
"updated": "2021-12-15T19:58:28.089Z"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"rule": "jQuery-load(",
|
"rule": "jQuery-load(",
|
||||||
|
@ -8402,4 +8402,4 @@
|
||||||
"reasonCategory": "usageTrusted",
|
"reasonCategory": "usageTrusted",
|
||||||
"updated": "2021-09-17T21:02:59.414Z"
|
"updated": "2021-09-17T21:02:59.414Z"
|
||||||
}
|
}
|
||||||
]
|
]
|
Loading…
Add table
Add a link
Reference in a new issue