Add private group chat handling (#136)

This commit is contained in:
Szymon Szlachtowicz 2021-11-26 12:24:37 +01:00 committed by GitHub
parent 77dfd154b2
commit 30984cdc05
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
34 changed files with 2336 additions and 701 deletions

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

File diff suppressed because one or more lines are too long

768
.yarn/releases/yarn-3.1.0.cjs vendored Executable file

File diff suppressed because one or more lines are too long

View File

@ -1,2 +1,3 @@
yarnPath: .yarn/releases/yarn-3.0.2.cjs
nodeLinker: node-modules # Needed to work with Mocha, see https://github.com/yarnpkg/berry/issues/638
nodeLinker: node-modules
yarnPath: .yarn/releases/yarn-3.1.0.cjs

View File

@ -1,6 +1,6 @@
{
"name": "dappconnect-sdks",
"packageManager": "yarn@3.0.2",
"packageManager": "yarn@3.1.0",
"license": "MIT OR Apache-2.0",
"scripts": {
"fix": "run-s 'fix:*' && wsrun -e -c -s fix",

View File

@ -20,13 +20,16 @@
"dependencies": {
"@dappconnect/react-chat": "^0.1.0",
"assert": "^2.0.0",
"browserify-zlib": "^0.2.0",
"buffer": "^6.0.3",
"crypto-browserify": "^3.12.0",
"https-browserify": "^1.0.0",
"process": "^0.11.10",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"react-router-dom": "^5.2.0",
"stream-browserify": "^3.0.0",
"stream-http": "^3.2.0",
"styled-components": "^5.3.1"
},
"devDependencies": {

View File

@ -25,6 +25,9 @@ module.exports = env => {
crypto: require.resolve('crypto-browserify'),
stream: require.resolve('stream-browserify'),
assert: require.resolve('assert'),
http: require.resolve('stream-http'),
https: require.resolve('https-browserify'),
zlib: require.resolve('browserify-zlib')
},
},
module: {

View File

@ -18,8 +18,7 @@ export function ChatCreation({ editGroup }: ChatCreationProps) {
const identity = useIdentity();
const [query, setQuery] = useState("");
const [styledGroup, setStyledGroup] = useState<string[]>([]);
const { contacts, setChannel } = useMessengerContext();
const { contacts, createGroupChat } = useMessengerContext();
const setChatState = useChatState()[1];
const addMember = useCallback(
@ -40,19 +39,9 @@ export function ChatCreation({ editGroup }: ChatCreationProps) {
};
const createChat = (group: string[]) => {
group.length > 1
? setChannel({
id: group.join(""),
name: group.join(", "),
type: "group",
description: `${group.length + 1} members`,
})
: setChannel({
id: group[0],
name: group[0],
type: "dm",
description: "Contact",
});
const newGroup = group.slice();
newGroup.push(bufToHex(identity.publicKey));
group.length > 1 ? createGroupChat(newGroup) : createGroupChat(newGroup);
setChatState(ChatState.ChatBody);
};

View File

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

View File

@ -0,0 +1,91 @@
import { useCallback, useMemo } from "react";
import {
GroupChat,
GroupChats,
Identity,
Messenger,
ChatMessage as StatusChatMessage,
} from "status-communities/dist/cjs";
import { ChannelData, ChannelsData } from "../../models/ChannelData";
import { ChatMessage } from "../../models/ChatMessage";
import { Contact } from "../../models/Contact";
export function useGroupChats(
messenger: Messenger | undefined,
identity: Identity | undefined,
setChannels: React.Dispatch<React.SetStateAction<ChannelsData>>,
setActiveChannel: (channel: ChannelData) => void,
addChatMessage: (newMessage: ChatMessage | undefined, id: string) => void,
channels: ChannelsData
) {
const groupChat = useMemo(() => {
if (messenger && identity) {
const addChat = (chat: GroupChat) => {
const contactFromId = (member: string): Contact => {
return {
blocked: false,
id: member,
isUntrustworthy: false,
online: false,
trueName: member,
};
};
const members = chat.members.map(contactFromId);
const channel: ChannelData = {
id: chat.chatId,
name: chat.chatId,
type: "group",
members: members,
};
setChannels((prev) => {
return { ...prev, [channel.id]: channel };
});
setActiveChannel(channel);
};
const removeChat = (chat: GroupChat) => {
setChannels((prev) => {
delete prev[chat.chatId];
return prev;
});
setActiveChannel({
id: "",
name: "",
type: "channel",
} as ChannelData);
};
const handleMessage = (msg: StatusChatMessage, sender: string) =>
addChatMessage(
new ChatMessage(msg.text ?? "", new Date(msg.clock ?? 0), sender),
msg.chatId
);
return new GroupChats(
identity,
messenger.waku,
addChat,
removeChat,
handleMessage
);
}
}, [messenger, identity]);
const createGroupChat = useCallback(
(members: string[]) => {
if (groupChat) {
groupChat.createGroupChat(members);
}
},
[groupChat]
);
const removeChannel = useCallback(
(channelId: string) => {
if (groupChat) {
groupChat.quitChat(channelId);
}
},
[channels, groupChat]
);
return { createGroupChat, removeChannel, groupChat };
}

View File

@ -24,9 +24,8 @@ export function useMessages(
const mentions = useNotifications();
const addMessage = useCallback(
(msg: ApplicationMetadataMessage, id: string, date: Date) => {
const newMessage = ChatMessage.fromMetadataMessage(msg, date);
const addChatMessage = useCallback(
(newMessage: ChatMessage | undefined, id: string) => {
if (newMessage) {
contacts?.addContact(newMessage.sender);
setMessages((prev) => {
@ -52,6 +51,14 @@ export function useMessages(
[contacts, identity]
);
const addMessage = useCallback(
(msg: ApplicationMetadataMessage, id: string, date: Date) => {
const newMessage = ChatMessage.fromMetadataMessage(msg, date);
addChatMessage(newMessage, id);
},
[contacts, identity]
);
const activeMessages = useMemo(
() => messages?.[chatId] ?? [],
[messages, chatId]
@ -64,5 +71,6 @@ export function useMessages(
clearNotifications,
mentions: mentions.notifications,
clearMentions: mentions.clearNotifications,
addChatMessage,
};
}

View File

@ -15,6 +15,7 @@ import { createCommunity } from "../../utils/createCommunity";
import { createMessenger } from "../../utils/createMessenger";
import { uintToImgUrl } from "../../utils/uintToImgUrl";
import { useGroupChats } from "./useGroupChats";
import { useLoadPrevDay } from "./useLoadPrevDay";
import { useMessages } from "./useMessages";
@ -39,6 +40,7 @@ export type MessengerType = {
removeChannel: (channelId: string) => void;
activeChannel: ChannelData;
setActiveChannel: (channel: ChannelData) => void;
createGroupChat: (members: string[]) => void;
};
export function useMessenger(
@ -98,6 +100,7 @@ export function useMessenger(
}, [internalContacts]);
const {
addChatMessage,
addMessage,
clearNotifications,
notifications,
@ -132,25 +135,6 @@ export function useMessenger(
}
}, [messenger, community]);
const sendMessage = useCallback(
async (messageText?: string, image?: Uint8Array) => {
if (messageText) {
await messenger?.sendMessage(chatId, {
text: messageText,
contentType: 0,
});
}
if (image) {
await messenger?.sendMessage(chatId, {
image,
imageType: 1,
contentType: 2,
});
}
},
[chatId, messenger]
);
const [channels, setChannels] = useState<ChannelsData>({});
const setChannel = useCallback((channel: ChannelData) => {
@ -160,21 +144,6 @@ export function useMessenger(
setActiveChannel(channel);
}, []);
const removeChannel = useCallback(
(channelId: string) => {
setChannels((prev) => {
delete prev[channelId];
return prev;
});
const newActiveChannel: ChannelData = Object.values(channels)?.[0] ?? {
id: "",
name: "",
};
setActiveChannel(newActiveChannel);
},
[channels]
);
useEffect(() => {
if (community?.chats) {
for (const chat of community.chats.values()) {
@ -223,6 +192,38 @@ export function useMessenger(
}
}, [community]);
const { groupChat, removeChannel, createGroupChat } = useGroupChats(
messenger,
identity,
setChannels,
setActiveChannel,
addChatMessage,
channels
);
const sendMessage = useCallback(
async (messageText?: string, image?: Uint8Array) => {
if (activeChannel.type === "group") {
groupChat?.sendMessage(activeChannel.id, messageText ?? "");
} else {
if (messageText) {
await messenger?.sendMessage(activeChannel.id, {
text: messageText,
contentType: 0,
});
}
if (image) {
await messenger?.sendMessage(activeChannel.id, {
image,
imageType: 1,
contentType: 2,
});
}
}
},
[messenger, groupChat, activeChannel]
);
return {
messenger,
messages,
@ -241,5 +242,6 @@ export function useMessenger(
setActiveChannel,
mentions,
clearMentions,
createGroupChat,
};
}

View File

@ -6,7 +6,7 @@
"version": "0.1.0",
"repository": "https://github.com/status-im/dappconnect-chat-sdk/",
"license": "MIT OR Apache-2.0",
"packageManager": "yarn@3.0.1",
"packageManager": "yarn@3.1.0",
"scripts": {
"build": "run-s 'build:*'",
"build:esm": "tsc --module es2020 --target es2017 --outDir dist/esm",
@ -23,10 +23,13 @@
"proto:build": "buf generate"
},
"devDependencies": {
"@types/bn.js": "^5.1.0",
"@types/chai": "^4.2.22",
"@types/elliptic": "^6.4.14",
"@types/mocha": "^9.0.0",
"@types/pbkdf2": "^3.1.0",
"@types/secp256k1": "^4.0.3",
"@types/uuid": "^8.3.3",
"@typescript-eslint/eslint-plugin": "^4.31.1",
"@typescript-eslint/parser": "^4.31.1",
"chai": "^4.3.4",
@ -44,12 +47,15 @@
"typescript": "^4.4.3"
},
"dependencies": {
"bn.js": "^5.2.0",
"buffer": "^6.0.3",
"ecies-geth": "^1.5.3",
"elliptic": "^6.5.4",
"js-sha3": "^0.8.0",
"js-waku": "^0.13.1",
"pbkdf2": "^3.1.2",
"protobufjs": "^6.11.2",
"secp256k1": "^4.0.2"
"secp256k1": "^4.0.2",
"uuid": "^8.3.2"
}
}

View File

@ -0,0 +1,39 @@
syntax = "proto3";
package communities.v1;
import "communities/v1/enums.proto";
message EmojiReaction {
// clock Lamport timestamp of the chat message
uint64 clock = 1;
// chat_id the ID of the chat the message belongs to, for query efficiency the chat_id is stored in the db even though the
// target message also stores the chat_id
string chat_id = 2;
// message_id the ID of the target message that the user wishes to react to
string message_id = 3;
// message_type is (somewhat confusingly) the ID of the type of chat the message belongs to
MessageType message_type = 4;
// type the ID of the emoji the user wishes to react with
Type type = 5;
enum Type {
UNKNOWN_EMOJI_REACTION_TYPE = 0;
LOVE = 1;
THUMBS_UP = 2;
THUMBS_DOWN = 3;
LAUGH = 4;
SAD = 5;
ANGRY = 6;
}
// whether this is a rectraction of a previously sent emoji
bool retracted = 6;
// Grant for organisation chat messages
bytes grant = 7;
}

View File

@ -0,0 +1,45 @@
syntax = "proto3";
package communities.v1;
import "communities/v1/chat_message.proto";
import "communities/v1/emoji_reaction.proto";
message MembershipUpdateEvent {
// Lamport timestamp of the event
uint64 clock = 1;
// List of public keys of objects of the action
repeated string members = 2;
// Name of the chat for the CHAT_CREATED/NAME_CHANGED event types
string name = 3;
// The type of the event
EventType type = 4;
enum EventType {
UNKNOWN = 0;
CHAT_CREATED = 1;
NAME_CHANGED = 2;
MEMBERS_ADDED = 3;
MEMBER_JOINED = 4;
MEMBER_REMOVED = 5;
ADMINS_ADDED = 6;
ADMIN_REMOVED = 7;
}
}
// MembershipUpdateMessage is a message used to propagate information
// about group membership changes.
// For more information, see https://github.com/status-im/specs/blob/master/status-group-chats-spec.md.
message MembershipUpdateMessage {
// The chat id of the private group chat
string chat_id = 1;
// A list of events for this group chat, first x bytes are the signature, then is a
// protobuf encoded MembershipUpdateEvent
repeated bytes events = 2;
// An optional chat message
oneof chat_entity {
ChatMessage message = 3;
EmojiReaction emoji_reaction = 4;
}
}

View File

@ -0,0 +1,249 @@
import { Waku, WakuMessage } from "js-waku";
import { Identity } from "./identity";
import { MembershipUpdateEvent_EventType } from "./proto/communities/v1/membership_update_message";
import { getNegotiatedTopic, getPartitionedTopic } from "./topics";
import { bufToHex } from "./utils";
import { MembershipUpdateMessage } from "./wire/membership_update_message";
import { ChatMessage, ContentType } from ".";
export type GroupChat = {
chatId: string;
members: string[];
};
export type GroupChatsType = {
[id: string]: GroupChat;
};
/* TODO: add chat messages encryption */
export class GroupChats {
waku: Waku;
identity: Identity;
private callback: (chats: GroupChat) => void;
private removeCallback: (chats: GroupChat) => void;
private addMessage: (message: ChatMessage, sender: string) => void;
public chats: GroupChatsType = {};
/**
* GroupChats holds a list of private chats and listens to their status broadcast
*
* @param identity identity of user
*
* @param waku waku class used to listen to broadcast and broadcast status
*
* @param callback callback function called when new private group chat is ceated
*
* @param removeCallback callback function when private group chat is to be removed
*
* @param addMessage callback function when
*/
public constructor(
identity: Identity,
waku: Waku,
callback: (chat: GroupChat) => void,
removeCallback: (chat: GroupChat) => void,
addMessage: (message: ChatMessage, sender: string) => void
) {
this.waku = waku;
this.identity = identity;
this.callback = callback;
this.removeCallback = removeCallback;
this.addMessage = addMessage;
this.listen();
}
/**
* Send chat message on given private chat
*
* @param chatId chat id of private group chat
*
* @param text text message to send
*/
public async sendMessage(chatId: string, text: string): Promise<void> {
const now = Date.now();
const chat = this.chats[chatId];
if (chat) {
await Promise.all(
chat.members.map(async (member) => {
const chatMessage = ChatMessage.createMessage(now, now, chatId, {
text,
contentType: ContentType.Text,
});
const wakuMessage = await WakuMessage.fromBytes(
chatMessage.encode(),
await getNegotiatedTopic(this.identity, member)
);
this.waku.relay.send(wakuMessage);
})
);
}
}
private async decodeUpdateMessage(
message: WakuMessage,
useCallback: boolean
): Promise<void> {
try {
if (message.payload) {
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
);
}
}
}
}
} catch {
return;
}
}
private handleWakuChatMessage(
message: WakuMessage,
chat: GroupChat,
member: string
): void {
try {
if (message.payload) {
const chatMessage = ChatMessage.decode(message.payload);
if (chatMessage) {
if (chatMessage.chatId === chat.chatId) {
this.addMessage(chatMessage, member);
}
}
}
} catch {
return;
}
}
private async handleChatObserver(
chat: GroupChat,
removeObserver?: boolean
): Promise<void> {
const observerFunction = removeObserver ? "deleteObserver" : "addObserver";
await Promise.all(
chat.members.map(async (member) => {
const topic = await getNegotiatedTopic(this.identity, member);
this.waku.relay[observerFunction](
(message) => this.handleWakuChatMessage(message, chat, member),
[topic]
);
})
);
}
private async addChat(chat: GroupChat, useCallback: boolean): Promise<void> {
this.chats[chat.chatId] = chat;
if (useCallback) {
await this.handleChatObserver(chat);
this.callback(chat);
}
}
private async removeChat(
chat: GroupChat,
useCallback: boolean
): Promise<void> {
delete this.chats[chat.chatId];
if (useCallback) {
await this.handleChatObserver(chat, true);
this.removeCallback(chat);
}
}
private async listen(): Promise<void> {
const messages = await this.waku.store.queryHistory([
getPartitionedTopic(bufToHex(this.identity.publicKey)),
]);
messages.sort((a, b) =>
(a?.timestamp?.getTime() ?? 0) < (b?.timestamp?.getTime() ?? 0) ? -1 : 1
);
await Promise.all(
messages.map(
async (message) => await this.decodeUpdateMessage(message, false)
)
);
await Promise.all(
Object.values(this.chats).map(async (chat) => {
await this.handleChatObserver(chat);
this.callback(chat);
})
);
this.waku.relay.addObserver(
(message) => this.decodeUpdateMessage(message, true),
[getPartitionedTopic(bufToHex(this.identity.publicKey))]
);
}
/**
* Sends a create group chat membership update message with given members
*
* @param members a list of public keys of members to be included in private group chat
*/
public async createGroupChat(members: string[]): Promise<void> {
const payload = MembershipUpdateMessage.createChat(
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));
}
/**
* Sends a remove member to private group chat
*
* @param chatId id of private group chat
*/
public async quitChat(chatId: string): Promise<void> {
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));
}
}

View File

@ -3,6 +3,7 @@ export { Messenger } from "./messenger";
export { Community } from "./community";
export { Contacts } from "./contacts";
export { Chat } from "./chat";
export * from "./groupChats";
export * as utils from "./utils";
export { ApplicationMetadataMessage } from "./wire/application_metadata_message";
export {

View File

@ -0,0 +1,328 @@
/* eslint-disable */
import Long from "long";
import _m0 from "protobufjs/minimal";
import {
MessageType,
messageTypeFromJSON,
messageTypeToJSON,
} from "../../communities/v1/enums";
export const protobufPackage = "communities.v1";
export interface EmojiReaction {
/** clock Lamport timestamp of the chat message */
clock: number;
/**
* chat_id the ID of the chat the message belongs to, for query efficiency the chat_id is stored in the db even though the
* target message also stores the chat_id
*/
chatId: string;
/** message_id the ID of the target message that the user wishes to react to */
messageId: string;
/** message_type is (somewhat confusingly) the ID of the type of chat the message belongs to */
messageType: MessageType;
/** type the ID of the emoji the user wishes to react with */
type: EmojiReaction_Type;
/** whether this is a rectraction of a previously sent emoji */
retracted: boolean;
/** Grant for organisation chat messages */
grant: Uint8Array;
}
export enum EmojiReaction_Type {
UNKNOWN_EMOJI_REACTION_TYPE = 0,
LOVE = 1,
THUMBS_UP = 2,
THUMBS_DOWN = 3,
LAUGH = 4,
SAD = 5,
ANGRY = 6,
UNRECOGNIZED = -1,
}
export function emojiReaction_TypeFromJSON(object: any): EmojiReaction_Type {
switch (object) {
case 0:
case "UNKNOWN_EMOJI_REACTION_TYPE":
return EmojiReaction_Type.UNKNOWN_EMOJI_REACTION_TYPE;
case 1:
case "LOVE":
return EmojiReaction_Type.LOVE;
case 2:
case "THUMBS_UP":
return EmojiReaction_Type.THUMBS_UP;
case 3:
case "THUMBS_DOWN":
return EmojiReaction_Type.THUMBS_DOWN;
case 4:
case "LAUGH":
return EmojiReaction_Type.LAUGH;
case 5:
case "SAD":
return EmojiReaction_Type.SAD;
case 6:
case "ANGRY":
return EmojiReaction_Type.ANGRY;
case -1:
case "UNRECOGNIZED":
default:
return EmojiReaction_Type.UNRECOGNIZED;
}
}
export function emojiReaction_TypeToJSON(object: EmojiReaction_Type): string {
switch (object) {
case EmojiReaction_Type.UNKNOWN_EMOJI_REACTION_TYPE:
return "UNKNOWN_EMOJI_REACTION_TYPE";
case EmojiReaction_Type.LOVE:
return "LOVE";
case EmojiReaction_Type.THUMBS_UP:
return "THUMBS_UP";
case EmojiReaction_Type.THUMBS_DOWN:
return "THUMBS_DOWN";
case EmojiReaction_Type.LAUGH:
return "LAUGH";
case EmojiReaction_Type.SAD:
return "SAD";
case EmojiReaction_Type.ANGRY:
return "ANGRY";
default:
return "UNKNOWN";
}
}
const baseEmojiReaction: object = {
clock: 0,
chatId: "",
messageId: "",
messageType: 0,
type: 0,
retracted: false,
};
export const EmojiReaction = {
encode(
message: EmojiReaction,
writer: _m0.Writer = _m0.Writer.create()
): _m0.Writer {
if (message.clock !== 0) {
writer.uint32(8).uint64(message.clock);
}
if (message.chatId !== "") {
writer.uint32(18).string(message.chatId);
}
if (message.messageId !== "") {
writer.uint32(26).string(message.messageId);
}
if (message.messageType !== 0) {
writer.uint32(32).int32(message.messageType);
}
if (message.type !== 0) {
writer.uint32(40).int32(message.type);
}
if (message.retracted === true) {
writer.uint32(48).bool(message.retracted);
}
if (message.grant.length !== 0) {
writer.uint32(58).bytes(message.grant);
}
return writer;
},
decode(input: _m0.Reader | Uint8Array, length?: number): EmojiReaction {
const reader = input instanceof _m0.Reader ? input : new _m0.Reader(input);
let end = length === undefined ? reader.len : reader.pos + length;
const message = { ...baseEmojiReaction } as EmojiReaction;
message.grant = new Uint8Array();
while (reader.pos < end) {
const tag = reader.uint32();
switch (tag >>> 3) {
case 1:
message.clock = longToNumber(reader.uint64() as Long);
break;
case 2:
message.chatId = reader.string();
break;
case 3:
message.messageId = reader.string();
break;
case 4:
message.messageType = reader.int32() as any;
break;
case 5:
message.type = reader.int32() as any;
break;
case 6:
message.retracted = reader.bool();
break;
case 7:
message.grant = reader.bytes();
break;
default:
reader.skipType(tag & 7);
break;
}
}
return message;
},
fromJSON(object: any): EmojiReaction {
const message = { ...baseEmojiReaction } as EmojiReaction;
message.grant = new Uint8Array();
if (object.clock !== undefined && object.clock !== null) {
message.clock = Number(object.clock);
} else {
message.clock = 0;
}
if (object.chatId !== undefined && object.chatId !== null) {
message.chatId = String(object.chatId);
} else {
message.chatId = "";
}
if (object.messageId !== undefined && object.messageId !== null) {
message.messageId = String(object.messageId);
} else {
message.messageId = "";
}
if (object.messageType !== undefined && object.messageType !== null) {
message.messageType = messageTypeFromJSON(object.messageType);
} else {
message.messageType = 0;
}
if (object.type !== undefined && object.type !== null) {
message.type = emojiReaction_TypeFromJSON(object.type);
} else {
message.type = 0;
}
if (object.retracted !== undefined && object.retracted !== null) {
message.retracted = Boolean(object.retracted);
} else {
message.retracted = false;
}
if (object.grant !== undefined && object.grant !== null) {
message.grant = bytesFromBase64(object.grant);
}
return message;
},
toJSON(message: EmojiReaction): unknown {
const obj: any = {};
message.clock !== undefined && (obj.clock = message.clock);
message.chatId !== undefined && (obj.chatId = message.chatId);
message.messageId !== undefined && (obj.messageId = message.messageId);
message.messageType !== undefined &&
(obj.messageType = messageTypeToJSON(message.messageType));
message.type !== undefined &&
(obj.type = emojiReaction_TypeToJSON(message.type));
message.retracted !== undefined && (obj.retracted = message.retracted);
message.grant !== undefined &&
(obj.grant = base64FromBytes(
message.grant !== undefined ? message.grant : new Uint8Array()
));
return obj;
},
fromPartial(object: DeepPartial<EmojiReaction>): EmojiReaction {
const message = { ...baseEmojiReaction } as EmojiReaction;
if (object.clock !== undefined && object.clock !== null) {
message.clock = object.clock;
} else {
message.clock = 0;
}
if (object.chatId !== undefined && object.chatId !== null) {
message.chatId = object.chatId;
} else {
message.chatId = "";
}
if (object.messageId !== undefined && object.messageId !== null) {
message.messageId = object.messageId;
} else {
message.messageId = "";
}
if (object.messageType !== undefined && object.messageType !== null) {
message.messageType = object.messageType;
} else {
message.messageType = 0;
}
if (object.type !== undefined && object.type !== null) {
message.type = object.type;
} else {
message.type = 0;
}
if (object.retracted !== undefined && object.retracted !== null) {
message.retracted = object.retracted;
} else {
message.retracted = false;
}
if (object.grant !== undefined && object.grant !== null) {
message.grant = object.grant;
} else {
message.grant = new Uint8Array();
}
return message;
},
};
declare var self: any | undefined;
declare var window: any | undefined;
declare var global: any | undefined;
var globalThis: any = (() => {
if (typeof globalThis !== "undefined") return globalThis;
if (typeof self !== "undefined") return self;
if (typeof window !== "undefined") return window;
if (typeof global !== "undefined") return global;
throw "Unable to locate global object";
})();
const atob: (b64: string) => string =
globalThis.atob ||
((b64) => globalThis.Buffer.from(b64, "base64").toString("binary"));
function bytesFromBase64(b64: string): Uint8Array {
const bin = atob(b64);
const arr = new Uint8Array(bin.length);
for (let i = 0; i < bin.length; ++i) {
arr[i] = bin.charCodeAt(i);
}
return arr;
}
const btoa: (bin: string) => string =
globalThis.btoa ||
((bin) => globalThis.Buffer.from(bin, "binary").toString("base64"));
function base64FromBytes(arr: Uint8Array): string {
const bin: string[] = [];
for (const byte of arr) {
bin.push(String.fromCharCode(byte));
}
return btoa(bin.join(""));
}
type Builtin =
| Date
| Function
| Uint8Array
| string
| number
| boolean
| undefined;
export type DeepPartial<T> = T extends Builtin
? T
: T extends Array<infer U>
? Array<DeepPartial<U>>
: T extends ReadonlyArray<infer U>
? ReadonlyArray<DeepPartial<U>>
: T extends {}
? { [K in keyof T]?: DeepPartial<T[K]> }
: Partial<T>;
function longToNumber(long: Long): number {
if (long.gt(Number.MAX_SAFE_INTEGER)) {
throw new globalThis.Error("Value is larger than Number.MAX_SAFE_INTEGER");
}
return long.toNumber();
}
if (_m0.util.Long !== Long) {
_m0.util.Long = Long as any;
_m0.configure();
}

View File

@ -0,0 +1,436 @@
/* eslint-disable */
import Long from "long";
import _m0 from "protobufjs/minimal";
import { ChatMessage } from "../../communities/v1/chat_message";
import { EmojiReaction } from "../../communities/v1/emoji_reaction";
export const protobufPackage = "communities.v1";
export interface MembershipUpdateEvent {
/** Lamport timestamp of the event */
clock: number;
/** List of public keys of objects of the action */
members: string[];
/** Name of the chat for the CHAT_CREATED/NAME_CHANGED event types */
name: string;
/** The type of the event */
type: MembershipUpdateEvent_EventType;
}
export enum MembershipUpdateEvent_EventType {
UNKNOWN = 0,
CHAT_CREATED = 1,
NAME_CHANGED = 2,
MEMBERS_ADDED = 3,
MEMBER_JOINED = 4,
MEMBER_REMOVED = 5,
ADMINS_ADDED = 6,
ADMIN_REMOVED = 7,
UNRECOGNIZED = -1,
}
export function membershipUpdateEvent_EventTypeFromJSON(
object: any
): MembershipUpdateEvent_EventType {
switch (object) {
case 0:
case "UNKNOWN":
return MembershipUpdateEvent_EventType.UNKNOWN;
case 1:
case "CHAT_CREATED":
return MembershipUpdateEvent_EventType.CHAT_CREATED;
case 2:
case "NAME_CHANGED":
return MembershipUpdateEvent_EventType.NAME_CHANGED;
case 3:
case "MEMBERS_ADDED":
return MembershipUpdateEvent_EventType.MEMBERS_ADDED;
case 4:
case "MEMBER_JOINED":
return MembershipUpdateEvent_EventType.MEMBER_JOINED;
case 5:
case "MEMBER_REMOVED":
return MembershipUpdateEvent_EventType.MEMBER_REMOVED;
case 6:
case "ADMINS_ADDED":
return MembershipUpdateEvent_EventType.ADMINS_ADDED;
case 7:
case "ADMIN_REMOVED":
return MembershipUpdateEvent_EventType.ADMIN_REMOVED;
case -1:
case "UNRECOGNIZED":
default:
return MembershipUpdateEvent_EventType.UNRECOGNIZED;
}
}
export function membershipUpdateEvent_EventTypeToJSON(
object: MembershipUpdateEvent_EventType
): string {
switch (object) {
case MembershipUpdateEvent_EventType.UNKNOWN:
return "UNKNOWN";
case MembershipUpdateEvent_EventType.CHAT_CREATED:
return "CHAT_CREATED";
case MembershipUpdateEvent_EventType.NAME_CHANGED:
return "NAME_CHANGED";
case MembershipUpdateEvent_EventType.MEMBERS_ADDED:
return "MEMBERS_ADDED";
case MembershipUpdateEvent_EventType.MEMBER_JOINED:
return "MEMBER_JOINED";
case MembershipUpdateEvent_EventType.MEMBER_REMOVED:
return "MEMBER_REMOVED";
case MembershipUpdateEvent_EventType.ADMINS_ADDED:
return "ADMINS_ADDED";
case MembershipUpdateEvent_EventType.ADMIN_REMOVED:
return "ADMIN_REMOVED";
default:
return "UNKNOWN";
}
}
/**
* MembershipUpdateMessage is a message used to propagate information
* about group membership changes.
* For more information, see https://github.com/status-im/specs/blob/master/status-group-chats-spec.md.
*/
export interface MembershipUpdateMessage {
/** The chat id of the private group chat */
chatId: string;
/**
* A list of events for this group chat, first x bytes are the signature, then is a
* protobuf encoded MembershipUpdateEvent
*/
events: Uint8Array[];
message: ChatMessage | undefined;
emojiReaction: EmojiReaction | undefined;
}
const baseMembershipUpdateEvent: object = {
clock: 0,
members: "",
name: "",
type: 0,
};
export const MembershipUpdateEvent = {
encode(
message: MembershipUpdateEvent,
writer: _m0.Writer = _m0.Writer.create()
): _m0.Writer {
if (message.clock !== 0) {
writer.uint32(8).uint64(message.clock);
}
for (const v of message.members) {
writer.uint32(18).string(v!);
}
if (message.name !== "") {
writer.uint32(26).string(message.name);
}
if (message.type !== 0) {
writer.uint32(32).int32(message.type);
}
return writer;
},
decode(
input: _m0.Reader | Uint8Array,
length?: number
): MembershipUpdateEvent {
const reader = input instanceof _m0.Reader ? input : new _m0.Reader(input);
let end = length === undefined ? reader.len : reader.pos + length;
const message = { ...baseMembershipUpdateEvent } as MembershipUpdateEvent;
message.members = [];
while (reader.pos < end) {
const tag = reader.uint32();
switch (tag >>> 3) {
case 1:
message.clock = longToNumber(reader.uint64() as Long);
break;
case 2:
message.members.push(reader.string());
break;
case 3:
message.name = reader.string();
break;
case 4:
message.type = reader.int32() as any;
break;
default:
reader.skipType(tag & 7);
break;
}
}
return message;
},
fromJSON(object: any): MembershipUpdateEvent {
const message = { ...baseMembershipUpdateEvent } as MembershipUpdateEvent;
message.members = [];
if (object.clock !== undefined && object.clock !== null) {
message.clock = Number(object.clock);
} else {
message.clock = 0;
}
if (object.members !== undefined && object.members !== null) {
for (const e of object.members) {
message.members.push(String(e));
}
}
if (object.name !== undefined && object.name !== null) {
message.name = String(object.name);
} else {
message.name = "";
}
if (object.type !== undefined && object.type !== null) {
message.type = membershipUpdateEvent_EventTypeFromJSON(object.type);
} else {
message.type = 0;
}
return message;
},
toJSON(message: MembershipUpdateEvent): unknown {
const obj: any = {};
message.clock !== undefined && (obj.clock = message.clock);
if (message.members) {
obj.members = message.members.map((e) => e);
} else {
obj.members = [];
}
message.name !== undefined && (obj.name = message.name);
message.type !== undefined &&
(obj.type = membershipUpdateEvent_EventTypeToJSON(message.type));
return obj;
},
fromPartial(
object: DeepPartial<MembershipUpdateEvent>
): MembershipUpdateEvent {
const message = { ...baseMembershipUpdateEvent } as MembershipUpdateEvent;
message.members = [];
if (object.clock !== undefined && object.clock !== null) {
message.clock = object.clock;
} else {
message.clock = 0;
}
if (object.members !== undefined && object.members !== null) {
for (const e of object.members) {
message.members.push(e);
}
}
if (object.name !== undefined && object.name !== null) {
message.name = object.name;
} else {
message.name = "";
}
if (object.type !== undefined && object.type !== null) {
message.type = object.type;
} else {
message.type = 0;
}
return message;
},
};
const baseMembershipUpdateMessage: object = { chatId: "" };
export const MembershipUpdateMessage = {
encode(
message: MembershipUpdateMessage,
writer: _m0.Writer = _m0.Writer.create()
): _m0.Writer {
if (message.chatId !== "") {
writer.uint32(10).string(message.chatId);
}
for (const v of message.events) {
writer.uint32(18).bytes(v!);
}
if (message.message !== undefined) {
ChatMessage.encode(message.message, writer.uint32(26).fork()).ldelim();
}
if (message.emojiReaction !== undefined) {
EmojiReaction.encode(
message.emojiReaction,
writer.uint32(34).fork()
).ldelim();
}
return writer;
},
decode(
input: _m0.Reader | Uint8Array,
length?: number
): MembershipUpdateMessage {
const reader = input instanceof _m0.Reader ? input : new _m0.Reader(input);
let end = length === undefined ? reader.len : reader.pos + length;
const message = {
...baseMembershipUpdateMessage,
} as MembershipUpdateMessage;
message.events = [];
while (reader.pos < end) {
const tag = reader.uint32();
switch (tag >>> 3) {
case 1:
message.chatId = reader.string();
break;
case 2:
message.events.push(reader.bytes());
break;
case 3:
message.message = ChatMessage.decode(reader, reader.uint32());
break;
case 4:
message.emojiReaction = EmojiReaction.decode(reader, reader.uint32());
break;
default:
reader.skipType(tag & 7);
break;
}
}
return message;
},
fromJSON(object: any): MembershipUpdateMessage {
const message = {
...baseMembershipUpdateMessage,
} as MembershipUpdateMessage;
message.events = [];
if (object.chatId !== undefined && object.chatId !== null) {
message.chatId = String(object.chatId);
} else {
message.chatId = "";
}
if (object.events !== undefined && object.events !== null) {
for (const e of object.events) {
message.events.push(bytesFromBase64(e));
}
}
if (object.message !== undefined && object.message !== null) {
message.message = ChatMessage.fromJSON(object.message);
} else {
message.message = undefined;
}
if (object.emojiReaction !== undefined && object.emojiReaction !== null) {
message.emojiReaction = EmojiReaction.fromJSON(object.emojiReaction);
} else {
message.emojiReaction = undefined;
}
return message;
},
toJSON(message: MembershipUpdateMessage): unknown {
const obj: any = {};
message.chatId !== undefined && (obj.chatId = message.chatId);
if (message.events) {
obj.events = message.events.map((e) =>
base64FromBytes(e !== undefined ? e : new Uint8Array())
);
} else {
obj.events = [];
}
message.message !== undefined &&
(obj.message = message.message
? ChatMessage.toJSON(message.message)
: undefined);
message.emojiReaction !== undefined &&
(obj.emojiReaction = message.emojiReaction
? EmojiReaction.toJSON(message.emojiReaction)
: undefined);
return obj;
},
fromPartial(
object: DeepPartial<MembershipUpdateMessage>
): MembershipUpdateMessage {
const message = {
...baseMembershipUpdateMessage,
} as MembershipUpdateMessage;
message.events = [];
if (object.chatId !== undefined && object.chatId !== null) {
message.chatId = object.chatId;
} else {
message.chatId = "";
}
if (object.events !== undefined && object.events !== null) {
for (const e of object.events) {
message.events.push(e);
}
}
if (object.message !== undefined && object.message !== null) {
message.message = ChatMessage.fromPartial(object.message);
} else {
message.message = undefined;
}
if (object.emojiReaction !== undefined && object.emojiReaction !== null) {
message.emojiReaction = EmojiReaction.fromPartial(object.emojiReaction);
} else {
message.emojiReaction = undefined;
}
return message;
},
};
declare var self: any | undefined;
declare var window: any | undefined;
declare var global: any | undefined;
var globalThis: any = (() => {
if (typeof globalThis !== "undefined") return globalThis;
if (typeof self !== "undefined") return self;
if (typeof window !== "undefined") return window;
if (typeof global !== "undefined") return global;
throw "Unable to locate global object";
})();
const atob: (b64: string) => string =
globalThis.atob ||
((b64) => globalThis.Buffer.from(b64, "base64").toString("binary"));
function bytesFromBase64(b64: string): Uint8Array {
const bin = atob(b64);
const arr = new Uint8Array(bin.length);
for (let i = 0; i < bin.length; ++i) {
arr[i] = bin.charCodeAt(i);
}
return arr;
}
const btoa: (bin: string) => string =
globalThis.btoa ||
((bin) => globalThis.Buffer.from(bin, "binary").toString("base64"));
function base64FromBytes(arr: Uint8Array): string {
const bin: string[] = [];
for (const byte of arr) {
bin.push(String.fromCharCode(byte));
}
return btoa(bin.join(""));
}
type Builtin =
| Date
| Function
| Uint8Array
| string
| number
| boolean
| undefined;
export type DeepPartial<T> = T extends Builtin
? T
: T extends Array<infer U>
? Array<DeepPartial<U>>
: T extends ReadonlyArray<infer U>
? ReadonlyArray<DeepPartial<U>>
: T extends {}
? { [K in keyof T]?: DeepPartial<T[K]> }
: Partial<T>;
function longToNumber(long: Long): number {
if (long.gt(Number.MAX_SAFE_INTEGER)) {
throw new globalThis.Error("Value is larger than Number.MAX_SAFE_INTEGER");
}
return long.toNumber();
}
if (_m0.util.Long !== Long) {
_m0.util.Long = Long as any;
_m0.configure();
}

View File

@ -0,0 +1,46 @@
import { BN } from "bn.js";
import { derive } from "ecies-geth";
import { ec } from "elliptic";
import { bufToHex } from "js-waku/build/main/lib/utils";
import { idToContentTopic } from "./contentTopic";
import { hexToBuf } from "./utils";
import { Identity } from ".";
const EC = new ec("secp256k1");
const partitionsNum = new BN(5000);
/**
* Get the partitioned topic https://specs.status.im/spec/3#partitioned-topic
* @param publicKey Public key of recipient
* @returns string The Waku v2 Content Topic.
*/
export function getPartitionedTopic(publicKey: string): string {
const key = EC.keyFromPublic(publicKey.slice(2), "hex");
const X = key.getPublic().getX();
const partition = X.mod(partitionsNum);
const partitionTopic = `contact-discovery-${partition.toString()}`;
return idToContentTopic(partitionTopic);
}
/**
* Get the negotiated topic https://specs.status.im/spec/3#negotiated-topic
* @param identity identity of user
* @param publicKey Public key of recipient
* @returns string The Waku v2 Content Topic.
*/
export async function getNegotiatedTopic(
identity: Identity,
publicKey: string
): Promise<string> {
const key = EC.keyFromPublic(publicKey.slice(2), "hex");
const sharedSecret = await derive(
Buffer.from(identity.privateKey),
hexToBuf(key.getPublic("hex"))
);
return idToContentTopic(bufToHex(sharedSecret));
}

View File

@ -0,0 +1,172 @@
import { Reader } from "protobufjs";
import { v4 as uuidV4 } from "uuid";
import { Identity } from "..";
import * as proto from "../proto/communities/v1/membership_update_message";
import { bufToHex, hexToBuf } from "../utils";
export class MembershipUpdateEvent {
public constructor(public proto: proto.MembershipUpdateEvent) {}
static decode(bytes: Uint8Array): MembershipUpdateEvent {
const protoBuf = proto.MembershipUpdateEvent.decode(Reader.create(bytes));
return new MembershipUpdateEvent(protoBuf);
}
encode(): Uint8Array {
return proto.MembershipUpdateEvent.encode(this.proto).finish();
}
public get members(): string[] {
return this.proto.members;
}
public get name(): string {
return this.proto.name;
}
public get clock(): number {
return this.proto.clock;
}
public get type(): proto.MembershipUpdateEvent_EventType {
return this.proto.type;
}
}
export class MembershipUpdateMessage {
private clock: number = Date.now();
private identity: Identity = Identity.generate();
public constructor(public proto: proto.MembershipUpdateMessage) {}
public static create(
chatId: string,
identity: Identity
): MembershipUpdateMessage {
const partial = proto.MembershipUpdateMessage.fromPartial({
chatId,
events: [],
});
const newMessage = new MembershipUpdateMessage(partial);
newMessage.clock = Date.now();
newMessage.identity = identity;
return newMessage;
}
private addEvent(event: MembershipUpdateEvent): void {
const encEvent = event.encode();
const eventToSign = Buffer.concat([hexToBuf(this.proto.chatId), encEvent]);
const signature = this.identity.sign(eventToSign);
this.proto.events.push(Buffer.concat([signature, encEvent]));
}
public static createChat(
identity: Identity,
members: string[],
name?: string
): MembershipUpdateMessage {
const chatId = `${uuidV4()}-${bufToHex(identity.publicKey)}`;
const message = this.create(chatId, identity);
const type = proto.MembershipUpdateEvent_EventType.CHAT_CREATED;
const event = new MembershipUpdateEvent({
clock: message.clock,
members,
name: name ?? "",
type,
});
message.addEvent(event);
return message;
}
public addNameChangeEvent(name: string): void {
const type = proto.MembershipUpdateEvent_EventType.NAME_CHANGED;
const event = new MembershipUpdateEvent({
clock: this.clock,
members: [],
name: name,
type,
});
this.addEvent(event);
}
public addMembersAddedEvent(members: string[]): void {
const type = proto.MembershipUpdateEvent_EventType.MEMBERS_ADDED;
const event = new MembershipUpdateEvent({
clock: this.clock,
members,
name: "",
type,
});
this.addEvent(event);
}
public addMemberJoinedEvent(member: string): void {
const type = proto.MembershipUpdateEvent_EventType.MEMBER_JOINED;
const event = new MembershipUpdateEvent({
clock: this.clock,
members: [member],
name: "",
type,
});
this.addEvent(event);
}
public addMemberRemovedEvent(member: string): void {
const type = proto.MembershipUpdateEvent_EventType.MEMBER_REMOVED;
const event = new MembershipUpdateEvent({
clock: this.clock,
members: [member],
name: "",
type,
});
this.addEvent(event);
}
public addAdminsAddedEvent(members: string[]): void {
const type = proto.MembershipUpdateEvent_EventType.ADMINS_ADDED;
const event = new MembershipUpdateEvent({
clock: this.clock,
members,
name: "",
type,
});
this.addEvent(event);
}
public addAdminRemovedEvent(member: string): void {
const type = proto.MembershipUpdateEvent_EventType.ADMINS_ADDED;
const event = new MembershipUpdateEvent({
clock: this.clock,
members: [member],
name: "",
type,
});
this.addEvent(event);
}
static decode(bytes: Uint8Array): MembershipUpdateMessage {
const protoBuf = proto.MembershipUpdateMessage.decode(Reader.create(bytes));
return new MembershipUpdateMessage(protoBuf);
}
public get events(): {
sig: Uint8Array;
event: MembershipUpdateEvent;
}[] {
return this.proto.events.map((bufArray) => {
return {
sig: bufArray.slice(0, 65),
event: MembershipUpdateEvent.decode(bufArray.slice(65)),
};
});
}
public get chatId(): string {
return this.proto.chatId;
}
encode(): Uint8Array {
return proto.MembershipUpdateMessage.encode(this.proto).finish();
}
}

104
yarn.lock
View File

@ -2,7 +2,7 @@
# Manual changes might be lost - proceed with caution!
__metadata:
version: 4
version: 5
cacheKey: 8
"@babel/code-frame@npm:7.12.11":
@ -234,6 +234,7 @@ __metadata:
"@typescript-eslint/eslint-plugin": ^4.29.0
"@typescript-eslint/parser": ^4.29.0
assert: ^2.0.0
browserify-zlib: ^0.2.0
buffer: ^6.0.3
chai: ^4.3.4
crypto-browserify: ^3.12.0
@ -245,6 +246,7 @@ __metadata:
file-loader: ^6.2.0
fork-ts-checker-webpack-plugin: ^6.3.1
html-webpack-plugin: ^5.3.2
https-browserify: ^1.0.0
jsdom: ^16.7.0
jsdom-global: ^3.0.2
mocha: ^9.0.3
@ -257,6 +259,7 @@ __metadata:
rimraf: ^3.0.2
source-map-loader: ^3.0.0
stream-browserify: ^3.0.0
stream-http: ^3.2.0
style-loader: ^3.3.0
styled-components: ^5.3.1
ts-loader: ^9.2.5
@ -747,6 +750,15 @@ __metadata:
languageName: node
linkType: hard
"@types/bn.js@npm:*, @types/bn.js@npm:^5.1.0":
version: 5.1.0
resolution: "@types/bn.js@npm:5.1.0"
dependencies:
"@types/node": "*"
checksum: 1dc1cbbd7a1e8bf3614752e9602f558762a901031f499f3055828b5e3e2bba16e5b88c27b3c4152ad795248fbe4086c731a5c4b0f29bb243f1875beeeabee59c
languageName: node
linkType: hard
"@types/chai@npm:^4.2.21, @types/chai@npm:^4.2.22":
version: 4.2.22
resolution: "@types/chai@npm:4.2.22"
@ -763,6 +775,15 @@ __metadata:
languageName: node
linkType: hard
"@types/elliptic@npm:^6.4.14":
version: 6.4.14
resolution: "@types/elliptic@npm:6.4.14"
dependencies:
"@types/bn.js": "*"
checksum: d5a64f540e0ed4b74a12dfa5cc88c0aa7b531eab3b7a9fab17948ffbfc6e01814230e63d7417ce1b607dbd8b5d70e1b64f5afac632deabf96e44875aaac0ae1b
languageName: node
linkType: hard
"@types/emoji-mart@npm:^3.0.6":
version: 3.0.6
resolution: "@types/emoji-mart@npm:3.0.6"
@ -1054,6 +1075,13 @@ __metadata:
languageName: node
linkType: hard
"@types/uuid@npm:^8.3.3":
version: 8.3.3
resolution: "@types/uuid@npm:8.3.3"
checksum: 3f340155bb1161f9ffa7163926d6222fea8f3505c23619b72bcc230883354926672c08a06510c7a543f73553c792a84444cc1744cff631b4ba988763e4bd7a8f
languageName: node
linkType: hard
"@types/yargs-parser@npm:*":
version: 20.2.1
resolution: "@types/yargs-parser@npm:20.2.1"
@ -2182,7 +2210,7 @@ __metadata:
languageName: node
linkType: hard
"bn.js@npm:^5.0.0, bn.js@npm:^5.1.1":
"bn.js@npm:^5.0.0, bn.js@npm:^5.1.1, bn.js@npm:^5.2.0":
version: 5.2.0
resolution: "bn.js@npm:5.2.0"
checksum: 6117170393200f68b35a061ecbf55d01dd989302e7b3c798a3012354fa638d124f0b2f79e63f77be5556be80322a09c40339eda6413ba7468524c0b6d4b4cb7a
@ -2366,6 +2394,15 @@ __metadata:
languageName: node
linkType: hard
"browserify-zlib@npm:^0.2.0":
version: 0.2.0
resolution: "browserify-zlib@npm:0.2.0"
dependencies:
pako: ~1.0.5
checksum: 5cd9d6a665190fedb4a97dfbad8dabc8698d8a507298a03f42c734e96d58ca35d3c7d4085e283440bbca1cd1938cff85031728079bedb3345310c58ab1ec92d6
languageName: node
linkType: hard
"browserslist@npm:^4.14.5":
version: 4.17.1
resolution: "browserslist@npm:4.17.1"
@ -2412,6 +2449,13 @@ __metadata:
languageName: node
linkType: hard
"builtin-status-codes@npm:^3.0.0":
version: 3.0.0
resolution: "builtin-status-codes@npm:3.0.0"
checksum: 1119429cf4b0d57bf76b248ad6f529167d343156ebbcc4d4e4ad600484f6bc63002595cbb61b67ad03ce55cd1d3c4711c03bbf198bf24653b8392420482f3773
languageName: node
linkType: hard
"bytes@npm:3.0.0":
version: 3.0.0
resolution: "bytes@npm:3.0.0"
@ -4818,25 +4862,26 @@ fsevents@^1.2.7:
bindings: ^1.5.0
nan: ^2.12.1
checksum: ae855aa737aaa2f9167e9f70417cf6e45a5cd11918e1fee9923709a0149be52416d765433b4aeff56c789b1152e718cd1b13ddec6043b78cdda68260d86383c1
conditions: os=darwin
languageName: node
linkType: hard
"fsevents@patch:fsevents@^1.2.7#~builtin<compat/fsevents>":
version: 1.2.13
resolution: "fsevents@patch:fsevents@npm%3A1.2.13#~builtin<compat/fsevents>::version=1.2.13&hash=1cc4b2"
resolution: "fsevents@patch:fsevents@npm%3A1.2.13#~builtin<compat/fsevents>::version=1.2.13&hash=18f3a7"
dependencies:
bindings: ^1.5.0
nan: ^2.12.1
checksum: b264407498db2cfdcc2a05287334a4160c985a88e4a989e2f2f8dcc6afc8b04a4fcd82c797266442452e11c1fb07d7747d138b078fe4bb1f8f4fd2a6f2484d7e
conditions: os=darwin
languageName: node
linkType: hard
"fsevents@patch:fsevents@~2.3.2#~builtin<compat/fsevents>":
version: 2.3.2
resolution: "fsevents@patch:fsevents@npm%3A2.3.2#~builtin<compat/fsevents>::version=2.3.2&hash=1cc4b2"
resolution: "fsevents@patch:fsevents@npm%3A2.3.2#~builtin<compat/fsevents>::version=2.3.2&hash=18f3a7"
dependencies:
node-gyp: latest
checksum: 78db9daf1f6526a49cefee3917cc988f62dc7f25b5dd80ad6de4ffc4af7f0cab7491ac737626ff53e482a111bc53aac9e411fe3602458eca36f6a003ecf69c16
conditions: os=darwin
languageName: node
linkType: hard
@ -4846,6 +4891,7 @@ fsevents@~2.3.2:
dependencies:
node-gyp: latest
checksum: 97ade64e75091afee5265e6956cb72ba34db7819b4c3e94c431d4be2b19b8bb7a2d4116da417950c3425f17c8fe693d25e20212cac583ac1521ad066b77ae31f
conditions: os=darwin
languageName: node
linkType: hard
@ -5485,6 +5531,13 @@ fsevents@~2.3.2:
languageName: node
linkType: hard
"https-browserify@npm:^1.0.0":
version: 1.0.0
resolution: "https-browserify@npm:1.0.0"
checksum: 09b35353e42069fde2435760d13f8a3fb7dd9105e358270e2e225b8a94f811b461edd17cb57594e5f36ec1218f121c160ddceeec6e8be2d55e01dcbbbed8cbae
languageName: node
linkType: hard
"https-proxy-agent@npm:^5.0.0":
version: 5.0.0
resolution: "https-proxy-agent@npm:5.0.0"
@ -8584,6 +8637,13 @@ fsevents@~2.3.2:
languageName: node
linkType: hard
"pako@npm:~1.0.5":
version: 1.0.11
resolution: "pako@npm:1.0.11"
checksum: 1be2bfa1f807608c7538afa15d6f25baa523c30ec870a3228a89579e474a4d992f4293859524e46d5d87fd30fa17c5edf34dbef0671251d9749820b488660b16
languageName: node
linkType: hard
"param-case@npm:^3.0.3":
version: 3.0.4
resolution: "param-case@npm:3.0.4"
@ -9709,21 +9769,21 @@ resolve@^2.0.0-next.3:
"resolve@patch:resolve@^1.10.0#~builtin<compat/resolve>, resolve@patch:resolve@^1.20.0#~builtin<compat/resolve>, resolve@patch:resolve@^1.9.0#~builtin<compat/resolve>":
version: 1.20.0
resolution: "resolve@patch:resolve@npm%3A1.20.0#~builtin<compat/resolve>::version=1.20.0&hash=00b1ff"
resolution: "resolve@patch:resolve@npm%3A1.20.0#~builtin<compat/resolve>::version=1.20.0&hash=07638b"
dependencies:
is-core-module: ^2.2.0
path-parse: ^1.0.6
checksum: bed00be983cd20a8af0e7840664f655c4b269786dbd9595c5f156cd9d8a0050e65cdbbbdafc30ee9b6245b230c78a2c8ab6447a52545b582f476c29adb188cc5
checksum: a0dd7d16a8e47af23afa9386df2dff10e3e0debb2c7299a42e581d9d9b04d7ad5d2c53f24f1e043f7b3c250cbdc71150063e53d0b6559683d37f790b7c8c3cd5
languageName: node
linkType: hard
"resolve@patch:resolve@^2.0.0-next.3#~builtin<compat/resolve>":
version: 2.0.0-next.3
resolution: "resolve@patch:resolve@npm%3A2.0.0-next.3#~builtin<compat/resolve>::version=2.0.0-next.3&hash=00b1ff"
resolution: "resolve@patch:resolve@npm%3A2.0.0-next.3#~builtin<compat/resolve>::version=2.0.0-next.3&hash=07638b"
dependencies:
is-core-module: ^2.2.0
path-parse: ^1.0.6
checksum: eb88c5e53843bc022215744307a5f5664446c0fdb8f43c33456dce98d5ee6b3162d0cd0a177bb6f1c3d5c8bf01391ac7ab2de0e936e35318725fb40ba7efdaf6
checksum: 21684b4d99a4877337cdbd5484311c811b3e8910edb5d868eec85c6e6550b0f570d911f9a384f9e176172d6713f2715bd0b0887fa512cb8c6aeece018de6a9f8
languageName: node
linkType: hard
@ -10494,15 +10554,20 @@ resolve@^2.0.0-next.3:
version: 0.0.0-use.local
resolution: "status-communities@workspace:packages/status-communities"
dependencies:
"@types/bn.js": ^5.1.0
"@types/chai": ^4.2.22
"@types/elliptic": ^6.4.14
"@types/mocha": ^9.0.0
"@types/pbkdf2": ^3.1.0
"@types/secp256k1": ^4.0.3
"@types/uuid": ^8.3.3
"@typescript-eslint/eslint-plugin": ^4.31.1
"@typescript-eslint/parser": ^4.31.1
bn.js: ^5.2.0
buffer: ^6.0.3
chai: ^4.3.4
ecies-geth: ^1.5.3
elliptic: ^6.5.4
eslint: ^7.32.0
eslint-config-prettier: ^8.3.0
eslint-import-resolver-node: ^0.3.6
@ -10520,6 +10585,7 @@ resolve@^2.0.0-next.3:
ts-node: ^10.2.1
ts-proto: ^1.83.0
typescript: ^4.4.3
uuid: ^8.3.2
languageName: unknown
linkType: soft
@ -10540,6 +10606,18 @@ resolve@^2.0.0-next.3:
languageName: node
linkType: hard
"stream-http@npm:^3.2.0":
version: 3.2.0
resolution: "stream-http@npm:3.2.0"
dependencies:
builtin-status-codes: ^3.0.0
inherits: ^2.0.4
readable-stream: ^3.6.0
xtend: ^4.0.2
checksum: c9b78453aeb0c84fcc59555518ac62bacab9fa98e323e7b7666e5f9f58af8f3155e34481078509b02929bd1268427f664d186604cdccee95abc446099b339f83
languageName: node
linkType: hard
"stream-to-it@npm:^0.2.2":
version: 0.2.4
resolution: "stream-to-it@npm:0.2.4"
@ -11329,11 +11407,11 @@ resolve@^2.0.0-next.3:
"typescript@patch:typescript@^4.3.5#~builtin<compat/typescript>, typescript@patch:typescript@^4.4.3#~builtin<compat/typescript>":
version: 4.4.3
resolution: "typescript@patch:typescript@npm%3A4.4.3#~builtin<compat/typescript>::version=4.4.3&hash=32657b"
resolution: "typescript@patch:typescript@npm%3A4.4.3#~builtin<compat/typescript>::version=4.4.3&hash=ddd1e8"
bin:
tsc: bin/tsc
tsserver: bin/tsserver
checksum: 28ab98313afab46788ff41014fdb5932430ada6e03cf9e92ac47f406526a2cac1ae2894834e7da61e46b7429318e9c47f45ba8de323332f0cb9af99b72ebae74
checksum: 79f5c13d21c9dea3eb44d2b7002ff25a0569fefc432e083d65a360e3aca990aca25fc733e14aa6883b5e9a68e3e2f0330a34123e048806f91d701732ece00e6f
languageName: node
linkType: hard
@ -12159,7 +12237,7 @@ resolve@^2.0.0-next.3:
languageName: node
linkType: hard
"xtend@npm:~4.0.1":
"xtend@npm:^4.0.2, xtend@npm:~4.0.1":
version: 4.0.2
resolution: "xtend@npm:4.0.2"
checksum: ac5dfa738b21f6e7f0dd6e65e1b3155036d68104e67e5d5d1bde74892e327d7e5636a076f625599dc394330a731861e87343ff184b0047fef1360a7ec0a5a36a