Skip to content

Commit

Permalink
Additional work to include story=true on send
Browse files Browse the repository at this point in the history
Co-authored-by: Scott Nonnenberg <scott@signal.org>
  • Loading branch information
automated-signal and scottnonnenberg-signal committed Oct 7, 2022
1 parent 6ff86c3 commit 7b39315
Show file tree
Hide file tree
Showing 22 changed files with 328 additions and 171 deletions.
2 changes: 1 addition & 1 deletion package.json
Expand Up @@ -196,7 +196,7 @@
"@babel/preset-typescript": "7.17.12",
"@electron/fuses": "1.5.0",
"@mixer/parallel-prettier": "2.0.1",
"@signalapp/mock-server": "2.10.0",
"@signalapp/mock-server": "2.11.0",
"@storybook/addon-a11y": "6.5.6",
"@storybook/addon-actions": "6.5.6",
"@storybook/addon-controls": "6.5.6",
Expand Down
3 changes: 2 additions & 1 deletion protos/SignalService.proto
Expand Up @@ -35,7 +35,8 @@ message Envelope {
optional bool ephemeral = 12; // indicates that the message should not be persisted if the recipient is offline
optional bool urgent = 14 [default=true]; // indicates that the content is considered timely by the sender; defaults to true so senders have to opt-out to say something isn't time critical
optional string updated_pni = 15;
// next: 16
optional bool story = 16; // indicates that the content is a story.
// next: 17
}

message Content {
Expand Down
53 changes: 27 additions & 26 deletions protos/SignalStorage.proto
Expand Up @@ -131,32 +131,33 @@ message AccountRecord {
}
}

optional bytes profileKey = 1;
optional string givenName = 2;
optional string familyName = 3;
optional string avatarUrl = 4;
optional bool noteToSelfArchived = 5;
optional bool readReceipts = 6;
optional bool sealedSenderIndicators = 7;
optional bool typingIndicators = 8;
optional bool proxiedLinkPreviews = 9;
optional bool noteToSelfMarkedUnread = 10;
optional bool linkPreviews = 11;
optional PhoneNumberSharingMode phoneNumberSharingMode = 12;
optional bool notDiscoverableByPhoneNumber = 13;
repeated PinnedConversation pinnedConversations = 14;
optional bool preferContactAvatars = 15;
optional uint32 universalExpireTimer = 17;
optional bool primarySendsSms = 18;
optional string e164 = 19;
repeated string preferredReactionEmoji = 20;
optional bytes subscriberId = 21;
optional string subscriberCurrencyCode = 22;
optional bool displayBadgesOnProfile = 23;
optional bool keepMutedChatsArchived = 25;
optional bool hasSetMyStoriesPrivacy = 26;
reserved /* hasViewedOnboardingStory */ 27;
optional bool storiesDisabled = 28;
optional bytes profileKey = 1;
optional string givenName = 2;
optional string familyName = 3;
optional string avatarUrl = 4;
optional bool noteToSelfArchived = 5;
optional bool readReceipts = 6;
optional bool sealedSenderIndicators = 7;
optional bool typingIndicators = 8;
optional bool proxiedLinkPreviews = 9;
optional bool noteToSelfMarkedUnread = 10;
optional bool linkPreviews = 11;
optional PhoneNumberSharingMode phoneNumberSharingMode = 12;
optional bool notDiscoverableByPhoneNumber = 13;
repeated PinnedConversation pinnedConversations = 14;
optional bool preferContactAvatars = 15;
optional uint32 universalExpireTimer = 17;
optional bool primarySendsSms = 18;
optional string e164 = 19;
repeated string preferredReactionEmoji = 20;
optional bytes subscriberId = 21;
optional string subscriberCurrencyCode = 22;
optional bool displayBadgesOnProfile = 23;
optional bool keepMutedChatsArchived = 25;
optional bool hasSetMyStoriesPrivacy = 26;
reserved /* hasViewedOnboardingStory */ 27;
reserved 28; // deprecatedStoriesDisabled
optional bool storiesDisabled = 29;
}

message StoryDistributionListRecord {
Expand Down
3 changes: 3 additions & 0 deletions ts/jobs/helpers/sendNormalMessage.ts
Expand Up @@ -183,6 +183,7 @@ export async function sendNormalMessage(
quote,
recipients: allRecipientIdentifiers,
sticker,
// No storyContext; you can't reply to your own stories
timestamp: messageTimestamp,
});
messageSendPromise = message.sendSyncMessageOnly(dataMessage, saveErrors);
Expand Down Expand Up @@ -234,6 +235,7 @@ export async function sendNormalMessage(
sendOptions,
sendTarget: conversation.toSenderKeyTarget(),
sendType: 'message',
story: Boolean(storyContext),
urgent: true,
})
);
Expand Down Expand Up @@ -282,6 +284,7 @@ export async function sendNormalMessage(
sticker,
storyContext,
timestamp: messageTimestamp,
// Note: 1:1 story replies should not set story=true - they aren't group sends
urgent: true,
includePniSignatureMessage: true,
});
Expand Down
3 changes: 2 additions & 1 deletion ts/jobs/helpers/sendStory.ts
Expand Up @@ -264,7 +264,8 @@ export async function sendStory(
const recipientsSet = new Set(pendingSendRecipientIds);

const sendOptions = await getSendOptionsForRecipients(
pendingSendRecipientIds
pendingSendRecipientIds,
{ story: true }
);

log.info(
Expand Down
8 changes: 4 additions & 4 deletions ts/messageModifiers/Reactions.ts
Expand Up @@ -179,11 +179,13 @@ export class Reactions extends Collection<ReactionModel> {
storyReactionEmoji: reaction.get('emoji'),
});

const [generatedMessageId] = await Promise.all([
// Note: generatedMessage comes with an id, so we have to force this save
await Promise.all([
window.Signal.Data.saveMessage(generatedMessage.attributes, {
ourUuid: window.textsecure.storage.user
.getCheckedUuid()
.toString(),
forceSave: true,
}),
generatedMessage.hydrateStoryContext(message),
]);
Expand All @@ -197,10 +199,8 @@ export class Reactions extends Collection<ReactionModel> {
timestamp: reaction.get('timestamp'),
});

generatedMessage.set({ id: generatedMessageId });

const messageToAdd = window.MessageController.register(
generatedMessageId,
generatedMessage.id,
generatedMessage
);
targetConversation.addSingleMessage(messageToAdd);
Expand Down
19 changes: 2 additions & 17 deletions ts/models/messages.ts
Expand Up @@ -159,7 +159,6 @@ import { getMessageIdForLogging } from '../util/idForLogging';
import { hasAttachmentDownloads } from '../util/hasAttachmentDownloads';
import { queueAttachmentDownloads } from '../util/queueAttachmentDownloads';
import { findStoryMessage } from '../util/findStoryMessage';
import { isConversationAccepted } from '../util/isConversationAccepted';
import { getStoryDataFromMessageAttributes } from '../services/storyLoader';
import type { ConversationQueueJobData } from '../jobs/conversationJobQueue';
import { getMessageById } from '../messages/getMessageById';
Expand Down Expand Up @@ -2097,20 +2096,6 @@ export class MessageModel extends window.Backbone.Model<MessageAttributesType> {
await conversation.queueJob('handleDataMessage', async () => {
log.info(`${idLog}: starting processing in queue`);

if (
isStory(message.attributes) &&
!isConversationAccepted(conversation.attributes, {
ignoreEmptyConvo: true,
})
) {
log.info(
`${idLog}: dropping story from !accepted`,
this.getSenderIdentifier()
);
confirm();
return;
}

// First, check for duplicates. If we find one, stop processing here.
const inMemoryMessage = window.MessageController.findBySender(
this.getSenderIdentifier()
Expand Down Expand Up @@ -2387,8 +2372,8 @@ export class MessageModel extends window.Backbone.Model<MessageAttributesType> {

const messageId = message.get('id') || UUID.generate().toString();

// Send delivery receipts, but only for incoming sealed sender messages
// and not for messages from unaccepted conversations
// Send delivery receipts, but only for non-story sealed sender messages
// and not for messages from unaccepted conversations
if (
type === 'incoming' &&
this.get('unidentifiedDeliveryReceived') &&
Expand Down
1 change: 1 addition & 0 deletions ts/sql/Interface.ts
Expand Up @@ -265,6 +265,7 @@ export type UnprocessedType = {
serverTimestamp?: number;
decrypted?: string;
urgent?: boolean;
story?: boolean;
};

export type UnprocessedUpdateType = {
Expand Down
10 changes: 8 additions & 2 deletions ts/sql/Server.ts
Expand Up @@ -3178,6 +3178,7 @@ function saveUnprocessedSync(data: UnprocessedType): string {
serverTimestamp,
decrypted,
urgent,
story,
} = data;
if (!id) {
throw new Error('saveUnprocessedSync: id was falsey');
Expand All @@ -3204,7 +3205,8 @@ function saveUnprocessedSync(data: UnprocessedType): string {
serverGuid,
serverTimestamp,
decrypted,
urgent
urgent,
story
) values (
$id,
$timestamp,
Expand All @@ -3218,7 +3220,8 @@ function saveUnprocessedSync(data: UnprocessedType): string {
$serverGuid,
$serverTimestamp,
$decrypted,
$urgent
$urgent,
$story
);
`
).run({
Expand All @@ -3235,6 +3238,7 @@ function saveUnprocessedSync(data: UnprocessedType): string {
serverTimestamp: serverTimestamp || null,
decrypted: decrypted || null,
urgent: urgent || !isBoolean(urgent) ? 1 : 0,
story: story ? 1 : 0,
});

return id;
Expand Down Expand Up @@ -3309,6 +3313,7 @@ async function getUnprocessedById(
return {
...row,
urgent: isNumber(row.urgent) ? Boolean(row.urgent) : true,
story: Boolean(row.story),
};
}

Expand Down Expand Up @@ -3370,6 +3375,7 @@ async function getAllUnprocessedAndIncrementAttempts(): Promise<
.map(row => ({
...row,
urgent: isNumber(row.urgent) ? Boolean(row.urgent) : true,
story: Boolean(row.story),
}));
})();
}
Expand Down
28 changes: 28 additions & 0 deletions ts/sql/migrations/67-add-story-to-unprocessed.ts
@@ -0,0 +1,28 @@
// Copyright 2021-2022 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only

import type { Database } from 'better-sqlite3';

import type { LoggerType } from '../../types/Logging';

export default function updateToSchemaVersion67(
currentVersion: number,
db: Database,
logger: LoggerType
): void {
if (currentVersion >= 67) {
return;
}

db.transaction(() => {
db.exec(
`
ALTER TABLE unprocessed ADD COLUMN story INTEGER;
`
);

db.pragma('user_version = 67');
})();

logger.info('updateToSchemaVersion67: success!');
}
2 changes: 2 additions & 0 deletions ts/sql/migrations/index.ts
Expand Up @@ -42,6 +42,7 @@ import updateToSchemaVersion63 from './63-add-urgent-to-unprocessed';
import updateToSchemaVersion64 from './64-uuid-column-for-pre-keys';
import updateToSchemaVersion65 from './65-add-storage-id-to-stickers';
import updateToSchemaVersion66 from './66-add-pni-signature-to-sent-protos';
import updateToSchemaVersion67 from './67-add-story-to-unprocessed';

function updateToSchemaVersion1(
currentVersion: number,
Expand Down Expand Up @@ -1947,6 +1948,7 @@ export const SCHEMA_VERSIONS = [
updateToSchemaVersion64,
updateToSchemaVersion65,
updateToSchemaVersion66,
updateToSchemaVersion67,
];

export function updateSchema(db: Database, logger: LoggerType): void {
Expand Down
27 changes: 2 additions & 25 deletions ts/test-mock/rate-limit/story_test.ts
Expand Up @@ -15,7 +15,7 @@ export const debug = createDebug('mock:test:rate-limit');

const IdentifierType = Proto.ManifestRecord.Identifier.Type;

describe('rate-limit/story', function needsName() {
describe('story/no-sender-key', function needsName() {
this.timeout(durations.MINUTE);

let bootstrap: Bootstrap;
Expand Down Expand Up @@ -65,7 +65,7 @@ describe('rate-limit/story', function needsName() {
await bootstrap.teardown();
});

it('should request challenge and accept solution', async () => {
it('should successfully send story', async () => {
const {
server,
contactsWithoutProfileKey: contacts,
Expand Down Expand Up @@ -115,29 +115,6 @@ describe('rate-limit/story', function needsName() {
await window.locator('button.SendStoryModal__send').click();
}

debug('Waiting for challenge');
const request = await app.waitForChallenge();

debug('Checking for presence of captcha modal');
await window
.locator('.module-Modal__title >> "Verify to continue messaging"')
.waitFor();

debug('Removing rate-limiting');
for (const contact of contacts) {
const failedMessages = server.stopRateLimiting({
source: desktop.uuid,
target: contact.device.uuid,
});
assert.isAtMost(failedMessages ?? 0, 1);
}

debug('Solving challenge');
app.solveChallenge({
seq: request.seq,
data: { captcha: 'anything' },
});

debug('Verifying that all contacts received story');
await Promise.all(
contacts.map(async contact => {
Expand Down

0 comments on commit 7b39315

Please sign in to comment.