Update safety number change warning dialog
This commit is contained in:
parent
e87a0103cc
commit
5b83485c89
38 changed files with 1221 additions and 425 deletions
|
@ -4,6 +4,7 @@ import { actions as emojis } from './ducks/emojis';
|
|||
import { actions as expiration } from './ducks/expiration';
|
||||
import { actions as items } from './ducks/items';
|
||||
import { actions as network } from './ducks/network';
|
||||
import { actions as safetyNumber } from './ducks/safetyNumber';
|
||||
import { actions as search } from './ducks/search';
|
||||
import { actions as stickers } from './ducks/stickers';
|
||||
import { actions as updates } from './ducks/updates';
|
||||
|
@ -16,6 +17,7 @@ export const mapDispatchToProps = {
|
|||
...expiration,
|
||||
...items,
|
||||
...network,
|
||||
...safetyNumber,
|
||||
...search,
|
||||
...stickers,
|
||||
...updates,
|
||||
|
|
|
@ -24,12 +24,15 @@ export type DBConversationType = {
|
|||
};
|
||||
export type ConversationType = {
|
||||
id: string;
|
||||
uuid?: string;
|
||||
e164: string;
|
||||
name?: string;
|
||||
profileName?: string;
|
||||
avatarPath?: string;
|
||||
color?: ColorType;
|
||||
isArchived?: boolean;
|
||||
isBlocked?: boolean;
|
||||
isVerified?: boolean;
|
||||
activeAt?: number;
|
||||
timestamp: number;
|
||||
inboxPosition: number;
|
||||
|
@ -42,6 +45,7 @@ export type ConversationType = {
|
|||
type: 'direct' | 'group';
|
||||
isMe: boolean;
|
||||
lastUpdated: number;
|
||||
title: string;
|
||||
unreadCount: number;
|
||||
isSelected: boolean;
|
||||
typingContact?: {
|
||||
|
|
214
ts/state/ducks/safetyNumber.ts
Normal file
214
ts/state/ducks/safetyNumber.ts
Normal file
|
@ -0,0 +1,214 @@
|
|||
import { generateSecurityNumberBlock } from '../../util/safetyNumber';
|
||||
import { ConversationType } from './conversations';
|
||||
import {
|
||||
reloadProfiles,
|
||||
toggleVerification,
|
||||
} from '../../shims/contactVerification';
|
||||
|
||||
export type SafetyNumberContactType = {
|
||||
safetyNumber: string;
|
||||
safetyNumberChanged?: boolean;
|
||||
verificationDisabled: boolean;
|
||||
};
|
||||
|
||||
export type SafetyNumberStateType = {
|
||||
contacts: {
|
||||
[key: string]: SafetyNumberContactType;
|
||||
};
|
||||
};
|
||||
|
||||
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 = {
|
||||
contact: ConversationType;
|
||||
safetyNumber: string;
|
||||
};
|
||||
|
||||
type GenerateActionType = {
|
||||
type: 'safetyNumber/GENERATE';
|
||||
payload: Promise<GenerateAsyncActionType>;
|
||||
};
|
||||
|
||||
type GenerateFulfilledActionType = {
|
||||
type: 'safetyNumber/GENERATE_FULFILLED';
|
||||
payload: GenerateAsyncActionType;
|
||||
};
|
||||
|
||||
type ToggleVerifiedAsyncActionType = {
|
||||
contact: ConversationType;
|
||||
safetyNumber?: string;
|
||||
safetyNumberChanged?: boolean;
|
||||
};
|
||||
|
||||
type ToggleVerifiedActionType = {
|
||||
type: 'safetyNumber/TOGGLE_VERIFIED';
|
||||
payload: {
|
||||
data: { contact: ConversationType };
|
||||
promise: Promise<ToggleVerifiedAsyncActionType>;
|
||||
};
|
||||
};
|
||||
|
||||
type ToggleVerifiedPendingActionType = {
|
||||
type: 'safetyNumber/TOGGLE_VERIFIED_PENDING';
|
||||
payload: ToggleVerifiedAsyncActionType;
|
||||
};
|
||||
|
||||
type ToggleVerifiedFulfilledActionType = {
|
||||
type: 'safetyNumber/TOGGLE_VERIFIED_FULFILLED';
|
||||
payload: ToggleVerifiedAsyncActionType;
|
||||
};
|
||||
|
||||
export type SafetyNumberActionTypes =
|
||||
| GenerateActionType
|
||||
| GenerateFulfilledActionType
|
||||
| ToggleVerifiedActionType
|
||||
| ToggleVerifiedPendingActionType
|
||||
| ToggleVerifiedFulfilledActionType;
|
||||
|
||||
function generate(contact: ConversationType): GenerateActionType {
|
||||
return {
|
||||
type: GENERATE,
|
||||
payload: doGenerate(contact),
|
||||
};
|
||||
}
|
||||
|
||||
async function doGenerate(
|
||||
contact: ConversationType
|
||||
): Promise<GenerateAsyncActionType> {
|
||||
const securityNumberBlock = await generateSecurityNumberBlock(contact);
|
||||
return {
|
||||
contact,
|
||||
safetyNumber: securityNumberBlock.join(' '),
|
||||
};
|
||||
}
|
||||
|
||||
function toggleVerified(contact: ConversationType): ToggleVerifiedActionType {
|
||||
return {
|
||||
type: TOGGLE_VERIFIED,
|
||||
payload: {
|
||||
data: { contact },
|
||||
promise: doToggleVerified(contact),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
async function alterVerification(contact: ConversationType): Promise<void> {
|
||||
try {
|
||||
await toggleVerification(contact.id);
|
||||
} catch (result) {
|
||||
if (result instanceof Error) {
|
||||
if (result.name === 'OutgoingIdentityKeyError') {
|
||||
throw result;
|
||||
} else {
|
||||
window.log.error(
|
||||
'failed to toggle verified:',
|
||||
result && result.stack ? result.stack : result
|
||||
);
|
||||
}
|
||||
} else {
|
||||
const keyError = result.errors.find(
|
||||
(error: Error) => error.name === 'OutgoingIdentityKeyError'
|
||||
);
|
||||
if (keyError) {
|
||||
throw keyError;
|
||||
} else {
|
||||
result.errors.forEach((error: Error) => {
|
||||
window.log.error(
|
||||
'failed to toggle verified:',
|
||||
error && error.stack ? error.stack : error
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
};
|
||||
|
||||
function getEmptyState(): SafetyNumberStateType {
|
||||
return {
|
||||
contacts: {},
|
||||
};
|
||||
}
|
||||
|
||||
export function reducer(
|
||||
state: SafetyNumberStateType = getEmptyState(),
|
||||
action: SafetyNumberActionTypes
|
||||
): SafetyNumberStateType {
|
||||
if (action.type === TOGGLE_VERIFIED_PENDING) {
|
||||
const { contact } = action.payload;
|
||||
const { id } = contact;
|
||||
const record = state.contacts[id];
|
||||
return {
|
||||
contacts: {
|
||||
...state.contacts,
|
||||
[id]: {
|
||||
...record,
|
||||
safetyNumberChanged: false,
|
||||
verificationDisabled: true,
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
if (action.type === TOGGLE_VERIFIED_FULFILLED) {
|
||||
const { contact, ...restProps } = action.payload;
|
||||
const { id } = contact;
|
||||
const record = state.contacts[id];
|
||||
return {
|
||||
contacts: {
|
||||
...state.contacts,
|
||||
[id]: {
|
||||
...record,
|
||||
...restProps,
|
||||
verificationDisabled: false,
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
if (action.type === GENERATE_FULFILLED) {
|
||||
const { contact, safetyNumber } = action.payload;
|
||||
const { id } = contact;
|
||||
const record = state.contacts[id];
|
||||
return {
|
||||
contacts: {
|
||||
...state.contacts,
|
||||
[id]: {
|
||||
...record,
|
||||
safetyNumber,
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
return state;
|
||||
}
|
|
@ -30,6 +30,11 @@ import {
|
|||
NetworkStateType,
|
||||
reducer as network,
|
||||
} from './ducks/network';
|
||||
import {
|
||||
reducer as safetyNumber,
|
||||
SafetyNumberActionTypes,
|
||||
SafetyNumberStateType,
|
||||
} from './ducks/safetyNumber';
|
||||
import {
|
||||
reducer as search,
|
||||
SEARCH_TYPES as SearchActionType,
|
||||
|
@ -54,6 +59,7 @@ export type StateType = {
|
|||
expiration: ExpirationStateType;
|
||||
items: ItemsStateType;
|
||||
network: NetworkStateType;
|
||||
safetyNumber: SafetyNumberStateType;
|
||||
search: SearchStateType;
|
||||
stickers: StickersStateType;
|
||||
updates: UpdatesStateType;
|
||||
|
@ -67,6 +73,7 @@ export type ActionsType =
|
|||
| ConversationActionType
|
||||
| ItemsActionType
|
||||
| NetworkActionType
|
||||
| SafetyNumberActionTypes
|
||||
| StickersActionType
|
||||
| SearchActionType
|
||||
| UpdatesActionType;
|
||||
|
@ -78,6 +85,7 @@ export const reducers = {
|
|||
expiration,
|
||||
items,
|
||||
network,
|
||||
safetyNumber,
|
||||
search,
|
||||
stickers,
|
||||
updates,
|
||||
|
|
21
ts/state/roots/createSafetyNumberViewer.tsx
Normal file
21
ts/state/roots/createSafetyNumberViewer.tsx
Normal file
|
@ -0,0 +1,21 @@
|
|||
import React from 'react';
|
||||
import { Provider } from 'react-redux';
|
||||
|
||||
import { Store } from 'redux';
|
||||
|
||||
import { SmartSafetyNumberViewer } from '../smart/SafetyNumberViewer';
|
||||
|
||||
// Workaround: A react component's required properties are filtering up through connect()
|
||||
// https://github.com/DefinitelyTyped/DefinitelyTyped/issues/31363
|
||||
const FilteredSafetyNumberViewer = SmartSafetyNumberViewer as any;
|
||||
|
||||
type Props = {
|
||||
contactID: string;
|
||||
onClose?: () => void;
|
||||
};
|
||||
|
||||
export const createSafetyNumberViewer = (store: Store, props: Props) => (
|
||||
<Provider store={store}>
|
||||
<FilteredSafetyNumberViewer {...props} />
|
||||
</Provider>
|
||||
);
|
24
ts/state/selectors/safetyNumber.ts
Normal file
24
ts/state/selectors/safetyNumber.ts
Normal file
|
@ -0,0 +1,24 @@
|
|||
import { createSelector } from 'reselect';
|
||||
|
||||
import { StateType } from '../reducer';
|
||||
import {
|
||||
SafetyNumberContactType,
|
||||
SafetyNumberStateType,
|
||||
} from '../ducks/safetyNumber';
|
||||
|
||||
const getSafetyNumber = (state: StateType): SafetyNumberStateType =>
|
||||
state.safetyNumber;
|
||||
|
||||
type Props = {
|
||||
contactID: string;
|
||||
};
|
||||
|
||||
const getContactID = (_: StateType, props: Props): string => props.contactID;
|
||||
|
||||
export const getContactSafetyNumber = createSelector(
|
||||
[getSafetyNumber, getContactID],
|
||||
(
|
||||
{ contacts }: SafetyNumberStateType,
|
||||
contactID: string
|
||||
): SafetyNumberContactType => contacts[contactID]
|
||||
);
|
25
ts/state/smart/SafetyNumberViewer.tsx
Normal file
25
ts/state/smart/SafetyNumberViewer.tsx
Normal file
|
@ -0,0 +1,25 @@
|
|||
import { connect } from 'react-redux';
|
||||
import { mapDispatchToProps } from '../actions';
|
||||
import { SafetyNumberViewer } from '../../components/SafetyNumberViewer';
|
||||
import { StateType } from '../reducer';
|
||||
import { getContactSafetyNumber } from '../selectors/safetyNumber';
|
||||
import { getConversationSelector } from '../selectors/conversations';
|
||||
import { getIntl } from '../selectors/user';
|
||||
|
||||
type Props = {
|
||||
contactID: string;
|
||||
onClose?: () => void;
|
||||
};
|
||||
|
||||
const mapStateToProps = (state: StateType, props: Props) => {
|
||||
return {
|
||||
...props,
|
||||
...getContactSafetyNumber(state, props),
|
||||
contact: getConversationSelector(state)(props.contactID),
|
||||
i18n: getIntl(state),
|
||||
};
|
||||
};
|
||||
|
||||
const smart = connect(mapStateToProps, mapDispatchToProps);
|
||||
|
||||
export const SmartSafetyNumberViewer = smart(SafetyNumberViewer);
|
Loading…
Add table
Add a link
Reference in a new issue