fix leave opengroup button right panel, and add memberCount

This commit is contained in:
Audric Ackermann 2021-05-03 10:40:43 +10:00
parent 127b7d41fa
commit 3aa9ca785f
No known key found for this signature in database
GPG key ID: 999F434D76324AD4
12 changed files with 180 additions and 25 deletions

View file

@ -601,7 +601,7 @@
}
});
Whisper.events.on('leaveGroup', async groupConvo => {
Whisper.events.on('leaveClosedGroup', async groupConvo => {
if (appView) {
appView.showLeaveGroupDialog(groupConvo);
}

View file

@ -37,7 +37,7 @@
this.remove();
},
submit() {
this.convo.leaveGroup();
this.convo.leaveClosedGroup();
},
});
})();

View file

@ -178,7 +178,7 @@
window.confirmationDialog({
title,
message,
resolve: () => groupConvo.leaveGroup(),
resolve: () => groupConvo.leaveClosedGroup(),
theme: this.getThemeObject(),
});
} else {

View file

@ -61,7 +61,7 @@ describe('ConversationCollection', () => {
// type: 'group',
// id: '052d11d01e56d9bfc3d74115c33225a632321b509ac17a13fdeac71165d09b94ab',
// });
// await convo.leaveGroup();
// await convo.leaveClosedGroup();
// assert.notEqual(convo.messageCollection.length, 0);
// });
// it('has a title', () => {

View file

@ -384,7 +384,7 @@ export class SessionConversation extends React.Component<Props, State> {
conversation.copyPublicKey();
},
onLeaveGroup: () => {
window.Whisper.events.trigger('leaveGroup', conversation);
window.Whisper.events.trigger('leaveClosedGroup', conversation);
},
onInviteContacts: () => {
window.Whisper.events.trigger('inviteContacts', conversation);
@ -492,8 +492,9 @@ export class SessionConversation extends React.Component<Props, State> {
onInviteContacts: () => {
window.Whisper.events.trigger('inviteContacts', conversation);
},
onDeleteContact: conversation.deleteContact,
onLeaveGroup: () => {
window.Whisper.events.trigger('leaveGroup', conversation);
window.Whisper.events.trigger('leaveClosedGroup', conversation);
},
onAddModerators: () => {
window.Whisper.events.trigger('addModerators', conversation);

View file

@ -39,6 +39,7 @@ interface Props {
onGoBack: () => void;
onInviteContacts: () => void;
onLeaveGroup: () => void;
onDeleteContact: () => void;
onUpdateGroupName: () => void;
onAddModerators: () => void;
onRemoveModerators: () => void;
@ -218,6 +219,7 @@ class SessionRightPanel extends React.Component<Props, State> {
name,
timerOptions,
onLeaveGroup,
onDeleteContact,
isKickedFromGroup,
left,
isPublic,
@ -310,7 +312,7 @@ class SessionRightPanel extends React.Component<Props, State> {
buttonColor={SessionButtonColor.Danger}
disabled={isKickedFromGroup || left}
buttonType={SessionButtonType.SquareOutline}
onClick={onLeaveGroup}
onClick={isPublic ? onDeleteContact : onLeaveGroup}
/>
)}
</div>

View file

@ -419,7 +419,7 @@ export class ConversationModel extends Backbone.Model<ConversationAttributes> {
onCopyPublicKey: this.copyPublicKey,
onDeleteContact: this.deleteContact,
onLeaveGroup: () => {
window.Whisper.events.trigger('leaveGroup', this);
window.Whisper.events.trigger('leaveClosedGroup', this);
},
onDeleteMessages: this.deleteMessages,
onInviteContacts: () => {
@ -953,7 +953,7 @@ export class ConversationModel extends Backbone.Model<ConversationAttributes> {
return model;
}
public async leaveGroup() {
public async leaveClosedGroup() {
if (this.isMediumGroup()) {
await leaveClosedGroup(this.id);
} else {

View file

@ -419,7 +419,9 @@ export const getAllRoomInfos = async (roomInfos: OpenGroupV2Room) => {
return parseRooms(result);
};
export const getMemberCount = async (roomInfos: OpenGroupRequestCommonType): Promise<void> => {
export const getMemberCount = async (
roomInfos: OpenGroupRequestCommonType
): Promise<number | undefined> => {
const request: OpenGroupV2Request = {
method: 'GET',
room: roomInfos.roomId,
@ -438,18 +440,7 @@ export const getMemberCount = async (roomInfos: OpenGroupRequestCommonType): Pro
return;
}
const conversationId = getOpenGroupV2ConversationId(roomInfos.serverUrl, roomInfos.roomId);
const convo = ConversationController.getInstance().get(conversationId);
if (!convo) {
window.log.warn('cannot update conversation memberCount as it does not exist');
return;
}
if (convo.get('subscriberCount') !== count) {
convo.set({ subscriberCount: count });
// triggers the save to db and the refresh of the UI
await convo.commit();
}
return count;
};
/**

View file

@ -8,7 +8,7 @@ import { parseStatusCodeFromOnionRequest } from './OpenGroupAPIV2Parser';
import _ from 'lodash';
import { sendViaOnion } from '../../session/onions/onionSend';
import { OpenGroupMessageV2 } from './OpenGroupMessageV2';
import { downloadPreviewOpenGroupV2, getAuthToken } from './OpenGroupAPIV2';
import { downloadPreviewOpenGroupV2, getAuthToken, getMemberCount } from './OpenGroupAPIV2';
const COMPACT_POLL_ENDPOINT = 'compact_poll';
@ -72,6 +72,50 @@ export const getAllBase64AvatarForRooms = async (
return validPreviewBase64 ? validPreviewBase64 : null;
};
export const getAllMemberCount = async (
serverUrl: string,
rooms: Set<string>,
abortSignal: AbortSignal
): Promise<Array<ParsedMemberCount> | null> => {
// fetch all we need
const allValidRoomInfos = await getAllValidRoomInfos(serverUrl, rooms);
if (!allValidRoomInfos?.length) {
window.log.info('getAllMemberCount: no valid roominfos got.');
return null;
}
if (abortSignal.aborted) {
window.log.info('memberCount aborted, returning null');
return null;
}
// Currently this call will not abort if AbortSignal is aborted,
// but the call will return null.
const validMemberCount = _.compact(
await Promise.all(
allValidRoomInfos.map(async room => {
try {
const memberCount = await getMemberCount(room);
if (memberCount !== undefined) {
return {
roomId: room.roomId,
memberCount,
};
}
} catch (e) {
window.log.warn('getPreview failed for room', room);
}
return null;
})
)
);
if (abortSignal.aborted) {
window.log.info('getMemberCount aborted, returning null');
return null;
}
return validMemberCount ? validMemberCount : null;
};
/**
* This function fetches the valid roomInfos from the database.
* It also makes sure that the pubkey for all those rooms are the same, or returns null.
@ -258,6 +302,11 @@ export type ParsedBase64Avatar = {
base64: string;
};
export type ParsedMemberCount = {
roomId: string;
memberCount: number;
};
const parseCompactPollResult = async (
singleRoomResult: any,
serverUrl: string

View file

@ -5,8 +5,10 @@ import { OpenGroupRequestCommonType } from './ApiUtil';
import {
compactFetchEverything,
getAllBase64AvatarForRooms,
getAllMemberCount,
ParsedBase64Avatar,
ParsedDeletions,
ParsedMemberCount,
ParsedRoomCompactPollResults,
} from './OpenGroupAPIV2CompactPoll';
import _ from 'lodash';
@ -15,13 +17,14 @@ import { getMessageIdsFromServerIds, removeMessage } from '../../data/data';
import { getV2OpenGroupRoom, saveV2OpenGroupRoom } from '../../data/opengroups';
import { OpenGroupMessageV2 } from './OpenGroupMessageV2';
import { handleOpenGroupV2Message } from '../../receiver/receiver';
import { DAYS, SECONDS } from '../../session/utils/Number';
import { DAYS, MINUTES, SECONDS } from '../../session/utils/Number';
import autoBind from 'auto-bind';
import { sha256 } from '../../session/crypto';
import { fromBase64ToArrayBuffer } from '../../session/utils/String';
const pollForEverythingInterval = SECONDS * 6;
const pollForRoomAvatarInterval = DAYS * 1;
const pollForMemberCountInterval = MINUTES * 10;
/**
* An OpenGroupServerPollerV2 polls for everything for a particular server. We should
@ -31,10 +34,26 @@ const pollForRoomAvatarInterval = DAYS * 1;
* for this server.
*/
export class OpenGroupServerPoller {
/**
* The server url to poll for this opengroup poller.
* Remember, we have one poller per opengroup poller, no matter how many rooms we have joined on this same server
*/
private readonly serverUrl: string;
/**
* The set of rooms to poll from.
*
*/
private readonly roomIdsToPoll: Set<string> = new Set();
/**
* This timer is used to tick for compact Polling for this opengroup server
* It ticks every `pollForEverythingInterval` except.
* If the last run is still in progress, the new one won't start and just return.
*/
private pollForEverythingTimer?: NodeJS.Timeout;
private pollForRoomAvatarTimer?: NodeJS.Timeout;
private pollForMemberCountTimer?: NodeJS.Timeout;
private readonly abortController: AbortController;
/**
@ -45,6 +64,7 @@ export class OpenGroupServerPoller {
*/
private isPolling = false;
private isPreviewPolling = false;
private isMemberCountPolling = false;
private wasStopped = false;
constructor(roomInfos: Array<OpenGroupRequestCommonType>) {
@ -71,6 +91,10 @@ export class OpenGroupServerPoller {
this.previewPerRoomPoll,
pollForRoomAvatarInterval
);
this.pollForMemberCountTimer = global.setInterval(
this.pollForAllMemberCount,
pollForMemberCountInterval
);
}
/**
@ -90,6 +114,7 @@ export class OpenGroupServerPoller {
// if we are not already polling right now, trigger a polling
void this.compactPoll();
void this.previewPerRoomPoll();
void this.pollForAllMemberCount();
}
public removeRoomFromPoll(room: OpenGroupRequestCommonType) {
@ -120,6 +145,10 @@ export class OpenGroupServerPoller {
if (this.pollForRoomAvatarTimer) {
global.clearInterval(this.pollForRoomAvatarTimer);
}
if (this.pollForMemberCountTimer) {
global.clearInterval(this.pollForMemberCountTimer);
}
if (this.pollForEverythingTimer) {
// cancel next ticks for each timer
global.clearInterval(this.pollForEverythingTimer);
@ -128,6 +157,7 @@ export class OpenGroupServerPoller {
this.abortController?.abort();
this.pollForEverythingTimer = undefined;
this.pollForRoomAvatarTimer = undefined;
this.pollForMemberCountTimer = undefined;
this.wasStopped = true;
}
}
@ -162,6 +192,21 @@ export class OpenGroupServerPoller {
return true;
}
private shouldPollForMemberCount() {
if (this.wasStopped) {
window.log.error('Serverpoller was stopped. PolLForMemberCount should not happen');
return false;
}
if (!this.roomIdsToPoll.size) {
return false;
}
// return early if a poll is already in progress
if (this.isMemberCountPolling) {
return false;
}
return true;
}
private async previewPerRoomPoll() {
if (!this.shouldPollPreview()) {
return;
@ -201,6 +246,46 @@ export class OpenGroupServerPoller {
}
}
private async pollForAllMemberCount() {
if (!this.shouldPollForMemberCount()) {
return;
}
// do everything with throwing so we can check only at one place
// what we have to clean
try {
this.isMemberCountPolling = true;
// don't try to make the request if we are aborted
if (this.abortController.signal.aborted) {
throw new Error('Poller aborted');
}
let memberCountGotResults = await getAllMemberCount(
this.serverUrl,
this.roomIdsToPoll,
this.abortController.signal
);
// check that we are still not aborted
if (this.abortController.signal.aborted) {
throw new Error('Abort controller was canceled. Dropping memberCount request');
}
if (!memberCountGotResults) {
throw new Error('MemberCount: no results');
}
// we were not aborted, make sure to filter out roomIds we are not polling for anymore
memberCountGotResults = memberCountGotResults.filter(result =>
this.roomIdsToPoll.has(result.roomId)
);
// ==> At this point all those results need to trigger conversation updates, so update what we have to update
await handleAllMemberCount(this.serverUrl, memberCountGotResults);
} catch (e) {
window.log.warn('Got error while memberCount fetch:', e);
} finally {
this.isMemberCountPolling = false;
}
}
private async compactPoll() {
if (!this.shouldPoll()) {
return;
@ -396,3 +481,29 @@ const handleBase64AvatarUpdate = async (
})
);
};
async function handleAllMemberCount(
serverUrl: string,
memberCountGotResults: Array<ParsedMemberCount>
) {
if (!memberCountGotResults.length) {
return;
}
await Promise.all(
memberCountGotResults.map(async roomCount => {
const conversationId = getOpenGroupV2ConversationId(serverUrl, roomCount.roomId);
const convo = ConversationController.getInstance().get(conversationId);
if (!convo) {
window.log.warn('cannot update conversation memberCount as it does not exist');
return;
}
if (convo.get('subscriberCount') !== roomCount.memberCount) {
convo.set({ subscriberCount: roomCount.memberCount });
// triggers the save to db and the refresh of the UI
await convo.commit();
}
})
);
}

View file

@ -189,7 +189,7 @@ export class ConversationController {
// Close group leaving
if (conversation.isClosedGroup()) {
await conversation.leaveGroup();
await conversation.leaveClosedGroup();
} else if (conversation.isPublic() && !conversation.isOpenGroupV2()) {
const channelAPI = await conversation.getPublicSendData();
if (channelAPI === null) {

View file

@ -439,6 +439,7 @@ const sendOnionRequest = async (
const guardUrl = `https://${nodePath[0].ip}:${nodePath[0].port}${target}`;
// no logs for that one as we do need to call insecureNodeFetch to our guardNode
// window.log.info('insecureNodeFetch => plaintext for sendOnionRequest');
console.warn('sendViaOnion payload: ', payload.length);
const response = await insecureNodeFetch(guardUrl, guardFetchOptions);
return processOnionResponse(reqIdx, response, destCtx.symmetricKey, false, abortSignal);