diff --git a/package.json b/package.json index 1f3baec5d..edd312fab 100644 --- a/package.json +++ b/package.json @@ -92,7 +92,7 @@ "fs-extra": "9.0.0", "glob": "10.3.10", "image-type": "^4.1.0", - "libsession_util_nodejs": "https://github.com/session-foundation/libsession-util-nodejs/releases/download/v0.4.15/libsession_util_nodejs-v0.4.15.tar.gz", + "libsession_util_nodejs": "https://github.com/session-foundation/libsession-util-nodejs/releases/download/v0.4.16/libsession_util_nodejs-v0.4.16.tar.gz", "libsodium-wrappers-sumo": "^0.7.9", "linkify-it": "^4.0.1", "lodash": "^4.17.21", diff --git a/preload.js b/preload.js index b22c7c8ec..d10341156 100644 --- a/preload.js +++ b/preload.js @@ -51,7 +51,7 @@ window.getUserKeys = async () => { window.sessionFeatureFlags = { useOnionRequests: true, useTestNet: isTestNet() || isTestIntegration(), - useClosedGroupV2: true, // TODO DO NOT MERGE Remove after QA + useClosedGroupV2: false, // 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, diff --git a/ts/components/conversation/SessionConversation.tsx b/ts/components/conversation/SessionConversation.tsx index e0623aba2..17d72465b 100644 --- a/ts/components/conversation/SessionConversation.tsx +++ b/ts/components/conversation/SessionConversation.tsx @@ -69,6 +69,7 @@ import { useSelectedWeAreAdmin, } from '../../state/selectors/selectedConversation'; import { useSelectedDisableLegacyGroupDeprecatedActions } from '../../hooks/useRefreshReleasedFeaturesTimestamp'; +import { useAreGroupsCreatedAsNewGroupsYet } from '../../state/selectors/releasedFeatures'; const DEFAULT_JPEG_QUALITY = 0.85; @@ -675,6 +676,8 @@ function OutdatedLegacyGroupBanner() { const isLegacyGroup = !isPrivate && !isPublic && selectedConversationKey && selectedConversationKey.startsWith('05'); + const newGroupsCanBeCreated = useAreGroupsCreatedAsNewGroupsYet(); + // FIXME change the date here. Remove after QA const text = deprecatedLegacyGroups ? localize( @@ -686,7 +689,7 @@ function OutdatedLegacyGroupBanner() { .withArgs({ date: '[Date]' }) .toString(); - return isLegacyGroup ? ( + return isLegacyGroup && newGroupsCanBeCreated ? ( { diff --git a/ts/components/conversation/header/ConversationHeader.tsx b/ts/components/conversation/header/ConversationHeader.tsx index fe4433d39..935c8fdce 100644 --- a/ts/components/conversation/header/ConversationHeader.tsx +++ b/ts/components/conversation/header/ConversationHeader.tsx @@ -25,6 +25,7 @@ import { groupInfoActions } from '../../../state/ducks/metaGroups'; import { updateConfirmModal } from '../../../state/ducks/modalDialog'; import { setLeftOverlayMode } from '../../../state/ducks/section'; import { SessionButtonColor, SessionButton } from '../../basic/SessionButton'; +import { useAreGroupsCreatedAsNewGroupsYet } from '../../../state/selectors/releasedFeatures'; export const ConversationHeaderWithDetails = () => { const isSelectionMode = useIsMessageSelectionMode(); @@ -121,8 +122,9 @@ function RecreateGroupButton() { const weAreAdmin = useSelectedWeAreAdmin(); const showRecreateGroupModal = useShowRecreateModal(); + const newGroupsCanBeCreated = useAreGroupsCreatedAsNewGroupsYet(); - if (!isLegacyGroup || !weAreAdmin) { + if (!isLegacyGroup || !weAreAdmin || !newGroupsCanBeCreated) { return null; } diff --git a/ts/components/leftpane/LeftPaneMessageSection.tsx b/ts/components/leftpane/LeftPaneMessageSection.tsx index 249dcfb22..8381dd83f 100644 --- a/ts/components/leftpane/LeftPaneMessageSection.tsx +++ b/ts/components/leftpane/LeftPaneMessageSection.tsx @@ -20,6 +20,7 @@ import { OverlayInvite } from './overlay/OverlayInvite'; import { OverlayMessage } from './overlay/OverlayMessage'; import { OverlayMessageRequest } from './overlay/OverlayMessageRequest'; import { OverlayChooseAction } from './overlay/choose-action/OverlayChooseAction'; +import { useAreGroupsCreatedAsNewGroupsYet } from '../../state/selectors/releasedFeatures'; const StyledLeftPaneContent = styled.div` display: flex; @@ -40,17 +41,15 @@ const StyledConversationListContent = styled.div` const ClosableOverlay = () => { const leftOverlayMode = useSelector(getLeftOverlayMode); + const shouldCreateNewGroups = useAreGroupsCreatedAsNewGroupsYet(); + switch (leftOverlayMode) { case 'choose-action': return ; case 'open-group': return ; case 'closed-group': - return window.sessionFeatureFlags.useClosedGroupV2 ? ( - - ) : ( - - ); + return shouldCreateNewGroups ? : ; case 'message': return ; case 'message-requests': diff --git a/ts/components/leftpane/overlay/choose-action/OverlayChooseAction.tsx b/ts/components/leftpane/overlay/choose-action/OverlayChooseAction.tsx index 6b6badd30..b589c4b5a 100644 --- a/ts/components/leftpane/overlay/choose-action/OverlayChooseAction.tsx +++ b/ts/components/leftpane/overlay/choose-action/OverlayChooseAction.tsx @@ -3,15 +3,19 @@ import { useCallback, useEffect } from 'react'; import { isEmpty, isString } from 'lodash'; import { useDispatch } from 'react-redux'; import useKey from 'react-use/lib/useKey'; +import useUpdate from 'react-use/lib/useUpdate'; import { resetLeftOverlayMode, setLeftOverlayMode } from '../../../../state/ducks/section'; import { SpacerSM } from '../../../basic/Text'; import { StyledLeftPaneOverlay } from '../OverlayMessage'; import { ActionRow, StyledActionRowContainer } from './ActionRow'; import { ContactsListWithBreaks } from './ContactsListWithBreaks'; import { groupInfoActions } from '../../../../state/ducks/metaGroups'; +import { SessionToggle } from '../../../basic/SessionToggle'; export const OverlayChooseAction = () => { const dispatch = useDispatch(); + const forceRefresh = useUpdate(); + function closeOverlay() { dispatch(resetLeftOverlayMode()); } @@ -78,6 +82,19 @@ export const OverlayChooseAction = () => { onClick={openNewMessage} dataTestId="chooser-new-conversation-button" /> + {window.sessionFeatureFlags.useClosedGroupV2QAButtons ? ( + + Create group v2?{' '} + { + window.sessionFeatureFlags.useClosedGroupV2 = + !window.sessionFeatureFlags.useClosedGroupV2; + forceRefresh(); + }} + /> + + ) : null} !allGroupsInWrapper.some(w => w.pubkeyHex === m.pubkey.key)) .map(entryToKey); - const legacyGroupDeprecatedDisabled = areLegacyGroupsDeprecatedYetOutsideRedux(); + const legacyGroupDeprecatedDisabled = areLegacyGroupsReadOnlyOutsideRedux(); const allLegacyGroupsTracked = legacyGroupDeprecatedDisabled ? [] diff --git a/ts/session/constants.ts b/ts/session/constants.ts index 0ef7e7486..b7a2066b0 100644 --- a/ts/session/constants.ts +++ b/ts/session/constants.ts @@ -99,9 +99,24 @@ export const REACT_LIMIT = 6; export const UPDATER_INTERVAL_MS = 10 * DURATION.MINUTES; +// update this to be when we ship desktop groups REMOVE AFTER QA +const GROUP_DESKTOP_RELEASE = 1767225600000; // currently 1st Jan 2026 + +/** + * 3+7 days after the release of groups (more or less), we force new groups to be created as new groups + */ +const START_CREATE_NEW_GROUP = GROUP_DESKTOP_RELEASE + DURATION.DAYS * 10; + +/** + * 2 weeks after `START_CREATE_NEW_GROUP`, we mark legacy groups readonly + */ +const LEGACY_GROUP_READONLY = START_CREATE_NEW_GROUP + DURATION.WEEKS * 2; + export const FEATURE_RELEASE_TIMESTAMPS = { DISAPPEARING_MESSAGES_V2: 1710284400000, // 13/03/2024 10:00 Melbourne time USER_CONFIG: 1690761600000, // Monday July 31st at 10am Melbourne time + START_CREATE_NEW_GROUP, + LEGACY_GROUP_READONLY, }; export const ONBOARDING_TIMES = { diff --git a/ts/state/ducks/metaGroups.ts b/ts/state/ducks/metaGroups.ts index ff8d3a482..bf6fcc97c 100644 --- a/ts/state/ducks/metaGroups.ts +++ b/ts/state/ducks/metaGroups.ts @@ -59,8 +59,6 @@ export type GroupState = { members: Record>; memberChangesFromUIPending: boolean; nameChangesFromUIPending: boolean; - membersInviteSending: Record>; - membersPromoteSending: Record>; // those are group creation-related fields creationFromUIPending: boolean; @@ -74,8 +72,6 @@ export const initialGroupState: GroupState = { creationFromUIPending: false, memberChangesFromUIPending: false, nameChangesFromUIPending: false, - membersInviteSending: {}, - membersPromoteSending: {}, creationMembersSelected: [], creationGroupName: '', }; @@ -406,6 +402,10 @@ const loadMetaDumpsFromDB = createAsyncThunk( metaDumped: data, }); + // If we were sending to that member an invite/promote, we won't auto retry. + // We need to reset the sending state (on load from disk) so that the user can resend manually if needed + await MetaGroupWrapperActions.memberResetAllSendingState(groupPk); + const infos = await MetaGroupWrapperActions.infoGet(groupPk); const members = await MetaGroupWrapperActions.memberGetAll(groupPk); @@ -680,6 +680,9 @@ async function handleMemberAddedFromUI({ updateMessagesToPush.push(groupChange); } } + await LibSessionUtil.saveDumpsToDb(groupPk); + refreshConvosModelProps([groupPk]); + window.inboxStore?.dispatch(refreshGroupDetailsFromWrapper({ groupPk }) as any); const extraStoreRequests = await StoreGroupRequestFactory.makeGroupMessageSubRequest( updateMessagesToPush, @@ -1281,8 +1284,6 @@ const metaGroupSlice = createSlice({ ) { delete state.infos[payload.groupPk]; delete state.members[payload.groupPk]; - delete state.membersInviteSending[payload.groupPk]; - delete state.membersPromoteSending[payload.groupPk]; }, addSelectedGroupMember( state: GroupState, diff --git a/ts/state/ducks/releasedFeatures.tsx b/ts/state/ducks/releasedFeatures.tsx index 346ec31cb..a6e1ea138 100644 --- a/ts/state/ducks/releasedFeatures.tsx +++ b/ts/state/ducks/releasedFeatures.tsx @@ -1,15 +1,17 @@ import { createSlice, type PayloadAction } from '@reduxjs/toolkit'; -import { DURATION } from '../../session/constants'; - -// FIXME update this to the correct timestamp REMOVE AFTER QA -export const LEGACY_GROUP_DEPRECATED_TIMESTAMP_MS = Date.now() + DURATION.WEEKS * 52; +import { NetworkTime } from '../../util/NetworkTime'; +import { FEATURE_RELEASE_TIMESTAMPS } from '../../session/constants'; export interface ReleasedFeaturesState { legacyGroupDeprecationTimestampRefreshAtMs: number; + canCreateGroupV2: boolean; + legacyGroupsReadOnly: boolean; } export const initialReleasedFeaturesState = { legacyGroupDeprecationTimestampRefreshAtMs: Date.now(), + canCreateGroupV2: Date.now() >= FEATURE_RELEASE_TIMESTAMPS.START_CREATE_NEW_GROUP, + legacyGroupsReadOnly: Date.now() >= FEATURE_RELEASE_TIMESTAMPS.LEGACY_GROUP_READONLY, }; const releasedFeaturesSlice = createSlice({ @@ -18,6 +20,11 @@ const releasedFeaturesSlice = createSlice({ reducers: { updateLegacyGroupDeprecationTimestampUpdatedAt: (state, action: PayloadAction) => { state.legacyGroupDeprecationTimestampRefreshAtMs = action.payload; + state.canCreateGroupV2 = + NetworkTime.now() >= FEATURE_RELEASE_TIMESTAMPS.START_CREATE_NEW_GROUP; + state.legacyGroupsReadOnly = + NetworkTime.now() >= FEATURE_RELEASE_TIMESTAMPS.LEGACY_GROUP_READONLY; + return state; }, }, }); diff --git a/ts/state/selectors/releasedFeatures.ts b/ts/state/selectors/releasedFeatures.ts index e10b14219..3d5ccf243 100644 --- a/ts/state/selectors/releasedFeatures.ts +++ b/ts/state/selectors/releasedFeatures.ts @@ -1,15 +1,29 @@ -import { NetworkTime } from '../../util/NetworkTime'; -import { LEGACY_GROUP_DEPRECATED_TIMESTAMP_MS } from '../ducks/releasedFeatures'; +import { useSelector } from 'react-redux'; +import type { StateType } from '../reducer'; -export const areLegacyGroupsDeprecatedYet = (): boolean => { - const theyAreDeprecated = NetworkTime.now() >= LEGACY_GROUP_DEPRECATED_TIMESTAMP_MS; +const areGroupsCreatedAsNewGroupsYet = (): boolean => { + const shouldCreateNewGroups = !!window.inboxStore?.getState()?.releasedFeatures.canCreateGroupV2; - return window.sessionFeatureFlags.forceLegacyGroupsDeprecated || theyAreDeprecated; + return window.sessionFeatureFlags.useClosedGroupV2 || shouldCreateNewGroups; }; -export function areLegacyGroupsDeprecatedYetOutsideRedux() { +export const areLegacyGroupsReadOnly = (): boolean => { + const theyAre = !!window.inboxStore?.getState()?.releasedFeatures.legacyGroupsReadOnly; + + return window.sessionFeatureFlags.forceLegacyGroupsDeprecated || theyAre; +}; + +export function useAreGroupsCreatedAsNewGroupsYet() { + useSelector((state: StateType) => state.releasedFeatures.canCreateGroupV2); + return useSelector(areGroupsCreatedAsNewGroupsYet); +} + +/** + * @returns true if legacy groups should not be polled anymore + */ +export function areLegacyGroupsReadOnlyOutsideRedux() { if (!window.inboxStore) { return false; } - return areLegacyGroupsDeprecatedYet(); + return areLegacyGroupsReadOnly(); } diff --git a/ts/webworker/workers/browser/libsession_worker_interface.ts b/ts/webworker/workers/browser/libsession_worker_interface.ts index b178ffc5c..1e74e350f 100644 --- a/ts/webworker/workers/browser/libsession_worker_interface.ts +++ b/ts/webworker/workers/browser/libsession_worker_interface.ts @@ -674,6 +674,10 @@ export const MetaGroupWrapperActions: MetaGroupWrapperActionsCalls = { pubkeyHex, profilePicture, ]) as Promise>, + memberResetAllSendingState: async (groupPk: GroupPubkeyType) => + callLibSessionWorker([`MetaGroupConfig-${groupPk}`, 'memberResetAllSendingState']) as Promise< + ReturnType + >, /** GroupKeys wrapper specific actions */ keyRekey: async (groupPk: GroupPubkeyType) => diff --git a/yarn.lock b/yarn.lock index 3d7426f0d..866292a61 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4944,9 +4944,9 @@ levn@~0.3.0: prelude-ls "~1.1.2" type-check "~0.3.2" -"libsession_util_nodejs@https://github.com/session-foundation/libsession-util-nodejs/releases/download/v0.4.15/libsession_util_nodejs-v0.4.15.tar.gz": - version "0.4.15" - resolved "https://github.com/session-foundation/libsession-util-nodejs/releases/download/v0.4.15/libsession_util_nodejs-v0.4.15.tar.gz#de0e90e14327e60d81d2a6941bcd0af33fcfed82" +"libsession_util_nodejs@https://github.com/session-foundation/libsession-util-nodejs/releases/download/v0.4.16/libsession_util_nodejs-v0.4.16.tar.gz": + version "0.4.16" + resolved "https://github.com/session-foundation/libsession-util-nodejs/releases/download/v0.4.16/libsession_util_nodejs-v0.4.16.tar.gz#253d4d02388b5bfb41f24c88fae5061b137ca615" dependencies: cmake-js "7.2.1" node-addon-api "^6.1.0"