2023-01-03 19:55:46 +00:00
|
|
|
// Copyright 2019 Signal Messenger, LLC
|
2020-12-04 20:41:40 +00:00
|
|
|
// SPDX-License-Identifier: AGPL-3.0-only
|
|
|
|
|
|
|
|
import { createSelector } from 'reselect';
|
2021-09-09 16:29:01 +00:00
|
|
|
import { isInteger } from 'lodash';
|
2020-12-04 20:41:40 +00:00
|
|
|
|
2021-06-01 20:45:43 +00:00
|
|
|
import { ITEM_NAME as UNIVERSAL_EXPIRE_TIMER_ITEM } from '../../util/universalExpireTimer';
|
2023-07-13 19:06:42 +00:00
|
|
|
import { SafetyNumberMode } from '../../types/safetyNumber';
|
2022-10-20 21:02:22 +00:00
|
|
|
import { innerIsBucketValueEnabled } from '../../RemoteConfig';
|
2022-01-26 23:05:26 +00:00
|
|
|
import type { ConfigKeyType, ConfigMapType } from '../../RemoteConfig';
|
2021-10-26 19:15:33 +00:00
|
|
|
import type { StateType } from '../reducer';
|
|
|
|
import type { ItemsStateType } from '../ducks/items';
|
|
|
|
import type {
|
2021-06-02 21:05:09 +00:00
|
|
|
ConversationColorType,
|
|
|
|
CustomColorType,
|
|
|
|
} from '../../types/Colors';
|
2022-10-20 21:02:22 +00:00
|
|
|
import type { UUIDStringType } from '../../types/UUID';
|
2021-10-26 19:15:33 +00:00
|
|
|
import { DEFAULT_CONVERSATION_COLOR } from '../../types/Colors';
|
2021-09-15 18:59:51 +00:00
|
|
|
import { getPreferredReactionEmoji as getPreferredReactionEmojiFromStoredValue } from '../../reactions/preferredReactionEmoji';
|
2022-10-12 17:39:05 +00:00
|
|
|
import { isBeta } from '../../util/version';
|
2022-11-16 20:18:02 +00:00
|
|
|
import { DurationInSeconds } from '../../util/durations';
|
2022-10-20 21:02:22 +00:00
|
|
|
import { getUserNumber, getUserACI } from './user';
|
2020-12-04 20:41:40 +00:00
|
|
|
|
2021-10-12 23:59:08 +00:00
|
|
|
const DEFAULT_PREFERRED_LEFT_PANE_WIDTH = 320;
|
|
|
|
|
2020-12-04 20:41:40 +00:00
|
|
|
export const getItems = (state: StateType): ItemsStateType => state.items;
|
2022-05-06 19:02:44 +00:00
|
|
|
|
2021-11-30 16:29:57 +00:00
|
|
|
export const getAreWeASubscriber = createSelector(
|
|
|
|
getItems,
|
|
|
|
({ areWeASubscriber }: Readonly<ItemsStateType>): boolean =>
|
|
|
|
Boolean(areWeASubscriber)
|
|
|
|
);
|
|
|
|
|
2020-12-04 20:41:40 +00:00
|
|
|
export const getUserAgent = createSelector(
|
|
|
|
getItems,
|
|
|
|
(state: ItemsStateType): string => state.userAgent as string
|
|
|
|
);
|
|
|
|
|
|
|
|
export const getPinnedConversationIds = createSelector(
|
|
|
|
getItems,
|
|
|
|
(state: ItemsStateType): Array<string> =>
|
|
|
|
(state.pinnedConversationIds || []) as Array<string>
|
|
|
|
);
|
2021-06-01 20:45:43 +00:00
|
|
|
|
|
|
|
export const getUniversalExpireTimer = createSelector(
|
|
|
|
getItems,
|
2022-11-16 20:18:02 +00:00
|
|
|
(state: ItemsStateType): DurationInSeconds =>
|
|
|
|
DurationInSeconds.fromSeconds(state[UNIVERSAL_EXPIRE_TIMER_ITEM] || 0)
|
2021-06-01 20:45:43 +00:00
|
|
|
);
|
2021-06-02 21:05:09 +00:00
|
|
|
|
2023-04-14 18:16:28 +00:00
|
|
|
export const isRemoteConfigFlagEnabled = (
|
2022-01-26 23:05:26 +00:00
|
|
|
config: Readonly<ConfigMapType>,
|
|
|
|
key: ConfigKeyType
|
|
|
|
): boolean => Boolean(config[key]?.enabled);
|
|
|
|
|
2022-10-20 21:02:22 +00:00
|
|
|
// See isBucketValueEnabled in RemoteConfig.ts
|
|
|
|
const isRemoteConfigBucketEnabled = (
|
|
|
|
config: Readonly<ConfigMapType>,
|
|
|
|
name: ConfigKeyType,
|
|
|
|
e164: string | undefined,
|
|
|
|
uuid: UUIDStringType | undefined
|
|
|
|
): boolean => {
|
|
|
|
const flagValue = config[name]?.value;
|
|
|
|
return innerIsBucketValueEnabled(name, flagValue, e164, uuid);
|
|
|
|
};
|
|
|
|
|
2023-02-28 22:17:22 +00:00
|
|
|
export const getRemoteConfig = createSelector(
|
2021-11-01 19:13:35 +00:00
|
|
|
getItems,
|
2022-01-26 23:05:26 +00:00
|
|
|
(state: ItemsStateType): ConfigMapType => state.remoteConfig || {}
|
2021-11-01 19:13:35 +00:00
|
|
|
);
|
|
|
|
|
2023-07-13 19:06:42 +00:00
|
|
|
export const getServerTimeSkew = createSelector(
|
|
|
|
getItems,
|
|
|
|
(state: ItemsStateType): number => state.serverTimeSkew || 0
|
|
|
|
);
|
|
|
|
|
2021-11-01 19:13:35 +00:00
|
|
|
export const getUsernamesEnabled = createSelector(
|
|
|
|
getRemoteConfig,
|
2022-01-26 23:05:26 +00:00
|
|
|
(remoteConfig: ConfigMapType): boolean =>
|
|
|
|
isRemoteConfigFlagEnabled(remoteConfig, 'desktop.usernames')
|
|
|
|
);
|
|
|
|
|
2023-02-13 18:51:41 +00:00
|
|
|
export const getHasCompletedUsernameOnboarding = createSelector(
|
|
|
|
getItems,
|
|
|
|
(state: ItemsStateType): boolean =>
|
|
|
|
Boolean(state.hasCompletedUsernameOnboarding)
|
|
|
|
);
|
|
|
|
|
2023-07-13 19:06:42 +00:00
|
|
|
export const getHasCompletedSafetyNumberOnboarding = createSelector(
|
|
|
|
getItems,
|
|
|
|
(state: ItemsStateType): boolean =>
|
|
|
|
Boolean(state.hasCompletedSafetyNumberOnboarding)
|
|
|
|
);
|
|
|
|
|
2022-11-22 22:33:15 +00:00
|
|
|
export const isInternalUser = createSelector(
|
|
|
|
getRemoteConfig,
|
|
|
|
(remoteConfig: ConfigMapType): boolean => {
|
|
|
|
return isRemoteConfigFlagEnabled(remoteConfig, 'desktop.internalUser');
|
|
|
|
}
|
|
|
|
);
|
|
|
|
|
2022-10-20 21:02:22 +00:00
|
|
|
// Note: ts/util/stories is the other place this check is done
|
2022-03-04 21:14:52 +00:00
|
|
|
export const getStoriesEnabled = createSelector(
|
2022-07-20 00:47:05 +00:00
|
|
|
getItems,
|
2022-03-04 21:14:52 +00:00
|
|
|
getRemoteConfig,
|
2022-10-20 21:02:22 +00:00
|
|
|
getUserNumber,
|
|
|
|
getUserACI,
|
|
|
|
(
|
|
|
|
state: ItemsStateType,
|
|
|
|
remoteConfig: ConfigMapType,
|
|
|
|
e164: string | undefined,
|
|
|
|
aci: UUIDStringType | undefined
|
|
|
|
): boolean => {
|
|
|
|
if (state.hasStoriesDisabled) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (
|
2022-11-08 20:55:51 +00:00
|
|
|
isRemoteConfigBucketEnabled(remoteConfig, 'desktop.stories2', e164, aci)
|
2022-10-20 21:02:22 +00:00
|
|
|
) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (isRemoteConfigFlagEnabled(remoteConfig, 'desktop.internalUser')) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (
|
2022-11-08 20:55:51 +00:00
|
|
|
isRemoteConfigFlagEnabled(remoteConfig, 'desktop.stories2.beta') &&
|
2022-10-20 21:02:22 +00:00
|
|
|
isBeta(window.getVersion())
|
|
|
|
) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
2022-03-04 21:14:52 +00:00
|
|
|
);
|
|
|
|
|
2023-04-05 20:48:00 +00:00
|
|
|
export const getContactManagementEnabled = createSelector(
|
|
|
|
getRemoteConfig,
|
|
|
|
(remoteConfig: ConfigMapType): boolean => {
|
|
|
|
if (isRemoteConfigFlagEnabled(remoteConfig, 'desktop.contactManagement')) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (
|
|
|
|
isRemoteConfigFlagEnabled(
|
|
|
|
remoteConfig,
|
|
|
|
'desktop.contactManagement.beta'
|
|
|
|
) &&
|
|
|
|
isBeta(window.getVersion())
|
|
|
|
) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
);
|
|
|
|
|
2023-07-13 19:06:42 +00:00
|
|
|
export const getSafetyNumberMode = createSelector(
|
2023-04-07 17:46:00 +00:00
|
|
|
getRemoteConfig,
|
2023-07-13 19:06:42 +00:00
|
|
|
getServerTimeSkew,
|
2023-04-07 17:46:00 +00:00
|
|
|
(_state: StateType, { now }: { now: number }) => now,
|
2023-07-13 19:06:42 +00:00
|
|
|
(
|
|
|
|
remoteConfig: ConfigMapType,
|
|
|
|
serverTimeSkew: number,
|
|
|
|
now: number
|
|
|
|
): SafetyNumberMode => {
|
|
|
|
if (!isRemoteConfigFlagEnabled(remoteConfig, 'desktop.safetyNumberAci')) {
|
|
|
|
return SafetyNumberMode.E164;
|
2023-04-07 17:46:00 +00:00
|
|
|
}
|
|
|
|
|
2023-07-13 19:06:42 +00:00
|
|
|
const timestamp = remoteConfig['global.safetyNumberAci']?.value;
|
2023-04-07 17:46:00 +00:00
|
|
|
if (typeof timestamp !== 'number') {
|
2023-07-13 19:06:42 +00:00
|
|
|
return SafetyNumberMode.ACIAndE164;
|
2023-04-07 17:46:00 +00:00
|
|
|
}
|
|
|
|
|
2023-07-13 19:06:42 +00:00
|
|
|
// Note: serverTimeSkew is a difference between server time and local time,
|
|
|
|
// so we have to add local time to it to correct it for a skew.
|
|
|
|
return now + serverTimeSkew >= timestamp
|
|
|
|
? SafetyNumberMode.ACI
|
|
|
|
: SafetyNumberMode.ACIAndE164;
|
2023-04-07 17:46:00 +00:00
|
|
|
}
|
|
|
|
);
|
|
|
|
|
2021-06-02 21:05:09 +00:00
|
|
|
export const getDefaultConversationColor = createSelector(
|
|
|
|
getItems,
|
|
|
|
(
|
|
|
|
state: ItemsStateType
|
|
|
|
): {
|
|
|
|
color: ConversationColorType;
|
|
|
|
customColorData?: {
|
|
|
|
id: string;
|
|
|
|
value: CustomColorType;
|
|
|
|
};
|
2021-06-08 21:31:35 +00:00
|
|
|
} => state.defaultConversationColor ?? DEFAULT_CONVERSATION_COLOR
|
2021-06-02 21:05:09 +00:00
|
|
|
);
|
2021-08-18 20:08:14 +00:00
|
|
|
|
|
|
|
export const getCustomColors = createSelector(
|
|
|
|
getItems,
|
|
|
|
(state: ItemsStateType): Record<string, CustomColorType> | undefined =>
|
|
|
|
state.customColors?.colors
|
|
|
|
);
|
2021-09-09 16:29:01 +00:00
|
|
|
|
|
|
|
export const getEmojiSkinTone = createSelector(
|
|
|
|
getItems,
|
|
|
|
({ skinTone }: Readonly<ItemsStateType>): number =>
|
|
|
|
typeof skinTone === 'number' &&
|
|
|
|
isInteger(skinTone) &&
|
|
|
|
skinTone >= 0 &&
|
|
|
|
skinTone <= 5
|
|
|
|
? skinTone
|
|
|
|
: 0
|
|
|
|
);
|
|
|
|
|
2021-10-12 23:59:08 +00:00
|
|
|
export const getPreferredLeftPaneWidth = createSelector(
|
|
|
|
getItems,
|
|
|
|
({ preferredLeftPaneWidth }: Readonly<ItemsStateType>): number =>
|
|
|
|
typeof preferredLeftPaneWidth === 'number' &&
|
|
|
|
Number.isInteger(preferredLeftPaneWidth)
|
|
|
|
? preferredLeftPaneWidth
|
|
|
|
: DEFAULT_PREFERRED_LEFT_PANE_WIDTH
|
|
|
|
);
|
|
|
|
|
2021-09-09 16:29:01 +00:00
|
|
|
export const getPreferredReactionEmoji = createSelector(
|
|
|
|
getItems,
|
2021-09-09 23:47:30 +00:00
|
|
|
getEmojiSkinTone,
|
|
|
|
(state: Readonly<ItemsStateType>, skinTone: number): Array<string> =>
|
|
|
|
getPreferredReactionEmojiFromStoredValue(
|
|
|
|
state.preferredReactionEmoji,
|
|
|
|
skinTone
|
|
|
|
)
|
2021-09-09 16:29:01 +00:00
|
|
|
);
|
2022-06-20 18:26:31 +00:00
|
|
|
|
|
|
|
export const getHideMenuBar = createSelector(
|
|
|
|
getItems,
|
|
|
|
(state: ItemsStateType): boolean => Boolean(state['hide-menu-bar'])
|
|
|
|
);
|
2022-08-23 17:24:55 +00:00
|
|
|
|
|
|
|
export const getHasSetMyStoriesPrivacy = createSelector(
|
|
|
|
getItems,
|
|
|
|
(state: ItemsStateType): boolean => Boolean(state.hasSetMyStoriesPrivacy)
|
|
|
|
);
|
2022-08-31 16:11:14 +00:00
|
|
|
|
|
|
|
export const getHasReadReceiptSetting = createSelector(
|
|
|
|
getItems,
|
|
|
|
(state: ItemsStateType): boolean => Boolean(state['read-receipt-setting'])
|
|
|
|
);
|
2022-10-25 22:18:42 +00:00
|
|
|
|
|
|
|
export const getHasStoryViewReceiptSetting = createSelector(
|
|
|
|
getItems,
|
|
|
|
(state: ItemsStateType): boolean =>
|
|
|
|
Boolean(
|
|
|
|
state.storyViewReceiptsEnabled ?? state['read-receipt-setting'] ?? false
|
|
|
|
)
|
|
|
|
);
|
2023-01-18 23:31:10 +00:00
|
|
|
|
|
|
|
export const getRemoteBuildExpiration = createSelector(
|
|
|
|
getItems,
|
|
|
|
(state: ItemsStateType): number | undefined =>
|
2023-02-01 18:29:38 +00:00
|
|
|
state.remoteBuildExpiration === undefined
|
|
|
|
? undefined
|
|
|
|
: Number(state.remoteBuildExpiration)
|
2023-01-18 23:31:10 +00:00
|
|
|
);
|
|
|
|
|
|
|
|
export const getAutoDownloadUpdate = createSelector(
|
|
|
|
getItems,
|
|
|
|
(state: ItemsStateType): boolean =>
|
|
|
|
Boolean(state['auto-download-update'] ?? true)
|
|
|
|
);
|
2023-04-14 18:16:28 +00:00
|
|
|
|
|
|
|
export const getTextFormattingEnabled = createSelector(
|
|
|
|
getItems,
|
|
|
|
(state: ItemsStateType): boolean => Boolean(state.textFormatting ?? true)
|
|
|
|
);
|