Add ability to send poll create messages

This commit is contained in:
yash-signal
2025-10-21 16:40:10 -05:00
committed by GitHub
parent 9d34375bb7
commit 1ddb81e053
7 changed files with 113 additions and 0 deletions

View File

@@ -41,6 +41,9 @@ const KnownConfigKeys = [
'desktop.pollReceive.alpha', 'desktop.pollReceive.alpha',
'desktop.pollReceive.beta', 'desktop.pollReceive.beta',
'desktop.pollReceive.prod', 'desktop.pollReceive.prod',
'desktop.pollSend.alpha',
'desktop.pollSend.beta',
'desktop.pollSend.prod',
'global.attachments.maxBytes', 'global.attachments.maxBytes',
'global.attachments.maxReceiveBytes', 'global.attachments.maxReceiveBytes',
'global.backups.mediaTierFallbackCdnNumber', 'global.backups.mediaTierFallbackCdnNumber',

View File

@@ -46,6 +46,7 @@ import { copyCdnFields } from '../../util/attachments.preload.js';
import type { RawBodyRange } from '../../types/BodyRange.std.js'; import type { RawBodyRange } from '../../types/BodyRange.std.js';
import type { EmbeddedContactWithUploadedAvatar } from '../../types/EmbeddedContact.std.js'; import type { EmbeddedContactWithUploadedAvatar } from '../../types/EmbeddedContact.std.js';
import type { StoryContextType } from '../../types/Util.std.js'; import type { StoryContextType } from '../../types/Util.std.js';
import type { PollCreateType } from '../../types/Polls.dom.js';
import type { LoggerType } from '../../types/Logging.std.js'; import type { LoggerType } from '../../types/Logging.std.js';
import { GROUP } from '../../types/Message2.preload.js'; import { GROUP } from '../../types/Message2.preload.js';
import type { import type {
@@ -231,6 +232,7 @@ export async function sendNormalMessage(
sticker, sticker,
storyMessage, storyMessage,
storyContext, storyContext,
poll,
} = await getMessageSendData({ } = await getMessageSendData({
log, log,
message, message,
@@ -317,6 +319,7 @@ export async function sendNormalMessage(
: undefined, : undefined,
timestamp: targetTimestamp, timestamp: targetTimestamp,
reaction, reaction,
pollCreate: poll,
}); });
messageSendPromise = sendSyncMessageOnly(message, { messageSendPromise = sendSyncMessageOnly(message, {
dataMessage, dataMessage,
@@ -362,6 +365,7 @@ export async function sendNormalMessage(
sticker, sticker,
storyContext, storyContext,
reaction, reaction,
pollCreate: poll,
targetTimestampForEdit: editedMessageTimestamp targetTimestampForEdit: editedMessageTimestamp
? targetOfThisEditTimestamp ? targetOfThisEditTimestamp
: undefined, : undefined,
@@ -619,6 +623,7 @@ async function getMessageSendData({
reaction: ReactionType | undefined; reaction: ReactionType | undefined;
storyMessage?: MessageModel; storyMessage?: MessageModel;
storyContext?: StoryContextType; storyContext?: StoryContextType;
poll?: PollCreateType;
}> { }> {
const storyId = message.get('storyId'); const storyId = message.get('storyId');
@@ -761,6 +766,7 @@ async function getMessageSendData({
timestamp: storyMessage.get('sent_at'), timestamp: storyMessage.get('sent_at'),
} }
: undefined, : undefined,
poll: message.get('poll'),
}; };
} }

View File

@@ -140,6 +140,7 @@ import type {
LinkPreviewType, LinkPreviewType,
LinkPreviewWithHydratedData, LinkPreviewWithHydratedData,
} from '../types/message/LinkPreviews.std.js'; } from '../types/message/LinkPreviews.std.js';
import type { PollCreateType } from '../types/Polls.dom.js';
import { import {
MINUTE, MINUTE,
SECOND, SECOND,
@@ -4022,6 +4023,7 @@ export class ConversationModel {
preview, preview,
quote, quote,
sticker, sticker,
poll,
}: { }: {
attachments: Array<AttachmentType>; attachments: Array<AttachmentType>;
body: string | undefined; body: string | undefined;
@@ -4030,6 +4032,7 @@ export class ConversationModel {
preview?: Array<LinkPreviewWithHydratedData>; preview?: Array<LinkPreviewWithHydratedData>;
quote?: QuotedMessageType; quote?: QuotedMessageType;
sticker?: StickerWithHydratedData; sticker?: StickerWithHydratedData;
poll?: PollCreateType;
}, },
{ {
dontClearDraft = false, dontClearDraft = false,
@@ -4154,6 +4157,7 @@ export class ConversationModel {
}) })
), ),
storyId, storyId,
poll,
}); });
const model = window.MessageCache.register(new MessageModel(attributes)); const model = window.MessageCache.register(new MessageModel(attributes));

