diff --git a/packages/status-js/.mocharc.json b/packages/status-js/.mocharc.json deleted file mode 100644 index b65763d3..00000000 --- a/packages/status-js/.mocharc.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "extension": ["ts"], - "spec": "src/**/*.spec.ts", - "require": "ts-node/register", - "exit": true -} diff --git a/packages/status-js/README.md b/packages/status-js/README.md deleted file mode 100644 index 174c751b..00000000 --- a/packages/status-js/README.md +++ /dev/null @@ -1 +0,0 @@ -# `status-js` diff --git a/packages/status-js/package.json b/packages/status-js/package.json index 927ab816..830c694c 100644 --- a/packages/status-js/package.json +++ b/packages/status-js/package.json @@ -38,30 +38,14 @@ "proto:build": "buf generate" }, "dependencies": { - "bn.js": "^5.2.0", - "buffer": "^6.0.3", - "debug": "^4.3.3", - "ecies-geth": "^1.5.3", - "elliptic": "^6.5.4", "ethereum-cryptography": "^1.0.3", - "js-sha3": "^0.8.0", "js-waku": "^0.23.0", "long": "^5.2.0", - "pbkdf2": "^3.1.2", "protobufjs": "^6.11.3", - "protons-runtime": "^1.0.4", - "secp256k1": "^4.0.2", - "uuid": "^8.3.2" + "protons-runtime": "^1.0.4" }, "devDependencies": { - "@types/bn.js": "^5.1.0", - "@types/elliptic": "^6.4.14", - "@types/pbkdf2": "^3.1.0", - "@types/secp256k1": "^4.0.3", - "@types/uuid": "^8.3.3", "npm-run-all": "^4.1.5", - "protons": "^3.0.4", - "ts-node": "^10.2.1", - "ts-proto": "^1.115.1" + "protons": "^3.0.4" } } diff --git a/packages/status-js/src/chat.ts b/packages/status-js/src/chat.ts deleted file mode 100644 index b666a5bc..00000000 --- a/packages/status-js/src/chat.ts +++ /dev/null @@ -1,87 +0,0 @@ -import { idToContentTopic } from './contentTopic' -import { createSymKeyFromPassword } from './encryption' -import { ChatMessage } from './wire/chat_message' - -import type { Content } from './wire/chat_message' -import type { CommunityChat } from './wire/community_chat' - -/** - * Represent a chat room. Only public chats are currently supported. - */ -export class Chat { - private lastClockValue?: number - private lastMessage?: ChatMessage - - private constructor( - public id: string, - public symKey: Uint8Array, - public communityChat?: CommunityChat - ) {} - - /** - * Create a public chat room. - * [[Community.instantiateChat]] MUST be used for chats belonging to a community. - */ - public static async create( - id: string, - communityChat?: CommunityChat - ): Promise { - const symKey = await createSymKeyFromPassword(id) - - return new Chat(id, symKey, communityChat) - } - - public get contentTopic(): string { - return idToContentTopic(this.id) - } - - public createMessage(content: Content, responseTo?: string): ChatMessage { - const { timestamp, clock } = this._nextClockAndTimestamp() - - const message = ChatMessage.createMessage( - clock, - timestamp, - this.id, - content, - responseTo - ) - - this._updateClockFromMessage(message) - - return message - } - - public handleNewMessage(message: ChatMessage): void { - this._updateClockFromMessage(message) - } - - private _nextClockAndTimestamp(): { clock: number; timestamp: number } { - let clock = this.lastClockValue - const timestamp = Date.now() - - if (!clock || clock < timestamp) { - clock = timestamp - } else { - clock += 1 - } - - return { clock, timestamp } - } - - private _updateClockFromMessage(message: ChatMessage): void { - if ( - !this.lastMessage || - !this.lastMessage.clock || - (message.clock && this.lastMessage.clock <= message.clock) - ) { - this.lastMessage = message - } - - if ( - !this.lastClockValue || - (message.clock && this.lastClockValue < message.clock) - ) { - this.lastClockValue = message.clock - } - } -} diff --git a/packages/status-js/src/client-v2.ts b/packages/status-js/src/client-v2.ts deleted file mode 100644 index cd4162ec..00000000 --- a/packages/status-js/src/client-v2.ts +++ /dev/null @@ -1,147 +0,0 @@ -import { getPredefinedBootstrapNodes, Waku } from 'js-waku' - -import { ApplicationMetadataMessage } from '../protos/application-metadata-message' -import { ChatMessage } from '../protos/chat-message' -import { CommunityChat, CommunityDescription } from '../protos/communities' -import { Account } from './account' -import { idToContentTopic } from './contentTopic' -import { createSymKeyFromPassword } from './encryption' - -export interface ClientOptions { - publicKey: string - env?: 'production' | 'test' - callback: (message: ChatMessage) => void -} - -export class Client { - options: ClientOptions - publicKey: string - callback: (message: ChatMessage) => void - waku?: Waku - account?: Account - communityDescription?: CommunityDescription - clocks: Record - - constructor(options: ClientOptions) { - this.options = options - this.publicKey = options.publicKey - this.callback = options.callback - this.clocks = {} - } - - public async start() { - console.log(getPredefinedBootstrapNodes('test')) - this.waku = await Waku.create( - this.options.env === 'test' - ? { - bootstrap: { - peers: [ - '/dns4/node-01.gc-us-central1-a.wakuv2.test.statusim.net/tcp/443/wss/p2p/16Uiu2HAmJb2e28qLXxT5kZxVUUoJt72EMzNGXB47Rxx5hw3q4YjS', - ], - }, - } - : { bootstrap: { default: true } } - ) - - console.log('here') - - await this.waku.waitForRemotePeer() - } - - public async getCommunityDescription(): Promise { - if (!this.waku) { - throw new Error('Waku not started') - } - - const contentTopic = idToContentTopic(this.options.publicKey) - - try { - // const symKey = await createSymKeyFromPassword(hexCommunityPublicKey) - const symKey = await createSymKeyFromPassword(this.options.publicKey) - - await this.waku.store.queryHistory([contentTopic], { - callback: messages => { - for (const message of messages.reverse()) { - if (!message.payload) { - return - } - // try { - const metadata = ApplicationMetadataMessage.decode(message.payload) - if (!metadata.payload) { - return - } - - const communityDescription = CommunityDescription.decode( - metadata.payload - ) - - if (communityDescription.identity) { - this.communityDescription = communityDescription - this.observeCommunityChats(communityDescription.chats) - return true - } - } - }, - decryptionKeys: [symKey], - }) - } catch (error) { - console.log(error) - throw error - } - - if (!this.communityDescription) { - throw new Error('Community not found') - } - - return this.communityDescription - } - - private observeCommunityChats(chats: CommunityDescription['chats']) { - const contentTopics = Object.entries(chats).map(([chatUuid, chat]) => { - const chatId = `${this.publicKey}${chatUuid}` - return idToContentTopic(chatId) - }) - - this.waku!.relay.addObserver(this.handleMessage, contentTopics) - } - - private async handleMessage(message: WakuMessage) { - if (!message.payload || !message.timestamp) { - return - } - - // handle increment of Lamport clock - const { timestamp, payload } = message - const metadata = ApplicationMetadataMessage.decode(payload) - - // decode and validate before sending to consumers of status-js - switch (metadata.type) { - case ApplicationMetadataMessage.Type.TYPE_CHAT_MESSAGE: { - const chatMessage = ChatMessage.decode(metadata.payload) - - this.clocks[chatMessage.chatId] = timestamp - this.callback(chatMessage) - return - } - - // case ApplicationMetadataMessage.Type.TYPE_EMOJI_REACTION: { - // return - // } - - default: { - console.log('Unknown message type:', metadata.type) - } - } - } - - createAccount = async (): Promise => { - this.account = new Account() - return this.account - } -} - -export const createClient = async (options: ClientOptions) => { - const client = new Client(options) - await client.start() - return client -} diff --git a/packages/status-js/src/account.test.ts b/packages/status-js/src/client/account.test.ts similarity index 100% rename from packages/status-js/src/account.test.ts rename to packages/status-js/src/client/account.test.ts diff --git a/packages/status-js/src/account.ts b/packages/status-js/src/client/account.ts similarity index 89% rename from packages/status-js/src/account.ts rename to packages/status-js/src/client/account.ts index 399ef6c5..a4b17b98 100644 --- a/packages/status-js/src/account.ts +++ b/packages/status-js/src/client/account.ts @@ -2,8 +2,8 @@ import { keccak256 } from 'ethereum-cryptography/keccak' import { getPublicKey, sign, utils } from 'ethereum-cryptography/secp256k1' import { bytesToHex, concatBytes } from 'ethereum-cryptography/utils' -import { compressPublicKey } from './utils/compress-public-key' -import { generateUsername } from './utils/generate-username' +import { compressPublicKey } from '../utils/compress-public-key' +import { generateUsername } from '../utils/generate-username' export class Account { public privateKey: string diff --git a/packages/status-js/src/client/chat.ts b/packages/status-js/src/client/chat.ts index 0ec78744..58961c8d 100644 --- a/packages/status-js/src/client/chat.ts +++ b/packages/status-js/src/client/chat.ts @@ -1,22 +1,24 @@ import { PageDirection } from 'js-waku' import { + AudioMessage, ChatMessage as ChatMessageProto, DeleteMessage, EditMessage, + ImageType, } from '~/protos/chat-message' import { EmojiReaction } from '~/protos/emoji-reaction' -import { idToContentTopic } from '../contentTopic' -import { createSymKeyFromPassword } from '../encryption' import { containsOnlyEmoji } from '../helpers/contains-only-emoji' +import { generateKeyFromPassword } from '../utils/generate-key-from-password' +import { idToContentTopic } from '../utils/id-to-content-topic' import { getReactions } from './community/get-reactions' import type { MessageType } from '../../protos/enums' -import type { Client } from '../client' +import type { Client } from './client' import type { Community } from './community/community' import type { Reactions } from './community/get-reactions' -import type { ImageMessage } from '~/src/proto/communities/v1/chat_message' +import type { ImageMessage } from '~/protos/chat-message' import type { CommunityChat } from '~/src/proto/communities/v1/communities' import type { WakuMessage } from 'js-waku' @@ -89,7 +91,7 @@ export class Chat { ) => { const id = `${community.publicKey}${uuid}` const contentTopic = idToContentTopic(id) - const symmetricKey = await createSymKeyFromPassword(id) + const symmetricKey = await generateKeyFromPassword(id) return new Chat({ client, @@ -441,11 +443,11 @@ export class Chat { messageType: 'COMMUNITY_CHAT' as MessageType, sticker: { hash: '', pack: 0 }, image: { - type: 'JPEG', + type: ImageType.JPEG, payload: new Uint8Array([]), }, audio: { - type: 'AAC', + type: AudioMessage.AudioType.AAC, payload: new Uint8Array([]), durationMs: BigInt(0), }, @@ -478,7 +480,7 @@ export class Chat { payload: image.payload, }, audio: { - type: 'AAC', + type: AudioMessage.AudioType.AAC, payload: new Uint8Array([]), durationMs: BigInt(0), }, diff --git a/packages/status-js/src/client.ts b/packages/status-js/src/client/client.ts similarity index 95% rename from packages/status-js/src/client.ts rename to packages/status-js/src/client/client.ts index eddbed17..f1912291 100644 --- a/packages/status-js/src/client.ts +++ b/packages/status-js/src/client/client.ts @@ -8,8 +8,8 @@ import { Waku, WakuMessage } from 'js-waku' import { ApplicationMetadataMessage } from '~/protos/application-metadata-message' import { Account } from './account' -import { Community } from './client/community/community' -import { handleWakuMessage } from './client/community/handle-waku-message' +import { Community } from './community/community' +import { handleWakuMessage } from './community/handle-waku-message' export interface ClientOptions { publicKey: string diff --git a/packages/status-js/src/client/community/community.ts b/packages/status-js/src/client/community/community.ts index eb6a41e3..1f26e21c 100644 --- a/packages/status-js/src/client/community/community.ts +++ b/packages/status-js/src/client/community/community.ts @@ -6,13 +6,13 @@ import { MessageType } from '~/protos/enums' import { getDifferenceByKeys } from '~/src/helpers/get-difference-by-keys' import { getObjectsDifference } from '~/src/helpers/get-objects-difference' import { compressPublicKey } from '~/src/utils/compress-public-key' +import { generateKeyFromPassword } from '~/src/utils/generate-key-from-password' +import { idToContentTopic } from '~/src/utils/id-to-content-topic' -import { idToContentTopic } from '../../contentTopic' -import { createSymKeyFromPassword } from '../../encryption' import { Chat } from '../chat' import { Member } from '../member' -import type { Client } from '../../client' +import type { Client } from '../client' import type { CommunityChat, CommunityDescription, @@ -44,7 +44,7 @@ export class Community { public async start() { this.contentTopic = idToContentTopic(this.publicKey) - this.symmetricKey = await createSymKeyFromPassword(this.publicKey) + this.symmetricKey = await generateKeyFromPassword(this.publicKey) // Waku this.client.waku.store.addDecryptionKey(this.symmetricKey, { @@ -147,14 +147,24 @@ export class Community { private unobserveChatMessages = ( chatDescription: CommunityDescription['chats'] ) => { - const contentTopics = Object.keys(chatDescription).map(chatUuid => { + const contentTopics: string[] = [] + + for (const chatUuid of Object.keys(chatDescription)) { const chat = this.chats.get(chatUuid) - const contentTopic = chat!.contentTopic + + if (!chat) { + continue + } + + const contentTopic = chat.contentTopic this.chats.delete(chatUuid) + contentTopics.push(contentTopic) + } - return contentTopic - }) + if (!contentTopics.length) { + return + } this.client.waku.relay.deleteObserver( this.client.handleWakuMessage, diff --git a/packages/status-js/src/client/community/handle-waku-message.ts b/packages/status-js/src/client/community/handle-waku-message.ts index 0b4dd9b5..19d0c8a3 100644 --- a/packages/status-js/src/client/community/handle-waku-message.ts +++ b/packages/status-js/src/client/community/handle-waku-message.ts @@ -16,7 +16,7 @@ import { recoverPublicKey } from '../../utils/recover-public-key' import { getChatUuid } from './get-chat-uuid' import { mapChatMessage } from './map-chat-message' -import type { Client } from '../../client' +import type { Client } from '../client' import type { Community } from './community' import type { WakuMessage } from 'js-waku' diff --git a/packages/status-js/src/contacts.ts b/packages/status-js/src/contacts.ts deleted file mode 100644 index 93536a4b..00000000 --- a/packages/status-js/src/contacts.ts +++ /dev/null @@ -1,172 +0,0 @@ -// see -import { PageDirection, WakuMessage } from 'js-waku' - -import { idToContactCodeTopic } from './contentTopic' -import { StatusUpdate_StatusType } from './proto/communities/v1/status_update' -import { bufToHex, getLatestUserNickname } from './utils' -import { ChatIdentity } from './wire/chat_identity' -import { StatusUpdate } from './wire/status_update' - -import type { Identity } from './identity' -import type { Waku } from 'js-waku' - -const STATUS_BROADCAST_INTERVAL = 30000 -const NICKNAME_BROADCAST_INTERVAL = 300000 - -export class Contacts { - waku: Waku - identity: Identity | undefined - nickname?: string - private callback: (publicKey: string, clock: number) => void - private callbackNickname: (publicKey: string, nickname: string) => void - private contacts: string[] = [] - - /** - * Contacts holds a list of user contacts and listens to their status broadcast - * - * When watched user broadcast callback is called. - * - * Class also broadcasts own status on contact-code topic - * - * @param identity identity of user that is used to broadcast status message - * - * @param waku waku class used to listen to broadcast and broadcast status - * - * @param callback callback function called when user status broadcast is received - */ - public constructor( - identity: Identity | undefined, - waku: Waku, - callback: (publicKey: string, clock: number) => void, - callbackNickname: (publicKey: string, nickname: string) => void, - nickname?: string - ) { - this.waku = waku - this.identity = identity - this.nickname = nickname - this.callback = callback - this.callbackNickname = callbackNickname - this.startBroadcast() - if (identity) { - this.addContact(bufToHex(identity.publicKey)) - } - } - - /** - * Add contact to watch list of status broadcast - * - * When user broadcasts its status callback is called - * - * @param publicKey public key of user - */ - public addContact(publicKey: string): void { - if (!this.contacts.find(e => publicKey === e)) { - const now = new Date() - const callback = (wakuMessage: WakuMessage): void => { - if (wakuMessage.payload) { - const msg = StatusUpdate.decode(wakuMessage.payload) - this.callback(publicKey, msg.clock ?? 0) - } - } - this.contacts.push(publicKey) - this.callback(publicKey, 0) - this.waku.store.queryHistory([idToContactCodeTopic(publicKey)], { - callback: msgs => msgs.forEach(e => callback(e)), - timeFilter: { - startTime: new Date(now.getTime() - STATUS_BROADCAST_INTERVAL * 2), - endTime: now, - }, - }) - this.waku.store.queryHistory([idToContactCodeTopic(publicKey)], { - callback: msgs => - msgs.some(e => { - try { - if (e.payload) { - const chatIdentity = ChatIdentity.decode(e?.payload) - if (chatIdentity) { - this.callbackNickname( - publicKey, - chatIdentity?.displayName ?? '' - ) - } - return true - } - } catch { - return false - } - }), - pageDirection: PageDirection.BACKWARD, - }) - this.waku.relay.addObserver(callback, [idToContactCodeTopic(publicKey)]) - } - } - - private startBroadcast(): void { - const send = async (): Promise => { - if (this.identity) { - const statusUpdate = StatusUpdate.create( - StatusUpdate_StatusType.AUTOMATIC, - '' - ) - const msg = await WakuMessage.fromBytes( - statusUpdate.encode(), - idToContactCodeTopic(bufToHex(this.identity.publicKey)) - ) - this.waku.relay.send(msg) - } - } - - const handleNickname = async (): Promise => { - if (this.identity) { - const now = new Date().getTime() - const { clock, nickname: newNickname } = await getLatestUserNickname( - this.identity.publicKey, - this.waku - ) - - if (this.nickname) { - if (this.nickname !== newNickname) { - await sendNickname() - } else { - if (clock < now - NICKNAME_BROADCAST_INTERVAL) { - await sendNickname() - } - } - } else { - this.nickname = newNickname - this.callbackNickname(bufToHex(this.identity.publicKey), newNickname) - if (clock < now - NICKNAME_BROADCAST_INTERVAL) { - await sendNickname() - } - } - } - setInterval(send, NICKNAME_BROADCAST_INTERVAL) - } - - const sendNickname = async (): Promise => { - if (this.identity) { - const publicKey = bufToHex(this.identity.publicKey) - if (this.nickname) { - const chatIdentity = new ChatIdentity({ - clock: new Date().getTime(), - color: '', - description: '', - emoji: '', - images: {}, - ensName: '', - displayName: this?.nickname ?? '', - }) - const msg = await WakuMessage.fromBytes( - chatIdentity.encode(), - idToContactCodeTopic(publicKey), - { sigPrivKey: this.identity.privateKey } - ) - await this.waku.relay.send(msg) - } - } - } - handleNickname() - send() - setInterval(send, STATUS_BROADCAST_INTERVAL) - } -} diff --git a/packages/status-js/src/contentTopic.ts b/packages/status-js/src/contentTopic.ts deleted file mode 100644 index 4101a4f9..00000000 --- a/packages/status-js/src/contentTopic.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { Buffer } from 'buffer' -import { keccak256 } from 'js-sha3' - -const TopicLength = 4 - -/** - * Get the content topic of for a given Chat or Community - * @param id The Chat id or Community id (hex string prefixed with 0x). - * @returns string The Waku v2 Content Topic. - */ -export function idToContentTopic(id: string): string { - const hash = keccak256.arrayBuffer(id) - - const topic = Buffer.from(hash).slice(0, TopicLength) - - return '/waku/1/' + '0x' + topic.toString('hex') + '/rfc26' -} - -export function idToContactCodeTopic(id: string): string { - return idToContentTopic(id + '-contact-code') -} diff --git a/packages/status-js/src/encryption.spec.ts b/packages/status-js/src/encryption.spec.ts deleted file mode 100644 index f74a5312..00000000 --- a/packages/status-js/src/encryption.spec.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { createSymKeyFromPassword } from './encryption' - -describe('Encryption', () => { - test('Generate symmetric key from password', async () => { - const str = 'arbitrary data here' - const symKey = await createSymKeyFromPassword(str) - - expect(Buffer.from(symKey).toString('hex')).toEqual( - 'c49ad65ebf2a7b7253bf400e3d27719362a91b2c9b9f54d50a69117021666c33' - ) - }) - - test('Generate symmetric key from password for chat', async () => { - const str = - '0x02dcec6041fb999d65f1d33363e08c93d3c1f6f0fbbb26add383e2cf46c2b921f41dc14fd8-9a8b-4df5-a358-2c3067be5439' - const symKey = await createSymKeyFromPassword(str) - - expect(Buffer.from(symKey).toString('hex')).toEqual( - '76ff5bf0a74a8e724367c7fc003f066d477641f468768a8da2817addf5c2ce76' - ) - }) -}) diff --git a/packages/status-js/src/encryption.ts b/packages/status-js/src/encryption.ts deleted file mode 100644 index c2d7a8f4..00000000 --- a/packages/status-js/src/encryption.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { utf8ToBytes } from 'ethereum-cryptography/utils' -import { pbkdf2 } from 'pbkdf2' - -const AESKeyLength = 32 // bytes - -export async function createSymKeyFromPassword( - password: string -): Promise { - return new Promise((resolve, reject) => { - pbkdf2( - utf8ToBytes(password), - '', - 65356, - AESKeyLength, - 'sha256', - (err, buf) => { - if (err) { - reject(err) - } else { - resolve(buf) - } - } - ) - }) -} diff --git a/packages/status-js/src/groupChats.ts b/packages/status-js/src/groupChats.ts deleted file mode 100644 index 81dca8ae..00000000 --- a/packages/status-js/src/groupChats.ts +++ /dev/null @@ -1,455 +0,0 @@ -import { waku_message, WakuMessage } from 'js-waku' - -// FIXME?: import from 'js-waku' not /build -import { ChatMessage } from '.' -import { createSymKeyFromPassword } from './encryption' -import { MembershipUpdateEvent_EventType } from './proto/communities/v1/membership_update_message' -import { getNegotiatedTopic, getPartitionedTopic } from './topics' -import { bufToHex, compressPublicKey } from './utils' -import { MembershipUpdateMessage } from './wire/membership_update_message' - -import type { Content } from '.' -import type { Identity } from './identity' -import type { MembershipSignedEvent } from './wire/membership_update_message' -import type { Waku } from 'js-waku' - -type GroupMember = { - id: string - topic: string - symKey: Uint8Array - partitionedTopic: string -} - -export type GroupChat = { - chatId: string - members: GroupMember[] - admins?: string[] - name?: string - removed: boolean -} - -export type GroupChatsType = { - [id: string]: GroupChat -} -/* TODO: add chat messages encryption */ - -class GroupChatUsers { - private users: { [id: string]: GroupMember } = {} - private identity: Identity - - public constructor(_identity: Identity) { - this.identity = _identity - } - - public async getUser(id: string): Promise { - if (this.users[id]) { - return this.users[id] - } - const topic = await getNegotiatedTopic(this.identity, id) - const symKey = await createSymKeyFromPassword(topic) - const partitionedTopic = getPartitionedTopic(id) - const groupUser: GroupMember = { topic, symKey, id, partitionedTopic } - this.users[id] = groupUser - return groupUser - } -} - -export class GroupChats { - waku: Waku - identity: Identity - private callback: (chats: GroupChat) => void - private removeCallback: (chats: GroupChat) => void - private addMessage: (message: ChatMessage, sender: string) => void - private groupChatUsers - - 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.groupChatUsers = new GroupChatUsers(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, - content: Content, - responseTo?: string - ): Promise { - 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, - content, - responseTo - ) - const wakuMessage = await WakuMessage.fromBytes( - chatMessage.encode(), - member.topic, - { sigPrivKey: this.identity.privateKey, symKey: member.symKey } - ) - this.waku.relay.send(wakuMessage) - }) - ) - } - } - - private async handleUpdateEvent( - chatId: string, - event: MembershipSignedEvent, - useCallback: boolean - ): Promise { - const signer = event.signer ? bufToHex(event.signer) : '' - const thisUser = bufToHex(this.identity.publicKey) - const chat: GroupChat | undefined = this.chats[chatId] - if (signer) { - switch (event.event.type) { - case MembershipUpdateEvent_EventType.CHAT_CREATED: { - const members: GroupMember[] = [] - await Promise.all( - event.event.members.map(async member => { - members.push(await this.groupChatUsers.getUser(member)) - }) - ) - await this.addChat( - { - chatId: chatId, - members, - admins: [signer], - removed: false, - }, - useCallback - ) - break - } - case MembershipUpdateEvent_EventType.MEMBER_REMOVED: { - if (chat) { - chat.members = chat.members.filter( - member => !event.event.members.includes(member.id) - ) - if (event.event.members.includes(thisUser)) { - await this.removeChat( - { - ...chat, - removed: true, - }, - useCallback - ) - } else { - if (!chat.removed && useCallback) { - this.callback(this.chats[chatId]) - } - } - } - break - } - case MembershipUpdateEvent_EventType.MEMBERS_ADDED: { - if (chat && chat.admins?.includes(signer)) { - const members: GroupMember[] = [] - await Promise.all( - event.event.members.map(async member => { - members.push(await this.groupChatUsers.getUser(member)) - }) - ) - chat.members.push(...members) - if (chat.members.findIndex(member => member.id === thisUser) > -1) { - chat.removed = false - await this.addChat(chat, useCallback) - } - } - break - } - case MembershipUpdateEvent_EventType.NAME_CHANGED: { - if (chat) { - if (chat.admins?.includes(signer)) { - chat.name = event.event.name - this.callback(chat) - } - } - break - } - } - } - } - - private async decodeUpdateMessage( - message: WakuMessage, - useCallback: boolean - ): Promise { - try { - if (message?.payload) { - const membershipUpdate = MembershipUpdateMessage.decode(message.payload) - await Promise.all( - membershipUpdate.events.map( - async event => - await this.handleUpdateEvent( - membershipUpdate.chatId, - event, - 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) { - let sender = member - if (message.signaturePublicKey) { - sender = compressPublicKey(message.signaturePublicKey) - } - this.addMessage(chatMessage, sender) - } - } - } - } catch { - return - } - } - - private async handleChatObserver( - chat: GroupChat, - removeObserver?: boolean - ): Promise { - const observerFunction = removeObserver ? 'deleteObserver' : 'addObserver' - await Promise.all( - chat.members.map(async member => { - if (!removeObserver) { - this.waku.relay.addDecryptionKey(member.symKey, { - method: waku_message.DecryptionMethod.Symmetric, - contentTopics: [member.topic], - }) - } - this.waku.relay[observerFunction]( - message => this.handleWakuChatMessage(message, chat, member.id), - [member.topic] - ) - }) - ) - } - - private async addChat(chat: GroupChat, useCallback: boolean): Promise { - if (this.chats[chat.chatId]) { - this.chats[chat.chatId] = chat - if (useCallback) { - this.callback(chat) - } - } else { - this.chats[chat.chatId] = chat - if (useCallback) { - await this.handleChatObserver(chat) - this.callback(chat) - } - } - } - - private async removeChat( - chat: GroupChat, - useCallback: boolean - ): Promise { - this.chats[chat.chatId] = chat - if (useCallback) { - await this.handleChatObserver(chat, true) - this.removeCallback(chat) - } - } - - private async listen(): Promise { - const topic = getPartitionedTopic(bufToHex(this.identity.publicKey)) - const messages = await this.waku.store.queryHistory([topic]) - messages.sort((a, b) => - (a?.timestamp?.getTime() ?? 0) < (b?.timestamp?.getTime() ?? 0) ? -1 : 1 - ) - for (let i = 0; i < messages.length; i++) { - await this.decodeUpdateMessage(messages[i], false) - } - this.waku.relay.addObserver( - message => this.decodeUpdateMessage(message, true), - [topic] - ) - await Promise.all( - Object.values(this.chats).map(async chat => { - if (!chat?.removed) { - await this.handleChatObserver(chat) - this.callback(chat) - } - }) - ) - } - - private async sendUpdateMessage( - payload: Uint8Array, - members: GroupMember[] - ): Promise { - const wakuMessages = await Promise.all( - members.map( - async member => - await WakuMessage.fromBytes(payload, member.partitionedTopic) - ) - ) - wakuMessages.forEach(msg => this.waku.relay.send(msg)) - } - - /** - * Sends a change chat name chat membership update message - * - * @param chatId a chat id to which message is to be sent - * - * @param name a name which chat should be changed to - */ - public async changeChatName(chatId: string, name: string): Promise { - 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 add members group chat membership update message with given members - * - * @param chatId a chat id to which message is to be sent - * - * @param members a list of members to be added - */ - public async addMembers(chatId: string, members: string[]): Promise { - const payload = MembershipUpdateMessage.create(chatId, this.identity) - const chat = this.chats[chatId] - if (chat && payload) { - const newMembers: GroupMember[] = [] - - await Promise.all( - members - .filter( - member => - !chat.members.map(chatMember => chatMember.id).includes(member) - ) - .map(async member => { - newMembers.push(await this.groupChatUsers.getUser(member)) - }) - ) - - payload.addMembersAddedEvent(newMembers.map(member => member.id)) - await this.sendUpdateMessage(payload.encode(), [ - ...chat.members, - ...newMembers, - ]) - } - } - - /** - * 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 { - const payload = MembershipUpdateMessage.createChat( - this.identity, - members - ).encode() - - const newMembers: GroupMember[] = [] - - await Promise.all( - members.map(async member => { - newMembers.push(await this.groupChatUsers.getUser(member)) - }) - ) - - await this.sendUpdateMessage(payload, newMembers) - } - - /** - * Sends a remove member to private group chat - * - * @param chatId id of private group chat - */ - public async quitChat(chatId: string): Promise { - const payload = MembershipUpdateMessage.create(chatId, this.identity) - const chat = this.chats[chatId] - payload.addMemberRemovedEvent(bufToHex(this.identity.publicKey)) - await this.sendUpdateMessage(payload.encode(), chat.members) - } - - /** - * Retrieve previous messages from a Waku Store node for the given chat Id. - * - */ - public async retrievePreviousMessages( - chatId: string, - startTime: Date, - endTime: Date - ): Promise { - const chat = this.chats[chatId] - - if (!chat) - throw `Failed to retrieve messages, chat is not joined: ${chatId}` - - const _callback = (wakuMessages: WakuMessage[], member: string): void => { - wakuMessages.forEach((wakuMessage: WakuMessage) => - this.handleWakuChatMessage(wakuMessage, chat, member) - ) - } - - const amountOfMessages: number[] = [] - - await Promise.all( - chat.members.map(async member => { - const msgLength = ( - await this.waku.store.queryHistory([member.topic], { - timeFilter: { startTime, endTime }, - callback: msg => _callback(msg, member.id), - decryptionKeys: [member.symKey], - }) - ).length - amountOfMessages.push(msgLength) - }) - ) - return amountOfMessages.reduce((a, b) => a + b) - } -} diff --git a/packages/status-js/src/identity.ts b/packages/status-js/src/identity.ts deleted file mode 100644 index 39790ca0..00000000 --- a/packages/status-js/src/identity.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { Buffer } from 'buffer' -import { keccak256 } from 'js-sha3' -import { generatePrivateKey } from 'js-waku' -import * as secp256k1 from 'secp256k1' - -import { hexToBuf } from './utils' - -export class Identity { - private pubKey: Uint8Array - public constructor(public privateKey: Uint8Array) { - this.pubKey = secp256k1.publicKeyCreate(this.privateKey, true) - } - - public static generate(): Identity { - const privateKey = generatePrivateKey() - return new Identity(privateKey) - } - - /** - * Hashes the payload with SHA3-256 and signs the result using the internal private key. - */ - public sign(payload: Uint8Array): Uint8Array { - const hash = keccak256(payload) - const { signature, recid } = secp256k1.ecdsaSign( - hexToBuf(hash), - this.privateKey - ) - - return Buffer.concat([signature, Buffer.from([recid])]) - } - - /** - * Returns the compressed public key. - */ - public get publicKey(): Uint8Array { - return this.pubKey - } -} diff --git a/packages/status-js/src/index.ts b/packages/status-js/src/index.ts index 9bc4a3f5..47a2d82c 100644 --- a/packages/status-js/src/index.ts +++ b/packages/status-js/src/index.ts @@ -1,6 +1,6 @@ -export type { Account } from './account' -export type { Client, ClientOptions } from './client' -export { createClient } from './client' +export type { Account } from './client/account' export type { ChatMessage as Message } from './client/chat' +export type { Client, ClientOptions } from './client/client' +export { createClient } from './client/client' export type { Community } from './client/community/community' export type { Member } from './client/member' diff --git a/packages/status-js/src/members.ts b/packages/status-js/src/members.ts deleted file mode 100644 index ac7a945e..00000000 --- a/packages/status-js/src/members.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { keccak256 } from 'ethereum-cryptography/keccak' -import { getPublicKey, sign, utils } from 'ethereum-cryptography/secp256k1' -import { bytesToHex } from 'ethereum-cryptography/utils' - -export class Members { - constructor() {} -} diff --git a/packages/status-js/src/messenger.spec.ts b/packages/status-js/src/messenger.spec.ts deleted file mode 100644 index f3ce3476..00000000 --- a/packages/status-js/src/messenger.spec.ts +++ /dev/null @@ -1,103 +0,0 @@ -import debug from 'debug' -import { Protocols } from 'js-waku' - -import { Identity } from './identity' -import { Messenger } from './messenger' -import { bufToHex } from './utils' -import { ContentType } from './wire/chat_message' - -import type { ApplicationMetadataMessage } from './wire/application_metadata_message' - -const testChatId = 'test-chat-id' - -const dbg = debug('communities:test:messenger') - -describe('Messenger', () => { - let messengerAlice: Messenger - let messengerBob: Messenger - let identityAlice: Identity - let identityBob: Identity - - beforeEach(async () => { - dbg('Generate keys') - identityAlice = Identity.generate() - identityBob = Identity.generate() - - dbg('Create messengers') - ;[messengerAlice, messengerBob] = await Promise.all([ - Messenger.create(identityAlice, { bootstrap: {} }), - Messenger.create(identityBob, { - bootstrap: {}, - libp2p: { addresses: { listen: ['/ip4/0.0.0.0/tcp/0/ws'] } }, - }), - ]) - - dbg('Connect messengers') - // Connect both messengers together for test purposes - messengerAlice.waku.addPeerToAddressBook( - messengerBob.waku.libp2p.peerId, - messengerBob.waku.libp2p.multiaddrs - ) - - dbg('Wait for remote peer') - await Promise.all([ - messengerAlice.waku.waitForRemotePeer([Protocols.Relay]), - messengerBob.waku.waitForRemotePeer([Protocols.Relay]), - ]) - dbg('Messengers ready') - }) - - test('Sends & Receive public chat messages', async () => { - await messengerAlice.joinChatById(testChatId) - await messengerBob.joinChatById(testChatId) - - const text = 'This is a message.' - - const receivedMessagePromise: Promise = - new Promise(resolve => { - messengerBob.addObserver(message => { - resolve(message) - }, testChatId) - }) - - await messengerAlice.sendMessage(testChatId, { - text, - contentType: ContentType.Text, - }) - - const receivedMessage = await receivedMessagePromise - - expect(receivedMessage.chatMessage?.text).toEqual(text) - }) - - test('public chat messages have signers', async () => { - await messengerAlice.joinChatById(testChatId) - await messengerBob.joinChatById(testChatId) - - const text = 'This is a message.' - - const receivedMessagePromise: Promise = - new Promise(resolve => { - messengerBob.addObserver(message => { - resolve(message) - }, testChatId) - }) - - await messengerAlice.sendMessage(testChatId, { - text, - contentType: ContentType.Text, - }) - - const receivedMessage = await receivedMessagePromise - - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - expect(bufToHex(receivedMessage.signer!)).toEqual( - bufToHex(identityAlice.publicKey) - ) - }) - - afterEach(async () => { - await messengerAlice.stop() - await messengerBob.stop() - }) -}) diff --git a/packages/status-js/src/messenger.ts b/packages/status-js/src/messenger.ts deleted file mode 100644 index 319b672d..00000000 --- a/packages/status-js/src/messenger.ts +++ /dev/null @@ -1,269 +0,0 @@ -import debug from 'debug' -import { Waku, waku_message, WakuMessage } from 'js-waku' - -import { Chat } from './chat' -import { ApplicationMetadataMessage_Type } from './proto/status/v1/application_metadata_message' -import { getLatestUserNickname } from './utils' -import { ApplicationMetadataMessage } from './wire/application_metadata_message' -import { ChatMessage } from './wire/chat_message' - -import type { Identity } from './identity' -import type { Content } from './wire/chat_message' -import type { waku } from 'js-waku' - -const dbg = debug('communities:messenger') - -export class Messenger { - waku: Waku - chatsById: Map - observers: { - [chatId: string]: Set< - ( - message: ApplicationMetadataMessage, - timestamp: Date, - chatId: string - ) => void - > - } - identity: Identity | undefined - - private constructor(identity: Identity | undefined, waku: Waku) { - this.identity = identity - this.waku = waku - this.chatsById = new Map() - this.observers = {} - } - - public static async create( - identity: Identity | undefined, - wakuOptions?: waku.CreateOptions - ): Promise { - const _wakuOptions = Object.assign( - { bootstrap: { default: true } }, - wakuOptions - ) - const waku = await Waku.create(_wakuOptions) - return new Messenger(identity, waku) - } - - /** - * Joins a public chat using its id. - * - * For community chats, prefer [[joinChat]]. - * - * Use `addListener` to get messages received on this chat. - */ - public async joinChatById(chatId: string): Promise { - const chat = await Chat.create(chatId) - - await this.joinChat(chat) - } - - /** - * Joins several of public chats. - * - * Use `addListener` to get messages received on these chats. - */ - public async joinChats(chats: Iterable): Promise { - await Promise.all( - Array.from(chats).map(chat => { - return this.joinChat(chat) - }) - ) - } - - /** - * Joins a public chat. - * - * Use `addListener` to get messages received on this chat. - */ - public async joinChat(chat: Chat): Promise { - if (this.chatsById.has(chat.id)) - throw `Failed to join chat, it is already joined: ${chat.id}` - - this.waku.addDecryptionKey(chat.symKey, { - method: waku_message.DecryptionMethod.Symmetric, - contentTopics: [chat.contentTopic], - }) - - this.waku.relay.addObserver( - (wakuMessage: WakuMessage) => { - if (!wakuMessage.payload || !wakuMessage.timestamp) return - - const message = ApplicationMetadataMessage.decode(wakuMessage.payload) - - switch (message.type) { - case ApplicationMetadataMessage_Type.TYPE_CHAT_MESSAGE: - this._handleNewChatMessage(chat, message, wakuMessage.timestamp) - break - default: - dbg('Received unsupported message type', message.type) - } - }, - [chat.contentTopic] - ) - - this.chatsById.set(chat.id, chat) - } - - /** - * Sends a message on the given chat Id. - */ - public async sendMessage( - chatId: string, - content: Content, - responseTo?: string - ): Promise { - if (this.identity) { - const chat = this.chatsById.get(chatId) - if (!chat) throw `Failed to send message, chat not joined: ${chatId}` - - const chatMessage = chat.createMessage(content, responseTo) - - const appMetadataMessage = ApplicationMetadataMessage.create( - chatMessage.encode(), - ApplicationMetadataMessage_Type.TYPE_CHAT_MESSAGE, - this.identity - ) - - const wakuMessage = await WakuMessage.fromBytes( - appMetadataMessage.encode(), - chat.contentTopic, - { symKey: chat.symKey, sigPrivKey: this.identity.privateKey } - ) - - await this.waku.relay.send(wakuMessage) - } - } - - /** - * Add an observer of new messages received on the given chat id. - * - * @throws string If the chat has not been joined first using [joinChat]. - */ - public addObserver( - observer: ( - message: ApplicationMetadataMessage, - timestamp: Date, - chatId: string - ) => void, - chatId: string | string[] - ): void { - let chats = [] - - if (typeof chatId === 'string') { - chats.push(chatId) - } else { - chats = [...chatId] - } - - chats.forEach(id => { - if (!this.chatsById.has(id)) - throw 'Cannot add observer on a chat that is not joined.' - if (!this.observers[id]) { - this.observers[id] = new Set() - } - - this.observers[id].add(observer) - }) - } - - /** - * Delete an observer of new messages received on the given chat id. - * - * @throws string If the chat has not been joined first using [joinChat]. - */ - - deleteObserver( - observer: (message: ApplicationMetadataMessage) => void, - chatId: string - ): void { - if (this.observers[chatId]) { - this.observers[chatId].delete(observer) - } - } - - /** - * Stops the messenger. - */ - public async stop(): Promise { - await this.waku.stop() - } - - /** - * Retrieve previous messages from a Waku Store node for the given chat Id. - * - * Note: note sure what is the preferred interface: callback or returning all messages - * Callback is more flexible and allow processing messages as they are retrieved instead of waiting for the - * full retrieval via paging to be done. - */ - public async retrievePreviousMessages( - chatId: string, - startTime: Date, - endTime: Date, - callback?: (messages: ApplicationMetadataMessage[]) => void - ): Promise { - const chat = this.chatsById.get(chatId) - if (!chat) - throw `Failed to retrieve messages, chat is not joined: ${chatId}` - - const _callback = (wakuMessages: WakuMessage[]): void => { - const isDefined = ( - msg: ApplicationMetadataMessage | undefined - ): msg is ApplicationMetadataMessage => { - return !!msg - } - - const messages = wakuMessages.map((wakuMessage: WakuMessage) => { - if (!wakuMessage.payload || !wakuMessage.timestamp) return - - const message = ApplicationMetadataMessage.decode(wakuMessage.payload) - - switch (message.type) { - case ApplicationMetadataMessage_Type.TYPE_CHAT_MESSAGE: - this._handleNewChatMessage(chat, message, wakuMessage.timestamp) - return message - default: - dbg('Retrieved unsupported message type', message.type) - return - } - }) - if (callback) { - callback(messages.filter(isDefined)) - } - } - const allMessages = await this.waku.store.queryHistory( - [chat.contentTopic], - { - timeFilter: { startTime, endTime }, - callback: _callback, - } - ) - return allMessages.length - } - - private _handleNewChatMessage( - chat: Chat, - message: ApplicationMetadataMessage, - timestamp: Date - ): void { - if (!message.payload || !message.type || !message.signature) return - - const chatMessage = ChatMessage.decode(message.payload) - chat.handleNewMessage(chatMessage) - - if (this.observers[chat.id]) { - this.observers[chat.id].forEach(observer => { - observer(message, timestamp, chat.id) - }) - } - } - - async checkIfUserInWakuNetwork(publicKey: Uint8Array): Promise { - const { clock, nickname } = await getLatestUserNickname( - publicKey, - this.waku - ) - return clock > 0 && nickname !== '' - } -} diff --git a/packages/status-js/src/proto/communities/v1/status_update.ts b/packages/status-js/src/proto/communities/v1/status_update.ts deleted file mode 100644 index 5c299fa5..00000000 --- a/packages/status-js/src/proto/communities/v1/status_update.ts +++ /dev/null @@ -1,212 +0,0 @@ -/* eslint-disable */ -import Long from 'long' -import _m0 from 'protobufjs/minimal' - -export const protobufPackage = 'communities.v1' - -/** - * Specs: - * :AUTOMATIC - * To Send - "AUTOMATIC" status ping every 5 minutes - * Display - Online for up to 5 minutes from the last clock, after that Offline - * :ALWAYS_ONLINE - * To Send - "ALWAYS_ONLINE" status ping every 5 minutes - * Display - Online for up to 2 weeks from the last clock, after that Offline - * :INACTIVE - * To Send - A single "INACTIVE" status ping - * Display - Offline forever - * Note: Only send pings if the user interacted with the app in the last x minutes. - */ -export interface StatusUpdate { - clock: number - statusType: StatusUpdate_StatusType - customText: string -} - -export enum StatusUpdate_StatusType { - UNKNOWN_STATUS_TYPE = 0, - AUTOMATIC = 1, - DO_NOT_DISTURB = 2, - ALWAYS_ONLINE = 3, - INACTIVE = 4, - UNRECOGNIZED = -1, -} - -export function statusUpdate_StatusTypeFromJSON( - object: any -): StatusUpdate_StatusType { - switch (object) { - case 0: - case 'UNKNOWN_STATUS_TYPE': - return StatusUpdate_StatusType.UNKNOWN_STATUS_TYPE - case 1: - case 'AUTOMATIC': - return StatusUpdate_StatusType.AUTOMATIC - case 2: - case 'DO_NOT_DISTURB': - return StatusUpdate_StatusType.DO_NOT_DISTURB - case 3: - case 'ALWAYS_ONLINE': - return StatusUpdate_StatusType.ALWAYS_ONLINE - case 4: - case 'INACTIVE': - return StatusUpdate_StatusType.INACTIVE - case -1: - case 'UNRECOGNIZED': - default: - return StatusUpdate_StatusType.UNRECOGNIZED - } -} - -export function statusUpdate_StatusTypeToJSON( - object: StatusUpdate_StatusType -): string { - switch (object) { - case StatusUpdate_StatusType.UNKNOWN_STATUS_TYPE: - return 'UNKNOWN_STATUS_TYPE' - case StatusUpdate_StatusType.AUTOMATIC: - return 'AUTOMATIC' - case StatusUpdate_StatusType.DO_NOT_DISTURB: - return 'DO_NOT_DISTURB' - case StatusUpdate_StatusType.ALWAYS_ONLINE: - return 'ALWAYS_ONLINE' - case StatusUpdate_StatusType.INACTIVE: - return 'INACTIVE' - default: - return 'UNKNOWN' - } -} - -const baseStatusUpdate: object = { clock: 0, statusType: 0, customText: '' } - -export const StatusUpdate = { - encode( - message: StatusUpdate, - writer: _m0.Writer = _m0.Writer.create() - ): _m0.Writer { - if (message.clock !== 0) { - writer.uint32(8).uint64(message.clock) - } - if (message.statusType !== 0) { - writer.uint32(16).int32(message.statusType) - } - if (message.customText !== '') { - writer.uint32(26).string(message.customText) - } - return writer - }, - - decode(input: _m0.Reader | Uint8Array, length?: number): StatusUpdate { - const reader = input instanceof _m0.Reader ? input : new _m0.Reader(input) - let end = length === undefined ? reader.len : reader.pos + length - const message = { ...baseStatusUpdate } as StatusUpdate - while (reader.pos < end) { - const tag = reader.uint32() - switch (tag >>> 3) { - case 1: - message.clock = longToNumber(reader.uint64() as Long) - break - case 2: - message.statusType = reader.int32() as any - break - case 3: - message.customText = reader.string() - break - default: - reader.skipType(tag & 7) - break - } - } - return message - }, - - fromJSON(object: any): StatusUpdate { - const message = { ...baseStatusUpdate } as StatusUpdate - if (object.clock !== undefined && object.clock !== null) { - message.clock = Number(object.clock) - } else { - message.clock = 0 - } - if (object.statusType !== undefined && object.statusType !== null) { - message.statusType = statusUpdate_StatusTypeFromJSON(object.statusType) - } else { - message.statusType = 0 - } - if (object.customText !== undefined && object.customText !== null) { - message.customText = String(object.customText) - } else { - message.customText = '' - } - return message - }, - - toJSON(message: StatusUpdate): unknown { - const obj: any = {} - message.clock !== undefined && (obj.clock = message.clock) - message.statusType !== undefined && - (obj.statusType = statusUpdate_StatusTypeToJSON(message.statusType)) - message.customText !== undefined && (obj.customText = message.customText) - return obj - }, - - fromPartial(object: DeepPartial): StatusUpdate { - const message = { ...baseStatusUpdate } as StatusUpdate - if (object.clock !== undefined && object.clock !== null) { - message.clock = object.clock - } else { - message.clock = 0 - } - if (object.statusType !== undefined && object.statusType !== null) { - message.statusType = object.statusType - } else { - message.statusType = 0 - } - if (object.customText !== undefined && object.customText !== null) { - message.customText = object.customText - } else { - message.customText = '' - } - 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' -})() - -type Builtin = - | Date - | Function - | Uint8Array - | string - | number - | boolean - | undefined -export type DeepPartial = T extends Builtin - ? T - : T extends Array - ? Array> - : T extends ReadonlyArray - ? ReadonlyArray> - : T extends {} - ? { [K in keyof T]?: DeepPartial } - : Partial - -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() -} diff --git a/packages/status-js/src/topics.ts b/packages/status-js/src/topics.ts deleted file mode 100644 index 2b635d0a..00000000 --- a/packages/status-js/src/topics.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { BN } from 'bn.js' -import { derive } from 'ecies-geth' -import { ec } from 'elliptic' - -import { idToContentTopic } from './contentTopic' -import { bufToHex, hexToBuf } from './utils' - -import type { Identity } from './identity' - -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 { - const key = EC.keyFromPublic(publicKey.slice(2), 'hex') - const sharedSecret = await derive( - Buffer.from(identity.privateKey), - Buffer.concat([hexToBuf(key.getPublic('hex'))]) - ) - return idToContentTopic(bufToHex(sharedSecret)) -} diff --git a/packages/status-js/src/utils.ts b/packages/status-js/src/utils.ts deleted file mode 100644 index 89a20f35..00000000 --- a/packages/status-js/src/utils.ts +++ /dev/null @@ -1,61 +0,0 @@ -import { ec } from 'elliptic' -import { PageDirection, utils } from 'js-waku' - -import { idToContactCodeTopic } from './contentTopic' -import { ChatIdentity } from './proto/communities/v1/chat_identity' - -import type { Waku } from 'js-waku' - -const EC = new ec('secp256k1') - -// TODO: rename -const hexToBuf = utils.hexToBytes -export { hexToBuf } - -// TODO: rename -/** - * Return hex string with 0x prefix (commonly used for string format of a community id/public key. - */ -export function bufToHex(buf: Uint8Array): string { - return '0x' + utils.bytesToHex(buf) -} - -export function compressPublicKey(key: Uint8Array): string { - const PubKey = EC.keyFromPublic(key) - return '0x' + PubKey.getPublic(true, 'hex') -} - -export function genPrivateKeyWithEntropy(key: string): Uint8Array { - const pair = EC.genKeyPair({ entropy: key }) - return hexToBuf('0x' + pair.getPrivate('hex')) -} - -export async function getLatestUserNickname( - key: Uint8Array, - waku: Waku -): Promise<{ clock: number; nickname: string }> { - const publicKey = bufToHex(key) - let nickname = '' - let clock = 0 - await waku.store.queryHistory([idToContactCodeTopic(publicKey)], { - callback: msgs => - msgs.some(e => { - try { - if (e.payload) { - const chatIdentity = ChatIdentity.decode(e?.payload) - if (chatIdentity) { - if (chatIdentity?.displayName) { - clock = chatIdentity?.clock ?? 0 - nickname = chatIdentity?.displayName - } - } - return true - } - } catch { - return false - } - }), - pageDirection: PageDirection.BACKWARD, - }) - return { clock, nickname } -} diff --git a/packages/status-js/src/utils/generate-key-from-password.test.ts b/packages/status-js/src/utils/generate-key-from-password.test.ts index 3308b642..c21537f2 100644 --- a/packages/status-js/src/utils/generate-key-from-password.test.ts +++ b/packages/status-js/src/utils/generate-key-from-password.test.ts @@ -4,7 +4,7 @@ import { generateKeyFromPassword } from './generate-key-from-password' describe('createSymKeyFromPassword', () => { it('should create symmetric key from password', async () => { - const password = 'password' + const password = 'arbitrary data here' const symKey = await generateKeyFromPassword(password) expect(bytesToHex(symKey)).toEqual( diff --git a/packages/status-js/src/utils/generate-key-from-password.ts b/packages/status-js/src/utils/generate-key-from-password.ts index 564d4c7b..5be4be64 100644 --- a/packages/status-js/src/utils/generate-key-from-password.ts +++ b/packages/status-js/src/utils/generate-key-from-password.ts @@ -1,5 +1,5 @@ import { pbkdf2 } from 'ethereum-cryptography/pbkdf2' -import { hexToBytes, utf8ToBytes } from 'ethereum-cryptography/utils' +import { utf8ToBytes } from 'ethereum-cryptography/utils' const AES_KEY_LENGTH = 32 // bytes @@ -10,7 +10,7 @@ export async function generateKeyFromPassword( password: string ): Promise { return await pbkdf2( - hexToBytes(password), + utf8ToBytes(password), utf8ToBytes(''), 65356, AES_KEY_LENGTH, diff --git a/packages/status-js/src/utils/id-to-content-topic.ts b/packages/status-js/src/utils/id-to-content-topic.ts index 4fc51967..cefaf2d6 100644 --- a/packages/status-js/src/utils/id-to-content-topic.ts +++ b/packages/status-js/src/utils/id-to-content-topic.ts @@ -1,5 +1,5 @@ import { keccak256 } from 'ethereum-cryptography/keccak' -import { bytesToHex, hexToBytes } from 'ethereum-cryptography/utils' +import { bytesToHex, utf8ToBytes } from 'ethereum-cryptography/utils' /** * waku spec: https://rfc.vac.dev/spec/23/#bridging-waku-v1-and-waku-v2 @@ -9,8 +9,8 @@ import { bytesToHex, hexToBytes } from 'ethereum-cryptography/utils' const TOPIC_LENGTH = 4 export function idToContentTopic(id: string): string { - const hash = keccak256(hexToBytes(id)) + const hash = keccak256(utf8ToBytes(id)) const topic = hash.slice(0, TOPIC_LENGTH) - return `/waku/1/${bytesToHex(topic)}/rfc26` + return `/waku/1/0x${bytesToHex(topic)}/rfc26` } diff --git a/packages/status-js/src/utils/recover-public-key.test.ts b/packages/status-js/src/utils/recover-public-key.test.ts index 4aed0978..b27e9028 100644 --- a/packages/status-js/src/utils/recover-public-key.test.ts +++ b/packages/status-js/src/utils/recover-public-key.test.ts @@ -1,9 +1,9 @@ import { bytesToHex, utf8ToBytes } from 'ethereum-cryptography/utils' -import { Account } from '../account' +import { Account } from '../client/account' import { recoverPublicKey } from './recover-public-key' -import type { ApplicationMetadataMessage } from '~/protos/application-metadata-message' +import type { ApplicationMetadataMessage } from '../../protos/application-metadata-message' describe('recoverPublicKey', () => { it('should recover public key', async () => { diff --git a/packages/status-js/src/wire/application_metadata_message.ts b/packages/status-js/src/wire/application_metadata_message.ts deleted file mode 100644 index 5186c0c9..00000000 --- a/packages/status-js/src/wire/application_metadata_message.ts +++ /dev/null @@ -1,75 +0,0 @@ -import { keccak256 } from 'js-sha3' -import { Reader } from 'protobufjs' -import secp256k1 from 'secp256k1' - -import * as proto from '../proto/status/v1/application_metadata_message' -import { hexToBuf } from '../utils' -import { ChatMessage } from './chat_message' - -import type { Identity } from '../identity' -import type { ApplicationMetadataMessage_Type } from '../proto/status/v1/application_metadata_message' - -export class ApplicationMetadataMessage { - private constructor(public proto: proto.ApplicationMetadataMessage) {} - - /** - * Create a chat message to be sent to an Open (permission = no membership) community - */ - public static create( - payload: Uint8Array, - type: ApplicationMetadataMessage_Type, - identity: Identity - ): ApplicationMetadataMessage { - const signature = identity.sign(payload) - - const proto = { - signature, - payload, - type, - } - - return new ApplicationMetadataMessage(proto) - } - - static decode(bytes: Uint8Array): ApplicationMetadataMessage { - const protoBuf = proto.ApplicationMetadataMessage.decode( - Reader.create(bytes) - ) - - return new ApplicationMetadataMessage(protoBuf) - } - - encode(): Uint8Array { - return proto.ApplicationMetadataMessage.encode(this.proto).finish() - } - - public get signature(): Uint8Array | undefined { - return this.proto.signature - } - - public get payload(): Uint8Array | undefined { - return this.proto.payload - } - public get type(): ApplicationMetadataMessage_Type | undefined { - return this.proto.type - } - - /** - * Returns a chat message if the type is [TYPE_CHAT_MESSAGE], undefined otherwise. - */ - public get chatMessage(): ChatMessage | undefined { - if (!this.payload) return - - return ChatMessage.decode(this.payload) - } - - public get signer(): Uint8Array | undefined { - if (!this.signature || !this.payload) return - - const signature = this.signature.slice(0, 64) - const recid = this.signature.slice(64)[0] - const hash = keccak256(this.payload) - - return secp256k1.ecdsaRecover(signature, recid, hexToBuf(hash)) - } -} diff --git a/packages/status-js/src/wire/chat_identity.ts b/packages/status-js/src/wire/chat_identity.ts deleted file mode 100644 index d6a4cd43..00000000 --- a/packages/status-js/src/wire/chat_identity.ts +++ /dev/null @@ -1,52 +0,0 @@ -import { Reader } from 'protobufjs' - -import * as proto from '../proto/communities/v1/chat_identity' - -import type { IdentityImage } from '../proto/communities/v1/chat_identity' - -export class ChatIdentity { - public constructor(public proto: proto.ChatIdentity) {} - - static decode(bytes: Uint8Array): ChatIdentity { - const protoBuf = proto.ChatIdentity.decode(Reader.create(bytes)) - - return new ChatIdentity(protoBuf) - } - - encode(): Uint8Array { - return proto.ChatIdentity.encode(this.proto).finish() - } - - /** Lamport timestamp of the message */ - get clock(): number | undefined { - return this.proto.clock - } - - /** ens_name is the valid ENS name associated with the chat key */ - get ensName(): string | undefined { - return this.proto.ensName - } - - /** images is a string indexed mapping of images associated with an identity */ - get images(): { [key: string]: IdentityImage } | undefined { - return this.proto.images - } - - /** display name is the user set identity, valid only for organisations */ - get displayName(): string | undefined { - return this.proto.displayName - } - - /** description is the user set description, valid only for organisations */ - get description(): string | undefined { - return this.proto.description - } - - get color(): string | undefined { - return this.proto.color - } - - get emoji(): string | undefined { - return this.proto.emoji - } -} diff --git a/packages/status-js/src/wire/chat_message.spec.ts b/packages/status-js/src/wire/chat_message.spec.ts deleted file mode 100644 index 2d97f2d6..00000000 --- a/packages/status-js/src/wire/chat_message.spec.ts +++ /dev/null @@ -1,93 +0,0 @@ -import { - AudioMessage_AudioType, - ChatMessage_ContentType, -} from '../proto/communities/v1/chat_message' -import { ImageType } from '../proto/communities/v1/enums' -import { ChatMessage, ContentType } from './chat_message' - -import type { AudioContent, ImageContent, StickerContent } from './chat_message' - -describe('Chat Message', () => { - // todo: - // test('Encode & decode Text message', () => { - // const payload = Buffer.from([1, 1]) - - // const imageContent: ImageContent = { - // image: payload, - // imageType: ImageType.IMAGE_TYPE_PNG, - // contentType: ContentType.Image, - // } - - // const message = ChatMessage.createMessage(1, 1, 'chat-id', imageContent) - - // const buf = message.encode() - // const dec = ChatMessage.decode(buf) - - // expect(dec.contentType).toEqual(ChatMessage_ContentType.CONTENT_TYPE_IMAGE) - // expect(dec.image?.payload?.toString()).toEqual(payload.toString()) - // expect(dec.image?.type).toEqual(ImageType.IMAGE_TYPE_PNG) - // }) - - test('Encode & decode Image message', () => { - const payload = Buffer.from([1, 1]) - - const imageContent: ImageContent = { - image: payload, - imageType: ImageType.IMAGE_TYPE_PNG, - contentType: ContentType.Image, - } - - const message = ChatMessage.createMessage(1, 1, 'chat-id', imageContent) - - const buf = message.encode() - const dec = ChatMessage.decode(buf) - - expect(dec.contentType).toEqual(ChatMessage_ContentType.CONTENT_TYPE_IMAGE) - expect(dec.image?.payload?.toString()).toEqual(payload.toString()) - expect(dec.image?.type).toEqual(ImageType.IMAGE_TYPE_PNG) - }) - - test('Encode & decode Audio message', () => { - const payload = Buffer.from([1, 1]) - const durationMs = 12345 - - const audioContent: AudioContent = { - audio: payload, - audioType: AudioMessage_AudioType.AUDIO_TYPE_AAC, - durationMs, - contentType: ContentType.Audio, - } - - const message = ChatMessage.createMessage(1, 1, 'chat-id', audioContent) - - const buf = message.encode() - const dec = ChatMessage.decode(buf) - - expect(dec.contentType).toEqual(ChatMessage_ContentType.CONTENT_TYPE_AUDIO) - expect(dec.audio?.payload?.toString()).toEqual(payload.toString()) - expect(dec.audio?.type).toEqual(ImageType.IMAGE_TYPE_PNG) - expect(dec.audio?.durationMs).toEqual(durationMs) - }) - - test('Encode & decode Sticker message', () => { - const hash = 'deadbeef' - const pack = 12345 - - const stickerContent: StickerContent = { - hash, - pack, - contentType: ContentType.Sticker, - } - - const message = ChatMessage.createMessage(1, 1, 'chat-id', stickerContent) - - const buf = message.encode() - const dec = ChatMessage.decode(buf) - - expect(dec.contentType).toEqual( - ChatMessage_ContentType.CONTENT_TYPE_STICKER - ) - expect(dec.sticker?.hash).toEqual(hash) - expect(dec.sticker?.pack).toEqual(pack) - }) -}) diff --git a/packages/status-js/src/wire/chat_message.ts b/packages/status-js/src/wire/chat_message.ts deleted file mode 100644 index 6a2b7c88..00000000 --- a/packages/status-js/src/wire/chat_message.ts +++ /dev/null @@ -1,224 +0,0 @@ -import { Reader } from 'protobufjs' - -import * as proto from '../proto/communities/v1/chat_message' -// import { proto.ChatMessage_ContentType } from '../proto/communities/v1/chat_message' -import { MessageType } from '../proto/communities/v1/enums' - -import type { - AudioMessage, - AudioMessage_AudioType, - ImageMessage, - StickerMessage, -} from '../proto/communities/v1/chat_message' -import type { ImageType } from '../proto/communities/v1/enums' - -export type Content = TextContent | StickerContent | ImageContent | AudioContent - -export enum ContentType { - Text, - Sticker, - Image, - Audio, -} - -export interface TextContent { - text: string - contentType: ContentType.Text -} - -export interface StickerContent { - hash: string - pack: number - contentType: ContentType.Sticker -} - -export interface ImageContent { - image: Uint8Array - imageType: ImageType - contentType: ContentType.Image -} - -export interface AudioContent { - audio: Uint8Array - audioType: AudioMessage_AudioType - durationMs: number - contentType: ContentType.Audio -} - -function isText(content: Content): content is TextContent { - return content.contentType === ContentType.Text -} - -function isSticker(content: Content): content is StickerContent { - return content.contentType === ContentType.Sticker -} - -function isImage(content: Content): content is ImageContent { - return content.contentType === ContentType.Image -} - -function isAudio(content: Content): content is AudioContent { - return content.contentType === ContentType.Audio -} - -export class ChatMessage { - private constructor(public _proto: proto.ChatMessage) {} - - /** - * Create a chat message to be sent to an Open (permission = no membership) community. - * - * @throws string If mediaContent is malformed - */ - public static createMessage( - clock: number, - timestamp: number, - chatId: string, - content: Content, - responseTo?: string - ): ChatMessage { - let sticker, - image, - audio, - text = 'Upgrade to the latest version to see this media content.' - let contentType = proto.ChatMessage_ContentType.CONTENT_TYPE_TEXT_PLAIN - - if (isText(content)) { - if (!content.text) throw 'Malformed Text Content' - text = content.text - contentType = proto.ChatMessage_ContentType.CONTENT_TYPE_TEXT_PLAIN - } else if (isSticker(content)) { - if (!content.hash || !content.pack) throw 'Malformed Sticker Content' - sticker = { - hash: content.hash, - pack: content.pack, - } - contentType = proto.ChatMessage_ContentType.CONTENT_TYPE_STICKER - } else if (isImage(content)) { - if (!content.image || !content.imageType) throw 'Malformed Image Content' - image = { - payload: content.image, - type: content.imageType, - } - contentType = proto.ChatMessage_ContentType.CONTENT_TYPE_IMAGE - } else if (isAudio(content)) { - if (!content.audio || !content.audioType || !content.durationMs) - throw 'Malformed Audio Content' - audio = { - payload: content.audio, - type: content.audioType, - durationMs: content.durationMs, - } - contentType = proto.ChatMessage_ContentType.CONTENT_TYPE_AUDIO - } - - const __proto = { - clock, // ms? - timestamp, //ms? - text, - /** Id of the message that we are replying to */ - responseTo: responseTo ?? '', - /** Ens name of the sender */ - ensName: '', - /** Public Key of the community (TBC) **/ - chatId, - /** The type of message (public/one-to-one/private-group-chat) */ - messageType: MessageType.MESSAGE_TYPE_COMMUNITY_CHAT, - /** The type of the content of the message */ - contentType, - sticker, - image, - audio, - community: undefined, // Used to share a community - grant: undefined, - } - - return new ChatMessage(__proto) - } - - static decode(bytes: Uint8Array): ChatMessage { - const protoBuf = proto.ChatMessage.decode(Reader.create(bytes)) - - return new ChatMessage(protoBuf) - } - - encode(): Uint8Array { - return proto.ChatMessage.encode(this._proto).finish() - } - - /** Lamport timestamp of the chat message */ - public get clock(): number | undefined { - return this._proto.clock - } - - /** - * Unix timestamps in milliseconds, currently not used as we use whisper as more reliable, but here - * so that we don't rely on it - */ - public get timestamp(): number | undefined { - return this._proto.timestamp - } - - /** - * Text of the message - */ - public get text(): string | undefined { - return this._proto.text - } - - /** - * Id of the message that we are replying to - */ - public get responseTo(): string | undefined { - return this._proto.responseTo - } - - /** - * Ens name of the sender - */ - public get ensName(): string | undefined { - return this._proto.ensName - } - - /** - * Chat id, this field is symmetric for public-chats and private group chats, - * but asymmetric in case of one-to-ones, as the sender will use the chat-id - * of the received, while the receiver will use the chat-id of the sender. - * Probably should be the concatenation of sender-pk & receiver-pk in alphabetical order - */ - public get chatId(): string { - return this._proto.chatId - } - - /** - * The type of message (public/one-to-one/private-group-chat) - */ - public get messageType(): MessageType | undefined { - return this._proto.messageType - } - - /** - * The type of the content of the message - */ - public get contentType(): proto.ChatMessage_ContentType | undefined { - return this._proto.contentType - } - - public get sticker(): StickerMessage | undefined { - return this._proto.sticker - } - - public get image(): ImageMessage | undefined { - return this._proto.image - } - - public get audio(): AudioMessage | undefined { - return this._proto.audio - } - - /** - * Used when sharing a community via a chat message. - */ - public get community(): Uint8Array | undefined { - return this._proto.community - } -} diff --git a/packages/status-js/src/wire/community_chat.ts b/packages/status-js/src/wire/community_chat.ts deleted file mode 100644 index f4f44f26..00000000 --- a/packages/status-js/src/wire/community_chat.ts +++ /dev/null @@ -1,59 +0,0 @@ -import { Reader } from 'protobufjs' - -import * as proto from '../proto/communities/v1/communities' -import { ChatIdentity } from './chat_identity' - -import type { - CommunityMember, - CommunityPermissions, -} from '../proto/communities/v1/communities' - -export class CommunityChat { - public constructor(public proto: proto.CommunityChat) {} - - /** - * Decode the payload as CommunityChat message. - * - * @throws - */ - static decode(bytes: Uint8Array): CommunityChat { - const protoBuf = proto.CommunityChat.decode(Reader.create(bytes)) - - return new CommunityChat(protoBuf) - } - - encode(): Uint8Array { - return proto.CommunityChat.encode(this.proto).finish() - } - - // TODO: check and document what is the key of the returned Map; - public get members(): Map { - const map = new Map() - - for (const key of Object.keys(this.proto.members)) { - map.set(key, this.proto.members[key]) - } - - return map - } - - public get permissions(): CommunityPermissions | undefined { - return this.proto.permissions - } - - public get identity(): ChatIdentity | undefined { - if (!this.proto.identity) return - - return new ChatIdentity(this.proto.identity) - } - - // TODO: Document this - public get categoryId(): string | undefined { - return this.proto.categoryId - } - - // TODO: Document this - public get position(): number | undefined { - return this.proto.position - } -} diff --git a/packages/status-js/src/wire/community_description.ts b/packages/status-js/src/wire/community_description.ts deleted file mode 100644 index a7c0df84..00000000 --- a/packages/status-js/src/wire/community_description.ts +++ /dev/null @@ -1,103 +0,0 @@ -import debug from 'debug' -import { Reader } from 'protobufjs' - -import { idToContentTopic } from '../contentTopic' -import { createSymKeyFromPassword } from '../encryption' -// TODO: replace for 'packages/status-js/protos/communities.ts' -import * as proto from '../proto/communities/v1/communities' -import { bufToHex } from '../utils' -import { ApplicationMetadataMessage } from './application_metadata_message' -import { ChatIdentity } from './chat_identity' - -import type { CommunityChat } from './community_chat' -import type { WakuMessage, WakuStore } from 'js-waku' - -const dbg = debug('communities:wire:community_description') - -export class CommunityDescription { - private constructor(public proto: proto.CommunityDescription) {} - - static decode(bytes: Uint8Array): CommunityDescription { - const protoBuf = proto.CommunityDescription.decode(Reader.create(bytes)) - - return new CommunityDescription(protoBuf) - } - - encode(): Uint8Array { - return proto.CommunityDescription.encode(this.proto).finish() - } - - /** - * Retrieves the most recent Community Description it can find on the network. - */ - public static async retrieve( - communityPublicKey: Uint8Array, - wakuStore: WakuStore - ): Promise { - const hexCommunityPublicKey = bufToHex(communityPublicKey) - // TEST: diff topic - const contentTopic = idToContentTopic(hexCommunityPublicKey) - - let communityDescription: CommunityDescription | undefined - - const callback = (messages: WakuMessage[]): void => { - // Value found, stop processing - if (communityDescription) return - - // Process most recent message first - const orderedMessages = messages.reverse() - orderedMessages.forEach((message: WakuMessage) => { - if (!message.payload) return - try { - const metadata = ApplicationMetadataMessage.decode(message.payload) - if (!metadata.payload) return - - const _communityDescription = CommunityDescription.decode( - metadata.payload - ) - - if (!_communityDescription.identity) return - - communityDescription = _communityDescription - } catch (e) { - dbg( - `Failed to decode message as CommunityDescription found on content topic ${contentTopic}`, - e - ) - } - }) - } - - const symKey = await createSymKeyFromPassword(hexCommunityPublicKey) - - await wakuStore - .queryHistory([contentTopic], { - callback, - decryptionKeys: [symKey], - }) - .catch(e => { - dbg( - `Failed to retrieve community description for ${hexCommunityPublicKey}`, - e - ) - }) - - return communityDescription - } - - get identity(): ChatIdentity | undefined { - if (!this.proto.identity) return - - return new ChatIdentity(this.proto.identity) - } - - get chats(): Map { - const map = new Map() - - for (const key of Object.keys(this.proto.chats)) { - map.set(key, this.proto.chats[key]) - } - - return map - } -} diff --git a/packages/status-js/src/wire/membership_update_message.ts b/packages/status-js/src/wire/membership_update_message.ts deleted file mode 100644 index 1787d0b9..00000000 --- a/packages/status-js/src/wire/membership_update_message.ts +++ /dev/null @@ -1,201 +0,0 @@ -import { keccak256 } from 'js-sha3' -import { Reader } from 'protobufjs' -import * as secp256k1 from 'secp256k1' -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 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() - 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(): MembershipSignedEvent[] { - return this.proto.events.map(bufArray => { - return new MembershipSignedEvent( - bufArray.slice(0, 65), - MembershipUpdateEvent.decode(bufArray.slice(65)), - this.chatId - ) - }) - } - - public get chatId(): string { - return this.proto.chatId - } - - encode(): Uint8Array { - return proto.MembershipUpdateMessage.encode(this.proto).finish() - } -} diff --git a/packages/status-js/src/wire/status_update.ts b/packages/status-js/src/wire/status_update.ts deleted file mode 100644 index f1573cf6..00000000 --- a/packages/status-js/src/wire/status_update.ts +++ /dev/null @@ -1,49 +0,0 @@ -import { Reader } from 'protobufjs' - -import * as proto from '../proto/communities/v1/status_update' - -export class StatusUpdate { - public constructor(public proto: proto.StatusUpdate) {} - - public static create( - statusType: proto.StatusUpdate_StatusType, - customText: string - ): StatusUpdate { - const clock = Date.now() - - const proto = { - clock, - statusType, - customText, - } - - return new StatusUpdate(proto) - } - - /** - * Decode the payload as CommunityChat message. - * - * @throws - */ - static decode(bytes: Uint8Array): StatusUpdate { - const protoBuf = proto.StatusUpdate.decode(Reader.create(bytes)) - - return new StatusUpdate(protoBuf) - } - - encode(): Uint8Array { - return proto.StatusUpdate.encode(this.proto).finish() - } - - public get clock(): number | undefined { - return this.proto.clock - } - - public get statusType(): proto.StatusUpdate_StatusType | undefined { - return this.proto.statusType - } - - public get customText(): string | undefined { - return this.proto.customText - } -} diff --git a/yarn.lock b/yarn.lock index 425d8bfe..4a350b58 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1043,18 +1043,6 @@ protobufjs "^6.11.2" uint8arrays "^3.0.0" -"@cspotcode/source-map-consumer@0.8.0": - version "0.8.0" - resolved "https://registry.yarnpkg.com/@cspotcode/source-map-consumer/-/source-map-consumer-0.8.0.tgz#33bf4b7b39c178821606f669bbc447a6a629786b" - integrity sha512-41qniHzTU8yAGbCp04ohlmSrZf8bkf/iJsl3V0dRGsQN/5GFfx+LbCSsCpp2gqrqjTVg/K6O8ycoV35JIwAzAg== - -"@cspotcode/source-map-support@0.7.0": - version "0.7.0" - resolved "https://registry.yarnpkg.com/@cspotcode/source-map-support/-/source-map-support-0.7.0.tgz#4789840aa859e46d2f3173727ab707c66bf344f5" - integrity sha512-X4xqRHqN8ACt2aHVe51OxeA2HjbcL4MqFqXkrmQszJ1NOUuUu5u6Vqx/0lZSVNku7velL5FC/s5uEAj1lsBMhA== - dependencies: - "@cspotcode/source-map-consumer" "0.8.0" - "@emotion/is-prop-valid@^0.8.8": version "0.8.8" resolved "https://registry.yarnpkg.com/@emotion/is-prop-valid/-/is-prop-valid-0.8.8.tgz#db28b1c4368a259b60a97311d6a952d4fd01ac1a" @@ -3279,6 +3267,11 @@ resolved "https://registry.yarnpkg.com/@stitches/react/-/react-1.2.8.tgz#954f8008be8d9c65c4e58efa0937f32388ce3a38" integrity sha512-9g9dWI4gsSVe8bNLlb+lMkBYsnIKCZTmvqvDG+Avnn69XfmHZKiaMrx7cgTaddq7aTPPmXiTsbFcUy0xgI4+wA== +"@swc/helpers@^0.2.11": + version "0.2.14" + resolved "https://registry.yarnpkg.com/@swc/helpers/-/helpers-0.2.14.tgz#20288c3627442339dd3d743c944f7043ee3590f0" + integrity sha512-wpCQMhf5p5GhNg2MmGKXzUNwxe7zRiCsmqYsamez2beP7mKPCSiu+BjZcdN95yYSzO857kr0VfQewmGpS77nqA== + "@swc/helpers@^0.3.15": version "0.3.16" resolved "https://registry.yarnpkg.com/@swc/helpers/-/helpers-0.3.16.tgz#896c44a5d476034d261f878bc4833da1624b1752" @@ -3291,26 +3284,6 @@ resolved "https://registry.yarnpkg.com/@trysound/sax/-/sax-0.2.0.tgz#cccaab758af56761eb7bf37af6f03f326dd798ad" integrity sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA== -"@tsconfig/node10@^1.0.7": - version "1.0.8" - resolved "https://registry.yarnpkg.com/@tsconfig/node10/-/node10-1.0.8.tgz#c1e4e80d6f964fbecb3359c43bd48b40f7cadad9" - integrity sha512-6XFfSQmMgq0CFLY1MslA/CPUfhIL919M1rMsa5lP2P097N2Wd1sSX0tx1u4olM16fLNhtHZpRhedZJphNJqmZg== - -"@tsconfig/node12@^1.0.7": - version "1.0.9" - resolved "https://registry.yarnpkg.com/@tsconfig/node12/-/node12-1.0.9.tgz#62c1f6dee2ebd9aead80dc3afa56810e58e1a04c" - integrity sha512-/yBMcem+fbvhSREH+s14YJi18sp7J9jpuhYByADT2rypfajMZZN4WQ6zBGgBKp53NKmqI36wFYDb3yaMPurITw== - -"@tsconfig/node14@^1.0.0": - version "1.0.1" - resolved "https://registry.yarnpkg.com/@tsconfig/node14/-/node14-1.0.1.tgz#95f2d167ffb9b8d2068b0b235302fafd4df711f2" - integrity sha512-509r2+yARFfHHE7T6Puu2jjkoycftovhXRqW328PDXTVGKihlb1P8Z9mMZH04ebyajfRY7dedfGynlrFHJUQCg== - -"@tsconfig/node16@^1.0.2": - version "1.0.2" - resolved "https://registry.yarnpkg.com/@tsconfig/node16/-/node16-1.0.2.tgz#423c77877d0569db20e1fc80885ac4118314010e" - integrity sha512-eZxlbI8GZscaGS7kkc/trHTT5xgrjH3/1n2JDwusC9iahPKWMRvRjJSAN5mCXviuTGQ/lHnhvv8Q1YTpnfz9gA== - "@types/babel__core@^7.1.14": version "7.1.19" resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.1.19.tgz#7b497495b7d1b4812bdb9d02804d0576f43ee460" @@ -3344,13 +3317,6 @@ dependencies: "@babel/types" "^7.3.0" -"@types/bn.js@*", "@types/bn.js@^5.1.0": - version "5.1.0" - resolved "https://registry.yarnpkg.com/@types/bn.js/-/bn.js-5.1.0.tgz#32c5d271503a12653c62cf4d2b45e6eab8cebc68" - integrity sha512-QSSVYj7pYFN49kW77o2s9xTCwZ8F2xLbjLLSEVh8D2F4JUhZtPAGOFLTD+ffqksBx/u4cE/KImFjyhqCjn/LIA== - dependencies: - "@types/node" "*" - "@types/debug@^4.1.7": version "4.1.7" resolved "https://registry.yarnpkg.com/@types/debug/-/debug-4.1.7.tgz#7cc0ea761509124709b8b2d1090d8f6c17aadb82" @@ -3365,13 +3331,6 @@ dependencies: "@types/node" "*" -"@types/elliptic@^6.4.14": - version "6.4.14" - resolved "https://registry.yarnpkg.com/@types/elliptic/-/elliptic-6.4.14.tgz#7bbaad60567a588c1f08b10893453e6b9b4de48e" - integrity sha512-z4OBcDAU0GVwDTuwJzQCiL6188QvZMkvoERgcVjq0/mPM8jCfdwZ3x5zQEVoL9WCAru3aG5wl3Z5Ww5wBWn7ZQ== - dependencies: - "@types/bn.js" "*" - "@types/emoji-mart@^3.0.6": version "3.0.9" resolved "https://registry.yarnpkg.com/@types/emoji-mart/-/emoji-mart-3.0.9.tgz#2f7ef5d9ec194f28029c46c81a5fc1e5b0efa73c" @@ -3446,11 +3405,6 @@ resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee" integrity sha1-7ihweulOEdK4J7y+UnC86n8+ce4= -"@types/lodash@^4.14.182": - version "4.14.182" - resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.182.tgz#05301a4d5e62963227eaafe0ce04dd77c54ea5c2" - integrity sha512-/THyiqyQAP9AfARo4pF+aCGcyiQ94tX/Is2I7HofNRqoYLgN1PBoOWu2/zTA5zMxzP5EFutMtWtGAFRKUe961Q== - "@types/long@^4.0.1": version "4.0.1" resolved "https://registry.yarnpkg.com/@types/long/-/long-4.0.1.tgz#459c65fa1867dafe6a8f322c4c51695663cc55e9" @@ -3486,23 +3440,11 @@ resolved "https://registry.yarnpkg.com/@types/normalize-package-data/-/normalize-package-data-2.4.1.tgz#d3357479a0fdfdd5907fe67e17e0a85c906e1301" integrity sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw== -"@types/object-hash@^1.3.0": - version "1.3.4" - resolved "https://registry.yarnpkg.com/@types/object-hash/-/object-hash-1.3.4.tgz#079ba142be65833293673254831b5e3e847fe58b" - integrity sha512-xFdpkAkikBgqBdG9vIlsqffDV8GpvnPEzs0IUtr1v3BEB97ijsFQ4RXVbUZwjFThhB4MDSTUfvmxUD5PGx0wXA== - "@types/parse-json@^4.0.0": version "4.0.0" resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.0.tgz#2f8bb441434d163b35fb8ffdccd7138927ffb8c0" integrity sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA== -"@types/pbkdf2@^3.1.0": - version "3.1.0" - resolved "https://registry.yarnpkg.com/@types/pbkdf2/-/pbkdf2-3.1.0.tgz#039a0e9b67da0cdc4ee5dab865caa6b267bb66b1" - integrity sha512-Cf63Rv7jCQ0LaL8tNXmEyqTHuIJxRdlS5vMh1mj5voN4+QFhVZnlZruezqpWYDiJ8UTzhP0VmeLXCmBk66YrMQ== - dependencies: - "@types/node" "*" - "@types/prettier@^2.1.5": version "2.6.3" resolved "https://registry.yarnpkg.com/@types/prettier/-/prettier-2.6.3.tgz#68ada76827b0010d0db071f739314fa429943d0a" @@ -3539,13 +3481,6 @@ resolved "https://registry.yarnpkg.com/@types/scheduler/-/scheduler-0.16.2.tgz#1a62f89525723dde24ba1b01b092bf5df8ad4d39" integrity sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew== -"@types/secp256k1@^4.0.3": - version "4.0.3" - resolved "https://registry.yarnpkg.com/@types/secp256k1/-/secp256k1-4.0.3.tgz#1b8e55d8e00f08ee7220b4d59a6abe89c37a901c" - integrity sha512-Da66lEIFeIz9ltsdMZcpQvmrmmoqrfju8pm1BH8WbYjZSwUgCwXLb9C+9XYogwBITnbsSaMdVPb2ekf7TV+03w== - dependencies: - "@types/node" "*" - "@types/stack-utils@^2.0.0": version "2.0.1" resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-2.0.1.tgz#20f18294f797f2209b5f65c8e3b5c8e8261d127c" @@ -3560,11 +3495,6 @@ "@types/react" "*" csstype "^3.0.2" -"@types/uuid@^8.3.3": - version "8.3.4" - resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-8.3.4.tgz#bd86a43617df0594787d38b735f55c805becf1bc" - integrity sha512-c/I8ZRb51j+pYGAu5CrFMRxqZ2ke4y2grEBO5AUjgSkSk+qT2Ea+OdWElz/OiMf5MNpn2b17kuVBwZLQJXzihw== - "@types/yargs-parser@*": version "20.2.1" resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-20.2.1.tgz#3b9ce2489919d9e4fea439b76916abc34b2df129" @@ -3686,12 +3616,7 @@ acorn-jsx@^5.3.1: resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937" integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== -acorn-walk@^8.1.1: - version "8.2.0" - resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.2.0.tgz#741210f2e2426454508853a2f44d0ab83b7f69c1" - integrity sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA== - -acorn@^8.4.1, acorn@^8.5.0, acorn@^8.7.0: +acorn@^8.5.0, acorn@^8.7.0: version "8.7.0" resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.7.0.tgz#90951fde0f8f09df93549481e5fc141445b791cf" integrity sha512-V/LGr1APy+PXIwKebEWrkZPwoeoF+w1jiOBUmuxuiUIaOHtob8Qc9BTrYo7VuI5fR8tqsy+buA2WFooR5olqvQ== @@ -3773,11 +3698,6 @@ anymatch@^3.0.3: normalize-path "^3.0.0" picomatch "^2.0.4" -arg@^4.1.0: - version "4.1.3" - resolved "https://registry.yarnpkg.com/arg/-/arg-4.1.3.tgz#269fc7ad5b8e42cb63c896d5666017261c144089" - integrity sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA== - argparse@^1.0.7: version "1.0.10" resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" @@ -4076,7 +3996,7 @@ bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.11.9: resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.12.0.tgz#775b3f278efbb9718eec7361f483fb36fbbfea88" integrity sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA== -bn.js@^5.0.0, bn.js@^5.1.1, bn.js@^5.2.0: +bn.js@^5.0.0, bn.js@^5.1.1: version "5.2.0" resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-5.2.0.tgz#358860674396c6997771a9d051fcc1b57d4ae002" integrity sha512-D7iWRBvnZE8ecXiLj/9wbxH7Tk79fAh8IHaTNq1RWRixsS02W+5qS+iE9yq6RYl0asXx5tw0bLhmT5pIfbSquw== @@ -4516,11 +4436,6 @@ create-hmac@^1.1.0, create-hmac@^1.1.4, create-hmac@^1.1.7: safe-buffer "^5.0.1" sha.js "^2.4.8" -create-require@^1.1.0: - version "1.1.1" - resolved "https://registry.yarnpkg.com/create-require/-/create-require-1.1.1.tgz#c1d7e8f1e5f6cfc9ff65f9cd352d37348756c333" - integrity sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ== - cross-spawn@^6.0.0, cross-spawn@^6.0.5: version "6.0.5" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-6.0.5.tgz#4a5ec7c64dfae22c3a14124dbacdee846d80cbc4" @@ -4681,11 +4596,6 @@ dashdash@^1.12.0: dependencies: assert-plus "^1.0.0" -dataloader@^1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/dataloader/-/dataloader-1.4.0.tgz#bca11d867f5d3f1b9ed9f737bd15970c65dff5c8" - integrity sha512-68s5jYdlvasItOJnCuI2Q9s4q98g0pCyL3HrcKJu8KNugUl8ahgmZYg38ysLTgQjjXX3H8CJLkAvWrclWfcalw== - datastore-core@^7.0.0: version "7.0.1" resolved "https://registry.yarnpkg.com/datastore-core/-/datastore-core-7.0.1.tgz#f50f30bb55474a569118d41bba6052896b096aec" @@ -4814,11 +4724,6 @@ diff-sequences@^28.0.2: resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-28.0.2.tgz#40f8d4ffa081acbd8902ba35c798458d0ff1af41" integrity sha512-YtEoNynLDFCRznv/XDalsKGSZDoj0U5kLnXvY0JSq3nBboRrZXjD81+eSiwi+nzcZDwedMmcowcxNwwgFW23mQ== -diff@^4.0.1: - version "4.0.2" - resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d" - integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A== - diffie-hellman@^5.0.0: version "5.0.3" resolved "https://registry.yarnpkg.com/diffie-hellman/-/diffie-hellman-5.0.3.tgz#40e8ee98f55a2149607146921c63e1ae5f3d2875" @@ -4935,14 +4840,6 @@ ecc-jsbn@~0.1.1: jsbn "~0.1.0" safer-buffer "^2.1.0" -ecies-geth@^1.5.3: - version "1.6.3" - resolved "https://registry.yarnpkg.com/ecies-geth/-/ecies-geth-1.6.3.tgz#7b58434b6d7a4d93d1c54b5abe8974b5e911004a" - integrity sha512-RAZs5p0MZLGWXt3weAHjefnWzJwTDvMw8GizSHhPNM8HkGDkRnOjbJtN613BD+/EOPaTP5j7bwwd83WhJq+5Ew== - dependencies: - elliptic "^6.5.4" - secp256k1 "^4.0.3" - electron-fetch@^1.7.2: version "1.7.4" resolved "https://registry.yarnpkg.com/electron-fetch/-/electron-fetch-1.7.4.tgz#af975ab92a14798bfaa025f88dcd2e54a7b0b769" @@ -4960,7 +4857,7 @@ electron-to-chromium@^1.4.71: resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.71.tgz#17056914465da0890ce00351a3b946fd4cd51ff6" integrity sha512-Hk61vXXKRb2cd3znPE9F+2pLWdIOmP7GjiTj45y6L3W/lO+hSnUSUhq+6lEaERWBdZOHbk2s3YV5c9xVl3boVw== -elliptic@^6.5.3, elliptic@^6.5.4: +elliptic@^6.5.3: version "6.5.4" resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.5.4.tgz#da37cebd31e79a1367e941b592ed1fbebd58abbb" integrity sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ== @@ -7352,7 +7249,7 @@ lodash.uniq@^4.5.0: resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773" integrity sha1-0CJTc662Uq3BvILklFM5qEJ1R3M= -lodash@^4.17.11, lodash@^4.17.15, lodash@^4.17.21, lodash@^4.17.4: +lodash@^4.17.11, lodash@^4.17.4: version "4.17.21" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== @@ -7405,7 +7302,7 @@ make-dir@^3.0.0: dependencies: semver "^6.0.0" -make-error@1.x, make-error@^1.1.1: +make-error@1.x: version "1.3.6" resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.6.tgz#2eb2e37ea9b67c4891f684a1394799af484cf7a2" integrity sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw== @@ -7683,11 +7580,6 @@ nice-try@^1.0.4: resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366" integrity sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ== -node-addon-api@^2.0.0: - version "2.0.2" - resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-2.0.2.tgz#432cfa82962ce494b132e9d72a15b29f71ff5d32" - integrity sha512-Ntyt4AIXyaLIuMHF6IOoTakB3K+RWxwtsHNRxllEoA6vPwP9o4866g6YWDLUdnucilZhmkxiHwHr11gAENw+QA== - node-addon-api@^3.2.1: version "3.2.1" resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-3.2.1.tgz#81325e0a2117789c0128dab65e7e38f07ceba161" @@ -7712,7 +7604,7 @@ node-gyp-build-optional-packages@^4.3.2: resolved "https://registry.yarnpkg.com/node-gyp-build-optional-packages/-/node-gyp-build-optional-packages-4.3.5.tgz#a1de0039f81ecacecefcbb4349cdb96842343b31" integrity sha512-5ke7D8SiQsTQL7CkHpfR1tLwfqtKc0KYEmlnkwd40jHCASskZeS98qoZ1qDUns2aUQWikcjidRUs6PM/3iyN/w== -node-gyp-build@^4.2.0, node-gyp-build@^4.2.3, node-gyp-build@^4.3.0: +node-gyp-build@^4.2.3, node-gyp-build@^4.3.0: version "4.3.0" resolved "https://registry.yarnpkg.com/node-gyp-build/-/node-gyp-build-4.3.0.tgz#9f256b03e5826150be39c764bf51e993946d71a3" integrity sha512-iWjXZvmboq0ja1pUGULQBexmxq8CV4xBhX7VDOTbL7ZR4FOowwY/VOtRxBN/yKxmdGoIp4j5ysNT4u3S2pDQ3Q== @@ -7813,11 +7705,6 @@ object-assign@^4.1.1: resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM= -object-hash@^1.3.1: - version "1.3.1" - resolved "https://registry.yarnpkg.com/object-hash/-/object-hash-1.3.1.tgz#fde452098a951cb145f039bb7d455449ddc126df" - integrity sha512-OSuu/pU4ENM9kmREg0BdNrUDIl1heYa4mBZacJc+vVWz4GtAwu7jO8s4AIt2aGRUTqxykpWzI3Oqnsm13tTMDA== - object-inspect@^1.11.0, object-inspect@^1.12.0, object-inspect@^1.9.0: version "1.12.0" resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.12.0.tgz#6e2c120e868fd1fd18cb4f18c31741d0d6e776f0" @@ -8181,7 +8068,7 @@ path-type@^4.0.0: resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b" integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== -pbkdf2@^3.0.3, pbkdf2@^3.1.2: +pbkdf2@^3.0.3: version "3.1.2" resolved "https://registry.yarnpkg.com/pbkdf2/-/pbkdf2-3.1.2.tgz#dd822aa0887580e52f1a039dc3eda108efae3075" integrity sha512-iuh7L6jA7JEGu2WxDwtQP1ddOpaJNC4KlDEFfdQajSGgGPNi4OyDc2R7QnbY2bR9QjBVGwgvTdNJZoE7RaxUMA== @@ -9042,15 +8929,6 @@ scheduler@^0.20.2: loose-envify "^1.1.0" object-assign "^4.1.1" -secp256k1@^4.0.2, secp256k1@^4.0.3: - version "4.0.3" - resolved "https://registry.yarnpkg.com/secp256k1/-/secp256k1-4.0.3.tgz#c4559ecd1b8d3c1827ed2d1b94190d69ce267303" - integrity sha512-NLZVf+ROMxwtEj3Xa562qgv2BK5e2WNmXPiOdVIPLgs6lyTzMvBq0aWTYMI5XCP9jZMVKOcqZLw/Wc4vDkuxhA== - dependencies: - elliptic "^6.5.4" - node-addon-api "^2.0.0" - node-gyp-build "^4.2.0" - "semver@2 || 3 || 4 || 5", semver@^5.5.0, semver@^5.7.0, semver@^5.7.1: version "5.7.1" resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" @@ -9643,53 +9521,6 @@ ts-jest@^28.0.4: semver "7.x" yargs-parser "^20.x" -ts-node@^10.2.1: - version "10.5.0" - resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-10.5.0.tgz#618bef5854c1fbbedf5e31465cbb224a1d524ef9" - integrity sha512-6kEJKwVxAJ35W4akuiysfKwKmjkbYxwQMTBaAxo9KKAx/Yd26mPUyhGz3ji+EsJoAgrLqVsYHNuuYwQe22lbtw== - dependencies: - "@cspotcode/source-map-support" "0.7.0" - "@tsconfig/node10" "^1.0.7" - "@tsconfig/node12" "^1.0.7" - "@tsconfig/node14" "^1.0.0" - "@tsconfig/node16" "^1.0.2" - acorn "^8.4.1" - acorn-walk "^8.1.1" - arg "^4.1.0" - create-require "^1.1.0" - diff "^4.0.1" - make-error "^1.1.1" - v8-compile-cache-lib "^3.0.0" - yn "3.1.1" - -"ts-poet@^t 4.11.0": - version "4.11.0" - resolved "https://registry.yarnpkg.com/ts-poet/-/ts-poet-4.11.0.tgz#5566f499ec767920cc18b977624f084c52e8734a" - integrity sha512-OaXnCKsRs0yrc0O7LFhnq/US2DB4Wd313cS+qjG2XMksZ74pF/jvMHkJdURXJiAo4kSahL2N4e8JOdwUjOMNdw== - dependencies: - lodash "^4.17.15" - prettier "^2.5.1" - -ts-proto-descriptors@1.6.0: - version "1.6.0" - resolved "https://registry.yarnpkg.com/ts-proto-descriptors/-/ts-proto-descriptors-1.6.0.tgz#ca6eafc882495a2e920da5b981d7b181b4e49c38" - integrity sha512-Vrhue2Ti99us/o76mGy28nF3W/Uanl1/8detyJw2yyRwiBC5yxy+hEZqQ/ZX2PbZ1vyCpJ51A9L4PnCCnkBMTQ== - dependencies: - long "^4.0.0" - protobufjs "^6.8.8" - -ts-proto@^1.115.1: - version "1.115.1" - resolved "https://registry.yarnpkg.com/ts-proto/-/ts-proto-1.115.1.tgz#262d9506fe575e5d1a821397ae7f72df2ea8574d" - integrity sha512-Zq1TvLQdnD6eNhbfdccnk1X9tVN5bwwPX4n2/gR9rVEHApZwHInV5Ntqd9lzl22lJ2LjlCNNYQdMlWak8d3EGA== - dependencies: - "@types/object-hash" "^1.3.0" - dataloader "^1.4.0" - object-hash "^1.3.1" - protobufjs "^6.11.3" - ts-poet "^t 4.11.0" - ts-proto-descriptors "1.6.0" - tsconfig-paths@^3.12.0, tsconfig-paths@^3.9.0: version "3.12.0" resolved "https://registry.yarnpkg.com/tsconfig-paths/-/tsconfig-paths-3.12.0.tgz#19769aca6ee8f6a1a341e38c8fa45dd9fb18899b" @@ -9875,11 +9706,6 @@ uuid@^8.3.2: resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2" integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg== -v8-compile-cache-lib@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.0.tgz#0582bcb1c74f3a2ee46487ceecf372e46bce53e8" - integrity sha512-mpSYqfsFvASnSn5qMiwrr4VKfumbPyONLCOPmsR3A6pTY/r0+tSaVbgPWSAIuzbk3lCTa+FForeTiO+wBQGkjA== - v8-compile-cache@^2.0.0, v8-compile-cache@^2.0.3: version "2.3.0" resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz#2de19618c66dc247dcfb6f99338035d8245a2cee" @@ -10126,11 +9952,6 @@ yargs@^17.3.1: y18n "^5.0.5" yargs-parser "^21.0.0" -yn@3.1.1: - version "3.1.1" - resolved "https://registry.yarnpkg.com/yn/-/yn-3.1.1.tgz#1e87401a09d767c1d5eab26a6e4c185182d2eb50" - integrity sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q== - yocto-queue@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b"