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 EditModal = () => {
const { activeChannel } = useMessengerContext();
const { activeChannel, changeGroupChatName } = useMessengerContext();
const [groupName, setGroupName] = useState("");
const [image, setImage] = useState("");
@ -34,7 +34,7 @@ export const EditModal = () => {
const handleUpload = () => {
activeChannel.icon = image;
activeChannel.name = groupName;
changeGroupChatName(groupName, activeChannel.id);
setModal(false);
};

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,4 +1,6 @@
import { keccak256 } from "js-sha3";
import { Reader } from "protobufjs";
import * as secp256k1 from "secp256k1";
import { v4 as uuidV4 } from "uuid";
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 {
private clock: number = Date.now();
private identity: Identity = Identity.generate();
@ -150,15 +181,13 @@ export class MembershipUpdateMessage {
return new MembershipUpdateMessage(protoBuf);
}
public get events(): {
sig: Uint8Array;
event: MembershipUpdateEvent;
}[] {
public get events(): MembershipSignedEvent[] {
return this.proto.events.map((bufArray) => {
return {
sig: bufArray.slice(0, 65),
event: MembershipUpdateEvent.decode(bufArray.slice(65)),
};
return new MembershipSignedEvent(
bufArray.slice(0, 65),
MembershipUpdateEvent.decode(bufArray.slice(65)),
this.chatId
);
});
}