diff --git a/packages/status-js/protos/communities.ts b/packages/status-js/protos/communities.ts index 2053888b..3eb498ba 100644 --- a/packages/status-js/protos/communities.ts +++ b/packages/status-js/protos/communities.ts @@ -114,13 +114,12 @@ export namespace CommunityPermissions { export interface CommunityDescription { clock: bigint - members: CommunityMember + members: Record permissions: CommunityPermissions identity: ChatIdentity - // fixme!: Map - chats: CommunityChat + chats: Record banList: string[] - categories: CommunityCategory + categories: Record archiveMagnetlinkClock: bigint adminSettings: CommunityAdminSettings } diff --git a/packages/status-js/src/client/community/community.ts b/packages/status-js/src/client/community/community.ts index 706b7d43..b0348c9e 100644 --- a/packages/status-js/src/client/community/community.ts +++ b/packages/status-js/src/client/community/community.ts @@ -2,10 +2,12 @@ import { waku_message } from 'js-waku' import { MessageType } from '~/protos/enums' import { getDifferenceByKeys } from '~/src/helpers/get-difference-by-keys' +import { getObjectsDifference } from '~/src/helpers/get-objects-difference' import { idToContentTopic } from '../../contentTopic' import { createSymKeyFromPassword } from '../../encryption' import { Chat } from '../chat' +import { Member } from '../member' import type { Client } from '../../client' import type { @@ -21,6 +23,7 @@ export class Community { private symmetricKey!: Uint8Array public description!: CommunityDescription public chats: Map + #members: Map public callback: ((description: CommunityDescription) => void) | undefined constructor(client: Client, publicKey: string) { @@ -28,6 +31,7 @@ export class Community { this.publicKey = publicKey this.chats = new Map() + this.#members = new Map() } public async start() { @@ -52,6 +56,7 @@ export class Community { this.description = description this.observe() + this.addMembers(this.description.members) // Chats await this.observeChatMessages(this.description.chats) @@ -62,6 +67,18 @@ export class Community { return [...this.chats.values()] } + public getChat(uuid: string) { + return this.chats.get(uuid) + } + + public get members() { + return [...this.#members.values()] + } + + public getMember(publicKey: string) { + return this.#members.get(publicKey) + } + public fetch = async () => { await this.client.waku.store.queryHistory([this.contentTopic], { // oldest message first @@ -137,6 +154,19 @@ export class Community { ) } + private addMembers = (members: CommunityDescription['members']) => { + for (const publicKey of Object.keys(members)) { + const member = new Member(publicKey) + this.#members.set(publicKey, member) + } + } + + private removeMembers = (ids: string[]) => { + for (const id of ids) { + this.#members.delete(id) + } + } + public handleDescription = (description: CommunityDescription) => { if (this.description) { // already handled @@ -161,6 +191,23 @@ export class Community { if (Object.keys(addedChats).length) { this.observeChatMessages(addedChats) } + + // TODO: migrate chats to new format + // const chats = getObjectsDifference( + // this.description.chats, + // description.chats + // ) + + // this.observeChatMessages(chats.added) + // this.unobserveChatMessages(chats.removed) + + const members = getObjectsDifference( + this.description.members, + description.members + ) + + this.addMembers(members.added) + this.removeMembers(members.removed) } // Community diff --git a/packages/status-js/src/client/member.ts b/packages/status-js/src/client/member.ts new file mode 100644 index 00000000..7406da25 --- /dev/null +++ b/packages/status-js/src/client/member.ts @@ -0,0 +1,17 @@ +import { generateUsername } from '../utils/generate-username' +import { publicKeyToColorHash } from '../utils/public-key-to-color-hash' + +import type { ColorHash } from '../utils/public-key-to-color-hash' + +export class Member { + publicKey: string + username: string + colorHash: ColorHash + + constructor(publicKey: string) { + this.publicKey = publicKey + this.username = generateUsername(publicKey) + // TODO: can it fail? + this.colorHash = publicKeyToColorHash(publicKey)! + } +} diff --git a/packages/status-js/src/helpers/get-objects-difference.test.ts b/packages/status-js/src/helpers/get-objects-difference.test.ts new file mode 100644 index 00000000..14246c9e --- /dev/null +++ b/packages/status-js/src/helpers/get-objects-difference.test.ts @@ -0,0 +1,25 @@ +import { getObjectsDifference } from './get-objects-difference' + +describe('getObjectsDifference', () => { + it('returns correct difference', () => { + const oldObject = { a: 1, b: 2, c: 3 } + const newObject = { c: 3, d: 4, e: 5 } + + expect(getObjectsDifference(oldObject, newObject)).toEqual({ + added: { + d: 4, + e: 5, + }, + removed: ['a', 'b'], + }) + }) + + it('returns empty arrays for the same object', () => { + const object = { a: 1, b: 2, c: 3 } + + expect(getObjectsDifference(object, object)).toEqual({ + added: {}, + removed: [], + }) + }) +}) diff --git a/packages/status-js/src/helpers/get-objects-difference.ts b/packages/status-js/src/helpers/get-objects-difference.ts new file mode 100644 index 00000000..9437ccac --- /dev/null +++ b/packages/status-js/src/helpers/get-objects-difference.ts @@ -0,0 +1,23 @@ +type Input = Record + +export function getObjectsDifference( + oldObject: Input, + newObject: Input +) { + const added: Record = {} + const removed: string[] = [] + + for (const key of Object.keys(oldObject)) { + if (!newObject[key]) { + removed.push(key) + } + } + + for (const key of Object.keys(newObject)) { + if (!oldObject[key]) { + added[key] = newObject[key] + } + } + + return { added, removed } +} diff --git a/packages/status-js/src/index.ts b/packages/status-js/src/index.ts index 19e0eee6..9bc4a3f5 100644 --- a/packages/status-js/src/index.ts +++ b/packages/status-js/src/index.ts @@ -3,3 +3,4 @@ export type { Client, ClientOptions } from './client' export { createClient } from './client' export type { ChatMessage as Message } from './client/chat' export type { Community } from './client/community/community' +export type { Member } from './client/member' diff --git a/packages/status-js/src/utils/public-key-to-color-hash.ts b/packages/status-js/src/utils/public-key-to-color-hash.ts index 3aac05cf..2b162ac3 100644 --- a/packages/status-js/src/utils/public-key-to-color-hash.ts +++ b/packages/status-js/src/utils/public-key-to-color-hash.ts @@ -1,6 +1,6 @@ import * as secp256k1 from 'ethereum-cryptography/secp256k1' -type ColorHash = number[][] +export type ColorHash = number[][] const COLOR_HASH_COLORS_COUNT = 32 const COLOR_HASH_SEGMENT_MAX_LENGTH = 5 diff --git a/packages/status-react/src/components/member-sidebar/index.tsx b/packages/status-react/src/components/member-sidebar/index.tsx index 209139e6..415250fd 100644 --- a/packages/status-react/src/components/member-sidebar/index.tsx +++ b/packages/status-react/src/components/member-sidebar/index.tsx @@ -9,8 +9,8 @@ import { MemberItem } from './member-item' import { UserItem } from './user-item' export function MemberSidebar() { - const members = useMembers() const { account } = useAccount() + const members = useMembers() return ( @@ -26,14 +26,12 @@ export function MemberSidebar() { {members.map(member => ( - {member} - + // indicator="" + member={member} + /> ))} {/* */} diff --git a/packages/status-react/src/components/member-sidebar/member-item.tsx b/packages/status-react/src/components/member-sidebar/member-item.tsx index 9aa3ab0f..814a25b5 100644 --- a/packages/status-react/src/components/member-sidebar/member-item.tsx +++ b/packages/status-react/src/components/member-sidebar/member-item.tsx @@ -2,26 +2,27 @@ import React from 'react' import { Avatar, EthAddress, Flex, Text } from '~/src/system' +import type { Member } from '~/src/protocol' import type { AvatarProps } from '~/src/system/avatar' interface Props { - children: string - chatKey: string verified: boolean untrustworthy: boolean indicator?: AvatarProps['indicator'] + member: Member } export const MemberItem = (props: Props) => { - const { children, chatKey, indicator, verified, untrustworthy } = props + const { member, indicator, verified, untrustworthy } = props + const { publicKey, username, colorHash } = member return ( - +
- {children} + {username} {verified && ( { )} - {chatKey} + {publicKey}
diff --git a/packages/status-react/src/protocol/index.tsx b/packages/status-react/src/protocol/index.tsx index d7e25304..ffc380f2 100644 --- a/packages/status-react/src/protocol/index.tsx +++ b/packages/status-react/src/protocol/index.tsx @@ -3,4 +3,5 @@ export { useAccount } from './use-account' export type { Chat } from './use-chat' export { useChat } from './use-chat' export { useChats } from './use-chats' +export type { Member } from './use-members' export { useMembers } from './use-members' diff --git a/packages/status-react/src/protocol/use-members.tsx b/packages/status-react/src/protocol/use-members.tsx index d4e1d35b..66690438 100644 --- a/packages/status-react/src/protocol/use-members.tsx +++ b/packages/status-react/src/protocol/use-members.tsx @@ -1,11 +1,12 @@ import { useProtocol } from '~/src/protocol' -import type { Community } from '@status-im/js' +import type { Member } from '@status-im/js' -export type Member = Community['description']['members'][0] +// todo: remove in favor of useCommunity +export const useMembers = (): Member[] => { + const { client } = useProtocol() -export const useMembers = (): string[] => { - const { community } = useProtocol() - - return Object.keys(community.members) + return client.community.members } + +export type { Member }