remove packages/status-js/src/community.ts
This commit is contained in:
parent
90834ba00e
commit
54fadb0cd2
|
@ -1,78 +0,0 @@
|
||||||
// todo?: move to __tests__
|
|
||||||
import { Community } from './community'
|
|
||||||
|
|
||||||
import type { CommunityDescription } from './wire/community_description'
|
|
||||||
import type { Waku, WakuMessage } from 'js-waku'
|
|
||||||
|
|
||||||
// todo: move to __mocks__
|
|
||||||
const WakuMock = {
|
|
||||||
store: {
|
|
||||||
queryHistory: jest.fn(),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
describe('Community', () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
// todo?: set as config instead
|
|
||||||
jest.resetAllMocks()
|
|
||||||
})
|
|
||||||
|
|
||||||
test('Retrieves community description', async () => {
|
|
||||||
// todo: move to __fixtures__
|
|
||||||
const messages = [
|
|
||||||
{
|
|
||||||
payload: new Uint8Array([
|
|
||||||
10, 65, 34, 159, 71, 242, 74, 40, 51, 11, 24, 120, 155, 102, 213, 241,
|
|
||||||
204, 130, 230, 70, 184, 67, 216, 119, 77, 31, 226, 210, 217, 31, 82,
|
|
||||||
178, 170, 106, 1, 67, 78, 127, 79, 198, 106, 0, 184, 167, 1, 117, 214,
|
|
||||||
100, 35, 204, 53, 241, 245, 213, 210, 12, 223, 184, 203, 240, 170,
|
|
||||||
153, 47, 186, 157, 134, 1, 18, 191, 2, 8, 3, 18, 140, 1, 10, 132, 1,
|
|
||||||
48, 120, 48, 52, 97, 99, 52, 49, 57, 100, 97, 99, 57, 97, 56, 98, 98,
|
|
||||||
98, 53, 56, 56, 50, 53, 97, 51, 99, 100, 101, 54, 48, 101, 101, 102,
|
|
||||||
48, 101, 101, 55, 49, 98, 56, 99, 102, 54, 99, 54, 51, 100, 102, 54,
|
|
||||||
49, 49, 101, 101, 101, 102, 99, 56, 101, 55, 97, 97, 99, 55, 99, 55,
|
|
||||||
57, 98, 53, 53, 57, 53, 52, 98, 54, 55, 57, 100, 50, 52, 99, 102, 53,
|
|
||||||
101, 99, 56, 50, 100, 97, 55, 101, 100, 57, 50, 49, 99, 97, 102, 50,
|
|
||||||
52, 48, 54, 50, 56, 97, 57, 98, 102, 98, 51, 52, 53, 48, 99, 53, 49,
|
|
||||||
49, 49, 97, 57, 99, 102, 102, 101, 53, 52, 101, 54, 51, 49, 56, 49,
|
|
||||||
49, 18, 3, 10, 1, 1, 26, 2, 24, 3, 42, 41, 34, 16, 66, 111, 114, 105,
|
|
||||||
110, 103, 32, 99, 111, 109, 109, 117, 110, 105, 116, 121, 42, 12, 68,
|
|
||||||
101, 115, 99, 114, 105, 112, 116, 105, 111, 110, 46, 50, 7, 35, 52,
|
|
||||||
51, 54, 48, 68, 70, 50, 60, 10, 36, 56, 52, 57, 53, 48, 98, 53, 50,
|
|
||||||
45, 51, 97, 52, 52, 45, 52, 98, 102, 102, 45, 97, 52, 56, 99, 45, 52,
|
|
||||||
98, 100, 102, 99, 48, 57, 55, 51, 49, 102, 57, 18, 20, 18, 2, 24, 1,
|
|
||||||
26, 14, 34, 6, 114, 97, 110, 100, 111, 109, 42, 4, 100, 101, 115, 99,
|
|
||||||
50, 63, 10, 36, 48, 48, 100, 51, 102, 53, 50, 53, 45, 97, 48, 99, 102,
|
|
||||||
45, 52, 99, 52, 48, 45, 56, 51, 50, 100, 45, 53, 52, 51, 101, 99, 57,
|
|
||||||
102, 56, 49, 56, 56, 98, 18, 23, 18, 2, 24, 1, 26, 15, 34, 8, 109,
|
|
||||||
101, 115, 115, 97, 103, 101, 115, 42, 3, 100, 115, 99, 40, 1, 24, 25,
|
|
||||||
]),
|
|
||||||
},
|
|
||||||
] as unknown as WakuMessage[]
|
|
||||||
|
|
||||||
WakuMock.store.queryHistory.mockImplementationOnce((_, { callback }) =>
|
|
||||||
Promise.resolve(callback(messages))
|
|
||||||
)
|
|
||||||
|
|
||||||
const community = await Community.instantiateCommunity(
|
|
||||||
'0x02cf13719c8b836bebd4e430c497ee38e798a43e4d8c4760c34bbd9bf4f2434d26',
|
|
||||||
WakuMock as unknown as Waku
|
|
||||||
)
|
|
||||||
const desc = community.description as CommunityDescription
|
|
||||||
expect(desc).toBeDefined()
|
|
||||||
|
|
||||||
expect(desc.identity?.displayName).toEqual('Boring community')
|
|
||||||
|
|
||||||
const descChats = Array.from(desc.chats.values()).map(
|
|
||||||
chat => chat?.identity?.displayName
|
|
||||||
)
|
|
||||||
expect(descChats).toEqual(expect.arrayContaining(['random']))
|
|
||||||
expect(descChats).toEqual(expect.arrayContaining(['messages']))
|
|
||||||
|
|
||||||
const chats = Array.from(community.chats.values()).map(
|
|
||||||
chat => chat?.communityChat?.identity?.displayName
|
|
||||||
)
|
|
||||||
expect(chats).toEqual(expect.arrayContaining(['random']))
|
|
||||||
expect(chats).toEqual(expect.arrayContaining(['messages']))
|
|
||||||
})
|
|
||||||
})
|
|
|
@ -1,301 +0,0 @@
|
||||||
import debug from 'debug'
|
|
||||||
|
|
||||||
import { Chat } from './chat'
|
|
||||||
import { idToContentTopic } from './contentTopic'
|
|
||||||
import { ApplicationMetadataMessage_Type } from './proto/status/v1/application_metadata_message'
|
|
||||||
import { bufToHex, hexToBuf } from './utils'
|
|
||||||
import { ApplicationMetadataMessage } from './wire/application_metadata_message'
|
|
||||||
import { ChatMessage } from './wire/chat_message'
|
|
||||||
import { CommunityDescription } from './wire/community_description'
|
|
||||||
|
|
||||||
import type { CommunityChat } from './wire/community_chat'
|
|
||||||
import type { Waku, WakuMessage } from 'js-waku'
|
|
||||||
import { createSymKeyFromPassword } from './encryption'
|
|
||||||
// import { createSymKeyFromPassword } from './encryption'
|
|
||||||
|
|
||||||
type Events = 'communityfetch' | 'communityudpate' | 'channelmessagereceive'
|
|
||||||
|
|
||||||
const dbg = debug('communities:community')
|
|
||||||
|
|
||||||
export class Community {
|
|
||||||
public publicKey: Uint8Array
|
|
||||||
private waku: Waku
|
|
||||||
// TODO?: rename to channels
|
|
||||||
public chats: Map<string, Chat> // Chat id, Chat
|
|
||||||
public description?: CommunityDescription
|
|
||||||
|
|
||||||
constructor(publicKey: Uint8Array, waku: Waku) {
|
|
||||||
this.publicKey = publicKey
|
|
||||||
this.waku = waku
|
|
||||||
this.chats = new Map()
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: explain why init func instead of constructor
|
|
||||||
/**
|
|
||||||
* Instantiate a Community by retrieving its details from the Waku network.
|
|
||||||
*
|
|
||||||
* This class is used to interact with existing communities only,
|
|
||||||
* the Status Desktop or Mobile app must be used to manage a community.
|
|
||||||
*
|
|
||||||
* @param publicKey The community's public key in hex format.
|
|
||||||
* Can be found in the community's invite link: https://join.status.im/c/<public key>
|
|
||||||
* @param waku The Waku instance, used to retrieve Community information from the network.
|
|
||||||
*/
|
|
||||||
public static async instantiateCommunity(
|
|
||||||
publicKey: string,
|
|
||||||
waku: Waku
|
|
||||||
): Promise<Community> {
|
|
||||||
const community = new Community(hexToBuf(publicKey), waku)
|
|
||||||
|
|
||||||
await community.refreshCommunityDescription()
|
|
||||||
|
|
||||||
return community
|
|
||||||
}
|
|
||||||
|
|
||||||
public get publicKeyStr(): string {
|
|
||||||
return bufToHex(this.publicKey)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieve and update community information from the network.
|
|
||||||
* Uses most recent community description message available.
|
|
||||||
*/
|
|
||||||
async refreshCommunityDescription(): Promise<void> {
|
|
||||||
const desc = await CommunityDescription.retrieve(
|
|
||||||
this.publicKey,
|
|
||||||
this.waku.store
|
|
||||||
)
|
|
||||||
|
|
||||||
if (!desc) {
|
|
||||||
dbg(`Failed to retrieve Community Description for ${this.publicKeyStr}`)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
this.description = desc
|
|
||||||
|
|
||||||
await Promise.all(
|
|
||||||
Array.from(this.description.chats).map(([chatUuid, communityChat]) => {
|
|
||||||
return this.instantiateChat(chatUuid, communityChat)
|
|
||||||
})
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Instantiate [[Chat]] object based on the passed chat name.
|
|
||||||
* The Chat MUST already be part of the Community and the name MUST be exact (including casing).
|
|
||||||
*
|
|
||||||
* @throws string If the Community Description is unavailable or the chat is not found;
|
|
||||||
*/
|
|
||||||
private async instantiateChat(
|
|
||||||
chatUuid: string,
|
|
||||||
communityChat: CommunityChat
|
|
||||||
): Promise<void> {
|
|
||||||
if (!this.description)
|
|
||||||
throw 'Failed to retrieve community description, cannot instantiate chat'
|
|
||||||
|
|
||||||
const chatId = this.publicKeyStr + chatUuid
|
|
||||||
if (this.chats.get(chatId)) return
|
|
||||||
|
|
||||||
const chat = await Chat.create(chatId, communityChat)
|
|
||||||
|
|
||||||
this.chats.set(chatId, chat)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class CommunityBeta {
|
|
||||||
private waku: Waku
|
|
||||||
private publicKey: string
|
|
||||||
private callbacks: any = {}
|
|
||||||
private communityData: any = {}
|
|
||||||
private channelEvents: any = {}
|
|
||||||
private symKey
|
|
||||||
|
|
||||||
private channels: Record<string, ChatMessage[]> = {}
|
|
||||||
|
|
||||||
constructor(waku: Waku, publicKey: string) {
|
|
||||||
this.waku = waku
|
|
||||||
this.publicKey = publicKey
|
|
||||||
}
|
|
||||||
|
|
||||||
// todo: observer only if some callbacks set
|
|
||||||
// todo: doc as to be called after callbacks have been added
|
|
||||||
public async start() {
|
|
||||||
this.symKey = await createSymKeyFromPassword(this.publicKey)
|
|
||||||
|
|
||||||
const communityTopic = idToContentTopic(this.publicKey)
|
|
||||||
|
|
||||||
await this.fetchCommunity(communityTopic)
|
|
||||||
// this.observeCommunity(communityTopic)
|
|
||||||
|
|
||||||
// const channelTopics = []
|
|
||||||
// this.observeChannelMessages(channelTopics)
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: should return unsubscribe function
|
|
||||||
public addCallback(type: Events, callback: () => void) {
|
|
||||||
const callbacks = this.callbacks[type]
|
|
||||||
|
|
||||||
if (!callbacks?.length) {
|
|
||||||
this.callbacks[type] = [callback]
|
|
||||||
} else {
|
|
||||||
this.callbacks[type].push(callback)
|
|
||||||
}
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
this.callbacks[type].filter(cb => cb === callback)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async fetchCommunity(topic: string): Promise<void> {
|
|
||||||
let payload = undefined
|
|
||||||
|
|
||||||
// todo: request Waku to replace callbacks
|
|
||||||
await this.waku.store
|
|
||||||
.queryHistory([topic], {
|
|
||||||
decryptionKeys: [this.symKey],
|
|
||||||
callback: messages => {
|
|
||||||
for (const message of messages.reverse()) {
|
|
||||||
if (!message.payload) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const decodedMetadata = ApplicationMetadataMessage.decode(
|
|
||||||
message.payload
|
|
||||||
)
|
|
||||||
if (!decodedMetadata.payload) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const decodedPayload = CommunityDescription.decode(
|
|
||||||
decodedMetadata.payload
|
|
||||||
)
|
|
||||||
// todo: explain
|
|
||||||
if (!decodedPayload.identity) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
payload = decodedPayload
|
|
||||||
|
|
||||||
// this.channels[chatId] = [
|
|
||||||
// ...maessages,
|
|
||||||
// ...this.channels[chatId]
|
|
||||||
// ]
|
|
||||||
|
|
||||||
// this.callbacks[''].forEach(cb => cb(this.channels[chatId]))
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
})
|
|
||||||
.catch(error => {
|
|
||||||
console.error(error)
|
|
||||||
})
|
|
||||||
|
|
||||||
if (payload) {
|
|
||||||
const result = {
|
|
||||||
name: undefined,
|
|
||||||
description: undefined,
|
|
||||||
color: undefined,
|
|
||||||
channels: undefined,
|
|
||||||
members: undefined,
|
|
||||||
}
|
|
||||||
|
|
||||||
// todo: set `this.communityData`
|
|
||||||
this.callbacks['communityfetch'].forEach(callback => callback(result))
|
|
||||||
}
|
|
||||||
|
|
||||||
// todo: handle no (valid) messages case
|
|
||||||
}
|
|
||||||
|
|
||||||
private observeCommunity(topic: string) {
|
|
||||||
let payload = undefined
|
|
||||||
|
|
||||||
this.waku.relay.addObserver(
|
|
||||||
message => {
|
|
||||||
if (!message.payload) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const decodedMetadata = ApplicationMetadataMessage.decode(
|
|
||||||
message.payload
|
|
||||||
)
|
|
||||||
if (!decodedMetadata.payload) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const decodedPayload = CommunityDescription.decode(
|
|
||||||
decodedMetadata.payload
|
|
||||||
)
|
|
||||||
// todo: explain
|
|
||||||
if (!decodedPayload.identity) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
payload = decodedPayload
|
|
||||||
},
|
|
||||||
[topic]
|
|
||||||
)
|
|
||||||
|
|
||||||
if (payload) {
|
|
||||||
const result = {
|
|
||||||
name: undefined,
|
|
||||||
description: undefined,
|
|
||||||
color: undefined,
|
|
||||||
channels: undefined,
|
|
||||||
members: undefined,
|
|
||||||
}
|
|
||||||
|
|
||||||
this.callbacks['communityupdate'].forEach(callback => callback(result))
|
|
||||||
}
|
|
||||||
|
|
||||||
// todo: handle no (valid) messages case
|
|
||||||
}
|
|
||||||
|
|
||||||
private observeChannelMessages(topics: string[]) {
|
|
||||||
let payload = undefined
|
|
||||||
|
|
||||||
this.waku.relay.addObserver(message => {
|
|
||||||
if (!message.payload || !message.timestamp) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const decodedMetadata = ApplicationMetadataMessage.decode(message.payload)
|
|
||||||
if (!decodedMetadata.payload) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (decodedMetadata.type) {
|
|
||||||
case ApplicationMetadataMessage_Type.TYPE_CHAT_MESSAGE: {
|
|
||||||
const message = ChatMessage.decode(decodedMetadata.payload)
|
|
||||||
|
|
||||||
this.channels[message.chatId].push(message)
|
|
||||||
this.callbacks.forEach(callback => callback(this.channels[chatId]))
|
|
||||||
|
|
||||||
// handle chat message
|
|
||||||
// push
|
|
||||||
|
|
||||||
payload = message
|
|
||||||
return
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}, topics)
|
|
||||||
|
|
||||||
if (payload) {
|
|
||||||
// todo: push to `this.channelEvents` by type
|
|
||||||
|
|
||||||
this.callbacks['channelmessagereceive'].forEach(callback =>
|
|
||||||
callback(result)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// todo: handle no (valid) messages case
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function createCommunityBeta(waku: Waku, publicKey: string) {
|
|
||||||
const community = new CommunityBeta(waku, publicKey)
|
|
||||||
|
|
||||||
return community
|
|
||||||
}
|
|
Loading…
Reference in New Issue