Add group chat name change handling (#143)
This commit is contained in:
parent
49417a5c25
commit
daabab46ed
|
@ -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);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -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() {
|
||||||
|
|
|
@ -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 };
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -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));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue