Additional logging, more reliable contact/group sync fetch
This commit is contained in:
parent
8e1a30d720
commit
efc237d106
11 changed files with 86 additions and 16 deletions
|
@ -241,6 +241,8 @@ export class SignalProtocolStore extends EventEmitter {
|
||||||
|
|
||||||
sessionQueues = new Map<SessionIdType, PQueue>();
|
sessionQueues = new Map<SessionIdType, PQueue>();
|
||||||
|
|
||||||
|
sessionQueueJobCounter = 0;
|
||||||
|
|
||||||
private readonly identityQueues = new Map<UUIDStringType, PQueue>();
|
private readonly identityQueues = new Map<UUIDStringType, PQueue>();
|
||||||
|
|
||||||
private currentZone?: Zone;
|
private currentZone?: Zone;
|
||||||
|
@ -703,13 +705,31 @@ export class SignalProtocolStore extends EventEmitter {
|
||||||
|
|
||||||
async enqueueSessionJob<T>(
|
async enqueueSessionJob<T>(
|
||||||
qualifiedAddress: QualifiedAddress,
|
qualifiedAddress: QualifiedAddress,
|
||||||
|
name: string,
|
||||||
task: () => Promise<T>,
|
task: () => Promise<T>,
|
||||||
zone: Zone = GLOBAL_ZONE
|
zone: Zone = GLOBAL_ZONE
|
||||||
): Promise<T> {
|
): Promise<T> {
|
||||||
|
this.sessionQueueJobCounter += 1;
|
||||||
|
const id = this.sessionQueueJobCounter;
|
||||||
|
|
||||||
|
const waitStart = Date.now();
|
||||||
|
|
||||||
return this.withZone(zone, 'enqueueSessionJob', async () => {
|
return this.withZone(zone, 'enqueueSessionJob', async () => {
|
||||||
const queue = this._getSessionQueue(qualifiedAddress);
|
const queue = this._getSessionQueue(qualifiedAddress);
|
||||||
|
|
||||||
return queue.add<T>(task);
|
const waitTime = Date.now() - waitStart;
|
||||||
|
log.info(
|
||||||
|
`enqueueSessionJob(${id}): queuing task ${name}, waited ${waitTime}ms`
|
||||||
|
);
|
||||||
|
const queueStart = Date.now();
|
||||||
|
|
||||||
|
return queue.add<T>(() => {
|
||||||
|
const queueTime = Date.now() - queueStart;
|
||||||
|
log.info(
|
||||||
|
`enqueueSessionJob(${id}): running task ${name}, waited ${queueTime}ms`
|
||||||
|
);
|
||||||
|
return task();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1322,6 +1342,7 @@ export class SignalProtocolStore extends EventEmitter {
|
||||||
|
|
||||||
await this.enqueueSessionJob(
|
await this.enqueueSessionJob(
|
||||||
addr,
|
addr,
|
||||||
|
`_archiveSession(${addr.toString()})`,
|
||||||
async () => {
|
async () => {
|
||||||
const item = entry.hydrated
|
const item = entry.hydrated
|
||||||
? entry.item
|
? entry.item
|
||||||
|
|
|
@ -3279,7 +3279,7 @@ async function getTapToViewMessagesNeedingErase(): Promise<Array<MessageType>> {
|
||||||
return rows.map(row => jsonToObject(row.json));
|
return rows.map(row => jsonToObject(row.json));
|
||||||
}
|
}
|
||||||
|
|
||||||
const MAX_UNPROCESSED_ATTEMPTS = 3;
|
const MAX_UNPROCESSED_ATTEMPTS = 10;
|
||||||
|
|
||||||
function saveUnprocessedSync(data: UnprocessedType): string {
|
function saveUnprocessedSync(data: UnprocessedType): string {
|
||||||
const db = getInstance();
|
const db = getInstance();
|
||||||
|
|
|
@ -1542,7 +1542,7 @@ describe('SignalProtocolStore', () => {
|
||||||
id: '1-one',
|
id: '1-one',
|
||||||
version: 2,
|
version: 2,
|
||||||
|
|
||||||
attempts: 3,
|
attempts: 10,
|
||||||
envelope: 'first',
|
envelope: 'first',
|
||||||
receivedAtCounter: 0,
|
receivedAtCounter: 0,
|
||||||
timestamp: NOW + 1,
|
timestamp: NOW + 1,
|
||||||
|
|
|
@ -21,7 +21,9 @@ import WebSocketResource from '../textsecure/WebsocketResources';
|
||||||
describe('WebSocket-Resource', () => {
|
describe('WebSocket-Resource', () => {
|
||||||
class FakeSocket extends EventEmitter {
|
class FakeSocket extends EventEmitter {
|
||||||
public sendBytes(_: Uint8Array) {}
|
public sendBytes(_: Uint8Array) {}
|
||||||
|
public socket = {
|
||||||
|
localPort: 5678,
|
||||||
|
};
|
||||||
public close() {}
|
public close() {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -47,7 +47,7 @@ import { normalizeUuid } from '../util/normalizeUuid';
|
||||||
import { parseIntOrThrow } from '../util/parseIntOrThrow';
|
import { parseIntOrThrow } from '../util/parseIntOrThrow';
|
||||||
import { clearTimeoutIfNecessary } from '../util/clearTimeoutIfNecessary';
|
import { clearTimeoutIfNecessary } from '../util/clearTimeoutIfNecessary';
|
||||||
import { Zone } from '../util/Zone';
|
import { Zone } from '../util/Zone';
|
||||||
import { DurationInSeconds } from '../util/durations';
|
import { DurationInSeconds, SECOND } from '../util/durations';
|
||||||
import { bytesToUuid } from '../Crypto';
|
import { bytesToUuid } from '../Crypto';
|
||||||
import type { DownloadedAttachmentType } from '../types/Attachment';
|
import type { DownloadedAttachmentType } from '../types/Attachment';
|
||||||
import { Address } from '../types/Address';
|
import { Address } from '../types/Address';
|
||||||
|
@ -1752,6 +1752,7 @@ export default class MessageReceiver
|
||||||
);
|
);
|
||||||
const unsealedPlaintext = await this.storage.protocol.enqueueSessionJob(
|
const unsealedPlaintext = await this.storage.protocol.enqueueSessionJob(
|
||||||
address,
|
address,
|
||||||
|
`sealedSenderDecryptMessage(${address.toString()})`,
|
||||||
() =>
|
() =>
|
||||||
sealedSenderDecryptMessage(
|
sealedSenderDecryptMessage(
|
||||||
Buffer.from(ciphertext),
|
Buffer.from(ciphertext),
|
||||||
|
@ -1839,6 +1840,7 @@ export default class MessageReceiver
|
||||||
|
|
||||||
const plaintext = await this.storage.protocol.enqueueSessionJob(
|
const plaintext = await this.storage.protocol.enqueueSessionJob(
|
||||||
address,
|
address,
|
||||||
|
`signalDecrypt(${address.toString()})`,
|
||||||
async () =>
|
async () =>
|
||||||
this.unpad(
|
this.unpad(
|
||||||
await signalDecrypt(
|
await signalDecrypt(
|
||||||
|
@ -1870,6 +1872,7 @@ export default class MessageReceiver
|
||||||
|
|
||||||
const plaintext = await this.storage.protocol.enqueueSessionJob(
|
const plaintext = await this.storage.protocol.enqueueSessionJob(
|
||||||
address,
|
address,
|
||||||
|
`signalDecryptPreKey(${address.toString()})`,
|
||||||
async () =>
|
async () =>
|
||||||
this.unpad(
|
this.unpad(
|
||||||
await signalDecryptPreKey(
|
await signalDecryptPreKey(
|
||||||
|
@ -3397,7 +3400,10 @@ export default class MessageReceiver
|
||||||
|
|
||||||
this.removeFromCache(envelope);
|
this.removeFromCache(envelope);
|
||||||
|
|
||||||
const attachmentPointer = await this.handleAttachment(blob);
|
const attachmentPointer = await this.handleAttachment(blob, {
|
||||||
|
disableRetries: true,
|
||||||
|
timeout: 90 * SECOND,
|
||||||
|
});
|
||||||
const contactBuffer = new ContactBuffer(attachmentPointer.data);
|
const contactBuffer = new ContactBuffer(attachmentPointer.data);
|
||||||
|
|
||||||
const contactSync = new ContactSyncEvent(
|
const contactSync = new ContactSyncEvent(
|
||||||
|
@ -3430,7 +3436,10 @@ export default class MessageReceiver
|
||||||
|
|
||||||
// Note: we do not return here because we don't want to block the next message on
|
// Note: we do not return here because we don't want to block the next message on
|
||||||
// this attachment download and a lot of processing of that attachment.
|
// this attachment download and a lot of processing of that attachment.
|
||||||
const attachmentPointer = await this.handleAttachment(blob);
|
const attachmentPointer = await this.handleAttachment(blob, {
|
||||||
|
disableRetries: true,
|
||||||
|
timeout: 90 * SECOND,
|
||||||
|
});
|
||||||
const groupBuffer = new GroupBuffer(attachmentPointer.data);
|
const groupBuffer = new GroupBuffer(attachmentPointer.data);
|
||||||
let groupDetails = groupBuffer.next();
|
let groupDetails = groupBuffer.next();
|
||||||
const promises = [];
|
const promises = [];
|
||||||
|
@ -3550,10 +3559,11 @@ export default class MessageReceiver
|
||||||
}
|
}
|
||||||
|
|
||||||
private async handleAttachment(
|
private async handleAttachment(
|
||||||
attachment: Proto.IAttachmentPointer
|
attachment: Proto.IAttachmentPointer,
|
||||||
|
options?: { timeout?: number; disableRetries?: boolean }
|
||||||
): Promise<DownloadedAttachmentType> {
|
): Promise<DownloadedAttachmentType> {
|
||||||
const cleaned = processAttachment(attachment);
|
const cleaned = processAttachment(attachment);
|
||||||
return downloadAttachment(this.server, cleaned);
|
return downloadAttachment(this.server, cleaned, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async handleEndSession(
|
private async handleEndSession(
|
||||||
|
|
|
@ -440,6 +440,7 @@ export default class OutgoingMessage {
|
||||||
|
|
||||||
return window.textsecure.storage.protocol.enqueueSessionJob<MessageType>(
|
return window.textsecure.storage.protocol.enqueueSessionJob<MessageType>(
|
||||||
address,
|
address,
|
||||||
|
`doSendMessage(${address.toString()})`,
|
||||||
async () => {
|
async () => {
|
||||||
const protocolAddress = ProtocolAddress.new(
|
const protocolAddress = ProtocolAddress.new(
|
||||||
theirUuid.toString(),
|
theirUuid.toString(),
|
||||||
|
|
|
@ -225,7 +225,9 @@ export class SocketManager extends EventListener {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
log.info('SocketManager: connected authenticated socket');
|
log.info(
|
||||||
|
`SocketManager: connected authenticated socket (localPort: ${authenticated.localPort})`
|
||||||
|
);
|
||||||
|
|
||||||
window.logAuthenticatedConnect?.();
|
window.logAuthenticatedConnect?.();
|
||||||
this.backOff.reset();
|
this.backOff.reset();
|
||||||
|
@ -503,7 +505,9 @@ export class SocketManager extends EventListener {
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
|
|
||||||
log.info('SocketManager: connected unauthenticated socket');
|
log.info(
|
||||||
|
`SocketManager: connected unauthenticated socket (localPort: ${unauthenticated.localPort})`
|
||||||
|
);
|
||||||
|
|
||||||
unauthenticated.addEventListener('close', ({ code, reason }): void => {
|
unauthenticated.addEventListener('close', ({ code, reason }): void => {
|
||||||
if (this.unauthenticated !== process) {
|
if (this.unauthenticated !== process) {
|
||||||
|
|
|
@ -130,6 +130,7 @@ type PromiseAjaxOptionsType = {
|
||||||
certificateAuthority?: string;
|
certificateAuthority?: string;
|
||||||
contentType?: string;
|
contentType?: string;
|
||||||
data?: Uint8Array | string;
|
data?: Uint8Array | string;
|
||||||
|
disableRetries?: boolean;
|
||||||
disableSessionResumption?: boolean;
|
disableSessionResumption?: boolean;
|
||||||
headers?: HeaderListType;
|
headers?: HeaderListType;
|
||||||
host?: string;
|
host?: string;
|
||||||
|
@ -451,6 +452,10 @@ async function _outerAjax(
|
||||||
): Promise<unknown> {
|
): Promise<unknown> {
|
||||||
options.stack = new Error().stack; // just in case, save stack here.
|
options.stack = new Error().stack; // just in case, save stack here.
|
||||||
|
|
||||||
|
if (options.disableRetries) {
|
||||||
|
return _promiseAjax(url, options);
|
||||||
|
}
|
||||||
|
|
||||||
return _retryAjax(url, options);
|
return _retryAjax(url, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -868,7 +873,14 @@ export type WebAPIType = {
|
||||||
imageFiles: Array<string>
|
imageFiles: Array<string>
|
||||||
) => Promise<Array<Uint8Array>>;
|
) => Promise<Array<Uint8Array>>;
|
||||||
getArtAuth: () => Promise<ArtAuthType>;
|
getArtAuth: () => Promise<ArtAuthType>;
|
||||||
getAttachment: (cdnKey: string, cdnNumber?: number) => Promise<Uint8Array>;
|
getAttachment: (
|
||||||
|
cdnKey: string,
|
||||||
|
cdnNumber?: number,
|
||||||
|
options?: {
|
||||||
|
disableRetries?: boolean;
|
||||||
|
timeout?: number;
|
||||||
|
}
|
||||||
|
) => Promise<Uint8Array>;
|
||||||
getAvatar: (path: string) => Promise<Uint8Array>;
|
getAvatar: (path: string) => Promise<Uint8Array>;
|
||||||
getDevices: () => Promise<GetDevicesResultType>;
|
getDevices: () => Promise<GetDevicesResultType>;
|
||||||
getHasSubscription: (subscriberId: Uint8Array) => Promise<boolean>;
|
getHasSubscription: (subscriberId: Uint8Array) => Promise<boolean>;
|
||||||
|
@ -2473,7 +2485,14 @@ export function initialize({
|
||||||
return packId;
|
return packId;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getAttachment(cdnKey: string, cdnNumber?: number) {
|
async function getAttachment(
|
||||||
|
cdnKey: string,
|
||||||
|
cdnNumber?: number,
|
||||||
|
options?: {
|
||||||
|
disableRetries?: boolean;
|
||||||
|
timeout?: number;
|
||||||
|
}
|
||||||
|
) {
|
||||||
const abortController = new AbortController();
|
const abortController = new AbortController();
|
||||||
|
|
||||||
const cdnUrl = isNumber(cdnNumber)
|
const cdnUrl = isNumber(cdnNumber)
|
||||||
|
@ -2482,9 +2501,10 @@ export function initialize({
|
||||||
// This is going to the CDN, not the service, so we use _outerAjax
|
// This is going to the CDN, not the service, so we use _outerAjax
|
||||||
const stream = await _outerAjax(`${cdnUrl}/attachments/${cdnKey}`, {
|
const stream = await _outerAjax(`${cdnUrl}/attachments/${cdnKey}`, {
|
||||||
certificateAuthority,
|
certificateAuthority,
|
||||||
|
disableRetries: options?.disableRetries,
|
||||||
proxyUrl,
|
proxyUrl,
|
||||||
responseType: 'stream',
|
responseType: 'stream',
|
||||||
timeout: 0,
|
timeout: options?.timeout || 0,
|
||||||
type: 'GET',
|
type: 'GET',
|
||||||
redactUrl: _createRedactor(cdnKey),
|
redactUrl: _createRedactor(cdnKey),
|
||||||
version,
|
version,
|
||||||
|
|
|
@ -126,6 +126,8 @@ export default class WebSocketResource extends EventTarget {
|
||||||
|
|
||||||
private readonly logId: string;
|
private readonly logId: string;
|
||||||
|
|
||||||
|
public readonly localPort: number | undefined;
|
||||||
|
|
||||||
// Public for tests
|
// Public for tests
|
||||||
public readonly keepalive?: KeepAlive;
|
public readonly keepalive?: KeepAlive;
|
||||||
|
|
||||||
|
@ -136,6 +138,7 @@ export default class WebSocketResource extends EventTarget {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
this.logId = `WebSocketResource(${options.name})`;
|
this.logId = `WebSocketResource(${options.name})`;
|
||||||
|
this.localPort = socket.socket.localPort;
|
||||||
|
|
||||||
this.boundOnMessage = this.onMessage.bind(this);
|
this.boundOnMessage = this.onMessage.bind(this);
|
||||||
|
|
||||||
|
|
|
@ -15,7 +15,11 @@ import type { WebAPIType } from './WebAPI';
|
||||||
|
|
||||||
export async function downloadAttachment(
|
export async function downloadAttachment(
|
||||||
server: WebAPIType,
|
server: WebAPIType,
|
||||||
attachment: ProcessedAttachment
|
attachment: ProcessedAttachment,
|
||||||
|
options?: {
|
||||||
|
disableRetries?: boolean;
|
||||||
|
timeout?: number;
|
||||||
|
}
|
||||||
): Promise<DownloadedAttachmentType> {
|
): Promise<DownloadedAttachmentType> {
|
||||||
const cdnId = attachment.cdnId || attachment.cdnKey;
|
const cdnId = attachment.cdnId || attachment.cdnKey;
|
||||||
const { cdnNumber } = attachment;
|
const { cdnNumber } = attachment;
|
||||||
|
@ -25,7 +29,11 @@ export async function downloadAttachment(
|
||||||
}
|
}
|
||||||
|
|
||||||
strictAssert(cdnId, 'attachment without cdnId');
|
strictAssert(cdnId, 'attachment without cdnId');
|
||||||
const encrypted = await server.getAttachment(cdnId, dropNull(cdnNumber));
|
const encrypted = await server.getAttachment(
|
||||||
|
cdnId,
|
||||||
|
dropNull(cdnNumber),
|
||||||
|
options
|
||||||
|
);
|
||||||
const { key, digest, size, contentType } = attachment;
|
const { key, digest, size, contentType } = attachment;
|
||||||
|
|
||||||
if (!digest) {
|
if (!digest) {
|
||||||
|
|
|
@ -154,6 +154,7 @@ async function handleServerKeys(
|
||||||
try {
|
try {
|
||||||
await window.textsecure.storage.protocol.enqueueSessionJob(
|
await window.textsecure.storage.protocol.enqueueSessionJob(
|
||||||
address,
|
address,
|
||||||
|
`handleServerKeys(${identifier})`,
|
||||||
() =>
|
() =>
|
||||||
processPreKeyBundle(
|
processPreKeyBundle(
|
||||||
preKeyBundle,
|
preKeyBundle,
|
||||||
|
|
Loading…
Reference in a new issue