From 193fb2a101c4f7ad1f767f0e6f052e2a0e38b21a Mon Sep 17 00:00:00 2001 From: Audric Ackermann Date: Fri, 23 Apr 2021 11:08:38 +1000 Subject: [PATCH] move some message interactions logic to another file --- .../session/LeftPaneMessageSection.tsx | 2 + ts/data/opengroups.ts | 6 +- ts/interactions/index.ts | 3 + ts/interactions/message.ts | 62 +++++++++++++++++++ ts/models/conversation.ts | 20 +++--- ts/models/message.ts | 46 ++------------ ts/opengroup/opengroupV2/OpenGroupAPIV2.ts | 32 +++++++--- .../opengroupV2/OpenGroupManagerV2.ts | 5 +- ts/opengroup/opengroupV2/index.ts | 3 + ts/opengroup/utils/OpenGroupUtils.ts | 2 +- .../conversations/ConversationController.ts | 25 ++++---- ts/session/onions/onionSend.ts | 2 +- ts/session/snode_api/serviceNodeAPI.ts | 1 - ts/test/session/unit/utils/Messages_test.ts | 6 +- ts/test/test-utils/utils/message.ts | 2 +- 15 files changed, 140 insertions(+), 77 deletions(-) create mode 100644 ts/interactions/index.ts create mode 100644 ts/interactions/message.ts create mode 100644 ts/opengroup/opengroupV2/index.ts diff --git a/ts/components/session/LeftPaneMessageSection.tsx b/ts/components/session/LeftPaneMessageSection.tsx index ee05b7101..0ce4fb4b5 100644 --- a/ts/components/session/LeftPaneMessageSection.tsx +++ b/ts/components/session/LeftPaneMessageSection.tsx @@ -384,6 +384,8 @@ export class LeftPaneMessageSection extends React.Component { return; } + // guess if this is an open + // Server URL valid? if (serverUrl.length === 0 || !OpenGroup.validate(serverUrl)) { ToastUtils.pushToastError('connectToServer', window.i18n('invalidOpenGroupUrl')); diff --git a/ts/data/opengroups.ts b/ts/data/opengroups.ts index 1d02d7ad8..7ba0670ac 100644 --- a/ts/data/opengroups.ts +++ b/ts/data/opengroups.ts @@ -1,4 +1,5 @@ import { OpenGroupRequestCommonType } from '../opengroup/opengroupV2/ApiUtil'; +import { isOpenGroupV2 } from '../opengroup/utils/OpenGroupUtils'; import { channels } from './channels'; export type OpenGroupV2Room = { @@ -35,7 +36,10 @@ export async function getAllV2OpenGroupRooms(): Promise { - const opengroupv2Rooms = await channels.getV2OpenGroupRoom(conversationId); + if (!isOpenGroupV2(conversationId)) { + throw new Error(`getV2OpenGroupRoom: this is not a valid v2 id: ${conversationId}`); + } + const opengroupv2Rooms = channels.getV2OpenGroupRoom(conversationId); if (!opengroupv2Rooms) { return undefined; diff --git a/ts/interactions/index.ts b/ts/interactions/index.ts new file mode 100644 index 000000000..fe6cff982 --- /dev/null +++ b/ts/interactions/index.ts @@ -0,0 +1,3 @@ +import * as MessageInteraction from './message'; + +export { MessageInteraction }; diff --git a/ts/interactions/message.ts b/ts/interactions/message.ts new file mode 100644 index 000000000..b11a78691 --- /dev/null +++ b/ts/interactions/message.ts @@ -0,0 +1,62 @@ +import _ from 'lodash'; +import { getV2OpenGroupRoom } from '../data/opengroups'; +import { ConversationModel } from '../models/conversation'; +import { ApiV2 } from '../opengroup/opengroupV2'; +import { isOpenGroupV2 } from '../opengroup/utils/OpenGroupUtils'; +import { PubKey } from '../session/types'; +import { ToastUtils } from '../session/utils'; + +export function banUser(userToBan: string, conversation?: ConversationModel) { + let pubKeyToBan: PubKey; + try { + pubKeyToBan = PubKey.cast(userToBan); + } catch (e) { + window.log.warn(e); + ToastUtils.pushUserBanFailure(); + return; + } + window.confirmationDialog({ + title: window.i18n('banUser'), + message: window.i18n('banUserConfirm'), + resolve: async () => { + if (!conversation) { + window.log.info('cannot ban user, the corresponding conversation was not found.'); + return; + } + let success = false; + if (isOpenGroupV2(conversation.id)) { + const roomInfos = await getV2OpenGroupRoom(conversation.id); + if (!roomInfos) { + window.log.warn('banUser room not found'); + } else { + await ApiV2.banUser(pubKeyToBan, _.pick(roomInfos, 'serverUrl', 'roomId')); + } + } else { + const channelAPI = await conversation.getPublicSendData(); + if (!channelAPI) { + window.log.info('cannot ban user, the corresponding channelAPI was not found.'); + return; + } + success = await channelAPI.banUser(userToBan); + } + if (success) { + ToastUtils.pushUserBanSuccess(); + } else { + ToastUtils.pushUserBanFailure(); + } + }, + }); +} + +export function copyBodyToClipboard(body?: string) { + window.clipboard.writeText(body); + + ToastUtils.pushCopiedToClipBoard(); +} + +export function copyPubKey(sender: string) { + // this.getSource return out pubkey if this is an outgoing message, or the sender pubkey + window.clipboard.writeText(); + + ToastUtils.pushCopiedToClipBoard(); +} diff --git a/ts/models/conversation.ts b/ts/models/conversation.ts index 8583eb46a..a74b7ab57 100644 --- a/ts/models/conversation.ts +++ b/ts/models/conversation.ts @@ -37,11 +37,14 @@ import { import { GroupInvitationMessage } from '../session/messages/outgoing/visibleMessage/GroupInvitationMessage'; import { ReadReceiptMessage } from '../session/messages/outgoing/controlMessage/receipt/ReadReceiptMessage'; import { OpenGroup } from '../opengroup/opengroupV1/OpenGroup'; -import { openGroupPrefixRegex } from '../opengroup/utils/OpenGroupUtils'; +import { + openGroupPrefixRegex, + openGroupV1ConversationIdRegex, + openGroupV2ConversationIdRegex, +} from '../opengroup/utils/OpenGroupUtils'; export enum ConversationType { GROUP = 'group', - OPEN_GROUP = 'opengroup', PRIVATE = 'private', } @@ -188,11 +191,11 @@ export class ConversationModel extends Backbone.Model { public isMe() { return UserUtils.isUsFromCache(this.id); } - public isPublic() { - return !!(this.id && this.id.match(openGroupPrefixRegex)); + public isPublic(): boolean { + return Boolean(this.id && this.id.match(openGroupPrefixRegex)); } - public isOpenGroupV2() { - return this.get('type') === ConversationType.OPEN_GROUP; + public isOpenGroupV2(): boolean { + return Boolean(this.id && this.id.match(openGroupV2ConversationIdRegex)); } public isClosedGroup() { return this.get('type') === ConversationType.GROUP && !this.isPublic(); @@ -374,7 +377,6 @@ export class ConversationModel extends Backbone.Model { const groupAdmins = this.getGroupAdmins(); const members = this.isGroup() && !this.isPublic() ? this.get('members') : undefined; - // isSelected is overriden by redux return { isSelected: false, @@ -1309,6 +1311,10 @@ export class ConversationModel extends Backbone.Model { } public async deletePublicMessages(messages: Array) { + if (this.isOpenGroupV2()) { + console.warn('FIXME deletePublicMessages'); + throw new Error('deletePublicMessages todo'); + } const channelAPI = await this.getPublicSendData(); if (!channelAPI) { diff --git a/ts/models/message.ts b/ts/models/message.ts index 2e277d061..422eda4ce 100644 --- a/ts/models/message.ts +++ b/ts/models/message.ts @@ -25,6 +25,8 @@ import { VisibleMessage } from '../session/messages/outgoing/visibleMessage/Visi import { buildSyncMessage } from '../session/utils/syncUtils'; import { isOpenGroupV2 } from '../opengroup/utils/OpenGroupUtils'; import { banUser } from '../opengroup/opengroupV2/OpenGroupAPIV2'; +import { getV2OpenGroupRoom } from '../data/opengroups'; +import { MessageInteraction } from '../interactions'; export class MessageModel extends Backbone.Model { public propsForTimerNotification: any; public propsForGroupNotification: any; @@ -456,8 +458,6 @@ export class MessageModel extends Backbone.Model { const expirationTimestamp = expirationLength && expireTimerStart ? expireTimerStart + expirationLength : null; - // TODO: investigate why conversation is undefined - // for the public group chat const conversation = this.getConversation(); const convoId = conversation ? conversation.id : undefined; @@ -697,50 +697,16 @@ export class MessageModel extends Backbone.Model { } public copyPubKey() { - if (this.isIncoming()) { - window.clipboard.writeText(this.get('source')); - } else { - window.clipboard.writeText(UserUtils.getOurPubKeyStrFromCache()); - } - - ToastUtils.pushCopiedToClipBoard(); + // this.getSource return out pubkey if this is an outgoing message, or the sender pubkey + MessageInteraction.copyPubKey(this.getSource()); } public banUser() { - window.confirmationDialog({ - title: window.i18n('banUser'), - message: window.i18n('banUserConfirm'), - resolve: async () => { - const source = this.get('source'); - const conversation = this.getConversation(); - if (!conversation) { - window.log.info('cannot ban user, the corresponding conversation was not found.'); - return; - } - let success = false; - if (isOpenGroupV2(conversation.id)) { - await banUser(); - } else { - const channelAPI = await conversation.getPublicSendData(); - if (!channelAPI) { - window.log.info('cannot ban user, the corresponding channelAPI was not found.'); - return; - } - success = await channelAPI.banUser(source); - } - if (success) { - ToastUtils.pushUserBanSuccess(); - } else { - ToastUtils.pushUserBanFailure(); - } - }, - }); + MessageInteraction.banUser(this.get('source'), this.getConversation()); } public copyText() { - window.clipboard.writeText(this.get('body')); - - ToastUtils.pushCopiedToClipBoard(); + MessageInteraction.copyBodyToClipboard(this.get('body')); } /** diff --git a/ts/opengroup/opengroupV2/OpenGroupAPIV2.ts b/ts/opengroup/opengroupV2/OpenGroupAPIV2.ts index 193b9fe68..e49c94018 100644 --- a/ts/opengroup/opengroupV2/OpenGroupAPIV2.ts +++ b/ts/opengroup/opengroupV2/OpenGroupAPIV2.ts @@ -2,6 +2,7 @@ import _ from 'lodash'; import { getV2OpenGroupRoomByRoomId, saveV2OpenGroupRoom } from '../../data/opengroups'; import { ConversationController } from '../../session/conversations'; import { sendViaOnion } from '../../session/onions/onionSend'; +import { PubKey } from '../../session/types'; import { allowOnlyOneAtATime } from '../../session/utils/Promise'; import { fromArrayBufferToBase64, @@ -273,7 +274,10 @@ export async function getAuthToken({ return null; } -export const deleteAuthToken = async ({ serverUrl, roomId }: OpenGroupRequestCommonType) => { +export const deleteAuthToken = async ({ + serverUrl, + roomId, +}: OpenGroupRequestCommonType): Promise => { const request: OpenGroupV2Request = { method: 'DELETE', room: roomId, @@ -281,10 +285,17 @@ export const deleteAuthToken = async ({ serverUrl, roomId }: OpenGroupRequestCom isAuthRequired: false, endpoint: 'auth_token', }; - const result = await sendOpenGroupV2Request(request); - const statusCode = parseStatusCodeFromOnionRequest(result); - if (statusCode !== 200) { - window.log.warn(`Could not deleteAuthToken, status code: ${statusCode}`); + try { + const result = await sendOpenGroupV2Request(request); + const statusCode = parseStatusCodeFromOnionRequest(result); + if (statusCode !== 200) { + window.log.warn(`Could not deleteAuthToken, status code: ${statusCode}`); + return false; + } + return true; + } catch (e) { + window.log.error('deleteAuthToken failed:', e); + return false; } }; @@ -402,10 +413,10 @@ export const isUserModerator = ( }; export const banUser = async ( - publicKey: string, + userToBan: PubKey, roomInfos: OpenGroupRequestCommonType ): Promise => { - const queryParams = { public_key: publicKey }; + const queryParams = { public_key: userToBan.key }; const request: OpenGroupV2Request = { method: 'POST', room: roomInfos.roomId, @@ -414,11 +425,12 @@ export const banUser = async ( queryParams, endpoint: 'block_list', }; - await sendOpenGroupV2Request(request); + const banResult = await sendOpenGroupV2Request(request); + console.warn('banResult', banResult); }; export const unbanUser = async ( - publicKey: string, + userToBan: PubKey, roomInfos: OpenGroupRequestCommonType ): Promise => { const request: OpenGroupV2Request = { @@ -426,7 +438,7 @@ export const unbanUser = async ( room: roomInfos.roomId, server: roomInfos.serverUrl, isAuthRequired: true, - endpoint: `block_list/${publicKey}`, + endpoint: `block_list/${userToBan.key}`, }; await sendOpenGroupV2Request(request); }; diff --git a/ts/opengroup/opengroupV2/OpenGroupManagerV2.ts b/ts/opengroup/opengroupV2/OpenGroupManagerV2.ts index 5e573d5cd..63a8d9dce 100644 --- a/ts/opengroup/opengroupV2/OpenGroupManagerV2.ts +++ b/ts/opengroup/opengroupV2/OpenGroupManagerV2.ts @@ -62,16 +62,19 @@ async function attemptConnectionV2( } const conversation = await ConversationController.getInstance().getOrCreateAndWait( conversationId, - ConversationType.OPEN_GROUP + ConversationType.GROUP ); room.imageID = roomInfos.imageId || undefined; room.roomName = roomInfos.name || undefined; await saveV2OpenGroupRoom(room); + console.warn('openGroupRoom info', roomInfos); // 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(); diff --git a/ts/opengroup/opengroupV2/index.ts b/ts/opengroup/opengroupV2/index.ts new file mode 100644 index 000000000..c054d2c68 --- /dev/null +++ b/ts/opengroup/opengroupV2/index.ts @@ -0,0 +1,3 @@ +import * as ApiV2 from './OpenGroupAPIV2'; + +export { ApiV2 }; diff --git a/ts/opengroup/utils/OpenGroupUtils.ts b/ts/opengroup/utils/OpenGroupUtils.ts index 5641d89f2..5e7e5a767 100644 --- a/ts/opengroup/utils/OpenGroupUtils.ts +++ b/ts/opengroup/utils/OpenGroupUtils.ts @@ -28,7 +28,7 @@ export const openGroupPrefix = 'publicChat:'; /** * Just a regex to match a public chat (i.e. a string starting with publicChat:) */ -export const openGroupPrefixRegex = new RegExp(`/^${openGroupPrefix}/`); +export const openGroupPrefixRegex = new RegExp(`^${openGroupPrefix}`); /** * An open group v1 conversation id can only have the char '1' as roomId diff --git a/ts/session/conversations/ConversationController.ts b/ts/session/conversations/ConversationController.ts index 26cd373d8..af27695bf 100644 --- a/ts/session/conversations/ConversationController.ts +++ b/ts/session/conversations/ConversationController.ts @@ -14,6 +14,9 @@ import { BlockedNumberController } from '../../util'; import { getSnodesFor } from '../snode_api/snodePool'; import { PubKey } from '../types'; import { actions as conversationActions } from '../../state/ducks/conversations'; +import { getV2OpenGroupRoom, removeV2OpenGroupRoom } from '../../data/opengroups'; +import { deleteAuthToken } from '../../opengroup/opengroupV2/OpenGroupAPIV2'; +import _ from 'lodash'; export class ConversationController { private static instance: ConversationController | null; @@ -68,12 +71,8 @@ export class ConversationController { throw new TypeError("'id' must be a string"); } - if ( - type !== ConversationType.PRIVATE && - type !== ConversationType.GROUP && - type !== ConversationType.OPEN_GROUP - ) { - throw new TypeError(`'type' must be 'private' or 'group' or 'opengroup; got: '${type}'`); + if (type !== ConversationType.PRIVATE && type !== ConversationType.GROUP) { + throw new TypeError(`'type' must be 'private' or 'group' got: '${type}'`); } if (!this._initialFetchComplete) { @@ -178,10 +177,6 @@ export class ConversationController { } public async deleteContact(id: string) { - if (typeof id !== 'string') { - throw new TypeError("'id' must be a string"); - } - if (!this._initialFetchComplete) { throw new Error('ConversationController.get() needs complete initial fetch'); } @@ -202,9 +197,17 @@ export class ConversationController { channelAPI.serverAPI.partChannel((channelAPI as any).channelId); } } else if (conversation.isOpenGroupV2()) { - window.log.warn('leave open group v2 todo'); + window.log.info('leaving open group v2', conversation.id); + const roomInfos = await getV2OpenGroupRoom(conversation.id); + if (roomInfos) { + // leave the group on the remote server + await deleteAuthToken(_.pick(roomInfos, 'serverUrl', 'roomId')); + // remove the roomInfos locally for this open group room + await removeV2OpenGroupRoom(conversation.id); + } } + // those are the stuff to do for all contact types await conversation.destroyMessages(); await removeConversation(id); diff --git a/ts/session/onions/onionSend.ts b/ts/session/onions/onionSend.ts index c765a59af..41160a7c6 100644 --- a/ts/session/onions/onionSend.ts +++ b/ts/session/onions/onionSend.ts @@ -165,7 +165,7 @@ export const sendViaOnion = async ( // port: url.port, }; - console.warn('sendViaOnion payloadObj ==> ', payloadObj); + window.log.debug('sendViaOnion payloadObj ==> ', payloadObj); result = await sendOnionRequestLsrpcDest( 0, diff --git a/ts/session/snode_api/serviceNodeAPI.ts b/ts/session/snode_api/serviceNodeAPI.ts index 1eaad9131..23a03f17d 100644 --- a/ts/session/snode_api/serviceNodeAPI.ts +++ b/ts/session/snode_api/serviceNodeAPI.ts @@ -113,7 +113,6 @@ const getSslAgentForSeedNode = (seedNodeHost: string, isSsl = false) => { // read the cert each time. We only run this request once for each seed node nevertheless. const appPath = remote.app.getAppPath(); const crt = fs.readFileSync(path.join(appPath, `/certificates/${filePrefix}.crt`), 'utf-8'); - // debugger; const sslOptions = { // as the seed nodes are using a self signed certificate, we have to provide it here. ca: crt, diff --git a/ts/test/session/unit/utils/Messages_test.ts b/ts/test/session/unit/utils/Messages_test.ts index 068a42f48..e511e7bb3 100644 --- a/ts/test/session/unit/utils/Messages_test.ts +++ b/ts/test/session/unit/utils/Messages_test.ts @@ -227,17 +227,17 @@ describe('Message Utils', () => { let convos: Array; const mockValidOpenGroup = new MockConversation({ - type: ConversationType.OPEN_GROUP, + type: ConversationType.GROUP, id: `${openGroupPrefix}1@chat-dev.lokinet.org`, }); const mockValidOpenGroup2 = new MockConversation({ - type: ConversationType.OPEN_GROUP, + type: ConversationType.GROUP, id: `${openGroupPrefix}1@chat-dev2.lokinet.org`, }); const mockValidClosedGroup = new MockConversation({ - type: ConversationType.OPEN_GROUP, + type: ConversationType.GROUP, }); const mockValidPrivate = { diff --git a/ts/test/test-utils/utils/message.ts b/ts/test/test-utils/utils/message.ts index afe7dfe34..5f03216c7 100644 --- a/ts/test/test-utils/utils/message.ts +++ b/ts/test/test-utils/utils/message.ts @@ -70,7 +70,7 @@ export class MockConversation { id: this.id, name: '', profileName: undefined, - type: params.type === ConversationType.OPEN_GROUP ? 'group' : params.type, + type: params.type === ConversationType.GROUP ? 'group' : params.type, members, left: false, expireTimer: 0,