feat: add the sending state to invite&promote actions

pull/2963/head
Audric Ackermann 1 year ago
parent 5e3f968d22
commit 392e243b08

@ -306,9 +306,11 @@
"groupOthersPromoted": "<b>$name$</b> and <b>$count$ others</b> were promoted to Admin.",
"inviteFailed": "Invite Failed",
"invitePending": "Invite Pending",
"inviteSent": "Invite Sent",
"inviteSending": "Sending Invite",
"promotionFailed": "Promotion Failed",
"promotionPending": "Promotion Pending",
"promotionSent": "Promotion Sent",
"promotionSending": "Sending Promotion",
"groupInviteFailedOne": "Failed to invite <b>$name$</b> to <b>$groupname$</b>",
"groupInviteFailedTwo": "Failed to invite <b>$first$</b> and <b>$second$</b> to <b>$groupname$</b>",
"groupInviteFailedOthers": "Failed to invite <b>$first$</b> and <b>$count$ others</b> to <b>$groupname$</b>",

@ -31,7 +31,7 @@ window.sessionFeatureFlags = {
useOnionRequests: true,
useTestNet: isTestNet(),
integrationTestEnv: isTestIntegration(),
useClosedGroupV3: true, // TODO DO NOT MERGE Remove after QA
useClosedGroupV2: true, // TODO DO NOT MERGE Remove after QA debugger
debug: {
debugLogging: !_.isEmpty(process.env.SESSION_DEBUG),
debugLibsessionDumps: !_.isEmpty(process.env.SESSION_DEBUG_LIBSESSION_DUMPS),

@ -9,9 +9,11 @@ import { GroupInvite } from '../session/utils/job_runners/jobs/GroupInviteJob';
import { GroupPromote } from '../session/utils/job_runners/jobs/GroupPromoteJob';
import {
useMemberInviteFailed,
useMemberInvitePending,
useMemberInviteSending,
useMemberInviteSent,
useMemberPromoteSending,
useMemberPromotionFailed,
useMemberPromotionPending,
useMemberPromotionSent,
} from '../state/selectors/groups';
import { Avatar, AvatarSize, CrownIcon } from './avatar/Avatar';
import { Flex } from './basic/Flex';
@ -129,24 +131,32 @@ const StyledGroupStatusText = styled.span<{ isFailure: boolean }>`
color: ${props => (props.isFailure ? 'var(--danger-color)' : 'var(--text-secondary-color)')};
font-size: var(--font-size-xs);
margin-top: var(--margins-xs);
min-width: 100px; // min-width so that the dialog does not resize when the status change to sending
text-align: left;
`;
const GroupStatusText = ({ groupPk, pubkey }: { pubkey: PubkeyType; groupPk: GroupPubkeyType }) => {
const groupInviteFailed = useMemberInviteFailed(pubkey, groupPk);
const groupPromotionFailed = useMemberPromotionFailed(pubkey, groupPk);
const groupPromotionSending = useMemberPromoteSending(groupPk, pubkey);
const groupInvitePending = useMemberInvitePending(pubkey, groupPk);
const groupPromotionPending = useMemberPromotionPending(pubkey, groupPk);
const groupInviteSent = useMemberInviteSent(pubkey, groupPk);
const groupPromotionSent = useMemberPromotionSent(pubkey, groupPk);
const groupInviteSending = useMemberInviteSending(groupPk, pubkey);
const statusText = groupPromotionFailed
? window.i18n('promotionFailed')
: groupInviteFailed
? window.i18n('inviteFailed')
: groupInvitePending
? window.i18n('invitePending')
: groupPromotionPending
? window.i18n('promotionPending')
: null;
: groupInviteSending
? window.i18n('inviteSending')
: groupPromotionSending
? window.i18n('promotionSending')
: groupInviteSent
? window.i18n('inviteSent')
: groupPromotionSent
? window.i18n('promotionSent')
: null;
if (!statusText) {
return null;
@ -208,7 +218,7 @@ const ResendPromoteButton = ({
buttonShape={SessionButtonShape.Square}
buttonType={SessionButtonType.Solid}
buttonColor={SessionButtonColor.Danger}
text="ReSEND PRomote"
text="PrOmOtE"
onClick={() => {
void GroupPromote.addJob({ groupPk, member: pubkey });
}}

@ -16,7 +16,7 @@ import { PubKey } from '../session/types';
import { UserUtils } from '../session/utils';
import { PropsForMessageWithoutConvoProps, lookupQuote } from '../state/ducks/conversations';
import { showMessageRequestBannerOutsideRedux } from '../state/ducks/userConfig';
import { getMemberInvitePendingOutsideRedux } from '../state/selectors/groups';
import { getMemberInviteSentOutsideRedux } from '../state/selectors/groups';
import { getHideMessageRequestBannerOutsideRedux } from '../state/selectors/userConfig';
import { GoogleChrome } from '../util';
import { LinkPreviews } from '../util/linkPreviews';
@ -262,8 +262,8 @@ async function handleMessageFromPendingMember(
return;
}
const isMemberInvitePending = getMemberInvitePendingOutsideRedux(source, convoId);
if (!isMemberInvitePending) {
const isMemberInviteSent = getMemberInviteSentOutsideRedux(source, convoId);
if (!isMemberInviteSent) {
return; // nothing else to do
}
// we are an admin and we received a message from a member whose invite is `pending`. Update that member state now and push a change.

@ -2,6 +2,7 @@ import { GroupPubkeyType, PubkeyType } from 'libsession_util_nodejs';
import { debounce, difference, isNumber } from 'lodash';
import { v4 } from 'uuid';
import { ToastUtils, UserUtils } from '../..';
import { groupInfoActions } from '../../../../state/ducks/metaGroups';
import {
MetaGroupWrapperActions,
UserGroupsWrapperActions,
@ -51,6 +52,9 @@ async function addJob({ groupPk, member }: JobExtraArgs) {
});
window.log.debug(`addGroupInviteJob: adding group invite for ${groupPk}:${member} `);
await runners.groupInviteJobRunner.addJob(groupInviteJob);
window?.inboxStore?.dispatch(
groupInfoActions.setInvitePending({ groupPk, pubkey: member, sending: true })
);
}
}
@ -151,6 +155,10 @@ class GroupInviteJob extends PersistedJob<GroupInvitePersistedData> {
failed = false;
}
} finally {
window?.inboxStore?.dispatch(
groupInfoActions.setInvitePending({ groupPk, pubkey: member, sending: false })
);
updateFailedStateForMember(groupPk, member, failed);
try {
await MetaGroupWrapperActions.memberSetInvited(groupPk, member, failed);

@ -2,6 +2,7 @@ import { GroupPubkeyType, PubkeyType } from 'libsession_util_nodejs';
import { isNumber } from 'lodash';
import { v4 } from 'uuid';
import { UserUtils } from '../..';
import { groupInfoActions } from '../../../../state/ducks/metaGroups';
import {
MetaGroupWrapperActions,
UserGroupsWrapperActions,
@ -43,6 +44,9 @@ async function addJob({ groupPk, member }: JobExtraArgs) {
});
window.log.debug(`addGroupPromoteJob: adding group promote for ${groupPk}:${member} `);
await runners.groupPromoteJobRunner.addJob(groupPromoteJob);
window?.inboxStore?.dispatch(
groupInfoActions.setPromotionPending({ groupPk, pubkey: member, sending: true })
);
}
}
@ -109,6 +113,9 @@ class GroupPromoteJob extends PersistedJob<GroupPromotePersistedData> {
failed = false;
}
} finally {
window?.inboxStore?.dispatch(
groupInfoActions.setPromotionPending({ groupPk, pubkey: member, sending: false })
);
try {
await MetaGroupWrapperActions.memberSetPromoted(groupPk, member, failed);
} catch (e) {

@ -1,5 +1,5 @@
/* eslint-disable no-await-in-loop */
import { createAsyncThunk, createSlice } from '@reduxjs/toolkit';
import { createAsyncThunk, createSlice, PayloadAction } from '@reduxjs/toolkit';
import {
GroupInfoGet,
GroupMemberGet,
@ -8,6 +8,7 @@ import {
Uint8ArrayLen64,
UserGroupsGet,
WithGroupPubkey,
WithPubkey,
} from 'libsession_util_nodejs';
import { base64_variants, from_base64 } from 'libsodium-wrappers-sumo';
import { intersection, isEmpty, uniq } from 'lodash';
@ -31,13 +32,13 @@ import { GroupUpdateMemberChangeMessage } from '../../session/messages/outgoing/
import { GroupUpdateDeleteMessage } from '../../session/messages/outgoing/controlMessage/group_v2/to_user/GroupUpdateDeleteMessage';
import { PubKey } from '../../session/types';
import { UserUtils } from '../../session/utils';
import { getUserED25519KeyPairBytes } from '../../session/utils/User';
import { PreConditionFailed } from '../../session/utils/errors';
import { RunJobResult } from '../../session/utils/job_runners/PersistedJob';
import { GroupInvite } from '../../session/utils/job_runners/jobs/GroupInviteJob';
import { GroupSync } from '../../session/utils/job_runners/jobs/GroupSyncJob';
import { UserSync } from '../../session/utils/job_runners/jobs/UserSyncJob';
import { RunJobResult } from '../../session/utils/job_runners/PersistedJob';
import { LibSessionUtil } from '../../session/utils/libsession/libsession_utils';
import { getUserED25519KeyPairBytes } from '../../session/utils/User';
import { stringify, toFixedUint8ArrayOfLength } from '../../types/sqlSharedTypes';
import {
getGroupPubkeyFromWrapperType,
@ -62,6 +63,8 @@ export type GroupState = {
creationFromUIPending: boolean;
memberChangesFromUIPending: boolean;
nameChangesFromUIPending: boolean;
membersInviteSending: Record<GroupPubkeyType, Array<PubkeyType>>;
membersPromoteSending: Record<GroupPubkeyType, Array<PubkeyType>>;
};
export const initialGroupState: GroupState = {
@ -70,6 +73,8 @@ export const initialGroupState: GroupState = {
creationFromUIPending: false,
memberChangesFromUIPending: false,
nameChangesFromUIPending: false,
membersInviteSending: {},
membersPromoteSending: {},
};
type GroupDetailsUpdate = {
@ -1186,13 +1191,64 @@ const currentDeviceGroupNameChange = createAsyncThunk(
}
);
function deleteGroupPkEntriesFromState(state: GroupState, groupPk: GroupPubkeyType) {
delete state.infos[groupPk];
delete state.members[groupPk];
delete state.membersInviteSending[groupPk];
delete state.membersPromoteSending[groupPk];
}
function applySendingStateChange({
groupPk,
pubkey,
sending,
state,
changeType,
}: WithGroupPubkey &
WithPubkey & { sending: boolean; changeType: 'invite' | 'promote'; state: GroupState }) {
if (changeType === 'invite' && !state.membersInviteSending[groupPk]) {
state.membersInviteSending[groupPk] = [];
} else if (changeType === 'promote' && !state.membersPromoteSending[groupPk]) {
state.membersPromoteSending[groupPk] = [];
}
const arrRef =
changeType === 'invite'
? state.membersInviteSending[groupPk]
: state.membersPromoteSending[groupPk];
const foundAt = arrRef.findIndex(p => p === pubkey);
if (sending && foundAt === -1) {
arrRef.push(pubkey);
return state;
}
if (!sending && foundAt >= 0) {
arrRef.splice(foundAt, 1);
}
return state;
}
/**
* This slice is the one holding the default joinable rooms fetched once in a while from the default opengroup v2 server.
*/
const metaGroupSlice = createSlice({
name: 'metaGroup',
initialState: initialGroupState,
reducers: {},
reducers: {
setInvitePending(
state: GroupState,
{ payload }: PayloadAction<{ sending: boolean } & WithGroupPubkey & WithPubkey>
) {
return applySendingStateChange({ changeType: 'invite', ...payload, state });
},
setPromotionPending(
state: GroupState,
{ payload }: PayloadAction<{ pubkey: PubkeyType; groupPk: GroupPubkeyType; sending: boolean }>
) {
return applySendingStateChange({ changeType: 'promote', ...payload, state });
},
},
extraReducers: builder => {
builder.addCase(initNewGroupInWrapper.fulfilled, (state, action) => {
const { groupPk, infos, members } = action.payload;
@ -1238,8 +1294,7 @@ const metaGroupSlice = createSlice({
`refreshGroupDetailsFromWrapper no details found, removing from slice: ${groupPk}}`
);
delete state.infos[groupPk];
delete state.members[groupPk];
deleteGroupPkEntriesFromState(state, groupPk);
}
return state;
});
@ -1249,8 +1304,7 @@ const metaGroupSlice = createSlice({
builder.addCase(destroyGroupDetails.fulfilled, (state, action) => {
const { groupPk } = action.payload;
// FIXME destroyGroupDetails marks the info as destroyed, but does not really remove the wrapper currently
delete state.infos[groupPk];
delete state.members[groupPk];
deleteGroupPkEntriesFromState(state, groupPk);
});
builder.addCase(destroyGroupDetails.rejected, (_state, action) => {
window.log.error('a destroyGroupDetails was rejected', action.error);
@ -1268,8 +1322,7 @@ const metaGroupSlice = createSlice({
`handleUserGroupUpdate no details found, removing from slice: ${groupPk}}`
);
delete state.infos[groupPk];
delete state.members[groupPk];
deleteGroupPkEntriesFromState(state, groupPk);
}
});
builder.addCase(handleUserGroupUpdate.rejected, (_state, action) => {
@ -1363,6 +1416,7 @@ export const groupInfoActions = {
inviteResponseReceived,
handleMemberLeftMessage,
currentDeviceGroupNameChange,
...metaGroupSlice.actions,
};
export const groupReducer = metaGroupSlice.reducer;

@ -5,6 +5,8 @@ import { GroupState } from '../ducks/metaGroups';
import { StateType } from '../reducer';
const getLibGroupsState = (state: StateType): GroupState => state.groups;
const getInviteSendingState = (state: StateType) => getLibGroupsState(state).membersInviteSending;
const getPromoteSendingState = (state: StateType) => getLibGroupsState(state).membersPromoteSending;
function getMembersOfGroup(state: StateType, convo?: string): Array<GroupMemberGet> {
if (!convo) {
@ -47,8 +49,9 @@ function getMemberInviteFailed(state: StateType, pubkey: PubkeyType, convo?: Gro
return findMemberInMembers(members, pubkey)?.inviteFailed || false;
}
function getMemberInvitePending(state: StateType, pubkey: PubkeyType, convo?: GroupPubkeyType) {
function getMemberInviteSent(state: StateType, pubkey: PubkeyType, convo?: GroupPubkeyType) {
const members = getMembersOfGroup(state, convo);
return findMemberInMembers(members, pubkey)?.invitePending || false;
}
@ -62,7 +65,7 @@ function getMemberPromotionFailed(state: StateType, pubkey: PubkeyType, convo?:
return findMemberInMembers(members, pubkey)?.promotionFailed || false;
}
function getMemberPromotionPending(state: StateType, pubkey: PubkeyType, convo?: GroupPubkeyType) {
function getMemberPromotionSent(state: StateType, pubkey: PubkeyType, convo?: GroupPubkeyType) {
const members = getMembersOfGroup(state, convo);
return findMemberInMembers(members, pubkey)?.promotionPending || false;
}
@ -110,12 +113,12 @@ export function getLibGroupAdminsOutsideRedux(convoId: string): Array<string> {
return state ? getLibAdminsPubkeys(state, convoId) : [];
}
export function getMemberInvitePendingOutsideRedux(
export function getMemberInviteSentOutsideRedux(
member: PubkeyType,
convoId: GroupPubkeyType
): boolean {
const state = window.inboxStore?.getState();
return state ? getMemberInvitePending(state, member, convoId) : false;
return state ? getMemberInviteSent(state, member, convoId) : false;
}
export function useIsCreatingGroupFromUIPending() {
@ -126,9 +129,10 @@ export function useMemberInviteFailed(member: PubkeyType, groupPk: GroupPubkeyTy
return useSelector((state: StateType) => getMemberInviteFailed(state, member, groupPk));
}
export function useMemberInvitePending(member: PubkeyType, groupPk: GroupPubkeyType) {
return useSelector((state: StateType) => getMemberInvitePending(state, member, groupPk));
export function useMemberInviteSent(member: PubkeyType, groupPk: GroupPubkeyType) {
return useSelector((state: StateType) => getMemberInviteSent(state, member, groupPk));
}
export function useMemberIsPromoted(member: PubkeyType, groupPk: GroupPubkeyType) {
return useSelector((state: StateType) => getMemberIsPromoted(state, member, groupPk));
}
@ -137,10 +141,32 @@ export function useMemberPromotionFailed(member: PubkeyType, groupPk: GroupPubke
return useSelector((state: StateType) => getMemberPromotionFailed(state, member, groupPk));
}
export function useMemberPromotionPending(member: PubkeyType, groupPk: GroupPubkeyType) {
return useSelector((state: StateType) => getMemberPromotionPending(state, member, groupPk));
export function useMemberPromotionSent(member: PubkeyType, groupPk: GroupPubkeyType) {
return useSelector((state: StateType) => getMemberPromotionSent(state, member, groupPk));
}
export function useMemberGroupChangePending() {
return useSelector(getIsMemberGroupChangePendingFromUI);
}
/**
* The selectors above are all deriving data from libsession.
* There is also some data that we only need in memory, not part of libsession (and so unsaved).
* An example is the "sending invite" or "sending promote" state of a member in a group.
*/
function useMembersInviteSending(groupPk: GroupPubkeyType) {
return useSelector((state: StateType) => getInviteSendingState(state)[groupPk] || []);
}
export function useMemberInviteSending(groupPk: GroupPubkeyType, memberPk: PubkeyType) {
return useMembersInviteSending(groupPk).includes(memberPk);
}
function useMembersPromoteSending(groupPk: GroupPubkeyType) {
return useSelector((state: StateType) => getPromoteSendingState(state)[groupPk] || []);
}
export function useMemberPromoteSending(groupPk: GroupPubkeyType, memberPk: PubkeyType) {
return useMembersPromoteSending(groupPk).includes(memberPk);
}

@ -270,7 +270,8 @@ export type LocalizerKeys =
| 'invalidSessionId'
| 'inviteContacts'
| 'inviteFailed'
| 'invitePending'
| 'inviteSending'
| 'inviteSent'
| 'join'
| 'joinACommunity'
| 'joinOpenGroup'
@ -415,7 +416,8 @@ export type LocalizerKeys =
| 'primaryColorYellow'
| 'privacySettingsTitle'
| 'promotionFailed'
| 'promotionPending'
| 'promotionSending'
| 'promotionSent'
| 'pruneSettingDescription'
| 'pruneSettingTitle'
| 'publicChatExists'

@ -99,7 +99,7 @@ async function checkIsUserConfigFeatureReleased() {
async function checkIsDisappearMessageV2FeatureReleased() {
return true;
// ((await checkIsFeatureReleased('disappearing_messages')) ||
// !!process.env.MULTI?.toLocaleLowerCase().includes('disappear_v2') // FIXME to remove after QA
// !!process.env.MULTI?.toLocaleLowerCase().includes('disappear_v2') // FIXME to remove after QA debugger
// );
}

Loading…
Cancel
Save