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.messageCleanup'
| 'desktop.messageRequests' | 'desktop.messageRequests'
| 'desktop.pnp' | 'desktop.pnp'
| 'desktop.safetyNumberUUID'
| 'desktop.safetyNumberUUID.timestamp'
| 'desktop.retryReceiptLifespan' | 'desktop.retryReceiptLifespan'
| 'desktop.retryRespondMaxAge' | 'desktop.retryRespondMaxAge'
| 'desktop.senderKey.retry' | 'desktop.senderKey.retry'

View file

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

View file

@ -5,6 +5,7 @@ import { createSelector } from 'reselect';
import { isInteger } from 'lodash'; import { isInteger } from 'lodash';
import { ITEM_NAME as UNIVERSAL_EXPIRE_TIMER_ITEM } from '../../util/universalExpireTimer'; import { ITEM_NAME as UNIVERSAL_EXPIRE_TIMER_ITEM } from '../../util/universalExpireTimer';
import { SecurityNumberIdentifierType } from '../../util/safetyNumber';
import { innerIsBucketValueEnabled } from '../../RemoteConfig'; import { innerIsBucketValueEnabled } from '../../RemoteConfig';
import type { ConfigKeyType, ConfigMapType } from '../../RemoteConfig'; import type { ConfigKeyType, ConfigMapType } from '../../RemoteConfig';
import type { StateType } from '../reducer'; 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( export const getDefaultConversationColor = createSelector(
getItems, getItems,
( (

View file

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

View file

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