Fix group chats nicknames (#172)

This commit is contained in:
Szymon Szlachtowicz 2022-01-05 01:42:34 +01:00 committed by GitHub
parent 64a67dce68
commit f8d24eb264
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 111 additions and 31 deletions

View File

@ -1,8 +1,9 @@
import React from "react"; import React, { useMemo } from "react";
import { utils } from "status-communities/dist/cjs"; import { utils } from "status-communities/dist/cjs";
import styled from "styled-components"; import styled from "styled-components";
import { useIdentity } from "../../contexts/identityProvider"; import { useIdentity } from "../../contexts/identityProvider";
import { useMessengerContext } from "../../contexts/messengerProvider";
import { ChannelData } from "../../models/ChannelData"; import { ChannelData } from "../../models/ChannelData";
import { textMediumStyles } from "../Text"; import { textMediumStyles } from "../Text";
@ -13,9 +14,17 @@ type EmptyChannelProps = {
}; };
export function EmptyChannel({ channel }: EmptyChannelProps) { export function EmptyChannel({ channel }: EmptyChannelProps) {
const groupName = channel.name.split(", ");
const identity = useIdentity(); 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 ( return (
<Wrapper> <Wrapper>
<ChannelInfoEmpty> <ChannelInfoEmpty>
@ -36,8 +45,12 @@ export function EmptyChannel({ channel }: EmptyChannelProps) {
<EmptyTextGroup> <EmptyTextGroup>
{identity && <span>{utils.bufToHex(identity.publicKey)}</span>}{" "} {identity && <span>{utils.bufToHex(identity.publicKey)}</span>}{" "}
created a group with{" "} created a group with{" "}
<span>{groupName.slice(groupName.length - 1)}</span> and{" "} {members.map((contact, idx) => (
<span>{groupName.at(-1)}</span> <span key={contact.id}>
{contact?.customName ?? contact.trueName.slice(0, 10)}
{idx < members.length - 1 && <> and </>}
</span>
))}
</EmptyTextGroup> </EmptyTextGroup>
) : ( ) : (
<EmptyText> <EmptyText>

View File

@ -33,7 +33,9 @@ export function useGroupChats(
const groupChat = useMemo(() => { const groupChat = useMemo(() => {
if (messenger && identity) { if (messenger && identity) {
const addChat = (chat: GroupChat) => { const addChat = (chat: GroupChat) => {
const members = chat.members.map(contactFromId); const members = chat.members
.map((member) => member.id)
.map(contactFromId);
const channel: ChannelData = { const channel: ChannelData = {
id: chat.chatId, id: chat.chatId,
name: chat.name ?? chat.chatId, name: chat.name ?? chat.chatId,

View File

@ -1,9 +1,10 @@
import { Waku, WakuMessage } from "js-waku"; import { Waku, WakuMessage } from "js-waku";
import { createSymKeyFromPassword } from "./encryption";
import { Identity } from "./identity"; import { Identity } from "./identity";
import { MembershipUpdateEvent_EventType } from "./proto/communities/v1/membership_update_message"; import { MembershipUpdateEvent_EventType } from "./proto/communities/v1/membership_update_message";
import { getNegotiatedTopic, getPartitionedTopic } from "./topics"; import { getNegotiatedTopic, getPartitionedTopic } from "./topics";
import { bufToHex } from "./utils"; import { bufToHex, compressPublicKey } from "./utils";
import { import {
MembershipSignedEvent, MembershipSignedEvent,
MembershipUpdateMessage, MembershipUpdateMessage,
@ -11,9 +12,16 @@ import {
import { ChatMessage, Content } from "."; import { ChatMessage, Content } from ".";
type GroupMember = {
id: string;
topic: string;
symKey: Uint8Array;
partitionedTopic: string;
};
export type GroupChat = { export type GroupChat = {
chatId: string; chatId: string;
members: string[]; members: GroupMember[];
admins?: string[]; admins?: string[];
name?: string; name?: string;
removed: boolean; removed: boolean;
@ -32,7 +40,6 @@ export class GroupChats {
private addMessage: (message: ChatMessage, sender: string) => void; private addMessage: (message: ChatMessage, sender: string) => void;
public chats: GroupChatsType = {}; public chats: GroupChatsType = {};
/** /**
* GroupChats holds a list of private chats and listens to their status broadcast * 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( const wakuMessage = await WakuMessage.fromBytes(
chatMessage.encode(), chatMessage.encode(),
await getNegotiatedTopic(this.identity, member) member.topic,
{ sigPrivKey: this.identity.privateKey, symKey: member.symKey }
); );
this.waku.relay.send(wakuMessage); this.waku.relay.send(wakuMessage);
}) })
@ -106,10 +114,19 @@ export class GroupChats {
if (signer) { if (signer) {
switch (event.event.type) { switch (event.event.type) {
case MembershipUpdateEvent_EventType.CHAT_CREATED: { 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( await this.addChat(
{ {
chatId: chatId, chatId: chatId,
members: event.event.members, members,
admins: [signer], admins: [signer],
removed: false, removed: false,
}, },
@ -120,7 +137,7 @@ export class GroupChats {
case MembershipUpdateEvent_EventType.MEMBER_REMOVED: { case MembershipUpdateEvent_EventType.MEMBER_REMOVED: {
if (chat) { if (chat) {
chat.members = chat.members.filter( chat.members = chat.members.filter(
(member) => !event.event.members.includes(member) (member) => !event.event.members.includes(member.id)
); );
if (event.event.members.includes(thisUser)) { if (event.event.members.includes(thisUser)) {
await this.removeChat( await this.removeChat(
@ -140,8 +157,19 @@ export class GroupChats {
} }
case MembershipUpdateEvent_EventType.MEMBERS_ADDED: { case MembershipUpdateEvent_EventType.MEMBERS_ADDED: {
if (chat && chat.admins?.includes(signer)) { if (chat && chat.admins?.includes(signer)) {
chat.members.push(...event.event.members); const members: GroupMember[] = [];
if (chat.members.includes(thisUser)) { 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; chat.removed = false;
await this.addChat(chat, useCallback); await this.addChat(chat, useCallback);
} }
@ -196,7 +224,11 @@ export class GroupChats {
const chatMessage = ChatMessage.decode(message.payload); const chatMessage = ChatMessage.decode(message.payload);
if (chatMessage) { if (chatMessage) {
if (chatMessage.chatId === chat.chatId) { 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"; const observerFunction = removeObserver ? "deleteObserver" : "addObserver";
await Promise.all( await Promise.all(
chat.members.map(async (member) => { chat.members.map(async (member) => {
const topic = await getNegotiatedTopic(this.identity, member); if (!removeObserver) {
this.waku.relay.addDecryptionKey(member.symKey);
}
this.waku.relay[observerFunction]( this.waku.relay[observerFunction](
(message) => this.handleWakuChatMessage(message, chat, member), (message) => this.handleWakuChatMessage(message, chat, member.id),
[topic] [member.topic]
); );
}) })
); );
@ -248,9 +282,8 @@ export class GroupChats {
} }
private async listen(): Promise<void> { private async listen(): Promise<void> {
const messages = await this.waku.store.queryHistory([ const topic = getPartitionedTopic(bufToHex(this.identity.publicKey));
getPartitionedTopic(bufToHex(this.identity.publicKey)), const messages = await this.waku.store.queryHistory([topic]);
]);
messages.sort((a, b) => messages.sort((a, b) =>
(a?.timestamp?.getTime() ?? 0) < (b?.timestamp?.getTime() ?? 0) ? -1 : 1 (a?.timestamp?.getTime() ?? 0) < (b?.timestamp?.getTime() ?? 0) ? -1 : 1
); );
@ -270,18 +303,18 @@ export class GroupChats {
); );
this.waku.relay.addObserver( this.waku.relay.addObserver(
(message) => this.decodeUpdateMessage(message, true), (message) => this.decodeUpdateMessage(message, true),
[getPartitionedTopic(bufToHex(this.identity.publicKey))] [topic]
); );
} }
private async sendUpdateMessage( private async sendUpdateMessage(
payload: Uint8Array, payload: Uint8Array,
members: string[] members: GroupMember[]
): Promise<void> { ): Promise<void> {
const wakuMessages = await Promise.all( const wakuMessages = await Promise.all(
members.map( members.map(
async (member) => async (member) =>
await WakuMessage.fromBytes(payload, getPartitionedTopic(member)) await WakuMessage.fromBytes(payload, member.partitionedTopic)
) )
); );
wakuMessages.forEach((msg) => this.waku.relay.send(msg)); wakuMessages.forEach((msg) => this.waku.relay.send(msg));
@ -314,10 +347,23 @@ export class GroupChats {
const payload = MembershipUpdateMessage.create(chatId, this.identity); const payload = MembershipUpdateMessage.create(chatId, this.identity);
const chat = this.chats[chatId]; const chat = this.chats[chatId];
if (chat && payload) { if (chat && payload) {
const newMembers = members.filter( const newMembers: GroupMember[] = [];
(member) => !chat.members.includes(member)
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(), [ await this.sendUpdateMessage(payload.encode(), [
...chat.members, ...chat.members,
...newMembers, ...newMembers,
@ -335,7 +381,19 @@ export class GroupChats {
this.identity, this.identity,
members members
).encode(); ).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( await Promise.all(
chat.members.map(async (member) => { chat.members.map(async (member) => {
const topic = await getNegotiatedTopic(this.identity, member);
const msgLength = ( const msgLength = (
await this.waku.store.queryHistory([topic], { await this.waku.store.queryHistory([member.topic], {
timeFilter: { startTime, endTime }, timeFilter: { startTime, endTime },
callback: (msg) => _callback(msg, member), callback: (msg) => _callback(msg, member.id),
}) })
).length; ).length;
amountOfMessages.push(msgLength); amountOfMessages.push(msgLength);

View File

@ -1,5 +1,8 @@
import { ec } from "elliptic";
import { utils } from "js-waku"; import { utils } from "js-waku";
const EC = new ec("secp256k1");
const hexToBuf = utils.hexToBuf; const hexToBuf = utils.hexToBuf;
export { hexToBuf }; export { hexToBuf };
@ -9,3 +12,8 @@ export { hexToBuf };
export function bufToHex(buf: Uint8Array): string { export function bufToHex(buf: Uint8Array): string {
return "0x" + utils.bufToHex(buf); return "0x" + utils.bufToHex(buf);
} }
export function compressPublicKey(key: Uint8Array): string {
const PubKey = EC.keyFromPublic(key);
return "0x" + PubKey.getPublic(true, "hex");
}