From f8d24eb2646bf020a67795114476fd78f2a9dea8 Mon Sep 17 00:00:00 2001 From: Szymon Szlachtowicz <38212223+Szymx95@users.noreply.github.com> Date: Wed, 5 Jan 2022 01:42:34 +0100 Subject: [PATCH] Fix group chats nicknames (#172) --- .../src/components/Channels/EmptyChannel.tsx | 23 +++- .../src/hooks/messenger/useGroupChats.ts | 4 +- packages/status-communities/src/groupChats.ts | 107 ++++++++++++++---- packages/status-communities/src/utils.ts | 8 ++ 4 files changed, 111 insertions(+), 31 deletions(-) diff --git a/packages/react-chat/src/components/Channels/EmptyChannel.tsx b/packages/react-chat/src/components/Channels/EmptyChannel.tsx index 8faabaf9..f4bbac45 100644 --- a/packages/react-chat/src/components/Channels/EmptyChannel.tsx +++ b/packages/react-chat/src/components/Channels/EmptyChannel.tsx @@ -1,8 +1,9 @@ -import React from "react"; +import React, { useMemo } from "react"; import { utils } from "status-communities/dist/cjs"; import styled from "styled-components"; import { useIdentity } from "../../contexts/identityProvider"; +import { useMessengerContext } from "../../contexts/messengerProvider"; import { ChannelData } from "../../models/ChannelData"; import { textMediumStyles } from "../Text"; @@ -13,9 +14,17 @@ type EmptyChannelProps = { }; export function EmptyChannel({ channel }: EmptyChannelProps) { - const groupName = channel.name.split(", "); const identity = useIdentity(); - + const { contacts } = useMessengerContext(); + const members = useMemo(() => { + if (channel?.members && identity) { + const publicKey = utils.bufToHex(identity.publicKey); + return channel.members + .filter((contact) => contact.id !== publicKey) + .map((member) => contacts?.[member.id] ?? member); + } + return []; + }, [channel, contacts]); return ( @@ -36,8 +45,12 @@ export function EmptyChannel({ channel }: EmptyChannelProps) { {identity && {utils.bufToHex(identity.publicKey)}}{" "} created a group with{" "} - {groupName.slice(groupName.length - 1)} and{" "} - {groupName.at(-1)} + {members.map((contact, idx) => ( + + {contact?.customName ?? contact.trueName.slice(0, 10)} + {idx < members.length - 1 && <> and } + + ))} ) : ( diff --git a/packages/react-chat/src/hooks/messenger/useGroupChats.ts b/packages/react-chat/src/hooks/messenger/useGroupChats.ts index f1442365..0145e6e9 100644 --- a/packages/react-chat/src/hooks/messenger/useGroupChats.ts +++ b/packages/react-chat/src/hooks/messenger/useGroupChats.ts @@ -33,7 +33,9 @@ export function useGroupChats( const groupChat = useMemo(() => { if (messenger && identity) { const addChat = (chat: GroupChat) => { - const members = chat.members.map(contactFromId); + const members = chat.members + .map((member) => member.id) + .map(contactFromId); const channel: ChannelData = { id: chat.chatId, name: chat.name ?? chat.chatId, diff --git a/packages/status-communities/src/groupChats.ts b/packages/status-communities/src/groupChats.ts index 53011ab4..24bfa196 100644 --- a/packages/status-communities/src/groupChats.ts +++ b/packages/status-communities/src/groupChats.ts @@ -1,9 +1,10 @@ import { Waku, WakuMessage } from "js-waku"; +import { createSymKeyFromPassword } from "./encryption"; import { Identity } from "./identity"; import { MembershipUpdateEvent_EventType } from "./proto/communities/v1/membership_update_message"; import { getNegotiatedTopic, getPartitionedTopic } from "./topics"; -import { bufToHex } from "./utils"; +import { bufToHex, compressPublicKey } from "./utils"; import { MembershipSignedEvent, MembershipUpdateMessage, @@ -11,9 +12,16 @@ import { import { ChatMessage, Content } from "."; +type GroupMember = { + id: string; + topic: string; + symKey: Uint8Array; + partitionedTopic: string; +}; + export type GroupChat = { chatId: string; - members: string[]; + members: GroupMember[]; admins?: string[]; name?: string; removed: boolean; @@ -32,7 +40,6 @@ export class GroupChats { private addMessage: (message: ChatMessage, sender: string) => void; public chats: GroupChatsType = {}; - /** * GroupChats holds a list of private chats and listens to their status broadcast * @@ -87,7 +94,8 @@ export class GroupChats { ); const wakuMessage = await WakuMessage.fromBytes( chatMessage.encode(), - await getNegotiatedTopic(this.identity, member) + member.topic, + { sigPrivKey: this.identity.privateKey, symKey: member.symKey } ); this.waku.relay.send(wakuMessage); }) @@ -106,10 +114,19 @@ export class GroupChats { if (signer) { switch (event.event.type) { case MembershipUpdateEvent_EventType.CHAT_CREATED: { + const members: GroupMember[] = []; + await Promise.all( + event.event.members.map(async (member) => { + const topic = await getNegotiatedTopic(this.identity, member); + const symKey = await createSymKeyFromPassword(topic); + const partitionedTopic = getPartitionedTopic(member); + members.push({ topic, symKey, id: member, partitionedTopic }); + }) + ); await this.addChat( { chatId: chatId, - members: event.event.members, + members, admins: [signer], removed: false, }, @@ -120,7 +137,7 @@ export class GroupChats { case MembershipUpdateEvent_EventType.MEMBER_REMOVED: { if (chat) { chat.members = chat.members.filter( - (member) => !event.event.members.includes(member) + (member) => !event.event.members.includes(member.id) ); if (event.event.members.includes(thisUser)) { await this.removeChat( @@ -140,8 +157,19 @@ export class GroupChats { } case MembershipUpdateEvent_EventType.MEMBERS_ADDED: { if (chat && chat.admins?.includes(signer)) { - chat.members.push(...event.event.members); - if (chat.members.includes(thisUser)) { + const members: GroupMember[] = []; + await Promise.all( + event.event.members.map(async (member) => { + const topic = await getNegotiatedTopic(this.identity, member); + const symKey = await createSymKeyFromPassword(topic); + const partitionedTopic = getPartitionedTopic(member); + members.push({ topic, symKey, id: member, partitionedTopic }); + }) + ); + chat.members.push(...members); + if ( + chat.members.findIndex((member) => member.id === thisUser) > -1 + ) { chat.removed = false; await this.addChat(chat, useCallback); } @@ -196,7 +224,11 @@ export class GroupChats { const chatMessage = ChatMessage.decode(message.payload); if (chatMessage) { if (chatMessage.chatId === chat.chatId) { - this.addMessage(chatMessage, member); + let sender = member; + if (message.signaturePublicKey) { + sender = compressPublicKey(message.signaturePublicKey); + } + this.addMessage(chatMessage, sender); } } } @@ -212,10 +244,12 @@ export class GroupChats { const observerFunction = removeObserver ? "deleteObserver" : "addObserver"; await Promise.all( chat.members.map(async (member) => { - const topic = await getNegotiatedTopic(this.identity, member); + if (!removeObserver) { + this.waku.relay.addDecryptionKey(member.symKey); + } this.waku.relay[observerFunction]( - (message) => this.handleWakuChatMessage(message, chat, member), - [topic] + (message) => this.handleWakuChatMessage(message, chat, member.id), + [member.topic] ); }) ); @@ -248,9 +282,8 @@ export class GroupChats { } private async listen(): Promise { - const messages = await this.waku.store.queryHistory([ - getPartitionedTopic(bufToHex(this.identity.publicKey)), - ]); + const topic = getPartitionedTopic(bufToHex(this.identity.publicKey)); + const messages = await this.waku.store.queryHistory([topic]); messages.sort((a, b) => (a?.timestamp?.getTime() ?? 0) < (b?.timestamp?.getTime() ?? 0) ? -1 : 1 ); @@ -270,18 +303,18 @@ export class GroupChats { ); this.waku.relay.addObserver( (message) => this.decodeUpdateMessage(message, true), - [getPartitionedTopic(bufToHex(this.identity.publicKey))] + [topic] ); } private async sendUpdateMessage( payload: Uint8Array, - members: string[] + members: GroupMember[] ): Promise { const wakuMessages = await Promise.all( members.map( async (member) => - await WakuMessage.fromBytes(payload, getPartitionedTopic(member)) + await WakuMessage.fromBytes(payload, member.partitionedTopic) ) ); wakuMessages.forEach((msg) => this.waku.relay.send(msg)); @@ -314,10 +347,23 @@ export class GroupChats { const payload = MembershipUpdateMessage.create(chatId, this.identity); const chat = this.chats[chatId]; if (chat && payload) { - const newMembers = members.filter( - (member) => !chat.members.includes(member) + const newMembers: GroupMember[] = []; + + await Promise.all( + members + .filter( + (member) => + !chat.members.map((chatMember) => chatMember.id).includes(member) + ) + .map(async (member) => { + const topic = await getNegotiatedTopic(this.identity, member); + const symKey = await createSymKeyFromPassword(topic); + const partitionedTopic = getPartitionedTopic(member); + newMembers.push({ topic, symKey, id: member, partitionedTopic }); + }) ); - payload.addMembersAddedEvent(newMembers); + + payload.addMembersAddedEvent(newMembers.map((member) => member.id)); await this.sendUpdateMessage(payload.encode(), [ ...chat.members, ...newMembers, @@ -335,7 +381,19 @@ export class GroupChats { this.identity, members ).encode(); - await this.sendUpdateMessage(payload, members); + + const newMembers: GroupMember[] = []; + + await Promise.all( + members.map(async (member) => { + const topic = await getNegotiatedTopic(this.identity, member); + const symKey = await createSymKeyFromPassword(topic); + const partitionedTopic = getPartitionedTopic(member); + newMembers.push({ topic, symKey, id: member, partitionedTopic }); + }) + ); + + await this.sendUpdateMessage(payload, newMembers); } /** @@ -374,11 +432,10 @@ export class GroupChats { await Promise.all( chat.members.map(async (member) => { - const topic = await getNegotiatedTopic(this.identity, member); const msgLength = ( - await this.waku.store.queryHistory([topic], { + await this.waku.store.queryHistory([member.topic], { timeFilter: { startTime, endTime }, - callback: (msg) => _callback(msg, member), + callback: (msg) => _callback(msg, member.id), }) ).length; amountOfMessages.push(msgLength); diff --git a/packages/status-communities/src/utils.ts b/packages/status-communities/src/utils.ts index e8a4656d..83cd3e47 100644 --- a/packages/status-communities/src/utils.ts +++ b/packages/status-communities/src/utils.ts @@ -1,5 +1,8 @@ +import { ec } from "elliptic"; import { utils } from "js-waku"; +const EC = new ec("secp256k1"); + const hexToBuf = utils.hexToBuf; export { hexToBuf }; @@ -9,3 +12,8 @@ export { hexToBuf }; export function bufToHex(buf: Uint8Array): string { return "0x" + utils.bufToHex(buf); } + +export function compressPublicKey(key: Uint8Array): string { + const PubKey = EC.keyFromPublic(key); + return "0x" + PubKey.getPublic(true, "hex"); +}