You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
session-desktop/ts/opengroup/opengroupV2/OpenGroupManagerV2.ts

212 lines
7.0 KiB
TypeScript

import {
getAllOpenGroupV2Conversations,
getAllV2OpenGroupRooms,
OpenGroupV2Room,
removeV2OpenGroupRoom,
saveV2OpenGroupRoom,
} from '../../data/opengroups';
import { ConversationModel, ConversationTypeEnum } from '../../models/conversation';
import { ConversationController } from '../../session/conversations';
import { allowOnlyOneAtATime } from '../../session/utils/Promise';
import { getOpenGroupV2ConversationId } from '../utils/OpenGroupUtils';
import { OpenGroupRequestCommonType } from './ApiUtil';
import { openGroupV2GetRoomInfo } from './OpenGroupAPIV2';
import { OpenGroupServerPoller } from './OpenGroupServerPoller';
import _ from 'lodash';
import { deleteAuthToken } from './ApiAuth';
export class OpenGroupManagerV2 {
public static readonly useV2OpenGroups = false;
private static instance: OpenGroupManagerV2;
/**
* The map of opengroup pollers, by serverUrl.
* A single poller polls for every room on the specified serverUrl
*/
private readonly pollers: Map<string, OpenGroupServerPoller> = new Map();
private isPolling = false;
private constructor() {
this.startPollingBouncy = this.startPollingBouncy.bind(this);
this.attemptConnectionV2 = this.attemptConnectionV2.bind(this);
}
public static getInstance() {
if (!OpenGroupManagerV2.instance) {
OpenGroupManagerV2.instance = new OpenGroupManagerV2();
}
return OpenGroupManagerV2.instance;
}
/**
* When we get our configuration from the network, we might get a few times the same open group on two different messages.
* If we don't do anything, we will join them multiple times.
* Even if the convo exists only once, the lokiPublicChat API will have several instances polling for the same open group.
* Which will cause a lot of duplicate messages as they will be merged on a single conversation.
*
* To avoid this issue, we allow only a single join of a specific opengroup at a time.
*/
public async attemptConnectionV2OneAtATime(
serverUrl: string,
roomId: string,
publicKey: string
): Promise<ConversationModel> {
const oneAtaTimeStr = `oneAtaTimeOpenGroupV2Join:${serverUrl}${roomId}`;
return allowOnlyOneAtATime(oneAtaTimeStr, async () => {
return this.attemptConnectionV2(serverUrl, roomId, publicKey);
});
}
public async startPolling() {
await allowOnlyOneAtATime('V2ManagerStartPolling', this.startPollingBouncy);
}
/**
* This is not designed to be restarted for now. If you stop polling
*/
public stopPolling() {
if (!this.isPolling) {
return;
}
// the stop call calls the abortController, which will effectively cancel the request right away,
// or drop the result from it.
this.pollers.forEach(poller => {
poller.stop();
});
this.pollers.clear();
this.isPolling = false;
}
public addRoomToPolledRooms(roomInfos: OpenGroupRequestCommonType) {
const poller = this.pollers.get(roomInfos.serverUrl);
if (!poller) {
this.pollers.set(roomInfos.serverUrl, new OpenGroupServerPoller([roomInfos]));
return;
}
// this won't do a thing if the room is already polled for
poller.addRoomToPoll(roomInfos);
}
public removeRoomFromPolledRooms(roomInfos: OpenGroupRequestCommonType) {
const poller = this.pollers.get(roomInfos.serverUrl);
if (!poller) {
return;
}
// this won't do a thing if the room is already polled for
poller.removeRoomFromPoll(roomInfos);
if (poller.getPolledRoomsCount() === 0) {
this.pollers.delete(roomInfos.serverUrl);
// this poller is not needed anymore, kill it
poller.stop();
}
}
/**
* This function is private because we want to make sure it only runs once at a time.
*/
private async startPollingBouncy() {
if (this.isPolling) {
return;
}
const allConvos = await getAllOpenGroupV2Conversations();
let allRoomInfos = await getAllV2OpenGroupRooms();
// this is time for some cleanup!
// We consider the conversations are our source-of-truth,
// so if there is a roomInfo without an associated convo, we remove it
if (allRoomInfos) {
await Promise.all(
[...allRoomInfos.values()].map(async infos => {
try {
const roomConvoId = getOpenGroupV2ConversationId(infos.serverUrl, infos.roomId);
if (!allConvos.get(roomConvoId)) {
// leave the group on the remote server
// this request doesn't throw
await deleteAuthToken(_.pick(infos, 'serverUrl', 'roomId'));
// remove the roomInfos locally for this open group room
await removeV2OpenGroupRoom(roomConvoId);
// no need to remove it from the ConversationController, the convo is already not there
}
} catch (e) {
window.log.warn('cleanup roomInfos error', e);
}
})
);
}
// refresh our roomInfos list
allRoomInfos = await getAllV2OpenGroupRooms();
if (allRoomInfos) {
allRoomInfos.forEach(infos => {
this.addRoomToPolledRooms(infos);
});
}
this.isPolling = true;
}
/**
*
* @param serverUrl with protocol, hostname and port included
*/
private async attemptConnectionV2(
serverUrl: string,
roomId: string,
serverPublicKey: string
): Promise<ConversationModel | undefined> {
const conversationId = getOpenGroupV2ConversationId(serverUrl, roomId);
if (ConversationController.getInstance().get(conversationId)) {
// Url incorrect or server not compatible
throw new Error(window.i18n('publicChatExists'));
}
// here, the convo does not exist. Make sure the db is clean too
await removeV2OpenGroupRoom(conversationId);
const room: OpenGroupV2Room = {
serverUrl,
roomId,
conversationId,
serverPublicKey,
};
try {
// save the pubkey to the db right now, the request for room Info
// will need it and access it from the db
await saveV2OpenGroupRoom(room);
const roomInfos = await openGroupV2GetRoomInfo({ roomId, serverUrl });
if (!roomInfos) {
throw new Error('Invalid open group roomInfo result');
}
const conversation = await ConversationController.getInstance().getOrCreateAndWait(
conversationId,
ConversationTypeEnum.GROUP
);
room.imageID = roomInfos.imageId || undefined;
room.roomName = roomInfos.name || undefined;
await saveV2OpenGroupRoom(room);
// mark active so it's not in the contacts list but in the conversation list
conversation.set({
active_at: Date.now(),
name: room.roomName,
avatarPath: room.roomName,
});
await conversation.commit();
// start polling this room
this.addRoomToPolledRooms(room);
return conversation;
} catch (e) {
window.log.warn('Failed to join open group v2', e);
await removeV2OpenGroupRoom(conversationId);
Session v1.6.2 (#1639) * padd Message buffer for all outgoing messages (even opengroupv2) * pad and unpad message everywhere attachment not padded for opengroup only * lint * enable fileserver v2 sending side * removed all en unused local strings * remove all unused keys for other locales * update displayname even if we dont have avatar on incoming profile * redesign group invitation message type * ask confirmation before joining opengroup invitation * remove the channelId from groupInvitation * fallback to envelope timestamp if dataMessage.timestamp is 0 * match group invitation design with ios * speed up first load of room message by prefetching token * create convo for members if they don't exist also, removing a private convo does not remove it entirely as we need the convo to be able to remove members * fix avatar download on restore when linking device Fixes #1601 * make sure the left member convo exist in rendering GroupUpdate * Reply attachments (#1591) * First attachment showing in reply composition. * WIP: Adding thumbnail to quote response composition component. * Added icon for voice recording attachment * Updated formatting. * Formatting. * removed duplicate styling. * WIP: Converting quote component to functional components. * Fix bug where thumbnails for attachment replies wasn't showing. * yarn Formatting. * Removed old quote component. * Add type to contentTypeSupported method. * Moved quote subcomponents out of Quote component. * yarn format * Add export to quote subcomponents. * Fixing linting errors. * remove commented line. * Addressing PR comments. * Allow pasting images into composition box as attachments (#1616) * Allow pasting images into composition box as attachments * Fix linter errors * Fix typo * Get snode from snode (#1614) * force deleteAccount after 10sec timeout waiting for configMessage * move some constants to file where they are used * add a way to fetch snodes from snodes * remove a snode from a pubkey's swarm if we get 421 without valid content * remove getVersion from snodes * hide groupMembers in right panel for non-group convo * Fix fonts sans serif (#1619) * force deleteAccount after 10sec timeout waiting for configMessage * move some constants to file where they are used * add a way to fetch snodes from snodes * remove a snode from a pubkey's swarm if we get 421 without valid content * remove getVersion from snodes * hide groupMembers in right panel for non-group convo * fix font sans serif by using roboto instead Fixes #1617 * WIP: User nicknames (#1618) * WIP Adding change nickname dialog. * WIP adding nickname change dialog. * WIP nickname dialog. * WIP: Able to set conversation nicknames. Next step cleaning and adding to conversation list menu. * Fix message capitilisations. * Add change nickname to conversation list menu. * Enable clear nickname menu item. * Added messages for changing nicknames. * Clearing nicknames working from header and message list. * Adding modal styling to nickname modal. * Reorder nickname menu item positions. * Add group based conditional nickname menu options to conversation header menu. * minor tidying. * Remove unused error causing el option. * Formatting. * Linting fixes. * Made PR fixes * Prioritise displaying nicknames for inviting new closed group members and updating closed group members. * Fix app image start for non-debian based distribs (#1622) Fixes #1620 * fixup nickname dialog for enter key pressed event (#1623) also add some type for it and remove unused props * Fix attachment extension vnd (#1628) * allow openoffice document extension and don't use * allow opendocument to be shared with the extension rather than mimetype Fixes #1593 * allow message without padding * add test for odt files * More Japanese translations (#1632) * Translate some untranslated strings into Japanese * Tweak some Japanese translations * Add new Japanese translations * WIP: Closed group reliability (#1630) * WIP: added non-durable messaging function. * WIP: Non-durable sending * WIP: adding dialog box. * Creating dialog if group invite message promises don't return true. * removed console log * applied PR changes, linting and formatting. * WIP: allowing resend invite to failures. * using lookup. * WIP: recursively opening dialog. * WIP: debugging reject triggering on confirmation modal. * register events fix. * Closed group invite retry dialog working. * Added english text to messages. * Prevent saving of hexkey pair if it already exists. * Fixed nickname edit input trimming end letter. * Don't show closed group invite dialog unless it has failed at least once. * Fix linting error. * Fix plurality. * Ensure admin members are included in all invite reattempts, mixed plurality. * test fixing windows build * Revert "test fixing windows build" This reverts commit 8ed2e0891d160a774de452e8373aea179502e221. Co-authored-by: Warrick <wcor690@aucklanduni.ac.nz> Co-authored-by: shellhazard <unva1idated@protonmail.com> Co-authored-by: beantaco <64012487+beantaco@users.noreply.github.com>
4 years ago
// throw new Error(window.i18n('connectToServerFail'));
return undefined;
}
}
}