Add group chat name change handling (#143)

This commit is contained in:
Szymon Szlachtowicz 2021-11-30 14:05:05 +01:00 committed by GitHub
parent 49417a5c25
commit daabab46ed
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 133 additions and 68 deletions

View File

@ -20,7 +20,7 @@ import {
export const EditModalName = "editModal"; export const EditModalName = "editModal";
export const EditModal = () => { export const EditModal = () => {
const { activeChannel } = useMessengerContext(); const { activeChannel, changeGroupChatName } = useMessengerContext();
const [groupName, setGroupName] = useState(""); const [groupName, setGroupName] = useState("");
const [image, setImage] = useState(""); const [image, setImage] = useState("");
@ -34,7 +34,7 @@ export const EditModal = () => {
const handleUpload = () => { const handleUpload = () => {
activeChannel.icon = image; activeChannel.icon = image;
activeChannel.name = groupName; changeGroupChatName(groupName, activeChannel.id);
setModal(false); setModal(false);
}; };

View File

@ -26,6 +26,7 @@ const MessengerContext = createContext<MessengerType>({
removeChannel: () => undefined, removeChannel: () => undefined,
setActiveChannel: () => undefined, setActiveChannel: () => undefined,
createGroupChat: () => undefined, createGroupChat: () => undefined,
changeGroupChatName: () => undefined,
}); });
export function useMessengerContext() { export function useMessengerContext() {

View File

@ -34,7 +34,7 @@ export function useGroupChats(
const members = chat.members.map(contactFromId); const members = chat.members.map(contactFromId);
const channel: ChannelData = { const channel: ChannelData = {
id: chat.chatId, id: chat.chatId,
name: chat.chatId, name: chat.name ?? chat.chatId,
type: "group", type: "group",
members: members, members: members,
}; };
@ -78,6 +78,15 @@ export function useGroupChats(
[groupChat] [groupChat]
); );
const changeGroupChatName = useCallback(
(name: string, chatId: string) => {
if (groupChat) {
groupChat.changeChatName(chatId, name);
}
},
[groupChat]
);
const removeChannel = useCallback( const removeChannel = useCallback(
(channelId: string) => { (channelId: string) => {
if (groupChat) { if (groupChat) {
@ -87,5 +96,5 @@ export function useGroupChats(
[channels, groupChat] [channels, groupChat]
); );
return { createGroupChat, removeChannel, groupChat }; return { createGroupChat, removeChannel, groupChat, changeGroupChatName };
} }

View File

@ -43,6 +43,7 @@ export type MessengerType = {
activeChannel: ChannelData; activeChannel: ChannelData;
setActiveChannel: (channel: ChannelData) => void; setActiveChannel: (channel: ChannelData) => void;
createGroupChat: (members: string[]) => void; createGroupChat: (members: string[]) => void;
changeGroupChatName: (name: string, chatId: string) => void;
}; };
function useCreateMessenger(identity: Identity | undefined) { function useCreateMessenger(identity: Identity | undefined) {
@ -167,14 +168,15 @@ export function useMessenger(
}); });
}, [contacts]); }, [contacts]);
const { groupChat, removeChannel, createGroupChat } = useGroupChats( const { groupChat, removeChannel, createGroupChat, changeGroupChatName } =
messenger, useGroupChats(
identity, messenger,
setChannels, identity,
setActiveChannel, setChannels,
addChatMessage, setActiveChannel,
channels addChatMessage,
); channels
);
const { loadPrevDay, loadingMessages } = useLoadPrevDay( const { loadPrevDay, loadingMessages } = useLoadPrevDay(
activeChannel.id, activeChannel.id,
@ -232,5 +234,6 @@ export function useMessenger(
mentions, mentions,
clearMentions, clearMentions,
createGroupChat, createGroupChat,
changeGroupChatName,
}; };
} }

View File

@ -11,6 +11,8 @@ import { ChatMessage, ContentType } from ".";
export type GroupChat = { export type GroupChat = {
chatId: string; chatId: string;
members: string[]; members: string[];
admins?: string[];
name?: string;
}; };
export type GroupChatsType = { export type GroupChatsType = {
@ -91,37 +93,52 @@ export class GroupChats {
const membershipUpdate = MembershipUpdateMessage.decode( const membershipUpdate = MembershipUpdateMessage.decode(
message?.payload message?.payload
); );
if (membershipUpdate.events.length > 0) { await Promise.all(
if ( membershipUpdate.events.map(async (event) => {
membershipUpdate.events[0].event.type == const bufSigner = event.signer;
MembershipUpdateEvent_EventType.CHAT_CREATED const signer = bufSigner ? bufToHex(bufSigner) : "";
) { const chatId = membershipUpdate.chatId;
await this.addChat( if (signer) {
{ switch (event.event.type) {
chatId: membershipUpdate.chatId, case MembershipUpdateEvent_EventType.CHAT_CREATED: {
members: membershipUpdate.events[0].event.members, await this.addChat(
}, {
useCallback chatId: chatId,
); members: event.event.members,
} admins: [signer],
if ( },
membershipUpdate.events[0].event.type == useCallback
MembershipUpdateEvent_EventType.MEMBER_REMOVED );
) { break;
if ( }
membershipUpdate.events[0].event.members[0] == case MembershipUpdateEvent_EventType.MEMBER_REMOVED: {
bufToHex(this.identity.publicKey) if (
) { event.event.members[0] == bufToHex(this.identity.publicKey)
await this.removeChat( ) {
{ await this.removeChat(
chatId: membershipUpdate.chatId, {
members: membershipUpdate.events[0].event.members, chatId: chatId,
}, members: event.event.members,
useCallback },
); useCallback
);
}
break;
}
case MembershipUpdateEvent_EventType.NAME_CHANGED: {
const chat = this.chats[chatId];
if (chat) {
if (chat.admins?.includes(signer)) {
chat.name = event.event.name;
this.callback(chat);
}
}
break;
}
}
} }
} })
} );
} }
} catch { } catch {
return; return;
@ -207,6 +224,28 @@ export class GroupChats {
); );
} }
private async sendUpdateMessage(
payload: Uint8Array,
members: string[]
): Promise<void> {
const wakuMessages = await Promise.all(
members.map(
async (member) =>
await WakuMessage.fromBytes(payload, getPartitionedTopic(member))
)
);
wakuMessages.forEach((msg) => this.waku.relay.send(msg));
}
public async changeChatName(chatId: string, name: string): Promise<void> {
const payload = MembershipUpdateMessage.create(chatId, this.identity);
const chat = this.chats[chatId];
if (chat && payload) {
payload.addNameChangeEvent(name);
await this.sendUpdateMessage(payload.encode(), chat.members);
}
}
/** /**
* Sends a create group chat membership update message with given members * Sends a create group chat membership update message with given members
* *
@ -217,13 +256,7 @@ export class GroupChats {
this.identity, this.identity,
members members
).encode(); ).encode();
const wakuMessages = await Promise.all( await this.sendUpdateMessage(payload, members);
members.map(
async (member) =>
await WakuMessage.fromBytes(payload, getPartitionedTopic(member))
)
);
wakuMessages.forEach((msg) => this.waku.relay.send(msg));
} }
/** /**
@ -235,16 +268,7 @@ 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];
payload.addMemberRemovedEvent(bufToHex(this.identity.publicKey)); payload.addMemberRemovedEvent(bufToHex(this.identity.publicKey));
const wakuMessages = await Promise.all( await this.sendUpdateMessage(payload.encode(), chat.members);
chat.members.map(
async (member) =>
await WakuMessage.fromBytes(
payload.encode(),
getPartitionedTopic(member)
)
)
);
wakuMessages.forEach((msg) => this.waku.relay.send(msg));
} }
/** /**

View File

@ -19,7 +19,6 @@ export class Identity {
*/ */
public sign(payload: Uint8Array): Uint8Array { public sign(payload: Uint8Array): Uint8Array {
const hash = keccak256(payload); const hash = keccak256(payload);
const { signature, recid } = secp256k1.ecdsaSign( const { signature, recid } = secp256k1.ecdsaSign(
hexToBuf(hash), hexToBuf(hash),
this.privateKey this.privateKey

View File

@ -1,4 +1,6 @@
import { keccak256 } from "js-sha3";
import { Reader } from "protobufjs"; import { Reader } from "protobufjs";
import * as secp256k1 from "secp256k1";
import { v4 as uuidV4 } from "uuid"; import { v4 as uuidV4 } from "uuid";
import { Identity } from ".."; import { Identity } from "..";
@ -34,6 +36,35 @@ export class MembershipUpdateEvent {
} }
} }
export class MembershipSignedEvent {
public sig: Uint8Array;
public event: MembershipUpdateEvent;
private chatId: string;
public constructor(
sig: Uint8Array,
event: MembershipUpdateEvent,
chatId: string
) {
this.sig = sig;
this.event = event;
this.chatId = chatId;
}
public get signer(): Uint8Array | undefined {
const encEvent = this.event.encode();
const eventToSign = Buffer.concat([hexToBuf(this.chatId), encEvent]);
if (!this.sig || !eventToSign) return;
const signature = this.sig.slice(0, 64);
const recid = this.sig.slice(64)[0];
const hash = keccak256(eventToSign);
return secp256k1.ecdsaRecover(signature, recid, hexToBuf(hash));
}
}
export class MembershipUpdateMessage { export class MembershipUpdateMessage {
private clock: number = Date.now(); private clock: number = Date.now();
private identity: Identity = Identity.generate(); private identity: Identity = Identity.generate();
@ -150,15 +181,13 @@ export class MembershipUpdateMessage {
return new MembershipUpdateMessage(protoBuf); return new MembershipUpdateMessage(protoBuf);
} }
public get events(): { public get events(): MembershipSignedEvent[] {
sig: Uint8Array;
event: MembershipUpdateEvent;
}[] {
return this.proto.events.map((bufArray) => { return this.proto.events.map((bufArray) => {
return { return new MembershipSignedEvent(
sig: bufArray.slice(0, 65), bufArray.slice(0, 65),
event: MembershipUpdateEvent.decode(bufArray.slice(65)), MembershipUpdateEvent.decode(bufArray.slice(65)),
}; this.chatId
);
}); });
} }