Files

188 lines
6.6 KiB
TypeScript

import { v4 } from 'uuid';
/* eslint-disable no-await-in-loop */
import { isNumber } from 'lodash';
import { runners } from '../JobRunner';
import {
AddJobCheckReturn,
PersistedJob,
RunJobResult,
type UpdateProRevocationListPersistedData,
} from '../PersistedJob';
import ProBackendAPI from '../../../apis/pro_backend_api/ProBackendAPI';
import { DURATION } from '../../../constants';
import { getFeatureFlag } from '../../../../state/ducks/types/releasedFeaturesReduxTypes';
import { formatRoundedUpDuration } from '../../../../util/i18n/formatting/generics';
import { isDevProd } from '../../../../shared/env_vars';
import { ProRevocationCache } from '../../../revocation_list/pro_revocation_list';
import { stringify } from '../../../../types/sqlSharedTypes';
import { proBackendDataActions } from '../../../../state/ducks/proBackendData';
import { getCachedUserConfig } from '../../../../webworker/workers/browser/libsession/libsession_worker_userconfig_interface';
import { ConvoHub } from '../../../conversations';
let lastRunAtMs = 0;
const delayBetweenRuns = isDevProd() ? 15 * DURATION.SECONDS : 15 * DURATION.MINUTES;
class UpdateProRevocationListJob extends PersistedJob<UpdateProRevocationListPersistedData> {
constructor({
identifier,
nextAttemptTimestamp,
maxAttempts,
currentRetry,
}: Partial<
Pick<
UpdateProRevocationListPersistedData,
'identifier' | 'nextAttemptTimestamp' | 'currentRetry' | 'maxAttempts'
>
>) {
super({
jobType: 'UpdateProRevocationListJobType',
identifier: identifier ?? v4(),
delayBetweenRetries: 15 * DURATION.SECONDS,
maxAttempts: isNumber(maxAttempts) ? maxAttempts : 2,
currentRetry: isNumber(currentRetry) ? currentRetry : 0,
nextAttemptTimestamp: nextAttemptTimestamp || Date.now(),
});
}
public async run(): Promise<RunJobResult> {
if (!getFeatureFlag('proAvailable')) {
return RunJobResult.Success;
}
const start = Date.now();
try {
window.log.debug(`UpdateProRevocationListJob run() started`);
const ticketFromDb = ProRevocationCache.getTicket();
const response = await ProBackendAPI.getRevocationList({
ticket: ticketFromDb,
});
if (getFeatureFlag('debugServerRequests')) {
window.log.info(
`[updateProRevocationList] response from server: ${JSON.stringify(response)}`
);
}
if (response?.status_code !== 200) {
window.log.debug(`UpdateProRevocationListJob run() failed: ${JSON.stringify(response)}`);
window.log.warn(`UpdateProRevocationListJob run() failed. Will retry soon if possible`);
return RunJobResult.RetryJobIfPossible;
}
if (response.result.ticket <= ticketFromDb) {
window.log.debug(
`UpdateProRevocationListJob: no new revocations from our existing ticket #${ticketFromDb}`
);
lastRunAtMs = Date.now();
return RunJobResult.Success;
}
const newTicket = response.result.ticket;
const newItems = response.result.items;
window.log.debug(
`UpdateProRevocationListJob: new revocations from ticket #${ticketFromDb}: to #${newTicket}. items: ${stringify(response.result.items)}`
);
// Note: we only want to update the lastRunAt once we have successfully fetched the new revocations
lastRunAtMs = Date.now();
await ProRevocationCache.setTicket(newTicket);
await ProRevocationCache.setListItems(newItems);
window.log.info(
`UpdateProRevocationListJob: new revocations from ticket #${ticketFromDb}: to #${newTicket}. itemsCount: ${response.result.items.length}`
);
const proConfig = getCachedUserConfig().proConfig;
if (
proConfig &&
proConfig.proProof.genIndexHashB64 &&
newItems.some(m => m.gen_index_hash_b64 === proConfig.proProof.genIndexHashB64)
) {
// if we've been revoked, refresh our pro proof.
// this will fetch the new one if one is provided or just remove it from our config.
window.log.info(
`UpdateProRevocationListJob: our current genIndexHash is revoked. Refreshing our pro proof.`
);
window.inboxStore?.dispatch(
proBackendDataActions.refreshGetProDetailsFromProBackend({}) as any
);
}
// find all the conversations that have a revoked genIndexHAsh and trigger a UI refresh on them
const convos = ConvoHub.use().getConversations();
convos.forEach(m => {
if (!m.dbContactProDetails()?.proGenIndexHashB64) {
return;
}
const revoked = newItems.some(
item => item.gen_index_hash_b64 === m.dbContactProDetails()?.proGenIndexHashB64
);
if (revoked) {
window.log.debug(
`UpdateProRevocationListJob: found a revoked genIndexHash for convo ${m.idForLogging()}. Triggering UI refresh.`
);
m.triggerUIRefresh();
}
});
return RunJobResult.Success;
} catch (e) {
window.log.warn('UpdateProRevocationListJob run() failed with', e.message);
return RunJobResult.RetryJobIfPossible;
} finally {
window.log.debug(`UpdateProRevocationListPersistedData run() took ${Date.now() - start}ms`);
}
}
public serializeJob(): UpdateProRevocationListPersistedData {
const fromParent = super.serializeBase();
return fromParent;
}
public addJobCheck(jobs: Array<UpdateProRevocationListPersistedData>): AddJobCheckReturn {
return this.addJobCheckSameTypePresent(jobs);
}
public nonRunningJobsToRemove(_jobs: Array<UpdateProRevocationListPersistedData>) {
return [];
}
public getJobTimeoutMs(): number {
return 20 * DURATION.SECONDS;
}
}
async function queueNewJobIfNeeded() {
const diffMs = Date.now() - lastRunAtMs;
if (diffMs <= delayBetweenRuns) {
window.log.debug(
`NOT Scheduling UpdateProRevocationListJob: as we have already run it recently (${formatRoundedUpDuration(diffMs)} ago)`
);
return;
}
window.log.debug(
`Scheduling UpdateProRevocationListJob: as the last (successful) run was not recent (${formatRoundedUpDuration(diffMs)} ago)`
);
await runners.updateProRevocationListRunner.addJob(
new UpdateProRevocationListJob({ nextAttemptTimestamp: Date.now() + 20 * DURATION.SECONDS })
);
}
async function runOnStartup() {
try {
const job = new UpdateProRevocationListJob({ nextAttemptTimestamp: Date.now() });
await job.run();
} catch (e) {
window.log.warn('UpdateProRevocationListJob runOnStartup failed with', e.message);
}
}
export const UpdateProRevocationList = {
UpdateProRevocationListJob,
queueNewJobIfNeeded,
runOnStartup,
};