Simplify redux ducks and avoid reexport

This commit is contained in:
Fedor Indutny 2023-04-07 10:46:00 -07:00 committed by GitHub
parent bd41d7b216
commit d34d187f1e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 144 additions and 94 deletions

View file

@ -27,6 +27,8 @@ export type ConfigKeyType =
| 'desktop.messageCleanup'
| 'desktop.messageRequests'
| 'desktop.pnp'
| 'desktop.safetyNumberUUID'
| 'desktop.safetyNumberUUID.timestamp'
| 'desktop.retryReceiptLifespan'
| 'desktop.retryRespondMaxAge'
| 'desktop.senderKey.retry'

View file

@ -2,6 +2,8 @@
// SPDX-License-Identifier: AGPL-3.0-only
import type { ReadonlyDeep } from 'type-fest';
import type { ThunkAction } from 'redux-thunk';
import { generateSecurityNumberBlock } from '../../util/safetyNumber';
import type { ConversationType } from './conversations';
import {
@ -10,6 +12,8 @@ import {
} from '../../shims/contactVerification';
import * as log from '../../logging/log';
import * as Errors from '../../types/errors';
import type { StateType as RootStateType } from '../reducer';
import { getSecurityNumberIdentifierType } from '../selectors/items';
export type SafetyNumberContactType = ReadonlyDeep<{
safetyNumber: string;
@ -23,83 +27,108 @@ export type SafetyNumberStateType = ReadonlyDeep<{
};
}>;
const GENERATE = 'safetyNumber/GENERATE';
const GENERATE_FULFILLED = 'safetyNumber/GENERATE_FULFILLED';
const TOGGLE_VERIFIED = 'safetyNumber/TOGGLE_VERIFIED';
const TOGGLE_VERIFIED_FULFILLED = 'safetyNumber/TOGGLE_VERIFIED_FULFILLED';
const TOGGLE_VERIFIED_PENDING = 'safetyNumber/TOGGLE_VERIFIED_PENDING';
type GenerateAsyncActionType = ReadonlyDeep<{
contact: ConversationType;
safetyNumber: string;
}>;
type GenerateActionType = ReadonlyDeep<{
type: 'safetyNumber/GENERATE';
payload: Promise<GenerateAsyncActionType>;
}>;
type GenerateFulfilledActionType = ReadonlyDeep<{
type: 'safetyNumber/GENERATE_FULFILLED';
payload: GenerateAsyncActionType;
}>;
type ToggleVerifiedAsyncActionType = ReadonlyDeep<{
contact: ConversationType;
safetyNumber?: string;
safetyNumberChanged?: boolean;
}>;
type ToggleVerifiedActionType = ReadonlyDeep<{
type: 'safetyNumber/TOGGLE_VERIFIED';
payload: {
data: { contact: ConversationType };
promise: Promise<ToggleVerifiedAsyncActionType>;
contact: ConversationType;
safetyNumber: string;
};
}>;
type ToggleVerifiedPendingActionType = ReadonlyDeep<{
type: 'safetyNumber/TOGGLE_VERIFIED_PENDING';
payload: ToggleVerifiedAsyncActionType;
payload: {
contact: ConversationType;
};
}>;
type ToggleVerifiedFulfilledActionType = ReadonlyDeep<{
type: 'safetyNumber/TOGGLE_VERIFIED_FULFILLED';
payload: ToggleVerifiedAsyncActionType;
payload: {
contact: ConversationType;
safetyNumber?: string;
safetyNumberChanged?: boolean;
};
}>;
export type SafetyNumberActionType = ReadonlyDeep<
| GenerateActionType
| GenerateFulfilledActionType
| ToggleVerifiedActionType
| ToggleVerifiedPendingActionType
| ToggleVerifiedFulfilledActionType
>;
function generate(contact: ConversationType): GenerateActionType {
return {
type: GENERATE,
payload: doGenerate(contact),
};
}
async function doGenerate(
function generate(
contact: ConversationType
): Promise<GenerateAsyncActionType> {
const securityNumberBlock = await generateSecurityNumberBlock(contact);
return {
contact,
safetyNumber: securityNumberBlock.join(' '),
): ThunkAction<void, RootStateType, unknown, GenerateFulfilledActionType> {
return async (dispatch, getState) => {
try {
const securityNumberBlock = await generateSecurityNumberBlock(
contact,
getSecurityNumberIdentifierType(getState(), { now: Date.now() })
);
dispatch({
type: GENERATE_FULFILLED,
payload: {
contact,
safetyNumber: securityNumberBlock.join(' '),
},
});
} catch (error) {
log.error(
'failed to generate security number:',
Errors.toLogFormat(error)
);
}
};
}
function toggleVerified(contact: ConversationType): ToggleVerifiedActionType {
return {
type: TOGGLE_VERIFIED,
payload: {
data: { contact },
promise: doToggleVerified(contact),
},
function toggleVerified(
contact: ConversationType
): ThunkAction<
void,
RootStateType,
unknown,
ToggleVerifiedPendingActionType | ToggleVerifiedFulfilledActionType
> {
return async (dispatch, getState) => {
dispatch({
type: TOGGLE_VERIFIED_PENDING,
payload: {
contact,
},
});
try {
await alterVerification(contact);
dispatch({
type: TOGGLE_VERIFIED_FULFILLED,
payload: {
contact,
},
});
} catch (err) {
if (err.name === 'OutgoingIdentityKeyError') {
await reloadProfiles(contact.id);
const securityNumberBlock = await generateSecurityNumberBlock(
contact,
getSecurityNumberIdentifierType(getState(), { now: Date.now() })
);
dispatch({
type: TOGGLE_VERIFIED_FULFILLED,
payload: {
contact,
safetyNumber: securityNumberBlock.join(' '),
safetyNumberChanged: true,
},
});
}
}
};
}
@ -128,27 +157,6 @@ async function alterVerification(contact: ConversationType): Promise<void> {
}
}
async function doToggleVerified(
contact: ConversationType
): Promise<ToggleVerifiedAsyncActionType> {
try {
await alterVerification(contact);
} catch (err) {
if (err.name === 'OutgoingIdentityKeyError') {
await reloadProfiles(contact.id);
const securityNumberBlock = await generateSecurityNumberBlock(contact);
return {
contact,
safetyNumber: securityNumberBlock.join(' '),
safetyNumberChanged: true,
};
}
}
return { contact };
}
export const actions = {
generateSafetyNumber: generate,
toggleVerified,

View file

@ -5,6 +5,7 @@ import { createSelector } from 'reselect';
import { isInteger } from 'lodash';
import { ITEM_NAME as UNIVERSAL_EXPIRE_TIMER_ITEM } from '../../util/universalExpireTimer';
import { SecurityNumberIdentifierType } from '../../util/safetyNumber';
import { innerIsBucketValueEnabled } from '../../RemoteConfig';
import type { ConfigKeyType, ConfigMapType } from '../../RemoteConfig';
import type { StateType } from '../reducer';
@ -145,6 +146,25 @@ export const getContactManagementEnabled = createSelector(
}
);
export const getSecurityNumberIdentifierType = createSelector(
getRemoteConfig,
(_state: StateType, { now }: { now: number }) => now,
(remoteConfig: ConfigMapType, now: number): SecurityNumberIdentifierType => {
if (isRemoteConfigFlagEnabled(remoteConfig, 'desktop.safetyNumberUUID')) {
return SecurityNumberIdentifierType.UUIDIdentifier;
}
const timestamp = remoteConfig['desktop.safetyNumberUUID.timestamp']?.value;
if (typeof timestamp !== 'number') {
return SecurityNumberIdentifierType.E164Identifier;
}
return now >= timestamp
? SecurityNumberIdentifierType.UUIDIdentifier
: SecurityNumberIdentifierType.E164Identifier;
}
);
export const getDefaultConversationColor = createSelector(
getItems,
(

View file

@ -9,7 +9,6 @@ import { createBatcher } from './batcher';
import { createWaitBatcher } from './waitBatcher';
import { deleteForEveryone } from './deleteForEveryone';
import { downloadAttachment } from './downloadAttachment';
import { generateSecurityNumber } from './safetyNumber';
import { getStringForProfileChange } from './getStringForProfileChange';
import { getTextWithMentions } from './getTextWithMentions';
import { getUuidsForE164s } from './getUuidsForE164s';
@ -53,7 +52,6 @@ export {
downloadAttachment,
flushMessageCounter,
fromWebSafeBase64,
generateSecurityNumber,
getStringForProfileChange,
getTextWithMentions,
getUserAgent,

View file

@ -6,17 +6,18 @@ import type { ConversationType } from '../state/ducks/conversations';
import { UUID } from '../types/UUID';
import { assertDev } from './assert';
import { missingCaseError } from './missingCaseError';
import * as log from '../logging/log';
export async function generateSecurityNumber(
ourNumber: string,
function generateSecurityNumber(
ourId: string,
ourKey: Uint8Array,
theirNumber: string,
theirId: string,
theirKey: Uint8Array
): Promise<string> {
const ourNumberBuf = Buffer.from(ourNumber);
): string {
const ourNumberBuf = Buffer.from(ourId);
const ourKeyObj = PublicKey.deserialize(Buffer.from(ourKey));
const theirNumberBuf = Buffer.from(theirNumber);
const theirNumberBuf = Buffer.from(theirId);
const theirKeyObj = PublicKey.deserialize(Buffer.from(theirKey));
const fingerprint = Fingerprint.new(
@ -28,13 +29,21 @@ export async function generateSecurityNumber(
theirKeyObj
);
const fingerprintString = fingerprint.displayableFingerprint().toString();
return Promise.resolve(fingerprintString);
return fingerprint.displayableFingerprint().toString();
}
export enum SecurityNumberIdentifierType {
UUIDIdentifier = 'UUIDIdentifier',
E164Identifier = 'E164Identifier',
}
export async function generateSecurityNumberBlock(
contact: ConversationType
contact: ConversationType,
identifierType: SecurityNumberIdentifierType
): Promise<Array<string>> {
const logId = `generateSecurityNumberBlock(${contact.id}, ${identifierType})`;
log.info(`${logId}: starting`);
const { storage } = window.textsecure;
const ourNumber = storage.user.getNumber();
const ourUuid = storage.user.getCheckedUuid();
@ -56,20 +65,33 @@ export async function generateSecurityNumberBlock(
throw new Error('Could not load their key');
}
if (!contact.e164) {
log.error(
'generateSecurityNumberBlock: Attempted to generate security number for contact with no e164'
);
return [];
}
let securityNumber: string;
if (identifierType === SecurityNumberIdentifierType.E164Identifier) {
if (!contact.e164) {
log.error(
`${logId}: Attempted to generate security number for contact with no e164`
);
return [];
}
assertDev(ourNumber, 'Should have our number');
const securityNumber = await generateSecurityNumber(
ourNumber,
ourKey,
contact.e164,
theirKey
);
assertDev(ourNumber, 'Should have our number');
securityNumber = generateSecurityNumber(
ourNumber,
ourKey,
contact.e164,
theirKey
);
} else if (identifierType === SecurityNumberIdentifierType.UUIDIdentifier) {
assertDev(theirUuid, 'Should have their uuid');
securityNumber = generateSecurityNumber(
ourUuid.toString(),
ourKey,
theirUuid.toString(),
theirKey
);
} else {
throw missingCaseError(identifierType);
}
const chunks = [];
for (let i = 0; i < securityNumber.length; i += 5) {