chore: disable a bunch of UI once legacy groups are deprecated

pull/3281/head
Audric Ackermann 3 months ago
parent e6b652a854
commit 5748fe5456
No known key found for this signature in database

@ -41,6 +41,7 @@ window.sessionFeatureFlags = {
useOnionRequests: true,
useTestNet: isTestNet() || isTestIntegration(),
useClosedGroupV2: true, // TODO DO NOT MERGE Remove after QA
forceLegacyGroupsDeprecated: false, // TODO DO NOT MERGE Remove after QA
useClosedGroupV2QAButtons: true, // TODO DO NOT MERGE Remove after QA
replaceLocalizedStringsWithKeys: false,
debug: {

@ -40,6 +40,7 @@ import { Storage } from '../util/storage';
import { UserGroupsWrapperActions } from '../webworker/workers/browser/libsession_worker_interface';
import { NoticeBanner } from './NoticeBanner';
import { Flex } from './basic/Flex';
import { initialReleasedFeaturesState } from '../state/ducks/releasedFeatures';
function makeLookup<T>(items: Array<T>, key: string): { [key: string]: T } {
// Yep, we can't index into item without knowing what it is. True. But we want to.
@ -88,6 +89,7 @@ async function createSessionInboxStore() {
settings: getSettingsInitialState(),
groups: initialGroupState,
userGroups: { userGroups },
releasedFeatures: initialReleasedFeaturesState,
};
return createStore(initialState);

@ -12,19 +12,21 @@ import {
} from '../../../state/selectors/selectedConversation';
import { Avatar, AvatarSize } from '../../avatar/Avatar';
import { SessionIconButton } from '../../icon';
import { useDisableLegacyGroupDeprecatedActions } from '../../../hooks/useRefreshReleasedFeaturesTimestamp';
export const AvatarHeader = (props: {
pubkey: string;
onAvatarClick?: (pubkey: string) => void;
}) => {
const { pubkey, onAvatarClick } = props;
const isDisabledLegacyGroupDeprecated = useDisableLegacyGroupDeprecatedActions(pubkey);
return (
<span className="module-conversation-header__avatar">
<Avatar
size={AvatarSize.S}
onAvatarClick={() => {
if (onAvatarClick) {
if (onAvatarClick && !isDisabledLegacyGroupDeprecated) {
onAvatarClick(pubkey);
}
}}

@ -18,6 +18,7 @@ import {
useSelectedSubscriberCount,
} from '../../../state/selectors/selectedConversation';
import { ConversationHeaderSubtitle } from './ConversationHeaderSubtitle';
import { useSelectedDisableLegacyGroupDeprecatedActions } from '../../../hooks/useRefreshReleasedFeaturesTimestamp';
export type SubtitleStrings = Record<string, string> & {
notifications?: string;
@ -63,6 +64,8 @@ export const ConversationHeaderTitle = (props: ConversationHeaderTitleProps) =>
const isGroup = useSelectedIsGroupOrCommunity();
const selectedMembersCount = useSelectedMembersCount();
const isDisabledLegacyGroupDeprecated = useSelectedDisableLegacyGroupDeprecatedActions();
const expirationMode = useSelectedConversationDisappearingMode();
const disappearingMessageSubtitle = useDisappearingMessageSettingText({
convoId,
@ -97,6 +100,9 @@ export const ConversationHeaderTitle = (props: ConversationHeaderTitleProps) =>
}, [i18n, isGroup, isKickedFromGroup, isPublic, selectedMembersCount, subscriberCount]);
const handleRightPanelToggle = () => {
if (isDisabledLegacyGroupDeprecated) {
return;
}
if (isRightPanelOn) {
dispatch(closeRightPanel());
return;

@ -13,6 +13,7 @@ import { getGenericReadableMessageSelectorProps } from '../../../../state/select
import { MessageContentWithStatuses } from '../message-content/MessageContentWithStatus';
import { StyledMessageReactionsContainer } from '../message-content/MessageReactions';
import { useIsMessageSelectionMode } from '../../../../state/selectors/selectedConversation';
import { useSelectedDisableLegacyGroupDeprecatedActions } from '../../../../hooks/useRefreshReleasedFeaturesTimestamp';
export type GenericReadableMessageSelectorProps = Pick<
MessageRenderingProps,
@ -65,6 +66,7 @@ export const GenericReadableMessage = (props: Props) => {
const { ctxMenuID, messageId } = props;
const [enableReactions, setEnableReactions] = useState(true);
const legacyGroupIsDeprecated = useSelectedDisableLegacyGroupDeprecatedActions();
const msgProps = useSelector((state: StateType) =>
getGenericReadableMessageSelectorProps(state, props.messageId)
@ -83,6 +85,9 @@ export const GenericReadableMessage = (props: Props) => {
const handleContextMenu = useCallback(
(e: MouseEvent<HTMLElement>) => {
if (legacyGroupIsDeprecated) {
return;
}
// this is quite dirty but considering that we want the context menu of the message to show on click on the attachment
// and the context menu save attachment item to save the right attachment I did not find a better way for now.
@ -108,7 +113,7 @@ export const GenericReadableMessage = (props: Props) => {
}
setIsRightClicked(enableContextMenu);
},
[ctxMenuID, multiSelectMode, msgProps?.isKickedFromGroup]
[ctxMenuID, multiSelectMode, msgProps?.isKickedFromGroup, legacyGroupIsDeprecated]
);
useEffect(() => {

@ -11,6 +11,7 @@ import { abbreviateNumber } from '../../../../util/abbreviateNumber';
import { nativeEmojiData } from '../../../../util/emoji';
import { popupXDefault, popupYDefault } from '../message-content/MessageReactions';
import { POPUP_WIDTH, ReactionPopup, TipPosition } from './ReactionPopup';
import { useSelectedDisableLegacyGroupDeprecatedActions } from '../../../../hooks/useRefreshReleasedFeaturesTimestamp';
const StyledReaction = styled.button<{
selected: boolean;
@ -79,6 +80,8 @@ export const Reaction = (props: ReactionProps) => {
} = props;
const rightOverlayMode = useRightOverlayMode();
const areDeprecatedLegacyGroupDisabled = useSelectedDisableLegacyGroupDeprecatedActions();
const legacyGroupDeprecated = useSelectedDisableLegacyGroupDeprecatedActions();
const isMessageSelection = useIsMessageSelectionMode();
const reactionsMap = (reactions && Object.fromEntries(reactions)) || {};
const senders = reactionsMap[emoji]?.senders || [];
@ -106,7 +109,8 @@ export const Reaction = (props: ReactionProps) => {
const handleReactionClick = () => {
if (!isMessageSelection) {
if (onClick) {
// Note: disable emoji clicks if the legacy group is deprecated (group is readonly)
if (onClick && !legacyGroupDeprecated) {
onClick(emoji);
}
}
@ -174,6 +178,9 @@ export const Reaction = (props: ReactionProps) => {
senders={reactionsMap[popupReaction]?.senders}
tooltipPosition={tooltipPosition}
onClick={() => {
if (areDeprecatedLegacyGroupDisabled) {
return;
}
if (handlePopupReaction) {
handlePopupReaction('');
}

@ -49,6 +49,7 @@ import { getIsModalVisible } from '../../state/selectors/modal';
import { ReleasedFeatures } from '../../util/releaseFeature';
import { MessageQueue } from '../../session/sending';
import { useRefreshReleasedFeaturesTimestamp } from '../../hooks/useRefreshReleasedFeaturesTimestamp';
const Section = (props: { type: SectionType }) => {
const ourNumber = useSelector(getOurNumber);
@ -297,6 +298,8 @@ export const ActionsPanel = () => {
void triggerAvatarReUploadIfNeeded();
}, DURATION.DAYS * 1);
useRefreshReleasedFeaturesTimestamp();
if (!ourPrimaryConversation) {
window?.log?.warn('ActionsPanel: ourPrimaryConversation is not set');
return null;

@ -33,7 +33,11 @@ import { ItemWithDataTestId } from './items/MenuItemWithDataTestId';
import { getMenuAnimation } from './MenuAnimation';
import { LeaveCommunityMenuItem } from './items/LeaveCommunity/LeaveCommunityMenuItem';
import { LeaveGroupMenuItem } from './items/LeaveAndDeleteGroup/LeaveGroupMenuItem';
import { DeleteGroupMenuItem } from './items/LeaveAndDeleteGroup/DeleteGroupMenuItem';
import {
DeleteDeprecatedLegacyGroupMenuItem,
DeleteGroupMenuItem,
} from './items/LeaveAndDeleteGroup/DeleteGroupMenuItem';
import { useDisableLegacyGroupDeprecatedActions } from '../../hooks/useRefreshReleasedFeaturesTimestamp';
export type PropsContextConversationItem = {
triggerId: string;
@ -45,10 +49,22 @@ const ConversationListItemContextMenu = (props: PropsContextConversationItem) =>
const convoIdFromContext = useConvoIdFromContext();
const disabledLegacyGroup = useDisableLegacyGroupDeprecatedActions(convoIdFromContext);
if (isSearching) {
return null;
}
if (disabledLegacyGroup) {
return (
<SessionContextMenuContainer>
<Menu id={triggerId} animation={getMenuAnimation()}>
<DeleteDeprecatedLegacyGroupMenuItem />
</Menu>
</SessionContextMenuContainer>
);
}
return (
<SessionContextMenuContainer>
<Menu id={triggerId} animation={getMenuAnimation()}>

@ -12,6 +12,7 @@ import { useIsMessageRequestOverlayShown } from '../../../../state/selectors/sec
import { ItemWithDataTestId } from '../MenuItemWithDataTestId';
import { showDeleteGroupItem } from './guard';
import { Localizer } from '../../../basic/Localizer';
import { useDisableLegacyGroupDeprecatedActions } from '../../../../hooks/useRefreshReleasedFeaturesTimestamp';
export const DeleteGroupMenuItem = () => {
const convoId = useConvoIdFromContext();
@ -46,3 +47,26 @@ export const DeleteGroupMenuItem = () => {
</ItemWithDataTestId>
);
};
export const DeleteDeprecatedLegacyGroupMenuItem = () => {
const convoId = useConvoIdFromContext();
const username = useConversationUsername(convoId) || convoId;
const shortCircuitDeleteDeprecatedGroup = useDisableLegacyGroupDeprecatedActions(convoId);
if (!shortCircuitDeleteDeprecatedGroup) {
return null;
}
const token = 'groupDelete';
return (
<ItemWithDataTestId
onClick={() => {
void showDeleteGroupByConvoId(convoId, username);
}}
>
<Localizer token={token} />
</ItemWithDataTestId>
);
};

@ -0,0 +1,46 @@
import useInterval from 'react-use/lib/useInterval';
import { useDispatch, useSelector } from 'react-redux';
import { DURATION } from '../session/constants';
import { updateLegacyGroupDeprecationTimestampUpdatedAt } from '../state/ducks/releasedFeatures';
import { NetworkTime } from '../util/NetworkTime';
import { PubKey } from '../session/types';
import { areLegacyGroupsDeprecatedYet } from '../state/selectors/releasedFeatures';
import { useSelectedConversationKey } from '../state/selectors/selectedConversation';
import type { StateType } from '../state/reducer';
import { ConversationTypeEnum } from '../models/types';
export function useRefreshReleasedFeaturesTimestamp() {
const dispatch = useDispatch();
useInterval(() => {
const nowFromNetwork = NetworkTime.now();
dispatch(updateLegacyGroupDeprecationTimestampUpdatedAt(nowFromNetwork));
}, 1 * DURATION.SECONDS);
}
export function getDisableLegacyGroupDeprecatedActions(state: StateType, convoId?: string) {
if (!convoId || !PubKey.is05Pubkey(convoId)) {
return false;
}
const selectedConvoIsGroup =
state.conversations.conversationLookup[convoId]?.type === ConversationTypeEnum.GROUP;
if (!selectedConvoIsGroup) {
return false;
}
const legacyGroupDeprecated = areLegacyGroupsDeprecatedYet();
// here we have
// - a valid convoId
// - that starts with 05
// - that is a group (i.e. a legacy group)
// - and legacy group deprecation date has been hit
return legacyGroupDeprecated;
}
export function useDisableLegacyGroupDeprecatedActions(convoId?: string) {
return useSelector((state: StateType) => getDisableLegacyGroupDeprecatedActions(state, convoId));
}
export function useSelectedDisableLegacyGroupDeprecatedActions() {
const convoId = useSelectedConversationKey();
return useDisableLegacyGroupDeprecatedActions(convoId);
}

@ -58,6 +58,7 @@ import {
} from './types';
import { ConversationTypeEnum } from '../../../models/types';
import { Snode } from '../../../data/types';
import { areLegacyGroupsDeprecatedYetOutsideRedux } from '../../../state/selectors/releasedFeatures';
const minMsgCountShouldRetry = 95;
/**
@ -296,11 +297,15 @@ export class SwarmPolling {
.filter(m => !allGroupsInWrapper.some(w => w.pubkeyHex === m.pubkey.key))
.map(entryToKey);
const allLegacyGroupsTracked = legacyGroups
.filter(m => this.shouldPollByTimeout(m)) // should we poll from it depending on this group activity?
.filter(m => allGroupsLegacyInWrapper.some(w => w.pubkeyHex === m.pubkey.key)) // we don't poll from legacy groups which are not in the user group wrapper
.map(m => m.pubkey.key) // extract the pubkey
.map(m => [m, ConversationTypeEnum.GROUP] as PollForLegacy); //
const legacyGroupDeprecatedDisabled = areLegacyGroupsDeprecatedYetOutsideRedux();
const allLegacyGroupsTracked = legacyGroupDeprecatedDisabled
? []
: legacyGroups
.filter(m => this.shouldPollByTimeout(m)) // should we poll from it depending on this group activity?
.filter(m => allGroupsLegacyInWrapper.some(w => w.pubkeyHex === m.pubkey.key)) // we don't poll from legacy groups which are not in the user group wrapper
.map(m => m.pubkey.key) // extract the pubkey
.map(m => [m, ConversationTypeEnum.GROUP] as PollForLegacy); //
toPollDetails = concat(toPollDetails, allLegacyGroupsTracked);
const allGroupsTracked = groups

@ -0,0 +1,25 @@
import { createSlice, type PayloadAction } from '@reduxjs/toolkit';
export const LEGACY_GROUP_DEPRECATED_TIMESTAMP_MS = Date.now() + 10 * 1000;
export interface ReleasedFeaturesState {
legacyGroupDeprecationTimestampRefreshAtMs: number;
}
export const initialReleasedFeaturesState = {
legacyGroupDeprecationTimestampRefreshAtMs: Date.now(),
};
const releasedFeaturesSlice = createSlice({
name: 'releasedFeatures',
initialState: initialReleasedFeaturesState,
reducers: {
updateLegacyGroupDeprecationTimestampUpdatedAt: (state, action: PayloadAction<number>) => {
state.legacyGroupDeprecationTimestampRefreshAtMs = action.payload;
},
},
});
const { actions, reducer } = releasedFeaturesSlice;
export const { updateLegacyGroupDeprecationTimestampUpdatedAt } = actions;
export const releasedFeaturesReducer = reducer;

@ -21,6 +21,7 @@ import {
} from './ducks/stagedAttachments';
import { userConfigReducer as userConfig, UserConfigState } from './ducks/userConfig';
import { userGroupReducer, UserGroupState } from './ducks/userGroups';
import { releasedFeaturesReducer, ReleasedFeaturesState } from './ducks/releasedFeatures';
export type StateType = {
search: SearchStateType;
@ -39,6 +40,7 @@ export type StateType = {
settings: SettingsState;
groups: GroupState;
userGroups: UserGroupState;
releasedFeatures: ReleasedFeaturesState;
};
const reducers = {
@ -58,6 +60,7 @@ const reducers = {
settings: settingsReducer,
groups: groupReducer,
userGroups: userGroupReducer,
releasedFeatures: releasedFeaturesReducer,
};
// Making this work would require that our reducer signature supported AnyAction, not

@ -9,6 +9,7 @@ import * as StagedAttachmentSelectors from './stagedAttachments';
import * as ThemeSelectors from './theme';
import * as UserSelectors from './user';
import * as UserConfigSelectors from './userConfig';
import * as ReleasedFeaturesSelectors from './releasedFeatures';
export {
CallSelectors,
@ -22,6 +23,7 @@ export {
ThemeSelectors,
UserConfigSelectors,
UserSelectors,
ReleasedFeaturesSelectors,
};
export * from './messages';

@ -0,0 +1,21 @@
import { useSelector } from 'react-redux';
import { NetworkTime } from '../../util/NetworkTime';
import { LEGACY_GROUP_DEPRECATED_TIMESTAMP_MS } from '../ducks/releasedFeatures';
export const areLegacyGroupsDeprecatedYet = (): boolean => {
const theyAreDeprecated = NetworkTime.now() >= LEGACY_GROUP_DEPRECATED_TIMESTAMP_MS;
return window.sessionFeatureFlags.forceLegacyGroupsDeprecated || theyAreDeprecated;
};
export function areLegacyGroupsDeprecatedYetOutsideRedux() {
if (!window.inboxStore) {
return false;
}
return areLegacyGroupsDeprecatedYet();
}
export function useAreLegacyGroupsDeprecatedYet() {
return useSelector(areLegacyGroupsDeprecatedYet);
}

@ -18,6 +18,7 @@ import {
import { getLibMembersPubkeys, useLibGroupName } from './groups';
import { getCanWrite, getModerators, getSubscriberCount } from './sogsRoomInfo';
import { getLibGroupDestroyed, getLibGroupKicked, useLibGroupDestroyed } from './userGroups';
import { getDisableLegacyGroupDeprecatedActions } from '../../hooks/useRefreshReleasedFeaturesTimestamp';
const getIsSelectedPrivate = (state: StateType): boolean => {
return Boolean(getSelectedConversation(state)?.isPrivate) || false;
@ -74,13 +75,20 @@ export function getSelectedCanWrite(state: StateType) {
const isBlindedAndDisabledMsgRequests = getSelectedBlindedDisabledMsgRequests(state); // true if isPrivate, blinded and explicitly disabled msgreq
return !(
isBlocked ||
isKickedFromGroup ||
isSelectedGroupKicked ||
isSelectedGroupDestroyed ||
readOnlySogs ||
isBlindedAndDisabledMsgRequests
const disabledLegacyGroupWrite = getDisableLegacyGroupDeprecatedActions(
state,
selectedConvoPubkey
);
return (
!(
isBlocked ||
isKickedFromGroup ||
isSelectedGroupKicked ||
isSelectedGroupDestroyed ||
readOnlySogs ||
isBlindedAndDisabledMsgRequests
) && !disabledLegacyGroupWrite
);
}

@ -109,6 +109,7 @@ function isDisappearMessageV2FeatureReleasedCached(): boolean {
return !!isDisappearingMessageFeatureReleased;
}
export const ReleasedFeatures = {
checkIsUserConfigFeatureReleased,
checkIsDisappearMessageV2FeatureReleased,

1
ts/window.d.ts vendored

@ -103,6 +103,7 @@ declare global {
useTestNet: boolean;
useClosedGroupV2: boolean;
useClosedGroupV2QAButtons: boolean;
forceLegacyGroupsDeprecated: boolean;
replaceLocalizedStringsWithKeys: boolean;
debug: {
debugLogging: boolean;

Loading…
Cancel
Save