Simplify redux ducks and avoid reexport
This commit is contained in:
parent
bd41d7b216
commit
d34d187f1e
5 changed files with 144 additions and 94 deletions
|
@ -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'
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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,
|
||||||
(
|
(
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
Loading…
Add table
Reference in a new issue