2021-05-27 20:17:05 +00:00
|
|
|
// Copyright 2021 Signal Messenger, LLC
|
|
|
|
// SPDX-License-Identifier: AGPL-3.0-only
|
2021-07-21 21:10:08 +00:00
|
|
|
/* eslint-disable class-methods-use-this */
|
2021-05-27 20:17:05 +00:00
|
|
|
|
|
|
|
import * as z from 'zod';
|
2021-08-26 14:10:58 +00:00
|
|
|
import * as durations from '../util/durations';
|
2021-07-23 17:23:50 +00:00
|
|
|
import { strictAssert } from '../util/assert';
|
2021-05-27 20:17:05 +00:00
|
|
|
import { waitForOnline } from '../util/waitForOnline';
|
|
|
|
import { isDone as isDeviceLinked } from '../util/registration';
|
2021-09-17 18:27:53 +00:00
|
|
|
import type { LoggerType } from '../types/Logging';
|
2021-05-27 20:17:05 +00:00
|
|
|
import { map } from '../util/iterables';
|
|
|
|
import { sleep } from '../util/sleep';
|
|
|
|
|
|
|
|
import { JobQueue } from './JobQueue';
|
|
|
|
import { jobQueueDatabaseStore } from './JobQueueDatabaseStore';
|
|
|
|
import { parseIntWithFallback } from '../util/parseIntWithFallback';
|
2021-07-23 17:23:50 +00:00
|
|
|
import type { WebAPIType } from '../textsecure/WebAPI';
|
2021-09-22 00:58:03 +00:00
|
|
|
import { HTTPError } from '../textsecure/Errors';
|
2021-05-27 20:17:05 +00:00
|
|
|
|
2021-08-26 14:10:58 +00:00
|
|
|
const RETRY_WAIT_TIME = durations.MINUTE;
|
2021-05-27 20:17:05 +00:00
|
|
|
const RETRYABLE_4XX_FAILURE_STATUSES = new Set([
|
|
|
|
404,
|
|
|
|
408,
|
|
|
|
410,
|
|
|
|
412,
|
|
|
|
413,
|
|
|
|
414,
|
|
|
|
417,
|
|
|
|
423,
|
|
|
|
424,
|
|
|
|
425,
|
|
|
|
426,
|
|
|
|
428,
|
|
|
|
429,
|
|
|
|
431,
|
|
|
|
449,
|
|
|
|
]);
|
|
|
|
|
|
|
|
const is4xxStatus = (code: number): boolean => code >= 400 && code <= 499;
|
|
|
|
const is5xxStatus = (code: number): boolean => code >= 500 && code <= 599;
|
|
|
|
const isRetriable4xxStatus = (code: number): boolean =>
|
|
|
|
RETRYABLE_4XX_FAILURE_STATUSES.has(code);
|
|
|
|
|
|
|
|
const reportSpamJobDataSchema = z.object({
|
|
|
|
e164: z.string().min(1),
|
|
|
|
serverGuids: z.string().array().min(1).max(1000),
|
|
|
|
});
|
|
|
|
|
|
|
|
export type ReportSpamJobData = z.infer<typeof reportSpamJobDataSchema>;
|
|
|
|
|
2021-07-21 21:10:08 +00:00
|
|
|
export class ReportSpamJobQueue extends JobQueue<ReportSpamJobData> {
|
2021-07-23 17:23:50 +00:00
|
|
|
private server?: WebAPIType;
|
|
|
|
|
|
|
|
public initialize({ server }: { server: WebAPIType }): void {
|
|
|
|
this.server = server;
|
|
|
|
}
|
|
|
|
|
2021-07-21 21:10:08 +00:00
|
|
|
protected parseData(data: unknown): ReportSpamJobData {
|
2021-05-27 20:17:05 +00:00
|
|
|
return reportSpamJobDataSchema.parse(data);
|
2021-07-21 21:10:08 +00:00
|
|
|
}
|
2021-05-27 20:17:05 +00:00
|
|
|
|
2021-08-17 15:43:26 +00:00
|
|
|
protected async run(
|
|
|
|
{ data }: Readonly<{ data: ReportSpamJobData }>,
|
|
|
|
{ log }: Readonly<{ log: LoggerType }>
|
|
|
|
): Promise<void> {
|
2021-05-27 20:17:05 +00:00
|
|
|
const { e164, serverGuids } = data;
|
|
|
|
|
|
|
|
await new Promise<void>(resolve => {
|
|
|
|
window.storage.onready(resolve);
|
|
|
|
});
|
|
|
|
|
|
|
|
if (!isDeviceLinked()) {
|
|
|
|
log.info("reportSpamJobQueue: skipping this job because we're unlinked");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
await waitForOnline(window.navigator, window);
|
|
|
|
|
2021-07-23 17:23:50 +00:00
|
|
|
const { server } = this;
|
|
|
|
strictAssert(server !== undefined, 'ReportSpamJobQueue not initialized');
|
2021-05-27 20:17:05 +00:00
|
|
|
|
|
|
|
try {
|
|
|
|
await Promise.all(
|
|
|
|
map(serverGuids, serverGuid => server.reportMessage(e164, serverGuid))
|
|
|
|
);
|
|
|
|
} catch (err: unknown) {
|
2021-09-22 00:58:03 +00:00
|
|
|
if (!(err instanceof HTTPError)) {
|
2021-05-27 20:17:05 +00:00
|
|
|
throw err;
|
|
|
|
}
|
|
|
|
|
|
|
|
const code = parseIntWithFallback(err.code, -1);
|
|
|
|
|
|
|
|
// This is an unexpected case, except for -1, which can happen for network failures.
|
|
|
|
if (code < 400) {
|
|
|
|
throw err;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (code === 508) {
|
|
|
|
log.info(
|
|
|
|
'reportSpamJobQueue: server responded with 508. Giving up on this job'
|
|
|
|
);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (isRetriable4xxStatus(code) || is5xxStatus(code)) {
|
|
|
|
log.info(
|
|
|
|
`reportSpamJobQueue: server responded with ${code} status code. Sleeping before our next attempt`
|
|
|
|
);
|
|
|
|
await sleep(RETRY_WAIT_TIME);
|
|
|
|
throw err;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (is4xxStatus(code)) {
|
|
|
|
log.error(
|
|
|
|
`reportSpamJobQueue: server responded with ${code} status code. Giving up on this job`
|
|
|
|
);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
throw err;
|
|
|
|
}
|
2021-07-21 21:10:08 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
export const reportSpamJobQueue = new ReportSpamJobQueue({
|
|
|
|
store: jobQueueDatabaseStore,
|
|
|
|
queueType: 'report spam',
|
|
|
|
maxAttempts: 25,
|
2021-05-27 20:17:05 +00:00
|
|
|
});
|