Support reporting token on envelope
This commit is contained in:
parent
dc8d8e529d
commit
486cbe0471
12 changed files with 98 additions and 26 deletions
|
@ -36,7 +36,8 @@ message Envelope {
|
|||
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;
|
||||
optional bool story = 16; // indicates that the content is a story.
|
||||
// next: 17
|
||||
optional bytes reporting_token = 17;
|
||||
// next: 18
|
||||
}
|
||||
|
||||
message Content {
|
||||
|
|
|
@ -2853,7 +2853,7 @@ export async function startApp(): Promise<void> {
|
|||
}: EnvelopeEvent): Promise<void> {
|
||||
const ourUuid = window.textsecure.storage.user.getUuid()?.toString();
|
||||
if (envelope.sourceUuid && envelope.sourceUuid !== ourUuid) {
|
||||
const { mergePromises } =
|
||||
const { mergePromises, conversation } =
|
||||
window.ConversationController.maybeMergeContacts({
|
||||
e164: envelope.source,
|
||||
aci: envelope.sourceUuid,
|
||||
|
@ -2863,6 +2863,10 @@ export async function startApp(): Promise<void> {
|
|||
if (mergePromises.length > 0) {
|
||||
await Promise.all(mergePromises);
|
||||
}
|
||||
|
||||
if (envelope.reportingToken) {
|
||||
await conversation.updateReportingToken(envelope.reportingToken);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -2,8 +2,9 @@
|
|||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import { assertDev } from '../../util/assert';
|
||||
import { isDirectConversation } from '../../util/whatTypeOfConversation';
|
||||
import * as log from '../../logging/log';
|
||||
import type { ConversationType } from '../../state/ducks/conversations';
|
||||
import type { ConversationAttributesType } from '../../model-types.d';
|
||||
import type { reportSpamJobQueue } from '../reportSpamJobQueue';
|
||||
|
||||
export async function addReportSpamJob({
|
||||
|
@ -11,14 +12,16 @@ export async function addReportSpamJob({
|
|||
getMessageServerGuidsForSpam,
|
||||
jobQueue,
|
||||
}: Readonly<{
|
||||
conversation: Readonly<ConversationType>;
|
||||
conversation: Readonly<
|
||||
Pick<ConversationAttributesType, 'id' | 'type' | 'uuid' | 'reportingToken'>
|
||||
>;
|
||||
getMessageServerGuidsForSpam: (
|
||||
conversationId: string
|
||||
) => Promise<Array<string>>;
|
||||
jobQueue: Pick<typeof reportSpamJobQueue, 'add'>;
|
||||
}>): Promise<void> {
|
||||
assertDev(
|
||||
conversation.type === 'direct',
|
||||
isDirectConversation(conversation),
|
||||
'addReportSpamJob: cannot report spam for non-direct conversations'
|
||||
);
|
||||
|
||||
|
@ -41,5 +44,5 @@ export async function addReportSpamJob({
|
|||
return;
|
||||
}
|
||||
|
||||
await jobQueue.add({ uuid, serverGuids });
|
||||
await jobQueue.add({ uuid, serverGuids, token: conversation.reportingToken });
|
||||
}
|
||||
|
|
|
@ -28,6 +28,7 @@ const isRetriable4xxStatus = (code: number): boolean =>
|
|||
|
||||
const reportSpamJobDataSchema = z.object({
|
||||
uuid: z.string().min(1),
|
||||
token: z.string().optional(),
|
||||
serverGuids: z.string().array().min(1).max(1000),
|
||||
});
|
||||
|
||||
|
@ -48,7 +49,7 @@ export class ReportSpamJobQueue extends JobQueue<ReportSpamJobData> {
|
|||
{ data }: Readonly<{ data: ReportSpamJobData }>,
|
||||
{ log }: Readonly<{ log: LoggerType }>
|
||||
): Promise<void> {
|
||||
const { uuid, serverGuids } = data;
|
||||
const { uuid: senderUuid, token, serverGuids } = data;
|
||||
|
||||
await new Promise<void>(resolve => {
|
||||
window.storage.onready(resolve);
|
||||
|
@ -66,7 +67,9 @@ export class ReportSpamJobQueue extends JobQueue<ReportSpamJobData> {
|
|||
|
||||
try {
|
||||
await Promise.all(
|
||||
map(serverGuids, serverGuid => server.reportMessage(uuid, serverGuid))
|
||||
map(serverGuids, serverGuid =>
|
||||
server.reportMessage({ senderUuid, serverGuid, token })
|
||||
)
|
||||
);
|
||||
} catch (err: unknown) {
|
||||
if (!(err instanceof HTTPError)) {
|
||||
|
|
1
ts/model-types.d.ts
vendored
1
ts/model-types.d.ts
vendored
|
@ -353,6 +353,7 @@ export type ConversationAttributesType = {
|
|||
username?: string;
|
||||
shareMyPhoneNumber?: boolean;
|
||||
previousIdentityKey?: string;
|
||||
reportingToken?: string;
|
||||
|
||||
// Group-only
|
||||
groupId?: string;
|
||||
|
|
|
@ -2090,6 +2090,18 @@ export class ConversationModel extends window.Backbone
|
|||
}
|
||||
}
|
||||
|
||||
async updateReportingToken(token?: Uint8Array): Promise<void> {
|
||||
const oldValue = this.get('reportingToken');
|
||||
const newValue = token ? Bytes.toBase64(token) : undefined;
|
||||
|
||||
if (oldValue === newValue) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.set('reportingToken', newValue);
|
||||
await window.Signal.Data.updateConversation(this.attributes);
|
||||
}
|
||||
|
||||
incrementMessageCount(): void {
|
||||
this.set({
|
||||
messageCount: (this.get('messageCount') || 0) + 1,
|
||||
|
|
|
@ -264,6 +264,7 @@ export type UnprocessedType = {
|
|||
decrypted?: string;
|
||||
urgent?: boolean;
|
||||
story?: boolean;
|
||||
reportingToken?: string;
|
||||
};
|
||||
|
||||
export type UnprocessedUpdateType = {
|
||||
|
|
|
@ -2870,7 +2870,7 @@ function blockAndReportSpam(
|
|||
await Promise.all([
|
||||
conversation.syncMessageRequestResponse(messageRequestEnum.BLOCK),
|
||||
addReportSpamJob({
|
||||
conversation: conversation.format(),
|
||||
conversation: conversation.attributes,
|
||||
getMessageServerGuidsForSpam:
|
||||
window.Signal.Data.getMessageServerGuidsForSpam,
|
||||
jobQueue: reportSpamJobQueue,
|
||||
|
|
|
@ -2,8 +2,8 @@
|
|||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import * as sinon from 'sinon';
|
||||
import { getDefaultConversation } from '../../../test-both/helpers/getDefaultConversation';
|
||||
import { Job } from '../../../jobs/Job';
|
||||
import { UUID } from '../../../types/UUID';
|
||||
|
||||
import { addReportSpamJob } from '../../../jobs/helpers/addReportSpamJob';
|
||||
|
||||
|
@ -11,6 +11,12 @@ describe('addReportSpamJob', () => {
|
|||
let getMessageServerGuidsForSpam: sinon.SinonStub;
|
||||
let jobQueue: { add: sinon.SinonStub };
|
||||
|
||||
const conversation = {
|
||||
id: 'convo',
|
||||
type: 'private' as const,
|
||||
uuid: UUID.generate().toString(),
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
getMessageServerGuidsForSpam = sinon.stub().resolves(['abc', 'xyz']);
|
||||
jobQueue = {
|
||||
|
@ -31,7 +37,10 @@ describe('addReportSpamJob', () => {
|
|||
|
||||
it('does nothing if the conversation lacks a UUID', async () => {
|
||||
await addReportSpamJob({
|
||||
conversation: getDefaultConversation({ uuid: undefined }),
|
||||
conversation: {
|
||||
...conversation,
|
||||
uuid: undefined,
|
||||
},
|
||||
getMessageServerGuidsForSpam,
|
||||
jobQueue,
|
||||
});
|
||||
|
@ -44,7 +53,7 @@ describe('addReportSpamJob', () => {
|
|||
getMessageServerGuidsForSpam.resolves([]);
|
||||
|
||||
await addReportSpamJob({
|
||||
conversation: getDefaultConversation(),
|
||||
conversation,
|
||||
getMessageServerGuidsForSpam,
|
||||
jobQueue,
|
||||
});
|
||||
|
@ -52,9 +61,7 @@ describe('addReportSpamJob', () => {
|
|||
sinon.assert.notCalled(jobQueue.add);
|
||||
});
|
||||
|
||||
it('enqueues a job', async () => {
|
||||
const conversation = getDefaultConversation();
|
||||
|
||||
it('enqueues a job without a token', async () => {
|
||||
await addReportSpamJob({
|
||||
conversation,
|
||||
getMessageServerGuidsForSpam,
|
||||
|
@ -68,6 +75,28 @@ describe('addReportSpamJob', () => {
|
|||
sinon.assert.calledWith(jobQueue.add, {
|
||||
uuid: conversation.uuid,
|
||||
serverGuids: ['abc', 'xyz'],
|
||||
token: undefined,
|
||||
});
|
||||
});
|
||||
|
||||
it('enqueues a job with a token', async () => {
|
||||
await addReportSpamJob({
|
||||
conversation: {
|
||||
...conversation,
|
||||
reportingToken: 'uvw',
|
||||
},
|
||||
getMessageServerGuidsForSpam,
|
||||
jobQueue,
|
||||
});
|
||||
|
||||
sinon.assert.calledOnce(getMessageServerGuidsForSpam);
|
||||
sinon.assert.calledWith(getMessageServerGuidsForSpam, conversation.id);
|
||||
|
||||
sinon.assert.calledOnce(jobQueue.add);
|
||||
sinon.assert.calledWith(jobQueue.add, {
|
||||
uuid: conversation.uuid,
|
||||
serverGuids: ['abc', 'xyz'],
|
||||
token: 'uvw',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -412,6 +412,9 @@ export default class MessageReceiver
|
|||
serverTimestamp,
|
||||
urgent: isBoolean(decoded.urgent) ? decoded.urgent : true,
|
||||
story: decoded.story,
|
||||
reportingToken: decoded.reportingToken?.length
|
||||
? decoded.reportingToken
|
||||
: undefined,
|
||||
};
|
||||
|
||||
// After this point, decoding errors are not the server's
|
||||
|
@ -848,6 +851,9 @@ export default class MessageReceiver
|
|||
item.serverTimestamp || decoded.serverTimestamp?.toNumber(),
|
||||
urgent: isBoolean(item.urgent) ? item.urgent : true,
|
||||
story: Boolean(item.story),
|
||||
reportingToken: item.reportingToken
|
||||
? Bytes.fromBase64(item.reportingToken)
|
||||
: undefined,
|
||||
};
|
||||
|
||||
const { decrypted } = item;
|
||||
|
@ -1123,6 +1129,9 @@ export default class MessageReceiver
|
|||
receivedAtCounter: envelope.receivedAtCounter,
|
||||
urgent: envelope.urgent,
|
||||
story: envelope.story,
|
||||
reportingToken: envelope.reportingToken
|
||||
? Bytes.toBase64(envelope.reportingToken)
|
||||
: undefined,
|
||||
};
|
||||
this.decryptAndCacheBatcher.add({
|
||||
request,
|
||||
|
@ -1262,14 +1271,12 @@ export default class MessageReceiver
|
|||
return;
|
||||
}
|
||||
|
||||
if (envelope.content) {
|
||||
await this.innerHandleContentMessage(envelope, plaintext);
|
||||
|
||||
return;
|
||||
if (!envelope.content) {
|
||||
this.removeFromCache(envelope);
|
||||
throw new Error('Received message with no content');
|
||||
}
|
||||
|
||||
this.removeFromCache(envelope);
|
||||
throw new Error('Received message with no content');
|
||||
await this.innerHandleContentMessage(envelope, plaintext);
|
||||
}
|
||||
|
||||
private async unsealEnvelope(
|
||||
|
|
1
ts/textsecure/Types.d.ts
vendored
1
ts/textsecure/Types.d.ts
vendored
|
@ -97,6 +97,7 @@ export type ProcessedEnvelope = Readonly<{
|
|||
groupId?: string;
|
||||
urgent?: boolean;
|
||||
story?: boolean;
|
||||
reportingToken?: Uint8Array;
|
||||
}>;
|
||||
|
||||
export type ProcessedAttachment = {
|
||||
|
|
|
@ -812,6 +812,12 @@ export type ConfirmCodeOptionsType = Readonly<{
|
|||
accessKey?: Uint8Array;
|
||||
}>;
|
||||
|
||||
export type ReportMessageOptionsType = Readonly<{
|
||||
senderUuid: string;
|
||||
serverGuid: string;
|
||||
token?: string;
|
||||
}>;
|
||||
|
||||
export type WebAPIType = {
|
||||
startRegistration(): unknown;
|
||||
finishRegistration(baton: unknown): void;
|
||||
|
@ -931,7 +937,7 @@ export type WebAPIType = {
|
|||
registerCapabilities: (capabilities: CapabilitiesUploadType) => Promise<void>;
|
||||
registerKeys: (genKeys: KeysType, uuidKind: UUIDKind) => Promise<void>;
|
||||
registerSupportForUnauthenticatedDelivery: () => Promise<void>;
|
||||
reportMessage: (senderUuid: string, serverGuid: string) => Promise<void>;
|
||||
reportMessage: (options: ReportMessageOptionsType) => Promise<void>;
|
||||
requestVerificationSMS: (number: string, token: string) => Promise<void>;
|
||||
requestVerificationVoice: (number: string, token: string) => Promise<void>;
|
||||
checkAccountExistence: (uuid: UUID) => Promise<boolean>;
|
||||
|
@ -1800,15 +1806,19 @@ export function initialize({
|
|||
});
|
||||
}
|
||||
|
||||
async function reportMessage(
|
||||
senderUuid: string,
|
||||
serverGuid: string
|
||||
): Promise<void> {
|
||||
async function reportMessage({
|
||||
senderUuid,
|
||||
serverGuid,
|
||||
token,
|
||||
}: ReportMessageOptionsType): Promise<void> {
|
||||
const jsonData = { token };
|
||||
|
||||
await _ajax({
|
||||
call: 'reportMessage',
|
||||
httpType: 'POST',
|
||||
urlParameters: urlPathFromComponents([senderUuid, serverGuid]),
|
||||
responseType: 'bytes',
|
||||
jsonData,
|
||||
});
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue