mirror of
https://github.com/signalapp/Signal-Desktop.git
synced 2025-12-24 04:09:49 +00:00
Support sending pin/unpin messages
Co-authored-by: Jamie <113370520+jamiebuilds-signal@users.noreply.github.com>
This commit is contained in:
@@ -57,6 +57,8 @@ import { clearTimeoutIfNecessary } from '../util/clearTimeoutIfNecessary.std.js'
|
|||||||
import { FIBONACCI } from '../util/BackOff.std.js';
|
import { FIBONACCI } from '../util/BackOff.std.js';
|
||||||
import { parseUnknown } from '../util/schemas.std.js';
|
import { parseUnknown } from '../util/schemas.std.js';
|
||||||
import { challengeHandler } from '../services/challengeHandler.preload.js';
|
import { challengeHandler } from '../services/challengeHandler.preload.js';
|
||||||
|
import { sendPinMessage } from './helpers/sendPinMessage.preload.js';
|
||||||
|
import { sendUnpinMessage } from './helpers/sendUnpinMessage.preload.js';
|
||||||
|
|
||||||
const globalLogger = createLogger('conversationJobQueue');
|
const globalLogger = createLogger('conversationJobQueue');
|
||||||
|
|
||||||
@@ -71,6 +73,7 @@ export const conversationQueueJobEnum = z.enum([
|
|||||||
'GroupUpdate',
|
'GroupUpdate',
|
||||||
'NormalMessage',
|
'NormalMessage',
|
||||||
'NullMessage',
|
'NullMessage',
|
||||||
|
'PinMessage',
|
||||||
'PollTerminate',
|
'PollTerminate',
|
||||||
'PollVote',
|
'PollVote',
|
||||||
'ProfileKey',
|
'ProfileKey',
|
||||||
@@ -81,6 +84,7 @@ export const conversationQueueJobEnum = z.enum([
|
|||||||
'SenderKeyDistribution',
|
'SenderKeyDistribution',
|
||||||
'Story',
|
'Story',
|
||||||
'Receipts',
|
'Receipts',
|
||||||
|
'UnpinMessage',
|
||||||
]);
|
]);
|
||||||
type ConversationQueueJobEnum = z.infer<typeof conversationQueueJobEnum>;
|
type ConversationQueueJobEnum = z.infer<typeof conversationQueueJobEnum>;
|
||||||
|
|
||||||
@@ -198,6 +202,16 @@ const reactionJobDataSchema = z.object({
|
|||||||
});
|
});
|
||||||
export type ReactionJobData = z.infer<typeof reactionJobDataSchema>;
|
export type ReactionJobData = z.infer<typeof reactionJobDataSchema>;
|
||||||
|
|
||||||
|
const pinMessageJobDataSchema = z.object({
|
||||||
|
type: z.literal(conversationQueueJobEnum.enum.PinMessage),
|
||||||
|
conversationId: z.string(),
|
||||||
|
targetMessageId: z.string(),
|
||||||
|
targetAuthorAci: aciSchema,
|
||||||
|
targetSentTimestamp: z.number(),
|
||||||
|
pinDurationSeconds: z.number().nullable(),
|
||||||
|
});
|
||||||
|
export type PinMessageJobData = z.infer<typeof pinMessageJobDataSchema>;
|
||||||
|
|
||||||
const pollVoteJobDataSchema = z.object({
|
const pollVoteJobDataSchema = z.object({
|
||||||
type: z.literal(conversationQueueJobEnum.enum.PollVote),
|
type: z.literal(conversationQueueJobEnum.enum.PollVote),
|
||||||
conversationId: z.string(),
|
conversationId: z.string(),
|
||||||
@@ -270,6 +284,15 @@ const receiptsJobDataSchema = z.object({
|
|||||||
});
|
});
|
||||||
export type ReceiptsJobData = z.infer<typeof receiptsJobDataSchema>;
|
export type ReceiptsJobData = z.infer<typeof receiptsJobDataSchema>;
|
||||||
|
|
||||||
|
const unpinMessageJobDataSchema = z.object({
|
||||||
|
type: z.literal(conversationQueueJobEnum.enum.UnpinMessage),
|
||||||
|
conversationId: z.string(),
|
||||||
|
targetMessageId: z.string(),
|
||||||
|
targetAuthorAci: aciSchema,
|
||||||
|
targetSentTimestamp: z.number(),
|
||||||
|
});
|
||||||
|
export type UnpinMessageJobData = z.infer<typeof unpinMessageJobDataSchema>;
|
||||||
|
|
||||||
export const conversationQueueJobDataSchema = z.union([
|
export const conversationQueueJobDataSchema = z.union([
|
||||||
callingMessageJobDataSchema,
|
callingMessageJobDataSchema,
|
||||||
deleteForEveryoneJobDataSchema,
|
deleteForEveryoneJobDataSchema,
|
||||||
@@ -279,6 +302,7 @@ export const conversationQueueJobDataSchema = z.union([
|
|||||||
groupUpdateJobDataSchema,
|
groupUpdateJobDataSchema,
|
||||||
normalMessageSendJobDataSchema,
|
normalMessageSendJobDataSchema,
|
||||||
nullMessageJobDataSchema,
|
nullMessageJobDataSchema,
|
||||||
|
pinMessageJobDataSchema,
|
||||||
pollTerminateJobDataSchema,
|
pollTerminateJobDataSchema,
|
||||||
pollVoteJobDataSchema,
|
pollVoteJobDataSchema,
|
||||||
profileKeyJobDataSchema,
|
profileKeyJobDataSchema,
|
||||||
@@ -288,6 +312,7 @@ export const conversationQueueJobDataSchema = z.union([
|
|||||||
senderKeyDistributionJobDataSchema,
|
senderKeyDistributionJobDataSchema,
|
||||||
storyJobDataSchema,
|
storyJobDataSchema,
|
||||||
receiptsJobDataSchema,
|
receiptsJobDataSchema,
|
||||||
|
unpinMessageJobDataSchema,
|
||||||
]);
|
]);
|
||||||
export type ConversationQueueJobData = z.infer<
|
export type ConversationQueueJobData = z.infer<
|
||||||
typeof conversationQueueJobDataSchema
|
typeof conversationQueueJobDataSchema
|
||||||
@@ -330,6 +355,9 @@ function shouldSendShowCaptcha(type: ConversationQueueJobEnum): boolean {
|
|||||||
if (type === 'NullMessage') {
|
if (type === 'NullMessage') {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
if (type === 'PinMessage') {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
if (type === 'ProfileKey') {
|
if (type === 'ProfileKey') {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -362,6 +390,9 @@ function shouldSendShowCaptcha(type: ConversationQueueJobEnum): boolean {
|
|||||||
if (type === 'Story') {
|
if (type === 'Story') {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
if (type === 'UnpinMessage') {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
throw missingCaseError(type);
|
throw missingCaseError(type);
|
||||||
}
|
}
|
||||||
@@ -989,6 +1020,9 @@ export class ConversationJobQueue extends JobQueue<ConversationQueueJobData> {
|
|||||||
case jobSet.Reaction:
|
case jobSet.Reaction:
|
||||||
await sendReaction(conversation, jobBundle, data);
|
await sendReaction(conversation, jobBundle, data);
|
||||||
break;
|
break;
|
||||||
|
case jobSet.PinMessage:
|
||||||
|
await sendPinMessage(conversation, jobBundle, data);
|
||||||
|
break;
|
||||||
case jobSet.PollTerminate:
|
case jobSet.PollTerminate:
|
||||||
await sendPollTerminate(conversation, jobBundle, data);
|
await sendPollTerminate(conversation, jobBundle, data);
|
||||||
break;
|
break;
|
||||||
@@ -1010,6 +1044,9 @@ export class ConversationJobQueue extends JobQueue<ConversationQueueJobData> {
|
|||||||
case jobSet.Receipts:
|
case jobSet.Receipts:
|
||||||
await sendReceipts(conversation, jobBundle, data);
|
await sendReceipts(conversation, jobBundle, data);
|
||||||
break;
|
break;
|
||||||
|
case jobSet.UnpinMessage:
|
||||||
|
await sendUnpinMessage(conversation, jobBundle, data);
|
||||||
|
break;
|
||||||
default: {
|
default: {
|
||||||
// Note: This should never happen, because the zod call in parseData wouldn't
|
// Note: This should never happen, because the zod call in parseData wouldn't
|
||||||
// accept data that doesn't look like our type specification.
|
// accept data that doesn't look like our type specification.
|
||||||
|
|||||||
195
ts/jobs/helpers/createSendMessageJob.preload.ts
Normal file
195
ts/jobs/helpers/createSendMessageJob.preload.ts
Normal file
@@ -0,0 +1,195 @@
|
|||||||
|
// Copyright 2025 Signal Messenger, LLC
|
||||||
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
import { ContentHint } from '@signalapp/libsignal-client';
|
||||||
|
import type { ConversationModel } from '../../models/conversations.preload.js';
|
||||||
|
import { getSendOptions } from '../../util/getSendOptions.preload.js';
|
||||||
|
import { sendToGroup } from '../../util/sendToGroup.preload.js';
|
||||||
|
import {
|
||||||
|
isDirectConversation,
|
||||||
|
isGroupV2,
|
||||||
|
} from '../../util/whatTypeOfConversation.dom.js';
|
||||||
|
import type { ConversationQueueJobBundle } from '../conversationJobQueue.preload.js';
|
||||||
|
import { getSendRecipientLists } from './getSendRecipientLists.dom.js';
|
||||||
|
import type { SendTypesType } from '../../util/handleMessageSend.preload.js';
|
||||||
|
import { handleMessageSend } from '../../util/handleMessageSend.preload.js';
|
||||||
|
import type { SharedMessageOptionsType } from '../../textsecure/SendMessage.preload.js';
|
||||||
|
import { strictAssert } from '../../util/assert.std.js';
|
||||||
|
import { wrapWithSyncMessageSend } from '../../util/wrapWithSyncMessageSend.preload.js';
|
||||||
|
import {
|
||||||
|
handleMultipleSendErrors,
|
||||||
|
maybeExpandErrors,
|
||||||
|
} from './handleMultipleSendErrors.std.js';
|
||||||
|
|
||||||
|
export type SendMessageJobOptions<Data> = Readonly<{
|
||||||
|
sendName: string; // ex: 'sendExampleMessage'
|
||||||
|
sendType: SendTypesType;
|
||||||
|
getMessageId: (data: Data) => string | null;
|
||||||
|
getMessageOptions: (
|
||||||
|
data: Data,
|
||||||
|
jobTimestamp: number
|
||||||
|
) => Omit<SharedMessageOptionsType, 'recipients'>;
|
||||||
|
}>;
|
||||||
|
|
||||||
|
export function createSendMessageJob<Data>(
|
||||||
|
options: SendMessageJobOptions<Data>
|
||||||
|
) {
|
||||||
|
return async function sendMessage(
|
||||||
|
conversation: ConversationModel,
|
||||||
|
job: ConversationQueueJobBundle,
|
||||||
|
data: Data
|
||||||
|
): Promise<void> {
|
||||||
|
const { sendName, sendType, getMessageId, getMessageOptions } = options;
|
||||||
|
|
||||||
|
const logId = `${sendName}(${conversation.idForLogging()}/${job.timestamp})`;
|
||||||
|
const log = job.log.child(logId);
|
||||||
|
|
||||||
|
if (!job.shouldContinue) {
|
||||||
|
log.info('Ran out of time, cancelling send');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const {
|
||||||
|
allRecipientServiceIds,
|
||||||
|
recipientServiceIdsWithoutMe,
|
||||||
|
untrustedServiceIds,
|
||||||
|
} = getSendRecipientLists({
|
||||||
|
log,
|
||||||
|
conversation,
|
||||||
|
conversationIds: conversation.getRecipients(),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (untrustedServiceIds.length > 0) {
|
||||||
|
window.reduxActions.conversations.conversationStoppedByMissingVerification(
|
||||||
|
{
|
||||||
|
conversationId: conversation.id,
|
||||||
|
untrustedServiceIds,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
throw new Error(
|
||||||
|
`${sendType} blocked because ${untrustedServiceIds.length} ` +
|
||||||
|
'conversation(s) were untrusted. Failing this attempt.'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const messageId = getMessageId(data);
|
||||||
|
const messageOptions = getMessageOptions(data, job.timestamp);
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (recipientServiceIdsWithoutMe.length === 0) {
|
||||||
|
const sendOptions = await getSendOptions(conversation.attributes, {
|
||||||
|
syncMessage: true,
|
||||||
|
});
|
||||||
|
// Only sending a sync to ourselves
|
||||||
|
await conversation.queueJob(
|
||||||
|
`conversationQueue/${sendName}/sync`,
|
||||||
|
async () => {
|
||||||
|
const encodedDataMessage = await job.messaging.getDataOrEditMessage(
|
||||||
|
{
|
||||||
|
...messageOptions,
|
||||||
|
recipients: allRecipientServiceIds,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
return handleMessageSend(
|
||||||
|
job.messaging.sendSyncMessage({
|
||||||
|
encodedDataMessage,
|
||||||
|
timestamp: job.timestamp,
|
||||||
|
destinationE164: conversation.get('e164'),
|
||||||
|
destinationServiceId: conversation.getServiceId(),
|
||||||
|
expirationStartTimestamp: null,
|
||||||
|
isUpdate: false,
|
||||||
|
options: sendOptions,
|
||||||
|
urgent: false,
|
||||||
|
}),
|
||||||
|
{
|
||||||
|
messageIds: messageId != null ? [messageId] : [],
|
||||||
|
sendType,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
} else if (isDirectConversation(conversation.attributes)) {
|
||||||
|
const recipientServiceId = recipientServiceIdsWithoutMe.at(0);
|
||||||
|
|
||||||
|
if (recipientServiceId == null) {
|
||||||
|
log.info('Recipient was dropped');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const sendOptions = await getSendOptions(conversation.attributes);
|
||||||
|
|
||||||
|
await conversation.queueJob(
|
||||||
|
`conversationQueue/${sendName}/direct`,
|
||||||
|
() => {
|
||||||
|
return wrapWithSyncMessageSend({
|
||||||
|
conversation,
|
||||||
|
logId,
|
||||||
|
messageIds: messageId != null ? [messageId] : [],
|
||||||
|
send: sender => {
|
||||||
|
return sender.sendMessageToServiceId({
|
||||||
|
serviceId: recipientServiceId,
|
||||||
|
messageOptions: getMessageOptions(data, job.timestamp),
|
||||||
|
groupId: undefined,
|
||||||
|
contentHint: ContentHint.Resendable,
|
||||||
|
options: sendOptions,
|
||||||
|
urgent: true,
|
||||||
|
includePniSignatureMessage: true,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
sendType,
|
||||||
|
timestamp: job.timestamp,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
||||||
|
} else if (isGroupV2(conversation.attributes)) {
|
||||||
|
const sendOptions = await getSendOptions(conversation.attributes, {
|
||||||
|
groupId: conversation.get('groupId'),
|
||||||
|
});
|
||||||
|
const groupV2Info = conversation.getGroupV2Info({
|
||||||
|
members: recipientServiceIdsWithoutMe,
|
||||||
|
});
|
||||||
|
strictAssert(groupV2Info, 'Missing groupV2Info');
|
||||||
|
|
||||||
|
await conversation.queueJob(
|
||||||
|
`conversationQueue/${sendName}/group`,
|
||||||
|
abortSignal => {
|
||||||
|
return wrapWithSyncMessageSend({
|
||||||
|
conversation,
|
||||||
|
logId,
|
||||||
|
messageIds: messageId != null ? [messageId] : [],
|
||||||
|
send: () => {
|
||||||
|
return sendToGroup({
|
||||||
|
abortSignal,
|
||||||
|
contentHint: ContentHint.Resendable,
|
||||||
|
groupSendOptions: {
|
||||||
|
groupV2: groupV2Info,
|
||||||
|
...getMessageOptions(data, job.timestamp),
|
||||||
|
},
|
||||||
|
messageId: messageId ?? undefined,
|
||||||
|
sendOptions,
|
||||||
|
sendTarget: conversation.toSenderKeyTarget(),
|
||||||
|
sendType,
|
||||||
|
urgent: true,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
sendType,
|
||||||
|
timestamp: job.timestamp,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
throw new Error('Unexpected conversation type');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
const errors = maybeExpandErrors(error);
|
||||||
|
await handleMultipleSendErrors({
|
||||||
|
errors,
|
||||||
|
isFinalAttempt: job.isFinalAttempt,
|
||||||
|
log,
|
||||||
|
timeRemaining: job.timeRemaining,
|
||||||
|
toThrow: error,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
98
ts/jobs/helpers/getSendRecipientLists.dom.ts
Normal file
98
ts/jobs/helpers/getSendRecipientLists.dom.ts
Normal file
@@ -0,0 +1,98 @@
|
|||||||
|
// Copyright 2025 Signal Messenger, LLC
|
||||||
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
import type { ServiceIdString } from '@signalapp/mock-server/src/types';
|
||||||
|
import type { ConversationModel } from '../../models/conversations.preload.js';
|
||||||
|
import type { LoggerType } from '../../types/Logging.std.js';
|
||||||
|
import { isMe } from '../../util/whatTypeOfConversation.dom.js';
|
||||||
|
import { isSignalConversation } from '../../util/isSignalConversation.dom.js';
|
||||||
|
|
||||||
|
export type SendRecipientLists = Readonly<{
|
||||||
|
allRecipientServiceIds: Array<ServiceIdString>;
|
||||||
|
recipientServiceIdsWithoutMe: Array<ServiceIdString>;
|
||||||
|
untrustedServiceIds: Array<ServiceIdString>;
|
||||||
|
}>;
|
||||||
|
|
||||||
|
export type GetSendRecipientListsOptions = Readonly<{
|
||||||
|
log: LoggerType;
|
||||||
|
conversationIds: ReadonlyArray<string>;
|
||||||
|
conversation: ConversationModel;
|
||||||
|
}>;
|
||||||
|
|
||||||
|
export function getSendRecipientLists(
|
||||||
|
options: GetSendRecipientListsOptions
|
||||||
|
): SendRecipientLists {
|
||||||
|
const { log, conversationIds, conversation } = options;
|
||||||
|
|
||||||
|
const allRecipientServiceIds: Array<ServiceIdString> = [];
|
||||||
|
const recipientServiceIdsWithoutMe: Array<ServiceIdString> = [];
|
||||||
|
const untrustedServiceIds: Array<ServiceIdString> = [];
|
||||||
|
|
||||||
|
const memberConversationIds = conversation.getMemberConversationIds();
|
||||||
|
|
||||||
|
for (const conversationId of conversationIds) {
|
||||||
|
const recipient = window.ConversationController.get(conversationId);
|
||||||
|
if (!recipient) {
|
||||||
|
log.warn(
|
||||||
|
`getRecipients/${conversationId}: Missing conversation, dropping recipient`
|
||||||
|
);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const logPrefix = `getRecipients/${recipient.idForLogging()}`;
|
||||||
|
|
||||||
|
const sendTarget = recipient.getSendTarget();
|
||||||
|
if (sendTarget == null) {
|
||||||
|
log.warn(`${logPrefix}: Missing send target, dropping recipient`);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const isRecipientMe = isMe(recipient.attributes);
|
||||||
|
const isRecipientMember = memberConversationIds.has(conversationId);
|
||||||
|
|
||||||
|
if (!(isRecipientMember || isRecipientMe)) {
|
||||||
|
log.warn(
|
||||||
|
`${logPrefix}: Recipient is not a member of conversation, dropping`
|
||||||
|
);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (recipient.isUntrusted()) {
|
||||||
|
const serviceId = recipient.getServiceId();
|
||||||
|
if (!serviceId) {
|
||||||
|
log.error(
|
||||||
|
`${logPrefix}: Recipient is untrusted and missing serviceId, dropping`
|
||||||
|
);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
untrustedServiceIds.push(serviceId);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (recipient.isUnregistered()) {
|
||||||
|
log.warn(`${logPrefix}: Recipient is unregistered, dropping`);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (recipient.isBlocked()) {
|
||||||
|
log.warn(`${logPrefix}: Recipient is blocked, dropping`);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isSignalConversation(recipient.attributes)) {
|
||||||
|
log.info(`${logPrefix}: Recipient is Signal conversation, dropping`);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
allRecipientServiceIds.push(sendTarget);
|
||||||
|
if (!isRecipientMe) {
|
||||||
|
recipientServiceIdsWithoutMe.push(sendTarget);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
allRecipientServiceIds,
|
||||||
|
recipientServiceIdsWithoutMe,
|
||||||
|
untrustedServiceIds,
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -199,15 +199,13 @@ export async function sendDeleteForEveryone(
|
|||||||
sender.sendMessageToServiceId({
|
sender.sendMessageToServiceId({
|
||||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||||
serviceId: conversation.getSendTarget()!,
|
serviceId: conversation.getSendTarget()!,
|
||||||
messageText: undefined,
|
messageOptions: {
|
||||||
attachments: [],
|
|
||||||
deletedForEveryoneTimestamp: targetTimestamp,
|
deletedForEveryoneTimestamp: targetTimestamp,
|
||||||
timestamp,
|
timestamp,
|
||||||
expireTimer: undefined,
|
profileKey,
|
||||||
expireTimerVersion: undefined,
|
},
|
||||||
contentHint,
|
contentHint,
|
||||||
groupId: undefined,
|
groupId: undefined,
|
||||||
profileKey,
|
|
||||||
options: sendOptions,
|
options: sendOptions,
|
||||||
urgent: true,
|
urgent: true,
|
||||||
story,
|
story,
|
||||||
@@ -226,7 +224,8 @@ export async function sendDeleteForEveryone(
|
|||||||
const groupV2Info = conversation.getGroupV2Info({
|
const groupV2Info = conversation.getGroupV2Info({
|
||||||
members: recipients,
|
members: recipients,
|
||||||
});
|
});
|
||||||
if (groupV2Info && isNumber(revision)) {
|
strictAssert(groupV2Info, 'Missing groupV2Info');
|
||||||
|
if (isNumber(revision)) {
|
||||||
groupV2Info.revision = revision;
|
groupV2Info.revision = revision;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -180,17 +180,15 @@ export async function sendDeleteStoryForEveryone(
|
|||||||
await handleMessageSend(
|
await handleMessageSend(
|
||||||
messaging.sendMessageToServiceId({
|
messaging.sendMessageToServiceId({
|
||||||
serviceId,
|
serviceId,
|
||||||
messageText: undefined,
|
messageOptions: {
|
||||||
attachments: [],
|
|
||||||
deletedForEveryoneTimestamp: targetTimestamp,
|
deletedForEveryoneTimestamp: targetTimestamp,
|
||||||
timestamp,
|
timestamp,
|
||||||
expireTimer: undefined,
|
|
||||||
expireTimerVersion: undefined,
|
|
||||||
contentHint,
|
|
||||||
groupId: undefined,
|
|
||||||
profileKey: conversation.get('profileSharing')
|
profileKey: conversation.get('profileSharing')
|
||||||
? profileKey
|
? profileKey
|
||||||
: undefined,
|
: undefined,
|
||||||
|
},
|
||||||
|
groupId: undefined,
|
||||||
|
contentHint,
|
||||||
options: sendOptions,
|
options: sendOptions,
|
||||||
urgent: true,
|
urgent: true,
|
||||||
story: true,
|
story: true,
|
||||||
|
|||||||
@@ -340,7 +340,8 @@ export async function sendNormalMessage(
|
|||||||
const groupV2Info = conversation.getGroupV2Info({
|
const groupV2Info = conversation.getGroupV2Info({
|
||||||
members: recipientServiceIdsWithoutMe,
|
members: recipientServiceIdsWithoutMe,
|
||||||
});
|
});
|
||||||
if (groupV2Info && isNumber(revision)) {
|
strictAssert(groupV2Info, 'Missing groupV2Info');
|
||||||
|
if (isNumber(revision)) {
|
||||||
groupV2Info.revision = revision;
|
groupV2Info.revision = revision;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -358,7 +359,7 @@ export async function sendNormalMessage(
|
|||||||
deletedForEveryoneTimestamp,
|
deletedForEveryoneTimestamp,
|
||||||
expireTimer,
|
expireTimer,
|
||||||
groupV2: groupV2Info,
|
groupV2: groupV2Info,
|
||||||
messageText: body,
|
body,
|
||||||
preview,
|
preview,
|
||||||
profileKey,
|
profileKey,
|
||||||
quote,
|
quote,
|
||||||
@@ -416,17 +417,15 @@ export async function sendNormalMessage(
|
|||||||
|
|
||||||
log.info('sending direct message');
|
log.info('sending direct message');
|
||||||
innerPromise = messaging.sendMessageToServiceId({
|
innerPromise = messaging.sendMessageToServiceId({
|
||||||
|
serviceId: recipientServiceIdsWithoutMe[0],
|
||||||
|
messageOptions: {
|
||||||
attachments,
|
attachments,
|
||||||
|
body,
|
||||||
bodyRanges,
|
bodyRanges,
|
||||||
contact,
|
contact,
|
||||||
contentHint: ContentHint.Resendable,
|
|
||||||
deletedForEveryoneTimestamp,
|
deletedForEveryoneTimestamp,
|
||||||
expireTimer,
|
expireTimer,
|
||||||
expireTimerVersion: conversation.getExpireTimerVersion(),
|
expireTimerVersion: conversation.getExpireTimerVersion(),
|
||||||
groupId: undefined,
|
|
||||||
serviceId: recipientServiceIdsWithoutMe[0],
|
|
||||||
messageText: body,
|
|
||||||
options: sendOptions,
|
|
||||||
preview,
|
preview,
|
||||||
profileKey,
|
profileKey,
|
||||||
quote,
|
quote,
|
||||||
@@ -437,6 +436,10 @@ export async function sendNormalMessage(
|
|||||||
? targetOfThisEditTimestamp
|
? targetOfThisEditTimestamp
|
||||||
: undefined,
|
: undefined,
|
||||||
timestamp: targetTimestamp,
|
timestamp: targetTimestamp,
|
||||||
|
},
|
||||||
|
contentHint: ContentHint.Resendable,
|
||||||
|
groupId: undefined,
|
||||||
|
options: sendOptions,
|
||||||
// Note: 1:1 story replies should not set story=true - they aren't group sends
|
// Note: 1:1 story replies should not set story=true - they aren't group sends
|
||||||
urgent: true,
|
urgent: true,
|
||||||
includePniSignatureMessage: true,
|
includePniSignatureMessage: true,
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ import {
|
|||||||
import { MessageSender } from '../../textsecure/SendMessage.preload.js';
|
import { MessageSender } from '../../textsecure/SendMessage.preload.js';
|
||||||
import { sendToGroup } from '../../util/sendToGroup.preload.js';
|
import { sendToGroup } from '../../util/sendToGroup.preload.js';
|
||||||
import { itemStorage } from '../../textsecure/Storage.preload.js';
|
import { itemStorage } from '../../textsecure/Storage.preload.js';
|
||||||
|
import { strictAssert } from '../../util/assert.std.js';
|
||||||
|
|
||||||
async function clearResetsTracking(idForTracking: string | undefined) {
|
async function clearResetsTracking(idForTracking: string | undefined) {
|
||||||
if (!idForTracking) {
|
if (!idForTracking) {
|
||||||
@@ -101,9 +102,8 @@ export async function sendNullMessage(
|
|||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
const groupV2Info = conversation.getGroupV2Info();
|
const groupV2Info = conversation.getGroupV2Info();
|
||||||
if (groupV2Info) {
|
strictAssert(groupV2Info, 'Missing groupV2Info');
|
||||||
groupV2Info.revision = 0;
|
groupV2Info.revision = 0;
|
||||||
}
|
|
||||||
|
|
||||||
await conversation.queueJob(
|
await conversation.queueJob(
|
||||||
'conversationQueue/sendNullMessage/group',
|
'conversationQueue/sendNullMessage/group',
|
||||||
@@ -118,7 +118,7 @@ export async function sendNullMessage(
|
|||||||
deletedForEveryoneTimestamp: undefined,
|
deletedForEveryoneTimestamp: undefined,
|
||||||
expireTimer: undefined,
|
expireTimer: undefined,
|
||||||
groupV2: groupV2Info,
|
groupV2: groupV2Info,
|
||||||
messageText: undefined,
|
body: undefined,
|
||||||
preview: [],
|
preview: [],
|
||||||
profileKey: undefined,
|
profileKey: undefined,
|
||||||
quote: undefined,
|
quote: undefined,
|
||||||
|
|||||||
22
ts/jobs/helpers/sendPinMessage.preload.ts
Normal file
22
ts/jobs/helpers/sendPinMessage.preload.ts
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
// Copyright 2025 Signal Messenger, LLC
|
||||||
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
import type { PinMessageJobData } from '../conversationJobQueue.preload.js';
|
||||||
|
import { createSendMessageJob } from './createSendMessageJob.preload.js';
|
||||||
|
|
||||||
|
export const sendPinMessage = createSendMessageJob<PinMessageJobData>({
|
||||||
|
sendName: 'sendPinMessage',
|
||||||
|
sendType: 'pinMessage',
|
||||||
|
getMessageId(data) {
|
||||||
|
return data.targetMessageId;
|
||||||
|
},
|
||||||
|
getMessageOptions(data, jobTimestamp) {
|
||||||
|
return {
|
||||||
|
timestamp: jobTimestamp,
|
||||||
|
pinMessage: {
|
||||||
|
targetAuthorAci: data.targetAuthorAci,
|
||||||
|
targetSentTimestamp: data.targetSentTimestamp,
|
||||||
|
pinDurationSeconds: data.pinDurationSeconds,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
});
|
||||||
@@ -3,7 +3,7 @@
|
|||||||
|
|
||||||
import { ContentHint } from '@signalapp/libsignal-client';
|
import { ContentHint } from '@signalapp/libsignal-client';
|
||||||
import * as Errors from '../../types/errors.std.js';
|
import * as Errors from '../../types/errors.std.js';
|
||||||
import { isGroupV2, isMe } from '../../util/whatTypeOfConversation.dom.js';
|
import { isGroupV2 } from '../../util/whatTypeOfConversation.dom.js';
|
||||||
import { getSendOptions } from '../../util/getSendOptions.preload.js';
|
import { getSendOptions } from '../../util/getSendOptions.preload.js';
|
||||||
import { handleMessageSend } from '../../util/handleMessageSend.preload.js';
|
import { handleMessageSend } from '../../util/handleMessageSend.preload.js';
|
||||||
import { sendContentMessageToGroup } from '../../util/sendToGroup.preload.js';
|
import { sendContentMessageToGroup } from '../../util/sendToGroup.preload.js';
|
||||||
@@ -19,8 +19,6 @@ import {
|
|||||||
SendStatus,
|
SendStatus,
|
||||||
type SendStateByConversationId,
|
type SendStateByConversationId,
|
||||||
} from '../../messages/MessageSendState.std.js';
|
} from '../../messages/MessageSendState.std.js';
|
||||||
import type { ServiceIdString } from '../../types/ServiceId.std.js';
|
|
||||||
import type { LoggerType } from '../../types/Logging.std.js';
|
|
||||||
import type { MessagePollVoteType } from '../../types/Polls.dom.js';
|
import type { MessagePollVoteType } from '../../types/Polls.dom.js';
|
||||||
import type { ConversationModel } from '../../models/conversations.preload.js';
|
import type { ConversationModel } from '../../models/conversations.preload.js';
|
||||||
import type {
|
import type {
|
||||||
@@ -29,6 +27,7 @@ import type {
|
|||||||
} from '../conversationJobQueue.preload.js';
|
} from '../conversationJobQueue.preload.js';
|
||||||
import * as pollVoteUtil from '../../polls/util.std.js';
|
import * as pollVoteUtil from '../../polls/util.std.js';
|
||||||
import { strictAssert } from '../../util/assert.std.js';
|
import { strictAssert } from '../../util/assert.std.js';
|
||||||
|
import { getSendRecipientLists } from './getSendRecipientLists.dom.js';
|
||||||
|
|
||||||
export async function sendPollVote(
|
export async function sendPollVote(
|
||||||
conversation: ConversationModel,
|
conversation: ConversationModel,
|
||||||
@@ -122,11 +121,15 @@ export async function sendPollVote(
|
|||||||
const currentOptionIndexes = [...currentPendingVote.optionIndexes];
|
const currentOptionIndexes = [...currentPendingVote.optionIndexes];
|
||||||
const currentTimestamp = currentPendingVote.timestamp;
|
const currentTimestamp = currentPendingVote.timestamp;
|
||||||
|
|
||||||
const { recipientServiceIdsWithoutMe, untrustedServiceIds } = getRecipients(
|
const unsentConversationIds = Array.from(
|
||||||
jobLog,
|
pollVoteUtil.getUnsentConversationIds(currentPendingVote)
|
||||||
currentPendingVote,
|
|
||||||
conversation
|
|
||||||
);
|
);
|
||||||
|
const { recipientServiceIdsWithoutMe, untrustedServiceIds } =
|
||||||
|
getSendRecipientLists({
|
||||||
|
log: jobLog,
|
||||||
|
conversationIds: unsentConversationIds,
|
||||||
|
conversation,
|
||||||
|
});
|
||||||
|
|
||||||
if (untrustedServiceIds.length) {
|
if (untrustedServiceIds.length) {
|
||||||
window.reduxActions.conversations.conversationStoppedByMissingVerification(
|
window.reduxActions.conversations.conversationStoppedByMissingVerification(
|
||||||
@@ -145,9 +148,6 @@ export async function sendPollVote(
|
|||||||
? await ourProfileKeyService.get()
|
? await ourProfileKeyService.get()
|
||||||
: undefined;
|
: undefined;
|
||||||
|
|
||||||
const unsentConversationIds = Array.from(
|
|
||||||
pollVoteUtil.getUnsentConversationIds(currentPendingVote)
|
|
||||||
);
|
|
||||||
const ephemeral = new MessageModel({
|
const ephemeral = new MessageModel({
|
||||||
...generateMessageId(incrementMessageCounter()),
|
...generateMessageId(incrementMessageCounter()),
|
||||||
type: 'outgoing',
|
type: 'outgoing',
|
||||||
@@ -213,15 +213,15 @@ export async function sendPollVote(
|
|||||||
const groupV2Info = conversation.getGroupV2Info({
|
const groupV2Info = conversation.getGroupV2Info({
|
||||||
members: recipientServiceIdsWithoutMe,
|
members: recipientServiceIdsWithoutMe,
|
||||||
});
|
});
|
||||||
if (groupV2Info && revision != null) {
|
|
||||||
groupV2Info.revision = revision;
|
|
||||||
}
|
|
||||||
|
|
||||||
strictAssert(
|
strictAssert(
|
||||||
groupV2Info,
|
groupV2Info,
|
||||||
'could not get group info from conversation'
|
'could not get group info from conversation'
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (revision != null) {
|
||||||
|
groupV2Info.revision = revision;
|
||||||
|
}
|
||||||
|
|
||||||
const contentMessage = await messaging.getPollVoteContentMessage({
|
const contentMessage = await messaging.getPollVoteContentMessage({
|
||||||
groupV2: groupV2Info,
|
groupV2: groupV2Info,
|
||||||
timestamp: currentTimestamp,
|
timestamp: currentTimestamp,
|
||||||
@@ -338,70 +338,3 @@ export async function sendPollVote(
|
|||||||
await window.MessageCache.saveMessage(pollMessage.attributes);
|
await window.MessageCache.saveMessage(pollMessage.attributes);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function getRecipients(
|
|
||||||
log: LoggerType,
|
|
||||||
pendingVote: MessagePollVoteType,
|
|
||||||
conversation: ConversationModel
|
|
||||||
): {
|
|
||||||
allRecipientServiceIds: Array<ServiceIdString>;
|
|
||||||
recipientServiceIdsWithoutMe: Array<ServiceIdString>;
|
|
||||||
untrustedServiceIds: Array<ServiceIdString>;
|
|
||||||
} {
|
|
||||||
const allRecipientServiceIds: Array<ServiceIdString> = [];
|
|
||||||
const recipientServiceIdsWithoutMe: Array<ServiceIdString> = [];
|
|
||||||
const untrustedServiceIds: Array<ServiceIdString> = [];
|
|
||||||
|
|
||||||
const currentConversationRecipients = conversation.getMemberConversationIds();
|
|
||||||
|
|
||||||
// Only send to recipients who haven't received this vote yet
|
|
||||||
for (const conversationId of pollVoteUtil.getUnsentConversationIds(
|
|
||||||
pendingVote
|
|
||||||
)) {
|
|
||||||
const recipient = window.ConversationController.get(conversationId);
|
|
||||||
if (!recipient) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const recipientIdentifier = recipient.getSendTarget();
|
|
||||||
const isRecipientMe = isMe(recipient.attributes);
|
|
||||||
|
|
||||||
if (
|
|
||||||
!recipientIdentifier ||
|
|
||||||
(!currentConversationRecipients.has(conversationId) && !isRecipientMe)
|
|
||||||
) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (recipient.isUntrusted()) {
|
|
||||||
const serviceId = recipient.getServiceId();
|
|
||||||
if (!serviceId) {
|
|
||||||
log.error(
|
|
||||||
`sendPollVote/getRecipients: Recipient ${recipient.idForLogging()} is untrusted but has no serviceId`
|
|
||||||
);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
untrustedServiceIds.push(serviceId);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (recipient.isUnregistered()) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (recipient.isBlocked()) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
allRecipientServiceIds.push(recipientIdentifier);
|
|
||||||
if (!isRecipientMe) {
|
|
||||||
recipientServiceIdsWithoutMe.push(recipientIdentifier);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
allRecipientServiceIds,
|
|
||||||
recipientServiceIdsWithoutMe,
|
|
||||||
untrustedServiceIds,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ import {
|
|||||||
import { shouldSendToConversation } from './shouldSendToConversation.preload.js';
|
import { shouldSendToConversation } from './shouldSendToConversation.preload.js';
|
||||||
import { sendToGroup } from '../../util/sendToGroup.preload.js';
|
import { sendToGroup } from '../../util/sendToGroup.preload.js';
|
||||||
import { itemStorage } from '../../textsecure/Storage.preload.js';
|
import { itemStorage } from '../../textsecure/Storage.preload.js';
|
||||||
|
import { strictAssert } from '../../util/assert.std.js';
|
||||||
|
|
||||||
const { isNumber } = lodash;
|
const { isNumber } = lodash;
|
||||||
|
|
||||||
@@ -148,7 +149,8 @@ export async function sendProfileKey(
|
|||||||
}
|
}
|
||||||
|
|
||||||
const groupV2Info = conversation.getGroupV2Info();
|
const groupV2Info = conversation.getGroupV2Info();
|
||||||
if (groupV2Info && isNumber(revision)) {
|
strictAssert(groupV2Info, 'Missing groupV2Info');
|
||||||
|
if (isNumber(revision)) {
|
||||||
groupV2Info.revision = revision;
|
groupV2Info.revision = revision;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -17,7 +17,6 @@ import { isSent, SendStatus } from '../../messages/MessageSendState.std.js';
|
|||||||
import { getMessageById } from '../../messages/getMessageById.preload.js';
|
import { getMessageById } from '../../messages/getMessageById.preload.js';
|
||||||
import { isIncoming } from '../../messages/helpers.std.js';
|
import { isIncoming } from '../../messages/helpers.std.js';
|
||||||
import {
|
import {
|
||||||
isMe,
|
|
||||||
isDirectConversation,
|
isDirectConversation,
|
||||||
isGroupV2,
|
isGroupV2,
|
||||||
} from '../../util/whatTypeOfConversation.dom.js';
|
} from '../../util/whatTypeOfConversation.dom.js';
|
||||||
@@ -26,7 +25,7 @@ import { handleMessageSend } from '../../util/handleMessageSend.preload.js';
|
|||||||
import { ourProfileKeyService } from '../../services/ourProfileKey.std.js';
|
import { ourProfileKeyService } from '../../services/ourProfileKey.std.js';
|
||||||
import { canReact, isStory } from '../../state/selectors/message.preload.js';
|
import { canReact, isStory } from '../../state/selectors/message.preload.js';
|
||||||
import { findAndFormatContact } from '../../util/findAndFormatContact.preload.js';
|
import { findAndFormatContact } from '../../util/findAndFormatContact.preload.js';
|
||||||
import type { AciString, ServiceIdString } from '../../types/ServiceId.std.js';
|
import type { AciString } from '../../types/ServiceId.std.js';
|
||||||
import { isAciString } from '../../util/isAciString.std.js';
|
import { isAciString } from '../../util/isAciString.std.js';
|
||||||
import { handleMultipleSendErrors } from './handleMultipleSendErrors.std.js';
|
import { handleMultipleSendErrors } from './handleMultipleSendErrors.std.js';
|
||||||
import { incrementMessageCounter } from '../../util/incrementMessageCounter.preload.js';
|
import { incrementMessageCounter } from '../../util/incrementMessageCounter.preload.js';
|
||||||
@@ -38,11 +37,11 @@ import type {
|
|||||||
} from '../conversationJobQueue.preload.js';
|
} from '../conversationJobQueue.preload.js';
|
||||||
import { isConversationAccepted } from '../../util/isConversationAccepted.preload.js';
|
import { isConversationAccepted } from '../../util/isConversationAccepted.preload.js';
|
||||||
import { isConversationUnregistered } from '../../util/isConversationUnregistered.dom.js';
|
import { isConversationUnregistered } from '../../util/isConversationUnregistered.dom.js';
|
||||||
import type { LoggerType } from '../../types/Logging.std.js';
|
|
||||||
import { sendToGroup } from '../../util/sendToGroup.preload.js';
|
import { sendToGroup } from '../../util/sendToGroup.preload.js';
|
||||||
import { hydrateStoryContext } from '../../util/hydrateStoryContext.preload.js';
|
import { hydrateStoryContext } from '../../util/hydrateStoryContext.preload.js';
|
||||||
import { send, sendSyncMessageOnly } from '../../messages/send.preload.js';
|
import { send, sendSyncMessageOnly } from '../../messages/send.preload.js';
|
||||||
import { itemStorage } from '../../textsecure/Storage.preload.js';
|
import { itemStorage } from '../../textsecure/Storage.preload.js';
|
||||||
|
import { getSendRecipientLists } from './getSendRecipientLists.dom.js';
|
||||||
|
|
||||||
const { isNumber } = lodash;
|
const { isNumber } = lodash;
|
||||||
|
|
||||||
@@ -123,11 +122,18 @@ export async function sendReaction(
|
|||||||
}
|
}
|
||||||
|
|
||||||
const expireTimer = messageConversation.get('expireTimer');
|
const expireTimer = messageConversation.get('expireTimer');
|
||||||
|
const unsentConversationIds = Array.from(
|
||||||
|
reactionUtil.getUnsentConversationIds(pendingReaction)
|
||||||
|
);
|
||||||
const {
|
const {
|
||||||
allRecipientServiceIds,
|
allRecipientServiceIds,
|
||||||
recipientServiceIdsWithoutMe,
|
recipientServiceIdsWithoutMe,
|
||||||
untrustedServiceIds,
|
untrustedServiceIds,
|
||||||
} = getRecipients(log, pendingReaction, conversation);
|
} = getSendRecipientLists({
|
||||||
|
log,
|
||||||
|
conversationIds: unsentConversationIds,
|
||||||
|
conversation,
|
||||||
|
});
|
||||||
|
|
||||||
if (untrustedServiceIds.length) {
|
if (untrustedServiceIds.length) {
|
||||||
window.reduxActions.conversations.conversationStoppedByMissingVerification(
|
window.reduxActions.conversations.conversationStoppedByMissingVerification(
|
||||||
@@ -242,19 +248,15 @@ export async function sendReaction(
|
|||||||
log.info('sending direct reaction message');
|
log.info('sending direct reaction message');
|
||||||
promise = messaging.sendMessageToServiceId({
|
promise = messaging.sendMessageToServiceId({
|
||||||
serviceId: recipientServiceIdsWithoutMe[0],
|
serviceId: recipientServiceIdsWithoutMe[0],
|
||||||
messageText: undefined,
|
messageOptions: {
|
||||||
attachments: [],
|
|
||||||
quote: undefined,
|
|
||||||
preview: [],
|
|
||||||
sticker: undefined,
|
|
||||||
reaction: reactionForSend,
|
reaction: reactionForSend,
|
||||||
deletedForEveryoneTimestamp: undefined,
|
|
||||||
timestamp: pendingReaction.timestamp,
|
timestamp: pendingReaction.timestamp,
|
||||||
expireTimer,
|
expireTimer,
|
||||||
expireTimerVersion: conversation.getExpireTimerVersion(),
|
expireTimerVersion: conversation.getExpireTimerVersion(),
|
||||||
contentHint: ContentHint.Resendable,
|
|
||||||
groupId: undefined,
|
|
||||||
profileKey,
|
profileKey,
|
||||||
|
},
|
||||||
|
groupId: undefined,
|
||||||
|
contentHint: ContentHint.Resendable,
|
||||||
options: sendOptions,
|
options: sendOptions,
|
||||||
urgent: true,
|
urgent: true,
|
||||||
includePniSignatureMessage: true,
|
includePniSignatureMessage: true,
|
||||||
@@ -272,7 +274,8 @@ export async function sendReaction(
|
|||||||
const groupV2Info = conversation.getGroupV2Info({
|
const groupV2Info = conversation.getGroupV2Info({
|
||||||
members: recipientServiceIdsWithoutMe,
|
members: recipientServiceIdsWithoutMe,
|
||||||
});
|
});
|
||||||
if (groupV2Info && isNumber(revision)) {
|
strictAssert(groupV2Info, 'Missing groupV2Info');
|
||||||
|
if (isNumber(revision)) {
|
||||||
groupV2Info.revision = revision;
|
groupV2Info.revision = revision;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -393,68 +396,6 @@ const setReactions = (
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
function getRecipients(
|
|
||||||
log: LoggerType,
|
|
||||||
reaction: Readonly<MessageReactionType>,
|
|
||||||
conversation: ConversationModel
|
|
||||||
): {
|
|
||||||
allRecipientServiceIds: Array<ServiceIdString>;
|
|
||||||
recipientServiceIdsWithoutMe: Array<ServiceIdString>;
|
|
||||||
untrustedServiceIds: Array<ServiceIdString>;
|
|
||||||
} {
|
|
||||||
const allRecipientServiceIds: Array<ServiceIdString> = [];
|
|
||||||
const recipientServiceIdsWithoutMe: Array<ServiceIdString> = [];
|
|
||||||
const untrustedServiceIds: Array<ServiceIdString> = [];
|
|
||||||
|
|
||||||
const currentConversationRecipients = conversation.getMemberConversationIds();
|
|
||||||
|
|
||||||
for (const id of reactionUtil.getUnsentConversationIds(reaction)) {
|
|
||||||
const recipient = window.ConversationController.get(id);
|
|
||||||
if (!recipient) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const recipientIdentifier = recipient.getSendTarget();
|
|
||||||
const isRecipientMe = isMe(recipient.attributes);
|
|
||||||
|
|
||||||
if (
|
|
||||||
!recipientIdentifier ||
|
|
||||||
(!currentConversationRecipients.has(id) && !isRecipientMe)
|
|
||||||
) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (recipient.isUntrusted()) {
|
|
||||||
const serviceId = recipient.getServiceId();
|
|
||||||
if (!serviceId) {
|
|
||||||
log.error(
|
|
||||||
`getRecipients: Untrusted conversation ${recipient.idForLogging()} missing serviceId.`
|
|
||||||
);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
untrustedServiceIds.push(serviceId);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (recipient.isUnregistered()) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (recipient.isBlocked()) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
allRecipientServiceIds.push(recipientIdentifier);
|
|
||||||
if (!isRecipientMe) {
|
|
||||||
recipientServiceIdsWithoutMe.push(recipientIdentifier);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
allRecipientServiceIds,
|
|
||||||
recipientServiceIdsWithoutMe,
|
|
||||||
untrustedServiceIds,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function markReactionFailed(
|
function markReactionFailed(
|
||||||
message: MessageModel,
|
message: MessageModel,
|
||||||
pendingReaction: MessageReactionType
|
pendingReaction: MessageReactionType
|
||||||
|
|||||||
22
ts/jobs/helpers/sendUnpinMessage.preload.ts
Normal file
22
ts/jobs/helpers/sendUnpinMessage.preload.ts
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
// Copyright 2025 Signal Messenger, LLC
|
||||||
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
import type { UnpinMessageJobData } from '../conversationJobQueue.preload.js';
|
||||||
|
import { createSendMessageJob } from './createSendMessageJob.preload.js';
|
||||||
|
|
||||||
|
export const sendUnpinMessage = createSendMessageJob<UnpinMessageJobData>({
|
||||||
|
sendName: 'sendUnpinMessage',
|
||||||
|
sendType: 'unpinMessage',
|
||||||
|
getMessageId(data) {
|
||||||
|
return data.targetMessageId;
|
||||||
|
},
|
||||||
|
getMessageOptions(data, jobTimestamp) {
|
||||||
|
return {
|
||||||
|
timestamp: jobTimestamp,
|
||||||
|
unpinMessage: {
|
||||||
|
targetAuthorAci: data.targetAuthorAci,
|
||||||
|
targetSentTimestamp: data.targetSentTimestamp,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
});
|
||||||
@@ -104,6 +104,10 @@ import type { GroupSendToken } from '../types/GroupSendEndorsements.std.js';
|
|||||||
import type { OutgoingPollVote, PollCreateType } from '../types/Polls.dom.js';
|
import type { OutgoingPollVote, PollCreateType } from '../types/Polls.dom.js';
|
||||||
import { itemStorage } from './Storage.preload.js';
|
import { itemStorage } from './Storage.preload.js';
|
||||||
import { accountManager } from './AccountManager.preload.js';
|
import { accountManager } from './AccountManager.preload.js';
|
||||||
|
import type {
|
||||||
|
SendPinMessageType,
|
||||||
|
SendUnpinMessageType,
|
||||||
|
} from '../types/PinnedMessage.std.js';
|
||||||
|
|
||||||
const log = createLogger('SendMessage');
|
const log = createLogger('SendMessage');
|
||||||
|
|
||||||
@@ -195,61 +199,48 @@ export const singleProtoJobDataSchema = z.object({
|
|||||||
|
|
||||||
export type SingleProtoJobData = z.infer<typeof singleProtoJobDataSchema>;
|
export type SingleProtoJobData = z.infer<typeof singleProtoJobDataSchema>;
|
||||||
|
|
||||||
export type MessageOptionsType = {
|
export type SharedMessageOptionsType = Readonly<{
|
||||||
|
// required
|
||||||
|
timestamp: number;
|
||||||
|
// optional
|
||||||
attachments?: ReadonlyArray<Proto.IAttachmentPointer>;
|
attachments?: ReadonlyArray<Proto.IAttachmentPointer>;
|
||||||
body?: string;
|
body?: string;
|
||||||
bodyRanges?: ReadonlyArray<RawBodyRange>;
|
bodyRanges?: ReadonlyArray<RawBodyRange>;
|
||||||
contact?: ReadonlyArray<EmbeddedContactWithUploadedAvatar>;
|
contact?: ReadonlyArray<EmbeddedContactWithUploadedAvatar>;
|
||||||
|
deletedForEveryoneTimestamp?: number;
|
||||||
expireTimer?: DurationInSeconds;
|
expireTimer?: DurationInSeconds;
|
||||||
expireTimerVersion: number | undefined;
|
|
||||||
flags?: number;
|
flags?: number;
|
||||||
group?: {
|
groupCallUpdate?: GroupCallUpdateType;
|
||||||
id: string;
|
|
||||||
type: number;
|
|
||||||
};
|
|
||||||
groupV2?: GroupV2InfoType;
|
groupV2?: GroupV2InfoType;
|
||||||
needsSync?: boolean;
|
pinMessage?: SendPinMessageType;
|
||||||
|
pollVote?: OutgoingPollVote;
|
||||||
|
pollCreate?: PollCreateType;
|
||||||
|
pollTerminate?: Readonly<{
|
||||||
|
targetTimestamp: number;
|
||||||
|
}>;
|
||||||
preview?: ReadonlyArray<OutgoingLinkPreviewType>;
|
preview?: ReadonlyArray<OutgoingLinkPreviewType>;
|
||||||
profileKey?: Uint8Array;
|
profileKey?: Uint8Array;
|
||||||
quote?: OutgoingQuoteType;
|
quote?: OutgoingQuoteType;
|
||||||
|
reaction?: ReactionType;
|
||||||
|
sticker?: OutgoingStickerType;
|
||||||
|
storyContext?: StoryContextType;
|
||||||
|
targetTimestampForEdit?: number;
|
||||||
|
unpinMessage?: SendUnpinMessageType;
|
||||||
|
}>;
|
||||||
|
|
||||||
|
export type MessageOptionsType = Readonly<
|
||||||
|
SharedMessageOptionsType & {
|
||||||
|
// Not needed for group messages, lives in group state
|
||||||
|
expireTimerVersion?: number | undefined;
|
||||||
recipients: ReadonlyArray<ServiceIdString>;
|
recipients: ReadonlyArray<ServiceIdString>;
|
||||||
sticker?: OutgoingStickerType;
|
}
|
||||||
reaction?: ReactionType;
|
>;
|
||||||
pollVote?: OutgoingPollVote;
|
|
||||||
pollCreate?: PollCreateType;
|
export type GroupMessageOptionsType = Readonly<
|
||||||
pollTerminate?: Readonly<{
|
SharedMessageOptionsType & {
|
||||||
targetTimestamp: number;
|
groupV2: GroupV2InfoType;
|
||||||
}>;
|
}
|
||||||
deletedForEveryoneTimestamp?: number;
|
>;
|
||||||
targetTimestampForEdit?: number;
|
|
||||||
timestamp: number;
|
|
||||||
groupCallUpdate?: GroupCallUpdateType;
|
|
||||||
storyContext?: StoryContextType;
|
|
||||||
};
|
|
||||||
export type GroupSendOptionsType = {
|
|
||||||
attachments?: ReadonlyArray<Proto.IAttachmentPointer>;
|
|
||||||
bodyRanges?: ReadonlyArray<RawBodyRange>;
|
|
||||||
contact?: ReadonlyArray<EmbeddedContactWithUploadedAvatar>;
|
|
||||||
deletedForEveryoneTimestamp?: number;
|
|
||||||
targetTimestampForEdit?: number;
|
|
||||||
expireTimer?: DurationInSeconds;
|
|
||||||
flags?: number;
|
|
||||||
groupCallUpdate?: GroupCallUpdateType;
|
|
||||||
groupV2?: GroupV2InfoType;
|
|
||||||
messageText?: string;
|
|
||||||
preview?: ReadonlyArray<OutgoingLinkPreviewType>;
|
|
||||||
profileKey?: Uint8Array;
|
|
||||||
quote?: OutgoingQuoteType;
|
|
||||||
reaction?: ReactionType;
|
|
||||||
sticker?: OutgoingStickerType;
|
|
||||||
storyContext?: StoryContextType;
|
|
||||||
timestamp: number;
|
|
||||||
pollVote?: OutgoingPollVote;
|
|
||||||
pollCreate?: PollCreateType;
|
|
||||||
pollTerminate?: Readonly<{
|
|
||||||
targetTimestamp: number;
|
|
||||||
}>;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type PollVoteBuildOptions = Required<
|
export type PollVoteBuildOptions = Required<
|
||||||
Pick<MessageOptionsType, 'groupV2' | 'timestamp' | 'pollVote'>
|
Pick<MessageOptionsType, 'groupV2' | 'timestamp' | 'pollVote'>
|
||||||
@@ -276,15 +267,8 @@ class Message {
|
|||||||
|
|
||||||
flags?: number;
|
flags?: number;
|
||||||
|
|
||||||
group?: {
|
|
||||||
id: string;
|
|
||||||
type: number;
|
|
||||||
};
|
|
||||||
|
|
||||||
groupV2?: GroupV2InfoType;
|
groupV2?: GroupV2InfoType;
|
||||||
|
|
||||||
needsSync?: boolean;
|
|
||||||
|
|
||||||
preview?: ReadonlyArray<OutgoingLinkPreviewType>;
|
preview?: ReadonlyArray<OutgoingLinkPreviewType>;
|
||||||
|
|
||||||
profileKey?: Uint8Array;
|
profileKey?: Uint8Array;
|
||||||
@@ -303,6 +287,9 @@ class Message {
|
|||||||
targetTimestamp: number;
|
targetTimestamp: number;
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
|
pinMessage?: SendPinMessageType;
|
||||||
|
unpinMessage?: SendUnpinMessageType;
|
||||||
|
|
||||||
timestamp: number;
|
timestamp: number;
|
||||||
|
|
||||||
dataMessage?: Proto.DataMessage;
|
dataMessage?: Proto.DataMessage;
|
||||||
@@ -323,9 +310,7 @@ class Message {
|
|||||||
this.expireTimer = options.expireTimer;
|
this.expireTimer = options.expireTimer;
|
||||||
this.expireTimerVersion = options.expireTimerVersion;
|
this.expireTimerVersion = options.expireTimerVersion;
|
||||||
this.flags = options.flags;
|
this.flags = options.flags;
|
||||||
this.group = options.group;
|
|
||||||
this.groupV2 = options.groupV2;
|
this.groupV2 = options.groupV2;
|
||||||
this.needsSync = options.needsSync;
|
|
||||||
this.preview = options.preview;
|
this.preview = options.preview;
|
||||||
this.profileKey = options.profileKey;
|
this.profileKey = options.profileKey;
|
||||||
this.quote = options.quote;
|
this.quote = options.quote;
|
||||||
@@ -340,12 +325,14 @@ class Message {
|
|||||||
this.storyContext = options.storyContext;
|
this.storyContext = options.storyContext;
|
||||||
// Polls
|
// Polls
|
||||||
this.pollVote = options.pollVote;
|
this.pollVote = options.pollVote;
|
||||||
|
this.pinMessage = options.pinMessage;
|
||||||
|
this.unpinMessage = options.unpinMessage;
|
||||||
|
|
||||||
if (!(this.recipients instanceof Array)) {
|
if (!(this.recipients instanceof Array)) {
|
||||||
throw new Error('Invalid recipient list');
|
throw new Error('Invalid recipient list');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!this.group && !this.groupV2 && this.recipients.length !== 1) {
|
if (!this.groupV2 && this.recipients.length !== 1) {
|
||||||
throw new Error('Invalid recipient list for non-group');
|
throw new Error('Invalid recipient list for non-group');
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -370,29 +357,15 @@ class Message {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (this.isEndSession()) {
|
if (this.isEndSession()) {
|
||||||
if (
|
if (this.body != null || this.attachments.length !== 0) {
|
||||||
this.body != null ||
|
|
||||||
this.group != null ||
|
|
||||||
this.attachments.length !== 0
|
|
||||||
) {
|
|
||||||
throw new Error('Invalid end session message');
|
throw new Error('Invalid end session message');
|
||||||
}
|
}
|
||||||
} else {
|
} else if (
|
||||||
if (
|
|
||||||
typeof this.timestamp !== 'number' ||
|
typeof this.timestamp !== 'number' ||
|
||||||
(this.body && typeof this.body !== 'string')
|
(this.body && typeof this.body !== 'string')
|
||||||
) {
|
) {
|
||||||
throw new Error('Invalid message body');
|
throw new Error('Invalid message body');
|
||||||
}
|
}
|
||||||
if (this.group) {
|
|
||||||
if (
|
|
||||||
typeof this.group.id !== 'string' ||
|
|
||||||
typeof this.group.type !== 'number'
|
|
||||||
) {
|
|
||||||
throw new Error('Invalid group context');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
isEndSession() {
|
isEndSession() {
|
||||||
@@ -682,6 +655,35 @@ class Message {
|
|||||||
proto.requiredProtocolVersion = Proto.DataMessage.ProtocolVersion.POLLS;
|
proto.requiredProtocolVersion = Proto.DataMessage.ProtocolVersion.POLLS;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.pinMessage != null) {
|
||||||
|
const { targetAuthorAci, targetSentTimestamp, pinDurationSeconds } =
|
||||||
|
this.pinMessage;
|
||||||
|
|
||||||
|
const pinMessage = new Proto.DataMessage.PinMessage({
|
||||||
|
targetAuthorAciBinary: toAciObject(targetAuthorAci).getRawUuidBytes(),
|
||||||
|
targetSentTimestamp: Long.fromNumber(targetSentTimestamp),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (pinDurationSeconds != null) {
|
||||||
|
pinMessage.pinDurationSeconds = pinDurationSeconds;
|
||||||
|
} else {
|
||||||
|
pinMessage.pinDurationForever = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
proto.pinMessage = pinMessage;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.unpinMessage != null) {
|
||||||
|
const { targetAuthorAci, targetSentTimestamp } = this.unpinMessage;
|
||||||
|
|
||||||
|
const unpinMessage = new Proto.DataMessage.UnpinMessage({
|
||||||
|
targetAuthorAciBinary: toAciObject(targetAuthorAci).getRawUuidBytes(),
|
||||||
|
targetSentTimestamp: Long.fromNumber(targetSentTimestamp),
|
||||||
|
});
|
||||||
|
|
||||||
|
proto.unpinMessage = unpinMessage;
|
||||||
|
}
|
||||||
|
|
||||||
this.dataMessage = proto;
|
this.dataMessage = proto;
|
||||||
return proto;
|
return proto;
|
||||||
}
|
}
|
||||||
@@ -1107,7 +1109,7 @@ export class MessageSender {
|
|||||||
}
|
}
|
||||||
|
|
||||||
getAttrsFromGroupOptions(
|
getAttrsFromGroupOptions(
|
||||||
options: Readonly<GroupSendOptionsType>
|
options: Readonly<GroupMessageOptionsType>
|
||||||
): MessageOptionsType {
|
): MessageOptionsType {
|
||||||
const {
|
const {
|
||||||
attachments,
|
attachments,
|
||||||
@@ -1118,7 +1120,7 @@ export class MessageSender {
|
|||||||
flags,
|
flags,
|
||||||
groupCallUpdate,
|
groupCallUpdate,
|
||||||
groupV2,
|
groupV2,
|
||||||
messageText,
|
body,
|
||||||
preview,
|
preview,
|
||||||
profileKey,
|
profileKey,
|
||||||
quote,
|
quote,
|
||||||
@@ -1156,7 +1158,7 @@ export class MessageSender {
|
|||||||
return {
|
return {
|
||||||
attachments,
|
attachments,
|
||||||
bodyRanges,
|
bodyRanges,
|
||||||
body: messageText,
|
body,
|
||||||
contact,
|
contact,
|
||||||
deletedForEveryoneTimestamp,
|
deletedForEveryoneTimestamp,
|
||||||
expireTimer,
|
expireTimer,
|
||||||
@@ -1389,70 +1391,28 @@ export class MessageSender {
|
|||||||
// You might wonder why this takes a groupId. models/messages.resend() can send a group
|
// You might wonder why this takes a groupId. models/messages.resend() can send a group
|
||||||
// message to just one person.
|
// message to just one person.
|
||||||
async sendMessageToServiceId({
|
async sendMessageToServiceId({
|
||||||
attachments,
|
messageOptions,
|
||||||
bodyRanges,
|
|
||||||
contact,
|
|
||||||
contentHint,
|
contentHint,
|
||||||
deletedForEveryoneTimestamp,
|
|
||||||
expireTimer,
|
|
||||||
expireTimerVersion,
|
|
||||||
groupId,
|
groupId,
|
||||||
serviceId,
|
serviceId,
|
||||||
messageText,
|
|
||||||
options,
|
options,
|
||||||
preview,
|
|
||||||
profileKey,
|
|
||||||
quote,
|
|
||||||
reaction,
|
|
||||||
sticker,
|
|
||||||
storyContext,
|
|
||||||
story,
|
story,
|
||||||
targetTimestampForEdit,
|
|
||||||
timestamp,
|
|
||||||
urgent,
|
urgent,
|
||||||
includePniSignatureMessage,
|
includePniSignatureMessage,
|
||||||
}: Readonly<{
|
}: Readonly<{
|
||||||
attachments: ReadonlyArray<Proto.IAttachmentPointer> | undefined;
|
|
||||||
bodyRanges?: ReadonlyArray<RawBodyRange>;
|
|
||||||
contact?: ReadonlyArray<EmbeddedContactWithUploadedAvatar>;
|
|
||||||
contentHint: number;
|
|
||||||
deletedForEveryoneTimestamp: number | undefined;
|
|
||||||
expireTimer: DurationInSeconds | undefined;
|
|
||||||
expireTimerVersion: number | undefined;
|
|
||||||
groupId: string | undefined;
|
|
||||||
serviceId: ServiceIdString;
|
serviceId: ServiceIdString;
|
||||||
messageText: string | undefined;
|
groupId: string | undefined;
|
||||||
|
messageOptions: Omit<MessageOptionsType, 'recipients'>;
|
||||||
|
contentHint: number;
|
||||||
options?: SendOptionsType;
|
options?: SendOptionsType;
|
||||||
preview?: ReadonlyArray<OutgoingLinkPreviewType> | undefined;
|
|
||||||
profileKey?: Uint8Array;
|
|
||||||
quote?: OutgoingQuoteType;
|
|
||||||
reaction?: ReactionType;
|
|
||||||
sticker?: OutgoingStickerType;
|
|
||||||
storyContext?: StoryContextType;
|
|
||||||
story?: boolean;
|
story?: boolean;
|
||||||
targetTimestampForEdit?: number;
|
|
||||||
timestamp: number;
|
|
||||||
urgent: boolean;
|
urgent: boolean;
|
||||||
includePniSignatureMessage?: boolean;
|
includePniSignatureMessage?: boolean;
|
||||||
}>): Promise<CallbackResultType> {
|
}>): Promise<CallbackResultType> {
|
||||||
return this.sendMessage({
|
return this.sendMessage({
|
||||||
messageOptions: {
|
messageOptions: {
|
||||||
attachments,
|
...messageOptions,
|
||||||
bodyRanges,
|
|
||||||
body: messageText,
|
|
||||||
contact,
|
|
||||||
deletedForEveryoneTimestamp,
|
|
||||||
expireTimer,
|
|
||||||
expireTimerVersion,
|
|
||||||
preview,
|
|
||||||
profileKey,
|
|
||||||
quote,
|
|
||||||
reaction,
|
|
||||||
recipients: [serviceId],
|
recipients: [serviceId],
|
||||||
sticker,
|
|
||||||
storyContext,
|
|
||||||
targetTimestampForEdit,
|
|
||||||
timestamp,
|
|
||||||
},
|
},
|
||||||
contentHint,
|
contentHint,
|
||||||
groupId,
|
groupId,
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
// Copyright 2025 Signal Messenger, LLC
|
// Copyright 2025 Signal Messenger, LLC
|
||||||
// SPDX-License-Identifier: AGPL-3.0-only
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
import type { AciString } from '@signalapp/mock-server/src/types.js';
|
||||||
import type { MessageAttributesType } from '../model-types.js';
|
import type { MessageAttributesType } from '../model-types.js';
|
||||||
import type { ConversationType } from '../state/ducks/conversations.preload.js';
|
import type { ConversationType } from '../state/ducks/conversations.preload.js';
|
||||||
|
|
||||||
@@ -21,3 +22,14 @@ export type PinnedMessageRenderData = Readonly<{
|
|||||||
sender: ConversationType;
|
sender: ConversationType;
|
||||||
message: MessageAttributesType;
|
message: MessageAttributesType;
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
|
export type SendPinMessageType = Readonly<{
|
||||||
|
targetAuthorAci: AciString;
|
||||||
|
targetSentTimestamp: number;
|
||||||
|
pinDurationSeconds: number | null;
|
||||||
|
}>;
|
||||||
|
|
||||||
|
export type SendUnpinMessageType = Readonly<{
|
||||||
|
targetAuthorAci: AciString;
|
||||||
|
targetSentTimestamp: number;
|
||||||
|
}>;
|
||||||
|
|||||||
@@ -31,8 +31,10 @@ export const sendTypesEnum = z.enum([
|
|||||||
'expirationTimerUpdate', // non-urgent
|
'expirationTimerUpdate', // non-urgent
|
||||||
'groupChange', // non-urgent
|
'groupChange', // non-urgent
|
||||||
'reaction',
|
'reaction',
|
||||||
|
'pinMessage',
|
||||||
'pollTerminate',
|
'pollTerminate',
|
||||||
'pollVote', // non-urgent
|
'pollVote', // non-urgent
|
||||||
|
'unpinMessage',
|
||||||
'typing', // excluded from send log; non-urgent
|
'typing', // excluded from send log; non-urgent
|
||||||
|
|
||||||
// Responding to incoming messages, all non-urgent
|
// Responding to incoming messages, all non-urgent
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ import { isRecord } from './isRecord.std.js';
|
|||||||
|
|
||||||
import { isOlderThan } from './timestamp.std.js';
|
import { isOlderThan } from './timestamp.std.js';
|
||||||
import type {
|
import type {
|
||||||
GroupSendOptionsType,
|
GroupMessageOptionsType,
|
||||||
SendOptionsType,
|
SendOptionsType,
|
||||||
} from '../textsecure/SendMessage.preload.js';
|
} from '../textsecure/SendMessage.preload.js';
|
||||||
import { messageSender } from '../textsecure/SendMessage.preload.js';
|
import { messageSender } from '../textsecure/SendMessage.preload.js';
|
||||||
@@ -137,7 +137,7 @@ export async function sendToGroup({
|
|||||||
}: {
|
}: {
|
||||||
abortSignal?: AbortSignal;
|
abortSignal?: AbortSignal;
|
||||||
contentHint: number;
|
contentHint: number;
|
||||||
groupSendOptions: GroupSendOptionsType;
|
groupSendOptions: GroupMessageOptionsType;
|
||||||
isPartialSend?: boolean;
|
isPartialSend?: boolean;
|
||||||
messageId: string | undefined;
|
messageId: string | undefined;
|
||||||
sendOptions?: SendOptionsType;
|
sendOptions?: SendOptionsType;
|
||||||
@@ -931,7 +931,7 @@ export function _shouldFailSend(error: unknown, logId: string): boolean {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function getRecipients(
|
function getRecipients(
|
||||||
options: GroupSendOptionsType
|
options: GroupMessageOptionsType
|
||||||
): ReadonlyArray<ServiceIdString> {
|
): ReadonlyArray<ServiceIdString> {
|
||||||
if (options.groupV2) {
|
if (options.groupV2) {
|
||||||
return options.groupV2.members;
|
return options.groupV2.members;
|
||||||
|
|||||||
Reference in New Issue
Block a user