diff --git a/.eslintignore b/.eslintignore index 52191fe..a706360 100644 --- a/.eslintignore +++ b/.eslintignore @@ -1,3 +1,4 @@ **/dist **/node_modules **/protos +**/proto diff --git a/.vscode/launch.json b/.vscode/launch.json index baf3da5..b9993eb 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -4,10 +4,12 @@ { "type": "node", "request": "launch", - "name": "Launch status-js", - "program": "${workspaceFolder}/packages/status-js/src/index.ts", + "name": "Launch Client", + "program": "${workspaceFolder}/packages/status-js/src/debug.ts", "preLaunchTask": "npm: build:status-js", - "outFiles": ["${workspaceFolder}/packages/status-js/dist/index.js"], + "outFiles": [ + "${workspaceFolder}/packages/status-js/dist/index.js" + ], "sourceMaps": true, "smartStep": true, "internalConsoleOptions": "openOnSessionStart", diff --git a/.parcelrc b/.xparcelrc similarity index 100% rename from .parcelrc rename to .xparcelrc diff --git a/examples/community/index.tsx b/examples/community/index.tsx index ee8e72a..274fa6b 100644 --- a/examples/community/index.tsx +++ b/examples/community/index.tsx @@ -7,7 +7,12 @@ import { Community } from '@status-im/react' // TODO?: import/rename CommunityAndMessenger, or Status even const App = () => { - return + return ( + + ) } render( diff --git a/examples/community/package.json b/examples/community/package.json index 9ad6928..6223190 100644 --- a/examples/community/package.json +++ b/examples/community/package.json @@ -4,7 +4,7 @@ "source": "./index.html", "browserslist": "> 0.5%, last 2 versions, not dead, not ios_saf < 13", "scripts": { - "dev": "parcel --https", + "dev": "parcel --https --host 192.168.86.38 --port 8080", "prebuild": "rm -rf dist", "build": "parcel build" }, diff --git a/jest.config.js b/jest.config.js new file mode 100644 index 0000000..6d47193 --- /dev/null +++ b/jest.config.js @@ -0,0 +1,16 @@ +/** @type {import('ts-jest/dist/types').InitialOptionsTsJest} */ +module.exports = { + // preset: 'ts-jest', + testEnvironment: 'node', + extensionsToTreatAsEsm: ['.ts'], + preset: 'ts-jest/presets/default-esm', + globals: { + 'ts-jest': { + useESM: true, + tsconfig: 'tsconfig.base.json', + }, + }, + moduleNameMapper: { + '^(\\.{1,2}/.*)\\.js$': '$1', + }, +} diff --git a/package.json b/package.json index 604a561..c3f7aec 100644 --- a/package.json +++ b/package.json @@ -1,4 +1,8 @@ { + "alias": { + "protons-runtime": "./node_modules/protons-runtime/dist/src/index.js", + "uint8arraylist": "./node_modules/uint8arraylist/dist/src/index.js" + }, "private": true, "workspaces": [ "packages/*", @@ -9,7 +13,7 @@ "prepare": "husky install", "fix": "run-s 'fix:*' && wsrun -e -c -s fix", "build": "wsrun -e -c -s build", - "build:status-js": "yarn workspace @status-im/js build", + "build:status-js": "yarn workspace @status-im/js debug", "build:status-react": "yarn workspace @status-im/react build", "lint": "eslint 'packages/**/*.{ts,tsx}'", "lint:fix": "eslint 'packages/**/*.{ts,tsx}' --fix", @@ -23,10 +27,10 @@ "@babel/preset-env": "^7.18.2", "@babel/preset-typescript": "^7.17.12", "@parcel/config-default": "^2.6.0", - "@parcel/packager-ts": "2.3.2", + "@parcel/packager-ts": "^2.6.0", "@parcel/transformer-js": "^2.6.0", "@parcel/transformer-react-refresh-wrap": "^2.6.0", - "@parcel/transformer-typescript-types": "2.3.2", + "@parcel/transformer-typescript-types": "^2.6.0", "@types/jest": "^27.5.1", "@typescript-eslint/eslint-plugin": "^5.12.0", "@typescript-eslint/parser": "^5.12.0", @@ -47,10 +51,11 @@ "jest": "^28.1.0", "lint-staged": "^12.3.4", "npm-run-all": "^4.1.5", - "parcel": "^2.3.2", + "parcel": "^2.6.0", "prettier": "^2.5.1", "typescript": "^4.5.5", - "wsrun": "^5.2.4" + "wsrun": "^5.2.4", + "ts-jest": "^28.0.4" }, "lint-staged": { "*.{ts,tsx,js,jsx}": [ diff --git a/packages/status-js/.scripts/example copy 2.ts b/packages/status-js/.scripts/example copy 2.ts index 0d199a4..a9bba64 100644 --- a/packages/status-js/.scripts/example copy 2.ts +++ b/packages/status-js/.scripts/example copy 2.ts @@ -1,27 +1,23 @@ import { createClient } from '../src/client' -import { Community } from '../src/community' -// import { Messenger } from '../src/messenger' - const COMMUNITY_PUBLIC_KEY = - '0x029dd5fecbd689dc11e2a5b399afed92cf1fab65d315b883efca753e8f3882f3bd' // compressed; A catchy name -// '0x02c788e419b56c714460220bedadc9c5d401ea10eee48d25ac81fc9a06fb75162e' // compressed; A boring name -// const COMMUNITY_CHANNEL_KEY = '0x029dd5fecbd689dc11e2a5b399afed92cf1fab65d315b883efca753e8f3882f3bd06935bce-a863-4827-9990-1652ae375c89' // 06935bce-a863-4827-9990-1652ae375c89; #channel -const COMMUNITY_CHANNEL_KEY = '6102c603-3246-4b90-986d-43c1b87b165f' // #random; UUID + '0x029f196bbfef4fa6a5eb81dd802133a63498325445ca1af1d154b1bb4542955133' // Boring community +// '0x0243611cc13cc4e4390180fe8fd35234ab0fe2a7ba8d32e8ae5dd23b60ac7ec177' +// '0x02e7102c85ed78e5be30124f8f52014b1135f972c383f55f83ec8ff50436cd1260' +const CHANNEL_ID = '00d3f525-a0cf-4c40-832d-543ec9f8188b' // messages ;(async () => { - const client = await createClient() + const client = await createClient(COMMUNITY_PUBLIC_KEY) - // Community (e.g. name, description, permissions, members, channels, channel messages) - const community = await Community.instantiateCommunity( - COMMUNITY_PUBLIC_KEY, - client + await client.start() + + // const community = await client.community.fetchCommunity() + + // client.community.onCommunityUpdate(() => console.log("community:update")) + // client.community.onChannelUpdate(() => console.log("channel:update")) + client.community.onChannelMessages(CHANNEL_ID, () => + console.log('channel:message') ) - // // Messenger/Messages (e.g. direct messages) - // const messenger = await Messenger.create(, client) - - // history - - await client.stop() + // await client.stop() })() diff --git a/packages/status-js/buf.gen.yaml b/packages/status-js/buf.gen.yaml new file mode 100644 index 0000000..40edf96 --- /dev/null +++ b/packages/status-js/buf.gen.yaml @@ -0,0 +1,6 @@ +version: v1beta1 + +plugins: + - name: ts_proto + out: ./src/proto + opt: grpc_js,esModuleInterop=true diff --git a/packages/status-js/buf.yaml b/packages/status-js/buf.yaml new file mode 100644 index 0000000..c6a09b8 --- /dev/null +++ b/packages/status-js/buf.yaml @@ -0,0 +1,9 @@ +version: v1beta1 + +build: + roots: + - ./proto +lint: + except: + - ENUM_ZERO_VALUE_SUFFIX + - ENUM_VALUE_PREFIX diff --git a/packages/status-js/package.json b/packages/status-js/package.json index 795e954..21bb908 100644 --- a/packages/status-js/package.json +++ b/packages/status-js/package.json @@ -24,15 +24,19 @@ }, "scripts": { "prebuild": "rm -rf dist", - "dev": "parcel", + "dev": "parcel --host 192.168.86.38 --port 8080", "build": "parcel build", + "debug": "parcel build src/debug.ts --dist-dir output", "build:vite": "vite build", "build:types": "tsc --emitDeclarationOnly", "build:protos": "protons protos/*.proto", "typecheck": "tsc --noEmit", "lint": "eslint src", "format": "prettier --write src", - "clean": "rm -rf node_modules && rm -rf dist" + "clean": "rm -rf node_modules && rm -rf dist", + "proto": "run-s 'proto:*'", + "proto:lint": "buf lint", + "proto:build": "buf generate" }, "dependencies": { "bn.js": "^5.2.0", @@ -43,6 +47,7 @@ "ethereum-cryptography": "^1.0.3", "js-sha3": "^0.8.0", "js-waku": "^0.23.0", + "lodash": "^4.17.21", "long": "^5.2.0", "pbkdf2": "^3.1.2", "protobufjs": "^6.11.3", @@ -53,11 +58,13 @@ "devDependencies": { "@types/bn.js": "^5.1.0", "@types/elliptic": "^6.4.14", + "@types/lodash": "^4.14.182", "@types/pbkdf2": "^3.1.0", "@types/secp256k1": "^4.0.3", "@types/uuid": "^8.3.3", "npm-run-all": "^4.1.5", "protons": "^3.0.4", - "ts-node": "^10.2.1" + "ts-node": "^10.2.1", + "ts-proto": "^1.115.1" } } diff --git a/packages/status-js/proto/status/v1/communities.proto b/packages/status-js/proto/status/v1/communities.proto new file mode 100644 index 0000000..48badc9 --- /dev/null +++ b/packages/status-js/proto/status/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-js/proto/status/v1/protocol_message.proto b/packages/status-js/proto/status/v1/protocol_message.proto new file mode 100644 index 0000000..8e4be55 --- /dev/null +++ b/packages/status-js/proto/status/v1/protocol_message.proto @@ -0,0 +1,88 @@ +syntax = "proto3"; + +message SignedPreKey { + bytes signed_pre_key = 1; + uint32 version = 2; + uint32 protocol_version = 3; +} + +// X3DH prekey bundle +message Bundle { + // Identity key + bytes identity = 1; + // Installation id + map signed_pre_keys = 2; + // Prekey signature + bytes signature = 4; + + // When the bundle was created locally + int64 timestamp = 5; +} + +message BundleContainer { + reserved 3; + // X3DH prekey bundle + Bundle bundle = 1; + // Private signed prekey + bytes private_signed_pre_key = 2; +} + +message DRHeader { + // Current ratchet public key + bytes key = 1; + // Number of the message in the sending chain + uint32 n = 2; + // Length of the previous sending chain + uint32 pn = 3; + // Bundle ID + bytes id = 4; +} + +message DHHeader { + // Compressed ephemeral public key + bytes key = 1; +} + +message X3DHHeader { + reserved 3; + // Ephemeral key used + bytes key = 1; + // Used bundle's signed prekey + bytes id = 4; +} + +// Hash Ratchet Header +message HRHeader { + // community key ID + uint32 key_id = 1; + // Community message number for this key_id + uint32 seq_no = 2; + // Community ID + string group_id = 3; +} + +// Direct message value +message EncryptedMessageProtocol { + X3DHHeader x3dh_header = 1; + DRHeader dr_header = 2; + DHHeader dh_header = 101; + HRHeader hr_header = 102; + // Encrypted payload + bytes payload = 3; +} + +// Top-level protocol message +message ProtocolMessage { + // The device id of the sender + string installation_id = 2; + + // List of bundles + repeated Bundle bundles = 3; + + // One to one message, encrypted, indexed by installation_id + // TODO map here is redundant in case of community messages + map encrypted_message = 101; + + // Public chats, not encrypted + bytes public_message = 102; +} diff --git a/packages/status-js/protos copy/application-metadata-message.proto b/packages/status-js/protos copy/application-metadata-message.proto new file mode 100644 index 0000000..79fe435 --- /dev/null +++ b/packages/status-js/protos copy/application-metadata-message.proto @@ -0,0 +1,48 @@ +syntax = "proto3"; + +message ApplicationMetadataMessage { + // Signature of the payload field + bytes signature = 1; + // This is the encoded protobuf of the application level message, i.e ChatMessage + bytes payload = 2; + + // The type of protobuf message sent + Type type = 3; + + enum Type { + TYPE_UNKNOWN_UNSPECIFIED = 0; + TYPE_CHAT_MESSAGE = 1; + TYPE_CONTACT_UPDATE = 2; + TYPE_MEMBERSHIP_UPDATE_MESSAGE = 3; + TYPE_PAIR_INSTALLATION = 4; + TYPE_SYNC_INSTALLATION = 5; + TYPE_REQUEST_ADDRESS_FOR_TRANSACTION = 6; + TYPE_ACCEPT_REQUEST_ADDRESS_FOR_TRANSACTION = 7; + TYPE_DECLINE_REQUEST_ADDRESS_FOR_TRANSACTION = 8; + TYPE_REQUEST_TRANSACTION = 9; + TYPE_SEND_TRANSACTION = 10; + TYPE_DECLINE_REQUEST_TRANSACTION = 11; + TYPE_SYNC_INSTALLATION_CONTACT = 12; + TYPE_SYNC_INSTALLATION_ACCOUNT = 13; + TYPE_SYNC_INSTALLATION_PUBLIC_CHAT = 14; + TYPE_CONTACT_CODE_ADVERTISEMENT = 15; + TYPE_PUSH_NOTIFICATION_REGISTRATION = 16; + TYPE_PUSH_NOTIFICATION_REGISTRATION_RESPONSE = 17; + TYPE_PUSH_NOTIFICATION_QUERY = 18; + TYPE_PUSH_NOTIFICATION_QUERY_RESPONSE = 19; + TYPE_PUSH_NOTIFICATION_REQUEST = 20; + TYPE_PUSH_NOTIFICATION_RESPONSE = 21; + TYPE_EMOJI_REACTION = 22; + TYPE_GROUP_CHAT_INVITATION = 23; + TYPE_CHAT_IDENTITY = 24; + TYPE_COMMUNITY_DESCRIPTION = 25; + TYPE_COMMUNITY_INVITATION = 26; + TYPE_COMMUNITY_REQUEST_TO_JOIN = 27; + TYPE_PIN_MESSAGE = 28; + TYPE_EDIT_MESSAGE = 29; + TYPE_STATUS_UPDATE = 30; + TYPE_DELETE_MESSAGE = 31; + TYPE_SYNC_INSTALLATION_COMMUNITY = 32; + TYPE_ANONYMOUS_METRIC_BATCH = 33; + } +} diff --git a/packages/status-js/protos copy/application-metadata-message.ts b/packages/status-js/protos copy/application-metadata-message.ts new file mode 100644 index 0000000..2243c72 --- /dev/null +++ b/packages/status-js/protos copy/application-metadata-message.ts @@ -0,0 +1,115 @@ +/* eslint-disable import/export */ +/* eslint-disable @typescript-eslint/no-namespace */ + +import { + enumeration, + encodeMessage, + decodeMessage, + message, + bytes, +} from 'protons-runtime' +import type { Codec } from 'protons-runtime' + +export interface ApplicationMetadataMessage { + signature: Uint8Array + payload: Uint8Array + type: ApplicationMetadataMessage.Type +} + +export namespace ApplicationMetadataMessage { + export enum Type { + TYPE_UNKNOWN_UNSPECIFIED = 'TYPE_UNKNOWN_UNSPECIFIED', + TYPE_CHAT_MESSAGE = 'TYPE_CHAT_MESSAGE', + TYPE_CONTACT_UPDATE = 'TYPE_CONTACT_UPDATE', + TYPE_MEMBERSHIP_UPDATE_MESSAGE = 'TYPE_MEMBERSHIP_UPDATE_MESSAGE', + TYPE_PAIR_INSTALLATION = 'TYPE_PAIR_INSTALLATION', + TYPE_SYNC_INSTALLATION = 'TYPE_SYNC_INSTALLATION', + TYPE_REQUEST_ADDRESS_FOR_TRANSACTION = 'TYPE_REQUEST_ADDRESS_FOR_TRANSACTION', + TYPE_ACCEPT_REQUEST_ADDRESS_FOR_TRANSACTION = 'TYPE_ACCEPT_REQUEST_ADDRESS_FOR_TRANSACTION', + TYPE_DECLINE_REQUEST_ADDRESS_FOR_TRANSACTION = 'TYPE_DECLINE_REQUEST_ADDRESS_FOR_TRANSACTION', + TYPE_REQUEST_TRANSACTION = 'TYPE_REQUEST_TRANSACTION', + TYPE_SEND_TRANSACTION = 'TYPE_SEND_TRANSACTION', + TYPE_DECLINE_REQUEST_TRANSACTION = 'TYPE_DECLINE_REQUEST_TRANSACTION', + TYPE_SYNC_INSTALLATION_CONTACT = 'TYPE_SYNC_INSTALLATION_CONTACT', + TYPE_SYNC_INSTALLATION_ACCOUNT = 'TYPE_SYNC_INSTALLATION_ACCOUNT', + TYPE_SYNC_INSTALLATION_PUBLIC_CHAT = 'TYPE_SYNC_INSTALLATION_PUBLIC_CHAT', + TYPE_CONTACT_CODE_ADVERTISEMENT = 'TYPE_CONTACT_CODE_ADVERTISEMENT', + TYPE_PUSH_NOTIFICATION_REGISTRATION = 'TYPE_PUSH_NOTIFICATION_REGISTRATION', + TYPE_PUSH_NOTIFICATION_REGISTRATION_RESPONSE = 'TYPE_PUSH_NOTIFICATION_REGISTRATION_RESPONSE', + TYPE_PUSH_NOTIFICATION_QUERY = 'TYPE_PUSH_NOTIFICATION_QUERY', + TYPE_PUSH_NOTIFICATION_QUERY_RESPONSE = 'TYPE_PUSH_NOTIFICATION_QUERY_RESPONSE', + TYPE_PUSH_NOTIFICATION_REQUEST = 'TYPE_PUSH_NOTIFICATION_REQUEST', + TYPE_PUSH_NOTIFICATION_RESPONSE = 'TYPE_PUSH_NOTIFICATION_RESPONSE', + TYPE_EMOJI_REACTION = 'TYPE_EMOJI_REACTION', + TYPE_GROUP_CHAT_INVITATION = 'TYPE_GROUP_CHAT_INVITATION', + TYPE_CHAT_IDENTITY = 'TYPE_CHAT_IDENTITY', + TYPE_COMMUNITY_DESCRIPTION = 'TYPE_COMMUNITY_DESCRIPTION', + TYPE_COMMUNITY_INVITATION = 'TYPE_COMMUNITY_INVITATION', + TYPE_COMMUNITY_REQUEST_TO_JOIN = 'TYPE_COMMUNITY_REQUEST_TO_JOIN', + TYPE_PIN_MESSAGE = 'TYPE_PIN_MESSAGE', + TYPE_EDIT_MESSAGE = 'TYPE_EDIT_MESSAGE', + TYPE_STATUS_UPDATE = 'TYPE_STATUS_UPDATE', + TYPE_DELETE_MESSAGE = 'TYPE_DELETE_MESSAGE', + TYPE_SYNC_INSTALLATION_COMMUNITY = 'TYPE_SYNC_INSTALLATION_COMMUNITY', + TYPE_ANONYMOUS_METRIC_BATCH = 'TYPE_ANONYMOUS_METRIC_BATCH', + } + + enum __TypeValues { + TYPE_UNKNOWN_UNSPECIFIED = 0, + TYPE_CHAT_MESSAGE = 1, + TYPE_CONTACT_UPDATE = 2, + TYPE_MEMBERSHIP_UPDATE_MESSAGE = 3, + TYPE_PAIR_INSTALLATION = 4, + TYPE_SYNC_INSTALLATION = 5, + TYPE_REQUEST_ADDRESS_FOR_TRANSACTION = 6, + TYPE_ACCEPT_REQUEST_ADDRESS_FOR_TRANSACTION = 7, + TYPE_DECLINE_REQUEST_ADDRESS_FOR_TRANSACTION = 8, + TYPE_REQUEST_TRANSACTION = 9, + TYPE_SEND_TRANSACTION = 10, + TYPE_DECLINE_REQUEST_TRANSACTION = 11, + TYPE_SYNC_INSTALLATION_CONTACT = 12, + TYPE_SYNC_INSTALLATION_ACCOUNT = 13, + TYPE_SYNC_INSTALLATION_PUBLIC_CHAT = 14, + TYPE_CONTACT_CODE_ADVERTISEMENT = 15, + TYPE_PUSH_NOTIFICATION_REGISTRATION = 16, + TYPE_PUSH_NOTIFICATION_REGISTRATION_RESPONSE = 17, + TYPE_PUSH_NOTIFICATION_QUERY = 18, + TYPE_PUSH_NOTIFICATION_QUERY_RESPONSE = 19, + TYPE_PUSH_NOTIFICATION_REQUEST = 20, + TYPE_PUSH_NOTIFICATION_RESPONSE = 21, + TYPE_EMOJI_REACTION = 22, + TYPE_GROUP_CHAT_INVITATION = 23, + TYPE_CHAT_IDENTITY = 24, + TYPE_COMMUNITY_DESCRIPTION = 25, + TYPE_COMMUNITY_INVITATION = 26, + TYPE_COMMUNITY_REQUEST_TO_JOIN = 27, + TYPE_PIN_MESSAGE = 28, + TYPE_EDIT_MESSAGE = 29, + TYPE_STATUS_UPDATE = 30, + TYPE_DELETE_MESSAGE = 31, + TYPE_SYNC_INSTALLATION_COMMUNITY = 32, + TYPE_ANONYMOUS_METRIC_BATCH = 33, + } + + export namespace Type { + export const codec = () => { + return enumeration(__TypeValues) + } + } + + export const codec = (): Codec => { + return message({ + 1: { name: 'signature', codec: bytes }, + 2: { name: 'payload', codec: bytes }, + 3: { name: 'type', codec: ApplicationMetadataMessage.Type.codec() }, + }) + } + + export const encode = (obj: ApplicationMetadataMessage): Uint8Array => { + return encodeMessage(obj, ApplicationMetadataMessage.codec()) + } + + export const decode = (buf: Uint8Array): ApplicationMetadataMessage => { + return decodeMessage(buf, ApplicationMetadataMessage.codec()) + } +} diff --git a/packages/status-js/protos copy/chat-identity.proto b/packages/status-js/protos copy/chat-identity.proto new file mode 100644 index 0000000..0d60099 --- /dev/null +++ b/packages/status-js/protos copy/chat-identity.proto @@ -0,0 +1,59 @@ +syntax = "proto3"; + +import "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 + string display_name = 4; + + // description is the user set description, valid only for organisations + string description = 5; + + string color = 6; + + string emoji = 7; +} + +// 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; + + // encryption_keys is a list of encrypted keys that can be used to decrypted an encrypted payload + repeated bytes encryption_keys = 4; + + // encrypted signals the encryption state of the payload, default is false. + bool encrypted = 5; + + // 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-js/protos copy/chat-identity.ts b/packages/status-js/protos copy/chat-identity.ts new file mode 100644 index 0000000..8696a67 --- /dev/null +++ b/packages/status-js/protos copy/chat-identity.ts @@ -0,0 +1,139 @@ +/* eslint-disable import/export */ +/* eslint-disable @typescript-eslint/no-namespace */ + +import { + encodeMessage, + decodeMessage, + message, + uint64, + string, + enumeration, + bytes, + bool, +} from 'protons-runtime' +import type { Codec } from 'protons-runtime' + +export interface ChatIdentity { + clock: bigint + ensName: string + images: IdentityImage + displayName: string + description: string + color: string + emoji: string +} + +export namespace ChatIdentity { + export const codec = (): Codec => { + return message({ + 1: { name: 'clock', codec: uint64 }, + 2: { name: 'ensName', codec: string }, + 3: { name: 'images', codec: IdentityImage.codec() }, + 4: { name: 'displayName', codec: string }, + 5: { name: 'description', codec: string }, + 6: { name: 'color', codec: string }, + 7: { name: 'emoji', codec: string }, + }) + } + + export const encode = (obj: ChatIdentity): Uint8Array => { + return encodeMessage(obj, ChatIdentity.codec()) + } + + export const decode = (buf: Uint8Array): ChatIdentity => { + return decodeMessage(buf, ChatIdentity.codec()) + } +} + +export interface IdentityImage { + payload: Uint8Array + sourceType: IdentityImage.SourceType + imageType: ImageType + encryptionKeys: Uint8Array[] + encrypted: boolean +} + +export namespace IdentityImage { + export enum SourceType { + UNKNOWN_SOURCE_TYPE = 'UNKNOWN_SOURCE_TYPE', + RAW_PAYLOAD = 'RAW_PAYLOAD', + ENS_AVATAR = 'ENS_AVATAR', + } + + enum __SourceTypeValues { + UNKNOWN_SOURCE_TYPE = 0, + RAW_PAYLOAD = 1, + ENS_AVATAR = 2, + } + + export namespace SourceType { + export const codec = () => { + return enumeration(__SourceTypeValues) + } + } + + export const codec = (): Codec => { + return message({ + 1: { name: 'payload', codec: bytes }, + 2: { name: 'sourceType', codec: IdentityImage.SourceType.codec() }, + 3: { name: 'imageType', codec: ImageType.codec() }, + 4: { name: 'encryptionKeys', codec: bytes, repeats: true }, + 5: { name: 'encrypted', codec: bool }, + }) + } + + export const encode = (obj: IdentityImage): Uint8Array => { + return encodeMessage(obj, IdentityImage.codec()) + } + + export const decode = (buf: Uint8Array): IdentityImage => { + return decodeMessage(buf, IdentityImage.codec()) + } +} + +export enum MessageType { + UNKNOWN_MESSAGE_TYPE = 'UNKNOWN_MESSAGE_TYPE', + ONE_TO_ONE = 'ONE_TO_ONE', + PUBLIC_GROUP = 'PUBLIC_GROUP', + PRIVATE_GROUP = 'PRIVATE_GROUP', + SYSTEM_MESSAGE_PRIVATE_GROUP = 'SYSTEM_MESSAGE_PRIVATE_GROUP', + COMMUNITY_CHAT = 'COMMUNITY_CHAT', + SYSTEM_MESSAGE_GAP = 'SYSTEM_MESSAGE_GAP', +} + +enum __MessageTypeValues { + UNKNOWN_MESSAGE_TYPE = 0, + ONE_TO_ONE = 1, + PUBLIC_GROUP = 2, + PRIVATE_GROUP = 3, + SYSTEM_MESSAGE_PRIVATE_GROUP = 4, + COMMUNITY_CHAT = 5, + SYSTEM_MESSAGE_GAP = 6, +} + +export namespace MessageType { + export const codec = () => { + return enumeration(__MessageTypeValues) + } +} +export enum ImageType { + UNKNOWN_IMAGE_TYPE = 'UNKNOWN_IMAGE_TYPE', + PNG = 'PNG', + JPEG = 'JPEG', + WEBP = 'WEBP', + GIF = 'GIF', +} + +enum __ImageTypeValues { + UNKNOWN_IMAGE_TYPE = 0, + PNG = 1, + JPEG = 2, + WEBP = 3, + GIF = 4, +} + +export namespace ImageType { + export const codec = () => { + return enumeration(__ImageTypeValues) + } +} diff --git a/packages/status-js/protos copy/chat-message.proto b/packages/status-js/protos copy/chat-message.proto new file mode 100644 index 0000000..9c130ee --- /dev/null +++ b/packages/status-js/protos copy/chat-message.proto @@ -0,0 +1,106 @@ +syntax = "proto3"; + +import "enums.proto"; + +message StickerMessage { + string hash = 1; + int32 pack = 2; +} + +message ImageMessage { + bytes payload = 1; + ImageType type = 2; +} + +message AudioMessage { + bytes payload = 1; + AudioType type = 2; + uint64 duration_ms = 3; + enum AudioType { + UNKNOWN_AUDIO_TYPE = 0; + AAC = 1; + AMR = 2; + } +} + +message EditMessage { + uint64 clock = 1; + // Text of the message + string text = 2; + + string chat_id = 3; + string message_id = 4; + + // Grant for community edit messages + bytes grant = 5; + + // The type of message (public/one-to-one/private-group-chat) + MessageType message_type = 6; +} + +message DeleteMessage { + uint64 clock = 1; + + string chat_id = 2; + string message_id = 3; + + // Grant for community delete messages + bytes grant = 4; + + // The type of message (public/one-to-one/private-group-chat) + MessageType message_type = 5; +} + + +message ChatMessage { + // Lamport timestamp of the chat message + uint64 clock = 1; + // Unix timestamps in milliseconds, currently not used as we use whisper as more reliable, but here + // so that we don't rely on it + uint64 timestamp = 2; + // Text of the message + string text = 3; + // Id of the message that we are replying to + string response_to = 4; + // Ens name of the sender + string ens_name = 5; + // Chat id, this field is symmetric for public-chats and private group chats, + // but asymmetric in case of one-to-ones, as the sender will use the chat-id + // of the received, while the receiver will use the chat-id of the sender. + // Probably should be the concatenation of sender-pk & receiver-pk in alphabetical order + string chat_id = 6; + + // The type of message (public/one-to-one/private-group-chat) + MessageType message_type = 7; + // The type of the content of the message + ContentType content_type = 8; + + oneof payload { + StickerMessage sticker = 9; + ImageMessage image = 10; + AudioMessage audio = 11; + bytes community = 12; + } + + // Grant for community chat messages + bytes grant = 13; + + // Message author's display name, introduced in version 1 + string display_name = 14; + + enum ContentType { + UNKNOWN_CONTENT_TYPE = 0; + TEXT_PLAIN = 1; + STICKER = 2; + STATUS = 3; + EMOJI = 4; + TRANSACTION_COMMAND = 5; + // Only local + SYSTEM_MESSAGE_CONTENT_PRIVATE_GROUP = 6; + IMAGE = 7; + AUDIO = 8; + COMMUNITY = 9; + // Only local + SYSTEM_MESSAGE_GAP = 10; + } +} diff --git a/packages/status-js/protos copy/chat-message.ts b/packages/status-js/protos copy/chat-message.ts new file mode 100644 index 0000000..f854f41 --- /dev/null +++ b/packages/status-js/protos copy/chat-message.ts @@ -0,0 +1,285 @@ +/* eslint-disable import/export */ +/* eslint-disable @typescript-eslint/no-namespace */ + +import { + encodeMessage, + decodeMessage, + message, + string, + int32, + bytes, + enumeration, + uint64, +} from 'protons-runtime' +import type { Codec } from 'protons-runtime' + +export interface StickerMessage { + hash: string + pack: number +} + +export namespace StickerMessage { + export const codec = (): Codec => { + return message({ + 1: { name: 'hash', codec: string }, + 2: { name: 'pack', codec: int32 }, + }) + } + + export const encode = (obj: StickerMessage): Uint8Array => { + return encodeMessage(obj, StickerMessage.codec()) + } + + export const decode = (buf: Uint8Array): StickerMessage => { + return decodeMessage(buf, StickerMessage.codec()) + } +} + +export interface ImageMessage { + payload: Uint8Array + type: ImageType +} + +export namespace ImageMessage { + export const codec = (): Codec => { + return message({ + 1: { name: 'payload', codec: bytes }, + 2: { name: 'type', codec: ImageType.codec() }, + }) + } + + export const encode = (obj: ImageMessage): Uint8Array => { + return encodeMessage(obj, ImageMessage.codec()) + } + + export const decode = (buf: Uint8Array): ImageMessage => { + return decodeMessage(buf, ImageMessage.codec()) + } +} + +export interface AudioMessage { + payload: Uint8Array + type: AudioMessage.AudioType + durationMs: bigint +} + +export namespace AudioMessage { + export enum AudioType { + UNKNOWN_AUDIO_TYPE = 'UNKNOWN_AUDIO_TYPE', + AAC = 'AAC', + AMR = 'AMR', + } + + enum __AudioTypeValues { + UNKNOWN_AUDIO_TYPE = 0, + AAC = 1, + AMR = 2, + } + + export namespace AudioType { + export const codec = () => { + return enumeration(__AudioTypeValues) + } + } + + export const codec = (): Codec => { + return message({ + 1: { name: 'payload', codec: bytes }, + 2: { name: 'type', codec: AudioMessage.AudioType.codec() }, + 3: { name: 'durationMs', codec: uint64 }, + }) + } + + export const encode = (obj: AudioMessage): Uint8Array => { + return encodeMessage(obj, AudioMessage.codec()) + } + + export const decode = (buf: Uint8Array): AudioMessage => { + return decodeMessage(buf, AudioMessage.codec()) + } +} + +export interface EditMessage { + clock: bigint + text: string + chatId: string + messageId: string + grant: Uint8Array + messageType: MessageType +} + +export namespace EditMessage { + export const codec = (): Codec => { + return message({ + 1: { name: 'clock', codec: uint64 }, + 2: { name: 'text', codec: string }, + 3: { name: 'chatId', codec: string }, + 4: { name: 'messageId', codec: string }, + 5: { name: 'grant', codec: bytes }, + 6: { name: 'messageType', codec: MessageType.codec() }, + }) + } + + export const encode = (obj: EditMessage): Uint8Array => { + return encodeMessage(obj, EditMessage.codec()) + } + + export const decode = (buf: Uint8Array): EditMessage => { + return decodeMessage(buf, EditMessage.codec()) + } +} + +export interface DeleteMessage { + clock: bigint + chatId: string + messageId: string + grant: Uint8Array + messageType: MessageType +} + +export namespace DeleteMessage { + export const codec = (): Codec => { + return message({ + 1: { name: 'clock', codec: uint64 }, + 2: { name: 'chatId', codec: string }, + 3: { name: 'messageId', codec: string }, + 4: { name: 'grant', codec: bytes }, + 5: { name: 'messageType', codec: MessageType.codec() }, + }) + } + + export const encode = (obj: DeleteMessage): Uint8Array => { + return encodeMessage(obj, DeleteMessage.codec()) + } + + export const decode = (buf: Uint8Array): DeleteMessage => { + return decodeMessage(buf, DeleteMessage.codec()) + } +} + +export interface ChatMessage { + clock: bigint + timestamp: bigint + text: string + responseTo: string + ensName: string + chatId: string + messageType: MessageType + contentType: ChatMessage.ContentType + sticker: StickerMessage + image: ImageMessage + audio: AudioMessage + community: Uint8Array + grant: Uint8Array + displayName: string +} + +export namespace ChatMessage { + export enum ContentType { + UNKNOWN_CONTENT_TYPE = 'UNKNOWN_CONTENT_TYPE', + TEXT_PLAIN = 'TEXT_PLAIN', + STICKER = 'STICKER', + STATUS = 'STATUS', + EMOJI = 'EMOJI', + TRANSACTION_COMMAND = 'TRANSACTION_COMMAND', + SYSTEM_MESSAGE_CONTENT_PRIVATE_GROUP = 'SYSTEM_MESSAGE_CONTENT_PRIVATE_GROUP', + IMAGE = 'IMAGE', + AUDIO = 'AUDIO', + COMMUNITY = 'COMMUNITY', + SYSTEM_MESSAGE_GAP = 'SYSTEM_MESSAGE_GAP', + } + + enum __ContentTypeValues { + UNKNOWN_CONTENT_TYPE = 0, + TEXT_PLAIN = 1, + STICKER = 2, + STATUS = 3, + EMOJI = 4, + TRANSACTION_COMMAND = 5, + SYSTEM_MESSAGE_CONTENT_PRIVATE_GROUP = 6, + IMAGE = 7, + AUDIO = 8, + COMMUNITY = 9, + SYSTEM_MESSAGE_GAP = 10, + } + + export namespace ContentType { + export const codec = () => { + return enumeration(__ContentTypeValues) + } + } + + export const codec = (): Codec => { + return message({ + 1: { name: 'clock', codec: uint64 }, + 2: { name: 'timestamp', codec: uint64 }, + 3: { name: 'text', codec: string }, + 4: { name: 'responseTo', codec: string }, + 5: { name: 'ensName', codec: string }, + 6: { name: 'chatId', codec: string }, + 7: { name: 'messageType', codec: MessageType.codec() }, + 8: { name: 'contentType', codec: ChatMessage.ContentType.codec() }, + 9: { name: 'sticker', codec: StickerMessage.codec() }, + 10: { name: 'image', codec: ImageMessage.codec() }, + 11: { name: 'audio', codec: AudioMessage.codec() }, + 12: { name: 'community', codec: bytes }, + 13: { name: 'grant', codec: bytes }, + 14: { name: 'displayName', codec: string }, + }) + } + + export const encode = (obj: ChatMessage): Uint8Array => { + return encodeMessage(obj, ChatMessage.codec()) + } + + export const decode = (buf: Uint8Array): ChatMessage => { + return decodeMessage(buf, ChatMessage.codec()) + } +} + +export enum MessageType { + UNKNOWN_MESSAGE_TYPE = 'UNKNOWN_MESSAGE_TYPE', + ONE_TO_ONE = 'ONE_TO_ONE', + PUBLIC_GROUP = 'PUBLIC_GROUP', + PRIVATE_GROUP = 'PRIVATE_GROUP', + SYSTEM_MESSAGE_PRIVATE_GROUP = 'SYSTEM_MESSAGE_PRIVATE_GROUP', + COMMUNITY_CHAT = 'COMMUNITY_CHAT', + SYSTEM_MESSAGE_GAP = 'SYSTEM_MESSAGE_GAP', +} + +enum __MessageTypeValues { + UNKNOWN_MESSAGE_TYPE = 0, + ONE_TO_ONE = 1, + PUBLIC_GROUP = 2, + PRIVATE_GROUP = 3, + SYSTEM_MESSAGE_PRIVATE_GROUP = 4, + COMMUNITY_CHAT = 5, + SYSTEM_MESSAGE_GAP = 6, +} + +export namespace MessageType { + export const codec = () => { + return enumeration(__MessageTypeValues) + } +} +export enum ImageType { + UNKNOWN_IMAGE_TYPE = 'UNKNOWN_IMAGE_TYPE', + PNG = 'PNG', + JPEG = 'JPEG', + WEBP = 'WEBP', + GIF = 'GIF', +} + +enum __ImageTypeValues { + UNKNOWN_IMAGE_TYPE = 0, + PNG = 1, + JPEG = 2, + WEBP = 3, + GIF = 4, +} + +export namespace ImageType { + export const codec = () => { + return enumeration(__ImageTypeValues) + } +} diff --git a/packages/status-js/protos copy/communities.proto b/packages/status-js/protos copy/communities.proto new file mode 100644 index 0000000..a9ec84b --- /dev/null +++ b/packages/status-js/protos copy/communities.proto @@ -0,0 +1,123 @@ +syntax = "proto3"; + +import "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; + uint64 archive_magnetlink_clock = 9; + CommunityAdminSettings admin_settings = 10; +} + +message CommunityAdminSettings { + bool pin_message_all_members_enabled = 1; +} + +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; +} + +message CommunityMessageArchiveMagnetlink { + uint64 clock = 1; + string magnet_uri = 2; +} + +message WakuMessage { + bytes sig = 1; + uint64 timestamp = 2; + bytes topic = 3; + bytes payload = 4; + bytes padding = 5; + bytes hash = 6; +} + +message WakuMessageArchiveMetadata { + uint32 version = 1; + uint64 from = 2; + uint64 to = 3; + repeated bytes contentTopic = 4; +} + +message WakuMessageArchive { + uint32 version = 1; + WakuMessageArchiveMetadata metadata = 2; + repeated WakuMessage messages = 3; +} + +message WakuMessageArchiveIndexMetadata { + uint32 version = 1; + WakuMessageArchiveMetadata metadata = 2; + uint64 offset = 3; + uint64 size = 4; + uint64 padding = 5; +} + +message WakuMessageArchiveIndex { + map archives = 1; +} diff --git a/packages/status-js/protos copy/communities.ts b/packages/status-js/protos copy/communities.ts new file mode 100644 index 0000000..cd7510a --- /dev/null +++ b/packages/status-js/protos copy/communities.ts @@ -0,0 +1,589 @@ +/* eslint-disable import/export */ +/* eslint-disable @typescript-eslint/no-namespace */ + +import { + encodeMessage, + decodeMessage, + message, + bytes, + string, + uint64, + enumeration, + bool, + int32, + uint32, +} from 'protons-runtime' +import type { Codec } from 'protons-runtime' + +export interface Grant { + communityId: Uint8Array + memberId: Uint8Array + chatId: string + clock: bigint +} + +export namespace Grant { + export const codec = (): Codec => { + return message({ + 1: { name: 'communityId', codec: bytes }, + 2: { name: 'memberId', codec: bytes }, + 3: { name: 'chatId', codec: string }, + 4: { name: 'clock', codec: uint64 }, + }) + } + + export const encode = (obj: Grant): Uint8Array => { + return encodeMessage(obj, Grant.codec()) + } + + export const decode = (buf: Uint8Array): Grant => { + return decodeMessage(buf, Grant.codec()) + } +} + +export interface CommunityMember { + roles: CommunityMember.Roles[] +} + +export namespace CommunityMember { + export enum Roles { + UNKNOWN_ROLE = 'UNKNOWN_ROLE', + ROLE_ALL = 'ROLE_ALL', + ROLE_MANAGE_USERS = 'ROLE_MANAGE_USERS', + } + + enum __RolesValues { + UNKNOWN_ROLE = 0, + ROLE_ALL = 1, + ROLE_MANAGE_USERS = 2, + } + + export namespace Roles { + export const codec = () => { + return enumeration(__RolesValues) + } + } + + export const codec = (): Codec => { + return message({ + 1: { name: 'roles', codec: CommunityMember.Roles.codec(), repeats: true }, + }) + } + + export const encode = (obj: CommunityMember): Uint8Array => { + return encodeMessage(obj, CommunityMember.codec()) + } + + export const decode = (buf: Uint8Array): CommunityMember => { + return decodeMessage(buf, CommunityMember.codec()) + } +} + +export interface CommunityPermissions { + ensOnly: boolean + private: boolean + access: CommunityPermissions.Access +} + +export namespace CommunityPermissions { + export enum Access { + UNKNOWN_ACCESS = 'UNKNOWN_ACCESS', + NO_MEMBERSHIP = 'NO_MEMBERSHIP', + INVITATION_ONLY = 'INVITATION_ONLY', + ON_REQUEST = 'ON_REQUEST', + } + + enum __AccessValues { + UNKNOWN_ACCESS = 0, + NO_MEMBERSHIP = 1, + INVITATION_ONLY = 2, + ON_REQUEST = 3, + } + + export namespace Access { + export const codec = () => { + return enumeration(__AccessValues) + } + } + + export const codec = (): Codec => { + return message({ + 1: { name: 'ensOnly', codec: bool }, + 2: { name: 'private', codec: bool }, + 3: { name: 'access', codec: CommunityPermissions.Access.codec() }, + }) + } + + export const encode = (obj: CommunityPermissions): Uint8Array => { + return encodeMessage(obj, CommunityPermissions.codec()) + } + + export const decode = (buf: Uint8Array): CommunityPermissions => { + return decodeMessage(buf, CommunityPermissions.codec()) + } +} + +export interface CommunityDescription { + clock: bigint + members: CommunityMember + permissions: CommunityPermissions + identity: ChatIdentity + chats: CommunityChat + banList: string[] + categories: CommunityCategory + archiveMagnetlinkClock: bigint + adminSettings: CommunityAdminSettings +} + +export namespace CommunityDescription { + export const codec = (): Codec => { + return message({ + 1: { name: 'clock', codec: uint64 }, + 2: { name: 'members', codec: CommunityMember.codec() }, + 3: { name: 'permissions', codec: CommunityPermissions.codec() }, + 5: { name: 'identity', codec: ChatIdentity.codec() }, + 6: { name: 'chats', codec: CommunityChat.codec() }, + 7: { name: 'banList', codec: string, repeats: true }, + 8: { name: 'categories', codec: CommunityCategory.codec() }, + 9: { name: 'archiveMagnetlinkClock', codec: uint64 }, + 10: { name: 'adminSettings', codec: CommunityAdminSettings.codec() }, + }) + } + + export const encode = (obj: CommunityDescription): Uint8Array => { + return encodeMessage(obj, CommunityDescription.codec()) + } + + export const decode = (buf: Uint8Array): CommunityDescription => { + return decodeMessage(buf, CommunityDescription.codec()) + } +} + +export interface CommunityAdminSettings { + pinMessageAllMembersEnabled: boolean +} + +export namespace CommunityAdminSettings { + export const codec = (): Codec => { + return message({ + 1: { name: 'pinMessageAllMembersEnabled', codec: bool }, + }) + } + + export const encode = (obj: CommunityAdminSettings): Uint8Array => { + return encodeMessage(obj, CommunityAdminSettings.codec()) + } + + export const decode = (buf: Uint8Array): CommunityAdminSettings => { + return decodeMessage(buf, CommunityAdminSettings.codec()) + } +} + +export interface CommunityChat { + members: CommunityMember + permissions: CommunityPermissions + identity: ChatIdentity + categoryId: string + position: number +} + +export namespace CommunityChat { + export const codec = (): Codec => { + return message({ + 1: { name: 'members', codec: CommunityMember.codec() }, + 2: { name: 'permissions', codec: CommunityPermissions.codec() }, + 3: { name: 'identity', codec: ChatIdentity.codec() }, + 4: { name: 'categoryId', codec: string }, + 5: { name: 'position', codec: int32 }, + }) + } + + export const encode = (obj: CommunityChat): Uint8Array => { + return encodeMessage(obj, CommunityChat.codec()) + } + + export const decode = (buf: Uint8Array): CommunityChat => { + return decodeMessage(buf, CommunityChat.codec()) + } +} + +export interface CommunityCategory { + categoryId: string + name: string + position: number +} + +export namespace CommunityCategory { + export const codec = (): Codec => { + return message({ + 1: { name: 'categoryId', codec: string }, + 2: { name: 'name', codec: string }, + 3: { name: 'position', codec: int32 }, + }) + } + + export const encode = (obj: CommunityCategory): Uint8Array => { + return encodeMessage(obj, CommunityCategory.codec()) + } + + export const decode = (buf: Uint8Array): CommunityCategory => { + return decodeMessage(buf, CommunityCategory.codec()) + } +} + +export interface CommunityInvitation { + communityDescription: Uint8Array + grant: Uint8Array + chatId: string + publicKey: Uint8Array +} + +export namespace CommunityInvitation { + export const codec = (): Codec => { + return message({ + 1: { name: 'communityDescription', codec: bytes }, + 2: { name: 'grant', codec: bytes }, + 3: { name: 'chatId', codec: string }, + 4: { name: 'publicKey', codec: bytes }, + }) + } + + export const encode = (obj: CommunityInvitation): Uint8Array => { + return encodeMessage(obj, CommunityInvitation.codec()) + } + + export const decode = (buf: Uint8Array): CommunityInvitation => { + return decodeMessage(buf, CommunityInvitation.codec()) + } +} + +export interface CommunityRequestToJoin { + clock: bigint + ensName: string + chatId: string + communityId: Uint8Array +} + +export namespace CommunityRequestToJoin { + export const codec = (): Codec => { + return message({ + 1: { name: 'clock', codec: uint64 }, + 2: { name: 'ensName', codec: string }, + 3: { name: 'chatId', codec: string }, + 4: { name: 'communityId', codec: bytes }, + }) + } + + export const encode = (obj: CommunityRequestToJoin): Uint8Array => { + return encodeMessage(obj, CommunityRequestToJoin.codec()) + } + + export const decode = (buf: Uint8Array): CommunityRequestToJoin => { + return decodeMessage(buf, CommunityRequestToJoin.codec()) + } +} + +export interface CommunityRequestToJoinResponse { + clock: bigint + community: CommunityDescription + accepted: boolean + grant: Uint8Array +} + +export namespace CommunityRequestToJoinResponse { + export const codec = (): Codec => { + return message({ + 1: { name: 'clock', codec: uint64 }, + 2: { name: 'community', codec: CommunityDescription.codec() }, + 3: { name: 'accepted', codec: bool }, + 4: { name: 'grant', codec: bytes }, + }) + } + + export const encode = (obj: CommunityRequestToJoinResponse): Uint8Array => { + return encodeMessage(obj, CommunityRequestToJoinResponse.codec()) + } + + export const decode = (buf: Uint8Array): CommunityRequestToJoinResponse => { + return decodeMessage(buf, CommunityRequestToJoinResponse.codec()) + } +} + +export interface CommunityMessageArchiveMagnetlink { + clock: bigint + magnetUri: string +} + +export namespace CommunityMessageArchiveMagnetlink { + export const codec = (): Codec => { + return message({ + 1: { name: 'clock', codec: uint64 }, + 2: { name: 'magnetUri', codec: string }, + }) + } + + export const encode = ( + obj: CommunityMessageArchiveMagnetlink + ): Uint8Array => { + return encodeMessage(obj, CommunityMessageArchiveMagnetlink.codec()) + } + + export const decode = ( + buf: Uint8Array + ): CommunityMessageArchiveMagnetlink => { + return decodeMessage(buf, CommunityMessageArchiveMagnetlink.codec()) + } +} + +export interface WakuMessage { + sig: Uint8Array + timestamp: bigint + topic: Uint8Array + payload: Uint8Array + padding: Uint8Array + hash: Uint8Array +} + +export namespace WakuMessage { + export const codec = (): Codec => { + return message({ + 1: { name: 'sig', codec: bytes }, + 2: { name: 'timestamp', codec: uint64 }, + 3: { name: 'topic', codec: bytes }, + 4: { name: 'payload', codec: bytes }, + 5: { name: 'padding', codec: bytes }, + 6: { name: 'hash', codec: bytes }, + }) + } + + export const encode = (obj: WakuMessage): Uint8Array => { + return encodeMessage(obj, WakuMessage.codec()) + } + + export const decode = (buf: Uint8Array): WakuMessage => { + return decodeMessage(buf, WakuMessage.codec()) + } +} + +export interface WakuMessageArchiveMetadata { + version: number + from: bigint + to: bigint + contentTopic: Uint8Array[] +} + +export namespace WakuMessageArchiveMetadata { + export const codec = (): Codec => { + return message({ + 1: { name: 'version', codec: uint32 }, + 2: { name: 'from', codec: uint64 }, + 3: { name: 'to', codec: uint64 }, + 4: { name: 'contentTopic', codec: bytes, repeats: true }, + }) + } + + export const encode = (obj: WakuMessageArchiveMetadata): Uint8Array => { + return encodeMessage(obj, WakuMessageArchiveMetadata.codec()) + } + + export const decode = (buf: Uint8Array): WakuMessageArchiveMetadata => { + return decodeMessage(buf, WakuMessageArchiveMetadata.codec()) + } +} + +export interface WakuMessageArchive { + version: number + metadata: WakuMessageArchiveMetadata + messages: WakuMessage[] +} + +export namespace WakuMessageArchive { + export const codec = (): Codec => { + return message({ + 1: { name: 'version', codec: uint32 }, + 2: { name: 'metadata', codec: WakuMessageArchiveMetadata.codec() }, + 3: { name: 'messages', codec: WakuMessage.codec(), repeats: true }, + }) + } + + export const encode = (obj: WakuMessageArchive): Uint8Array => { + return encodeMessage(obj, WakuMessageArchive.codec()) + } + + export const decode = (buf: Uint8Array): WakuMessageArchive => { + return decodeMessage(buf, WakuMessageArchive.codec()) + } +} + +export interface WakuMessageArchiveIndexMetadata { + version: number + metadata: WakuMessageArchiveMetadata + offset: bigint + size: bigint + padding: bigint +} + +export namespace WakuMessageArchiveIndexMetadata { + export const codec = (): Codec => { + return message({ + 1: { name: 'version', codec: uint32 }, + 2: { name: 'metadata', codec: WakuMessageArchiveMetadata.codec() }, + 3: { name: 'offset', codec: uint64 }, + 4: { name: 'size', codec: uint64 }, + 5: { name: 'padding', codec: uint64 }, + }) + } + + export const encode = (obj: WakuMessageArchiveIndexMetadata): Uint8Array => { + return encodeMessage(obj, WakuMessageArchiveIndexMetadata.codec()) + } + + export const decode = (buf: Uint8Array): WakuMessageArchiveIndexMetadata => { + return decodeMessage(buf, WakuMessageArchiveIndexMetadata.codec()) + } +} + +export interface WakuMessageArchiveIndex { + archives: WakuMessageArchiveIndexMetadata +} + +export namespace WakuMessageArchiveIndex { + export const codec = (): Codec => { + return message({ + 1: { name: 'archives', codec: WakuMessageArchiveIndexMetadata.codec() }, + }) + } + + export const encode = (obj: WakuMessageArchiveIndex): Uint8Array => { + return encodeMessage(obj, WakuMessageArchiveIndex.codec()) + } + + export const decode = (buf: Uint8Array): WakuMessageArchiveIndex => { + return decodeMessage(buf, WakuMessageArchiveIndex.codec()) + } +} + +export interface ChatIdentity { + clock: bigint + ensName: string + images: IdentityImage + displayName: string + description: string + color: string + emoji: string +} + +export namespace ChatIdentity { + export const codec = (): Codec => { + return message({ + 1: { name: 'clock', codec: uint64 }, + 2: { name: 'ensName', codec: string }, + 3: { name: 'images', codec: IdentityImage.codec() }, + 4: { name: 'displayName', codec: string }, + 5: { name: 'description', codec: string }, + 6: { name: 'color', codec: string }, + 7: { name: 'emoji', codec: string }, + }) + } + + export const encode = (obj: ChatIdentity): Uint8Array => { + return encodeMessage(obj, ChatIdentity.codec()) + } + + export const decode = (buf: Uint8Array): ChatIdentity => { + return decodeMessage(buf, ChatIdentity.codec()) + } +} + +export interface IdentityImage { + payload: Uint8Array + sourceType: IdentityImage.SourceType + imageType: ImageType + encryptionKeys: Uint8Array[] + encrypted: boolean +} + +export namespace IdentityImage { + export enum SourceType { + UNKNOWN_SOURCE_TYPE = 'UNKNOWN_SOURCE_TYPE', + RAW_PAYLOAD = 'RAW_PAYLOAD', + ENS_AVATAR = 'ENS_AVATAR', + } + + enum __SourceTypeValues { + UNKNOWN_SOURCE_TYPE = 0, + RAW_PAYLOAD = 1, + ENS_AVATAR = 2, + } + + export namespace SourceType { + export const codec = () => { + return enumeration(__SourceTypeValues) + } + } + + export const codec = (): Codec => { + return message({ + 1: { name: 'payload', codec: bytes }, + 2: { name: 'sourceType', codec: IdentityImage.SourceType.codec() }, + 3: { name: 'imageType', codec: ImageType.codec() }, + 4: { name: 'encryptionKeys', codec: bytes, repeats: true }, + 5: { name: 'encrypted', codec: bool }, + }) + } + + export const encode = (obj: IdentityImage): Uint8Array => { + return encodeMessage(obj, IdentityImage.codec()) + } + + export const decode = (buf: Uint8Array): IdentityImage => { + return decodeMessage(buf, IdentityImage.codec()) + } +} + +export enum MessageType { + UNKNOWN_MESSAGE_TYPE = 'UNKNOWN_MESSAGE_TYPE', + ONE_TO_ONE = 'ONE_TO_ONE', + PUBLIC_GROUP = 'PUBLIC_GROUP', + PRIVATE_GROUP = 'PRIVATE_GROUP', + SYSTEM_MESSAGE_PRIVATE_GROUP = 'SYSTEM_MESSAGE_PRIVATE_GROUP', + COMMUNITY_CHAT = 'COMMUNITY_CHAT', + SYSTEM_MESSAGE_GAP = 'SYSTEM_MESSAGE_GAP', +} + +enum __MessageTypeValues { + UNKNOWN_MESSAGE_TYPE = 0, + ONE_TO_ONE = 1, + PUBLIC_GROUP = 2, + PRIVATE_GROUP = 3, + SYSTEM_MESSAGE_PRIVATE_GROUP = 4, + COMMUNITY_CHAT = 5, + SYSTEM_MESSAGE_GAP = 6, +} + +export namespace MessageType { + export const codec = () => { + return enumeration(__MessageTypeValues) + } +} +export enum ImageType { + UNKNOWN_IMAGE_TYPE = 'UNKNOWN_IMAGE_TYPE', + PNG = 'PNG', + JPEG = 'JPEG', + WEBP = 'WEBP', + GIF = 'GIF', +} + +enum __ImageTypeValues { + UNKNOWN_IMAGE_TYPE = 0, + PNG = 1, + JPEG = 2, + WEBP = 3, + GIF = 4, +} + +export namespace ImageType { + export const codec = () => { + return enumeration(__ImageTypeValues) + } +} diff --git a/packages/status-js/protos copy/emoji-reaction.proto b/packages/status-js/protos copy/emoji-reaction.proto new file mode 100644 index 0000000..f1652c7 --- /dev/null +++ b/packages/status-js/protos copy/emoji-reaction.proto @@ -0,0 +1,37 @@ +syntax = "proto3"; + +import "enums.proto"; + +message EmojiReaction { + // clock Lamport timestamp of the chat message + uint64 clock = 1; + + // chat_id the ID of the chat the message belongs to, for query efficiency the chat_id is stored in the db even though the + // target message also stores the chat_id + string chat_id = 2; + + // message_id the ID of the target message that the user wishes to react to + string message_id = 3; + + // message_type is (somewhat confusingly) the ID of the type of chat the message belongs to + MessageType message_type = 4; + + // type the ID of the emoji the user wishes to react with + Type type = 5; + + enum Type { + UNKNOWN_EMOJI_REACTION_TYPE = 0; + LOVE = 1; + THUMBS_UP = 2; + THUMBS_DOWN = 3; + LAUGH = 4; + SAD = 5; + ANGRY = 6; + } + + // whether this is a rectraction of a previously sent emoji + bool retracted = 6; + + // Grant for organisation chat messages + bytes grant = 7; +} diff --git a/packages/status-js/protos copy/emoji-reaction.ts b/packages/status-js/protos copy/emoji-reaction.ts new file mode 100644 index 0000000..b8c6980 --- /dev/null +++ b/packages/status-js/protos copy/emoji-reaction.ts @@ -0,0 +1,119 @@ +/* eslint-disable import/export */ +/* eslint-disable @typescript-eslint/no-namespace */ + +import { + enumeration, + encodeMessage, + decodeMessage, + message, + uint64, + string, + bool, + bytes, +} from 'protons-runtime' +import type { Codec } from 'protons-runtime' + +export interface EmojiReaction { + clock: bigint + chatId: string + messageId: string + messageType: MessageType + type: EmojiReaction.Type + retracted: boolean + grant: Uint8Array +} + +export namespace EmojiReaction { + export enum Type { + UNKNOWN_EMOJI_REACTION_TYPE = 'UNKNOWN_EMOJI_REACTION_TYPE', + LOVE = 'LOVE', + THUMBS_UP = 'THUMBS_UP', + THUMBS_DOWN = 'THUMBS_DOWN', + LAUGH = 'LAUGH', + SAD = 'SAD', + ANGRY = 'ANGRY', + } + + enum __TypeValues { + UNKNOWN_EMOJI_REACTION_TYPE = 0, + LOVE = 1, + THUMBS_UP = 2, + THUMBS_DOWN = 3, + LAUGH = 4, + SAD = 5, + ANGRY = 6, + } + + export namespace Type { + export const codec = () => { + return enumeration(__TypeValues) + } + } + + export const codec = (): Codec => { + return message({ + 1: { name: 'clock', codec: uint64 }, + 2: { name: 'chatId', codec: string }, + 3: { name: 'messageId', codec: string }, + 4: { name: 'messageType', codec: MessageType.codec() }, + 5: { name: 'type', codec: EmojiReaction.Type.codec() }, + 6: { name: 'retracted', codec: bool }, + 7: { name: 'grant', codec: bytes }, + }) + } + + export const encode = (obj: EmojiReaction): Uint8Array => { + return encodeMessage(obj, EmojiReaction.codec()) + } + + export const decode = (buf: Uint8Array): EmojiReaction => { + return decodeMessage(buf, EmojiReaction.codec()) + } +} + +export enum MessageType { + UNKNOWN_MESSAGE_TYPE = 'UNKNOWN_MESSAGE_TYPE', + ONE_TO_ONE = 'ONE_TO_ONE', + PUBLIC_GROUP = 'PUBLIC_GROUP', + PRIVATE_GROUP = 'PRIVATE_GROUP', + SYSTEM_MESSAGE_PRIVATE_GROUP = 'SYSTEM_MESSAGE_PRIVATE_GROUP', + COMMUNITY_CHAT = 'COMMUNITY_CHAT', + SYSTEM_MESSAGE_GAP = 'SYSTEM_MESSAGE_GAP', +} + +enum __MessageTypeValues { + UNKNOWN_MESSAGE_TYPE = 0, + ONE_TO_ONE = 1, + PUBLIC_GROUP = 2, + PRIVATE_GROUP = 3, + SYSTEM_MESSAGE_PRIVATE_GROUP = 4, + COMMUNITY_CHAT = 5, + SYSTEM_MESSAGE_GAP = 6, +} + +export namespace MessageType { + export const codec = () => { + return enumeration(__MessageTypeValues) + } +} +export enum ImageType { + UNKNOWN_IMAGE_TYPE = 'UNKNOWN_IMAGE_TYPE', + PNG = 'PNG', + JPEG = 'JPEG', + WEBP = 'WEBP', + GIF = 'GIF', +} + +enum __ImageTypeValues { + UNKNOWN_IMAGE_TYPE = 0, + PNG = 1, + JPEG = 2, + WEBP = 3, + GIF = 4, +} + +export namespace ImageType { + export const codec = () => { + return enumeration(__ImageTypeValues) + } +} diff --git a/packages/status-js/protos copy/enums.proto b/packages/status-js/protos copy/enums.proto new file mode 100644 index 0000000..b58762e --- /dev/null +++ b/packages/status-js/protos copy/enums.proto @@ -0,0 +1,23 @@ +syntax = "proto3"; + +enum MessageType { + UNKNOWN_MESSAGE_TYPE = 0; + ONE_TO_ONE = 1; + PUBLIC_GROUP = 2; + PRIVATE_GROUP = 3; + // Only local + SYSTEM_MESSAGE_PRIVATE_GROUP = 4; + COMMUNITY_CHAT = 5; + // Only local + SYSTEM_MESSAGE_GAP = 6; +} + +enum ImageType { + UNKNOWN_IMAGE_TYPE = 0; + + // Raster image files is payload data that can be read as a raster image + PNG = 1; + JPEG = 2; + WEBP = 3; + GIF = 4; +} diff --git a/packages/status-js/protos copy/enums.ts b/packages/status-js/protos copy/enums.ts new file mode 100644 index 0000000..3446b90 --- /dev/null +++ b/packages/status-js/protos copy/enums.ts @@ -0,0 +1,51 @@ +/* eslint-disable import/export */ +/* eslint-disable @typescript-eslint/no-namespace */ + +import { enumeration } from 'protons-runtime' + +export enum MessageType { + UNKNOWN_MESSAGE_TYPE = 'UNKNOWN_MESSAGE_TYPE', + ONE_TO_ONE = 'ONE_TO_ONE', + PUBLIC_GROUP = 'PUBLIC_GROUP', + PRIVATE_GROUP = 'PRIVATE_GROUP', + SYSTEM_MESSAGE_PRIVATE_GROUP = 'SYSTEM_MESSAGE_PRIVATE_GROUP', + COMMUNITY_CHAT = 'COMMUNITY_CHAT', + SYSTEM_MESSAGE_GAP = 'SYSTEM_MESSAGE_GAP', +} + +enum __MessageTypeValues { + UNKNOWN_MESSAGE_TYPE = 0, + ONE_TO_ONE = 1, + PUBLIC_GROUP = 2, + PRIVATE_GROUP = 3, + SYSTEM_MESSAGE_PRIVATE_GROUP = 4, + COMMUNITY_CHAT = 5, + SYSTEM_MESSAGE_GAP = 6, +} + +export namespace MessageType { + export const codec = () => { + return enumeration(__MessageTypeValues) + } +} +export enum ImageType { + UNKNOWN_IMAGE_TYPE = 'UNKNOWN_IMAGE_TYPE', + PNG = 'PNG', + JPEG = 'JPEG', + WEBP = 'WEBP', + GIF = 'GIF', +} + +enum __ImageTypeValues { + UNKNOWN_IMAGE_TYPE = 0, + PNG = 1, + JPEG = 2, + WEBP = 3, + GIF = 4, +} + +export namespace ImageType { + export const codec = () => { + return enumeration(__ImageTypeValues) + } +} diff --git a/packages/status-js/protos copy/membership-update-message.proto b/packages/status-js/protos copy/membership-update-message.proto new file mode 100644 index 0000000..01aafeb --- /dev/null +++ b/packages/status-js/protos copy/membership-update-message.proto @@ -0,0 +1,43 @@ +syntax = "proto3"; + +import "chat-message.proto"; +import "emoji-reaction.proto"; + +message MembershipUpdateEvent { + // Lamport timestamp of the event + uint64 clock = 1; + // List of public keys of objects of the action + repeated string members = 2; + // Name of the chat for the CHAT_CREATED/NAME_CHANGED event types + string name = 3; + // The type of the event + EventType type = 4; + + enum EventType { + UNKNOWN = 0; + CHAT_CREATED = 1; + NAME_CHANGED = 2; + MEMBERS_ADDED = 3; + MEMBER_JOINED = 4; + MEMBER_REMOVED = 5; + ADMINS_ADDED = 6; + ADMIN_REMOVED = 7; + } +} + +// MembershipUpdateMessage is a message used to propagate information +// about group membership changes. +// For more information, see https://github.com/status-im/specs/blob/master/status-group-chats-spec.md. +message MembershipUpdateMessage { + // The chat id of the private group chat + string chat_id = 1; + // A list of events for this group chat, first x bytes are the signature, then is a + // protobuf encoded MembershipUpdateEvent + repeated bytes events = 2; + + // An optional chat message + oneof chat_entity { + ChatMessage message = 3; + EmojiReaction emoji_reaction = 4; + } +} diff --git a/packages/status-js/protos copy/membership-update-message.ts b/packages/status-js/protos copy/membership-update-message.ts new file mode 100644 index 0000000..ebd6d7e --- /dev/null +++ b/packages/status-js/protos copy/membership-update-message.ts @@ -0,0 +1,423 @@ +/* eslint-disable import/export */ +/* eslint-disable @typescript-eslint/no-namespace */ + +import { + enumeration, + encodeMessage, + decodeMessage, + message, + uint64, + string, + bytes, + int32, + bool, +} from 'protons-runtime' +import type { Codec } from 'protons-runtime' + +export interface MembershipUpdateEvent { + clock: bigint + members: string[] + name: string + type: MembershipUpdateEvent.EventType +} + +export namespace MembershipUpdateEvent { + export enum EventType { + UNKNOWN = 'UNKNOWN', + CHAT_CREATED = 'CHAT_CREATED', + NAME_CHANGED = 'NAME_CHANGED', + MEMBERS_ADDED = 'MEMBERS_ADDED', + MEMBER_JOINED = 'MEMBER_JOINED', + MEMBER_REMOVED = 'MEMBER_REMOVED', + ADMINS_ADDED = 'ADMINS_ADDED', + ADMIN_REMOVED = 'ADMIN_REMOVED', + } + + enum __EventTypeValues { + UNKNOWN = 0, + CHAT_CREATED = 1, + NAME_CHANGED = 2, + MEMBERS_ADDED = 3, + MEMBER_JOINED = 4, + MEMBER_REMOVED = 5, + ADMINS_ADDED = 6, + ADMIN_REMOVED = 7, + } + + export namespace EventType { + export const codec = () => { + return enumeration(__EventTypeValues) + } + } + + export const codec = (): Codec => { + return message({ + 1: { name: 'clock', codec: uint64 }, + 2: { name: 'members', codec: string, repeats: true }, + 3: { name: 'name', codec: string }, + 4: { name: 'type', codec: MembershipUpdateEvent.EventType.codec() }, + }) + } + + export const encode = (obj: MembershipUpdateEvent): Uint8Array => { + return encodeMessage(obj, MembershipUpdateEvent.codec()) + } + + export const decode = (buf: Uint8Array): MembershipUpdateEvent => { + return decodeMessage(buf, MembershipUpdateEvent.codec()) + } +} + +export interface MembershipUpdateMessage { + chatId: string + events: Uint8Array[] + message: ChatMessage + emojiReaction: EmojiReaction +} + +export namespace MembershipUpdateMessage { + export const codec = (): Codec => { + return message({ + 1: { name: 'chatId', codec: string }, + 2: { name: 'events', codec: bytes, repeats: true }, + 3: { name: 'message', codec: ChatMessage.codec() }, + 4: { name: 'emojiReaction', codec: EmojiReaction.codec() }, + }) + } + + export const encode = (obj: MembershipUpdateMessage): Uint8Array => { + return encodeMessage(obj, MembershipUpdateMessage.codec()) + } + + export const decode = (buf: Uint8Array): MembershipUpdateMessage => { + return decodeMessage(buf, MembershipUpdateMessage.codec()) + } +} + +export interface StickerMessage { + hash: string + pack: number +} + +export namespace StickerMessage { + export const codec = (): Codec => { + return message({ + 1: { name: 'hash', codec: string }, + 2: { name: 'pack', codec: int32 }, + }) + } + + export const encode = (obj: StickerMessage): Uint8Array => { + return encodeMessage(obj, StickerMessage.codec()) + } + + export const decode = (buf: Uint8Array): StickerMessage => { + return decodeMessage(buf, StickerMessage.codec()) + } +} + +export interface ImageMessage { + payload: Uint8Array + type: ImageType +} + +export namespace ImageMessage { + export const codec = (): Codec => { + return message({ + 1: { name: 'payload', codec: bytes }, + 2: { name: 'type', codec: ImageType.codec() }, + }) + } + + export const encode = (obj: ImageMessage): Uint8Array => { + return encodeMessage(obj, ImageMessage.codec()) + } + + export const decode = (buf: Uint8Array): ImageMessage => { + return decodeMessage(buf, ImageMessage.codec()) + } +} + +export interface AudioMessage { + payload: Uint8Array + type: AudioMessage.AudioType + durationMs: bigint +} + +export namespace AudioMessage { + export enum AudioType { + UNKNOWN_AUDIO_TYPE = 'UNKNOWN_AUDIO_TYPE', + AAC = 'AAC', + AMR = 'AMR', + } + + enum __AudioTypeValues { + UNKNOWN_AUDIO_TYPE = 0, + AAC = 1, + AMR = 2, + } + + export namespace AudioType { + export const codec = () => { + return enumeration(__AudioTypeValues) + } + } + + export const codec = (): Codec => { + return message({ + 1: { name: 'payload', codec: bytes }, + 2: { name: 'type', codec: AudioMessage.AudioType.codec() }, + 3: { name: 'durationMs', codec: uint64 }, + }) + } + + export const encode = (obj: AudioMessage): Uint8Array => { + return encodeMessage(obj, AudioMessage.codec()) + } + + export const decode = (buf: Uint8Array): AudioMessage => { + return decodeMessage(buf, AudioMessage.codec()) + } +} + +export interface EditMessage { + clock: bigint + text: string + chatId: string + messageId: string + grant: Uint8Array + messageType: MessageType +} + +export namespace EditMessage { + export const codec = (): Codec => { + return message({ + 1: { name: 'clock', codec: uint64 }, + 2: { name: 'text', codec: string }, + 3: { name: 'chatId', codec: string }, + 4: { name: 'messageId', codec: string }, + 5: { name: 'grant', codec: bytes }, + 6: { name: 'messageType', codec: MessageType.codec() }, + }) + } + + export const encode = (obj: EditMessage): Uint8Array => { + return encodeMessage(obj, EditMessage.codec()) + } + + export const decode = (buf: Uint8Array): EditMessage => { + return decodeMessage(buf, EditMessage.codec()) + } +} + +export interface DeleteMessage { + clock: bigint + chatId: string + messageId: string + grant: Uint8Array + messageType: MessageType +} + +export namespace DeleteMessage { + export const codec = (): Codec => { + return message({ + 1: { name: 'clock', codec: uint64 }, + 2: { name: 'chatId', codec: string }, + 3: { name: 'messageId', codec: string }, + 4: { name: 'grant', codec: bytes }, + 5: { name: 'messageType', codec: MessageType.codec() }, + }) + } + + export const encode = (obj: DeleteMessage): Uint8Array => { + return encodeMessage(obj, DeleteMessage.codec()) + } + + export const decode = (buf: Uint8Array): DeleteMessage => { + return decodeMessage(buf, DeleteMessage.codec()) + } +} + +export interface ChatMessage { + clock: bigint + timestamp: bigint + text: string + responseTo: string + ensName: string + chatId: string + messageType: MessageType + contentType: ChatMessage.ContentType + sticker: StickerMessage + image: ImageMessage + audio: AudioMessage + community: Uint8Array + grant: Uint8Array + displayName: string +} + +export namespace ChatMessage { + export enum ContentType { + UNKNOWN_CONTENT_TYPE = 'UNKNOWN_CONTENT_TYPE', + TEXT_PLAIN = 'TEXT_PLAIN', + STICKER = 'STICKER', + STATUS = 'STATUS', + EMOJI = 'EMOJI', + TRANSACTION_COMMAND = 'TRANSACTION_COMMAND', + SYSTEM_MESSAGE_CONTENT_PRIVATE_GROUP = 'SYSTEM_MESSAGE_CONTENT_PRIVATE_GROUP', + IMAGE = 'IMAGE', + AUDIO = 'AUDIO', + COMMUNITY = 'COMMUNITY', + SYSTEM_MESSAGE_GAP = 'SYSTEM_MESSAGE_GAP', + } + + enum __ContentTypeValues { + UNKNOWN_CONTENT_TYPE = 0, + TEXT_PLAIN = 1, + STICKER = 2, + STATUS = 3, + EMOJI = 4, + TRANSACTION_COMMAND = 5, + SYSTEM_MESSAGE_CONTENT_PRIVATE_GROUP = 6, + IMAGE = 7, + AUDIO = 8, + COMMUNITY = 9, + SYSTEM_MESSAGE_GAP = 10, + } + + export namespace ContentType { + export const codec = () => { + return enumeration(__ContentTypeValues) + } + } + + export const codec = (): Codec => { + return message({ + 1: { name: 'clock', codec: uint64 }, + 2: { name: 'timestamp', codec: uint64 }, + 3: { name: 'text', codec: string }, + 4: { name: 'responseTo', codec: string }, + 5: { name: 'ensName', codec: string }, + 6: { name: 'chatId', codec: string }, + 7: { name: 'messageType', codec: MessageType.codec() }, + 8: { name: 'contentType', codec: ChatMessage.ContentType.codec() }, + 9: { name: 'sticker', codec: StickerMessage.codec() }, + 10: { name: 'image', codec: ImageMessage.codec() }, + 11: { name: 'audio', codec: AudioMessage.codec() }, + 12: { name: 'community', codec: bytes }, + 13: { name: 'grant', codec: bytes }, + 14: { name: 'displayName', codec: string }, + }) + } + + export const encode = (obj: ChatMessage): Uint8Array => { + return encodeMessage(obj, ChatMessage.codec()) + } + + export const decode = (buf: Uint8Array): ChatMessage => { + return decodeMessage(buf, ChatMessage.codec()) + } +} + +export enum MessageType { + UNKNOWN_MESSAGE_TYPE = 'UNKNOWN_MESSAGE_TYPE', + ONE_TO_ONE = 'ONE_TO_ONE', + PUBLIC_GROUP = 'PUBLIC_GROUP', + PRIVATE_GROUP = 'PRIVATE_GROUP', + SYSTEM_MESSAGE_PRIVATE_GROUP = 'SYSTEM_MESSAGE_PRIVATE_GROUP', + COMMUNITY_CHAT = 'COMMUNITY_CHAT', + SYSTEM_MESSAGE_GAP = 'SYSTEM_MESSAGE_GAP', +} + +enum __MessageTypeValues { + UNKNOWN_MESSAGE_TYPE = 0, + ONE_TO_ONE = 1, + PUBLIC_GROUP = 2, + PRIVATE_GROUP = 3, + SYSTEM_MESSAGE_PRIVATE_GROUP = 4, + COMMUNITY_CHAT = 5, + SYSTEM_MESSAGE_GAP = 6, +} + +export namespace MessageType { + export const codec = () => { + return enumeration(__MessageTypeValues) + } +} +export enum ImageType { + UNKNOWN_IMAGE_TYPE = 'UNKNOWN_IMAGE_TYPE', + PNG = 'PNG', + JPEG = 'JPEG', + WEBP = 'WEBP', + GIF = 'GIF', +} + +enum __ImageTypeValues { + UNKNOWN_IMAGE_TYPE = 0, + PNG = 1, + JPEG = 2, + WEBP = 3, + GIF = 4, +} + +export namespace ImageType { + export const codec = () => { + return enumeration(__ImageTypeValues) + } +} +export interface EmojiReaction { + clock: bigint + chatId: string + messageId: string + messageType: MessageType + type: EmojiReaction.Type + retracted: boolean + grant: Uint8Array +} + +export namespace EmojiReaction { + export enum Type { + UNKNOWN_EMOJI_REACTION_TYPE = 'UNKNOWN_EMOJI_REACTION_TYPE', + LOVE = 'LOVE', + THUMBS_UP = 'THUMBS_UP', + THUMBS_DOWN = 'THUMBS_DOWN', + LAUGH = 'LAUGH', + SAD = 'SAD', + ANGRY = 'ANGRY', + } + + enum __TypeValues { + UNKNOWN_EMOJI_REACTION_TYPE = 0, + LOVE = 1, + THUMBS_UP = 2, + THUMBS_DOWN = 3, + LAUGH = 4, + SAD = 5, + ANGRY = 6, + } + + export namespace Type { + export const codec = () => { + return enumeration(__TypeValues) + } + } + + export const codec = (): Codec => { + return message({ + 1: { name: 'clock', codec: uint64 }, + 2: { name: 'chatId', codec: string }, + 3: { name: 'messageId', codec: string }, + 4: { name: 'messageType', codec: MessageType.codec() }, + 5: { name: 'type', codec: EmojiReaction.Type.codec() }, + 6: { name: 'retracted', codec: bool }, + 7: { name: 'grant', codec: bytes }, + }) + } + + export const encode = (obj: EmojiReaction): Uint8Array => { + return encodeMessage(obj, EmojiReaction.codec()) + } + + export const decode = (buf: Uint8Array): EmojiReaction => { + return decodeMessage(buf, EmojiReaction.codec()) + } +} diff --git a/packages/status-js/protos copy/pin-message.proto b/packages/status-js/protos copy/pin-message.proto new file mode 100644 index 0000000..9becc03 --- /dev/null +++ b/packages/status-js/protos copy/pin-message.proto @@ -0,0 +1,12 @@ +syntax = "proto3"; + +import "enums.proto"; + +message PinMessage { + uint64 clock = 1; + string message_id = 2; + string chat_id = 3; + bool pinned = 4; + // The type of message (public/one-to-one/private-group-chat) + MessageType message_type = 5; +} diff --git a/packages/status-js/protos copy/pin-message.ts b/packages/status-js/protos copy/pin-message.ts new file mode 100644 index 0000000..14c0b32 --- /dev/null +++ b/packages/status-js/protos copy/pin-message.ts @@ -0,0 +1,88 @@ +/* eslint-disable import/export */ +/* eslint-disable @typescript-eslint/no-namespace */ + +import { + encodeMessage, + decodeMessage, + message, + uint64, + string, + bool, + enumeration, +} from 'protons-runtime' +import type { Codec } from 'protons-runtime' + +export interface PinMessage { + clock: bigint + messageId: string + chatId: string + pinned: boolean + messageType: MessageType +} + +export namespace PinMessage { + export const codec = (): Codec => { + return message({ + 1: { name: 'clock', codec: uint64 }, + 2: { name: 'messageId', codec: string }, + 3: { name: 'chatId', codec: string }, + 4: { name: 'pinned', codec: bool }, + 5: { name: 'messageType', codec: MessageType.codec() }, + }) + } + + export const encode = (obj: PinMessage): Uint8Array => { + return encodeMessage(obj, PinMessage.codec()) + } + + export const decode = (buf: Uint8Array): PinMessage => { + return decodeMessage(buf, PinMessage.codec()) + } +} + +export enum MessageType { + UNKNOWN_MESSAGE_TYPE = 'UNKNOWN_MESSAGE_TYPE', + ONE_TO_ONE = 'ONE_TO_ONE', + PUBLIC_GROUP = 'PUBLIC_GROUP', + PRIVATE_GROUP = 'PRIVATE_GROUP', + SYSTEM_MESSAGE_PRIVATE_GROUP = 'SYSTEM_MESSAGE_PRIVATE_GROUP', + COMMUNITY_CHAT = 'COMMUNITY_CHAT', + SYSTEM_MESSAGE_GAP = 'SYSTEM_MESSAGE_GAP', +} + +enum __MessageTypeValues { + UNKNOWN_MESSAGE_TYPE = 0, + ONE_TO_ONE = 1, + PUBLIC_GROUP = 2, + PRIVATE_GROUP = 3, + SYSTEM_MESSAGE_PRIVATE_GROUP = 4, + COMMUNITY_CHAT = 5, + SYSTEM_MESSAGE_GAP = 6, +} + +export namespace MessageType { + export const codec = () => { + return enumeration(__MessageTypeValues) + } +} +export enum ImageType { + UNKNOWN_IMAGE_TYPE = 'UNKNOWN_IMAGE_TYPE', + PNG = 'PNG', + JPEG = 'JPEG', + WEBP = 'WEBP', + GIF = 'GIF', +} + +enum __ImageTypeValues { + UNKNOWN_IMAGE_TYPE = 0, + PNG = 1, + JPEG = 2, + WEBP = 3, + GIF = 4, +} + +export namespace ImageType { + export const codec = () => { + return enumeration(__ImageTypeValues) + } +} diff --git a/packages/status-js/protos copy/protocol-message.proto b/packages/status-js/protos copy/protocol-message.proto new file mode 100644 index 0000000..3df1736 --- /dev/null +++ b/packages/status-js/protos copy/protocol-message.proto @@ -0,0 +1,88 @@ +syntax = "proto3"; + +message SignedPreKey { + bytes signed_pre_key = 1; + uint32 version = 2; + uint32 protocol_version = 3; +} + +// X3DH prekey bundle +message Bundle { + // Identity key + bytes identity = 1; + // Installation id + map signed_pre_keys = 2; + // Prekey signature + bytes signature = 4; + + // When the bundle was created locally + int64 timestamp = 5; +} + +message BundleContainer { + reserved 3; + // X3DH prekey bundle + Bundle bundle = 1; + // Private signed prekey + bytes private_signed_pre_key = 2; +} + +message DRHeader { + // Current ratchet public key + bytes key = 1; + // Number of the message in the sending chain + uint32 n = 2; + // Length of the previous sending chain + uint32 pn = 3; + // Bundle ID + bytes id = 4; +} + +message DHHeader { + // Compressed ephemeral public key + bytes key = 1; +} + +message X3DHHeader { + reserved 3; + // Ephemeral key used + bytes key = 1; + // Used bundle's signed prekey + bytes id = 4; +} + +// Hash Ratchet Header +message HRHeader { + // community key ID + uint32 key_id = 1; + // Community message number for this key_id + uint32 seq_no = 2; + // Community ID + string group_id = 3; +} + +// Direct message value +message EncryptedMessageProtocol { + X3DHHeader X3DH_header = 1; + DRHeader DR_header = 2; + DHHeader DH_header = 101; + HRHeader HR_header = 102; + // Encrypted payload + bytes payload = 3; +} + +// Top-level protocol message +message ProtocolMessage { + // The device id of the sender + string installation_id = 2; + + // List of bundles + repeated Bundle bundles = 3; + + // One to one message, encrypted, indexed by installation_id + // TODO map here is redundant in case of community messages + map encrypted_message = 101; + + // Public chats, not encrypted + bytes public_message = 102; +} diff --git a/packages/status-js/protos copy/protocol-message.ts b/packages/status-js/protos copy/protocol-message.ts new file mode 100644 index 0000000..32bd5bb --- /dev/null +++ b/packages/status-js/protos copy/protocol-message.ts @@ -0,0 +1,234 @@ +/* eslint-disable import/export */ +/* eslint-disable @typescript-eslint/no-namespace */ + +import { + encodeMessage, + decodeMessage, + message, + bytes, + uint32, + int64, + string, +} from 'protons-runtime' +import type { Codec } from 'protons-runtime' + +export interface SignedPreKey { + signedPreKey: Uint8Array + version: number + protocolVersion: number +} + +export namespace SignedPreKey { + export const codec = (): Codec => { + return message({ + 1: { name: 'signedPreKey', codec: bytes }, + 2: { name: 'version', codec: uint32 }, + 3: { name: 'protocolVersion', codec: uint32 }, + }) + } + + export const encode = (obj: SignedPreKey): Uint8Array => { + return encodeMessage(obj, SignedPreKey.codec()) + } + + export const decode = (buf: Uint8Array): SignedPreKey => { + return decodeMessage(buf, SignedPreKey.codec()) + } +} + +export interface Bundle { + identity: Uint8Array + signedPreKeys: SignedPreKey + signature: Uint8Array + timestamp: bigint +} + +export namespace Bundle { + export const codec = (): Codec => { + return message({ + 1: { name: 'identity', codec: bytes }, + 2: { name: 'signedPreKeys', codec: SignedPreKey.codec() }, + 4: { name: 'signature', codec: bytes }, + 5: { name: 'timestamp', codec: int64 }, + }) + } + + export const encode = (obj: Bundle): Uint8Array => { + return encodeMessage(obj, Bundle.codec()) + } + + export const decode = (buf: Uint8Array): Bundle => { + return decodeMessage(buf, Bundle.codec()) + } +} + +export interface BundleContainer { + bundle: Bundle + privateSignedPreKey: Uint8Array +} + +export namespace BundleContainer { + export const codec = (): Codec => { + return message({ + 1: { name: 'bundle', codec: Bundle.codec() }, + 2: { name: 'privateSignedPreKey', codec: bytes }, + }) + } + + export const encode = (obj: BundleContainer): Uint8Array => { + return encodeMessage(obj, BundleContainer.codec()) + } + + export const decode = (buf: Uint8Array): BundleContainer => { + return decodeMessage(buf, BundleContainer.codec()) + } +} + +export interface DRHeader { + key: Uint8Array + n: number + pn: number + id: Uint8Array +} + +export namespace DRHeader { + export const codec = (): Codec => { + return message({ + 1: { name: 'key', codec: bytes }, + 2: { name: 'n', codec: uint32 }, + 3: { name: 'pn', codec: uint32 }, + 4: { name: 'id', codec: bytes }, + }) + } + + export const encode = (obj: DRHeader): Uint8Array => { + return encodeMessage(obj, DRHeader.codec()) + } + + export const decode = (buf: Uint8Array): DRHeader => { + return decodeMessage(buf, DRHeader.codec()) + } +} + +export interface DHHeader { + key: Uint8Array +} + +export namespace DHHeader { + export const codec = (): Codec => { + return message({ + 1: { name: 'key', codec: bytes }, + }) + } + + export const encode = (obj: DHHeader): Uint8Array => { + return encodeMessage(obj, DHHeader.codec()) + } + + export const decode = (buf: Uint8Array): DHHeader => { + return decodeMessage(buf, DHHeader.codec()) + } +} + +export interface X3DHHeader { + key: Uint8Array + id: Uint8Array +} + +export namespace X3DHHeader { + export const codec = (): Codec => { + return message({ + 1: { name: 'key', codec: bytes }, + 4: { name: 'id', codec: bytes }, + }) + } + + export const encode = (obj: X3DHHeader): Uint8Array => { + return encodeMessage(obj, X3DHHeader.codec()) + } + + export const decode = (buf: Uint8Array): X3DHHeader => { + return decodeMessage(buf, X3DHHeader.codec()) + } +} + +export interface HRHeader { + keyId: number + seqNo: number + groupId: string +} + +export namespace HRHeader { + export const codec = (): Codec => { + return message({ + 1: { name: 'keyId', codec: uint32 }, + 2: { name: 'seqNo', codec: uint32 }, + 3: { name: 'groupId', codec: string }, + }) + } + + export const encode = (obj: HRHeader): Uint8Array => { + return encodeMessage(obj, HRHeader.codec()) + } + + export const decode = (buf: Uint8Array): HRHeader => { + return decodeMessage(buf, HRHeader.codec()) + } +} + +export interface EncryptedMessageProtocol { + X3DHHeader: X3DHHeader + DRHeader: DRHeader + DHHeader: DHHeader + HRHeader: HRHeader + payload: Uint8Array +} + +export namespace EncryptedMessageProtocol { + export const codec = (): Codec => { + return message({ + 1: { name: 'X3DHHeader', codec: X3DHHeader.codec() }, + 2: { name: 'DRHeader', codec: DRHeader.codec() }, + 101: { name: 'DHHeader', codec: DHHeader.codec() }, + 102: { name: 'HRHeader', codec: HRHeader.codec() }, + 3: { name: 'payload', codec: bytes }, + }) + } + + export const encode = (obj: EncryptedMessageProtocol): Uint8Array => { + return encodeMessage(obj, EncryptedMessageProtocol.codec()) + } + + export const decode = (buf: Uint8Array): EncryptedMessageProtocol => { + return decodeMessage(buf, EncryptedMessageProtocol.codec()) + } +} + +export interface ProtocolMessage { + installationId: string + bundles: Bundle[] + encryptedMessage: EncryptedMessageProtocol + publicMessage: Uint8Array +} + +export namespace ProtocolMessage { + export const codec = (): Codec => { + return message({ + 2: { name: 'installationId', codec: string }, + 3: { name: 'bundles', codec: Bundle.codec(), repeats: true }, + 101: { + name: 'encryptedMessage', + codec: EncryptedMessageProtocol.codec(), + }, + 102: { name: 'publicMessage', codec: bytes }, + }) + } + + export const encode = (obj: ProtocolMessage): Uint8Array => { + return encodeMessage(obj, ProtocolMessage.codec()) + } + + export const decode = (buf: Uint8Array): ProtocolMessage => { + return decodeMessage(buf, ProtocolMessage.codec()) + } +} diff --git a/packages/status-js/protos copy/status-update.proto b/packages/status-js/protos copy/status-update.proto new file mode 100644 index 0000000..a9d6358 --- /dev/null +++ b/packages/status-js/protos copy/status-update.proto @@ -0,0 +1,30 @@ +syntax = "proto3"; + +/* Specs: +:AUTOMATIC + To Send - "AUTOMATIC" status ping every 5 minutes + Display - Online for up to 5 minutes from the last clock, after that Offline +:ALWAYS_ONLINE + To Send - "ALWAYS_ONLINE" status ping every 5 minutes + Display - Online for up to 2 weeks from the last clock, after that Offline +:INACTIVE + To Send - A single "INACTIVE" status ping + Display - Offline forever +Note: Only send pings if the user interacted with the app in the last x minutes. */ +message StatusUpdate { + + uint64 clock = 1; + + StatusType status_type = 2; + + string custom_text = 3; + + enum StatusType { + UNKNOWN_STATUS_TYPE = 0; + AUTOMATIC = 1; + DO_NOT_DISTURB = 2; + ALWAYS_ONLINE = 3; + INACTIVE = 4; + }; + +} diff --git a/packages/status-js/protos copy/status-update.ts b/packages/status-js/protos copy/status-update.ts new file mode 100644 index 0000000..ad86a27 --- /dev/null +++ b/packages/status-js/protos copy/status-update.ts @@ -0,0 +1,58 @@ +/* eslint-disable import/export */ +/* eslint-disable @typescript-eslint/no-namespace */ + +import { + enumeration, + encodeMessage, + decodeMessage, + message, + uint64, + string, +} from 'protons-runtime' +import type { Codec } from 'protons-runtime' + +export interface StatusUpdate { + clock: bigint + statusType: StatusUpdate.StatusType + customText: string +} + +export namespace StatusUpdate { + export enum StatusType { + UNKNOWN_STATUS_TYPE = 'UNKNOWN_STATUS_TYPE', + AUTOMATIC = 'AUTOMATIC', + DO_NOT_DISTURB = 'DO_NOT_DISTURB', + ALWAYS_ONLINE = 'ALWAYS_ONLINE', + INACTIVE = 'INACTIVE', + } + + enum __StatusTypeValues { + UNKNOWN_STATUS_TYPE = 0, + AUTOMATIC = 1, + DO_NOT_DISTURB = 2, + ALWAYS_ONLINE = 3, + INACTIVE = 4, + } + + export namespace StatusType { + export const codec = () => { + return enumeration(__StatusTypeValues) + } + } + + export const codec = (): Codec => { + return message({ + 1: { name: 'clock', codec: uint64 }, + 2: { name: 'statusType', codec: StatusUpdate.StatusType.codec() }, + 3: { name: 'customText', codec: string }, + }) + } + + export const encode = (obj: StatusUpdate): Uint8Array => { + return encodeMessage(obj, StatusUpdate.codec()) + } + + export const decode = (buf: Uint8Array): StatusUpdate => { + return decodeMessage(buf, StatusUpdate.codec()) + } +} diff --git a/packages/status-js/protos/communities-old.proto b/packages/status-js/protos/communities-old.proto new file mode 100644 index 0000000..67f2028 --- /dev/null +++ b/packages/status-js/protos/communities-old.proto @@ -0,0 +1,78 @@ +syntax = "proto3"; + +import "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-js/protos/communities-old.ts b/packages/status-js/protos/communities-old.ts new file mode 100644 index 0000000..c6876e8 --- /dev/null +++ b/packages/status-js/protos/communities-old.ts @@ -0,0 +1,400 @@ +/* eslint-disable import/export */ +/* eslint-disable @typescript-eslint/no-namespace */ + +import { encodeMessage, decodeMessage, message, bytes, string, uint64, enumeration, bool, int32 } from 'protons-runtime' +import type { Codec } from 'protons-runtime' + +export interface Grant { + communityId: Uint8Array + memberId: Uint8Array + chatId: string + clock: bigint +} + +export namespace Grant { + export const codec = (): Codec => { + return message({ + 1: { name: 'communityId', codec: bytes }, + 2: { name: 'memberId', codec: bytes }, + 3: { name: 'chatId', codec: string }, + 4: { name: 'clock', codec: uint64 } + }) + } + + export const encode = (obj: Grant): Uint8Array => { + return encodeMessage(obj, Grant.codec()) + } + + export const decode = (buf: Uint8Array): Grant => { + return decodeMessage(buf, Grant.codec()) + } +} + +export interface CommunityMember { + roles: CommunityMember.Roles[] +} + +export namespace CommunityMember { + export enum Roles { + UNKNOWN_ROLE = 'UNKNOWN_ROLE', + ROLE_ALL = 'ROLE_ALL', + ROLE_MANAGE_USERS = 'ROLE_MANAGE_USERS' + } + + enum __RolesValues { + UNKNOWN_ROLE = 0, + ROLE_ALL = 1, + ROLE_MANAGE_USERS = 2 + } + + export namespace Roles { + export const codec = () => { + return enumeration(__RolesValues) + } + } + + export const codec = (): Codec => { + return message({ + 1: { name: 'roles', codec: CommunityMember.Roles.codec() } + }) + } + + export const encode = (obj: CommunityMember): Uint8Array => { + return encodeMessage(obj, CommunityMember.codec()) + } + + export const decode = (buf: Uint8Array): CommunityMember => { + return decodeMessage(buf, CommunityMember.codec()) + } +} + +export interface CommunityPermissions { + ensOnly: boolean + private: boolean + access: CommunityPermissions.Access +} + +export namespace CommunityPermissions { + export enum Access { + UNKNOWN_ACCESS = 'UNKNOWN_ACCESS', + NO_MEMBERSHIP = 'NO_MEMBERSHIP', + INVITATION_ONLY = 'INVITATION_ONLY', + ON_REQUEST = 'ON_REQUEST' + } + + enum __AccessValues { + UNKNOWN_ACCESS = 0, + NO_MEMBERSHIP = 1, + INVITATION_ONLY = 2, + ON_REQUEST = 3 + } + + export namespace Access { + export const codec = () => { + return enumeration(__AccessValues) + } + } + + export const codec = (): Codec => { + return message({ + 1: { name: 'ensOnly', codec: bool }, + 2: { name: 'private', codec: bool }, + 3: { name: 'access', codec: CommunityPermissions.Access.codec() } + }) + } + + export const encode = (obj: CommunityPermissions): Uint8Array => { + return encodeMessage(obj, CommunityPermissions.codec()) + } + + export const decode = (buf: Uint8Array): CommunityPermissions => { + return decodeMessage(buf, CommunityPermissions.codec()) + } +} + +export interface CommunityDescription { + clock: bigint + members: CommunityMember + permissions: CommunityPermissions + identity: ChatIdentity + chats: CommunityChat + banList: string[] + categories: CommunityCategory +} + +export namespace CommunityDescription { + export const codec = (): Codec => { + return message({ + 1: { name: 'clock', codec: uint64 }, + 2: { name: 'members', codec: CommunityMember.codec() }, + 3: { name: 'permissions', codec: CommunityPermissions.codec() }, + 5: { name: 'identity', codec: ChatIdentity.codec() }, + 6: { name: 'chats', codec: CommunityChat.codec() }, + 7: { name: 'banList', codec: string, repeats: true }, + 8: { name: 'categories', codec: CommunityCategory.codec() } + }) + } + + export const encode = (obj: CommunityDescription): Uint8Array => { + return encodeMessage(obj, CommunityDescription.codec()) + } + + export const decode = (buf: Uint8Array): CommunityDescription => { + return decodeMessage(buf, CommunityDescription.codec()) + } +} + +export interface CommunityChat { + members: CommunityMember + permissions: CommunityPermissions + identity: ChatIdentity + categoryId: string + position: number +} + +export namespace CommunityChat { + export const codec = (): Codec => { + return message({ + 1: { name: 'members', codec: CommunityMember.codec() }, + 2: { name: 'permissions', codec: CommunityPermissions.codec() }, + 3: { name: 'identity', codec: ChatIdentity.codec() }, + 4: { name: 'categoryId', codec: string }, + 5: { name: 'position', codec: int32 } + }) + } + + export const encode = (obj: CommunityChat): Uint8Array => { + return encodeMessage(obj, CommunityChat.codec()) + } + + export const decode = (buf: Uint8Array): CommunityChat => { + return decodeMessage(buf, CommunityChat.codec()) + } +} + +export interface CommunityCategory { + categoryId: string + name: string + position: number +} + +export namespace CommunityCategory { + export const codec = (): Codec => { + return message({ + 1: { name: 'categoryId', codec: string }, + 2: { name: 'name', codec: string }, + 3: { name: 'position', codec: int32 } + }) + } + + export const encode = (obj: CommunityCategory): Uint8Array => { + return encodeMessage(obj, CommunityCategory.codec()) + } + + export const decode = (buf: Uint8Array): CommunityCategory => { + return decodeMessage(buf, CommunityCategory.codec()) + } +} + +export interface CommunityInvitation { + communityDescription: Uint8Array + grant: Uint8Array + chatId: string + publicKey: Uint8Array +} + +export namespace CommunityInvitation { + export const codec = (): Codec => { + return message({ + 1: { name: 'communityDescription', codec: bytes }, + 2: { name: 'grant', codec: bytes }, + 3: { name: 'chatId', codec: string }, + 4: { name: 'publicKey', codec: bytes } + }) + } + + export const encode = (obj: CommunityInvitation): Uint8Array => { + return encodeMessage(obj, CommunityInvitation.codec()) + } + + export const decode = (buf: Uint8Array): CommunityInvitation => { + return decodeMessage(buf, CommunityInvitation.codec()) + } +} + +export interface CommunityRequestToJoin { + clock: bigint + ensName: string + chatId: string + communityId: Uint8Array +} + +export namespace CommunityRequestToJoin { + export const codec = (): Codec => { + return message({ + 1: { name: 'clock', codec: uint64 }, + 2: { name: 'ensName', codec: string }, + 3: { name: 'chatId', codec: string }, + 4: { name: 'communityId', codec: bytes } + }) + } + + export const encode = (obj: CommunityRequestToJoin): Uint8Array => { + return encodeMessage(obj, CommunityRequestToJoin.codec()) + } + + export const decode = (buf: Uint8Array): CommunityRequestToJoin => { + return decodeMessage(buf, CommunityRequestToJoin.codec()) + } +} + +export interface CommunityRequestToJoinResponse { + clock: bigint + community: CommunityDescription + accepted: boolean + grant: Uint8Array +} + +export namespace CommunityRequestToJoinResponse { + export const codec = (): Codec => { + return message({ + 1: { name: 'clock', codec: uint64 }, + 2: { name: 'community', codec: CommunityDescription.codec() }, + 3: { name: 'accepted', codec: bool }, + 4: { name: 'grant', codec: bytes } + }) + } + + export const encode = (obj: CommunityRequestToJoinResponse): Uint8Array => { + return encodeMessage(obj, CommunityRequestToJoinResponse.codec()) + } + + export const decode = (buf: Uint8Array): CommunityRequestToJoinResponse => { + return decodeMessage(buf, CommunityRequestToJoinResponse.codec()) + } +} + +export interface ChatIdentity { + clock: bigint + ensName: string + images: IdentityImage + displayName: string + description: string + color: string + emoji: string +} + +export namespace ChatIdentity { + export const codec = (): Codec => { + return message({ + 1: { name: 'clock', codec: uint64 }, + 2: { name: 'ensName', codec: string }, + 3: { name: 'images', codec: IdentityImage.codec() }, + 4: { name: 'displayName', codec: string }, + 5: { name: 'description', codec: string }, + 6: { name: 'color', codec: string }, + 7: { name: 'emoji', codec: string } + }) + } + + export const encode = (obj: ChatIdentity): Uint8Array => { + return encodeMessage(obj, ChatIdentity.codec()) + } + + export const decode = (buf: Uint8Array): ChatIdentity => { + return decodeMessage(buf, ChatIdentity.codec()) + } +} + +export interface IdentityImage { + payload: Uint8Array + sourceType: IdentityImage.SourceType + imageType: ImageType + encryptionKeys: Uint8Array[] + encrypted: boolean +} + +export namespace IdentityImage { + export enum SourceType { + UNKNOWN_SOURCE_TYPE = 'UNKNOWN_SOURCE_TYPE', + RAW_PAYLOAD = 'RAW_PAYLOAD', + ENS_AVATAR = 'ENS_AVATAR' + } + + enum __SourceTypeValues { + UNKNOWN_SOURCE_TYPE = 0, + RAW_PAYLOAD = 1, + ENS_AVATAR = 2 + } + + export namespace SourceType { + export const codec = () => { + return enumeration(__SourceTypeValues) + } + } + + export const codec = (): Codec => { + return message({ + 1: { name: 'payload', codec: bytes }, + 2: { name: 'sourceType', codec: IdentityImage.SourceType.codec() }, + 3: { name: 'imageType', codec: ImageType.codec() }, + 4: { name: 'encryptionKeys', codec: bytes, repeats: true }, + 5: { name: 'encrypted', codec: bool } + }) + } + + export const encode = (obj: IdentityImage): Uint8Array => { + return encodeMessage(obj, IdentityImage.codec()) + } + + export const decode = (buf: Uint8Array): IdentityImage => { + return decodeMessage(buf, IdentityImage.codec()) + } +} + +export enum MessageType { + UNKNOWN_MESSAGE_TYPE = 'UNKNOWN_MESSAGE_TYPE', + ONE_TO_ONE = 'ONE_TO_ONE', + PUBLIC_GROUP = 'PUBLIC_GROUP', + PRIVATE_GROUP = 'PRIVATE_GROUP', + SYSTEM_MESSAGE_PRIVATE_GROUP = 'SYSTEM_MESSAGE_PRIVATE_GROUP', + COMMUNITY_CHAT = 'COMMUNITY_CHAT', + SYSTEM_MESSAGE_GAP = 'SYSTEM_MESSAGE_GAP' +} + +enum __MessageTypeValues { + UNKNOWN_MESSAGE_TYPE = 0, + ONE_TO_ONE = 1, + PUBLIC_GROUP = 2, + PRIVATE_GROUP = 3, + SYSTEM_MESSAGE_PRIVATE_GROUP = 4, + COMMUNITY_CHAT = 5, + SYSTEM_MESSAGE_GAP = 6 +} + +export namespace MessageType { + export const codec = () => { + return enumeration(__MessageTypeValues) + } +} +export enum ImageType { + UNKNOWN_IMAGE_TYPE = 'UNKNOWN_IMAGE_TYPE', + PNG = 'PNG', + JPEG = 'JPEG', + WEBP = 'WEBP', + GIF = 'GIF' +} + +enum __ImageTypeValues { + UNKNOWN_IMAGE_TYPE = 0, + PNG = 1, + JPEG = 2, + WEBP = 3, + GIF = 4 +} + +export namespace ImageType { + export const codec = () => { + return enumeration(__ImageTypeValues) + } +} diff --git a/packages/status-js/protos/communities.ts b/packages/status-js/protos/communities.ts index e44467c..a857f4d 100644 --- a/packages/status-js/protos/communities.ts +++ b/packages/status-js/protos/communities.ts @@ -117,7 +117,7 @@ export interface CommunityDescription { members: CommunityMember permissions: CommunityPermissions identity: ChatIdentity - chats: Record + chats: CommunityChat banList: string[] categories: CommunityCategory archiveMagnetlinkClock: bigint diff --git a/packages/status-js/src/account.ts b/packages/status-js/src/account.ts index 7fe5434..5e8626c 100644 --- a/packages/status-js/src/account.ts +++ b/packages/status-js/src/account.ts @@ -13,7 +13,11 @@ export class Account { const chatKey = getPublicKey(privateKey, true) this.privateKey = bytesToHex(privateKey) - this.publicKey = bytesToHex(publicKey) + // this.publicKey = bytesToHex(publicKey) + this.publicKey = + '0x04ac419dac9a8bbb58825a3cde60eef0ee71b8cf6c63df611eeefc8e7aac7c79b55954b679d24cf5ec82da7ed921caf240628a9bfb3450c5111a9cffe54e631811' + + // TODO?: add 0x prefix to public key this.chatKey = bytesToHex(chatKey) } diff --git a/packages/status-js/src/client.test.ts b/packages/status-js/src/client.test.ts new file mode 100644 index 0000000..1d5cc08 --- /dev/null +++ b/packages/status-js/src/client.test.ts @@ -0,0 +1,10 @@ +import { createClient } from './client' + +describe('Client', () => { + it('', async () => { + const client = await createClient({ publicKey: '' }) + await client.start() + + debugger + }) +}) diff --git a/packages/status-js/src/client.ts b/packages/status-js/src/client.ts index 2af731d..c30d394 100644 --- a/packages/status-js/src/client.ts +++ b/packages/status-js/src/client.ts @@ -1,23 +1,735 @@ -import { getPredefinedBootstrapNodes, Waku } from 'js-waku' +// todo: validate sig +// todo: observer contact updates +// todo: replies +// todo: subsribe to newly added channel +// todo: identities/members? +// todo: getmesages +import { bytesToHex } from 'ethereum-cryptography/utils' +import { Waku, waku_message } from 'js-waku' +import difference from 'lodash/difference' +import sortBy from 'lodash/sortBy' +import uniqBy from 'lodash/uniqBy' +import chunk from 'lodash/chunk' -// import { Fleet } from 'js-waku/build/main/lib/discovery/predefined' -// TOOD: params -// TODO?: reconnect/keep alive -// TODO?: error handling -// TOOD?: teardown -export async function createClient(): Promise { - // TODO?: set tiemout - const waku = await Waku.create({ - bootstrap: { - default: false, - // peers: ['/dns4/node-01.gc-us-central1-a.wakuv2.prod.statusim.net/tcp/443/wss/p2p/16Uiu2HAmVkKntsECaYfefR1V2yCR79CegLATuTPE6B9TxgxBiiiA'] - peers: [ - '/dns4/node-01.gc-us-central1-a.wakuv2.test.statusim.net/tcp/443/wss/p2p/16Uiu2HAmJb2e28qLXxT5kZxVUUoJt72EMzNGXB47Rxx5hw3q4YjS', - ], - }, - // TODO?: remove - libp2p: { config: { pubsub: { enabled: true, emitSelf: true } } }, - }) - await waku.waitForRemotePeer() - return waku +import { ApplicationMetadataMessage } from '../protos/application-metadata-message' +// import { ChatIdentity } from '../protos/chat-identity' +import { ChatMessage, DeleteMessage, EditMessage } from '../protos/chat-message' +import { EmojiReaction } from '../protos/emoji-reaction' +import { PinMessage } from '../protos/pin-message' +import { ProtocolMessage } from '../protos/protocol-message' +import { Account } from './account' +// import { ChatIdentity } from './wire/chat_identity' +import { idToContentTopic } from './contentTopic' +import { createSymKeyFromPassword } from './encryption' +import { payloadToId } from './utils/payload-to-id' +import { CommunityDescription } from './wire/community_description' + +import type { WakuMessage } from 'js-waku' + +// todo: rename to chat +type CommunityType = CommunityDescription['proto'] +type ChannelType = any +export type MessageType = ChatMessage & { + messageId: string + pinned: boolean + reactions: Reactions +} + +type Reaction = + | 'heart' + | 'thumbs-up' + | 'thumbs-down' + | 'smile' + | 'sad' + | 'angry' + +type Reactions = { + [key in Reaction]: { + count: number + me: boolean + } +} + +export interface ClientOptions { + publicKey: string + env?: 'production' | 'test' +} + +class Client { + private waku!: Waku + private communityPublicKey: string + + public account?: Account + // fixme + public community!: Community + + constructor(options: ClientOptions) { + this.communityPublicKey = options.publicKey + } + + public async start() { + const waku = await Waku.create({ + bootstrap: { + default: false, + peers: [ + // '/dns4/node-01.gc-us-central1-a.wakuv2.test.statusim.net/tcp/443/wss/p2p/16Uiu2HAmJb2e28qLXxT5kZxVUUoJt72EMzNGXB47Rxx5hw3q4YjS', + '/dns4/node-01.do-ams3.wakuv2.test.statusim.net/tcp/8000/wss/p2p/16Uiu2HAmPLe7Mzm8TsYUubgCAW1aJoeFScxrLj8ppHFivPo97bUZ', + // '/dns4/node-01.do-ams3.status.test.statusim.net/tcp/30303/p2p/16Uiu2HAkukebeXjTQ9QDBeNDWuGfbaSg79wkkhK4vPocLgR6QFDf' + ], + }, + }) + await waku.waitForRemotePeer() + this.waku = waku + + const community = new Community(this, waku, this.communityPublicKey) + await community.start() + this.community = community + } + + public async stop() { + await this.waku.stop() + } + + public createAccount = (): Account => { + this.account = new Account() + return this.account + } + + // TODO?: should this exist + // public deleteAccount = () => { + // this.account = undefined + // } +} + +class Community { + private client: Client + private waku: Waku + private communityPublicKey: string + private communityContentTopic!: string + private communityDecryptionKey!: Uint8Array + public communityMetadata!: CommunityType + private communityCallback: ((community: CommunityType) => void) | undefined + private channelMessages: Partial<{ [key: string]: MessageType[] }> = {} + private channelCallbacks: { + [key: string]: (channel: ChannelType) => void + } = {} + private channelMessagesCallbacks: { + [key: string]: (messages: MessageType[]) => void + } = {} + + constructor(client: Client, waku: Waku, publicKey: string) { + this.client = client + this.waku = waku + this.communityPublicKey = publicKey + } + + public async start() { + // Community + this.communityContentTopic = idToContentTopic(this.communityPublicKey) + this.communityDecryptionKey = await createSymKeyFromPassword( + this.communityPublicKey + ) + + this.waku.store.addDecryptionKey(this.communityDecryptionKey) + await this.fetchCommunity() + + // handle community not connected + await this.observeCommunity() + + // Channel messages + await this.observeChannelMessages(Object.keys(this.communityMetadata.chats)) + + console.log('CLINET: STARTED') + console.log('COMMUNITY:', this) + } + + // todo? and channels + private async observeCommunity() { + // console.log('here') + this.waku.relay.addDecryptionKey(this.communityDecryptionKey) + this.waku.relay.addObserver( + message => { + if (!message.payload) { + return + } + + const decodedMetadata = ApplicationMetadataMessage.decode( + message.payload + ) + if (!decodedMetadata.payload) { + return + } + + const decodedPayload = CommunityDescription.decode( + decodedMetadata.payload + ) + if (!decodedPayload.identity) { + return + } + + const removedChats = difference( + Object.keys(this.communityMetadata.chats), + Object.keys(decodedPayload.proto.chats) + ) + const addedChats = difference( + Object.keys(decodedPayload.proto.chats), + Object.keys(this.communityMetadata.chats) + ) + + if (removedChats.length) { + this.unobserveChannelMessages(removedChats) + } + + if (addedChats.length) { + this.observeChannelMessages(addedChats) + } + + this.communityMetadata = decodedPayload.proto + this.communityCallback?.(decodedPayload.proto) + }, + [this.communityContentTopic] + ) + } + + private observeChannelMessages(chats: string[]) { + const contentTopics: string[] = [] + + for (const chatId of chats) { + const id = `${this.communityPublicKey}${chatId}` + const channelContentTopic = idToContentTopic(id) + const symKey = createSymKeyFromPassword(id) + + contentTopics.push(channelContentTopic) + + // todo: request waku feature to be passed as param + // TODO?: use contentTopics as array instead of separate observer for each chat + this.waku.relay.addDecryptionKey(symKey, { + method: waku_message.DecryptionMethod.Symmetric, + contentTopics: [channelContentTopic], + }) + } + + // todo?: delete Waku observers + // todo?: check if waku propagates errors + // todo!: request Waku feature to accept decryption keys as a param + this.waku.relay.addObserver(this.handleMessage, contentTopics) + console.log('Added observer', contentTopics) + } + + private unobserveChannelMessages(chatIds: string[]) { + const contentTopics = chatIds.map(chatId => { + const id = `${this.communityPublicKey}${chatId}` + const channelContentTopic = idToContentTopic(id) + + return channelContentTopic + }) + + this.waku.relay.deleteObserver(this.handleMessage, contentTopics) + } + + private handleMessage = (wakuMessage: WakuMessage) => { + console.log('MESSAGE: HANDLE') + if (!wakuMessage.payload) { + return + } + + const decodedProtocol = ProtocolMessage.decode(wakuMessage.payload) + if (!decodedProtocol) { + return + } + + const decodedMetadata = ApplicationMetadataMessage.decode( + decodedProtocol.publicMessage + // message.payload + ) + if (!decodedMetadata.payload) { + return + } + + console.log('MESSAGE: DECODED METADATA') + + let shouldUpdate = false + let _decodedPayload: + | ChatMessage + | EditMessage + | DeleteMessage + | PinMessage + | EmojiReaction + | undefined + switch (decodedMetadata.type) { + case ApplicationMetadataMessage.Type.TYPE_CHAT_MESSAGE: { + console.log('MESSAGE:') + + if (!wakuMessage.signaturePublicKey) { + break + } + + const messageId = payloadToId( + decodedProtocol.publicMessage, + wakuMessage.signaturePublicKey + ) + const decodedPayload = ChatMessage.decode(decodedMetadata.payload) + + console.log('MESSAGE: DECODED') + + // todo: explain + // if (!decodedMetadata.identity) { + // break + // } + // const decodedIdentity = ChatIdentity.decode(decodedProtocol.bundles[0].identity) + + // todo: handle already received messages + + // TODO?: ignore messages which are messageType !== COMMUNITY_CHAT + + const channelId = decodedPayload.chatId.slice(68) + + console.log('THIS:', this) + + if (!this.channelMessages[channelId]) { + this.channelMessages[channelId] = [] + } + + console.log('THIS:', this) + console.log('CHANNEL:', channelId) + console.log('MESSAGES:', this.channelMessages) + + const channelMessage: MessageType = { + ...decodedPayload, + // ...decodedIdentity, + messageId, + pinned: false, + reactions: { + 'thumbs-up': { + count: 0, + me: false, + }, + 'thumbs-down': { + count: 0, + me: false, + }, + heart: { + count: 0, + me: false, + }, + smile: { + count: 0, + me: false, + }, + sad: { + count: 0, + me: false, + }, + angry: { + count: 0, + me: false, + }, + }, + } + console.log('MESSAGE: PREMAPPED') + + this.channelMessages[channelId].push(channelMessage) + + shouldUpdate = true + _decodedPayload = decodedPayload + + console.log('MESSAGE: MAPPED') + + break + } + case ApplicationMetadataMessage.Type.TYPE_EDIT_MESSAGE: { + if (!wakuMessage.signaturePublicKey) { + break + } + + const decodedPayload = EditMessage.decode(decodedMetadata.payload) + const channelId = decodedPayload.chatId.slice(68) + const messageId = decodedPayload.messageId + + const msgs = this.channelMessages[channelId].map(message => { + if (message.messageId === messageId) { + shouldUpdate = true + + return { + ...message, + // fixme?: other fields that user can edit + text: decodedPayload.text, + } + } + + return message + }) + + this.channelMessages[channelId] = msgs + _decodedPayload = decodedPayload + + break + } + case ApplicationMetadataMessage.Type.TYPE_DELETE_MESSAGE: { + const decodedPayload = DeleteMessage.decode(decodedMetadata.payload) + const channelId = decodedPayload.chatId.slice(68) + const messageId = decodedPayload.messageId + + const msgs = this.channelMessages[channelId].filter(message => { + if (message.messageId === messageId) { + shouldUpdate = true + return false + } + return true + }) + + this.channelMessages[channelId] = msgs + _decodedPayload = decodedPayload + + break + } + case ApplicationMetadataMessage.Type.TYPE_PIN_MESSAGE: { + const decodedPayload = PinMessage.decode(decodedMetadata.payload) + const channelId = decodedPayload.chatId.slice(68) + const messageId = decodedPayload.messageId + + const message = this.channelMessages[channelId].find( + message => message.messageId === messageId + ) + + if (message) { + message.pinned = Boolean(decodedPayload.pinned) + shouldUpdate = true + _decodedPayload = decodedPayload + } + + break + } + case ApplicationMetadataMessage.Type.TYPE_EMOJI_REACTION: { + if (!wakuMessage.signaturePublicKey) { + break + } + + const decodedPayload = EmojiReaction.decode(decodedMetadata.payload) + const channelId = decodedPayload.chatId.slice(68) + const messageId = decodedPayload.messageId + + const message = this.channelMessages[channelId].find( + message => message.messageId === messageId + ) + + if (message) { + const isMe = + this.client.account?.publicKey === + `0x${bytesToHex(wakuMessage.signaturePublicKey)}` + + // TODO?: not needed anymore + message.reactions ??= { + 'thumbs-up': { + count: 0, + me: false, + }, + 'thumbs-down': { + count: 0, + me: false, + }, + heart: { + count: 0, + me: false, + }, + smile: { + count: 0, + me: false, + }, + sad: { + count: 0, + me: false, + }, + angry: { + count: 0, + me: false, + }, + } + // fixme?: mutates + setReactions(message.reactions, decodedPayload, isMe) + + shouldUpdate = true + _decodedPayload = decodedPayload + } + + break + } + + default: + break + } + + if (shouldUpdate && _decodedPayload) { + const channelId = _decodedPayload.chatId.slice(68) + const messages = this.channelMessages[channelId] ?? [] + + const sortedMessages = sortBy(messages, ['timestamp']) + // todo: do not use + const uniqueChannelMessages = uniqBy(sortedMessages, 'messageId') + + this.channelMessages[channelId] = uniqueChannelMessages + this.channelMessagesCallbacks[channelId]?.( + this.channelMessages[channelId] + ) + + console.log('MESSAGE: NEW', messages, channelId) + } + } + + public async fetchCommunity() { + let community: CommunityType | undefined + + await this.waku.store.queryHistory([this.communityContentTopic], { + decryptionKeys: [this.communityDecryptionKey], + callback: messages => { + for (const message of messages.reverse()) { + if (!message.payload) { + return + } + + const decodedMetadata = ApplicationMetadataMessage.decode( + message.payload + ) + if (!decodedMetadata.payload) { + return + } + + const decodedPayload = CommunityDescription.decode( + decodedMetadata.payload + ) + // todo: explain + if (!decodedPayload.identity) { + return + } + + community = decodedPayload.proto + this.communityMetadata = decodedPayload.proto + + return true + } + }, + }) + + return community + } + + public getMessages(channelId: string): MessageType[] { + return this.channelMessages[channelId] ?? [] + } + + public async fetchChannelMessages( + channelId: string, + callback: (messages: MessageType[], isDone: boolean) => boolean, + options: { start: Date; end: Date; chunk: number /*total: number*/ } + ) { + // const id = `${this.communityPublicKey}${channelId}` + // const channelContentTopic = await idToContentTopic(channelId) + // const symKey = await createSymKeyFromPassword(id) + + // this.waku.store.addDecryptionKey(symKey, { + // method: waku_message.DecryptionMethod.Symmetric, + // contentTopics: [channelContentTopic], + // }) + + const id = `${this.communityPublicKey}${channelId}` + const channelContentTopic = await idToContentTopic(id) + const symKey = await createSymKeyFromPassword(id) + + // todo: request waku feature to be passed as param + // this.waku.store.addDecryptionKey(symKey, { + // method: waku_message.DecryptionMethod.Symmetric, + // contentTopics: [channelContentTopic], + // }) + + const messages: MessageType[] = [] + let count = 0 + let shouldStop = false + + await this.waku.store.queryHistory([channelContentTopic], { + // timeFilter: { + // startTime: options.start, + // endTime: options.end, + // }, + // todo!: increase after testing + // pageSize: 5, + decryptionKeys: [symKey], + callback: wakuMessages => { + for (const wakuMessage of wakuMessages.reverse()) { + if (!wakuMessage.payload) { + continue + } + + if (!wakuMessage.signaturePublicKey) { + continue + } + + const decodedProtocol = ProtocolMessage.decode(wakuMessage.payload) + if (!decodedProtocol) { + continue + } + + const decodedMetadata = ApplicationMetadataMessage.decode( + decodedProtocol.publicMessage + ) + if (!decodedMetadata.payload) { + continue + } + + const messageId = payloadToId( + decodedProtocol.publicMessage, + wakuMessage.signaturePublicKey + ) + + const decodedPayload = ChatMessage.decode(decodedMetadata.payload) + + messages.push({ + ...decodedPayload, + messageId: messageId, + pinned: false, + reactions: { + 'thumbs-up': { + count: 0, + me: false, + }, + 'thumbs-down': { + count: 0, + me: false, + }, + heart: { + count: 0, + me: false, + }, + smile: { + count: 0, + me: false, + }, + sad: { + count: 0, + me: false, + }, + angry: { + count: 0, + me: false, + }, + }, + }) + + count++ + } + + for (const _chunk of chunk(messages, options.chunk)) { + if (_chunk.length === options.chunk) { + const sortedMessages = sortBy(messages.splice(0, options.chunk), [ + 'timestamp', + ]) + const _messages = [ + ...sortedMessages, + ...(this.channelMessages[channelId] ?? []), + ] + + this.channelMessages[channelId] = _messages + + shouldStop = callback(_messages, false) + + if (shouldStop) { + return true + } + } + } + }, + }) + + if (messages.length && !shouldStop) { + const _messages = [ + ...messages.splice(0), + ...(this.channelMessages[channelId] ?? []), + ] + const sortedMessages = sortBy(_messages, ['timestamp']) + + this.channelMessages[channelId] = sortedMessages + + callback(sortedMessages, true) + + return + } + + // do?: always return at least last state + // callback(this.channelMessages[channelId] ?? [], true) + + return + } + + public onCommunityUpdate(callback: (community: CommunityType) => void) { + this.communityCallback = callback + + return () => { + this.communityCallback = undefined + } + } + + public onChannelUpdate( + channelId: string, + callback: (channel: ChannelType) => void + ) { + this.channelCallbacks[channelId] = callback + + return () => { + delete this.channelCallbacks[channelId] + } + } + + public onChannelMessageUpdate( + channelId: string, + callback: (messages: MessageType[]) => void + ) { + this.channelMessagesCallbacks[channelId] = callback + + return () => { + delete this.channelMessagesCallbacks[channelId] + } + } +} + +// fixme: type +const REACTION_MAP: Record = { + [EmojiReaction.Type.LOVE]: 'heart', + [EmojiReaction.Type.THUMBS_UP]: 'thumbs-up', + [EmojiReaction.Type.THUMBS_DOWN]: 'thumbs-down', + [EmojiReaction.Type.LAUGH]: 'smile', + [EmojiReaction.Type.SAD]: 'sad', + [EmojiReaction.Type.ANGRY]: 'angry', + [EmojiReaction.Type.UNKNOWN_EMOJI_REACTION_TYPE]: 'unknown', +} + +function setReactions( + reactions: Reactions, + reaction: EmojiReaction, + isMe: boolean +) { + const type = REACTION_MAP[reaction.type] + const isRetracted = reaction.retracted + + if (!reactions[type]) { + reactions[type] = { + count: 1, + me: isMe, + } + + return + } + + reactions[type].count += isRetracted ? -1 : 1 + + if (isMe) { + reactions[type].me = isRetracted ? false : true + } +} + +// todo export community metadata type +export type { Client, Community, CommunityType } + +export async function createClient(options: ClientOptions): Promise { + const client = new Client(options) + // TODO?: add start + return client } diff --git a/packages/status-js/src/community.ts b/packages/status-js/src/community.ts index 8840fb7..010ece2 100644 --- a/packages/status-js/src/community.ts +++ b/packages/status-js/src/community.ts @@ -1,11 +1,19 @@ import debug from 'debug' import { Chat } from './chat' +import { idToContentTopic } from './contentTopic' +import { ApplicationMetadataMessage_Type } from './proto/status/v1/application_metadata_message' import { bufToHex, hexToBuf } from './utils' +import { ApplicationMetadataMessage } from './wire/application_metadata_message' +import { ChatMessage } from './wire/chat_message' import { CommunityDescription } from './wire/community_description' import type { CommunityChat } from './wire/community_chat' -import type { Waku } from 'js-waku' +import type { Waku, WakuMessage } from 'js-waku' +import { createSymKeyFromPassword } from './encryption' +// import { createSymKeyFromPassword } from './encryption' + +type Events = 'communityfetch' | 'communityudpate' | 'channelmessagereceive' const dbg = debug('communities:community') @@ -93,3 +101,201 @@ export class Community { this.chats.set(chatId, chat) } } + +class CommunityBeta { + private waku: Waku + private publicKey: string + private callbacks: any = {} + private communityData: any = {} + private channelEvents: any = {} + private symKey + + private channels: Record = {} + + constructor(waku: Waku, publicKey: string) { + this.waku = waku + this.publicKey = publicKey + } + + // todo: observer only if some callbacks set + // todo: doc as to be called after callbacks have been added + public async start() { + this.symKey = await createSymKeyFromPassword(this.publicKey) + + const communityTopic = idToContentTopic(this.publicKey) + + await this.fetchCommunity(communityTopic) + // this.observeCommunity(communityTopic) + + // const channelTopics = [] + // this.observeChannelMessages(channelTopics) + } + + // TODO: should return unsubscribe function + public addCallback(type: Events, callback: () => void) { + const callbacks = this.callbacks[type] + + if (!callbacks?.length) { + this.callbacks[type] = [callback] + } else { + this.callbacks[type].push(callback) + } + + return () => { + this.callbacks[type].filter(cb => cb === callback) + } + } + + private async fetchCommunity(topic: string): Promise { + let payload = undefined + + // todo: request Waku to replace callbacks + await this.waku.store + .queryHistory([topic], { + decryptionKeys: [this.symKey], + callback: messages => { + for (const message of messages.reverse()) { + if (!message.payload) { + return + } + + const decodedMetadata = ApplicationMetadataMessage.decode( + message.payload + ) + if (!decodedMetadata.payload) { + return + } + + const decodedPayload = CommunityDescription.decode( + decodedMetadata.payload + ) + // todo: explain + if (!decodedPayload.identity) { + return + } + + payload = decodedPayload + + // this.channels[chatId] = [ + // ...maessages, + // ...this.channels[chatId] + // ] + + // this.callbacks[''].forEach(cb => cb(this.channels[chatId])) + + return true + } + }, + }) + .catch(error => { + console.error(error) + }) + + if (payload) { + const result = { + name: undefined, + description: undefined, + color: undefined, + channels: undefined, + members: undefined, + } + + // todo: set `this.communityData` + this.callbacks['communityfetch'].forEach(callback => callback(result)) + } + + // todo: handle no (valid) messages case + } + + private observeCommunity(topic: string) { + let payload = undefined + + this.waku.relay.addObserver( + message => { + if (!message.payload) { + return + } + + const decodedMetadata = ApplicationMetadataMessage.decode( + message.payload + ) + if (!decodedMetadata.payload) { + return + } + + const decodedPayload = CommunityDescription.decode( + decodedMetadata.payload + ) + // todo: explain + if (!decodedPayload.identity) { + return + } + + payload = decodedPayload + }, + [topic] + ) + + if (payload) { + const result = { + name: undefined, + description: undefined, + color: undefined, + channels: undefined, + members: undefined, + } + + this.callbacks['communityupdate'].forEach(callback => callback(result)) + } + + // todo: handle no (valid) messages case + } + + private observeChannelMessages(topics: string[]) { + let payload = undefined + + this.waku.relay.addObserver(message => { + if (!message.payload || !message.timestamp) { + return + } + + const decodedMetadata = ApplicationMetadataMessage.decode(message.payload) + if (!decodedMetadata.payload) { + return + } + + switch (decodedMetadata.type) { + case ApplicationMetadataMessage_Type.TYPE_CHAT_MESSAGE: { + const message = ChatMessage.decode(decodedMetadata.payload) + + this.channels[message.chatId].push(message) + this.callbacks.forEach(callback => callback(this.channels[chatId])) + + // handle chat message + // push + + payload = message + return + } + default: + return + } + }, topics) + + if (payload) { + // todo: push to `this.channelEvents` by type + + this.callbacks['channelmessagereceive'].forEach(callback => + callback(result) + ) + } + + // todo: handle no (valid) messages case + } +} + +export function createCommunityBeta(waku: Waku, publicKey: string) { + const community = new CommunityBeta(waku, publicKey) + + return community +} diff --git a/packages/status-js/src/contacts.ts b/packages/status-js/src/contacts.ts index 76f8e4a..93536a4 100644 --- a/packages/status-js/src/contacts.ts +++ b/packages/status-js/src/contacts.ts @@ -1,3 +1,4 @@ +// see import { PageDirection, WakuMessage } from 'js-waku' import { idToContactCodeTopic } from './contentTopic' diff --git a/packages/status-js/src/debug.ts b/packages/status-js/src/debug.ts new file mode 100644 index 0000000..4008233 --- /dev/null +++ b/packages/status-js/src/debug.ts @@ -0,0 +1,37 @@ +import { createClient } from '../src/client' + +const COMMUNITY_PUBLIC_KEY = + '0x029f196bbfef4fa6a5eb81dd802133a63498325445ca1af1d154b1bb4542955133' // Boring community +// '0x0243611cc13cc4e4390180fe8fd35234ab0fe2a7ba8d32e8ae5dd23b60ac7ec177' +// '0x02e7102c85ed78e5be30124f8f52014b1135f972c383f55f83ec8ff50436cd1260' +const CHANNEL_ID = + // '00d3f525-a0cf-4c40-832d-543ec9f8188b' // #messages + '30804ea7-bd66-4d5d-91eb-b2dcfe2515b3' // #test-messages + +// 0x029f196bbfef4fa6a5eb81dd802133a63498325445ca1af1d154b1bb4542955133 c8b6df78-96be-4658-8bde-b51b2a09c599 + +;(async () => { + const client = await createClient({ publicKey: COMMUNITY_PUBLIC_KEY }) + + await client.start() + await client.createAccount() + + const community = client.community.communityMetadata + + client.community.fetchChannelMessages( + CHANNEL_ID, + (messages, isDone) => { + console.log(messages) + + return false + }, + { start: new Date('2022-01-01'), end: new Date(), chunk: 3 } + ) + // client.community.onCommunityUpdate(community => console.log(community)) + // client.community.onChannelUpdate(CHANNEL_ID, channel => console.log(channel)) + // client.community.onChannelMessageUpdate(CHANNEL_ID, messages => + // console.log(messages) + // ) + + // await client.stop() +})() diff --git a/packages/status-js/src/encryption.ts b/packages/status-js/src/encryption.ts index d6e5f19..80df154 100644 --- a/packages/status-js/src/encryption.ts +++ b/packages/status-js/src/encryption.ts @@ -2,9 +2,7 @@ import pbkdf2 from 'pbkdf2' const AESKeyLength = 32 // bytes -export async function createSymKeyFromPassword( - password: string -): Promise { +export function createSymKeyFromPassword(password: string): Uint8Array { return pbkdf2.pbkdf2Sync( Buffer.from(password, 'utf-8'), '', diff --git a/packages/status-js/src/example.ts b/packages/status-js/src/example.ts new file mode 100644 index 0000000..4bdf393 --- /dev/null +++ b/packages/status-js/src/example.ts @@ -0,0 +1,21 @@ +import { createClient } from '../src/client' + +const COMMUNITY_PUBLIC_KEY = + '0x029f196bbfef4fa6a5eb81dd802133a63498325445ca1af1d154b1bb4542955133' // Boring community +// '0x0243611cc13cc4e4390180fe8fd35234ab0fe2a7ba8d32e8ae5dd23b60ac7ec177' +// '0x02e7102c85ed78e5be30124f8f52014b1135f972c383f55f83ec8ff50436cd1260' +const CHANNEL_ID = '00d3f525-a0cf-4c40-832d-543ec9f8188b' // messages + +;(async () => { + const client = await createClient({ publicKey: COMMUNITY_PUBLIC_KEY }) + + await client.start() + + // client.community.onCommunityUpdate(() => console.log("community:update")) + // client.community.onChannelUpdate(() => console.log("channel:update")) + client.community.onChannelMessageUpdate(CHANNEL_ID, () => + console.log('channel:message') + ) + + // await client.stop() +})() diff --git a/packages/status-js/src/index.ts b/packages/status-js/src/index.ts index 73bc88f..4017ae4 100644 --- a/packages/status-js/src/index.ts +++ b/packages/status-js/src/index.ts @@ -1,61 +1,44 @@ -// export { Chat } from './chat' -// // export type { Client, ClientOptions } from './client' -// export { createClient } from './client' -// export { Community } from './community' -// export { Contacts } from './contacts' -// export type { GroupChat, GroupChatsType } from './groupChats' -// export { GroupChats } from './groupChats' -// export { Identity } from './identity' -// export { Messenger } from './messenger' -// export { -// bufToHex, -// compressPublicKey, -// genPrivateKeyWithEntropy, -// getLatestUserNickname, -// hexToBuf, -// } from './utils' -// export { ApplicationMetadataMessage } from './wire/application_metadata_message' -// export type { -// AudioContent, -// Content, -// ContentType, -// ImageContent, -// StickerContent, -// TextContent, -// } from './wire/chat_message' -// export { ChatMessage } from './wire/chat_message' -// export { getPredefinedBootstrapNodes } from 'js-waku' +// import { createClient } from '../src/client' -import { createClient } from './client-v2' - -const COMMUNITY_PUBLIC_KEY = - '0x029dd5fecbd689dc11e2a5b399afed92cf1fab65d315b883efca753e8f3882f3bd' // compressed // const COMMUNITY_PUBLIC_KEY = -// '0x0403aeff2fdd0044b136e06afa6d69bb563bb7b3fd518bb30c0d5115a2e020840a2247966c2cc9953ed02cc391e8883b3319f63a31e5f5369d0fb72b62b23dfcbd' // compressed +// '0x029f196bbfef4fa6a5eb81dd802133a63498325445ca1af1d154b1bb4542955133' // Boring community +// // '0x0243611cc13cc4e4390180fe8fd35234ab0fe2a7ba8d32e8ae5dd23b60ac7ec177' +// // '0x02e7102c85ed78e5be30124f8f52014b1135f972c383f55f83ec8ff50436cd1260' +// const CHANNEL_ID = +// // '00d3f525-a0cf-4c40-832d-543ec9f8188b' // #messages +// '30804ea7-bd66-4d5d-91eb-b2dcfe2515b3' // #test-messages -// import { Community } from '../src/community' -// import { Messenger } from '../src/messenger' +// ;(async () => { +// const client = await createClient({ publicKey: COMMUNITY_PUBLIC_KEY }) -console.log('🚀 > COMMUNITY_PUBLIC_KEY', COMMUNITY_PUBLIC_KEY) -;(async () => { - const client = await createClient({ - env: 'test', - publicKey: COMMUNITY_PUBLIC_KEY, - callback: msgs => {}, - }) +// await client.start() +// await client.createAccount() - const communityDescription = await client.getCommunityDescription() +// const community = client.community.communityMetadata - console.log('meow', communityDescription) +// client.community.fetchChannelMessages( +// CHANNEL_ID, +// (messages, isDone) => { +// console.log(messages) - // console.log(communityDescription) - // Retrieve Community's metadata (e.g. description) - // const community = await Community.instantiateCommunity(COMMUNITY_PUBLIC_KEY, client) - // // Retrieve and subscribe to messages - // const messenger = await Messenger.create(, client) - // // TODO: Register observers/callbacks - // messenger.addObserver(() => {}) - // await client.stop() -})() +// return false +// }, +// { start: new Date('2022-01-01'), end: new Date(), chunk: 3 } +// ) +// // client.community.onCommunityUpdate(community => console.log(community)) +// // client.community.onChannelUpdate(CHANNEL_ID, channel => console.log(channel)) +// // client.community.onChannelMessageUpdate(CHANNEL_ID, messages => +// // console.log(messages) +// // ) -// export {} +// // await client.stop() +// })() + +// export type {} from './' +export type { Client, ClientOptions, Community, MessageType } from './client' +export type { Account } from './account' +export { createClient } from './client' + +// import { Community } from './client' + +// type h = Community['communityMetadata']['members'][0] diff --git a/packages/status-js/src/members.ts b/packages/status-js/src/members.ts new file mode 100644 index 0000000..ac7a945 --- /dev/null +++ b/packages/status-js/src/members.ts @@ -0,0 +1,7 @@ +import { keccak256 } from 'ethereum-cryptography/keccak' +import { getPublicKey, sign, utils } from 'ethereum-cryptography/secp256k1' +import { bytesToHex } from 'ethereum-cryptography/utils' + +export class Members { + constructor() {} +} diff --git a/packages/status-js/src/proto/communities/v1/chat_identity.ts b/packages/status-js/src/proto/communities/v1/chat_identity.ts index b30525e..4aa2ed0 100644 --- a/packages/status-js/src/proto/communities/v1/chat_identity.ts +++ b/packages/status-js/src/proto/communities/v1/chat_identity.ts @@ -1,11 +1,7 @@ /* eslint-disable */ import Long from 'long' import _m0 from 'protobufjs/minimal' -import { - ImageType, - imageTypeFromJSON, - imageTypeToJSON, -} from './enums' +import { ImageType, imageTypeFromJSON, imageTypeToJSON } from './enums' export const protobufPackage = 'communities.v1' diff --git a/packages/status-js/src/proto/communities/v1/emoji_reaction.ts b/packages/status-js/src/proto/communities/v1/emoji_reaction.ts index b8ac476..c9014ab 100644 --- a/packages/status-js/src/proto/communities/v1/emoji_reaction.ts +++ b/packages/status-js/src/proto/communities/v1/emoji_reaction.ts @@ -1,11 +1,7 @@ /* eslint-disable */ import Long from 'long' import _m0 from 'protobufjs/minimal' -import { - MessageType, - messageTypeFromJSON, - messageTypeToJSON, -} from './enums' +import { MessageType, messageTypeFromJSON, messageTypeToJSON } from './enums' export const protobufPackage = 'communities.v1' diff --git a/packages/status-js/src/proto/status/v1/protocol_message.ts b/packages/status-js/src/proto/status/v1/protocol_message.ts new file mode 100644 index 0000000..a6d0c90 --- /dev/null +++ b/packages/status-js/src/proto/status/v1/protocol_message.ts @@ -0,0 +1,1160 @@ +/* eslint-disable */ +import Long from 'long' +import * as _m0 from 'protobufjs/minimal' + +export const protobufPackage = '' + +export interface SignedPreKey { + signedPreKey: Uint8Array + version: number + protocolVersion: number +} + +/** X3DH prekey bundle */ +export interface Bundle { + /** Identity key */ + identity: Uint8Array + /** Installation id */ + signedPreKeys: { [key: string]: SignedPreKey } + /** Prekey signature */ + signature: Uint8Array + /** When the bundle was created locally */ + timestamp: number +} + +export interface Bundle_SignedPreKeysEntry { + key: string + value: SignedPreKey | undefined +} + +export interface BundleContainer { + /** X3DH prekey bundle */ + bundle: Bundle | undefined + /** Private signed prekey */ + privateSignedPreKey: Uint8Array +} + +export interface DRHeader { + /** Current ratchet public key */ + key: Uint8Array + /** Number of the message in the sending chain */ + n: number + /** Length of the previous sending chain */ + pn: number + /** Bundle ID */ + id: Uint8Array +} + +export interface DHHeader { + /** Compressed ephemeral public key */ + key: Uint8Array +} + +export interface X3DHHeader { + /** Ephemeral key used */ + key: Uint8Array + /** Used bundle's signed prekey */ + id: Uint8Array +} + +/** Hash Ratchet Header */ +export interface HRHeader { + /** community key ID */ + keyId: number + /** Community message number for this key_id */ + seqNo: number + /** Community ID */ + groupId: string +} + +/** Direct message value */ +export interface EncryptedMessageProtocol { + x3dhHeader: X3DHHeader | undefined + drHeader: DRHeader | undefined + dhHeader: DHHeader | undefined + hrHeader: HRHeader | undefined + /** Encrypted payload */ + payload: Uint8Array +} + +/** Top-level protocol message */ +export interface ProtocolMessage { + /** The device id of the sender */ + installationId: string + /** List of bundles */ + bundles: Bundle[] + /** + * One to one message, encrypted, indexed by installation_id + * TODO map here is redundant in case of community messages + */ + encryptedMessage: { [key: string]: EncryptedMessageProtocol } + /** Public chats, not encrypted */ + publicMessage: Uint8Array +} + +export interface ProtocolMessage_EncryptedMessageEntry { + key: string + value: EncryptedMessageProtocol | undefined +} + +function createBaseSignedPreKey(): SignedPreKey { + return { signedPreKey: new Uint8Array(), version: 0, protocolVersion: 0 } +} + +export const SignedPreKey = { + encode( + message: SignedPreKey, + writer: _m0.Writer = _m0.Writer.create() + ): _m0.Writer { + if (message.signedPreKey.length !== 0) { + writer.uint32(10).bytes(message.signedPreKey) + } + if (message.version !== 0) { + writer.uint32(16).uint32(message.version) + } + if (message.protocolVersion !== 0) { + writer.uint32(24).uint32(message.protocolVersion) + } + return writer + }, + + decode(input: _m0.Reader | Uint8Array, length?: number): SignedPreKey { + const reader = input instanceof _m0.Reader ? input : new _m0.Reader(input) + let end = length === undefined ? reader.len : reader.pos + length + const message = createBaseSignedPreKey() + while (reader.pos < end) { + const tag = reader.uint32() + switch (tag >>> 3) { + case 1: + message.signedPreKey = reader.bytes() + break + case 2: + message.version = reader.uint32() + break + case 3: + message.protocolVersion = reader.uint32() + break + default: + reader.skipType(tag & 7) + break + } + } + return message + }, + + fromJSON(object: any): SignedPreKey { + return { + signedPreKey: isSet(object.signedPreKey) + ? bytesFromBase64(object.signedPreKey) + : new Uint8Array(), + version: isSet(object.version) ? Number(object.version) : 0, + protocolVersion: isSet(object.protocolVersion) + ? Number(object.protocolVersion) + : 0, + } + }, + + toJSON(message: SignedPreKey): unknown { + const obj: any = {} + message.signedPreKey !== undefined && + (obj.signedPreKey = base64FromBytes( + message.signedPreKey !== undefined + ? message.signedPreKey + : new Uint8Array() + )) + message.version !== undefined && (obj.version = Math.round(message.version)) + message.protocolVersion !== undefined && + (obj.protocolVersion = Math.round(message.protocolVersion)) + return obj + }, + + fromPartial, I>>( + object: I + ): SignedPreKey { + const message = createBaseSignedPreKey() + message.signedPreKey = object.signedPreKey ?? new Uint8Array() + message.version = object.version ?? 0 + message.protocolVersion = object.protocolVersion ?? 0 + return message + }, +} + +function createBaseBundle(): Bundle { + return { + identity: new Uint8Array(), + signedPreKeys: {}, + signature: new Uint8Array(), + timestamp: 0, + } +} + +export const Bundle = { + encode( + message: Bundle, + writer: _m0.Writer = _m0.Writer.create() + ): _m0.Writer { + if (message.identity.length !== 0) { + writer.uint32(10).bytes(message.identity) + } + Object.entries(message.signedPreKeys).forEach(([key, value]) => { + Bundle_SignedPreKeysEntry.encode( + { key: key as any, value }, + writer.uint32(18).fork() + ).ldelim() + }) + if (message.signature.length !== 0) { + writer.uint32(34).bytes(message.signature) + } + if (message.timestamp !== 0) { + writer.uint32(40).int64(message.timestamp) + } + return writer + }, + + decode(input: _m0.Reader | Uint8Array, length?: number): Bundle { + const reader = input instanceof _m0.Reader ? input : new _m0.Reader(input) + let end = length === undefined ? reader.len : reader.pos + length + const message = createBaseBundle() + while (reader.pos < end) { + const tag = reader.uint32() + switch (tag >>> 3) { + case 1: + message.identity = reader.bytes() + break + case 2: + const entry2 = Bundle_SignedPreKeysEntry.decode( + reader, + reader.uint32() + ) + if (entry2.value !== undefined) { + message.signedPreKeys[entry2.key] = entry2.value + } + break + case 4: + message.signature = reader.bytes() + break + case 5: + message.timestamp = longToNumber(reader.int64() as Long) + break + default: + reader.skipType(tag & 7) + break + } + } + return message + }, + + fromJSON(object: any): Bundle { + return { + identity: isSet(object.identity) + ? bytesFromBase64(object.identity) + : new Uint8Array(), + signedPreKeys: isObject(object.signedPreKeys) + ? Object.entries(object.signedPreKeys).reduce<{ + [key: string]: SignedPreKey + }>((acc, [key, value]) => { + acc[key] = SignedPreKey.fromJSON(value) + return acc + }, {}) + : {}, + signature: isSet(object.signature) + ? bytesFromBase64(object.signature) + : new Uint8Array(), + timestamp: isSet(object.timestamp) ? Number(object.timestamp) : 0, + } + }, + + toJSON(message: Bundle): unknown { + const obj: any = {} + message.identity !== undefined && + (obj.identity = base64FromBytes( + message.identity !== undefined ? message.identity : new Uint8Array() + )) + obj.signedPreKeys = {} + if (message.signedPreKeys) { + Object.entries(message.signedPreKeys).forEach(([k, v]) => { + obj.signedPreKeys[k] = SignedPreKey.toJSON(v) + }) + } + message.signature !== undefined && + (obj.signature = base64FromBytes( + message.signature !== undefined ? message.signature : new Uint8Array() + )) + message.timestamp !== undefined && + (obj.timestamp = Math.round(message.timestamp)) + return obj + }, + + fromPartial, I>>(object: I): Bundle { + const message = createBaseBundle() + message.identity = object.identity ?? new Uint8Array() + message.signedPreKeys = Object.entries(object.signedPreKeys ?? {}).reduce<{ + [key: string]: SignedPreKey + }>((acc, [key, value]) => { + if (value !== undefined) { + acc[key] = SignedPreKey.fromPartial(value) + } + return acc + }, {}) + message.signature = object.signature ?? new Uint8Array() + message.timestamp = object.timestamp ?? 0 + return message + }, +} + +function createBaseBundle_SignedPreKeysEntry(): Bundle_SignedPreKeysEntry { + return { key: '', value: undefined } +} + +export const Bundle_SignedPreKeysEntry = { + encode( + message: Bundle_SignedPreKeysEntry, + writer: _m0.Writer = _m0.Writer.create() + ): _m0.Writer { + if (message.key !== '') { + writer.uint32(10).string(message.key) + } + if (message.value !== undefined) { + SignedPreKey.encode(message.value, writer.uint32(18).fork()).ldelim() + } + return writer + }, + + decode( + input: _m0.Reader | Uint8Array, + length?: number + ): Bundle_SignedPreKeysEntry { + const reader = input instanceof _m0.Reader ? input : new _m0.Reader(input) + let end = length === undefined ? reader.len : reader.pos + length + const message = createBaseBundle_SignedPreKeysEntry() + while (reader.pos < end) { + const tag = reader.uint32() + switch (tag >>> 3) { + case 1: + message.key = reader.string() + break + case 2: + message.value = SignedPreKey.decode(reader, reader.uint32()) + break + default: + reader.skipType(tag & 7) + break + } + } + return message + }, + + fromJSON(object: any): Bundle_SignedPreKeysEntry { + return { + key: isSet(object.key) ? String(object.key) : '', + value: isSet(object.value) + ? SignedPreKey.fromJSON(object.value) + : undefined, + } + }, + + toJSON(message: Bundle_SignedPreKeysEntry): unknown { + const obj: any = {} + message.key !== undefined && (obj.key = message.key) + message.value !== undefined && + (obj.value = message.value + ? SignedPreKey.toJSON(message.value) + : undefined) + return obj + }, + + fromPartial, I>>( + object: I + ): Bundle_SignedPreKeysEntry { + const message = createBaseBundle_SignedPreKeysEntry() + message.key = object.key ?? '' + message.value = + object.value !== undefined && object.value !== null + ? SignedPreKey.fromPartial(object.value) + : undefined + return message + }, +} + +function createBaseBundleContainer(): BundleContainer { + return { bundle: undefined, privateSignedPreKey: new Uint8Array() } +} + +export const BundleContainer = { + encode( + message: BundleContainer, + writer: _m0.Writer = _m0.Writer.create() + ): _m0.Writer { + if (message.bundle !== undefined) { + Bundle.encode(message.bundle, writer.uint32(10).fork()).ldelim() + } + if (message.privateSignedPreKey.length !== 0) { + writer.uint32(18).bytes(message.privateSignedPreKey) + } + return writer + }, + + decode(input: _m0.Reader | Uint8Array, length?: number): BundleContainer { + const reader = input instanceof _m0.Reader ? input : new _m0.Reader(input) + let end = length === undefined ? reader.len : reader.pos + length + const message = createBaseBundleContainer() + while (reader.pos < end) { + const tag = reader.uint32() + switch (tag >>> 3) { + case 1: + message.bundle = Bundle.decode(reader, reader.uint32()) + break + case 2: + message.privateSignedPreKey = reader.bytes() + break + default: + reader.skipType(tag & 7) + break + } + } + return message + }, + + fromJSON(object: any): BundleContainer { + return { + bundle: isSet(object.bundle) ? Bundle.fromJSON(object.bundle) : undefined, + privateSignedPreKey: isSet(object.privateSignedPreKey) + ? bytesFromBase64(object.privateSignedPreKey) + : new Uint8Array(), + } + }, + + toJSON(message: BundleContainer): unknown { + const obj: any = {} + message.bundle !== undefined && + (obj.bundle = message.bundle ? Bundle.toJSON(message.bundle) : undefined) + message.privateSignedPreKey !== undefined && + (obj.privateSignedPreKey = base64FromBytes( + message.privateSignedPreKey !== undefined + ? message.privateSignedPreKey + : new Uint8Array() + )) + return obj + }, + + fromPartial, I>>( + object: I + ): BundleContainer { + const message = createBaseBundleContainer() + message.bundle = + object.bundle !== undefined && object.bundle !== null + ? Bundle.fromPartial(object.bundle) + : undefined + message.privateSignedPreKey = object.privateSignedPreKey ?? new Uint8Array() + return message + }, +} + +function createBaseDRHeader(): DRHeader { + return { key: new Uint8Array(), n: 0, pn: 0, id: new Uint8Array() } +} + +export const DRHeader = { + encode( + message: DRHeader, + writer: _m0.Writer = _m0.Writer.create() + ): _m0.Writer { + if (message.key.length !== 0) { + writer.uint32(10).bytes(message.key) + } + if (message.n !== 0) { + writer.uint32(16).uint32(message.n) + } + if (message.pn !== 0) { + writer.uint32(24).uint32(message.pn) + } + if (message.id.length !== 0) { + writer.uint32(34).bytes(message.id) + } + return writer + }, + + decode(input: _m0.Reader | Uint8Array, length?: number): DRHeader { + const reader = input instanceof _m0.Reader ? input : new _m0.Reader(input) + let end = length === undefined ? reader.len : reader.pos + length + const message = createBaseDRHeader() + while (reader.pos < end) { + const tag = reader.uint32() + switch (tag >>> 3) { + case 1: + message.key = reader.bytes() + break + case 2: + message.n = reader.uint32() + break + case 3: + message.pn = reader.uint32() + break + case 4: + message.id = reader.bytes() + break + default: + reader.skipType(tag & 7) + break + } + } + return message + }, + + fromJSON(object: any): DRHeader { + return { + key: isSet(object.key) ? bytesFromBase64(object.key) : new Uint8Array(), + n: isSet(object.n) ? Number(object.n) : 0, + pn: isSet(object.pn) ? Number(object.pn) : 0, + id: isSet(object.id) ? bytesFromBase64(object.id) : new Uint8Array(), + } + }, + + toJSON(message: DRHeader): unknown { + const obj: any = {} + message.key !== undefined && + (obj.key = base64FromBytes( + message.key !== undefined ? message.key : new Uint8Array() + )) + message.n !== undefined && (obj.n = Math.round(message.n)) + message.pn !== undefined && (obj.pn = Math.round(message.pn)) + message.id !== undefined && + (obj.id = base64FromBytes( + message.id !== undefined ? message.id : new Uint8Array() + )) + return obj + }, + + fromPartial, I>>(object: I): DRHeader { + const message = createBaseDRHeader() + message.key = object.key ?? new Uint8Array() + message.n = object.n ?? 0 + message.pn = object.pn ?? 0 + message.id = object.id ?? new Uint8Array() + return message + }, +} + +function createBaseDHHeader(): DHHeader { + return { key: new Uint8Array() } +} + +export const DHHeader = { + encode( + message: DHHeader, + writer: _m0.Writer = _m0.Writer.create() + ): _m0.Writer { + if (message.key.length !== 0) { + writer.uint32(10).bytes(message.key) + } + return writer + }, + + decode(input: _m0.Reader | Uint8Array, length?: number): DHHeader { + const reader = input instanceof _m0.Reader ? input : new _m0.Reader(input) + let end = length === undefined ? reader.len : reader.pos + length + const message = createBaseDHHeader() + while (reader.pos < end) { + const tag = reader.uint32() + switch (tag >>> 3) { + case 1: + message.key = reader.bytes() + break + default: + reader.skipType(tag & 7) + break + } + } + return message + }, + + fromJSON(object: any): DHHeader { + return { + key: isSet(object.key) ? bytesFromBase64(object.key) : new Uint8Array(), + } + }, + + toJSON(message: DHHeader): unknown { + const obj: any = {} + message.key !== undefined && + (obj.key = base64FromBytes( + message.key !== undefined ? message.key : new Uint8Array() + )) + return obj + }, + + fromPartial, I>>(object: I): DHHeader { + const message = createBaseDHHeader() + message.key = object.key ?? new Uint8Array() + return message + }, +} + +function createBaseX3DHHeader(): X3DHHeader { + return { key: new Uint8Array(), id: new Uint8Array() } +} + +export const X3DHHeader = { + encode( + message: X3DHHeader, + writer: _m0.Writer = _m0.Writer.create() + ): _m0.Writer { + if (message.key.length !== 0) { + writer.uint32(10).bytes(message.key) + } + if (message.id.length !== 0) { + writer.uint32(34).bytes(message.id) + } + return writer + }, + + decode(input: _m0.Reader | Uint8Array, length?: number): X3DHHeader { + const reader = input instanceof _m0.Reader ? input : new _m0.Reader(input) + let end = length === undefined ? reader.len : reader.pos + length + const message = createBaseX3DHHeader() + while (reader.pos < end) { + const tag = reader.uint32() + switch (tag >>> 3) { + case 1: + message.key = reader.bytes() + break + case 4: + message.id = reader.bytes() + break + default: + reader.skipType(tag & 7) + break + } + } + return message + }, + + fromJSON(object: any): X3DHHeader { + return { + key: isSet(object.key) ? bytesFromBase64(object.key) : new Uint8Array(), + id: isSet(object.id) ? bytesFromBase64(object.id) : new Uint8Array(), + } + }, + + toJSON(message: X3DHHeader): unknown { + const obj: any = {} + message.key !== undefined && + (obj.key = base64FromBytes( + message.key !== undefined ? message.key : new Uint8Array() + )) + message.id !== undefined && + (obj.id = base64FromBytes( + message.id !== undefined ? message.id : new Uint8Array() + )) + return obj + }, + + fromPartial, I>>( + object: I + ): X3DHHeader { + const message = createBaseX3DHHeader() + message.key = object.key ?? new Uint8Array() + message.id = object.id ?? new Uint8Array() + return message + }, +} + +function createBaseHRHeader(): HRHeader { + return { keyId: 0, seqNo: 0, groupId: '' } +} + +export const HRHeader = { + encode( + message: HRHeader, + writer: _m0.Writer = _m0.Writer.create() + ): _m0.Writer { + if (message.keyId !== 0) { + writer.uint32(8).uint32(message.keyId) + } + if (message.seqNo !== 0) { + writer.uint32(16).uint32(message.seqNo) + } + if (message.groupId !== '') { + writer.uint32(26).string(message.groupId) + } + return writer + }, + + decode(input: _m0.Reader | Uint8Array, length?: number): HRHeader { + const reader = input instanceof _m0.Reader ? input : new _m0.Reader(input) + let end = length === undefined ? reader.len : reader.pos + length + const message = createBaseHRHeader() + while (reader.pos < end) { + const tag = reader.uint32() + switch (tag >>> 3) { + case 1: + message.keyId = reader.uint32() + break + case 2: + message.seqNo = reader.uint32() + break + case 3: + message.groupId = reader.string() + break + default: + reader.skipType(tag & 7) + break + } + } + return message + }, + + fromJSON(object: any): HRHeader { + return { + keyId: isSet(object.keyId) ? Number(object.keyId) : 0, + seqNo: isSet(object.seqNo) ? Number(object.seqNo) : 0, + groupId: isSet(object.groupId) ? String(object.groupId) : '', + } + }, + + toJSON(message: HRHeader): unknown { + const obj: any = {} + message.keyId !== undefined && (obj.keyId = Math.round(message.keyId)) + message.seqNo !== undefined && (obj.seqNo = Math.round(message.seqNo)) + message.groupId !== undefined && (obj.groupId = message.groupId) + return obj + }, + + fromPartial, I>>(object: I): HRHeader { + const message = createBaseHRHeader() + message.keyId = object.keyId ?? 0 + message.seqNo = object.seqNo ?? 0 + message.groupId = object.groupId ?? '' + return message + }, +} + +function createBaseEncryptedMessageProtocol(): EncryptedMessageProtocol { + return { + x3dhHeader: undefined, + drHeader: undefined, + dhHeader: undefined, + hrHeader: undefined, + payload: new Uint8Array(), + } +} + +export const EncryptedMessageProtocol = { + encode( + message: EncryptedMessageProtocol, + writer: _m0.Writer = _m0.Writer.create() + ): _m0.Writer { + if (message.x3dhHeader !== undefined) { + X3DHHeader.encode(message.x3dhHeader, writer.uint32(10).fork()).ldelim() + } + if (message.drHeader !== undefined) { + DRHeader.encode(message.drHeader, writer.uint32(18).fork()).ldelim() + } + if (message.dhHeader !== undefined) { + DHHeader.encode(message.dhHeader, writer.uint32(810).fork()).ldelim() + } + if (message.hrHeader !== undefined) { + HRHeader.encode(message.hrHeader, writer.uint32(818).fork()).ldelim() + } + if (message.payload.length !== 0) { + writer.uint32(26).bytes(message.payload) + } + return writer + }, + + decode( + input: _m0.Reader | Uint8Array, + length?: number + ): EncryptedMessageProtocol { + const reader = input instanceof _m0.Reader ? input : new _m0.Reader(input) + let end = length === undefined ? reader.len : reader.pos + length + const message = createBaseEncryptedMessageProtocol() + while (reader.pos < end) { + const tag = reader.uint32() + switch (tag >>> 3) { + case 1: + message.x3dhHeader = X3DHHeader.decode(reader, reader.uint32()) + break + case 2: + message.drHeader = DRHeader.decode(reader, reader.uint32()) + break + case 101: + message.dhHeader = DHHeader.decode(reader, reader.uint32()) + break + case 102: + message.hrHeader = HRHeader.decode(reader, reader.uint32()) + break + case 3: + message.payload = reader.bytes() + break + default: + reader.skipType(tag & 7) + break + } + } + return message + }, + + fromJSON(object: any): EncryptedMessageProtocol { + return { + x3dhHeader: isSet(object.x3dhHeader) + ? X3DHHeader.fromJSON(object.x3dhHeader) + : undefined, + drHeader: isSet(object.drHeader) + ? DRHeader.fromJSON(object.drHeader) + : undefined, + dhHeader: isSet(object.dhHeader) + ? DHHeader.fromJSON(object.dhHeader) + : undefined, + hrHeader: isSet(object.hrHeader) + ? HRHeader.fromJSON(object.hrHeader) + : undefined, + payload: isSet(object.payload) + ? bytesFromBase64(object.payload) + : new Uint8Array(), + } + }, + + toJSON(message: EncryptedMessageProtocol): unknown { + const obj: any = {} + message.x3dhHeader !== undefined && + (obj.x3dhHeader = message.x3dhHeader + ? X3DHHeader.toJSON(message.x3dhHeader) + : undefined) + message.drHeader !== undefined && + (obj.drHeader = message.drHeader + ? DRHeader.toJSON(message.drHeader) + : undefined) + message.dhHeader !== undefined && + (obj.dhHeader = message.dhHeader + ? DHHeader.toJSON(message.dhHeader) + : undefined) + message.hrHeader !== undefined && + (obj.hrHeader = message.hrHeader + ? HRHeader.toJSON(message.hrHeader) + : undefined) + message.payload !== undefined && + (obj.payload = base64FromBytes( + message.payload !== undefined ? message.payload : new Uint8Array() + )) + return obj + }, + + fromPartial, I>>( + object: I + ): EncryptedMessageProtocol { + const message = createBaseEncryptedMessageProtocol() + message.x3dhHeader = + object.x3dhHeader !== undefined && object.x3dhHeader !== null + ? X3DHHeader.fromPartial(object.x3dhHeader) + : undefined + message.drHeader = + object.drHeader !== undefined && object.drHeader !== null + ? DRHeader.fromPartial(object.drHeader) + : undefined + message.dhHeader = + object.dhHeader !== undefined && object.dhHeader !== null + ? DHHeader.fromPartial(object.dhHeader) + : undefined + message.hrHeader = + object.hrHeader !== undefined && object.hrHeader !== null + ? HRHeader.fromPartial(object.hrHeader) + : undefined + message.payload = object.payload ?? new Uint8Array() + return message + }, +} + +function createBaseProtocolMessage(): ProtocolMessage { + return { + installationId: '', + bundles: [], + encryptedMessage: {}, + publicMessage: new Uint8Array(), + } +} + +export const ProtocolMessage = { + encode( + message: ProtocolMessage, + writer: _m0.Writer = _m0.Writer.create() + ): _m0.Writer { + if (message.installationId !== '') { + writer.uint32(18).string(message.installationId) + } + for (const v of message.bundles) { + Bundle.encode(v!, writer.uint32(26).fork()).ldelim() + } + Object.entries(message.encryptedMessage).forEach(([key, value]) => { + ProtocolMessage_EncryptedMessageEntry.encode( + { key: key as any, value }, + writer.uint32(810).fork() + ).ldelim() + }) + if (message.publicMessage.length !== 0) { + writer.uint32(818).bytes(message.publicMessage) + } + return writer + }, + + decode(input: _m0.Reader | Uint8Array, length?: number): ProtocolMessage { + const reader = input instanceof _m0.Reader ? input : new _m0.Reader(input) + let end = length === undefined ? reader.len : reader.pos + length + const message = createBaseProtocolMessage() + while (reader.pos < end) { + const tag = reader.uint32() + switch (tag >>> 3) { + case 2: + message.installationId = reader.string() + break + case 3: + message.bundles.push(Bundle.decode(reader, reader.uint32())) + break + case 101: + const entry101 = ProtocolMessage_EncryptedMessageEntry.decode( + reader, + reader.uint32() + ) + if (entry101.value !== undefined) { + message.encryptedMessage[entry101.key] = entry101.value + } + break + case 102: + message.publicMessage = reader.bytes() + break + default: + reader.skipType(tag & 7) + break + } + } + return message + }, + + fromJSON(object: any): ProtocolMessage { + return { + installationId: isSet(object.installationId) + ? String(object.installationId) + : '', + bundles: Array.isArray(object?.bundles) + ? object.bundles.map((e: any) => Bundle.fromJSON(e)) + : [], + encryptedMessage: isObject(object.encryptedMessage) + ? Object.entries(object.encryptedMessage).reduce<{ + [key: string]: EncryptedMessageProtocol + }>((acc, [key, value]) => { + acc[key] = EncryptedMessageProtocol.fromJSON(value) + return acc + }, {}) + : {}, + publicMessage: isSet(object.publicMessage) + ? bytesFromBase64(object.publicMessage) + : new Uint8Array(), + } + }, + + toJSON(message: ProtocolMessage): unknown { + const obj: any = {} + message.installationId !== undefined && + (obj.installationId = message.installationId) + if (message.bundles) { + obj.bundles = message.bundles.map(e => (e ? Bundle.toJSON(e) : undefined)) + } else { + obj.bundles = [] + } + obj.encryptedMessage = {} + if (message.encryptedMessage) { + Object.entries(message.encryptedMessage).forEach(([k, v]) => { + obj.encryptedMessage[k] = EncryptedMessageProtocol.toJSON(v) + }) + } + message.publicMessage !== undefined && + (obj.publicMessage = base64FromBytes( + message.publicMessage !== undefined + ? message.publicMessage + : new Uint8Array() + )) + return obj + }, + + fromPartial, I>>( + object: I + ): ProtocolMessage { + const message = createBaseProtocolMessage() + message.installationId = object.installationId ?? '' + message.bundles = object.bundles?.map(e => Bundle.fromPartial(e)) || [] + message.encryptedMessage = Object.entries( + object.encryptedMessage ?? {} + ).reduce<{ [key: string]: EncryptedMessageProtocol }>( + (acc, [key, value]) => { + if (value !== undefined) { + acc[key] = EncryptedMessageProtocol.fromPartial(value) + } + return acc + }, + {} + ) + message.publicMessage = object.publicMessage ?? new Uint8Array() + return message + }, +} + +function createBaseProtocolMessage_EncryptedMessageEntry(): ProtocolMessage_EncryptedMessageEntry { + return { key: '', value: undefined } +} + +export const ProtocolMessage_EncryptedMessageEntry = { + encode( + message: ProtocolMessage_EncryptedMessageEntry, + writer: _m0.Writer = _m0.Writer.create() + ): _m0.Writer { + if (message.key !== '') { + writer.uint32(10).string(message.key) + } + if (message.value !== undefined) { + EncryptedMessageProtocol.encode( + message.value, + writer.uint32(18).fork() + ).ldelim() + } + return writer + }, + + decode( + input: _m0.Reader | Uint8Array, + length?: number + ): ProtocolMessage_EncryptedMessageEntry { + const reader = input instanceof _m0.Reader ? input : new _m0.Reader(input) + let end = length === undefined ? reader.len : reader.pos + length + const message = createBaseProtocolMessage_EncryptedMessageEntry() + while (reader.pos < end) { + const tag = reader.uint32() + switch (tag >>> 3) { + case 1: + message.key = reader.string() + break + case 2: + message.value = EncryptedMessageProtocol.decode( + reader, + reader.uint32() + ) + break + default: + reader.skipType(tag & 7) + break + } + } + return message + }, + + fromJSON(object: any): ProtocolMessage_EncryptedMessageEntry { + return { + key: isSet(object.key) ? String(object.key) : '', + value: isSet(object.value) + ? EncryptedMessageProtocol.fromJSON(object.value) + : undefined, + } + }, + + toJSON(message: ProtocolMessage_EncryptedMessageEntry): unknown { + const obj: any = {} + message.key !== undefined && (obj.key = message.key) + message.value !== undefined && + (obj.value = message.value + ? EncryptedMessageProtocol.toJSON(message.value) + : undefined) + return obj + }, + + fromPartial< + I extends Exact, I> + >(object: I): ProtocolMessage_EncryptedMessageEntry { + const message = createBaseProtocolMessage_EncryptedMessageEntry() + message.key = object.key ?? '' + message.value = + object.value !== undefined && object.value !== null + ? EncryptedMessageProtocol.fromPartial(object.value) + : undefined + return message + }, +} + +declare var self: any | undefined +declare var window: any | undefined +declare var global: any | undefined +var globalThis: any = (() => { + if (typeof globalThis !== 'undefined') return globalThis + if (typeof self !== 'undefined') return self + if (typeof window !== 'undefined') return window + if (typeof global !== 'undefined') return global + throw 'Unable to locate global object' +})() + +const atob: (b64: string) => string = + globalThis.atob || + (b64 => globalThis.Buffer.from(b64, 'base64').toString('binary')) +function bytesFromBase64(b64: string): Uint8Array { + const bin = atob(b64) + const arr = new Uint8Array(bin.length) + for (let i = 0; i < bin.length; ++i) { + arr[i] = bin.charCodeAt(i) + } + return arr +} + +const btoa: (bin: string) => string = + globalThis.btoa || + (bin => globalThis.Buffer.from(bin, 'binary').toString('base64')) +function base64FromBytes(arr: Uint8Array): string { + const bin: string[] = [] + arr.forEach(byte => { + 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 + +type KeysOfUnion = T extends T ? keyof T : never +export type Exact = P extends Builtin + ? P + : P & { [K in keyof P]: Exact } & Record< + Exclude>, + never + > + +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() +} + +function isObject(value: any): boolean { + return typeof value === 'object' && value !== null +} + +function isSet(value: any): boolean { + return value !== null && value !== undefined +} diff --git a/packages/status-js/src/utils/id-to-content-topic.test.ts b/packages/status-js/src/utils/id-to-content-topic.test.ts new file mode 100644 index 0000000..1097177 --- /dev/null +++ b/packages/status-js/src/utils/id-to-content-topic.test.ts @@ -0,0 +1,7 @@ +import { idToContentTopic } from './id-to-content-topic' + +describe('idToContentTopic', () => { + it('should return content topic', () => { + expect(idToContentTopic).toBeDefined() + }) +}) diff --git a/packages/status-js/src/utils/id-to-content-topic.ts b/packages/status-js/src/utils/id-to-content-topic.ts new file mode 100644 index 0000000..4fc5196 --- /dev/null +++ b/packages/status-js/src/utils/id-to-content-topic.ts @@ -0,0 +1,16 @@ +import { keccak256 } from 'ethereum-cryptography/keccak' +import { bytesToHex, hexToBytes } from 'ethereum-cryptography/utils' + +/** + * waku spec: https://rfc.vac.dev/spec/23/#bridging-waku-v1-and-waku-v2 + * status-go: https://github.com/status-im/status-go/blob/f6dc6f752a/wakuv2/common/topic.go#L66 + */ + +const TOPIC_LENGTH = 4 + +export function idToContentTopic(id: string): string { + const hash = keccak256(hexToBytes(id)) + const topic = hash.slice(0, TOPIC_LENGTH) + + return `/waku/1/${bytesToHex(topic)}/rfc26` +} diff --git a/packages/status-js/src/utils/payload-to-id.ts b/packages/status-js/src/utils/payload-to-id.ts index 9db98a2..bf72139 100644 --- a/packages/status-js/src/utils/payload-to-id.ts +++ b/packages/status-js/src/utils/payload-to-id.ts @@ -2,8 +2,8 @@ import { keccak256 } from 'ethereum-cryptography/keccak' import { bytesToHex } from 'ethereum-cryptography/utils' export function payloadToId( - publicKey: Uint8Array, - payload: Uint8Array + payload: Uint8Array, + publicKey: Uint8Array ): string { const hash = keccak256(new Uint8Array([...publicKey, ...payload])) const hex = bytesToHex(hash) diff --git a/packages/status-js/src/wire/chat_message.spec.ts b/packages/status-js/src/wire/chat_message.spec.ts index 123bac1..2d97f2d 100644 --- a/packages/status-js/src/wire/chat_message.spec.ts +++ b/packages/status-js/src/wire/chat_message.spec.ts @@ -8,6 +8,26 @@ import { ChatMessage, ContentType } from './chat_message' import type { AudioContent, ImageContent, StickerContent } from './chat_message' describe('Chat Message', () => { + // todo: + // test('Encode & decode Text message', () => { + // const payload = Buffer.from([1, 1]) + + // const imageContent: ImageContent = { + // image: payload, + // imageType: ImageType.IMAGE_TYPE_PNG, + // contentType: ContentType.Image, + // } + + // const message = ChatMessage.createMessage(1, 1, 'chat-id', imageContent) + + // const buf = message.encode() + // const dec = ChatMessage.decode(buf) + + // expect(dec.contentType).toEqual(ChatMessage_ContentType.CONTENT_TYPE_IMAGE) + // expect(dec.image?.payload?.toString()).toEqual(payload.toString()) + // expect(dec.image?.type).toEqual(ImageType.IMAGE_TYPE_PNG) + // }) + test('Encode & decode Image message', () => { const payload = Buffer.from([1, 1]) diff --git a/packages/status-react/package.json b/packages/status-react/package.json index 1fbfcde..00a5dd9 100644 --- a/packages/status-react/package.json +++ b/packages/status-react/package.json @@ -46,6 +46,7 @@ "html-entities": "^2.3.2", "qrcode.react": "^3.0.1", "react": "^17.0.2", + "react-content-loader": "^6.2.0", "react-dom": "^17.0.2", "react-is": "^17.0.2", "react-router-dom": "^6.3.0", diff --git a/packages/status-react/src/components/loading/index.tsx b/packages/status-react/src/components/loading/index.tsx new file mode 100644 index 0000000..dfd1215 --- /dev/null +++ b/packages/status-react/src/components/loading/index.tsx @@ -0,0 +1,81 @@ +import React from 'react' +import { styled } from '~/src/styles/config' + +import ContentLoader from 'react-content-loader' +import { Box } from '~/src/system' + +const CommunityInfoLoader = () => ( + + + + + +) + +const ChannelLoader = () => { + return ( + + + + + ) +} + +export const Loading = () => { + return ( + + + + + + + + + + + + + + + + + ) +} + +const Wrapper = styled('div', { + overflow: 'hidden', + position: 'relative', + width: '100%', + height: '100%', + display: 'flex', + alignItems: 'stretch', + background: '$background', +}) + +const Sidebar = styled('div', { + display: 'none', + width: 304, + flexShrink: 0, + flexDirection: 'column', + padding: '10px 16px', + backgroundColor: '$gray-4', + overflowY: 'scroll', + + '@large': { + display: 'flex', + }, +}) diff --git a/packages/status-react/src/components/main-sidebar/components/channels/index.tsx b/packages/status-react/src/components/main-sidebar/components/channels/index.tsx index 43fe740..21438e1 100644 --- a/packages/status-react/src/components/main-sidebar/components/channels/index.tsx +++ b/packages/status-react/src/components/main-sidebar/components/channels/index.tsx @@ -1,19 +1,30 @@ import React from 'react' import { Box } from '~/src/system' +import { useChats } from '~/src/protocol' import { ChannelGroup } from './channel-group' import { ChannelItem } from './channel-item' -const CHANNELS = { - Public: ['welcome', 'general', 'random'], - Internal: ['watercooler', 'pm'], -} export const Channels = () => { + + const chats = useChats() + return ( - - {Object.entries(CHANNELS).map(([group, channels]) => ( + + {chats.map((chat) => ( + + {chat.identity!.displayName} + + ))} + + {/* {Object.entries(community.chats).map(([group, channels]) => ( {channels.map(channel => ( { ))} - ))} + ))} */} ) } diff --git a/packages/status-react/src/components/main-sidebar/components/community-info/community-dialog.tsx b/packages/status-react/src/components/main-sidebar/components/community-info/community-dialog.tsx index 4f6dc2e..e22cafd 100644 --- a/packages/status-react/src/components/main-sidebar/components/community-info/community-dialog.tsx +++ b/packages/status-react/src/components/main-sidebar/components/community-info/community-dialog.tsx @@ -1,13 +1,15 @@ import React from 'react' -import { useCommunity } from '~/src/protocol/use-community' +import { useCommunity } from '~/src/protocol' import { Button, CopyInput, Dialog, Flex, Grid, Text } from '~/src/system' export const CommunityDialog = () => { - const { name, description, publicKey } = useCommunity() + const { identity, publicKey='0xTODO' } = useCommunity() + const { displayName, description} = identity + return ( - + {description} @@ -17,7 +19,7 @@ export const CommunityDialog = () => { To access this community, paste community public key in Status - desktop or mobile app + desktop or mobile app. diff --git a/packages/status-react/src/components/main-sidebar/components/community-info/index.tsx b/packages/status-react/src/components/main-sidebar/components/community-info/index.tsx index 439d4ce..9e3aa70 100644 --- a/packages/status-react/src/components/main-sidebar/components/community-info/index.tsx +++ b/packages/status-react/src/components/main-sidebar/components/community-info/index.tsx @@ -1,22 +1,24 @@ import React from 'react' -import { useCommunity } from '~/src/protocol/use-community' +import { useCommunity, useMembers } from '~/src/protocol' import { styled } from '~/src/styles/config' import { Avatar, DialogTrigger, Text } from '~/src/system' import { CommunityDialog } from './community-dialog' export const CommunityInfo = () => { - const { name, imageUrl, membersCount } = useCommunity() + const community = useCommunity() + const members = useMembers() + console.log("file: index.tsx > line 11 > CommunityInfo > community", community) return ( diff --git a/packages/status-react/src/components/main-sidebar/components/get-started/index.tsx b/packages/status-react/src/components/main-sidebar/components/get-started/index.tsx index be51719..42f1bca 100644 --- a/packages/status-react/src/components/main-sidebar/components/get-started/index.tsx +++ b/packages/status-react/src/components/main-sidebar/components/get-started/index.tsx @@ -2,13 +2,14 @@ import React from 'react' import { CreateProfileDialog } from '~/src/components/create-profile-dialog' import { useLocalStorage } from '~/src/hooks/use-local-storage' +import { useAccount } from '~/src/protocol' import { Button, Flex } from '~/src/system' import { DialogTrigger } from '~/src/system/dialog' import { Grid } from '~/src/system/grid' import { Heading } from '~/src/system/heading' -import { ConnectWalletDialog } from './connect-wallet-dialog' -import { SyncStatusProfileDialog } from './sync-status-profile-dialog' +// import { ConnectWalletDialog } from './connect-wallet-dialog' +// import { SyncStatusProfileDialog } from './sync-status-profile-dialog' import { ThrowawayProfileFoundDialog } from './throwaway-profile-found-dialog' export const GetStarted = () => { @@ -18,6 +19,8 @@ export const GetStarted = () => { // TODO: Add skip logic } + const [account, { createAccount }] = useAccount() + return ( { - - Want to jump into the discussion? - - - {/* + + Want to jump into the discussion? + + + {/* */} - + {/* - + */} - - - {throwawayProfile ? ( + + {/* + + {account ? ( ) : ( )} - - + */} + ) } diff --git a/packages/status-react/src/components/main-sidebar/components/get-started/throwaway-profile-found-dialog.tsx b/packages/status-react/src/components/main-sidebar/components/get-started/throwaway-profile-found-dialog.tsx index d8a89d5..501f3b4 100644 --- a/packages/status-react/src/components/main-sidebar/components/get-started/throwaway-profile-found-dialog.tsx +++ b/packages/status-react/src/components/main-sidebar/components/get-started/throwaway-profile-found-dialog.tsx @@ -1,6 +1,6 @@ import React from 'react' -import { useProfile } from '~/src/protocol/use-profile' +import { useAccount } from '~/src/protocol' import { Avatar, Dialog, EmojiHash, Flex, Heading, Text } from '~/src/system' interface Props { @@ -10,18 +10,22 @@ interface Props { export const ThrowawayProfileFoundDialog = (props: Props) => { const { onSkip } = props - const profile = useProfile() + const [account] = useAccount() const handleLoadThrowawayProfile = () => { // TODO: load throwaway profile } +if (!account) { + return null +} + return ( - - {profile.name} + + {account.name} Chatkey: 0x63FaC9201494f0bd17B9892B9fae4d52fe3BD377 diff --git a/packages/status-react/src/components/main-sidebar/components/messages/chat-item.tsx b/packages/status-react/src/components/main-sidebar/components/messages/chat-item.tsx index ad773ac..4552506 100644 --- a/packages/status-react/src/components/main-sidebar/components/messages/chat-item.tsx +++ b/packages/status-react/src/components/main-sidebar/components/messages/chat-item.tsx @@ -1,7 +1,7 @@ import React from 'react' import { ChatMenu } from '~/src/components/chat-menu' -import { useChat } from '~/src/protocol/use-chat' +import { useChannel } from '~/src/protocol' import { ContextMenuTrigger } from '~/src/system' import { SidebarItem } from '../sidebar-item' @@ -15,7 +15,7 @@ interface Props extends SidebarItemProps { export const ChatItem = (props: Props) => { const { children, ...sidebarItemProps } = props - const chat = useChat(children) + const chat = useChannel(children) return ( diff --git a/packages/status-react/src/components/main-sidebar/components/messages/index.tsx b/packages/status-react/src/components/main-sidebar/components/messages/index.tsx index 12c2bd8..a1d0e92 100644 --- a/packages/status-react/src/components/main-sidebar/components/messages/index.tsx +++ b/packages/status-react/src/components/main-sidebar/components/messages/index.tsx @@ -5,9 +5,9 @@ import { Box, Grid, Heading, IconButton } from '~/src/system' import { ChatItem } from './chat-item' -const CHATS = ['vitalik.eth', 'pvl.eth', 'Climate Change'] - export const Messages = () => { + const chats = [] + return ( { - {CHATS.map(chat => ( + {chats.map(chat => ( {chat} diff --git a/packages/status-react/src/components/main-sidebar/index.tsx b/packages/status-react/src/components/main-sidebar/index.tsx index 20052ca..21865b3 100644 --- a/packages/status-react/src/components/main-sidebar/index.tsx +++ b/packages/status-react/src/components/main-sidebar/index.tsx @@ -7,8 +7,10 @@ import { Separator } from '~/src/system' import { Channels } from './components/channels' import { CommunityInfo } from './components/community-info' import { GetStarted } from './components/get-started' +import { useAccount } from '~/src/protocol' // import { Messages } from './components/messages' + export const MainSidebar = () => { const { options } = useAppState() @@ -16,14 +18,20 @@ export const MainSidebar = () => { return null } + const [account] = useAccount() + return ( {/* */} + { !account && ( + <> + + )} ) } diff --git a/packages/status-react/src/components/member-sidebar/disconnect-dialog.tsx b/packages/status-react/src/components/member-sidebar/disconnect-dialog.tsx index b142279..3769e1a 100644 --- a/packages/status-react/src/components/member-sidebar/disconnect-dialog.tsx +++ b/packages/status-react/src/components/member-sidebar/disconnect-dialog.tsx @@ -1,20 +1,20 @@ import React from 'react' -import { useProfile } from '~/src/protocol/use-profile' +import { useAccount } from '~/src/protocol' import { Avatar, Dialog, EmojiHash, Flex, Heading, Text } from '~/src/system' export const DisconnectDialog = () => { - const profile = useProfile() + const [account] = useAccount() return ( Do you want to disconnect your profile from this browser? - - {profile.name} + + {account.name} - Chatkey: 0x63FaC9201494f0bd17B9892B9fae4d52fe3BD377 + Chatkey: {account.chatKey} diff --git a/packages/status-react/src/components/member-sidebar/index.tsx b/packages/status-react/src/components/member-sidebar/index.tsx index 6588563..acaf0ed 100644 --- a/packages/status-react/src/components/member-sidebar/index.tsx +++ b/packages/status-react/src/components/member-sidebar/index.tsx @@ -6,8 +6,11 @@ import { Grid, Heading } from '~/src/system' import { MemberGroup } from './member-group' import { MemberItem } from './member-item' import { UserItem } from './user-item' +import { useMembers } from '~/src/protocol' export function MemberSidebar() { + const members = useMembers() + return ( @@ -18,28 +21,19 @@ export function MemberSidebar() { - - pvl.eth - - - carmen - - - carmen - - - - - mark - - - mark - + {members.map(member => ( + + {member} + + ))} + {/* */} ) diff --git a/packages/status-react/src/components/member-sidebar/member-item.tsx b/packages/status-react/src/components/member-sidebar/member-item.tsx index c3287ad..3d2ca69 100644 --- a/packages/status-react/src/components/member-sidebar/member-item.tsx +++ b/packages/status-react/src/components/member-sidebar/member-item.tsx @@ -6,24 +6,24 @@ import type { AvatarProps } from '~/src/system/avatar' interface Props { children: string + chatKey: string verified: boolean untrustworthy: boolean indicator?: AvatarProps['indicator'] } export const MemberItem = (props: Props) => { - const { children, indicator, verified, untrustworthy } = props + const { children, chatKey, indicator, verified, untrustworthy } = props return (
- + {children} {verified && ( @@ -62,7 +62,7 @@ export const MemberItem = (props: Props) => { )} - 71C7656EC7ab88b098defB751B7401B5f6d8976F + {chatKey}
diff --git a/packages/status-react/src/components/member-sidebar/user-item.tsx b/packages/status-react/src/components/member-sidebar/user-item.tsx index fe03e22..624269b 100644 --- a/packages/status-react/src/components/member-sidebar/user-item.tsx +++ b/packages/status-react/src/components/member-sidebar/user-item.tsx @@ -1,30 +1,36 @@ import React from 'react' -import { useProfile } from '~/src/protocol/use-profile' +import { useAccount } from '~/src/protocol' import { styled } from '~/src/styles/config' import { Avatar, DialogTrigger, EthAddress, Flex, Text } from '~/src/system' import { DisconnectDialog } from './disconnect-dialog' export const UserItem = () => { - const profile = useProfile() + const [account] = useAccount() + console.log("file: user-item.tsx > line 11 > UserItem > account", account) + + if (!account) { + return null + } return ( - +
- {profile.name} + {account.name} - {profile.publicKey} + {account.publicKey}
+ { /> - + {account && ( + + + )}
) diff --git a/packages/status-react/src/protocol/index.tsx b/packages/status-react/src/protocol/index.tsx new file mode 100644 index 0000000..ccf1abc --- /dev/null +++ b/packages/status-react/src/protocol/index.tsx @@ -0,0 +1,6 @@ +export { ClientProvider } from './provider' +export { useCommunity } from './use-community' +export { useChats } from './use-chats' +export { useChannel, Channel } from './use-channel' +export { useAccount } from './use-account' +export { useMembers } from './use-members' diff --git a/packages/status-react/src/protocol/provider.tsx b/packages/status-react/src/protocol/provider.tsx index 634f5e1..6f5bc74 100644 --- a/packages/status-react/src/protocol/provider.tsx +++ b/packages/status-react/src/protocol/provider.tsx @@ -1,14 +1,20 @@ -import React, { createContext, useContext, useMemo } from 'react' +import React, { + useEffect, + createContext, + useContext, + useMemo, + useState, +} from 'react' import type { Config } from '~/src/types/config' -// import { createClient } from '@status-im/js' -// import type { Client } from '@status-im/js' +import {Loading} from '~/src/components/loading' +import { createClient } from '@status-im/js' +import type { Client, ClientOptions, Community , Account} from '@status-im/js' -interface ClientContext { - client: Config -} - -const Context = createContext(undefined) +const Context = createContext(undefined) +const CommunityContext = createContext< + Community['communityMetadata'] | undefined +>(undefined) export function useClient() { const context = useContext(Context) @@ -20,18 +26,74 @@ export function useClient() { return context } +export function useCommunity() { + const context = useContext(CommunityContext) + + if (!context) { + // return {} + throw new Error(`useCommunity must be used within a ClientProvider`) + } + + return context +} + interface ClientProviderProps { - config: Config + options: ClientOptions children: React.ReactNode } export const ClientProvider = (props: ClientProviderProps) => { - const { config, children } = props + const [client, setClient] = useState() + const [community, setCommunity] = useState() + const [account, setAccount] = useState() + const [loading, setLoading] = useState(true) - const client = useMemo(() => { - // return createClient({ ...config }) - return { client: config } - }, [config]) + const { options, children } = props - return {children} + // const client = useMemo(() => { + // return createClient(options) + // }, [options]) + + useEffect(() => { + const loadClient = async () => { + // setLoading(true) + const client = await createClient({ publicKey: props.options.publicKey }) +console.log('starting') + await client.start() + console.log('init', client) + setCommunity(client.community.communityMetadata) + console.log("file: provider.tsx > line 64 > loadClient > client.community.communityMetadata", client.community.communityMetadata) + + setClient(client) + setLoading(false) + } + + loadClient() + }, []) + + useEffect(() => { + if (client) { + console.log('useEffect subscribe') + return client.community.onCommunityUpdate(community => { + setCommunity(community) + console.log("file: provider.tsx > line 75 > useEffect > community", community) + }) + } + }, [client]) + + // if (!client) { + // return + // } + + return ( + + + {loading ? ( + + ) : ( + children + )} + + + ) } diff --git a/packages/status-react/src/protocol/use-account.tsx b/packages/status-react/src/protocol/use-account.tsx new file mode 100644 index 0000000..599c66f --- /dev/null +++ b/packages/status-react/src/protocol/use-account.tsx @@ -0,0 +1,41 @@ +import { useCallback, useState ,useEffect} from 'react' +import { useClient } from './provider' + +// return { +// name: 'Satoshi', +// publicKey: '71C7656EC7ab88b098defB751B7401B5f6d8976F', +// imageUrl: +// 'https://images.unsplash.com/photo-1546776310-eef45dd6d63c?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1620&q=80', +// } + +export const useAccount = () => { + const client = useClient() + + const [account, setAccount] = useState() + console.log("file: use-account.tsx > line 15 > useAccount > account", account) + + + useEffect(() => { + // load account + },[]) + + const createAccount = useCallback(() => { + const account = client.createAccount() + console.log("createAccount > account", account) + + + setAccount(account) + // TODO: save account + + + + return account + }, []) + + const deleteAccount = useCallback(() => { + // client.deleteAccount() + setAccount(undefined) + }, []) + + return [account, { createAccount, deleteAccount }] as const +} diff --git a/packages/status-react/src/protocol/use-channel.tsx b/packages/status-react/src/protocol/use-channel.tsx new file mode 100644 index 0000000..67e6817 --- /dev/null +++ b/packages/status-react/src/protocol/use-channel.tsx @@ -0,0 +1,23 @@ +import { useMemo } from 'react' +import { useClient } from './provider' +import { useCommunity } from '~/src/protocol/use-community' +import { Community } from '@status-im/js' + +export type Channel = Community['communityMetadata']['chats'][0] + +export const useChannel = (id: string): Channel => { + const client = useClient() + + const community = useCommunity() + + + + + const chat = useMemo(() => { + return Object.entries(community.chats).find( + ([chatId]) => chatId === id + )?.[1] + }, [community, id]) + + return chat! +} diff --git a/packages/status-react/src/protocol/use-channels.tsx b/packages/status-react/src/protocol/use-channels.tsx deleted file mode 100644 index 874a25b..0000000 --- a/packages/status-react/src/protocol/use-channels.tsx +++ /dev/null @@ -1,5 +0,0 @@ -import { useClient } from './provider' - -export const useChannels = () => { - const client = useClient() -} diff --git a/packages/status-react/src/protocol/use-chat.tsx b/packages/status-react/src/protocol/use-chat.tsx deleted file mode 100644 index ec27fa9..0000000 --- a/packages/status-react/src/protocol/use-chat.tsx +++ /dev/null @@ -1,35 +0,0 @@ -export interface Chat { - type: 'channel' | 'group-chat' | 'chat' - imageUrl?: string -} - -const chats: Record = { - welcome: { type: 'channel', imageUrl: '' }, - general: { - type: 'channel', - imageUrl: - 'https://images.unsplash.com/flagged/photo-1559717865-a99cac1c95d8?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=MnwxfDB8MXxyYW5kb218MHx8Y2l0eSxuaWdodHx8fHx8fDE2NDk4NDYyMzI&ixlib=rb-1.2.1&q=80&utm_campaign=api-credit&utm_medium=referral&utm_source=unsplash_source&w=1080', - }, - random: { - type: 'channel', - imageUrl: - 'https://images.unsplash.com/photo-1599420186946-7b6fb4e297f0?ixlib=rb-1.2.1&ixid=MnwxMjA3fDF8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1287&q=80', - }, - pm: { - type: 'channel', - imageUrl: - 'https://images.unsplash.com/photo-1599420186946-7b6fb4e297f0?ixlib=rb-1.2.1&ixid=MnwxMjA3fDF8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1287&q=80', - }, - watercooler: { - type: 'channel', - imageUrl: - 'https://images.unsplash.com/photo-1599420186946-7b6fb4e297f0?ixlib=rb-1.2.1&ixid=MnwxMjA3fDF8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1287&q=80', - }, - 'vitalik.eth': { type: 'chat', imageUrl: '' }, - 'pvl.eth': { type: 'chat', imageUrl: '' }, - 'Climate Change': { type: 'group-chat', imageUrl: '' }, -} - -export const useChat = (id: string) => { - return chats[id] -} diff --git a/packages/status-react/src/protocol/use-chats.tsx b/packages/status-react/src/protocol/use-chats.tsx index 1aa1429..c41eb3a 100644 --- a/packages/status-react/src/protocol/use-chats.tsx +++ b/packages/status-react/src/protocol/use-chats.tsx @@ -1,9 +1,24 @@ +import { useMemo } from 'react' import { useClient } from './provider' +import { useCommunity } from '~/src/protocol/use-community' +import { Community } from '@status-im/js' -interface Chat { - type: 'channel' | 'group-chat' | 'chat' -} +export type Channel = Community['communityMetadata']['chats'][0] -export const useChats = (id: string) => { - const client = useClient() +export const useChats = (): Channel => { + const community = useCommunity() + + return useMemo(() => { + return Object.entries(community.chats) + .map(([chatId, chat]) => ({ id: chatId, ...chat })) + .sort((a, b) => { + if (a.position < b.position) { + return -1 + } + if (a.position > b.position) { + return 1 + } + return 0 + }) + }, [community]) } diff --git a/packages/status-react/src/protocol/use-community.tsx b/packages/status-react/src/protocol/use-community.tsx index d97c451..0fc8e29 100644 --- a/packages/status-react/src/protocol/use-community.tsx +++ b/packages/status-react/src/protocol/use-community.tsx @@ -1,11 +1,25 @@ +import { useState, useEffect, useReducer } from 'react' + +import { useClient, useCommunity as useCommunityContext } from './provider' +import { Community } from '@status-im/js' +// return { +// name: 'CryptoKitties', +// description: 'A community of cat lovers, meow!', +// publicKey: '0x2Ef1907d50926...6cEbd975aC5E0Ba', +// imageUrl: +// 'https://images.unsplash.com/photo-1592194996308-7b43878e84a6?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=987&q=80', +// membersCount: 182, +// requestNeeded: true, +// } + +// interface Community { +// name: string +// description: string +// channels: any[] +// } + export const useCommunity = () => { - return { - name: 'CryptoKitties', - description: 'A community of cat lovers, meow!', - publicKey: '0x2Ef1907d50926...6cEbd975aC5E0Ba', - imageUrl: - 'https://images.unsplash.com/photo-1592194996308-7b43878e84a6?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=987&q=80', - membersCount: 182, - requestNeeded: true, - } + const context = useCommunityContext() + + return context } diff --git a/packages/status-react/src/protocol/use-contacts.tsx b/packages/status-react/src/protocol/use-contacts.tsx deleted file mode 100644 index 95e0da1..0000000 --- a/packages/status-react/src/protocol/use-contacts.tsx +++ /dev/null @@ -1,3 +0,0 @@ -export const useContacts = () => { - return [] -} diff --git a/packages/status-react/src/protocol/use-members.tsx b/packages/status-react/src/protocol/use-members.tsx index b1a89be..327cb38 100644 --- a/packages/status-react/src/protocol/use-members.tsx +++ b/packages/status-react/src/protocol/use-members.tsx @@ -1,31 +1,13 @@ -import { useEffect, useRef, useState } from 'react' -import { useClient } from './provider' +import { useCommunity } from '~/src/protocol/use-community' +import { Community } from '@status-im/js' + +export type Member = Community['communityMetadata']['members'][0] + +export const useMembers = (): string[] => { + + const community = useCommunity() + +return Object.keys(community.members) -interface State { - fetching: boolean - stale: boolean - data?: any - error?: Error -} - -export const useMembers = (): State => { - const isMounted = useRef(true) - const client = useClient() - - const [state, setState] = useState({ - fetching: false, - stale: false, - data: undefined, - error: undefined, - }) - - useEffect(() => { - isMounted.current = true - return () => { - isMounted.current = false - } - }, []) - - return state } diff --git a/packages/status-react/src/protocol/use-messages.tsx b/packages/status-react/src/protocol/use-messages.tsx index 7c1299e..188f859 100644 --- a/packages/status-react/src/protocol/use-messages.tsx +++ b/packages/status-react/src/protocol/use-messages.tsx @@ -1,3 +1,10 @@ +import { useEffect, useReducer, useState } from 'react' + +import { useClient } from './provider' +import { useChannel } from './use-channel' + +import type { MessageType } from '@status-im/js' + export type Reaction = | 'heart' | 'thumbs-up' @@ -70,195 +77,241 @@ interface ImageTextReply extends BaseReply { export type Reply = TextReply | ImageReply | ImageTextReply -export const useMessages = (): Message[] => { - return [ - { - id: '1', - type: 'text', - contact: { - name: 'Leila Joyner', - imageUrl: - 'https://images.unsplash.com/photo-1593104547489-5cfb3839a3b5?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=MnwxfDB8MXxyYW5kb218MHx8cGVyc29ufHx8fHx8MTY0OTg0NjI0OQ&ixlib=rb-1.2.1&q=80&utm_campaign=api-credit&utm_medium=referral&utm_source=unsplash_source&w=1080', - }, - text: 'lorem, sit amet ultricies sem magna nec quam. Curabitur vel lectus. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Donec dignissim magna a tortor. Nunc commodo auctor velit. Aliquam nisl. Nulla eu neque pellentesque massa lobortis ultrices. Vivamus rhoncus. Donec est. Nunc ullamcorper, velit in aliquet lobortis, nisi nibh lacinia orci, consectetuer euismod est arcu ac orci. Ut semper pretium neque. Morbi', - owner: false, - pinned: true, - mention: false, - reactions: { - heart: { count: 0, me: false }, - 'thumbs-up': { count: 0, me: false }, - 'thumbs-down': { count: 0, me: false }, - smile: { count: 0, me: false }, - sad: { count: 0, me: false }, - angry: { count: 0, me: false }, - }, - reply: { - contact: { - name: 'Leila Joyner', - imageUrl: - 'https://images.unsplash.com/photo-1593104547489-5cfb3839a3b5?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=MnwxfDB8MXxyYW5kb218MHx8cGVyc29ufHx8fHx8MTY0OTg0NjI0OQ&ixlib=rb-1.2.1&q=80&utm_campaign=api-credit&utm_medium=referral&utm_source=unsplash_source&w=1080', - }, - type: 'text', - text: 'lorem, sit amet ultricies sem magna nec quam. Curabitur vel lectus. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Donec dignissim magna a tortor. Nunc commodo auctor velit. Aliquam nisl. Nulla eu neque pellentesque massa lobortis ultrices. Vivamus rhoncus. Donec est. Nunc ullamcorper, velit in aliquet lobortis, nisi nibh lacinia orci, consectetuer euismod est arcu ac orci. Ut semper pretium neque. Morbi', - }, - }, - { - id: '2', - type: 'text', - contact: { - name: 'Velma Mccarthy', - imageUrl: - 'https://images.unsplash.com/photo-1522075469751-3a6694fb2f61?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=MnwxfDB8MXxyYW5kb218MHx8cGVyc29ufHx8fHx8MTY0OTg0NjMwOQ&ixlib=rb-1.2.1&q=80&utm_campaign=api-credit&utm_medium=referral&utm_source=unsplash_source&w=1080', - }, - text: 'est tempor bibendum. Donec felis orci, adipiscing non, luctus sit amet, faucibus ut, nulla. Cras eu tellus eu augue porttitor interdum. Sed auctor odio a purus. Duis elementum, dui quis accumsan convallis, ante lectus convallis est, vitae sodales nisi magna sed dui. Fusce aliquam, enim nec tempus scelerisque, lorem ipsum sodales purus, in molestie tortor nibh sit amet orci. Ut sagittis lobortis mauris. Suspendisse aliquet molestie tellus. Aenean egestas hendrerit neque. In ornare sagittis felis. Donec tempor, est ac mattis semper, dui lectus rutrum urna, nec luctus felis', - owner: false, - pinned: false, - mention: false, - reactions: { - heart: { count: 0, me: false }, - 'thumbs-up': { count: 0, me: false }, - 'thumbs-down': { count: 0, me: false }, - smile: { count: 0, me: false }, - sad: { count: 0, me: false }, - angry: { count: 0, me: false }, - }, - reply: { - contact: { - name: 'Leila Joyner', - imageUrl: - 'https://images.unsplash.com/photo-1593104547489-5cfb3839a3b5?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=MnwxfDB8MXxyYW5kb218MHx8cGVyc29ufHx8fHx8MTY0OTg0NjI0OQ&ixlib=rb-1.2.1&q=80&utm_campaign=api-credit&utm_medium=referral&utm_source=unsplash_source&w=1080', - }, - type: 'image', - imageUrl: - 'https://images.unsplash.com/photo-1647531041383-fe7103712f16?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1287&q=80', - }, - }, - { - id: '3', - type: 'text', - contact: { - name: 'Gareth Garrison', - imageUrl: - 'https://images.unsplash.com/photo-1615473967657-9dc21773daa3?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=MnwxfDB8MXxyYW5kb218MHx8cGVyc29ufHx8fHx8MTY0OTg0NjMwNg&ixlib=rb-1.2.1&q=80&utm_campaign=api-credit&utm_medium=referral&utm_source=unsplash_source&w=1080', - }, - text: 'elit erat vitae risus. @satoshi Duis a mi fringilla mi lacinia mattis. Integer eu lacus. Quisque imperdiet, erat nonummy ultricies ornare, elit elit fermentum risus, at fringilla purus mauris a nunc. In at pede. Cras vulputate velit eu sem. Pellentesque ut ipsum ac mi eleifend egestas. Sed pharetra, felis', - owner: false, - pinned: false, - mention: true, - reactions: { - heart: { count: 1, me: false }, - 'thumbs-up': { count: 1, me: false }, - 'thumbs-down': { count: 3, me: true }, - smile: { count: 0, me: false }, - sad: { count: 0, me: false }, - angry: { count: 0, me: false }, - }, - reply: { - contact: { - name: 'Leila Joyner', - imageUrl: - 'https://images.unsplash.com/photo-1593104547489-5cfb3839a3b5?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=MnwxfDB8MXxyYW5kb218MHx8cGVyc29ufHx8fHx8MTY0OTg0NjI0OQ&ixlib=rb-1.2.1&q=80&utm_campaign=api-credit&utm_medium=referral&utm_source=unsplash_source&w=1080', - }, - type: 'image-text', - text: 'lorem, sit amet ultricies sem magna nec quam. Curabitur vel lectus. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Donec dignissim magna a tortor. Nunc commodo auctor velit. Aliquam nisl. Nulla eu neque pellentesque massa lobortis ultrices. Vivamus rhoncus. Donec est. Nunc ullamcorper, velit in aliquet lobortis, nisi nibh lacinia orci, consectetuer euismod est arcu ac orci. Ut semper pretium neque. Morbi', - imageUrl: - 'https://images.unsplash.com/photo-1647531041383-fe7103712f16?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1287&q=80', - }, - }, - { - id: '4', - type: 'image', - contact: { - name: 'Celeste Suarez', - imageUrl: '', - }, - imageUrl: - 'https://images.unsplash.com/photo-1647531041383-fe7103712f16?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1287&q=80', - owner: false, - pinned: false, - mention: false, - reactions: { - heart: { count: 0, me: false }, - 'thumbs-up': { count: 0, me: false }, - 'thumbs-down': { count: 0, me: false }, - smile: { count: 0, me: false }, - sad: { count: 0, me: false }, - angry: { count: 0, me: false }, - }, - }, - { - id: '5', - type: 'text', - contact: { - name: 'Satoshi', - imageUrl: - 'https://images.unsplash.com/photo-1542838686-37da4a9fd1b3?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=MnwxfDB8MXxyYW5kb218MHx8cGVyc29ufHx8fHx8MTY0OTg0NjMwNA&ixlib=rb-1.2.1&q=80&utm_campaign=api-credit&utm_medium=referral&utm_source=unsplash_source&w=1080', - }, - text: 'tincidunt tempus risus. Donec egestas. Duis ac arcu. Nunc mauris. Morbi non sapien molestie orci tincidunt adipiscing. Mauris molestie pharetra nibh. Aliquam ornare, libero at auctor ullamcorper, nisl arcu iaculis enim, sit amet ornare lectus justo eu arcu. Morbi sit amet massa. Quisque porttitor eros nec tellus. Nunc lectus pede, ultrices a, auctor non, feugiat nec, diam. Duis mi', - owner: true, - pinned: false, - mention: false, - reactions: { - heart: { count: 0, me: false }, - 'thumbs-up': { count: 0, me: false }, - 'thumbs-down': { count: 0, me: false }, - smile: { count: 0, me: false }, - sad: { count: 1, me: false }, - angry: { count: 1, me: true }, - }, - reply: { - contact: { - name: 'Leila Joyner', - imageUrl: - 'https://images.unsplash.com/photo-1593104547489-5cfb3839a3b5?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=MnwxfDB8MXxyYW5kb218MHx8cGVyc29ufHx8fHx8MTY0OTg0NjI0OQ&ixlib=rb-1.2.1&q=80&utm_campaign=api-credit&utm_medium=referral&utm_source=unsplash_source&w=1080', - }, - type: 'text', - text: 'tincidunt tempus risus. Donec egestas. Duis ac arcu. Nunc mauris. Morbi non sapien molestie orci tincidunt adipiscing. Mauris molestie pharetra nibh. Aliquam ornare, libero at auctor ullamcorper, nisl arcu iaculis enim, sit amet ornare lectus justo eu arcu. Morbi sit amet massa. Quisque porttitor eros nec tellus. Nunc lectus pede, ultrices a, auctor non, feugiat nec, diam. Duis mi', - }, - }, - { - id: '6', - type: 'image-text', - contact: { - name: 'Leila Joyner', - imageUrl: - 'https://images.unsplash.com/photo-1593104547489-5cfb3839a3b5?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=MnwxfDB8MXxyYW5kb218MHx8cGVyc29ufHx8fHx8MTY0OTg0NjI0OQ&ixlib=rb-1.2.1&q=80&utm_campaign=api-credit&utm_medium=referral&utm_source=unsplash_source&w=1080', - }, - text: 'lorem, sit amet ultricies sem magna nec quam.', - imageUrl: - 'https://images.unsplash.com/photo-1647531041383-fe7103712f16?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1287&q=80', - owner: false, - pinned: false, - mention: false, - reactions: { - heart: { count: 0, me: false }, - 'thumbs-up': { count: 10, me: true }, - 'thumbs-down': { count: 3, me: false }, - smile: { count: 0, me: false }, - sad: { count: 0, me: false }, - angry: { count: 0, me: false }, - }, - }, - { - id: '5', - type: 'text', - contact: { - name: 'Satoshi', - imageUrl: - 'https://images.unsplash.com/photo-1542838686-37da4a9fd1b3?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=MnwxfDB8MXxyYW5kb218MHx8cGVyc29ufHx8fHx8MTY0OTg0NjMwNA&ixlib=rb-1.2.1&q=80&utm_campaign=api-credit&utm_medium=referral&utm_source=unsplash_source&w=1080', - }, - text: 'tincidunt tempus risus. Donec egestas. Duis ac arcu. Nunc mauris. Morbi non sapien molestie orci tincidunt adipiscing. Mauris molestie pharetra nibh. Aliquam ornare, libero at auctor ullamcorper, nisl arcu iaculis enim, sit amet ornare lectus justo eu arcu. Morbi sit amet massa. Quisque porttitor eros nec tellus. Nunc lectus pede, ultrices a, auctor non, feugiat nec, diam. Duis mi', - owner: true, - pinned: false, - mention: true, - reactions: { - heart: { count: 0, me: false }, - 'thumbs-up': { count: 0, me: false }, - 'thumbs-down': { count: 0, me: false }, - smile: { count: 0, me: false }, - sad: { count: 0, me: false }, - angry: { count: 0, me: false }, - }, - }, - ] +export type { MessageType } + +interface Result { + data: MessageType[] + loading: boolean + error?: Error +} + +export const useMessages = (channelId: string): Result => { + const client = useClient() + + // const [state, dispatch] = useReducer((state,action) => {}, {}) + + const [data, setData] = useState(() => client.community.getMessages(channelId)) + const [loading, setLoading] = useState(true) + const [error, setError] = useState() + + useEffect(() => { + + console.log("subscribed to", channelId) + + setData(client.community.getMessages(channelId)) + + const handleUpdate = (messages: MessageType[]) => { + setLoading(false) + setData(messages) + console.log('update for',channelId, messages) + } +const unsubscribe= client.community.onChannelMessageUpdate(channelId, handleUpdate) + return () => + { + + unsubscribe() + console.log("unsubscribed from", channelId) + + } + }, [channelId]) + + return { + data, + loading, + error, + // hasMore + // fetchMore + // refetch + } + + // return [ + // { + // id: '1', + // type: 'text', + // contact: { + // name: 'Leila Joyner', + // imageUrl: + // 'https://images.unsplash.com/photo-1593104547489-5cfb3839a3b5?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=MnwxfDB8MXxyYW5kb218MHx8cGVyc29ufHx8fHx8MTY0OTg0NjI0OQ&ixlib=rb-1.2.1&q=80&utm_campaign=api-credit&utm_medium=referral&utm_source=unsplash_source&w=1080', + // }, + // text: 'lorem, sit amet ultricies sem magna nec quam. Curabitur vel lectus. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Donec dignissim magna a tortor. Nunc commodo auctor velit. Aliquam nisl. Nulla eu neque pellentesque massa lobortis ultrices. Vivamus rhoncus. Donec est. Nunc ullamcorper, velit in aliquet lobortis, nisi nibh lacinia orci, consectetuer euismod est arcu ac orci. Ut semper pretium neque. Morbi', + // owner: false, + // pinned: true, + // mention: false, + // reactions: { + // heart: { count: 0, me: false }, + // 'thumbs-up': { count: 0, me: false }, + // 'thumbs-down': { count: 0, me: false }, + // smile: { count: 0, me: false }, + // sad: { count: 0, me: false }, + // angry: { count: 0, me: false }, + // }, + // reply: { + // contact: { + // name: 'Leila Joyner', + // imageUrl: + // 'https://images.unsplash.com/photo-1593104547489-5cfb3839a3b5?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=MnwxfDB8MXxyYW5kb218MHx8cGVyc29ufHx8fHx8MTY0OTg0NjI0OQ&ixlib=rb-1.2.1&q=80&utm_campaign=api-credit&utm_medium=referral&utm_source=unsplash_source&w=1080', + // }, + // type: 'text', + // text: 'lorem, sit amet ultricies sem magna nec quam. Curabitur vel lectus. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Donec dignissim magna a tortor. Nunc commodo auctor velit. Aliquam nisl. Nulla eu neque pellentesque massa lobortis ultrices. Vivamus rhoncus. Donec est. Nunc ullamcorper, velit in aliquet lobortis, nisi nibh lacinia orci, consectetuer euismod est arcu ac orci. Ut semper pretium neque. Morbi', + // }, + // }, + // { + // id: '2', + // type: 'text', + // contact: { + // name: 'Velma Mccarthy', + // imageUrl: + // 'https://images.unsplash.com/photo-1522075469751-3a6694fb2f61?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=MnwxfDB8MXxyYW5kb218MHx8cGVyc29ufHx8fHx8MTY0OTg0NjMwOQ&ixlib=rb-1.2.1&q=80&utm_campaign=api-credit&utm_medium=referral&utm_source=unsplash_source&w=1080', + // }, + // text: 'est tempor bibendum. Donec felis orci, adipiscing non, luctus sit amet, faucibus ut, nulla. Cras eu tellus eu augue porttitor interdum. Sed auctor odio a purus. Duis elementum, dui quis accumsan convallis, ante lectus convallis est, vitae sodales nisi magna sed dui. Fusce aliquam, enim nec tempus scelerisque, lorem ipsum sodales purus, in molestie tortor nibh sit amet orci. Ut sagittis lobortis mauris. Suspendisse aliquet molestie tellus. Aenean egestas hendrerit neque. In ornare sagittis felis. Donec tempor, est ac mattis semper, dui lectus rutrum urna, nec luctus felis', + // owner: false, + // pinned: false, + // mention: false, + // reactions: { + // heart: { count: 0, me: false }, + // 'thumbs-up': { count: 0, me: false }, + // 'thumbs-down': { count: 0, me: false }, + // smile: { count: 0, me: false }, + // sad: { count: 0, me: false }, + // angry: { count: 0, me: false }, + // }, + // reply: { + // contact: { + // name: 'Leila Joyner', + // imageUrl: + // 'https://images.unsplash.com/photo-1593104547489-5cfb3839a3b5?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=MnwxfDB8MXxyYW5kb218MHx8cGVyc29ufHx8fHx8MTY0OTg0NjI0OQ&ixlib=rb-1.2.1&q=80&utm_campaign=api-credit&utm_medium=referral&utm_source=unsplash_source&w=1080', + // }, + // type: 'image', + // imageUrl: + // 'https://images.unsplash.com/photo-1647531041383-fe7103712f16?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1287&q=80', + // }, + // }, + // { + // id: '3', + // type: 'text', + // contact: { + // name: 'Gareth Garrison', + // imageUrl: + // 'https://images.unsplash.com/photo-1615473967657-9dc21773daa3?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=MnwxfDB8MXxyYW5kb218MHx8cGVyc29ufHx8fHx8MTY0OTg0NjMwNg&ixlib=rb-1.2.1&q=80&utm_campaign=api-credit&utm_medium=referral&utm_source=unsplash_source&w=1080', + // }, + // text: 'elit erat vitae risus. @satoshi Duis a mi fringilla mi lacinia mattis. Integer eu lacus. Quisque imperdiet, erat nonummy ultricies ornare, elit elit fermentum risus, at fringilla purus mauris a nunc. In at pede. Cras vulputate velit eu sem. Pellentesque ut ipsum ac mi eleifend egestas. Sed pharetra, felis', + // owner: false, + // pinned: false, + // mention: true, + // reactions: { + // heart: { count: 1, me: false }, + // 'thumbs-up': { count: 1, me: false }, + // 'thumbs-down': { count: 3, me: true }, + // smile: { count: 0, me: false }, + // sad: { count: 0, me: false }, + // angry: { count: 0, me: false }, + // }, + // reply: { + // contact: { + // name: 'Leila Joyner', + // imageUrl: + // 'https://images.unsplash.com/photo-1593104547489-5cfb3839a3b5?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=MnwxfDB8MXxyYW5kb218MHx8cGVyc29ufHx8fHx8MTY0OTg0NjI0OQ&ixlib=rb-1.2.1&q=80&utm_campaign=api-credit&utm_medium=referral&utm_source=unsplash_source&w=1080', + // }, + // type: 'image-text', + // text: 'lorem, sit amet ultricies sem magna nec quam. Curabitur vel lectus. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Donec dignissim magna a tortor. Nunc commodo auctor velit. Aliquam nisl. Nulla eu neque pellentesque massa lobortis ultrices. Vivamus rhoncus. Donec est. Nunc ullamcorper, velit in aliquet lobortis, nisi nibh lacinia orci, consectetuer euismod est arcu ac orci. Ut semper pretium neque. Morbi', + // imageUrl: + // 'https://images.unsplash.com/photo-1647531041383-fe7103712f16?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1287&q=80', + // }, + // }, + // { + // id: '4', + // type: 'image', + // contact: { + // name: 'Celeste Suarez', + // imageUrl: '', + // }, + // imageUrl: + // 'https://images.unsplash.com/photo-1647531041383-fe7103712f16?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1287&q=80', + // owner: false, + // pinned: false, + // mention: false, + // reactions: { + // heart: { count: 0, me: false }, + // 'thumbs-up': { count: 0, me: false }, + // 'thumbs-down': { count: 0, me: false }, + // smile: { count: 0, me: false }, + // sad: { count: 0, me: false }, + // angry: { count: 0, me: false }, + // }, + // }, + // { + // id: '5', + // type: 'text', + // contact: { + // name: 'Satoshi', + // imageUrl: + // 'https://images.unsplash.com/photo-1542838686-37da4a9fd1b3?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=MnwxfDB8MXxyYW5kb218MHx8cGVyc29ufHx8fHx8MTY0OTg0NjMwNA&ixlib=rb-1.2.1&q=80&utm_campaign=api-credit&utm_medium=referral&utm_source=unsplash_source&w=1080', + // }, + // text: 'tincidunt tempus risus. Donec egestas. Duis ac arcu. Nunc mauris. Morbi non sapien molestie orci tincidunt adipiscing. Mauris molestie pharetra nibh. Aliquam ornare, libero at auctor ullamcorper, nisl arcu iaculis enim, sit amet ornare lectus justo eu arcu. Morbi sit amet massa. Quisque porttitor eros nec tellus. Nunc lectus pede, ultrices a, auctor non, feugiat nec, diam. Duis mi', + // owner: true, + // pinned: false, + // mention: false, + // reactions: { + // heart: { count: 0, me: false }, + // 'thumbs-up': { count: 0, me: false }, + // 'thumbs-down': { count: 0, me: false }, + // smile: { count: 0, me: false }, + // sad: { count: 1, me: false }, + // angry: { count: 1, me: true }, + // }, + // reply: { + // contact: { + // name: 'Leila Joyner', + // imageUrl: + // 'https://images.unsplash.com/photo-1593104547489-5cfb3839a3b5?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=MnwxfDB8MXxyYW5kb218MHx8cGVyc29ufHx8fHx8MTY0OTg0NjI0OQ&ixlib=rb-1.2.1&q=80&utm_campaign=api-credit&utm_medium=referral&utm_source=unsplash_source&w=1080', + // }, + // type: 'text', + // text: 'tincidunt tempus risus. Donec egestas. Duis ac arcu. Nunc mauris. Morbi non sapien molestie orci tincidunt adipiscing. Mauris molestie pharetra nibh. Aliquam ornare, libero at auctor ullamcorper, nisl arcu iaculis enim, sit amet ornare lectus justo eu arcu. Morbi sit amet massa. Quisque porttitor eros nec tellus. Nunc lectus pede, ultrices a, auctor non, feugiat nec, diam. Duis mi', + // }, + // }, + // { + // id: '6', + // type: 'image-text', + // contact: { + // name: 'Leila Joyner', + // imageUrl: + // 'https://images.unsplash.com/photo-1593104547489-5cfb3839a3b5?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=MnwxfDB8MXxyYW5kb218MHx8cGVyc29ufHx8fHx8MTY0OTg0NjI0OQ&ixlib=rb-1.2.1&q=80&utm_campaign=api-credit&utm_medium=referral&utm_source=unsplash_source&w=1080', + // }, + // text: 'lorem, sit amet ultricies sem magna nec quam.', + // imageUrl: + // 'https://images.unsplash.com/photo-1647531041383-fe7103712f16?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1287&q=80', + // owner: false, + // pinned: false, + // mention: false, + // reactions: { + // heart: { count: 0, me: false }, + // 'thumbs-up': { count: 10, me: true }, + // 'thumbs-down': { count: 3, me: false }, + // smile: { count: 0, me: false }, + // sad: { count: 0, me: false }, + // angry: { count: 0, me: false }, + // }, + // }, + // { + // id: '5', + // type: 'text', + // contact: { + // name: 'Satoshi', + // imageUrl: + // 'https://images.unsplash.com/photo-1542838686-37da4a9fd1b3?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=MnwxfDB8MXxyYW5kb218MHx8cGVyc29ufHx8fHx8MTY0OTg0NjMwNA&ixlib=rb-1.2.1&q=80&utm_campaign=api-credit&utm_medium=referral&utm_source=unsplash_source&w=1080', + // }, + // text: 'tincidunt tempus risus. Donec egestas. Duis ac arcu. Nunc mauris. Morbi non sapien molestie orci tincidunt adipiscing. Mauris molestie pharetra nibh. Aliquam ornare, libero at auctor ullamcorper, nisl arcu iaculis enim, sit amet ornare lectus justo eu arcu. Morbi sit amet massa. Quisque porttitor eros nec tellus. Nunc lectus pede, ultrices a, auctor non, feugiat nec, diam. Duis mi', + // owner: true, + // pinned: false, + // mention: true, + // reactions: { + // heart: { count: 0, me: false }, + // 'thumbs-up': { count: 0, me: false }, + // 'thumbs-down': { count: 0, me: false }, + // smile: { count: 0, me: false }, + // sad: { count: 0, me: false }, + // angry: { count: 0, me: false }, + // }, + // }, + // ] } diff --git a/packages/status-react/src/protocol/use-profile.tsx b/packages/status-react/src/protocol/use-profile.tsx deleted file mode 100644 index 722f521..0000000 --- a/packages/status-react/src/protocol/use-profile.tsx +++ /dev/null @@ -1,8 +0,0 @@ -export const useProfile = () => { - return { - name: 'Satoshi', - publicKey: '71C7656EC7ab88b098defB751B7401B5f6d8976F', - imageUrl: - 'https://images.unsplash.com/photo-1546776310-eef45dd6d63c?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1620&q=80', - } -} diff --git a/packages/status-react/src/routes/chat/components/chat-info/index.tsx b/packages/status-react/src/routes/chat/components/chat-info/index.tsx index 6d94ae5..834a009 100644 --- a/packages/status-react/src/routes/chat/components/chat-info/index.tsx +++ b/packages/status-react/src/routes/chat/components/chat-info/index.tsx @@ -7,72 +7,73 @@ import { Avatar, DialogTrigger, Flex, Text } from '~/src/system' import { PinnedMessagesDialog } from './pinned-messages-dialog' -import type { Chat } from '~/src/protocol/use-chat' +import type { Channel } from '~/src/protocol' interface Props { - chat: Chat + chat: Channel } export const ChatInfo = (props: Props) => { const { chat } = props - const { params } = useMatch(':id')! // eslint-disable-line @typescript-eslint/no-non-null-assertion + console.log("file: index.tsx > line 18 > ChatInfo > chat", chat) - if (chat.type == 'channel') { - return ( - - -
- #{params.id} - - - - - - {' '} - | General discussions about CryptoKitties. - - -
-
- ) - } - - if (chat.type == 'group-chat') { - return ( - - -
- Climate Change - - - - 2 pinned messages - - - - - | 5 members - - -
-
- ) - } + // if (chat.type == 'channel') { return ( - +
- pvl.eth - - 0x63FaC9201494f0bd17B9892B9fae4d52fe3BD377 - + #{chat.identity?.displayName} + + + {chat.identity?.description} + +
) + + // + // + // + // {' '} + // | + // } + + // if (chat.type == 'group-chat') { + // return ( + // + // + //
+ // Climate Change + // + // + // + // 2 pinned messages + // + // + // + // + // | 5 members + // + // + //
+ //
+ // ) + // } + + // return ( + // + // + //
+ // pvl.eth + // + // 0x63FaC9201494f0bd17B9892B9fae4d52fe3BD377 + // + //
+ //
+ // ) } diff --git a/packages/status-react/src/routes/chat/components/chat-message/index.tsx b/packages/status-react/src/routes/chat/components/chat-message/index.tsx index b3b94b0..b4bd5bf 100644 --- a/packages/status-react/src/routes/chat/components/chat-message/index.tsx +++ b/packages/status-react/src/routes/chat/components/chat-message/index.tsx @@ -25,10 +25,10 @@ import { Actions } from './actions' import { MessageReply } from './message-reply' import { MessageReactions } from './reactions' -import type { Message } from '~/src/protocol/use-messages' +import type { MessageType } from '~/src/protocol/use-messages' interface Props { - message: Message + message: MessageType } // const MessageLink = forwardRef(function MessageLink( @@ -54,8 +54,14 @@ interface Props { export const ChatMessage = (props: Props) => { const { message } = props + console.log("🚀 > message", message) - const { type, contact, owner, mention, pinned, reply, reactions } = message + // const { type, contact, owner, mention, pinned, reply, reactions } = message + const owner=false + const mention=false + const pinned = false + const reply = false + const { contentType, text, displayName, reactions } = message const [editing, setEditing] = useState(false) const [reacting, setReacting] = useState(false) @@ -72,7 +78,7 @@ export const ChatMessage = (props: Props) => { } const handlePinClick = () => { - console.log(pinned) + // console.log(pinned) } const handleReaction = (reaction: string) => { @@ -98,8 +104,8 @@ export const ChatMessage = (props: Props) => { ) } - switch (type) { - case 'text': { + switch (contentType) { + case 'TEXT_PLAIN': { // // // https://specs.status.im/spec @@ -155,19 +161,20 @@ export const ChatMessage = (props: Props) => { - - {contact.name} + {/* */} + {/* {contact.name} */} + {displayName} } onSelect={() => - userProfileDialog.open({ name: contact.name }) + userProfileDialog.open({ name: displayName }) } > View Profile @@ -199,7 +206,8 @@ export const ChatMessage = (props: Props) => { - {contact.name} + {displayName} + {/* {contact.name} */} 10:00 AM diff --git a/packages/status-react/src/routes/chat/components/navbar/index.tsx b/packages/status-react/src/routes/chat/components/navbar/index.tsx index fa060c3..1978dc5 100644 --- a/packages/status-react/src/routes/chat/components/navbar/index.tsx +++ b/packages/status-react/src/routes/chat/components/navbar/index.tsx @@ -7,7 +7,7 @@ import { useAppState } from '~/src/contexts/app-context' import { BellIcon } from '~/src/icons/bell-icon' import { DotsIcon } from '~/src/icons/dots-icon' import { GroupIcon } from '~/src/icons/group-icon' -import { useChat } from '~/src/protocol/use-chat' +import { useChannel } from '~/src/protocol' import { styled } from '~/src/styles/config' import { DropdownMenuTrigger, Flex, IconButton, Separator } from '~/src/system' @@ -23,7 +23,7 @@ export const Navbar = (props: Props) => { const { state, dispatch } = useAppState() const { params } = useMatch(':id')! // eslint-disable-line @typescript-eslint/no-non-null-assertion - const chat = useChat(params.id!) + const chat = useChannel(params.id!) return ( @@ -44,7 +44,7 @@ export const Navbar = (props: Props) => { - + diff --git a/packages/status-react/src/routes/chat/index.tsx b/packages/status-react/src/routes/chat/index.tsx index f9c3f2d..044c563 100644 --- a/packages/status-react/src/routes/chat/index.tsx +++ b/packages/status-react/src/routes/chat/index.tsx @@ -5,7 +5,7 @@ import { useMatch } from 'react-router-dom' import { MemberSidebar } from '~/src/components/member-sidebar' import { useAppState } from '~/src/contexts/app-context' import { ChatProvider } from '~/src/contexts/chat-context' -import { useChat } from '~/src/protocol/use-chat' +import { useChannel } from '~/src/protocol' import { useMessages } from '~/src/protocol/use-messages' import { styled } from '~/src/styles/config' import { Avatar, Flex, Heading, Text } from '~/src/system' @@ -18,13 +18,15 @@ const ChatStart = () => { // TODO: unify this with the useChat hook const { params } = useMatch(':id')! // eslint-disable-line @typescript-eslint/no-non-null-assertion - const chat = useChat(params.id!) + const chat = useChannel(params.id!) return ( - - general - Welcome to the beginning of the #general channel! + {/* */} + {chat.identity?.displayName} + + Welcome to the beginning of the #{chat.identity?.displayName} channel! + ) } @@ -32,18 +34,20 @@ const ChatStart = () => { const Content = () => { const contentRef = useRef(null) + const { params } = useMatch(':id')! // eslint-disable-line @typescript-eslint/no-non-null-assertion + useEffect(() => { // eslint-disable-next-line @typescript-eslint/no-non-null-assertion contentRef.current!.scrollTop = contentRef.current!.scrollHeight ?? 0 }, []) - const messages = useMessages() + const messages = useMessages(params.id!) return ( - {messages.map(message => ( - + {messages.data.map(message => ( + ))} ) diff --git a/packages/status-react/src/routes/index.tsx b/packages/status-react/src/routes/index.tsx index 640780f..1d917da 100644 --- a/packages/status-react/src/routes/index.tsx +++ b/packages/status-react/src/routes/index.tsx @@ -2,6 +2,7 @@ import React from 'react' import { BrowserRouter, Route, Routes } from 'react-router-dom' +import { ClientProvider } from '~/src/protocol' import { MainSidebar } from '~/src/components/main-sidebar' import { AppProvider } from '~/src/contexts/app-context' import { DialogProvider } from '~/src/contexts/dialog-context' @@ -23,18 +24,20 @@ export const Community = (props: Props) => { return ( - - - - - - - } /> - {/* } /> */} - - - - + + + + + + + + } /> + {/* } /> */} + + + + + ) diff --git a/packages/status-react/src/system/eth-address/eth-address.tsx b/packages/status-react/src/system/eth-address/eth-address.tsx index 589c6e1..a041201 100644 --- a/packages/status-react/src/system/eth-address/eth-address.tsx +++ b/packages/status-react/src/system/eth-address/eth-address.tsx @@ -13,7 +13,7 @@ const EthAddress = (props: Props) => { return ( - 0x{children.substring(0, 3)}...{children.substring(children.length - 3)} + {children.substring(0, 5)}...{children.substring(children.length - 3)} ) } diff --git a/babel.config.js b/xbabel.config.js similarity index 100% rename from babel.config.js rename to xbabel.config.js diff --git a/yarn.lock b/yarn.lock index 33cdc7c..b91f683 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1556,7 +1556,7 @@ "@parcel/transformer-react-refresh-wrap" "2.3.2" "@parcel/transformer-svg" "2.3.2" -"@parcel/config-default@^2.6.0": +"@parcel/config-default@2.6.0", "@parcel/config-default@^2.6.0": version "2.6.0" resolved "https://registry.yarnpkg.com/@parcel/config-default/-/config-default-2.6.0.tgz#2cc9a05d195a97a93b6e14cbbda96d47d2ccf118" integrity sha512-DXovFPhZITmTvFaSEdC8RRqROs9FLIJ4u8yFSU6FUyq2wpvtYVRXXoDrvXgClh2csXmK7JTJTp5JF7r0rd2UaA== @@ -1622,6 +1622,36 @@ nullthrows "^1.1.1" semver "^5.7.1" +"@parcel/core@2.6.0": + version "2.6.0" + resolved "https://registry.yarnpkg.com/@parcel/core/-/core-2.6.0.tgz#dad1f5f529ffb47df772c155ef09119d3294538c" + integrity sha512-8OOWbPuxpFydpwNyKoz6d3e3O4DmxNYmMw4DXwrPSj/jyg7oa+SDtMT0/VXEhujE0HYkQPCHt4npRajkSuf99A== + dependencies: + "@mischnic/json-sourcemap" "^0.1.0" + "@parcel/cache" "2.6.0" + "@parcel/diagnostic" "2.6.0" + "@parcel/events" "2.6.0" + "@parcel/fs" "2.6.0" + "@parcel/graph" "2.6.0" + "@parcel/hash" "2.6.0" + "@parcel/logger" "2.6.0" + "@parcel/package-manager" "2.6.0" + "@parcel/plugin" "2.6.0" + "@parcel/source-map" "^2.0.0" + "@parcel/types" "2.6.0" + "@parcel/utils" "2.6.0" + "@parcel/workers" "2.6.0" + abortcontroller-polyfill "^1.1.9" + base-x "^3.0.8" + browserslist "^4.6.6" + clone "^2.1.1" + dotenv "^7.0.0" + dotenv-expand "^5.1.0" + json5 "^2.2.0" + msgpackr "^1.5.4" + nullthrows "^1.1.1" + semver "^5.7.1" + "@parcel/css-darwin-arm64@1.9.0": version "1.9.0" resolved "https://registry.yarnpkg.com/@parcel/css-darwin-arm64/-/css-darwin-arm64-1.9.0.tgz#5a020c604249180afcf69ce0f6978b807e2011b3" @@ -1748,6 +1778,14 @@ "@parcel/utils" "2.3.2" nullthrows "^1.1.1" +"@parcel/graph@2.6.0": + version "2.6.0" + resolved "https://registry.yarnpkg.com/@parcel/graph/-/graph-2.6.0.tgz#04f9660333e314a51af38483efefd766a5841bb0" + integrity sha512-rxrAzWm6rwbCRPbu0Z+zwMscpG8omffODniVWPlX2G0jgQGpjKsutBQ6RMfFIcfaQ4MzL3pIQOTf8bkjQOPsbg== + dependencies: + "@parcel/utils" "2.6.0" + nullthrows "^1.1.1" + "@parcel/hash@2.3.2": version "2.3.2" resolved "https://registry.yarnpkg.com/@parcel/hash/-/hash-2.3.2.tgz#33b8ff04bb44f6661bdc1054b302ef1b6bd3acb3" @@ -2069,12 +2107,12 @@ "@parcel/utils" "2.6.0" posthtml "^0.16.4" -"@parcel/packager-ts@2.3.2": - version "2.3.2" - resolved "https://registry.yarnpkg.com/@parcel/packager-ts/-/packager-ts-2.3.2.tgz#e2092e89bd7ee48b7b217ab9c4374c7ceef17e13" - integrity sha512-8Yb0N8Rnvj4Ag+jd2nTJPSrUKZxFEqju77cTRkkg4jFSNSFw8GTRsappNAyCID0W1tjcTyxMTfxetPmCC1Xm9g== +"@parcel/packager-ts@^2.6.0": + version "2.6.0" + resolved "https://registry.yarnpkg.com/@parcel/packager-ts/-/packager-ts-2.6.0.tgz#7e4a19e880f5e097906812f49bf62fa40e356df8" + integrity sha512-JDKmXHUEGsMy6plDigPKkNhXWPm/6KIvXpg7/WMswzO23adtgWr2x9yz1Filt4JO0lxnDrdRJedUJxTwaDSYiA== dependencies: - "@parcel/plugin" "2.3.2" + "@parcel/plugin" "2.6.0" "@parcel/plugin@2.3.2": version "2.3.2" @@ -2100,6 +2138,17 @@ "@parcel/utils" "2.3.2" chalk "^4.1.0" +"@parcel/reporter-cli@2.6.0": + version "2.6.0" + resolved "https://registry.yarnpkg.com/@parcel/reporter-cli/-/reporter-cli-2.6.0.tgz#09fe5a8eecc368f2bcaaf6ab8154378bab0e0242" + integrity sha512-QFG957NXx3L0D8Zw0+B2j7IHy8f/UzOVu6VvKE3rMkhq/iR2qLrPohQ+uvxlee+CLC0cG2qRSgJ7Ve/rjQPoJg== + dependencies: + "@parcel/plugin" "2.6.0" + "@parcel/types" "2.6.0" + "@parcel/utils" "2.6.0" + chalk "^4.1.0" + term-size "^2.2.1" + "@parcel/reporter-dev-server@2.3.2": version "2.3.2" resolved "https://registry.yarnpkg.com/@parcel/reporter-dev-server/-/reporter-dev-server-2.3.2.tgz#46ee4c53ad08c8b8afd2c79fb37381b6ba55cfb5" @@ -2474,21 +2523,21 @@ posthtml-render "^3.0.0" semver "^5.7.1" -"@parcel/transformer-typescript-types@2.3.2": - version "2.3.2" - resolved "https://registry.yarnpkg.com/@parcel/transformer-typescript-types/-/transformer-typescript-types-2.3.2.tgz#569d06d876594766d48c7bca79464f49e7fe5002" - integrity sha512-iYPah0UK2o10bsk5Ukr7meyI5vA27mnBWXHp8bOG5a2pJ/UzS1B7IKbgHBOzLmvpD7lSDfPLGpmbSWbIhsI5+g== +"@parcel/transformer-typescript-types@^2.6.0": + version "2.6.0" + resolved "https://registry.yarnpkg.com/@parcel/transformer-typescript-types/-/transformer-typescript-types-2.6.0.tgz#27297a8c59572bd00f54a6156f586f7da5570f7f" + integrity sha512-T1ul8EFc/VxJp6jywDLKV66P0YseeROsZ/kwKpfJezua2mRO4iH2O6G9v5jn2H1C+bb6/M0JvfJqRxLj2Himlw== dependencies: - "@parcel/diagnostic" "2.3.2" - "@parcel/plugin" "2.3.2" + "@parcel/diagnostic" "2.6.0" + "@parcel/plugin" "2.6.0" "@parcel/source-map" "^2.0.0" - "@parcel/ts-utils" "2.3.2" + "@parcel/ts-utils" "2.6.0" nullthrows "^1.1.1" -"@parcel/ts-utils@2.3.2": - version "2.3.2" - resolved "https://registry.yarnpkg.com/@parcel/ts-utils/-/ts-utils-2.3.2.tgz#d63f7027574f3c1a128e1c865d683d6aacb4476d" - integrity sha512-jYCHoSmU+oVtFA4q0BygVf74FpVnCDSNtVfLzd1EfGVHlBFMo9GzSY5luMTG4qhnNCDEEco89bkMIgjPHQ3qnA== +"@parcel/ts-utils@2.6.0": + version "2.6.0" + resolved "https://registry.yarnpkg.com/@parcel/ts-utils/-/ts-utils-2.6.0.tgz#f9b07be2faa62862933741669b758ce56e5c15bf" + integrity sha512-U2Spr/vdOnxLzztXP6WpMO7JZTsaYO1G6F/cUTG5fReTQ0imM952FAc/WswpZWAPZqXqWCnvC/Z91JIkMDuYrA== dependencies: nullthrows "^1.1.1" @@ -3402,6 +3451,11 @@ resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee" integrity sha1-7ihweulOEdK4J7y+UnC86n8+ce4= +"@types/lodash@^4.14.182": + version "4.14.182" + resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.182.tgz#05301a4d5e62963227eaafe0ce04dd77c54ea5c2" + integrity sha512-/THyiqyQAP9AfARo4pF+aCGcyiQ94tX/Is2I7HofNRqoYLgN1PBoOWu2/zTA5zMxzP5EFutMtWtGAFRKUe961Q== + "@types/long@^4.0.1": version "4.0.1" resolved "https://registry.yarnpkg.com/@types/long/-/long-4.0.1.tgz#459c65fa1867dafe6a8f322c4c51695663cc55e9" @@ -3437,6 +3491,11 @@ resolved "https://registry.yarnpkg.com/@types/normalize-package-data/-/normalize-package-data-2.4.1.tgz#d3357479a0fdfdd5907fe67e17e0a85c906e1301" integrity sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw== +"@types/object-hash@^1.3.0": + version "1.3.4" + resolved "https://registry.yarnpkg.com/@types/object-hash/-/object-hash-1.3.4.tgz#079ba142be65833293673254831b5e3e847fe58b" + integrity sha512-xFdpkAkikBgqBdG9vIlsqffDV8GpvnPEzs0IUtr1v3BEB97ijsFQ4RXVbUZwjFThhB4MDSTUfvmxUD5PGx0wXA== + "@types/parse-json@^4.0.0": version "4.0.0" resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.0.tgz#2f8bb441434d163b35fb8ffdccd7138927ffb8c0" @@ -4128,6 +4187,13 @@ browserslist@^4.20.2, browserslist@^4.20.3: node-releases "^2.0.3" picocolors "^1.0.0" +bs-logger@0.x: + version "0.2.6" + resolved "https://registry.yarnpkg.com/bs-logger/-/bs-logger-0.2.6.tgz#eb7d365307a72cf974cc6cda76b68354ad336bd8" + integrity sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog== + dependencies: + fast-json-stable-stringify "2.x" + bser@2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/bser/-/bser-2.1.1.tgz#e6787da20ece9d07998533cfd9de6f5c38f4bc05" @@ -4620,6 +4686,11 @@ dashdash@^1.12.0: dependencies: assert-plus "^1.0.0" +dataloader@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/dataloader/-/dataloader-1.4.0.tgz#bca11d867f5d3f1b9ed9f737bd15970c65dff5c8" + integrity sha512-68s5jYdlvasItOJnCuI2Q9s4q98g0pCyL3HrcKJu8KNugUl8ahgmZYg38ysLTgQjjXX3H8CJLkAvWrclWfcalw== + datastore-core@^7.0.0: version "7.0.1" resolved "https://registry.yarnpkg.com/datastore-core/-/datastore-core-7.0.1.tgz#f50f30bb55474a569118d41bba6052896b096aec" @@ -5412,7 +5483,7 @@ fast-glob@^3.2.9: merge2 "^1.3.0" micromatch "^4.0.4" -fast-json-stable-stringify@^2.0.0: +fast-json-stable-stringify@2.x, fast-json-stable-stringify@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== @@ -6722,7 +6793,7 @@ jest-snapshot@^28.1.0: pretty-format "^28.1.0" semver "^7.3.5" -jest-util@^28.1.0: +jest-util@^28.0.0, jest-util@^28.1.0: version "28.1.0" resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-28.1.0.tgz#d54eb83ad77e1dd441408738c5a5043642823be5" integrity sha512-qYdCKD77k4Hwkose2YBEqQk7PzUf/NSE+rutzceduFveQREeH6b+89Dc9+wjX9dAwHcgdx4yedGA3FQlU/qCTA== @@ -7271,7 +7342,7 @@ lodash.debounce@^4.0.8: resolved "https://registry.yarnpkg.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af" integrity sha1-gteb/zCmfEAF/9XiUVMArZyk168= -lodash.memoize@^4.1.2: +lodash.memoize@4.x, lodash.memoize@^4.1.2: version "4.1.2" resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe" integrity sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4= @@ -7286,7 +7357,7 @@ lodash.uniq@^4.5.0: resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773" integrity sha1-0CJTc662Uq3BvILklFM5qEJ1R3M= -lodash@^4.17.11, lodash@^4.17.4: +lodash@^4.17.11, lodash@^4.17.15, lodash@^4.17.21, lodash@^4.17.4: version "4.17.21" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== @@ -7339,7 +7410,7 @@ make-dir@^3.0.0: dependencies: semver "^6.0.0" -make-error@^1.1.1: +make-error@1.x, make-error@^1.1.1: version "1.3.6" resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.6.tgz#2eb2e37ea9b67c4891f684a1394799af484cf7a2" integrity sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw== @@ -7634,7 +7705,6 @@ node-addon-api@^4.3.0: "node-fetch@https://registry.npmjs.org/@achingbrain/node-fetch/-/node-fetch-2.6.7.tgz": version "2.6.7" - uid "1b5d62978f2ed07b99444f64f0df39f960a6d34d" resolved "https://registry.npmjs.org/@achingbrain/node-fetch/-/node-fetch-2.6.7.tgz#1b5d62978f2ed07b99444f64f0df39f960a6d34d" node-forge@^1.2.1: @@ -7748,6 +7818,11 @@ object-assign@^4.1.1: resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM= +object-hash@^1.3.1: + version "1.3.1" + resolved "https://registry.yarnpkg.com/object-hash/-/object-hash-1.3.1.tgz#fde452098a951cb145f039bb7d455449ddc126df" + integrity sha512-OSuu/pU4ENM9kmREg0BdNrUDIl1heYa4mBZacJc+vVWz4GtAwu7jO8s4AIt2aGRUTqxykpWzI3Oqnsm13tTMDA== + object-inspect@^1.11.0, object-inspect@^1.12.0, object-inspect@^1.9.0: version "1.12.0" resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.12.0.tgz#6e2c120e868fd1fd18cb4f18c31741d0d6e776f0" @@ -7993,7 +8068,7 @@ p-try@^2.0.0: resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== -parcel@^2.0.0, parcel@^2.3.2: +parcel@^2.0.0: version "2.3.2" resolved "https://registry.yarnpkg.com/parcel/-/parcel-2.3.2.tgz#d1cb475f27edae981edea7a7104e04d3a35a87ca" integrity sha512-4jhgoBcQaiGKmnmBvNyKyOvZrxCgzgUzdEoVup/fRCOP99hNmvYIN5IErIIJxsU9ObcG/RGCFF8wa4kVRsWfIg== @@ -8013,6 +8088,26 @@ parcel@^2.0.0, parcel@^2.3.2: get-port "^4.2.0" v8-compile-cache "^2.0.0" +parcel@^2.6.0: + version "2.6.0" + resolved "https://registry.yarnpkg.com/parcel/-/parcel-2.6.0.tgz#801bd3af8339966fc52370033e1b7f3c9b9e457d" + integrity sha512-pSTJ7wC6uTl16PKLXQV7RfL9FGoIDA1iVpNvaav47n6UkUdKqfx0spcVPpw35kWdRcHJF61YAvkPjP2hTwHQ+Q== + dependencies: + "@parcel/config-default" "2.6.0" + "@parcel/core" "2.6.0" + "@parcel/diagnostic" "2.6.0" + "@parcel/events" "2.6.0" + "@parcel/fs" "2.6.0" + "@parcel/logger" "2.6.0" + "@parcel/package-manager" "2.6.0" + "@parcel/reporter-cli" "2.6.0" + "@parcel/reporter-dev-server" "2.6.0" + "@parcel/utils" "2.6.0" + chalk "^4.1.0" + commander "^7.0.0" + get-port "^4.2.0" + v8-compile-cache "^2.0.0" + parent-module@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2" @@ -8575,6 +8670,11 @@ randomfill@^1.0.3: randombytes "^2.0.5" safe-buffer "^5.1.0" +react-content-loader@^6.2.0: + version "6.2.0" + resolved "https://registry.yarnpkg.com/react-content-loader/-/react-content-loader-6.2.0.tgz#cd8fee8160b8fda6610d0c69ce5aee7b8094cba6" + integrity sha512-r1dI6S+uHNLW68qraLE2njJYOuy6976PpCExuCZUcABWbfnF3FMcmuESRI8L4Bj45wnZ7n8g71hkPLzbma7/Cw== + react-dom@^17.0.0, react-dom@^17.0.2: version "17.0.2" resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-17.0.2.tgz#ecffb6845e3ad8dbfcdc498f0d0a939736502c23" @@ -8966,18 +9066,18 @@ semver@7.0.0: resolved "https://registry.yarnpkg.com/semver/-/semver-7.0.0.tgz#5f3ca35761e47e05b206c6daff2cf814f0316b8e" integrity sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A== -semver@^6.0.0, semver@^6.1.0, semver@^6.1.1, semver@^6.1.2, semver@^6.3.0: - version "6.3.0" - resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" - integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== - -semver@^7.3.4: +semver@7.x, semver@^7.3.4: version "7.3.7" resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.7.tgz#12c5b649afdbf9049707796e22a4028814ce523f" integrity sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g== dependencies: lru-cache "^6.0.0" +semver@^6.0.0, semver@^6.1.0, semver@^6.1.1, semver@^6.1.2, semver@^6.3.0: + version "6.3.0" + resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" + integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== + semver@^7.3.5: version "7.3.5" resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.5.tgz#0b621c879348d8998e4b0e4be94b3f12e6018ef7" @@ -9428,6 +9528,11 @@ svgo@^2.4.0, svgo@^2.7.0: picocolors "^1.0.0" stable "^0.1.8" +term-size@^2.2.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/term-size/-/term-size-2.2.1.tgz#2a6a54840432c2fb6320fea0f415531e90189f54" + integrity sha512-wK0Ri4fOGjv/XPy8SBHZChl8CM7uMc5VML7SqiQ0zG7+J5Vr+RMQDoHa2CNT6KHUnTGIXH34UDMkPzAUyapBZg== + terminal-link@^2.0.0: version "2.1.1" resolved "https://registry.yarnpkg.com/terminal-link/-/terminal-link-2.1.1.tgz#14a64a27ab3c0df933ea546fba55f2d078edc994" @@ -9529,6 +9634,20 @@ truncate-utf8-bytes@^1.0.0: dependencies: utf8-byte-length "^1.0.1" +ts-jest@^28.0.4: + version "28.0.4" + resolved "https://registry.yarnpkg.com/ts-jest/-/ts-jest-28.0.4.tgz#0ab705a60fc4b9f3506f35e26edfa9e9c915c31b" + integrity sha512-S6uRDDdCJBvnZqyGjB4VCnwbQrbgdL8WPeP4jevVSpYsBaeGRQAIS08o3Svav2Ex+oXwLgJ/m7F24TNq62kA1A== + dependencies: + bs-logger "0.x" + fast-json-stable-stringify "2.x" + jest-util "^28.0.0" + json5 "^2.2.1" + lodash.memoize "4.x" + make-error "1.x" + semver "7.x" + yargs-parser "^20.x" + ts-node@^10.2.1: version "10.5.0" resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-10.5.0.tgz#618bef5854c1fbbedf5e31465cbb224a1d524ef9" @@ -9548,6 +9667,34 @@ ts-node@^10.2.1: v8-compile-cache-lib "^3.0.0" yn "3.1.1" +"ts-poet@^t 4.11.0": + version "4.11.0" + resolved "https://registry.yarnpkg.com/ts-poet/-/ts-poet-4.11.0.tgz#5566f499ec767920cc18b977624f084c52e8734a" + integrity sha512-OaXnCKsRs0yrc0O7LFhnq/US2DB4Wd313cS+qjG2XMksZ74pF/jvMHkJdURXJiAo4kSahL2N4e8JOdwUjOMNdw== + dependencies: + lodash "^4.17.15" + prettier "^2.5.1" + +ts-proto-descriptors@1.6.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/ts-proto-descriptors/-/ts-proto-descriptors-1.6.0.tgz#ca6eafc882495a2e920da5b981d7b181b4e49c38" + integrity sha512-Vrhue2Ti99us/o76mGy28nF3W/Uanl1/8detyJw2yyRwiBC5yxy+hEZqQ/ZX2PbZ1vyCpJ51A9L4PnCCnkBMTQ== + dependencies: + long "^4.0.0" + protobufjs "^6.8.8" + +ts-proto@^1.115.1: + version "1.115.1" + resolved "https://registry.yarnpkg.com/ts-proto/-/ts-proto-1.115.1.tgz#262d9506fe575e5d1a821397ae7f72df2ea8574d" + integrity sha512-Zq1TvLQdnD6eNhbfdccnk1X9tVN5bwwPX4n2/gR9rVEHApZwHInV5Ntqd9lzl22lJ2LjlCNNYQdMlWak8d3EGA== + dependencies: + "@types/object-hash" "^1.3.0" + dataloader "^1.4.0" + object-hash "^1.3.1" + protobufjs "^6.11.3" + ts-poet "^t 4.11.0" + ts-proto-descriptors "1.6.0" + tsconfig-paths@^3.12.0, tsconfig-paths@^3.9.0: version "3.12.0" resolved "https://registry.yarnpkg.com/tsconfig-paths/-/tsconfig-paths-3.12.0.tgz#19769aca6ee8f6a1a341e38c8fa45dd9fb18899b" @@ -9945,7 +10092,7 @@ yargs-parser@^13.1.2: camelcase "^5.0.0" decamelize "^1.2.0" -yargs-parser@^20.2.9: +yargs-parser@^20.2.9, yargs-parser@^20.x: version "20.2.9" resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.9.tgz#2eb7dc3b0289718fc295f362753845c41a0c94ee" integrity sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==