From 5d04f731a76bf773c31ab6803e864c1122fa3ebe Mon Sep 17 00:00:00 2001 From: Franck Royer Date: Wed, 6 Oct 2021 10:43:53 +1100 Subject: [PATCH 1/9] Move proto wrappers in wire folder --- packages/status-communities/src/chat.ts | 2 +- packages/status-communities/src/chat_message.spec.ts | 12 ++++++------ packages/status-communities/src/index.ts | 5 ++++- packages/status-communities/src/messenger.spec.ts | 4 ++-- packages/status-communities/src/messenger.ts | 4 ++-- .../src/{ => wire}/application_metadata_message.ts | 7 ++++--- .../src/{ => wire}/chat_message.ts | 6 +++--- 7 files changed, 22 insertions(+), 18 deletions(-) rename packages/status-communities/src/{ => wire}/application_metadata_message.ts (89%) rename packages/status-communities/src/{ => wire}/chat_message.ts (96%) diff --git a/packages/status-communities/src/chat.ts b/packages/status-communities/src/chat.ts index 86ea0413..184d50dd 100644 --- a/packages/status-communities/src/chat.ts +++ b/packages/status-communities/src/chat.ts @@ -1,6 +1,6 @@ -import { ChatMessage, Content } from "./chat_message"; import { chatIdToContentTopic } from "./contentTopic"; import { createSymKeyFromPassword } from "./encryption"; +import { ChatMessage, Content } from "./wire/chat_message"; /** * Represent a chat room. Only public chats are currently supported. diff --git a/packages/status-communities/src/chat_message.spec.ts b/packages/status-communities/src/chat_message.spec.ts index 18638d4a..ebcc71ee 100644 --- a/packages/status-communities/src/chat_message.spec.ts +++ b/packages/status-communities/src/chat_message.spec.ts @@ -1,17 +1,17 @@ import { expect } from "chai"; +import { + AudioMessage_AudioType, + ChatMessage_ContentType, +} from "./proto/communities/v1/chat_message"; +import { ImageType } from "./proto/communities/v1/enums"; import { AudioContent, ChatMessage, ContentType, ImageContent, StickerContent, -} from "./chat_message"; -import { - AudioMessage_AudioType, - ChatMessage_ContentType, -} from "./proto/communities/v1/chat_message"; -import { ImageType } from "./proto/communities/v1/enums"; +} from "./wire/chat_message"; describe("Chat Message", () => { it("Encode & decode Image message", () => { diff --git a/packages/status-communities/src/index.ts b/packages/status-communities/src/index.ts index 4350c11c..5256c077 100644 --- a/packages/status-communities/src/index.ts +++ b/packages/status-communities/src/index.ts @@ -1,3 +1,6 @@ import { Identity } from "./identity"; import { Messenger } from "./messenger"; -export { Messenger, Identity }; +import { ApplicationMetadataMessage } from "./wire/application_metadata_message"; +import { ChatMessage } from "./wire/chat_message"; + +export { Messenger, Identity, ApplicationMetadataMessage, ChatMessage }; diff --git a/packages/status-communities/src/messenger.spec.ts b/packages/status-communities/src/messenger.spec.ts index cdc53966..6d10a9eb 100644 --- a/packages/status-communities/src/messenger.spec.ts +++ b/packages/status-communities/src/messenger.spec.ts @@ -2,10 +2,10 @@ import { expect } from "chai"; import debug from "debug"; import { utils } from "js-waku"; -import { ApplicationMetadataMessage } from "./application_metadata_message"; -import { ContentType } from "./chat_message"; import { Identity } from "./identity"; import { Messenger } from "./messenger"; +import { ApplicationMetadataMessage } from "./wire/application_metadata_message"; +import { ContentType } from "./wire/chat_message"; const testChatId = "test-chat-id"; diff --git a/packages/status-communities/src/messenger.ts b/packages/status-communities/src/messenger.ts index 305b77de..cab1dd78 100644 --- a/packages/status-communities/src/messenger.ts +++ b/packages/status-communities/src/messenger.ts @@ -2,11 +2,11 @@ import debug from "debug"; import { Waku, WakuMessage } from "js-waku"; import { CreateOptions as WakuCreateOptions } from "js-waku/build/main/lib/waku"; -import { ApplicationMetadataMessage } from "./application_metadata_message"; import { Chat } from "./chat"; -import { ChatMessage, Content } from "./chat_message"; import { Identity } from "./identity"; import { ApplicationMetadataMessage_Type } from "./proto/status/v1/application_metadata_message"; +import { ApplicationMetadataMessage } from "./wire/application_metadata_message"; +import { ChatMessage, Content } from "./wire/chat_message"; const dbg = debug("communities:messenger"); diff --git a/packages/status-communities/src/application_metadata_message.ts b/packages/status-communities/src/wire/application_metadata_message.ts similarity index 89% rename from packages/status-communities/src/application_metadata_message.ts rename to packages/status-communities/src/wire/application_metadata_message.ts index bfd5c954..b9ce2d0b 100644 --- a/packages/status-communities/src/application_metadata_message.ts +++ b/packages/status-communities/src/wire/application_metadata_message.ts @@ -3,10 +3,11 @@ import { utils } from "js-waku"; import { Reader } from "protobufjs"; import secp256k1 from "secp256k1"; +import { Identity } from "../identity"; +import * as proto from "../proto/status/v1/application_metadata_message"; +import { ApplicationMetadataMessage_Type } from "../proto/status/v1/application_metadata_message"; + import { ChatMessage } from "./chat_message"; -import { Identity } from "./identity"; -import * as proto from "./proto/status/v1/application_metadata_message"; -import { ApplicationMetadataMessage_Type } from "./proto/status/v1/application_metadata_message"; export class ApplicationMetadataMessage { private constructor(public proto: proto.ApplicationMetadataMessage) {} diff --git a/packages/status-communities/src/chat_message.ts b/packages/status-communities/src/wire/chat_message.ts similarity index 96% rename from packages/status-communities/src/chat_message.ts rename to packages/status-communities/src/wire/chat_message.ts index 65148a34..01c3beaf 100644 --- a/packages/status-communities/src/chat_message.ts +++ b/packages/status-communities/src/wire/chat_message.ts @@ -1,14 +1,14 @@ import { Reader } from "protobufjs"; -import * as proto from "./proto/communities/v1/chat_message"; +import * as proto from "../proto/communities/v1/chat_message"; import { AudioMessage, AudioMessage_AudioType, ChatMessage_ContentType, ImageMessage, StickerMessage, -} from "./proto/communities/v1/chat_message"; -import { ImageType, MessageType } from "./proto/communities/v1/enums"; +} from "../proto/communities/v1/chat_message"; +import { ImageType, MessageType } from "../proto/communities/v1/enums"; export type Content = | TextContent From 9f6abaf952723e07d5691a847ebca061e5b704c5 Mon Sep 17 00:00:00 2001 From: Franck Royer Date: Wed, 6 Oct 2021 12:51:36 +1100 Subject: [PATCH 2/9] Create util mod with `bufToHex` that includes 0x prefix As this is how the string is represented for Community id. --- packages/status-communities/src/identity.ts | 5 +++-- packages/status-communities/src/messenger.spec.ts | 6 +++--- packages/status-communities/src/utils.ts | 11 +++++++++++ .../src/wire/application_metadata_message.ts | 4 ++-- 4 files changed, 19 insertions(+), 7 deletions(-) create mode 100644 packages/status-communities/src/utils.ts diff --git a/packages/status-communities/src/identity.ts b/packages/status-communities/src/identity.ts index 88ec6e80..368d505b 100644 --- a/packages/status-communities/src/identity.ts +++ b/packages/status-communities/src/identity.ts @@ -2,9 +2,10 @@ import { Buffer } from "buffer"; import { keccak256 } from "js-sha3"; import { generatePrivateKey } from "js-waku"; -import { utils } from "js-waku"; import * as secp256k1 from "secp256k1"; +import { hexToBuf } from "./utils"; + export class Identity { public constructor(public privateKey: Uint8Array) {} @@ -20,7 +21,7 @@ export class Identity { const hash = keccak256(payload); const { signature, recid } = secp256k1.ecdsaSign( - utils.hexToBuf(hash), + hexToBuf(hash), this.privateKey ); diff --git a/packages/status-communities/src/messenger.spec.ts b/packages/status-communities/src/messenger.spec.ts index 6d10a9eb..1cf734d2 100644 --- a/packages/status-communities/src/messenger.spec.ts +++ b/packages/status-communities/src/messenger.spec.ts @@ -1,9 +1,9 @@ import { expect } from "chai"; import debug from "debug"; -import { utils } from "js-waku"; import { Identity } from "./identity"; import { Messenger } from "./messenger"; +import { bufToHex } from "./utils"; import { ApplicationMetadataMessage } from "./wire/application_metadata_message"; import { ContentType } from "./wire/chat_message"; @@ -106,8 +106,8 @@ describe("Messenger", () => { const receivedMessage = await receivedMessagePromise; - expect(utils.bufToHex(receivedMessage.signer!)).to.eq( - utils.bufToHex(identityAlice.publicKey) + expect(bufToHex(receivedMessage.signer!)).to.eq( + bufToHex(identityAlice.publicKey) ); }); diff --git a/packages/status-communities/src/utils.ts b/packages/status-communities/src/utils.ts new file mode 100644 index 00000000..e8a4656d --- /dev/null +++ b/packages/status-communities/src/utils.ts @@ -0,0 +1,11 @@ +import { utils } from "js-waku"; + +const hexToBuf = utils.hexToBuf; +export { hexToBuf }; + +/** + * 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.bufToHex(buf); +} diff --git a/packages/status-communities/src/wire/application_metadata_message.ts b/packages/status-communities/src/wire/application_metadata_message.ts index b9ce2d0b..f7458b57 100644 --- a/packages/status-communities/src/wire/application_metadata_message.ts +++ b/packages/status-communities/src/wire/application_metadata_message.ts @@ -1,11 +1,11 @@ import { keccak256 } from "js-sha3"; -import { utils } from "js-waku"; import { Reader } from "protobufjs"; import secp256k1 from "secp256k1"; import { Identity } from "../identity"; import * as proto from "../proto/status/v1/application_metadata_message"; import { ApplicationMetadataMessage_Type } from "../proto/status/v1/application_metadata_message"; +import { hexToBuf } from "../utils"; import { ChatMessage } from "./chat_message"; @@ -70,6 +70,6 @@ export class ApplicationMetadataMessage { const recid = this.signature.slice(64)[0]; const hash = keccak256(this.payload); - return secp256k1.ecdsaRecover(signature, recid, utils.hexToBuf(hash)); + return secp256k1.ecdsaRecover(signature, recid, hexToBuf(hash)); } } From 43ecd31e768753c2854b63a68ab7200ce9577ba5 Mon Sep 17 00:00:00 2001 From: Franck Royer Date: Wed, 6 Oct 2021 12:52:41 +1100 Subject: [PATCH 3/9] Generation of content topic is the same for chats and communities --- packages/status-communities/src/chat.ts | 4 ++-- packages/status-communities/src/contentTopic.ts | 9 +++++++-- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/packages/status-communities/src/chat.ts b/packages/status-communities/src/chat.ts index 184d50dd..95457c87 100644 --- a/packages/status-communities/src/chat.ts +++ b/packages/status-communities/src/chat.ts @@ -1,4 +1,4 @@ -import { chatIdToContentTopic } from "./contentTopic"; +import { idToContentTopic } from "./contentTopic"; import { createSymKeyFromPassword } from "./encryption"; import { ChatMessage, Content } from "./wire/chat_message"; @@ -21,7 +21,7 @@ export class Chat { } public get contentTopic(): string { - return chatIdToContentTopic(this.id); + return idToContentTopic(this.id); } public createMessage(content: Content): ChatMessage { diff --git a/packages/status-communities/src/contentTopic.ts b/packages/status-communities/src/contentTopic.ts index 07bba7b2..0c78777f 100644 --- a/packages/status-communities/src/contentTopic.ts +++ b/packages/status-communities/src/contentTopic.ts @@ -4,8 +4,13 @@ import { keccak256 } from "js-sha3"; const TopicLength = 4; -export function chatIdToContentTopic(chatId: string): string { - const hash = keccak256.arrayBuffer(chatId); +/** + * 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); From 2ee6b747873327955ad9d7a65c27305fbbdda36f Mon Sep 17 00:00:00 2001 From: Franck Royer Date: Wed, 6 Oct 2021 12:55:19 +1100 Subject: [PATCH 4/9] Instantiate Community and retrieve details --- packages/status-communities/buf.yaml | 4 + .../proto/communities/v1/chat_identity.proto | 53 + .../proto/communities/v1/communities.proto | 80 + packages/status-communities/src/community.ts | 62 +- .../src/proto/communities/v1/chat_identity.ts | 513 +++++ .../src/proto/communities/v1/communities.ts | 1768 +++++++++++++++++ .../src/wire/chat_identity.ts | 17 + .../src/wire/community_chat.ts | 17 + .../src/wire/community_description.ts | 93 + 9 files changed, 2606 insertions(+), 1 deletion(-) create mode 100644 packages/status-communities/proto/communities/v1/chat_identity.proto create mode 100644 packages/status-communities/proto/communities/v1/communities.proto create mode 100644 packages/status-communities/src/proto/communities/v1/chat_identity.ts create mode 100644 packages/status-communities/src/proto/communities/v1/communities.ts create mode 100644 packages/status-communities/src/wire/chat_identity.ts create mode 100644 packages/status-communities/src/wire/community_chat.ts create mode 100644 packages/status-communities/src/wire/community_description.ts diff --git a/packages/status-communities/buf.yaml b/packages/status-communities/buf.yaml index c7b2d892..c6a09b81 100644 --- a/packages/status-communities/buf.yaml +++ b/packages/status-communities/buf.yaml @@ -3,3 +3,7 @@ version: v1beta1 build: roots: - ./proto +lint: + except: + - ENUM_ZERO_VALUE_SUFFIX + - ENUM_VALUE_PREFIX diff --git a/packages/status-communities/proto/communities/v1/chat_identity.proto b/packages/status-communities/proto/communities/v1/chat_identity.proto new file mode 100644 index 00000000..c455f278 --- /dev/null +++ b/packages/status-communities/proto/communities/v1/chat_identity.proto @@ -0,0 +1,53 @@ +syntax = "proto3"; + +package communities.v1; + +import "communities/v1/enums.proto"; + +// ChatIdentity represents the user defined identity associated with their public chat key +message ChatIdentity { + // Lamport timestamp of the message + uint64 clock = 1; + + // ens_name is the valid ENS name associated with the chat key + string ens_name = 2; + + // images is a string indexed mapping of images associated with an identity + map images = 3; + + // display name is the user set identity, valid only for organisations + string display_name = 4; + + // description is the user set description, valid only for organisations + string description = 5; + + string color = 6; +} + +// ProfileImage represents data associated with a user's profile image +message IdentityImage { + + // payload is a context based payload for the profile image data, + // context is determined by the `source_type` + bytes payload = 1; + + // source_type signals the image payload source + SourceType source_type = 2; + + // image_type signals the image type and method of parsing the payload + ImageType image_type =3; + + // SourceType are the predefined types of image source allowed + enum SourceType { + UNKNOWN_SOURCE_TYPE = 0; + + // RAW_PAYLOAD image byte data + RAW_PAYLOAD = 1; + + // ENS_AVATAR uses the ENS record's resolver get-text-data.avatar data + // The `payload` field will be ignored if ENS_AVATAR is selected + // The application will read and parse the ENS avatar data as image payload data, URLs will be ignored + // The parent `ChatMessageIdentity` must have a valid `ens_name` set + ENS_AVATAR = 2; + } +} diff --git a/packages/status-communities/proto/communities/v1/communities.proto b/packages/status-communities/proto/communities/v1/communities.proto new file mode 100644 index 00000000..48badc95 --- /dev/null +++ b/packages/status-communities/proto/communities/v1/communities.proto @@ -0,0 +1,80 @@ +syntax = "proto3"; + +package communities.v1; + +import "communities/v1/chat_identity.proto"; + +message Grant { + bytes community_id = 1; + bytes member_id = 2; + string chat_id = 3; + uint64 clock = 4; +} + +message CommunityMember { + enum Roles { + UNKNOWN_ROLE = 0; + ROLE_ALL = 1; + ROLE_MANAGE_USERS = 2; + } + repeated Roles roles = 1; +} + +message CommunityPermissions { + enum Access { + UNKNOWN_ACCESS = 0; + NO_MEMBERSHIP = 1; + INVITATION_ONLY = 2; + ON_REQUEST = 3; + } + + bool ens_only = 1; + // https://gitlab.matrix.org/matrix-org/olm/blob/master/docs/megolm.md is a candidate for the algorithm to be used in case we want to have private communityal chats, lighter than pairwise encryption using the DR, less secure, but more efficient for large number of participants + bool private = 2; + Access access = 3; +} + +message CommunityDescription { + uint64 clock = 1; + map members = 2; + CommunityPermissions permissions = 3; + ChatIdentity identity = 5; + map chats = 6; + repeated string ban_list = 7; + map categories = 8; +} + +message CommunityChat { + map members = 1; + CommunityPermissions permissions = 2; + ChatIdentity identity = 3; + string category_id = 4; + int32 position = 5; +} + +message CommunityCategory { + string category_id = 1; + string name = 2; + int32 position = 3; +} + +message CommunityInvitation { + bytes community_description = 1; + bytes grant = 2; + string chat_id = 3; + bytes public_key = 4; +} + +message CommunityRequestToJoin { + uint64 clock = 1; + string ens_name = 2; + string chat_id = 3; + bytes community_id = 4; +} + +message CommunityRequestToJoinResponse { + uint64 clock = 1; + CommunityDescription community = 2; + bool accepted = 3; + bytes grant = 4; +} diff --git a/packages/status-communities/src/community.ts b/packages/status-communities/src/community.ts index f15b0a79..7801d8d3 100644 --- a/packages/status-communities/src/community.ts +++ b/packages/status-communities/src/community.ts @@ -1 +1,61 @@ -export class Community {} +import debug from "debug"; +import { Waku } from "js-waku"; + +import { bufToHex, hexToBuf } from "./utils"; +import { CommunityDescription } from "./wire/community_description"; + +const dbg = debug("communities:community"); + +export class Community { + public publicKey: Uint8Array; + private waku: Waku; + + public description?: CommunityDescription; + + constructor(publicKey: Uint8Array, waku: Waku) { + this.publicKey = publicKey; + this.waku = waku; + } + + /** + * 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 async instantiateCommunity( + publicKey: string, + waku: Waku + ): Promise { + 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 { + 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; + } +} diff --git a/packages/status-communities/src/proto/communities/v1/chat_identity.ts b/packages/status-communities/src/proto/communities/v1/chat_identity.ts new file mode 100644 index 00000000..7b311934 --- /dev/null +++ b/packages/status-communities/src/proto/communities/v1/chat_identity.ts @@ -0,0 +1,513 @@ +/* eslint-disable */ +import Long from "long"; +import _m0 from "protobufjs/minimal"; +import { + ImageType, + imageTypeFromJSON, + imageTypeToJSON, +} from "../../communities/v1/enums"; + +export const protobufPackage = "communities.v1"; + +/** ChatIdentity represents the user defined identity associated with their public chat key */ +export interface ChatIdentity { + /** Lamport timestamp of the message */ + clock: number; + /** ens_name is the valid ENS name associated with the chat key */ + ensName: string; + /** images is a string indexed mapping of images associated with an identity */ + images: { [key: string]: IdentityImage }; + /** display name is the user set identity, valid only for organisations */ + displayName: string; + /** description is the user set description, valid only for organisations */ + description: string; + color: string; +} + +export interface ChatIdentity_ImagesEntry { + key: string; + value: IdentityImage | undefined; +} + +/** ProfileImage represents data associated with a user's profile image */ +export interface IdentityImage { + /** + * payload is a context based payload for the profile image data, + * context is determined by the `source_type` + */ + payload: Uint8Array; + /** source_type signals the image payload source */ + sourceType: IdentityImage_SourceType; + /** image_type signals the image type and method of parsing the payload */ + imageType: ImageType; +} + +/** SourceType are the predefined types of image source allowed */ +export enum IdentityImage_SourceType { + UNKNOWN_SOURCE_TYPE = 0, + /** RAW_PAYLOAD - RAW_PAYLOAD image byte data */ + RAW_PAYLOAD = 1, + /** + * ENS_AVATAR - ENS_AVATAR uses the ENS record's resolver get-text-data.avatar data + * The `payload` field will be ignored if ENS_AVATAR is selected + * The application will read and parse the ENS avatar data as image payload data, URLs will be ignored + * The parent `ChatMessageIdentity` must have a valid `ens_name` set + */ + ENS_AVATAR = 2, + UNRECOGNIZED = -1, +} + +export function identityImage_SourceTypeFromJSON( + object: any +): IdentityImage_SourceType { + switch (object) { + case 0: + case "UNKNOWN_SOURCE_TYPE": + return IdentityImage_SourceType.UNKNOWN_SOURCE_TYPE; + case 1: + case "RAW_PAYLOAD": + return IdentityImage_SourceType.RAW_PAYLOAD; + case 2: + case "ENS_AVATAR": + return IdentityImage_SourceType.ENS_AVATAR; + case -1: + case "UNRECOGNIZED": + default: + return IdentityImage_SourceType.UNRECOGNIZED; + } +} + +export function identityImage_SourceTypeToJSON( + object: IdentityImage_SourceType +): string { + switch (object) { + case IdentityImage_SourceType.UNKNOWN_SOURCE_TYPE: + return "UNKNOWN_SOURCE_TYPE"; + case IdentityImage_SourceType.RAW_PAYLOAD: + return "RAW_PAYLOAD"; + case IdentityImage_SourceType.ENS_AVATAR: + return "ENS_AVATAR"; + default: + return "UNKNOWN"; + } +} + +const baseChatIdentity: object = { + clock: 0, + ensName: "", + displayName: "", + description: "", + color: "", +}; + +export const ChatIdentity = { + encode( + message: ChatIdentity, + writer: _m0.Writer = _m0.Writer.create() + ): _m0.Writer { + if (message.clock !== 0) { + writer.uint32(8).uint64(message.clock); + } + if (message.ensName !== "") { + writer.uint32(18).string(message.ensName); + } + Object.entries(message.images).forEach(([key, value]) => { + ChatIdentity_ImagesEntry.encode( + { key: key as any, value }, + writer.uint32(26).fork() + ).ldelim(); + }); + if (message.displayName !== "") { + writer.uint32(34).string(message.displayName); + } + if (message.description !== "") { + writer.uint32(42).string(message.description); + } + if (message.color !== "") { + writer.uint32(50).string(message.color); + } + return writer; + }, + + decode(input: _m0.Reader | Uint8Array, length?: number): ChatIdentity { + const reader = input instanceof _m0.Reader ? input : new _m0.Reader(input); + let end = length === undefined ? reader.len : reader.pos + length; + const message = { ...baseChatIdentity } as ChatIdentity; + message.images = {}; + while (reader.pos < end) { + const tag = reader.uint32(); + switch (tag >>> 3) { + case 1: + message.clock = longToNumber(reader.uint64() as Long); + break; + case 2: + message.ensName = reader.string(); + break; + case 3: + const entry3 = ChatIdentity_ImagesEntry.decode( + reader, + reader.uint32() + ); + if (entry3.value !== undefined) { + message.images[entry3.key] = entry3.value; + } + break; + case 4: + message.displayName = reader.string(); + break; + case 5: + message.description = reader.string(); + break; + case 6: + message.color = reader.string(); + break; + default: + reader.skipType(tag & 7); + break; + } + } + return message; + }, + + fromJSON(object: any): ChatIdentity { + const message = { ...baseChatIdentity } as ChatIdentity; + message.images = {}; + if (object.clock !== undefined && object.clock !== null) { + message.clock = Number(object.clock); + } else { + message.clock = 0; + } + if (object.ensName !== undefined && object.ensName !== null) { + message.ensName = String(object.ensName); + } else { + message.ensName = ""; + } + if (object.images !== undefined && object.images !== null) { + Object.entries(object.images).forEach(([key, value]) => { + message.images[key] = IdentityImage.fromJSON(value); + }); + } + if (object.displayName !== undefined && object.displayName !== null) { + message.displayName = String(object.displayName); + } else { + message.displayName = ""; + } + if (object.description !== undefined && object.description !== null) { + message.description = String(object.description); + } else { + message.description = ""; + } + if (object.color !== undefined && object.color !== null) { + message.color = String(object.color); + } else { + message.color = ""; + } + return message; + }, + + toJSON(message: ChatIdentity): unknown { + const obj: any = {}; + message.clock !== undefined && (obj.clock = message.clock); + message.ensName !== undefined && (obj.ensName = message.ensName); + obj.images = {}; + if (message.images) { + Object.entries(message.images).forEach(([k, v]) => { + obj.images[k] = IdentityImage.toJSON(v); + }); + } + message.displayName !== undefined && + (obj.displayName = message.displayName); + message.description !== undefined && + (obj.description = message.description); + message.color !== undefined && (obj.color = message.color); + return obj; + }, + + fromPartial(object: DeepPartial): ChatIdentity { + const message = { ...baseChatIdentity } as ChatIdentity; + message.images = {}; + if (object.clock !== undefined && object.clock !== null) { + message.clock = object.clock; + } else { + message.clock = 0; + } + if (object.ensName !== undefined && object.ensName !== null) { + message.ensName = object.ensName; + } else { + message.ensName = ""; + } + if (object.images !== undefined && object.images !== null) { + Object.entries(object.images).forEach(([key, value]) => { + if (value !== undefined) { + message.images[key] = IdentityImage.fromPartial(value); + } + }); + } + if (object.displayName !== undefined && object.displayName !== null) { + message.displayName = object.displayName; + } else { + message.displayName = ""; + } + if (object.description !== undefined && object.description !== null) { + message.description = object.description; + } else { + message.description = ""; + } + if (object.color !== undefined && object.color !== null) { + message.color = object.color; + } else { + message.color = ""; + } + return message; + }, +}; + +const baseChatIdentity_ImagesEntry: object = { key: "" }; + +export const ChatIdentity_ImagesEntry = { + encode( + message: ChatIdentity_ImagesEntry, + writer: _m0.Writer = _m0.Writer.create() + ): _m0.Writer { + if (message.key !== "") { + writer.uint32(10).string(message.key); + } + if (message.value !== undefined) { + IdentityImage.encode(message.value, writer.uint32(18).fork()).ldelim(); + } + return writer; + }, + + decode( + input: _m0.Reader | Uint8Array, + length?: number + ): ChatIdentity_ImagesEntry { + const reader = input instanceof _m0.Reader ? input : new _m0.Reader(input); + let end = length === undefined ? reader.len : reader.pos + length; + const message = { + ...baseChatIdentity_ImagesEntry, + } as ChatIdentity_ImagesEntry; + while (reader.pos < end) { + const tag = reader.uint32(); + switch (tag >>> 3) { + case 1: + message.key = reader.string(); + break; + case 2: + message.value = IdentityImage.decode(reader, reader.uint32()); + break; + default: + reader.skipType(tag & 7); + break; + } + } + return message; + }, + + fromJSON(object: any): ChatIdentity_ImagesEntry { + const message = { + ...baseChatIdentity_ImagesEntry, + } as ChatIdentity_ImagesEntry; + if (object.key !== undefined && object.key !== null) { + message.key = String(object.key); + } else { + message.key = ""; + } + if (object.value !== undefined && object.value !== null) { + message.value = IdentityImage.fromJSON(object.value); + } else { + message.value = undefined; + } + return message; + }, + + toJSON(message: ChatIdentity_ImagesEntry): unknown { + const obj: any = {}; + message.key !== undefined && (obj.key = message.key); + message.value !== undefined && + (obj.value = message.value + ? IdentityImage.toJSON(message.value) + : undefined); + return obj; + }, + + fromPartial( + object: DeepPartial + ): ChatIdentity_ImagesEntry { + const message = { + ...baseChatIdentity_ImagesEntry, + } as ChatIdentity_ImagesEntry; + if (object.key !== undefined && object.key !== null) { + message.key = object.key; + } else { + message.key = ""; + } + if (object.value !== undefined && object.value !== null) { + message.value = IdentityImage.fromPartial(object.value); + } else { + message.value = undefined; + } + return message; + }, +}; + +const baseIdentityImage: object = { sourceType: 0, imageType: 0 }; + +export const IdentityImage = { + encode( + message: IdentityImage, + writer: _m0.Writer = _m0.Writer.create() + ): _m0.Writer { + if (message.payload.length !== 0) { + writer.uint32(10).bytes(message.payload); + } + if (message.sourceType !== 0) { + writer.uint32(16).int32(message.sourceType); + } + if (message.imageType !== 0) { + writer.uint32(24).int32(message.imageType); + } + return writer; + }, + + decode(input: _m0.Reader | Uint8Array, length?: number): IdentityImage { + const reader = input instanceof _m0.Reader ? input : new _m0.Reader(input); + let end = length === undefined ? reader.len : reader.pos + length; + const message = { ...baseIdentityImage } as IdentityImage; + message.payload = new Uint8Array(); + while (reader.pos < end) { + const tag = reader.uint32(); + switch (tag >>> 3) { + case 1: + message.payload = reader.bytes(); + break; + case 2: + message.sourceType = reader.int32() as any; + break; + case 3: + message.imageType = reader.int32() as any; + break; + default: + reader.skipType(tag & 7); + break; + } + } + return message; + }, + + fromJSON(object: any): IdentityImage { + const message = { ...baseIdentityImage } as IdentityImage; + message.payload = new Uint8Array(); + if (object.payload !== undefined && object.payload !== null) { + message.payload = bytesFromBase64(object.payload); + } + if (object.sourceType !== undefined && object.sourceType !== null) { + message.sourceType = identityImage_SourceTypeFromJSON(object.sourceType); + } else { + message.sourceType = 0; + } + if (object.imageType !== undefined && object.imageType !== null) { + message.imageType = imageTypeFromJSON(object.imageType); + } else { + message.imageType = 0; + } + return message; + }, + + toJSON(message: IdentityImage): unknown { + const obj: any = {}; + message.payload !== undefined && + (obj.payload = base64FromBytes( + message.payload !== undefined ? message.payload : new Uint8Array() + )); + message.sourceType !== undefined && + (obj.sourceType = identityImage_SourceTypeToJSON(message.sourceType)); + message.imageType !== undefined && + (obj.imageType = imageTypeToJSON(message.imageType)); + return obj; + }, + + fromPartial(object: DeepPartial): IdentityImage { + const message = { ...baseIdentityImage } as IdentityImage; + if (object.payload !== undefined && object.payload !== null) { + message.payload = object.payload; + } else { + message.payload = new Uint8Array(); + } + if (object.sourceType !== undefined && object.sourceType !== null) { + message.sourceType = object.sourceType; + } else { + message.sourceType = 0; + } + if (object.imageType !== undefined && object.imageType !== null) { + message.imageType = object.imageType; + } else { + message.imageType = 0; + } + return message; + }, +}; + +declare var self: any | undefined; +declare var window: any | undefined; +declare var global: any | undefined; +var globalThis: any = (() => { + if (typeof globalThis !== "undefined") return globalThis; + if (typeof self !== "undefined") return self; + if (typeof window !== "undefined") return window; + if (typeof global !== "undefined") return global; + throw "Unable to locate global object"; +})(); + +const atob: (b64: string) => string = + globalThis.atob || + ((b64) => globalThis.Buffer.from(b64, "base64").toString("binary")); +function bytesFromBase64(b64: string): Uint8Array { + const bin = atob(b64); + const arr = new Uint8Array(bin.length); + for (let i = 0; i < bin.length; ++i) { + arr[i] = bin.charCodeAt(i); + } + return arr; +} + +const btoa: (bin: string) => string = + globalThis.btoa || + ((bin) => globalThis.Buffer.from(bin, "binary").toString("base64")); +function base64FromBytes(arr: Uint8Array): string { + const bin: string[] = []; + for (const byte of arr) { + bin.push(String.fromCharCode(byte)); + } + return btoa(bin.join("")); +} + +type Builtin = + | Date + | Function + | Uint8Array + | string + | number + | boolean + | undefined; +export type DeepPartial = T 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-communities/src/proto/communities/v1/communities.ts b/packages/status-communities/src/proto/communities/v1/communities.ts new file mode 100644 index 00000000..aabe78ec --- /dev/null +++ b/packages/status-communities/src/proto/communities/v1/communities.ts @@ -0,0 +1,1768 @@ +/* eslint-disable */ +import Long from "long"; +import _m0 from "protobufjs/minimal"; +import { ChatIdentity } from "../../communities/v1/chat_identity"; + +export const protobufPackage = "communities.v1"; + +export interface Grant { + communityId: Uint8Array; + memberId: Uint8Array; + chatId: string; + clock: number; +} + +export interface CommunityMember { + roles: CommunityMember_Roles[]; +} + +export enum CommunityMember_Roles { + UNKNOWN_ROLE = 0, + ROLE_ALL = 1, + ROLE_MANAGE_USERS = 2, + UNRECOGNIZED = -1, +} + +export function communityMember_RolesFromJSON( + object: any +): CommunityMember_Roles { + switch (object) { + case 0: + case "UNKNOWN_ROLE": + return CommunityMember_Roles.UNKNOWN_ROLE; + case 1: + case "ROLE_ALL": + return CommunityMember_Roles.ROLE_ALL; + case 2: + case "ROLE_MANAGE_USERS": + return CommunityMember_Roles.ROLE_MANAGE_USERS; + case -1: + case "UNRECOGNIZED": + default: + return CommunityMember_Roles.UNRECOGNIZED; + } +} + +export function communityMember_RolesToJSON( + object: CommunityMember_Roles +): string { + switch (object) { + case CommunityMember_Roles.UNKNOWN_ROLE: + return "UNKNOWN_ROLE"; + case CommunityMember_Roles.ROLE_ALL: + return "ROLE_ALL"; + case CommunityMember_Roles.ROLE_MANAGE_USERS: + return "ROLE_MANAGE_USERS"; + default: + return "UNKNOWN"; + } +} + +export interface CommunityPermissions { + ensOnly: boolean; + /** https://gitlab.matrix.org/matrix-org/olm/blob/master/docs/megolm.md is a candidate for the algorithm to be used in case we want to have private communityal chats, lighter than pairwise encryption using the DR, less secure, but more efficient for large number of participants */ + private: boolean; + access: CommunityPermissions_Access; +} + +export enum CommunityPermissions_Access { + UNKNOWN_ACCESS = 0, + NO_MEMBERSHIP = 1, + INVITATION_ONLY = 2, + ON_REQUEST = 3, + UNRECOGNIZED = -1, +} + +export function communityPermissions_AccessFromJSON( + object: any +): CommunityPermissions_Access { + switch (object) { + case 0: + case "UNKNOWN_ACCESS": + return CommunityPermissions_Access.UNKNOWN_ACCESS; + case 1: + case "NO_MEMBERSHIP": + return CommunityPermissions_Access.NO_MEMBERSHIP; + case 2: + case "INVITATION_ONLY": + return CommunityPermissions_Access.INVITATION_ONLY; + case 3: + case "ON_REQUEST": + return CommunityPermissions_Access.ON_REQUEST; + case -1: + case "UNRECOGNIZED": + default: + return CommunityPermissions_Access.UNRECOGNIZED; + } +} + +export function communityPermissions_AccessToJSON( + object: CommunityPermissions_Access +): string { + switch (object) { + case CommunityPermissions_Access.UNKNOWN_ACCESS: + return "UNKNOWN_ACCESS"; + case CommunityPermissions_Access.NO_MEMBERSHIP: + return "NO_MEMBERSHIP"; + case CommunityPermissions_Access.INVITATION_ONLY: + return "INVITATION_ONLY"; + case CommunityPermissions_Access.ON_REQUEST: + return "ON_REQUEST"; + default: + return "UNKNOWN"; + } +} + +export interface CommunityDescription { + clock: number; + members: { [key: string]: CommunityMember }; + permissions: CommunityPermissions | undefined; + identity: ChatIdentity | undefined; + chats: { [key: string]: CommunityChat }; + banList: string[]; + categories: { [key: string]: CommunityCategory }; +} + +export interface CommunityDescription_MembersEntry { + key: string; + value: CommunityMember | undefined; +} + +export interface CommunityDescription_ChatsEntry { + key: string; + value: CommunityChat | undefined; +} + +export interface CommunityDescription_CategoriesEntry { + key: string; + value: CommunityCategory | undefined; +} + +export interface CommunityChat { + members: { [key: string]: CommunityMember }; + permissions: CommunityPermissions | undefined; + identity: ChatIdentity | undefined; + categoryId: string; + position: number; +} + +export interface CommunityChat_MembersEntry { + key: string; + value: CommunityMember | undefined; +} + +export interface CommunityCategory { + categoryId: string; + name: string; + position: number; +} + +export interface CommunityInvitation { + communityDescription: Uint8Array; + grant: Uint8Array; + chatId: string; + publicKey: Uint8Array; +} + +export interface CommunityRequestToJoin { + clock: number; + ensName: string; + chatId: string; + communityId: Uint8Array; +} + +export interface CommunityRequestToJoinResponse { + clock: number; + community: CommunityDescription | undefined; + accepted: boolean; + grant: Uint8Array; +} + +const baseGrant: object = { chatId: "", clock: 0 }; + +export const Grant = { + encode(message: Grant, writer: _m0.Writer = _m0.Writer.create()): _m0.Writer { + if (message.communityId.length !== 0) { + writer.uint32(10).bytes(message.communityId); + } + if (message.memberId.length !== 0) { + writer.uint32(18).bytes(message.memberId); + } + if (message.chatId !== "") { + writer.uint32(26).string(message.chatId); + } + if (message.clock !== 0) { + writer.uint32(32).uint64(message.clock); + } + return writer; + }, + + decode(input: _m0.Reader | Uint8Array, length?: number): Grant { + const reader = input instanceof _m0.Reader ? input : new _m0.Reader(input); + let end = length === undefined ? reader.len : reader.pos + length; + const message = { ...baseGrant } as Grant; + message.communityId = new Uint8Array(); + message.memberId = new Uint8Array(); + while (reader.pos < end) { + const tag = reader.uint32(); + switch (tag >>> 3) { + case 1: + message.communityId = reader.bytes(); + break; + case 2: + message.memberId = reader.bytes(); + break; + case 3: + message.chatId = reader.string(); + break; + case 4: + message.clock = longToNumber(reader.uint64() as Long); + break; + default: + reader.skipType(tag & 7); + break; + } + } + return message; + }, + + fromJSON(object: any): Grant { + const message = { ...baseGrant } as Grant; + message.communityId = new Uint8Array(); + message.memberId = new Uint8Array(); + if (object.communityId !== undefined && object.communityId !== null) { + message.communityId = bytesFromBase64(object.communityId); + } + if (object.memberId !== undefined && object.memberId !== null) { + message.memberId = bytesFromBase64(object.memberId); + } + if (object.chatId !== undefined && object.chatId !== null) { + message.chatId = String(object.chatId); + } else { + message.chatId = ""; + } + if (object.clock !== undefined && object.clock !== null) { + message.clock = Number(object.clock); + } else { + message.clock = 0; + } + return message; + }, + + toJSON(message: Grant): unknown { + const obj: any = {}; + message.communityId !== undefined && + (obj.communityId = base64FromBytes( + message.communityId !== undefined + ? message.communityId + : new Uint8Array() + )); + message.memberId !== undefined && + (obj.memberId = base64FromBytes( + message.memberId !== undefined ? message.memberId : new Uint8Array() + )); + message.chatId !== undefined && (obj.chatId = message.chatId); + message.clock !== undefined && (obj.clock = message.clock); + return obj; + }, + + fromPartial(object: DeepPartial): Grant { + const message = { ...baseGrant } as Grant; + if (object.communityId !== undefined && object.communityId !== null) { + message.communityId = object.communityId; + } else { + message.communityId = new Uint8Array(); + } + if (object.memberId !== undefined && object.memberId !== null) { + message.memberId = object.memberId; + } else { + message.memberId = new Uint8Array(); + } + if (object.chatId !== undefined && object.chatId !== null) { + message.chatId = object.chatId; + } else { + message.chatId = ""; + } + if (object.clock !== undefined && object.clock !== null) { + message.clock = object.clock; + } else { + message.clock = 0; + } + return message; + }, +}; + +const baseCommunityMember: object = { roles: 0 }; + +export const CommunityMember = { + encode( + message: CommunityMember, + writer: _m0.Writer = _m0.Writer.create() + ): _m0.Writer { + writer.uint32(10).fork(); + for (const v of message.roles) { + writer.int32(v); + } + writer.ldelim(); + return writer; + }, + + decode(input: _m0.Reader | Uint8Array, length?: number): CommunityMember { + const reader = input instanceof _m0.Reader ? input : new _m0.Reader(input); + let end = length === undefined ? reader.len : reader.pos + length; + const message = { ...baseCommunityMember } as CommunityMember; + message.roles = []; + while (reader.pos < end) { + const tag = reader.uint32(); + switch (tag >>> 3) { + case 1: + if ((tag & 7) === 2) { + const end2 = reader.uint32() + reader.pos; + while (reader.pos < end2) { + message.roles.push(reader.int32() as any); + } + } else { + message.roles.push(reader.int32() as any); + } + break; + default: + reader.skipType(tag & 7); + break; + } + } + return message; + }, + + fromJSON(object: any): CommunityMember { + const message = { ...baseCommunityMember } as CommunityMember; + message.roles = []; + if (object.roles !== undefined && object.roles !== null) { + for (const e of object.roles) { + message.roles.push(communityMember_RolesFromJSON(e)); + } + } + return message; + }, + + toJSON(message: CommunityMember): unknown { + const obj: any = {}; + if (message.roles) { + obj.roles = message.roles.map((e) => communityMember_RolesToJSON(e)); + } else { + obj.roles = []; + } + return obj; + }, + + fromPartial(object: DeepPartial): CommunityMember { + const message = { ...baseCommunityMember } as CommunityMember; + message.roles = []; + if (object.roles !== undefined && object.roles !== null) { + for (const e of object.roles) { + message.roles.push(e); + } + } + return message; + }, +}; + +const baseCommunityPermissions: object = { + ensOnly: false, + private: false, + access: 0, +}; + +export const CommunityPermissions = { + encode( + message: CommunityPermissions, + writer: _m0.Writer = _m0.Writer.create() + ): _m0.Writer { + if (message.ensOnly === true) { + writer.uint32(8).bool(message.ensOnly); + } + if (message.private === true) { + writer.uint32(16).bool(message.private); + } + if (message.access !== 0) { + writer.uint32(24).int32(message.access); + } + return writer; + }, + + decode( + input: _m0.Reader | Uint8Array, + length?: number + ): CommunityPermissions { + const reader = input instanceof _m0.Reader ? input : new _m0.Reader(input); + let end = length === undefined ? reader.len : reader.pos + length; + const message = { ...baseCommunityPermissions } as CommunityPermissions; + while (reader.pos < end) { + const tag = reader.uint32(); + switch (tag >>> 3) { + case 1: + message.ensOnly = reader.bool(); + break; + case 2: + message.private = reader.bool(); + break; + case 3: + message.access = reader.int32() as any; + break; + default: + reader.skipType(tag & 7); + break; + } + } + return message; + }, + + fromJSON(object: any): CommunityPermissions { + const message = { ...baseCommunityPermissions } as CommunityPermissions; + if (object.ensOnly !== undefined && object.ensOnly !== null) { + message.ensOnly = Boolean(object.ensOnly); + } else { + message.ensOnly = false; + } + if (object.private !== undefined && object.private !== null) { + message.private = Boolean(object.private); + } else { + message.private = false; + } + if (object.access !== undefined && object.access !== null) { + message.access = communityPermissions_AccessFromJSON(object.access); + } else { + message.access = 0; + } + return message; + }, + + toJSON(message: CommunityPermissions): unknown { + const obj: any = {}; + message.ensOnly !== undefined && (obj.ensOnly = message.ensOnly); + message.private !== undefined && (obj.private = message.private); + message.access !== undefined && + (obj.access = communityPermissions_AccessToJSON(message.access)); + return obj; + }, + + fromPartial(object: DeepPartial): CommunityPermissions { + const message = { ...baseCommunityPermissions } as CommunityPermissions; + if (object.ensOnly !== undefined && object.ensOnly !== null) { + message.ensOnly = object.ensOnly; + } else { + message.ensOnly = false; + } + if (object.private !== undefined && object.private !== null) { + message.private = object.private; + } else { + message.private = false; + } + if (object.access !== undefined && object.access !== null) { + message.access = object.access; + } else { + message.access = 0; + } + return message; + }, +}; + +const baseCommunityDescription: object = { clock: 0, banList: "" }; + +export const CommunityDescription = { + encode( + message: CommunityDescription, + writer: _m0.Writer = _m0.Writer.create() + ): _m0.Writer { + if (message.clock !== 0) { + writer.uint32(8).uint64(message.clock); + } + Object.entries(message.members).forEach(([key, value]) => { + CommunityDescription_MembersEntry.encode( + { key: key as any, value }, + writer.uint32(18).fork() + ).ldelim(); + }); + if (message.permissions !== undefined) { + CommunityPermissions.encode( + message.permissions, + writer.uint32(26).fork() + ).ldelim(); + } + if (message.identity !== undefined) { + ChatIdentity.encode(message.identity, writer.uint32(42).fork()).ldelim(); + } + Object.entries(message.chats).forEach(([key, value]) => { + CommunityDescription_ChatsEntry.encode( + { key: key as any, value }, + writer.uint32(50).fork() + ).ldelim(); + }); + for (const v of message.banList) { + writer.uint32(58).string(v!); + } + Object.entries(message.categories).forEach(([key, value]) => { + CommunityDescription_CategoriesEntry.encode( + { key: key as any, value }, + writer.uint32(66).fork() + ).ldelim(); + }); + return writer; + }, + + decode( + input: _m0.Reader | Uint8Array, + length?: number + ): CommunityDescription { + const reader = input instanceof _m0.Reader ? input : new _m0.Reader(input); + let end = length === undefined ? reader.len : reader.pos + length; + const message = { ...baseCommunityDescription } as CommunityDescription; + message.members = {}; + message.chats = {}; + message.banList = []; + message.categories = {}; + while (reader.pos < end) { + const tag = reader.uint32(); + switch (tag >>> 3) { + case 1: + message.clock = longToNumber(reader.uint64() as Long); + break; + case 2: + const entry2 = CommunityDescription_MembersEntry.decode( + reader, + reader.uint32() + ); + if (entry2.value !== undefined) { + message.members[entry2.key] = entry2.value; + } + break; + case 3: + message.permissions = CommunityPermissions.decode( + reader, + reader.uint32() + ); + break; + case 5: + message.identity = ChatIdentity.decode(reader, reader.uint32()); + break; + case 6: + const entry6 = CommunityDescription_ChatsEntry.decode( + reader, + reader.uint32() + ); + if (entry6.value !== undefined) { + message.chats[entry6.key] = entry6.value; + } + break; + case 7: + message.banList.push(reader.string()); + break; + case 8: + const entry8 = CommunityDescription_CategoriesEntry.decode( + reader, + reader.uint32() + ); + if (entry8.value !== undefined) { + message.categories[entry8.key] = entry8.value; + } + break; + default: + reader.skipType(tag & 7); + break; + } + } + return message; + }, + + fromJSON(object: any): CommunityDescription { + const message = { ...baseCommunityDescription } as CommunityDescription; + message.members = {}; + message.chats = {}; + message.banList = []; + message.categories = {}; + if (object.clock !== undefined && object.clock !== null) { + message.clock = Number(object.clock); + } else { + message.clock = 0; + } + if (object.members !== undefined && object.members !== null) { + Object.entries(object.members).forEach(([key, value]) => { + message.members[key] = CommunityMember.fromJSON(value); + }); + } + if (object.permissions !== undefined && object.permissions !== null) { + message.permissions = CommunityPermissions.fromJSON(object.permissions); + } else { + message.permissions = undefined; + } + if (object.identity !== undefined && object.identity !== null) { + message.identity = ChatIdentity.fromJSON(object.identity); + } else { + message.identity = undefined; + } + if (object.chats !== undefined && object.chats !== null) { + Object.entries(object.chats).forEach(([key, value]) => { + message.chats[key] = CommunityChat.fromJSON(value); + }); + } + if (object.banList !== undefined && object.banList !== null) { + for (const e of object.banList) { + message.banList.push(String(e)); + } + } + if (object.categories !== undefined && object.categories !== null) { + Object.entries(object.categories).forEach(([key, value]) => { + message.categories[key] = CommunityCategory.fromJSON(value); + }); + } + return message; + }, + + toJSON(message: CommunityDescription): unknown { + const obj: any = {}; + message.clock !== undefined && (obj.clock = message.clock); + obj.members = {}; + if (message.members) { + Object.entries(message.members).forEach(([k, v]) => { + obj.members[k] = CommunityMember.toJSON(v); + }); + } + message.permissions !== undefined && + (obj.permissions = message.permissions + ? CommunityPermissions.toJSON(message.permissions) + : undefined); + message.identity !== undefined && + (obj.identity = message.identity + ? ChatIdentity.toJSON(message.identity) + : undefined); + obj.chats = {}; + if (message.chats) { + Object.entries(message.chats).forEach(([k, v]) => { + obj.chats[k] = CommunityChat.toJSON(v); + }); + } + if (message.banList) { + obj.banList = message.banList.map((e) => e); + } else { + obj.banList = []; + } + obj.categories = {}; + if (message.categories) { + Object.entries(message.categories).forEach(([k, v]) => { + obj.categories[k] = CommunityCategory.toJSON(v); + }); + } + return obj; + }, + + fromPartial(object: DeepPartial): CommunityDescription { + const message = { ...baseCommunityDescription } as CommunityDescription; + message.members = {}; + message.chats = {}; + message.banList = []; + message.categories = {}; + if (object.clock !== undefined && object.clock !== null) { + message.clock = object.clock; + } else { + message.clock = 0; + } + if (object.members !== undefined && object.members !== null) { + Object.entries(object.members).forEach(([key, value]) => { + if (value !== undefined) { + message.members[key] = CommunityMember.fromPartial(value); + } + }); + } + if (object.permissions !== undefined && object.permissions !== null) { + message.permissions = CommunityPermissions.fromPartial( + object.permissions + ); + } else { + message.permissions = undefined; + } + if (object.identity !== undefined && object.identity !== null) { + message.identity = ChatIdentity.fromPartial(object.identity); + } else { + message.identity = undefined; + } + if (object.chats !== undefined && object.chats !== null) { + Object.entries(object.chats).forEach(([key, value]) => { + if (value !== undefined) { + message.chats[key] = CommunityChat.fromPartial(value); + } + }); + } + if (object.banList !== undefined && object.banList !== null) { + for (const e of object.banList) { + message.banList.push(e); + } + } + if (object.categories !== undefined && object.categories !== null) { + Object.entries(object.categories).forEach(([key, value]) => { + if (value !== undefined) { + message.categories[key] = CommunityCategory.fromPartial(value); + } + }); + } + return message; + }, +}; + +const baseCommunityDescription_MembersEntry: object = { key: "" }; + +export const CommunityDescription_MembersEntry = { + encode( + message: CommunityDescription_MembersEntry, + writer: _m0.Writer = _m0.Writer.create() + ): _m0.Writer { + if (message.key !== "") { + writer.uint32(10).string(message.key); + } + if (message.value !== undefined) { + CommunityMember.encode(message.value, writer.uint32(18).fork()).ldelim(); + } + return writer; + }, + + decode( + input: _m0.Reader | Uint8Array, + length?: number + ): CommunityDescription_MembersEntry { + const reader = input instanceof _m0.Reader ? input : new _m0.Reader(input); + let end = length === undefined ? reader.len : reader.pos + length; + const message = { + ...baseCommunityDescription_MembersEntry, + } as CommunityDescription_MembersEntry; + while (reader.pos < end) { + const tag = reader.uint32(); + switch (tag >>> 3) { + case 1: + message.key = reader.string(); + break; + case 2: + message.value = CommunityMember.decode(reader, reader.uint32()); + break; + default: + reader.skipType(tag & 7); + break; + } + } + return message; + }, + + fromJSON(object: any): CommunityDescription_MembersEntry { + const message = { + ...baseCommunityDescription_MembersEntry, + } as CommunityDescription_MembersEntry; + if (object.key !== undefined && object.key !== null) { + message.key = String(object.key); + } else { + message.key = ""; + } + if (object.value !== undefined && object.value !== null) { + message.value = CommunityMember.fromJSON(object.value); + } else { + message.value = undefined; + } + return message; + }, + + toJSON(message: CommunityDescription_MembersEntry): unknown { + const obj: any = {}; + message.key !== undefined && (obj.key = message.key); + message.value !== undefined && + (obj.value = message.value + ? CommunityMember.toJSON(message.value) + : undefined); + return obj; + }, + + fromPartial( + object: DeepPartial + ): CommunityDescription_MembersEntry { + const message = { + ...baseCommunityDescription_MembersEntry, + } as CommunityDescription_MembersEntry; + if (object.key !== undefined && object.key !== null) { + message.key = object.key; + } else { + message.key = ""; + } + if (object.value !== undefined && object.value !== null) { + message.value = CommunityMember.fromPartial(object.value); + } else { + message.value = undefined; + } + return message; + }, +}; + +const baseCommunityDescription_ChatsEntry: object = { key: "" }; + +export const CommunityDescription_ChatsEntry = { + encode( + message: CommunityDescription_ChatsEntry, + writer: _m0.Writer = _m0.Writer.create() + ): _m0.Writer { + if (message.key !== "") { + writer.uint32(10).string(message.key); + } + if (message.value !== undefined) { + CommunityChat.encode(message.value, writer.uint32(18).fork()).ldelim(); + } + return writer; + }, + + decode( + input: _m0.Reader | Uint8Array, + length?: number + ): CommunityDescription_ChatsEntry { + const reader = input instanceof _m0.Reader ? input : new _m0.Reader(input); + let end = length === undefined ? reader.len : reader.pos + length; + const message = { + ...baseCommunityDescription_ChatsEntry, + } as CommunityDescription_ChatsEntry; + while (reader.pos < end) { + const tag = reader.uint32(); + switch (tag >>> 3) { + case 1: + message.key = reader.string(); + break; + case 2: + message.value = CommunityChat.decode(reader, reader.uint32()); + break; + default: + reader.skipType(tag & 7); + break; + } + } + return message; + }, + + fromJSON(object: any): CommunityDescription_ChatsEntry { + const message = { + ...baseCommunityDescription_ChatsEntry, + } as CommunityDescription_ChatsEntry; + if (object.key !== undefined && object.key !== null) { + message.key = String(object.key); + } else { + message.key = ""; + } + if (object.value !== undefined && object.value !== null) { + message.value = CommunityChat.fromJSON(object.value); + } else { + message.value = undefined; + } + return message; + }, + + toJSON(message: CommunityDescription_ChatsEntry): unknown { + const obj: any = {}; + message.key !== undefined && (obj.key = message.key); + message.value !== undefined && + (obj.value = message.value + ? CommunityChat.toJSON(message.value) + : undefined); + return obj; + }, + + fromPartial( + object: DeepPartial + ): CommunityDescription_ChatsEntry { + const message = { + ...baseCommunityDescription_ChatsEntry, + } as CommunityDescription_ChatsEntry; + if (object.key !== undefined && object.key !== null) { + message.key = object.key; + } else { + message.key = ""; + } + if (object.value !== undefined && object.value !== null) { + message.value = CommunityChat.fromPartial(object.value); + } else { + message.value = undefined; + } + return message; + }, +}; + +const baseCommunityDescription_CategoriesEntry: object = { key: "" }; + +export const CommunityDescription_CategoriesEntry = { + encode( + message: CommunityDescription_CategoriesEntry, + writer: _m0.Writer = _m0.Writer.create() + ): _m0.Writer { + if (message.key !== "") { + writer.uint32(10).string(message.key); + } + if (message.value !== undefined) { + CommunityCategory.encode( + message.value, + writer.uint32(18).fork() + ).ldelim(); + } + return writer; + }, + + decode( + input: _m0.Reader | Uint8Array, + length?: number + ): CommunityDescription_CategoriesEntry { + const reader = input instanceof _m0.Reader ? input : new _m0.Reader(input); + let end = length === undefined ? reader.len : reader.pos + length; + const message = { + ...baseCommunityDescription_CategoriesEntry, + } as CommunityDescription_CategoriesEntry; + while (reader.pos < end) { + const tag = reader.uint32(); + switch (tag >>> 3) { + case 1: + message.key = reader.string(); + break; + case 2: + message.value = CommunityCategory.decode(reader, reader.uint32()); + break; + default: + reader.skipType(tag & 7); + break; + } + } + return message; + }, + + fromJSON(object: any): CommunityDescription_CategoriesEntry { + const message = { + ...baseCommunityDescription_CategoriesEntry, + } as CommunityDescription_CategoriesEntry; + if (object.key !== undefined && object.key !== null) { + message.key = String(object.key); + } else { + message.key = ""; + } + if (object.value !== undefined && object.value !== null) { + message.value = CommunityCategory.fromJSON(object.value); + } else { + message.value = undefined; + } + return message; + }, + + toJSON(message: CommunityDescription_CategoriesEntry): unknown { + const obj: any = {}; + message.key !== undefined && (obj.key = message.key); + message.value !== undefined && + (obj.value = message.value + ? CommunityCategory.toJSON(message.value) + : undefined); + return obj; + }, + + fromPartial( + object: DeepPartial + ): CommunityDescription_CategoriesEntry { + const message = { + ...baseCommunityDescription_CategoriesEntry, + } as CommunityDescription_CategoriesEntry; + if (object.key !== undefined && object.key !== null) { + message.key = object.key; + } else { + message.key = ""; + } + if (object.value !== undefined && object.value !== null) { + message.value = CommunityCategory.fromPartial(object.value); + } else { + message.value = undefined; + } + return message; + }, +}; + +const baseCommunityChat: object = { categoryId: "", position: 0 }; + +export const CommunityChat = { + encode( + message: CommunityChat, + writer: _m0.Writer = _m0.Writer.create() + ): _m0.Writer { + Object.entries(message.members).forEach(([key, value]) => { + CommunityChat_MembersEntry.encode( + { key: key as any, value }, + writer.uint32(10).fork() + ).ldelim(); + }); + if (message.permissions !== undefined) { + CommunityPermissions.encode( + message.permissions, + writer.uint32(18).fork() + ).ldelim(); + } + if (message.identity !== undefined) { + ChatIdentity.encode(message.identity, writer.uint32(26).fork()).ldelim(); + } + if (message.categoryId !== "") { + writer.uint32(34).string(message.categoryId); + } + if (message.position !== 0) { + writer.uint32(40).int32(message.position); + } + return writer; + }, + + decode(input: _m0.Reader | Uint8Array, length?: number): CommunityChat { + const reader = input instanceof _m0.Reader ? input : new _m0.Reader(input); + let end = length === undefined ? reader.len : reader.pos + length; + const message = { ...baseCommunityChat } as CommunityChat; + message.members = {}; + while (reader.pos < end) { + const tag = reader.uint32(); + switch (tag >>> 3) { + case 1: + const entry1 = CommunityChat_MembersEntry.decode( + reader, + reader.uint32() + ); + if (entry1.value !== undefined) { + message.members[entry1.key] = entry1.value; + } + break; + case 2: + message.permissions = CommunityPermissions.decode( + reader, + reader.uint32() + ); + break; + case 3: + message.identity = ChatIdentity.decode(reader, reader.uint32()); + break; + case 4: + message.categoryId = reader.string(); + break; + case 5: + message.position = reader.int32(); + break; + default: + reader.skipType(tag & 7); + break; + } + } + return message; + }, + + fromJSON(object: any): CommunityChat { + const message = { ...baseCommunityChat } as CommunityChat; + message.members = {}; + if (object.members !== undefined && object.members !== null) { + Object.entries(object.members).forEach(([key, value]) => { + message.members[key] = CommunityMember.fromJSON(value); + }); + } + if (object.permissions !== undefined && object.permissions !== null) { + message.permissions = CommunityPermissions.fromJSON(object.permissions); + } else { + message.permissions = undefined; + } + if (object.identity !== undefined && object.identity !== null) { + message.identity = ChatIdentity.fromJSON(object.identity); + } else { + message.identity = undefined; + } + if (object.categoryId !== undefined && object.categoryId !== null) { + message.categoryId = String(object.categoryId); + } else { + message.categoryId = ""; + } + if (object.position !== undefined && object.position !== null) { + message.position = Number(object.position); + } else { + message.position = 0; + } + return message; + }, + + toJSON(message: CommunityChat): unknown { + const obj: any = {}; + obj.members = {}; + if (message.members) { + Object.entries(message.members).forEach(([k, v]) => { + obj.members[k] = CommunityMember.toJSON(v); + }); + } + message.permissions !== undefined && + (obj.permissions = message.permissions + ? CommunityPermissions.toJSON(message.permissions) + : undefined); + message.identity !== undefined && + (obj.identity = message.identity + ? ChatIdentity.toJSON(message.identity) + : undefined); + message.categoryId !== undefined && (obj.categoryId = message.categoryId); + message.position !== undefined && (obj.position = message.position); + return obj; + }, + + fromPartial(object: DeepPartial): CommunityChat { + const message = { ...baseCommunityChat } as CommunityChat; + message.members = {}; + if (object.members !== undefined && object.members !== null) { + Object.entries(object.members).forEach(([key, value]) => { + if (value !== undefined) { + message.members[key] = CommunityMember.fromPartial(value); + } + }); + } + if (object.permissions !== undefined && object.permissions !== null) { + message.permissions = CommunityPermissions.fromPartial( + object.permissions + ); + } else { + message.permissions = undefined; + } + if (object.identity !== undefined && object.identity !== null) { + message.identity = ChatIdentity.fromPartial(object.identity); + } else { + message.identity = undefined; + } + if (object.categoryId !== undefined && object.categoryId !== null) { + message.categoryId = object.categoryId; + } else { + message.categoryId = ""; + } + if (object.position !== undefined && object.position !== null) { + message.position = object.position; + } else { + message.position = 0; + } + return message; + }, +}; + +const baseCommunityChat_MembersEntry: object = { key: "" }; + +export const CommunityChat_MembersEntry = { + encode( + message: CommunityChat_MembersEntry, + writer: _m0.Writer = _m0.Writer.create() + ): _m0.Writer { + if (message.key !== "") { + writer.uint32(10).string(message.key); + } + if (message.value !== undefined) { + CommunityMember.encode(message.value, writer.uint32(18).fork()).ldelim(); + } + return writer; + }, + + decode( + input: _m0.Reader | Uint8Array, + length?: number + ): CommunityChat_MembersEntry { + const reader = input instanceof _m0.Reader ? input : new _m0.Reader(input); + let end = length === undefined ? reader.len : reader.pos + length; + const message = { + ...baseCommunityChat_MembersEntry, + } as CommunityChat_MembersEntry; + while (reader.pos < end) { + const tag = reader.uint32(); + switch (tag >>> 3) { + case 1: + message.key = reader.string(); + break; + case 2: + message.value = CommunityMember.decode(reader, reader.uint32()); + break; + default: + reader.skipType(tag & 7); + break; + } + } + return message; + }, + + fromJSON(object: any): CommunityChat_MembersEntry { + const message = { + ...baseCommunityChat_MembersEntry, + } as CommunityChat_MembersEntry; + if (object.key !== undefined && object.key !== null) { + message.key = String(object.key); + } else { + message.key = ""; + } + if (object.value !== undefined && object.value !== null) { + message.value = CommunityMember.fromJSON(object.value); + } else { + message.value = undefined; + } + return message; + }, + + toJSON(message: CommunityChat_MembersEntry): unknown { + const obj: any = {}; + message.key !== undefined && (obj.key = message.key); + message.value !== undefined && + (obj.value = message.value + ? CommunityMember.toJSON(message.value) + : undefined); + return obj; + }, + + fromPartial( + object: DeepPartial + ): CommunityChat_MembersEntry { + const message = { + ...baseCommunityChat_MembersEntry, + } as CommunityChat_MembersEntry; + if (object.key !== undefined && object.key !== null) { + message.key = object.key; + } else { + message.key = ""; + } + if (object.value !== undefined && object.value !== null) { + message.value = CommunityMember.fromPartial(object.value); + } else { + message.value = undefined; + } + return message; + }, +}; + +const baseCommunityCategory: object = { categoryId: "", name: "", position: 0 }; + +export const CommunityCategory = { + encode( + message: CommunityCategory, + writer: _m0.Writer = _m0.Writer.create() + ): _m0.Writer { + if (message.categoryId !== "") { + writer.uint32(10).string(message.categoryId); + } + if (message.name !== "") { + writer.uint32(18).string(message.name); + } + if (message.position !== 0) { + writer.uint32(24).int32(message.position); + } + return writer; + }, + + decode(input: _m0.Reader | Uint8Array, length?: number): CommunityCategory { + const reader = input instanceof _m0.Reader ? input : new _m0.Reader(input); + let end = length === undefined ? reader.len : reader.pos + length; + const message = { ...baseCommunityCategory } as CommunityCategory; + while (reader.pos < end) { + const tag = reader.uint32(); + switch (tag >>> 3) { + case 1: + message.categoryId = reader.string(); + break; + case 2: + message.name = reader.string(); + break; + case 3: + message.position = reader.int32(); + break; + default: + reader.skipType(tag & 7); + break; + } + } + return message; + }, + + fromJSON(object: any): CommunityCategory { + const message = { ...baseCommunityCategory } as CommunityCategory; + if (object.categoryId !== undefined && object.categoryId !== null) { + message.categoryId = String(object.categoryId); + } else { + message.categoryId = ""; + } + if (object.name !== undefined && object.name !== null) { + message.name = String(object.name); + } else { + message.name = ""; + } + if (object.position !== undefined && object.position !== null) { + message.position = Number(object.position); + } else { + message.position = 0; + } + return message; + }, + + toJSON(message: CommunityCategory): unknown { + const obj: any = {}; + message.categoryId !== undefined && (obj.categoryId = message.categoryId); + message.name !== undefined && (obj.name = message.name); + message.position !== undefined && (obj.position = message.position); + return obj; + }, + + fromPartial(object: DeepPartial): CommunityCategory { + const message = { ...baseCommunityCategory } as CommunityCategory; + if (object.categoryId !== undefined && object.categoryId !== null) { + message.categoryId = object.categoryId; + } else { + message.categoryId = ""; + } + if (object.name !== undefined && object.name !== null) { + message.name = object.name; + } else { + message.name = ""; + } + if (object.position !== undefined && object.position !== null) { + message.position = object.position; + } else { + message.position = 0; + } + return message; + }, +}; + +const baseCommunityInvitation: object = { chatId: "" }; + +export const CommunityInvitation = { + encode( + message: CommunityInvitation, + writer: _m0.Writer = _m0.Writer.create() + ): _m0.Writer { + if (message.communityDescription.length !== 0) { + writer.uint32(10).bytes(message.communityDescription); + } + if (message.grant.length !== 0) { + writer.uint32(18).bytes(message.grant); + } + if (message.chatId !== "") { + writer.uint32(26).string(message.chatId); + } + if (message.publicKey.length !== 0) { + writer.uint32(34).bytes(message.publicKey); + } + return writer; + }, + + decode(input: _m0.Reader | Uint8Array, length?: number): CommunityInvitation { + const reader = input instanceof _m0.Reader ? input : new _m0.Reader(input); + let end = length === undefined ? reader.len : reader.pos + length; + const message = { ...baseCommunityInvitation } as CommunityInvitation; + message.communityDescription = new Uint8Array(); + message.grant = new Uint8Array(); + message.publicKey = new Uint8Array(); + while (reader.pos < end) { + const tag = reader.uint32(); + switch (tag >>> 3) { + case 1: + message.communityDescription = reader.bytes(); + break; + case 2: + message.grant = reader.bytes(); + break; + case 3: + message.chatId = reader.string(); + break; + case 4: + message.publicKey = reader.bytes(); + break; + default: + reader.skipType(tag & 7); + break; + } + } + return message; + }, + + fromJSON(object: any): CommunityInvitation { + const message = { ...baseCommunityInvitation } as CommunityInvitation; + message.communityDescription = new Uint8Array(); + message.grant = new Uint8Array(); + message.publicKey = new Uint8Array(); + if ( + object.communityDescription !== undefined && + object.communityDescription !== null + ) { + message.communityDescription = bytesFromBase64( + object.communityDescription + ); + } + if (object.grant !== undefined && object.grant !== null) { + message.grant = bytesFromBase64(object.grant); + } + if (object.chatId !== undefined && object.chatId !== null) { + message.chatId = String(object.chatId); + } else { + message.chatId = ""; + } + if (object.publicKey !== undefined && object.publicKey !== null) { + message.publicKey = bytesFromBase64(object.publicKey); + } + return message; + }, + + toJSON(message: CommunityInvitation): unknown { + const obj: any = {}; + message.communityDescription !== undefined && + (obj.communityDescription = base64FromBytes( + message.communityDescription !== undefined + ? message.communityDescription + : new Uint8Array() + )); + message.grant !== undefined && + (obj.grant = base64FromBytes( + message.grant !== undefined ? message.grant : new Uint8Array() + )); + message.chatId !== undefined && (obj.chatId = message.chatId); + message.publicKey !== undefined && + (obj.publicKey = base64FromBytes( + message.publicKey !== undefined ? message.publicKey : new Uint8Array() + )); + return obj; + }, + + fromPartial(object: DeepPartial): CommunityInvitation { + const message = { ...baseCommunityInvitation } as CommunityInvitation; + if ( + object.communityDescription !== undefined && + object.communityDescription !== null + ) { + message.communityDescription = object.communityDescription; + } else { + message.communityDescription = new Uint8Array(); + } + if (object.grant !== undefined && object.grant !== null) { + message.grant = object.grant; + } else { + message.grant = new Uint8Array(); + } + if (object.chatId !== undefined && object.chatId !== null) { + message.chatId = object.chatId; + } else { + message.chatId = ""; + } + if (object.publicKey !== undefined && object.publicKey !== null) { + message.publicKey = object.publicKey; + } else { + message.publicKey = new Uint8Array(); + } + return message; + }, +}; + +const baseCommunityRequestToJoin: object = { + clock: 0, + ensName: "", + chatId: "", +}; + +export const CommunityRequestToJoin = { + encode( + message: CommunityRequestToJoin, + writer: _m0.Writer = _m0.Writer.create() + ): _m0.Writer { + if (message.clock !== 0) { + writer.uint32(8).uint64(message.clock); + } + if (message.ensName !== "") { + writer.uint32(18).string(message.ensName); + } + if (message.chatId !== "") { + writer.uint32(26).string(message.chatId); + } + if (message.communityId.length !== 0) { + writer.uint32(34).bytes(message.communityId); + } + return writer; + }, + + decode( + input: _m0.Reader | Uint8Array, + length?: number + ): CommunityRequestToJoin { + const reader = input instanceof _m0.Reader ? input : new _m0.Reader(input); + let end = length === undefined ? reader.len : reader.pos + length; + const message = { ...baseCommunityRequestToJoin } as CommunityRequestToJoin; + message.communityId = new Uint8Array(); + while (reader.pos < end) { + const tag = reader.uint32(); + switch (tag >>> 3) { + case 1: + message.clock = longToNumber(reader.uint64() as Long); + break; + case 2: + message.ensName = reader.string(); + break; + case 3: + message.chatId = reader.string(); + break; + case 4: + message.communityId = reader.bytes(); + break; + default: + reader.skipType(tag & 7); + break; + } + } + return message; + }, + + fromJSON(object: any): CommunityRequestToJoin { + const message = { ...baseCommunityRequestToJoin } as CommunityRequestToJoin; + message.communityId = new Uint8Array(); + if (object.clock !== undefined && object.clock !== null) { + message.clock = Number(object.clock); + } else { + message.clock = 0; + } + if (object.ensName !== undefined && object.ensName !== null) { + message.ensName = String(object.ensName); + } else { + message.ensName = ""; + } + if (object.chatId !== undefined && object.chatId !== null) { + message.chatId = String(object.chatId); + } else { + message.chatId = ""; + } + if (object.communityId !== undefined && object.communityId !== null) { + message.communityId = bytesFromBase64(object.communityId); + } + return message; + }, + + toJSON(message: CommunityRequestToJoin): unknown { + const obj: any = {}; + message.clock !== undefined && (obj.clock = message.clock); + message.ensName !== undefined && (obj.ensName = message.ensName); + message.chatId !== undefined && (obj.chatId = message.chatId); + message.communityId !== undefined && + (obj.communityId = base64FromBytes( + message.communityId !== undefined + ? message.communityId + : new Uint8Array() + )); + return obj; + }, + + fromPartial( + object: DeepPartial + ): CommunityRequestToJoin { + const message = { ...baseCommunityRequestToJoin } as CommunityRequestToJoin; + if (object.clock !== undefined && object.clock !== null) { + message.clock = object.clock; + } else { + message.clock = 0; + } + if (object.ensName !== undefined && object.ensName !== null) { + message.ensName = object.ensName; + } else { + message.ensName = ""; + } + if (object.chatId !== undefined && object.chatId !== null) { + message.chatId = object.chatId; + } else { + message.chatId = ""; + } + if (object.communityId !== undefined && object.communityId !== null) { + message.communityId = object.communityId; + } else { + message.communityId = new Uint8Array(); + } + return message; + }, +}; + +const baseCommunityRequestToJoinResponse: object = { + clock: 0, + accepted: false, +}; + +export const CommunityRequestToJoinResponse = { + encode( + message: CommunityRequestToJoinResponse, + writer: _m0.Writer = _m0.Writer.create() + ): _m0.Writer { + if (message.clock !== 0) { + writer.uint32(8).uint64(message.clock); + } + if (message.community !== undefined) { + CommunityDescription.encode( + message.community, + writer.uint32(18).fork() + ).ldelim(); + } + if (message.accepted === true) { + writer.uint32(24).bool(message.accepted); + } + if (message.grant.length !== 0) { + writer.uint32(34).bytes(message.grant); + } + return writer; + }, + + decode( + input: _m0.Reader | Uint8Array, + length?: number + ): CommunityRequestToJoinResponse { + const reader = input instanceof _m0.Reader ? input : new _m0.Reader(input); + let end = length === undefined ? reader.len : reader.pos + length; + const message = { + ...baseCommunityRequestToJoinResponse, + } as CommunityRequestToJoinResponse; + message.grant = new Uint8Array(); + while (reader.pos < end) { + const tag = reader.uint32(); + switch (tag >>> 3) { + case 1: + message.clock = longToNumber(reader.uint64() as Long); + break; + case 2: + message.community = CommunityDescription.decode( + reader, + reader.uint32() + ); + break; + case 3: + message.accepted = reader.bool(); + break; + case 4: + message.grant = reader.bytes(); + break; + default: + reader.skipType(tag & 7); + break; + } + } + return message; + }, + + fromJSON(object: any): CommunityRequestToJoinResponse { + const message = { + ...baseCommunityRequestToJoinResponse, + } as CommunityRequestToJoinResponse; + message.grant = new Uint8Array(); + if (object.clock !== undefined && object.clock !== null) { + message.clock = Number(object.clock); + } else { + message.clock = 0; + } + if (object.community !== undefined && object.community !== null) { + message.community = CommunityDescription.fromJSON(object.community); + } else { + message.community = undefined; + } + if (object.accepted !== undefined && object.accepted !== null) { + message.accepted = Boolean(object.accepted); + } else { + message.accepted = false; + } + if (object.grant !== undefined && object.grant !== null) { + message.grant = bytesFromBase64(object.grant); + } + return message; + }, + + toJSON(message: CommunityRequestToJoinResponse): unknown { + const obj: any = {}; + message.clock !== undefined && (obj.clock = message.clock); + message.community !== undefined && + (obj.community = message.community + ? CommunityDescription.toJSON(message.community) + : undefined); + message.accepted !== undefined && (obj.accepted = message.accepted); + message.grant !== undefined && + (obj.grant = base64FromBytes( + message.grant !== undefined ? message.grant : new Uint8Array() + )); + return obj; + }, + + fromPartial( + object: DeepPartial + ): CommunityRequestToJoinResponse { + const message = { + ...baseCommunityRequestToJoinResponse, + } as CommunityRequestToJoinResponse; + if (object.clock !== undefined && object.clock !== null) { + message.clock = object.clock; + } else { + message.clock = 0; + } + if (object.community !== undefined && object.community !== null) { + message.community = CommunityDescription.fromPartial(object.community); + } else { + message.community = undefined; + } + if (object.accepted !== undefined && object.accepted !== null) { + message.accepted = object.accepted; + } else { + message.accepted = false; + } + if (object.grant !== undefined && object.grant !== null) { + message.grant = object.grant; + } else { + message.grant = new Uint8Array(); + } + return message; + }, +}; + +declare var self: any | undefined; +declare var window: any | undefined; +declare var global: any | undefined; +var globalThis: any = (() => { + if (typeof globalThis !== "undefined") return globalThis; + if (typeof self !== "undefined") return self; + if (typeof window !== "undefined") return window; + if (typeof global !== "undefined") return global; + throw "Unable to locate global object"; +})(); + +const atob: (b64: string) => string = + globalThis.atob || + ((b64) => globalThis.Buffer.from(b64, "base64").toString("binary")); +function bytesFromBase64(b64: string): Uint8Array { + const bin = atob(b64); + const arr = new Uint8Array(bin.length); + for (let i = 0; i < bin.length; ++i) { + arr[i] = bin.charCodeAt(i); + } + return arr; +} + +const btoa: (bin: string) => string = + globalThis.btoa || + ((bin) => globalThis.Buffer.from(bin, "binary").toString("base64")); +function base64FromBytes(arr: Uint8Array): string { + const bin: string[] = []; + for (const byte of arr) { + bin.push(String.fromCharCode(byte)); + } + return btoa(bin.join("")); +} + +type Builtin = + | Date + | Function + | Uint8Array + | string + | number + | boolean + | undefined; +export type DeepPartial = T 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-communities/src/wire/chat_identity.ts b/packages/status-communities/src/wire/chat_identity.ts new file mode 100644 index 00000000..90bc9dc7 --- /dev/null +++ b/packages/status-communities/src/wire/chat_identity.ts @@ -0,0 +1,17 @@ +import { Reader } from "protobufjs"; + +import * as proto 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(); + } +} diff --git a/packages/status-communities/src/wire/community_chat.ts b/packages/status-communities/src/wire/community_chat.ts new file mode 100644 index 00000000..9cf04c97 --- /dev/null +++ b/packages/status-communities/src/wire/community_chat.ts @@ -0,0 +1,17 @@ +import { Reader } from "protobufjs"; + +import * as proto from "../proto/communities/v1/communities"; + +export class CommunityChat { + public constructor(public proto: proto.CommunityChat) {} + + 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(); + } +} diff --git a/packages/status-communities/src/wire/community_description.ts b/packages/status-communities/src/wire/community_description.ts new file mode 100644 index 00000000..f86d95f8 --- /dev/null +++ b/packages/status-communities/src/wire/community_description.ts @@ -0,0 +1,93 @@ +import debug from "debug"; +import { WakuMessage, WakuStore } from "js-waku"; +import { Reader } from "protobufjs"; + +import { idToContentTopic } from "../contentTopic"; +import * as proto from "../proto/communities/v1/communities"; +import { bufToHex } from "../utils"; + +import { ChatIdentity } from "./chat_identity"; +import { CommunityChat } from "./community_chat"; + +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); + 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 _communityDescription = CommunityDescription.decode( + message.payload + ); + + if (!_communityDescription.identity) return; + + communityDescription = _communityDescription; + } catch (e) { + dbg( + `Failed to decode message as CommunityDescription found on content topic ${contentTopic}`, + e + ); + } + }); + }; + + await wakuStore + .queryHistory([contentTopic], { + callback, + }) + .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; + } +} From 4fc7d6cc6261266383281e96468d61de6973754e Mon Sep 17 00:00:00 2001 From: Franck Royer Date: Wed, 6 Oct 2021 14:21:03 +1100 Subject: [PATCH 5/9] Enforce function return types Very useful for proto wire types as all fields are optional but not always reflected in the generated type. --- packages/status-communities/.eslintrc.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/status-communities/.eslintrc.json b/packages/status-communities/.eslintrc.json index 0e42b7d9..f0d0dc9b 100644 --- a/packages/status-communities/.eslintrc.json +++ b/packages/status-communities/.eslintrc.json @@ -14,7 +14,7 @@ ], "globals": { "BigInt": true, "console": true, "WebAssembly": true }, "rules": { - "@typescript-eslint/explicit-function-return-type": ["warn"], + "@typescript-eslint/explicit-function-return-type": ["error"], "@typescript-eslint/explicit-module-boundary-types": "off", "eslint-comments/disable-enable-pair": [ "error", From 9d9723d137914a02d089599af839e11ddbbea0a1 Mon Sep 17 00:00:00 2001 From: Franck Royer Date: Wed, 6 Oct 2021 14:51:45 +1100 Subject: [PATCH 6/9] Instantiate chat from community --- packages/status-communities/src/chat.ts | 1 + packages/status-communities/src/community.ts | 34 ++++++++++++++++ .../src/wire/community_chat.ts | 39 +++++++++++++++++++ 3 files changed, 74 insertions(+) diff --git a/packages/status-communities/src/chat.ts b/packages/status-communities/src/chat.ts index 95457c87..54ee5da2 100644 --- a/packages/status-communities/src/chat.ts +++ b/packages/status-communities/src/chat.ts @@ -13,6 +13,7 @@ export class Chat { /** * Create a public chat room. + * [[Community.instantiateChat]] MUST be used for chats belonging to a community. */ public static async create(id: string): Promise { const symKey = await createSymKeyFromPassword(id); diff --git a/packages/status-communities/src/community.ts b/packages/status-communities/src/community.ts index 7801d8d3..4e616804 100644 --- a/packages/status-communities/src/community.ts +++ b/packages/status-communities/src/community.ts @@ -1,7 +1,9 @@ import debug from "debug"; import { Waku } from "js-waku"; +import { Chat } from "./chat"; import { bufToHex, hexToBuf } from "./utils"; +import { CommunityChat } from "./wire/community_chat"; import { CommunityDescription } from "./wire/community_description"; const dbg = debug("communities:community"); @@ -25,6 +27,7 @@ export class 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/ + * @param waku The Waku instance, used to retrieve Community information from the network. */ public async instantiateCommunity( publicKey: string, @@ -58,4 +61,35 @@ export class Community { this.description = desc; } + + /** + * 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; + */ + public async instantiateChat(chatName: string): Promise { + if (!this.description) { + await this.refreshCommunityDescription(); + if (!this.description) + throw "Failed to retrieve community description, cannot instantiate chat"; + } + + let communityChat: CommunityChat | undefined; + let chatId: string | undefined; + + this.description.chats.forEach((_chat, _id) => { + if (chatId) return; + + if (_chat.identity?.displayName === chatName) { + chatId = _id; + communityChat = _chat; + } + }); + + if (!communityChat || !chatId) + throw `Failed to retrieve community community chat with name ${chatName}`; + + return Chat.create(chatId); + } } diff --git a/packages/status-communities/src/wire/community_chat.ts b/packages/status-communities/src/wire/community_chat.ts index 9cf04c97..915dfd5b 100644 --- a/packages/status-communities/src/wire/community_chat.ts +++ b/packages/status-communities/src/wire/community_chat.ts @@ -1,10 +1,20 @@ import { Reader } from "protobufjs"; +import { ChatIdentity } from "../proto/communities/v1/chat_identity"; import * as proto from "../proto/communities/v1/communities"; +import { + 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)); @@ -14,4 +24,33 @@ export class CommunityChat { 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 { + return 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; + } } From aef476cc4d819ef721f81ddaa75ef4cc57e46454 Mon Sep 17 00:00:00 2001 From: Franck Royer Date: Thu, 7 Oct 2021 11:32:13 +1100 Subject: [PATCH 7/9] Correct chat id --- packages/status-communities/src/community.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/status-communities/src/community.ts b/packages/status-communities/src/community.ts index 4e616804..2ed455a3 100644 --- a/packages/status-communities/src/community.ts +++ b/packages/status-communities/src/community.ts @@ -76,20 +76,20 @@ export class Community { } let communityChat: CommunityChat | undefined; - let chatId: string | undefined; + let chatUuid: string | undefined; this.description.chats.forEach((_chat, _id) => { - if (chatId) return; + if (chatUuid) return; if (_chat.identity?.displayName === chatName) { - chatId = _id; + chatUuid = _id; communityChat = _chat; } }); - if (!communityChat || !chatId) + if (!communityChat || !chatUuid) throw `Failed to retrieve community community chat with name ${chatName}`; - return Chat.create(chatId); + return Chat.create(this.publicKeyStr + chatUuid); } } From d677958a227d51c0e39e436c53f3483f9491f7e6 Mon Sep 17 00:00:00 2001 From: Franck Royer Date: Fri, 8 Oct 2021 11:12:54 +1100 Subject: [PATCH 8/9] Move test near file --- .../status-communities/src/{ => wire}/chat_message.spec.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) rename packages/status-communities/src/{ => wire}/chat_message.spec.ts (93%) diff --git a/packages/status-communities/src/chat_message.spec.ts b/packages/status-communities/src/wire/chat_message.spec.ts similarity index 93% rename from packages/status-communities/src/chat_message.spec.ts rename to packages/status-communities/src/wire/chat_message.spec.ts index ebcc71ee..48c0a56e 100644 --- a/packages/status-communities/src/chat_message.spec.ts +++ b/packages/status-communities/src/wire/chat_message.spec.ts @@ -3,15 +3,16 @@ import { expect } from "chai"; import { AudioMessage_AudioType, ChatMessage_ContentType, -} from "./proto/communities/v1/chat_message"; -import { ImageType } from "./proto/communities/v1/enums"; +} from "../proto/communities/v1/chat_message"; +import { ImageType } from "../proto/communities/v1/enums"; + import { AudioContent, ChatMessage, ContentType, ImageContent, StickerContent, -} from "./wire/chat_message"; +} from "./chat_message"; describe("Chat Message", () => { it("Encode & decode Image message", () => { From 907eacaa6cc3882c9f83d543df8d804829b909d8 Mon Sep 17 00:00:00 2001 From: Franck Royer Date: Fri, 8 Oct 2021 14:41:28 +1100 Subject: [PATCH 9/9] Export more types from root --- packages/react-chat/src/hooks/useMessenger.ts | 2 +- packages/status-communities/src/index.ts | 21 +++++++++++++------ 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/packages/react-chat/src/hooks/useMessenger.ts b/packages/react-chat/src/hooks/useMessenger.ts index 5a0dd2f8..c8dc47a4 100644 --- a/packages/react-chat/src/hooks/useMessenger.ts +++ b/packages/react-chat/src/hooks/useMessenger.ts @@ -2,7 +2,7 @@ import { getBootstrapNodes, StoreCodec } from "js-waku"; import { useCallback, useEffect, useState } from "react"; import { Identity, Messenger } from "status-communities/dist/cjs"; -import { ApplicationMetadataMessage } from "status-communities/dist/cjs/application_metadata_message"; +import { ApplicationMetadataMessage } from "status-communities/dist/cjs"; import { uintToImgUrl } from "../helpers/uintToImgUrl"; import { ChatMessage } from "../models/ChatMessage"; diff --git a/packages/status-communities/src/index.ts b/packages/status-communities/src/index.ts index 5256c077..4a3027b8 100644 --- a/packages/status-communities/src/index.ts +++ b/packages/status-communities/src/index.ts @@ -1,6 +1,15 @@ -import { Identity } from "./identity"; -import { Messenger } from "./messenger"; -import { ApplicationMetadataMessage } from "./wire/application_metadata_message"; -import { ChatMessage } from "./wire/chat_message"; - -export { Messenger, Identity, ApplicationMetadataMessage, ChatMessage }; +export { Identity } from "./identity"; +export { Messenger } from "./messenger"; +export { Community } from "./community"; +export { Chat } from "./chat"; +export * as utils from "./utils"; +export { ApplicationMetadataMessage } from "./wire/application_metadata_message"; +export { + ChatMessage, + ContentType, + Content, + StickerContent, + ImageContent, + AudioContent, + TextContent, +} from "./wire/chat_message";