View File

@@ -101,6 +101,7 @@ import {
import { MAX_MESSAGE_COUNT } from '../util/deleteForMe.types.std.js'; import { MAX_MESSAGE_COUNT } from '../util/deleteForMe.types.std.js';
import { isProtoBinaryEncodingEnabled } from '../util/isProtoBinaryEncodingEnabled.std.js'; import { isProtoBinaryEncodingEnabled } from '../util/isProtoBinaryEncodingEnabled.std.js';
import type { GroupSendToken } from '../types/GroupSendEndorsements.std.js'; import type { GroupSendToken } from '../types/GroupSendEndorsements.std.js';
import type { 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';
@@ -214,6 +215,7 @@ export type MessageOptionsType = {
recipients: ReadonlyArray<ServiceIdString>; recipients: ReadonlyArray<ServiceIdString>;
sticker?: OutgoingStickerType; sticker?: OutgoingStickerType;
reaction?: ReactionType; reaction?: ReactionType;
pollCreate?: PollCreateType;
deletedForEveryoneTimestamp?: number; deletedForEveryoneTimestamp?: number;
targetTimestampForEdit?: number; targetTimestampForEdit?: number;
timestamp: number; timestamp: number;
@@ -238,6 +240,7 @@ export type GroupSendOptionsType = {
sticker?: OutgoingStickerType; sticker?: OutgoingStickerType;
storyContext?: StoryContextType; storyContext?: StoryContextType;
timestamp: number; timestamp: number;
pollCreate?: PollCreateType;
}; };
class Message { class Message {
@@ -276,6 +279,8 @@ class Message {
reaction?: ReactionType; reaction?: ReactionType;
pollCreate?: PollCreateType;
timestamp: number; timestamp: number;
dataMessage?: Proto.DataMessage; dataMessage?: Proto.DataMessage;
@@ -303,6 +308,7 @@ class Message {
this.recipients = options.recipients; this.recipients = options.recipients;
this.sticker = options.sticker; this.sticker = options.sticker;
this.reaction = options.reaction; this.reaction = options.reaction;
this.pollCreate = options.pollCreate;
this.timestamp = options.timestamp; this.timestamp = options.timestamp;
this.deletedForEveryoneTimestamp = options.deletedForEveryoneTimestamp; this.deletedForEveryoneTimestamp = options.deletedForEveryoneTimestamp;
this.groupCallUpdate = options.groupCallUpdate; this.groupCallUpdate = options.groupCallUpdate;
@@ -627,6 +633,14 @@ class Message {
proto.storyContext = storyContext; proto.storyContext = storyContext;
} }
if (this.pollCreate) {
const create = new Proto.DataMessage.PollCreate();
create.question = this.pollCreate.question;
create.allowMultiple = Boolean(this.pollCreate.allowMultiple);
create.options = this.pollCreate.options.slice();
proto.pollCreate = create;
}
this.dataMessage = proto; this.dataMessage = proto;
return proto; return proto;
} }
@@ -938,6 +952,7 @@ export class MessageSender {
storyContext, storyContext,
targetTimestampForEdit, targetTimestampForEdit,
timestamp, timestamp,
pollCreate,
} = options; } = options;
if (!groupV2) { if (!groupV2) {
@@ -981,6 +996,7 @@ export class MessageSender {
storyContext, storyContext,
targetTimestampForEdit, targetTimestampForEdit,
timestamp, timestamp,
pollCreate,
}; };
} }

View File

@@ -80,12 +80,18 @@ export type PollMessageAttribute = {
terminatedAt?: number; terminatedAt?: number;
}; };
export type PollCreateType = Pick<
PollMessageAttribute,
'question' | 'options' | 'allowMultiple'
>;
export function isPollReceiveEnabled(): boolean { export function isPollReceiveEnabled(): boolean {
const env = getEnvironment(); const env = getEnvironment();
if ( if (
env === Environment.Development || env === Environment.Development ||
env === Environment.Test || env === Environment.Test ||
env === Environment.Staging ||
isMockEnvironment() isMockEnvironment()
) { ) {
return true; return true;
@@ -107,3 +113,32 @@ export function isPollReceiveEnabled(): boolean {
return false; return false;
} }
export function isPollSendEnabled(): boolean {
const env = getEnvironment();
if (
env === Environment.Development ||
env === Environment.Test ||
env === Environment.Staging ||
isMockEnvironment()
) {
return true;
}
const version = window.getVersion?.();
if (version != null) {
if (isProduction(version)) {
return RemoteConfig.isEnabled('desktop.pollSend.prod');
}
if (isBeta(version)) {
return RemoteConfig.isEnabled('desktop.pollSend.beta');
}
if (isAlpha(version)) {
return RemoteConfig.isEnabled('desktop.pollSend.alpha');
}
}
return false;
}

View File

@@ -0,0 +1,32 @@
// Copyright 2025 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import type { ConversationModel } from '../models/conversations.preload.js';
import { isGroupV2 } from './whatTypeOfConversation.dom.js';
import { isPollSendEnabled, type PollCreateType } from '../types/Polls.dom.js';
export async function enqueuePollCreateForSend(
conversation: ConversationModel,
poll: PollCreateType
): Promise<void> {
if (!isPollSendEnabled()) {
throw new Error('enqueuePollCreateForSend: poll sending is not enabled');
}
if (!isGroupV2(conversation.attributes)) {
throw new Error(
'enqueuePollCreateForSend: polls are group-only. Conversation is not GroupV2.'
);
}
await conversation.enqueueMessageForSend(
{
attachments: [],
body: undefined,
poll,
},
{
timestamp: Date.now(),
}
);
}

View File

@@ -31,6 +31,11 @@ import { Environment, getEnvironment } from '../../environment.std.js';
import { isProduction } from '../../util/version.std.js'; import { isProduction } from '../../util/version.std.js';
import { benchmarkConversationOpen } from '../../CI/benchmarkConversationOpen.preload.js'; import { benchmarkConversationOpen } from '../../CI/benchmarkConversationOpen.preload.js';
import { itemStorage } from '../../textsecure/Storage.preload.js'; import { itemStorage } from '../../textsecure/Storage.preload.js';
import { enqueuePollCreateForSend } from '../../util/enqueuePollCreateForSend.dom.js';
import {
isPollSendEnabled,
type PollCreateType,
} from '../../types/Polls.dom.js';
const { has } = lodash; const { has } = lodash;
@@ -117,6 +122,18 @@ if (
}, },
setRtcStatsInterval: (intervalMillis: number) => setRtcStatsInterval: (intervalMillis: number) =>
calling.setAllRtcStatsInterval(intervalMillis), calling.setAllRtcStatsInterval(intervalMillis),
sendPollInSelectedConversation: async (poll: PollCreateType) => {
if (!isPollSendEnabled()) {
throw new Error('Poll sending is not enabled');
}
const conversationId =
window.reduxStore.getState().conversations.selectedConversationId;
const conversation = window.ConversationController.get(conversationId);
if (!conversation) {
throw new Error('No conversation selected');
}
await enqueuePollCreateForSend(conversation, poll);
},
...(window.SignalContext.config.ciMode === 'benchmark' ...(window.SignalContext.config.ciMode === 'benchmark'
? { ? {
benchmarkConversationOpen, benchmarkConversationOpen,