Sender Key: Prepare for production
This commit is contained in:
parent
f226822dff
commit
bff3f0c74a
14 changed files with 334 additions and 183 deletions
|
@ -43,14 +43,14 @@ message UnidentifiedSenderMessage {
|
||||||
}
|
}
|
||||||
|
|
||||||
enum ContentHint {
|
enum ContentHint {
|
||||||
// Our parser does not handle reserved in enums: DESKTOP-1569
|
// Show an error immediately; it was important but we can't retry.
|
||||||
// reserved 0; // A content hint of "default" should never be encoded.
|
DEFAULT = 0;
|
||||||
|
|
||||||
// Do not insert an error.
|
// Sender will try to resend; delay any error UI if possible
|
||||||
SUPPLEMENTARY = 1;
|
RESENDABLE = 1;
|
||||||
|
|
||||||
// Put an invisible placeholder in the chat (using the groupId from the sealed sender envelope if available) and delay showing an error until later.
|
// Don't show any error UI at all; this is something sent implicitly like a typing message or a receipt
|
||||||
RESENDABLE = 2;
|
IMPLICIT = 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
optional Type type = 1;
|
optional Type type = 1;
|
||||||
|
|
|
@ -11,7 +11,10 @@ export type ConfigKeyType =
|
||||||
| 'desktop.gv2'
|
| 'desktop.gv2'
|
||||||
| 'desktop.mandatoryProfileSharing'
|
| 'desktop.mandatoryProfileSharing'
|
||||||
| 'desktop.messageRequests'
|
| 'desktop.messageRequests'
|
||||||
|
| 'desktop.retryReceiptLifespan'
|
||||||
|
| 'desktop.retryRespondMaxAge'
|
||||||
| 'desktop.screensharing2'
|
| 'desktop.screensharing2'
|
||||||
|
| 'desktop.sendSenderKey'
|
||||||
| 'desktop.storage'
|
| 'desktop.storage'
|
||||||
| 'desktop.storageWrite3'
|
| 'desktop.storageWrite3'
|
||||||
| 'desktop.worksAtSignal'
|
| 'desktop.worksAtSignal'
|
||||||
|
|
246
ts/background.ts
246
ts/background.ts
|
@ -425,30 +425,6 @@ export async function startApp(): Promise<void> {
|
||||||
first = false;
|
first = false;
|
||||||
|
|
||||||
cleanupSessionResets();
|
cleanupSessionResets();
|
||||||
const retryPlaceholders = new window.Signal.Util.RetryPlaceholders();
|
|
||||||
window.Signal.Services.retryPlaceholders = retryPlaceholders;
|
|
||||||
|
|
||||||
setInterval(async () => {
|
|
||||||
const expired = await retryPlaceholders.getExpiredAndRemove();
|
|
||||||
window.log.info(
|
|
||||||
`retryPlaceholders/interval: Found ${expired.length} expired items`
|
|
||||||
);
|
|
||||||
expired.forEach(item => {
|
|
||||||
const { conversationId, senderUuid } = item;
|
|
||||||
const conversation = window.ConversationController.get(conversationId);
|
|
||||||
if (conversation) {
|
|
||||||
const receivedAt = Date.now();
|
|
||||||
const receivedAtCounter = window.Signal.Util.incrementMessageCounter();
|
|
||||||
conversation.queueJob(() =>
|
|
||||||
conversation.addDeliveryIssue({
|
|
||||||
receivedAt,
|
|
||||||
receivedAtCounter,
|
|
||||||
senderUuid,
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}, FIVE_MINUTES);
|
|
||||||
|
|
||||||
// These make key operations available to IPC handlers created in preload.js
|
// These make key operations available to IPC handlers created in preload.js
|
||||||
window.Events = {
|
window.Events = {
|
||||||
|
@ -835,6 +811,46 @@ export async function startApp(): Promise<void> {
|
||||||
// we generate on load of each convo.
|
// we generate on load of each convo.
|
||||||
window.Signal.RemoteConfig.initRemoteConfig();
|
window.Signal.RemoteConfig.initRemoteConfig();
|
||||||
|
|
||||||
|
let retryReceiptLifespan: number | undefined;
|
||||||
|
try {
|
||||||
|
retryReceiptLifespan = parseIntOrThrow(
|
||||||
|
window.Signal.RemoteConfig.getValue('desktop.retryReceiptLifespan'),
|
||||||
|
'retryReceiptLifeSpan'
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
|
window.log.warn(
|
||||||
|
'Failed to parse integer out of desktop.retryReceiptLifespan feature flag',
|
||||||
|
error && error.stack ? error.stack : error
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const retryPlaceholders = new window.Signal.Util.RetryPlaceholders({
|
||||||
|
retryReceiptLifespan,
|
||||||
|
});
|
||||||
|
window.Signal.Services.retryPlaceholders = retryPlaceholders;
|
||||||
|
|
||||||
|
setInterval(async () => {
|
||||||
|
const expired = await retryPlaceholders.getExpiredAndRemove();
|
||||||
|
window.log.info(
|
||||||
|
`retryPlaceholders/interval: Found ${expired.length} expired items`
|
||||||
|
);
|
||||||
|
expired.forEach(item => {
|
||||||
|
const { conversationId, senderUuid } = item;
|
||||||
|
const conversation = window.ConversationController.get(conversationId);
|
||||||
|
if (conversation) {
|
||||||
|
const receivedAt = Date.now();
|
||||||
|
const receivedAtCounter = window.Signal.Util.incrementMessageCounter();
|
||||||
|
conversation.queueJob(() =>
|
||||||
|
conversation.addDeliveryIssue({
|
||||||
|
receivedAt,
|
||||||
|
receivedAtCounter,
|
||||||
|
senderUuid,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}, FIVE_MINUTES);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
window.ConversationController.load(),
|
window.ConversationController.load(),
|
||||||
|
@ -2148,7 +2164,9 @@ export async function startApp(): Promise<void> {
|
||||||
await server.registerCapabilities({
|
await server.registerCapabilities({
|
||||||
'gv2-3': true,
|
'gv2-3': true,
|
||||||
'gv1-migration': true,
|
'gv1-migration': true,
|
||||||
senderKey: false,
|
senderKey: window.Signal.RemoteConfig.isEnabled(
|
||||||
|
'desktop.sendSenderKey'
|
||||||
|
),
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
window.log.error(
|
window.log.error(
|
||||||
|
@ -3408,13 +3426,106 @@ export async function startApp(): Promise<void> {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function archiveSessionOnMatch({
|
||||||
|
requesterUuid,
|
||||||
|
requesterDevice,
|
||||||
|
senderDevice,
|
||||||
|
}: RetryRequestType): Promise<void> {
|
||||||
|
const ourDeviceId = parseIntOrThrow(
|
||||||
|
window.textsecure.storage.user.getDeviceId(),
|
||||||
|
'archiveSessionOnMatch/getDeviceId'
|
||||||
|
);
|
||||||
|
if (ourDeviceId === senderDevice) {
|
||||||
|
const address = `${requesterUuid}.${requesterDevice}`;
|
||||||
|
window.log.info(
|
||||||
|
'archiveSessionOnMatch: Devices match, archiving session'
|
||||||
|
);
|
||||||
|
await window.textsecure.storage.protocol.archiveSession(address);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function sendDistributionMessageOrNullMessage(
|
||||||
|
options: RetryRequestType
|
||||||
|
): Promise<void> {
|
||||||
|
const { groupId, requesterUuid } = options;
|
||||||
|
let sentDistributionMessage = false;
|
||||||
|
window.log.info('sendDistributionMessageOrNullMessage: Starting', {
|
||||||
|
groupId: groupId ? `groupv2(${groupId})` : undefined,
|
||||||
|
requesterUuid,
|
||||||
|
});
|
||||||
|
|
||||||
|
await archiveSessionOnMatch(options);
|
||||||
|
|
||||||
|
const conversation = window.ConversationController.getOrCreate(
|
||||||
|
requesterUuid,
|
||||||
|
'private'
|
||||||
|
);
|
||||||
|
|
||||||
|
if (groupId) {
|
||||||
|
const group = window.ConversationController.get(groupId);
|
||||||
|
const distributionId = group?.get('senderKeyInfo')?.distributionId;
|
||||||
|
|
||||||
|
if (group && distributionId) {
|
||||||
|
window.log.info(
|
||||||
|
'sendDistributionMessageOrNullMessage: Found matching group, sending sender key distribution message'
|
||||||
|
);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const {
|
||||||
|
ContentHint,
|
||||||
|
} = window.textsecure.protobuf.UnidentifiedSenderMessage.Message;
|
||||||
|
|
||||||
|
const result = await window.textsecure.messaging.sendSenderKeyDistributionMessage(
|
||||||
|
{
|
||||||
|
contentHint: ContentHint.DEFAULT,
|
||||||
|
distributionId,
|
||||||
|
groupId,
|
||||||
|
identifiers: [requesterUuid],
|
||||||
|
}
|
||||||
|
);
|
||||||
|
if (result.errors && result.errors.length > 0) {
|
||||||
|
throw result.errors[0];
|
||||||
|
}
|
||||||
|
sentDistributionMessage = true;
|
||||||
|
} catch (error) {
|
||||||
|
window.log.error(
|
||||||
|
'sendDistributionMessageOrNullMessage: Failed to send sender key distribution message',
|
||||||
|
error && error.stack ? error.stack : error
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!sentDistributionMessage) {
|
||||||
|
window.log.info(
|
||||||
|
'sendDistributionMessageOrNullMessage: Did not send distribution message, sending null message'
|
||||||
|
);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const sendOptions = await conversation.getSendOptions();
|
||||||
|
const result = await window.textsecure.messaging.sendNullMessage(
|
||||||
|
{ uuid: requesterUuid },
|
||||||
|
sendOptions
|
||||||
|
);
|
||||||
|
if (result.errors && result.errors.length > 0) {
|
||||||
|
throw result.errors[0];
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
window.log.error(
|
||||||
|
'maybeSendDistributionMessage: Failed to send null message',
|
||||||
|
error && error.stack ? error.stack : error
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function onRetryRequest(event: RetryRequestEventType) {
|
async function onRetryRequest(event: RetryRequestEventType) {
|
||||||
const { retryRequest } = event;
|
const { retryRequest } = event;
|
||||||
const {
|
const {
|
||||||
requesterUuid,
|
|
||||||
requesterDevice,
|
requesterDevice,
|
||||||
sentAt,
|
requesterUuid,
|
||||||
senderDevice,
|
senderDevice,
|
||||||
|
sentAt,
|
||||||
} = retryRequest;
|
} = retryRequest;
|
||||||
const logId = `${requesterUuid}.${requesterDevice} ${sentAt}-${senderDevice}`;
|
const logId = `${requesterUuid}.${requesterDevice} ${sentAt}-${senderDevice}`;
|
||||||
|
|
||||||
|
@ -3447,6 +3558,7 @@ export async function startApp(): Promise<void> {
|
||||||
|
|
||||||
if (!targetMessage) {
|
if (!targetMessage) {
|
||||||
window.log.info(`onRetryRequest/${logId}: Did not find message`);
|
window.log.info(`onRetryRequest/${logId}: Did not find message`);
|
||||||
|
await sendDistributionMessageOrNullMessage(retryRequest);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3454,47 +3566,36 @@ export async function startApp(): Promise<void> {
|
||||||
window.log.info(
|
window.log.info(
|
||||||
`onRetryRequest/${logId}: Message is erased, refusing to send again.`
|
`onRetryRequest/${logId}: Message is erased, refusing to send again.`
|
||||||
);
|
);
|
||||||
|
await sendDistributionMessageOrNullMessage(retryRequest);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const HOUR = 60 * 60 * 1000;
|
const HOUR = 60 * 60 * 1000;
|
||||||
const ONE_DAY = 24 * HOUR;
|
const ONE_DAY = 24 * HOUR;
|
||||||
if (isOlderThan(sentAt, ONE_DAY)) {
|
let retryRespondMaxAge = ONE_DAY;
|
||||||
|
try {
|
||||||
|
retryRespondMaxAge = parseIntOrThrow(
|
||||||
|
window.Signal.RemoteConfig.getValue('desktop.retryRespondMaxAge'),
|
||||||
|
'retryRespondMaxAge'
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
|
window.log.warn(
|
||||||
|
`onRetryRequest/${logId}: Failed to parse integer from desktop.retryRespondMaxAge feature flag`,
|
||||||
|
error && error.stack ? error.stack : error
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isOlderThan(sentAt, retryRespondMaxAge)) {
|
||||||
window.log.info(
|
window.log.info(
|
||||||
`onRetryRequest/${logId}: Message is too old, refusing to send again.`
|
`onRetryRequest/${logId}: Message is too old, refusing to send again.`
|
||||||
);
|
);
|
||||||
|
await sendDistributionMessageOrNullMessage(retryRequest);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const sentUnidentified = isInList(
|
|
||||||
requesterConversation,
|
|
||||||
targetMessage.get('unidentifiedDeliveries')
|
|
||||||
);
|
|
||||||
const wasDelivered = isInList(
|
|
||||||
requesterConversation,
|
|
||||||
targetMessage.get('delivered_to')
|
|
||||||
);
|
|
||||||
if (sentUnidentified && wasDelivered) {
|
|
||||||
window.log.info(
|
|
||||||
`onRetryRequest/${logId}: Message was sent sealed sender and was delivered, refusing to send again.`
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const ourDeviceId = parseIntOrThrow(
|
|
||||||
window.textsecure.storage.user.getDeviceId(),
|
|
||||||
'onRetryRequest/getDeviceId'
|
|
||||||
);
|
|
||||||
if (ourDeviceId === senderDevice) {
|
|
||||||
const address = `${requesterUuid}.${requesterDevice}`;
|
|
||||||
window.log.info(
|
|
||||||
`onRetryRequest/${logId}: Devices match, archiving session`
|
|
||||||
);
|
|
||||||
await window.textsecure.storage.protocol.archiveSession(address);
|
|
||||||
}
|
|
||||||
|
|
||||||
window.log.info(`onRetryRequest/${logId}: Resending message`);
|
window.log.info(`onRetryRequest/${logId}: Resending message`);
|
||||||
targetMessage.resend(requesterUuid);
|
await archiveSessionOnMatch(retryRequest);
|
||||||
|
await targetMessage.resend(requesterUuid);
|
||||||
}
|
}
|
||||||
|
|
||||||
type DecryptionErrorEventType = Event & {
|
type DecryptionErrorEventType = Event & {
|
||||||
|
@ -3558,16 +3659,6 @@ export async function startApp(): Promise<void> {
|
||||||
);
|
);
|
||||||
const conversation = group || sender;
|
const conversation = group || sender;
|
||||||
|
|
||||||
function immediatelyAddError() {
|
|
||||||
conversation.queueJob(async () => {
|
|
||||||
conversation.addDeliveryIssue({
|
|
||||||
receivedAt: receivedAtDate,
|
|
||||||
receivedAtCounter,
|
|
||||||
senderUuid,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// 2. Send resend request
|
// 2. Send resend request
|
||||||
|
|
||||||
if (!cipherTextBytes || !isNumber(cipherTextType)) {
|
if (!cipherTextBytes || !isNumber(cipherTextType)) {
|
||||||
|
@ -3611,14 +3702,7 @@ export async function startApp(): Promise<void> {
|
||||||
|
|
||||||
// 3. Determine how to represent this to the user. Three different options.
|
// 3. Determine how to represent this to the user. Three different options.
|
||||||
|
|
||||||
// This is a sync message of some kind that cannot be resent. Reset session but don't
|
// We believe that it could be successfully re-sent, so we'll add a placeholder.
|
||||||
// show any UI for it.
|
|
||||||
if (contentHint === ContentHint.SUPPLEMENTARY) {
|
|
||||||
scheduleSessionReset(senderUuid, senderDevice);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// If we request a re-send, it might just work out for us!
|
|
||||||
if (contentHint === ContentHint.RESENDABLE) {
|
if (contentHint === ContentHint.RESENDABLE) {
|
||||||
const { retryPlaceholders } = window.Signal.Services;
|
const { retryPlaceholders } = window.Signal.Services;
|
||||||
assert(retryPlaceholders, 'requestResend: adding placeholder');
|
assert(retryPlaceholders, 'requestResend: adding placeholder');
|
||||||
|
@ -3635,10 +3719,22 @@ export async function startApp(): Promise<void> {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// This message cannot be resent. We'll show no error and trust the other side to
|
||||||
|
// reset their session.
|
||||||
|
if (contentHint === ContentHint.IMPLICIT) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
window.log.warn(
|
window.log.warn(
|
||||||
`requestResend/${logId}: No content hint, adding error immediately`
|
`requestResend/${logId}: No content hint, adding error immediately`
|
||||||
);
|
);
|
||||||
immediatelyAddError();
|
conversation.queueJob(async () => {
|
||||||
|
conversation.addDeliveryIssue({
|
||||||
|
receivedAt: receivedAtDate,
|
||||||
|
receivedAtCounter,
|
||||||
|
senderUuid,
|
||||||
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function scheduleSessionReset(senderUuid: string, senderDevice: number) {
|
function scheduleSessionReset(senderUuid: string, senderDevice: number) {
|
||||||
|
|
|
@ -1322,7 +1322,7 @@ export async function modifyGroupV2({
|
||||||
profileKey,
|
profileKey,
|
||||||
},
|
},
|
||||||
conversation,
|
conversation,
|
||||||
ContentHint.SUPPLEMENTARY,
|
ContentHint.DEFAULT,
|
||||||
sendOptions
|
sendOptions
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
@ -1696,7 +1696,7 @@ export async function createGroupV2({
|
||||||
profileKey,
|
profileKey,
|
||||||
},
|
},
|
||||||
conversation,
|
conversation,
|
||||||
ContentHint.SUPPLEMENTARY,
|
ContentHint.DEFAULT,
|
||||||
sendOptions
|
sendOptions
|
||||||
),
|
),
|
||||||
timestamp,
|
timestamp,
|
||||||
|
@ -2226,7 +2226,7 @@ export async function initiateMigrationToGroupV2(
|
||||||
profileKey: ourProfileKey,
|
profileKey: ourProfileKey,
|
||||||
},
|
},
|
||||||
conversation,
|
conversation,
|
||||||
ContentHint.SUPPLEMENTARY,
|
ContentHint.DEFAULT,
|
||||||
sendOptions
|
sendOptions
|
||||||
),
|
),
|
||||||
timestamp,
|
timestamp,
|
||||||
|
|
|
@ -1201,7 +1201,7 @@ export class ConversationModel extends window.Backbone
|
||||||
timestamp,
|
timestamp,
|
||||||
groupMembers,
|
groupMembers,
|
||||||
contentMessage,
|
contentMessage,
|
||||||
ContentHint.SUPPLEMENTARY,
|
ContentHint.IMPLICIT,
|
||||||
undefined,
|
undefined,
|
||||||
{
|
{
|
||||||
...sendOptions,
|
...sendOptions,
|
||||||
|
@ -1212,7 +1212,7 @@ export class ConversationModel extends window.Backbone
|
||||||
} else {
|
} else {
|
||||||
handleMessageSend(
|
handleMessageSend(
|
||||||
window.Signal.Util.sendContentMessageToGroup({
|
window.Signal.Util.sendContentMessageToGroup({
|
||||||
contentHint: ContentHint.SUPPLEMENTARY,
|
contentHint: ContentHint.IMPLICIT,
|
||||||
contentMessage,
|
contentMessage,
|
||||||
conversation: this,
|
conversation: this,
|
||||||
online: true,
|
online: true,
|
||||||
|
@ -3289,7 +3289,7 @@ export class ConversationModel extends window.Backbone
|
||||||
targetTimestamp,
|
targetTimestamp,
|
||||||
timestamp,
|
timestamp,
|
||||||
undefined, // expireTimer
|
undefined, // expireTimer
|
||||||
ContentHint.SUPPLEMENTARY,
|
ContentHint.DEFAULT,
|
||||||
undefined, // groupId
|
undefined, // groupId
|
||||||
profileKey,
|
profileKey,
|
||||||
options
|
options
|
||||||
|
@ -3305,7 +3305,7 @@ export class ConversationModel extends window.Backbone
|
||||||
profileKey,
|
profileKey,
|
||||||
},
|
},
|
||||||
this,
|
this,
|
||||||
ContentHint.SUPPLEMENTARY,
|
ContentHint.DEFAULT,
|
||||||
options
|
options
|
||||||
);
|
);
|
||||||
})();
|
})();
|
||||||
|
@ -3446,7 +3446,7 @@ export class ConversationModel extends window.Backbone
|
||||||
undefined, // deletedForEveryoneTimestamp
|
undefined, // deletedForEveryoneTimestamp
|
||||||
timestamp,
|
timestamp,
|
||||||
expireTimer,
|
expireTimer,
|
||||||
ContentHint.SUPPLEMENTARY,
|
ContentHint.DEFAULT,
|
||||||
undefined, // groupId
|
undefined, // groupId
|
||||||
profileKey,
|
profileKey,
|
||||||
options
|
options
|
||||||
|
@ -3465,7 +3465,7 @@ export class ConversationModel extends window.Backbone
|
||||||
profileKey,
|
profileKey,
|
||||||
},
|
},
|
||||||
this,
|
this,
|
||||||
ContentHint.SUPPLEMENTARY,
|
ContentHint.DEFAULT,
|
||||||
options
|
options
|
||||||
);
|
);
|
||||||
})();
|
})();
|
||||||
|
|
|
@ -3601,9 +3601,13 @@ export class MessageModel extends window.Backbone.Model<MessageAttributesType> {
|
||||||
conversationId,
|
conversationId,
|
||||||
message.get('sent_at')
|
message.get('sent_at')
|
||||||
);
|
);
|
||||||
if (item) {
|
if (item && item.wasOpened) {
|
||||||
window.log.info(
|
window.log.info(
|
||||||
`handleDataMessage: found retry placeholder. Updating ${message.idForLogging()} received_at/received_at_ms`
|
`handleDataMessage: found retry placeholder for ${message.idForLogging()}, but conversation was opened. No updates made.`
|
||||||
|
);
|
||||||
|
} else if (item) {
|
||||||
|
window.log.info(
|
||||||
|
`handleDataMessage: found retry placeholder for ${message.idForLogging()}. Updating received_at/received_at_ms`
|
||||||
);
|
);
|
||||||
message.set({
|
message.set({
|
||||||
received_at: item.receivedAtCounter,
|
received_at: item.receivedAtCounter,
|
||||||
|
|
|
@ -805,7 +805,7 @@ export class CallingClass {
|
||||||
window.Signal.Util.sendToGroup(
|
window.Signal.Util.sendToGroup(
|
||||||
{ groupCallUpdate: { eraId }, groupV2, timestamp },
|
{ groupCallUpdate: { eraId }, groupV2, timestamp },
|
||||||
conversation,
|
conversation,
|
||||||
ContentHint.SUPPLEMENTARY,
|
ContentHint.DEFAULT,
|
||||||
sendOptions
|
sendOptions
|
||||||
),
|
),
|
||||||
timestamp,
|
timestamp,
|
||||||
|
|
|
@ -2,9 +2,10 @@
|
||||||
// SPDX-License-Identifier: AGPL-3.0-only
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
import { assert } from 'chai';
|
import { assert } from 'chai';
|
||||||
|
import sinon from 'sinon';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
getOneHourAgo,
|
getDeltaIntoPast,
|
||||||
RetryItemType,
|
RetryItemType,
|
||||||
RetryPlaceholders,
|
RetryPlaceholders,
|
||||||
STORAGE_KEY,
|
STORAGE_KEY,
|
||||||
|
@ -13,15 +14,26 @@ import {
|
||||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
|
|
||||||
describe('RetryPlaceholders', () => {
|
describe('RetryPlaceholders', () => {
|
||||||
|
const NOW = 1_000_000;
|
||||||
|
let clock: any;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
window.storage.put(STORAGE_KEY, null);
|
window.storage.put(STORAGE_KEY, null);
|
||||||
|
|
||||||
|
clock = sinon.useFakeTimers({
|
||||||
|
now: NOW,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
clock.restore();
|
||||||
});
|
});
|
||||||
|
|
||||||
function getDefaultItem(): RetryItemType {
|
function getDefaultItem(): RetryItemType {
|
||||||
return {
|
return {
|
||||||
conversationId: 'conversation-id',
|
conversationId: 'conversation-id',
|
||||||
sentAt: Date.now() - 10,
|
sentAt: NOW - 10,
|
||||||
receivedAt: Date.now() - 5,
|
receivedAt: NOW - 5,
|
||||||
receivedAtCounter: 4,
|
receivedAtCounter: 4,
|
||||||
senderUuid: 'sender-uuid',
|
senderUuid: 'sender-uuid',
|
||||||
};
|
};
|
||||||
|
@ -87,11 +99,11 @@ describe('RetryPlaceholders', () => {
|
||||||
it('returns soonest expiration given a list, and after add', async () => {
|
it('returns soonest expiration given a list, and after add', async () => {
|
||||||
const older = {
|
const older = {
|
||||||
...getDefaultItem(),
|
...getDefaultItem(),
|
||||||
receivedAt: Date.now(),
|
receivedAt: NOW,
|
||||||
};
|
};
|
||||||
const newer = {
|
const newer = {
|
||||||
...getDefaultItem(),
|
...getDefaultItem(),
|
||||||
receivedAt: Date.now() + 10,
|
receivedAt: NOW + 10,
|
||||||
};
|
};
|
||||||
const items: Array<RetryItemType> = [older, newer];
|
const items: Array<RetryItemType> = [older, newer];
|
||||||
window.storage.put(STORAGE_KEY, items);
|
window.storage.put(STORAGE_KEY, items);
|
||||||
|
@ -102,7 +114,7 @@ describe('RetryPlaceholders', () => {
|
||||||
|
|
||||||
const oldest = {
|
const oldest = {
|
||||||
...getDefaultItem(),
|
...getDefaultItem(),
|
||||||
receivedAt: Date.now() - 5,
|
receivedAt: NOW - 5,
|
||||||
};
|
};
|
||||||
|
|
||||||
await placeholders.add(oldest);
|
await placeholders.add(oldest);
|
||||||
|
@ -115,11 +127,11 @@ describe('RetryPlaceholders', () => {
|
||||||
it('does nothing if no item expired', async () => {
|
it('does nothing if no item expired', async () => {
|
||||||
const older = {
|
const older = {
|
||||||
...getDefaultItem(),
|
...getDefaultItem(),
|
||||||
receivedAt: Date.now() + 10,
|
receivedAt: NOW + 10,
|
||||||
};
|
};
|
||||||
const newer = {
|
const newer = {
|
||||||
...getDefaultItem(),
|
...getDefaultItem(),
|
||||||
receivedAt: Date.now() + 15,
|
receivedAt: NOW + 15,
|
||||||
};
|
};
|
||||||
const items: Array<RetryItemType> = [older, newer];
|
const items: Array<RetryItemType> = [older, newer];
|
||||||
window.storage.put(STORAGE_KEY, items);
|
window.storage.put(STORAGE_KEY, items);
|
||||||
|
@ -132,11 +144,11 @@ describe('RetryPlaceholders', () => {
|
||||||
it('removes just one if expired', async () => {
|
it('removes just one if expired', async () => {
|
||||||
const older = {
|
const older = {
|
||||||
...getDefaultItem(),
|
...getDefaultItem(),
|
||||||
receivedAt: getOneHourAgo() - 1000,
|
receivedAt: getDeltaIntoPast() - 1000,
|
||||||
};
|
};
|
||||||
const newer = {
|
const newer = {
|
||||||
...getDefaultItem(),
|
...getDefaultItem(),
|
||||||
receivedAt: Date.now() + 15,
|
receivedAt: NOW + 15,
|
||||||
};
|
};
|
||||||
const items: Array<RetryItemType> = [older, newer];
|
const items: Array<RetryItemType> = [older, newer];
|
||||||
window.storage.put(STORAGE_KEY, items);
|
window.storage.put(STORAGE_KEY, items);
|
||||||
|
@ -150,11 +162,11 @@ describe('RetryPlaceholders', () => {
|
||||||
it('removes all if expired', async () => {
|
it('removes all if expired', async () => {
|
||||||
const older = {
|
const older = {
|
||||||
...getDefaultItem(),
|
...getDefaultItem(),
|
||||||
receivedAt: getOneHourAgo() - 1000,
|
receivedAt: getDeltaIntoPast() - 1000,
|
||||||
};
|
};
|
||||||
const newer = {
|
const newer = {
|
||||||
...getDefaultItem(),
|
...getDefaultItem(),
|
||||||
receivedAt: getOneHourAgo() - 900,
|
receivedAt: getDeltaIntoPast() - 900,
|
||||||
};
|
};
|
||||||
const items: Array<RetryItemType> = [older, newer];
|
const items: Array<RetryItemType> = [older, newer];
|
||||||
window.storage.put(STORAGE_KEY, items);
|
window.storage.put(STORAGE_KEY, items);
|
||||||
|
@ -169,7 +181,7 @@ describe('RetryPlaceholders', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('#findByConversationAndRemove', () => {
|
describe('#findByConversationAndMarkOpened', () => {
|
||||||
it('does nothing if no items found matching conversation', async () => {
|
it('does nothing if no items found matching conversation', async () => {
|
||||||
const older = {
|
const older = {
|
||||||
...getDefaultItem(),
|
...getDefaultItem(),
|
||||||
|
@ -184,68 +196,101 @@ describe('RetryPlaceholders', () => {
|
||||||
|
|
||||||
const placeholders = new RetryPlaceholders();
|
const placeholders = new RetryPlaceholders();
|
||||||
assert.strictEqual(2, placeholders.getCount());
|
assert.strictEqual(2, placeholders.getCount());
|
||||||
assert.deepEqual(
|
await placeholders.findByConversationAndMarkOpened('conversation-id-3');
|
||||||
[],
|
|
||||||
await placeholders.findByConversationAndRemove('conversation-id-3')
|
|
||||||
);
|
|
||||||
assert.strictEqual(2, placeholders.getCount());
|
assert.strictEqual(2, placeholders.getCount());
|
||||||
|
|
||||||
|
const saveItems = window.storage.get(STORAGE_KEY);
|
||||||
|
assert.deepEqual([older, newer], saveItems);
|
||||||
});
|
});
|
||||||
it('removes all items matching conversation', async () => {
|
it('updates all items matching conversation', async () => {
|
||||||
const convo1a = {
|
const convo1a = {
|
||||||
...getDefaultItem(),
|
...getDefaultItem(),
|
||||||
conversationId: 'conversation-id-1',
|
conversationId: 'conversation-id-1',
|
||||||
receivedAt: Date.now() - 5,
|
receivedAt: NOW - 5,
|
||||||
};
|
};
|
||||||
const convo1b = {
|
const convo1b = {
|
||||||
...getDefaultItem(),
|
...getDefaultItem(),
|
||||||
conversationId: 'conversation-id-1',
|
conversationId: 'conversation-id-1',
|
||||||
receivedAt: Date.now() - 4,
|
receivedAt: NOW - 4,
|
||||||
};
|
};
|
||||||
const convo2a = {
|
const convo2a = {
|
||||||
...getDefaultItem(),
|
...getDefaultItem(),
|
||||||
conversationId: 'conversation-id-2',
|
conversationId: 'conversation-id-2',
|
||||||
receivedAt: Date.now() + 15,
|
receivedAt: NOW + 15,
|
||||||
};
|
};
|
||||||
const items: Array<RetryItemType> = [convo1a, convo1b, convo2a];
|
const items: Array<RetryItemType> = [convo1a, convo1b, convo2a];
|
||||||
window.storage.put(STORAGE_KEY, items);
|
window.storage.put(STORAGE_KEY, items);
|
||||||
|
|
||||||
const placeholders = new RetryPlaceholders();
|
const placeholders = new RetryPlaceholders();
|
||||||
assert.strictEqual(3, placeholders.getCount());
|
assert.strictEqual(3, placeholders.getCount());
|
||||||
|
await placeholders.findByConversationAndMarkOpened('conversation-id-1');
|
||||||
|
assert.strictEqual(3, placeholders.getCount());
|
||||||
|
|
||||||
|
const firstSaveItems = window.storage.get(STORAGE_KEY);
|
||||||
assert.deepEqual(
|
assert.deepEqual(
|
||||||
[convo1a, convo1b],
|
[
|
||||||
await placeholders.findByConversationAndRemove('conversation-id-1')
|
{
|
||||||
|
...convo1a,
|
||||||
|
wasOpened: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
...convo1b,
|
||||||
|
wasOpened: true,
|
||||||
|
},
|
||||||
|
convo2a,
|
||||||
|
],
|
||||||
|
firstSaveItems
|
||||||
);
|
);
|
||||||
assert.strictEqual(1, placeholders.getCount());
|
|
||||||
|
|
||||||
const convo2b = {
|
const convo2b = {
|
||||||
...getDefaultItem(),
|
...getDefaultItem(),
|
||||||
conversationId: 'conversation-id-2',
|
conversationId: 'conversation-id-2',
|
||||||
receivedAt: Date.now() + 16,
|
receivedAt: NOW + 16,
|
||||||
};
|
};
|
||||||
|
|
||||||
await placeholders.add(convo2b);
|
await placeholders.add(convo2b);
|
||||||
assert.strictEqual(2, placeholders.getCount());
|
assert.strictEqual(4, placeholders.getCount());
|
||||||
|
await placeholders.findByConversationAndMarkOpened('conversation-id-2');
|
||||||
|
assert.strictEqual(4, placeholders.getCount());
|
||||||
|
|
||||||
|
const secondSaveItems = window.storage.get(STORAGE_KEY);
|
||||||
assert.deepEqual(
|
assert.deepEqual(
|
||||||
[convo2a, convo2b],
|
[
|
||||||
await placeholders.findByConversationAndRemove('conversation-id-2')
|
{
|
||||||
|
...convo1a,
|
||||||
|
wasOpened: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
...convo1b,
|
||||||
|
wasOpened: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
...convo2a,
|
||||||
|
wasOpened: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
...convo2b,
|
||||||
|
wasOpened: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
secondSaveItems
|
||||||
);
|
);
|
||||||
assert.strictEqual(0, placeholders.getCount());
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('#findByMessageAndRemove', () => {
|
describe('#findByMessageAndRemove', () => {
|
||||||
it('does nothing if no item matching message found', async () => {
|
it('does nothing if no item matching message found', async () => {
|
||||||
const sentAt = Date.now() - 20;
|
const sentAt = NOW - 20;
|
||||||
|
|
||||||
const older = {
|
const older = {
|
||||||
...getDefaultItem(),
|
...getDefaultItem(),
|
||||||
conversationId: 'conversation-id-1',
|
conversationId: 'conversation-id-1',
|
||||||
sentAt: Date.now() - 10,
|
sentAt: NOW - 10,
|
||||||
};
|
};
|
||||||
const newer = {
|
const newer = {
|
||||||
...getDefaultItem(),
|
...getDefaultItem(),
|
||||||
conversationId: 'conversation-id-1',
|
conversationId: 'conversation-id-1',
|
||||||
sentAt: Date.now() - 11,
|
sentAt: NOW - 11,
|
||||||
};
|
};
|
||||||
const items: Array<RetryItemType> = [older, newer];
|
const items: Array<RetryItemType> = [older, newer];
|
||||||
window.storage.put(STORAGE_KEY, items);
|
window.storage.put(STORAGE_KEY, items);
|
||||||
|
@ -258,12 +303,12 @@ describe('RetryPlaceholders', () => {
|
||||||
assert.strictEqual(2, placeholders.getCount());
|
assert.strictEqual(2, placeholders.getCount());
|
||||||
});
|
});
|
||||||
it('removes the item matching message', async () => {
|
it('removes the item matching message', async () => {
|
||||||
const sentAt = Date.now() - 20;
|
const sentAt = NOW - 20;
|
||||||
|
|
||||||
const older = {
|
const older = {
|
||||||
...getDefaultItem(),
|
...getDefaultItem(),
|
||||||
conversationId: 'conversation-id-1',
|
conversationId: 'conversation-id-1',
|
||||||
sentAt: Date.now() - 10,
|
sentAt: NOW - 10,
|
||||||
};
|
};
|
||||||
const newer = {
|
const newer = {
|
||||||
...getDefaultItem(),
|
...getDefaultItem(),
|
||||||
|
|
3
ts/textsecure.d.ts
vendored
3
ts/textsecure.d.ts
vendored
|
@ -1411,7 +1411,8 @@ export declare namespace UnidentifiedSenderMessageClass.Message {
|
||||||
}
|
}
|
||||||
|
|
||||||
class ContentHint {
|
class ContentHint {
|
||||||
static SUPPLEMENTARY: number;
|
static DEFAULT: number;
|
||||||
static RESENDABLE: number;
|
static RESENDABLE: number;
|
||||||
|
static IMPLICIT: number;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -93,6 +93,7 @@ export type DecryptionErrorType = z.infer<typeof decryptionErrorTypeSchema>;
|
||||||
|
|
||||||
const retryRequestTypeSchema = z
|
const retryRequestTypeSchema = z
|
||||||
.object({
|
.object({
|
||||||
|
groupId: z.string().optional(),
|
||||||
requesterUuid: z.string(),
|
requesterUuid: z.string(),
|
||||||
requesterDevice: z.number(),
|
requesterDevice: z.number(),
|
||||||
senderDevice: z.number(),
|
senderDevice: z.number(),
|
||||||
|
@ -1757,10 +1758,11 @@ class MessageReceiverInner extends EventTarget {
|
||||||
|
|
||||||
const event = new Event('retry-request');
|
const event = new Event('retry-request');
|
||||||
event.retryRequest = {
|
event.retryRequest = {
|
||||||
sentAt: request.timestamp(),
|
groupId: envelope.groupId,
|
||||||
requesterUuid: sourceUuid,
|
|
||||||
requesterDevice: sourceDevice,
|
requesterDevice: sourceDevice,
|
||||||
|
requesterUuid: sourceUuid,
|
||||||
senderDevice: request.deviceId(),
|
senderDevice: request.deviceId(),
|
||||||
|
sentAt: request.timestamp(),
|
||||||
};
|
};
|
||||||
await this.dispatchAndWait(event);
|
await this.dispatchAndWait(event);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1039,7 +1039,7 @@ export default class MessageSender {
|
||||||
myUuid || myNumber,
|
myUuid || myNumber,
|
||||||
contentMessage,
|
contentMessage,
|
||||||
timestamp,
|
timestamp,
|
||||||
ContentHint.SUPPLEMENTARY,
|
ContentHint.IMPLICIT,
|
||||||
options
|
options
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1067,7 +1067,7 @@ export default class MessageSender {
|
||||||
myUuid || myNumber,
|
myUuid || myNumber,
|
||||||
contentMessage,
|
contentMessage,
|
||||||
Date.now(),
|
Date.now(),
|
||||||
ContentHint.SUPPLEMENTARY,
|
ContentHint.IMPLICIT,
|
||||||
options
|
options
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1098,7 +1098,7 @@ export default class MessageSender {
|
||||||
myUuid || myNumber,
|
myUuid || myNumber,
|
||||||
contentMessage,
|
contentMessage,
|
||||||
Date.now(),
|
Date.now(),
|
||||||
ContentHint.SUPPLEMENTARY,
|
ContentHint.IMPLICIT,
|
||||||
options
|
options
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1128,7 +1128,7 @@ export default class MessageSender {
|
||||||
myUuid || myNumber,
|
myUuid || myNumber,
|
||||||
contentMessage,
|
contentMessage,
|
||||||
Date.now(),
|
Date.now(),
|
||||||
ContentHint.SUPPLEMENTARY,
|
ContentHint.IMPLICIT,
|
||||||
options
|
options
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1160,7 +1160,7 @@ export default class MessageSender {
|
||||||
myUuid || myNumber,
|
myUuid || myNumber,
|
||||||
contentMessage,
|
contentMessage,
|
||||||
Date.now(),
|
Date.now(),
|
||||||
ContentHint.SUPPLEMENTARY,
|
ContentHint.IMPLICIT,
|
||||||
options
|
options
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1196,7 +1196,7 @@ export default class MessageSender {
|
||||||
myUuid || myNumber,
|
myUuid || myNumber,
|
||||||
contentMessage,
|
contentMessage,
|
||||||
Date.now(),
|
Date.now(),
|
||||||
ContentHint.SUPPLEMENTARY,
|
ContentHint.IMPLICIT,
|
||||||
options
|
options
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1228,7 +1228,7 @@ export default class MessageSender {
|
||||||
myUuid || myNumber,
|
myUuid || myNumber,
|
||||||
contentMessage,
|
contentMessage,
|
||||||
Date.now(),
|
Date.now(),
|
||||||
ContentHint.SUPPLEMENTARY,
|
ContentHint.IMPLICIT,
|
||||||
options
|
options
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1266,7 +1266,7 @@ export default class MessageSender {
|
||||||
myUuid || myNumber,
|
myUuid || myNumber,
|
||||||
contentMessage,
|
contentMessage,
|
||||||
Date.now(),
|
Date.now(),
|
||||||
ContentHint.SUPPLEMENTARY,
|
ContentHint.DEFAULT,
|
||||||
options
|
options
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1306,7 +1306,7 @@ export default class MessageSender {
|
||||||
myUuid || myNumber,
|
myUuid || myNumber,
|
||||||
contentMessage,
|
contentMessage,
|
||||||
Date.now(),
|
Date.now(),
|
||||||
ContentHint.SUPPLEMENTARY,
|
ContentHint.IMPLICIT,
|
||||||
options
|
options
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1347,7 +1347,7 @@ export default class MessageSender {
|
||||||
myUuid || myNumber,
|
myUuid || myNumber,
|
||||||
contentMessage,
|
contentMessage,
|
||||||
Date.now(),
|
Date.now(),
|
||||||
ContentHint.SUPPLEMENTARY,
|
ContentHint.IMPLICIT,
|
||||||
sendOptions
|
sendOptions
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1395,7 +1395,7 @@ export default class MessageSender {
|
||||||
myUuid || myNumber,
|
myUuid || myNumber,
|
||||||
contentMessage,
|
contentMessage,
|
||||||
Date.now(),
|
Date.now(),
|
||||||
ContentHint.SUPPLEMENTARY,
|
ContentHint.IMPLICIT,
|
||||||
options
|
options
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1451,7 +1451,7 @@ export default class MessageSender {
|
||||||
myUuid || myNumber,
|
myUuid || myNumber,
|
||||||
secondMessage,
|
secondMessage,
|
||||||
now,
|
now,
|
||||||
ContentHint.SUPPLEMENTARY,
|
ContentHint.IMPLICIT,
|
||||||
options
|
options
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
@ -1484,7 +1484,7 @@ export default class MessageSender {
|
||||||
}
|
}
|
||||||
: {}),
|
: {}),
|
||||||
},
|
},
|
||||||
ContentHint.SUPPLEMENTARY,
|
ContentHint.IMPLICIT,
|
||||||
undefined, // groupId
|
undefined, // groupId
|
||||||
sendOptions
|
sendOptions
|
||||||
);
|
);
|
||||||
|
@ -1509,7 +1509,7 @@ export default class MessageSender {
|
||||||
finalTimestamp,
|
finalTimestamp,
|
||||||
recipients,
|
recipients,
|
||||||
contentMessage,
|
contentMessage,
|
||||||
ContentHint.SUPPLEMENTARY,
|
ContentHint.DEFAULT,
|
||||||
undefined, // groupId
|
undefined, // groupId
|
||||||
sendOptions
|
sendOptions
|
||||||
);
|
);
|
||||||
|
@ -1547,7 +1547,7 @@ export default class MessageSender {
|
||||||
recipientUuid || recipientE164,
|
recipientUuid || recipientE164,
|
||||||
contentMessage,
|
contentMessage,
|
||||||
Date.now(),
|
Date.now(),
|
||||||
ContentHint.SUPPLEMENTARY,
|
ContentHint.IMPLICIT,
|
||||||
options
|
options
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1573,7 +1573,7 @@ export default class MessageSender {
|
||||||
senderUuid || senderE164,
|
senderUuid || senderE164,
|
||||||
contentMessage,
|
contentMessage,
|
||||||
Date.now(),
|
Date.now(),
|
||||||
ContentHint.SUPPLEMENTARY,
|
ContentHint.IMPLICIT,
|
||||||
options
|
options
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1608,7 +1608,7 @@ export default class MessageSender {
|
||||||
identifier,
|
identifier,
|
||||||
contentMessage,
|
contentMessage,
|
||||||
timestamp,
|
timestamp,
|
||||||
ContentHint.SUPPLEMENTARY,
|
ContentHint.IMPLICIT,
|
||||||
options
|
options
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1649,7 +1649,7 @@ export default class MessageSender {
|
||||||
identifier,
|
identifier,
|
||||||
proto,
|
proto,
|
||||||
timestamp,
|
timestamp,
|
||||||
ContentHint.SUPPLEMENTARY,
|
ContentHint.DEFAULT,
|
||||||
options
|
options
|
||||||
).catch(logError('resetSession/sendToContact error:'));
|
).catch(logError('resetSession/sendToContact error:'));
|
||||||
})
|
})
|
||||||
|
@ -1702,7 +1702,7 @@ export default class MessageSender {
|
||||||
flags:
|
flags:
|
||||||
window.textsecure.protobuf.DataMessage.Flags.EXPIRATION_TIMER_UPDATE,
|
window.textsecure.protobuf.DataMessage.Flags.EXPIRATION_TIMER_UPDATE,
|
||||||
},
|
},
|
||||||
ContentHint.SUPPLEMENTARY,
|
ContentHint.DEFAULT,
|
||||||
undefined, // groupId
|
undefined, // groupId
|
||||||
options
|
options
|
||||||
);
|
);
|
||||||
|
@ -1725,7 +1725,7 @@ export default class MessageSender {
|
||||||
Date.now(),
|
Date.now(),
|
||||||
[uuid],
|
[uuid],
|
||||||
plaintext,
|
plaintext,
|
||||||
ContentHint.SUPPLEMENTARY,
|
ContentHint.IMPLICIT,
|
||||||
undefined, // groupId
|
undefined, // groupId
|
||||||
options
|
options
|
||||||
);
|
);
|
||||||
|
@ -1864,7 +1864,7 @@ export default class MessageSender {
|
||||||
groupIdentifiers,
|
groupIdentifiers,
|
||||||
proto,
|
proto,
|
||||||
Date.now(),
|
Date.now(),
|
||||||
ContentHint.SUPPLEMENTARY,
|
ContentHint.DEFAULT,
|
||||||
undefined, // only for GV2 ids
|
undefined, // only for GV2 ids
|
||||||
options
|
options
|
||||||
);
|
);
|
||||||
|
@ -1911,7 +1911,7 @@ export default class MessageSender {
|
||||||
} = window.textsecure.protobuf.UnidentifiedSenderMessage.Message;
|
} = window.textsecure.protobuf.UnidentifiedSenderMessage.Message;
|
||||||
return this.sendMessage(
|
return this.sendMessage(
|
||||||
attrs,
|
attrs,
|
||||||
ContentHint.SUPPLEMENTARY,
|
ContentHint.DEFAULT,
|
||||||
undefined, // only for GV2 ids
|
undefined, // only for GV2 ids
|
||||||
options
|
options
|
||||||
);
|
);
|
||||||
|
|
|
@ -11,6 +11,7 @@ const retryItemSchema = z
|
||||||
receivedAt: z.number(),
|
receivedAt: z.number(),
|
||||||
receivedAtCounter: z.number(),
|
receivedAtCounter: z.number(),
|
||||||
senderUuid: z.string(),
|
senderUuid: z.string(),
|
||||||
|
wasOpened: z.boolean().optional(),
|
||||||
})
|
})
|
||||||
.passthrough();
|
.passthrough();
|
||||||
export type RetryItemType = z.infer<typeof retryItemSchema>;
|
export type RetryItemType = z.infer<typeof retryItemSchema>;
|
||||||
|
@ -30,8 +31,8 @@ export function getItemId(conversationId: string, sentAt: number): string {
|
||||||
const HOUR = 60 * 60 * 1000;
|
const HOUR = 60 * 60 * 1000;
|
||||||
export const STORAGE_KEY = 'retryPlaceholders';
|
export const STORAGE_KEY = 'retryPlaceholders';
|
||||||
|
|
||||||
export function getOneHourAgo(): number {
|
export function getDeltaIntoPast(delta?: number): number {
|
||||||
return Date.now() - HOUR;
|
return Date.now() - (delta || HOUR);
|
||||||
}
|
}
|
||||||
|
|
||||||
export class RetryPlaceholders {
|
export class RetryPlaceholders {
|
||||||
|
@ -41,7 +42,9 @@ export class RetryPlaceholders {
|
||||||
|
|
||||||
private byMessage: ByMessageLookupType;
|
private byMessage: ByMessageLookupType;
|
||||||
|
|
||||||
constructor() {
|
private retryReceiptLifespan: number;
|
||||||
|
|
||||||
|
constructor(options: { retryReceiptLifespan?: number } = {}) {
|
||||||
if (!window.storage) {
|
if (!window.storage) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
'RetryPlaceholders.constructor: window.storage not available!'
|
'RetryPlaceholders.constructor: window.storage not available!'
|
||||||
|
@ -67,6 +70,7 @@ export class RetryPlaceholders {
|
||||||
this.sortByExpiresAtAsc();
|
this.sortByExpiresAtAsc();
|
||||||
this.byConversation = this.makeByConversationLookup();
|
this.byConversation = this.makeByConversationLookup();
|
||||||
this.byMessage = this.makeByMessageLookup();
|
this.byMessage = this.makeByMessageLookup();
|
||||||
|
this.retryReceiptLifespan = options.retryReceiptLifespan || HOUR;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Arranging local data for efficiency
|
// Arranging local data for efficiency
|
||||||
|
@ -128,7 +132,7 @@ export class RetryPlaceholders {
|
||||||
}
|
}
|
||||||
|
|
||||||
async getExpiredAndRemove(): Promise<Array<RetryItemType>> {
|
async getExpiredAndRemove(): Promise<Array<RetryItemType>> {
|
||||||
const expiration = getOneHourAgo();
|
const expiration = getDeltaIntoPast(this.retryReceiptLifespan);
|
||||||
const max = this.items.length;
|
const max = this.items.length;
|
||||||
const result: Array<RetryItemType> = [];
|
const result: Array<RetryItemType> = [];
|
||||||
|
|
||||||
|
@ -152,28 +156,24 @@ export class RetryPlaceholders {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
async findByConversationAndRemove(
|
async findByConversationAndMarkOpened(conversationId: string): Promise<void> {
|
||||||
conversationId: string
|
let changed = 0;
|
||||||
): Promise<Array<RetryItemType>> {
|
const items = this.byConversation[conversationId];
|
||||||
const result = this.byConversation[conversationId];
|
(items || []).forEach(item => {
|
||||||
if (!result) {
|
if (!item.wasOpened) {
|
||||||
return [];
|
changed += 1;
|
||||||
|
// eslint-disable-next-line no-param-reassign
|
||||||
|
item.wasOpened = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (changed > 0) {
|
||||||
|
window.log.info(
|
||||||
|
`RetryPlaceholders.findByConversationAndMarkOpened: Updated ${changed} items for conversation ${conversationId}`
|
||||||
|
);
|
||||||
|
|
||||||
|
await this.save();
|
||||||
}
|
}
|
||||||
|
|
||||||
const items = this.items.filter(
|
|
||||||
item => item.conversationId !== conversationId
|
|
||||||
);
|
|
||||||
|
|
||||||
window.log.info(
|
|
||||||
`RetryPlaceholders.findByConversationAndRemove: Found ${result.length} expired items`
|
|
||||||
);
|
|
||||||
|
|
||||||
this.items = items;
|
|
||||||
this.sortByExpiresAtAsc();
|
|
||||||
this.makeLookups();
|
|
||||||
await this.save();
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async findByMessageAndRemove(
|
async findByMessageAndRemove(
|
||||||
|
|
|
@ -16,6 +16,7 @@ import {
|
||||||
padMessage,
|
padMessage,
|
||||||
SenderCertificateMode,
|
SenderCertificateMode,
|
||||||
} from '../textsecure/OutgoingMessage';
|
} from '../textsecure/OutgoingMessage';
|
||||||
|
import { isEnabled } from '../RemoteConfig';
|
||||||
|
|
||||||
import { isOlderThan } from './timestamp';
|
import { isOlderThan } from './timestamp';
|
||||||
import {
|
import {
|
||||||
|
@ -116,6 +117,7 @@ export async function sendContentMessageToGroup({
|
||||||
const ourConversation = window.ConversationController.get(ourConversationId);
|
const ourConversation = window.ConversationController.get(ourConversationId);
|
||||||
|
|
||||||
if (
|
if (
|
||||||
|
isEnabled('desktop.sendSenderKey') &&
|
||||||
ourConversation?.get('capabilities')?.senderKey &&
|
ourConversation?.get('capabilities')?.senderKey &&
|
||||||
isGroupV2(conversation.attributes)
|
isGroupV2(conversation.attributes)
|
||||||
) {
|
) {
|
||||||
|
@ -199,8 +201,9 @@ export async function sendToGroupViaSenderKey(options: {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
|
contentHint !== ContentHint.DEFAULT &&
|
||||||
contentHint !== ContentHint.RESENDABLE &&
|
contentHint !== ContentHint.RESENDABLE &&
|
||||||
contentHint !== ContentHint.SUPPLEMENTARY
|
contentHint !== ContentHint.IMPLICIT
|
||||||
) {
|
) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`sendToGroupViaSenderKey/${logId}: Invalid contentHint ${contentHint}`
|
`sendToGroupViaSenderKey/${logId}: Invalid contentHint ${contentHint}`
|
||||||
|
@ -326,7 +329,7 @@ export async function sendToGroupViaSenderKey(options: {
|
||||||
);
|
);
|
||||||
await window.textsecure.messaging.sendSenderKeyDistributionMessage(
|
await window.textsecure.messaging.sendSenderKeyDistributionMessage(
|
||||||
{
|
{
|
||||||
contentHint: ContentHint.SUPPLEMENTARY,
|
contentHint: ContentHint.DEFAULT,
|
||||||
distributionId,
|
distributionId,
|
||||||
groupId,
|
groupId,
|
||||||
identifiers: newToMemberUuids,
|
identifiers: newToMemberUuids,
|
||||||
|
|
|
@ -2266,10 +2266,7 @@ Whisper.ConversationView = Whisper.View.extend({
|
||||||
|
|
||||||
const { retryPlaceholders } = window.Signal.Services;
|
const { retryPlaceholders } = window.Signal.Services;
|
||||||
if (retryPlaceholders) {
|
if (retryPlaceholders) {
|
||||||
const placeholders = await retryPlaceholders.findByConversationAndRemove(
|
await retryPlaceholders.findByConversationAndMarkOpened(model.id);
|
||||||
model.id
|
|
||||||
);
|
|
||||||
window.log.info(`onOpened: Found ${placeholders.length} placeholders`);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this.loadNewestMessages();
|
this.loadNewestMessages();
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue