add idToContentTopic

This commit is contained in:
Pavel Prichodko 2022-06-04 13:09:49 +02:00 committed by Felicio Mununga
parent 7addaed9e7
commit 62bd4650a3
No known key found for this signature in database
GPG Key ID: 0EB8D75C775AB6F1
87 changed files with 6578 additions and 617 deletions

View File

@ -1,3 +1,4 @@
**/dist
**/node_modules
**/protos
**/proto

8
.vscode/launch.json vendored
View File

@ -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",

View File

@ -7,7 +7,12 @@ import { Community } from '@status-im/react'
// TODO?: import/rename CommunityAndMessenger, or Status even
const App = () => {
return <Community publicKey="<YOUR_COMMUNITY_KEY>" theme="light" />
return (
<Community
publicKey="0x029f196bbfef4fa6a5eb81dd802133a63498325445ca1af1d154b1bb4542955133"
theme="light"
/>
)
}
render(

View File

@ -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"
},

16
jest.config.js Normal file
View File

@ -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',
},
}

View File

@ -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}": [

View File

@ -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()
})()

View File

@ -0,0 +1,6 @@
version: v1beta1
plugins:
- name: ts_proto
out: ./src/proto
opt: grpc_js,esModuleInterop=true

View File

@ -0,0 +1,9 @@
version: v1beta1
build:
roots:
- ./proto
lint:
except:
- ENUM_ZERO_VALUE_SUFFIX
- ENUM_VALUE_PREFIX

View File

@ -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"
}
}

View File

@ -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<string,CommunityMember> members = 2;
CommunityPermissions permissions = 3;
ChatIdentity identity = 5;
map<string,CommunityChat> chats = 6;
repeated string ban_list = 7;
map<string,CommunityCategory> categories = 8;
}
message CommunityChat {
map<string,CommunityMember> 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;
}

View File

@ -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<string,SignedPreKey> 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<string,EncryptedMessageProtocol> encrypted_message = 101;
// Public chats, not encrypted
bytes public_message = 102;
}

View File

@ -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;
}
}

View File

@ -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<typeof Type>(__TypeValues)
}
}
export const codec = (): Codec<ApplicationMetadataMessage> => {
return message<ApplicationMetadataMessage>({
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())
}
}

View File

@ -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<string, IdentityImage> 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;
}
}

View File

@ -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<ChatIdentity> => {
return message<ChatIdentity>({
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<typeof SourceType>(__SourceTypeValues)
}
}
export const codec = (): Codec<IdentityImage> => {
return message<IdentityImage>({
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<typeof MessageType>(__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<typeof ImageType>(__ImageTypeValues)
}
}

View File

@ -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;
}
}

View File

@ -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<StickerMessage> => {
return message<StickerMessage>({
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<ImageMessage> => {
return message<ImageMessage>({
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<typeof AudioType>(__AudioTypeValues)
}
}
export const codec = (): Codec<AudioMessage> => {
return message<AudioMessage>({
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<EditMessage> => {
return message<EditMessage>({
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<DeleteMessage> => {
return message<DeleteMessage>({
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<typeof ContentType>(__ContentTypeValues)
}
}
export const codec = (): Codec<ChatMessage> => {
return message<ChatMessage>({
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<typeof MessageType>(__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<typeof ImageType>(__ImageTypeValues)
}
}

View File

@ -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<string,CommunityMember> members = 2;
CommunityPermissions permissions = 3;
ChatIdentity identity = 5;
map<string,CommunityChat> chats = 6;
repeated string ban_list = 7;
map<string,CommunityCategory> categories = 8;
uint64 archive_magnetlink_clock = 9;
CommunityAdminSettings admin_settings = 10;
}
message CommunityAdminSettings {
bool pin_message_all_members_enabled = 1;
}
message CommunityChat {
map<string,CommunityMember> 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<string, WakuMessageArchiveIndexMetadata> archives = 1;
}

View File

@ -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<Grant> => {
return message<Grant>({
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<typeof Roles>(__RolesValues)
}
}
export const codec = (): Codec<CommunityMember> => {
return message<CommunityMember>({
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<typeof Access>(__AccessValues)
}
}
export const codec = (): Codec<CommunityPermissions> => {
return message<CommunityPermissions>({
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<CommunityDescription> => {
return message<CommunityDescription>({
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<CommunityAdminSettings> => {
return message<CommunityAdminSettings>({
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<CommunityChat> => {
return message<CommunityChat>({
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<CommunityCategory> => {
return message<CommunityCategory>({
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<CommunityInvitation> => {
return message<CommunityInvitation>({
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<CommunityRequestToJoin> => {
return message<CommunityRequestToJoin>({
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<CommunityRequestToJoinResponse> => {
return message<CommunityRequestToJoinResponse>({
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<CommunityMessageArchiveMagnetlink> => {
return message<CommunityMessageArchiveMagnetlink>({
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<WakuMessage> => {
return message<WakuMessage>({
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<WakuMessageArchiveMetadata> => {
return message<WakuMessageArchiveMetadata>({
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<WakuMessageArchive> => {
return message<WakuMessageArchive>({
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<WakuMessageArchiveIndexMetadata> => {
return message<WakuMessageArchiveIndexMetadata>({
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<WakuMessageArchiveIndex> => {
return message<WakuMessageArchiveIndex>({
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<ChatIdentity> => {
return message<ChatIdentity>({
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<typeof SourceType>(__SourceTypeValues)
}
}
export const codec = (): Codec<IdentityImage> => {
return message<IdentityImage>({
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<typeof MessageType>(__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<typeof ImageType>(__ImageTypeValues)
}
}

View File

@ -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;
}

View File

@ -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<typeof Type>(__TypeValues)
}
}
export const codec = (): Codec<EmojiReaction> => {
return message<EmojiReaction>({
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<typeof MessageType>(__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<typeof ImageType>(__ImageTypeValues)
}
}

View File

@ -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;
}

View File

@ -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<typeof MessageType>(__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<typeof ImageType>(__ImageTypeValues)
}
}

View File

@ -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;
}
}

View File

@ -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<typeof EventType>(__EventTypeValues)
}
}
export const codec = (): Codec<MembershipUpdateEvent> => {
return message<MembershipUpdateEvent>({
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<MembershipUpdateMessage> => {
return message<MembershipUpdateMessage>({
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<StickerMessage> => {
return message<StickerMessage>({
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<ImageMessage> => {
return message<ImageMessage>({
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<typeof AudioType>(__AudioTypeValues)
}
}
export const codec = (): Codec<AudioMessage> => {
return message<AudioMessage>({
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<EditMessage> => {
return message<EditMessage>({
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<DeleteMessage> => {
return message<DeleteMessage>({
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<typeof ContentType>(__ContentTypeValues)
}
}
export const codec = (): Codec<ChatMessage> => {
return message<ChatMessage>({
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<typeof MessageType>(__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<typeof ImageType>(__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<typeof Type>(__TypeValues)
}
}
export const codec = (): Codec<EmojiReaction> => {
return message<EmojiReaction>({
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())
}
}

View File

@ -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;
}

View File

@ -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<PinMessage> => {
return message<PinMessage>({
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<typeof MessageType>(__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<typeof ImageType>(__ImageTypeValues)
}
}

View File

@ -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<string,SignedPreKey> 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<string,EncryptedMessageProtocol> encrypted_message = 101;
// Public chats, not encrypted
bytes public_message = 102;
}

View File

@ -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<SignedPreKey> => {
return message<SignedPreKey>({
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<Bundle> => {
return message<Bundle>({
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<BundleContainer> => {
return message<BundleContainer>({
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<DRHeader> => {
return message<DRHeader>({
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<DHHeader> => {
return message<DHHeader>({
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<X3DHHeader> => {
return message<X3DHHeader>({
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<HRHeader> => {
return message<HRHeader>({
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<EncryptedMessageProtocol> => {
return message<EncryptedMessageProtocol>({
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<ProtocolMessage> => {
return message<ProtocolMessage>({
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())
}
}

View File

@ -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;
};
}

View File

@ -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<typeof StatusType>(__StatusTypeValues)
}
}
export const codec = (): Codec<StatusUpdate> => {
return message<StatusUpdate>({
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())
}
}

View File

@ -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<string,CommunityMember> members = 2;
CommunityPermissions permissions = 3;
ChatIdentity identity = 5;
map<string,CommunityChat> chats = 6;
repeated string ban_list = 7;
map<string,CommunityCategory> categories = 8;
}
message CommunityChat {
map<string,CommunityMember> 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;
}

View File

@ -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<Grant> => {
return message<Grant>({
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<typeof Roles>(__RolesValues)
}
}
export const codec = (): Codec<CommunityMember> => {
return message<CommunityMember>({
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<typeof Access>(__AccessValues)
}
}
export const codec = (): Codec<CommunityPermissions> => {
return message<CommunityPermissions>({
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<CommunityDescription> => {
return message<CommunityDescription>({
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<CommunityChat> => {
return message<CommunityChat>({
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<CommunityCategory> => {
return message<CommunityCategory>({
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<CommunityInvitation> => {
return message<CommunityInvitation>({
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<CommunityRequestToJoin> => {
return message<CommunityRequestToJoin>({
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<CommunityRequestToJoinResponse> => {
return message<CommunityRequestToJoinResponse>({
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<ChatIdentity> => {
return message<ChatIdentity>({
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<typeof SourceType>(__SourceTypeValues)
}
}
export const codec = (): Codec<IdentityImage> => {
return message<IdentityImage>({
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<typeof MessageType>(__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<typeof ImageType>(__ImageTypeValues)
}
}

View File

@ -117,7 +117,7 @@ export interface CommunityDescription {
members: CommunityMember
permissions: CommunityPermissions
identity: ChatIdentity
chats: Record<string,CommunityChat>
chats: CommunityChat
banList: string[]
categories: CommunityCategory
archiveMagnetlinkClock: bigint

View File

@ -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)
}

View File

@ -0,0 +1,10 @@
import { createClient } from './client'
describe('Client', () => {
it('', async () => {
const client = await createClient({ publicKey: '' })
await client.start()
debugger
})
})

View File

@ -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<Waku> {
// TODO?: set tiemout
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.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',
// '/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'
],
},
// TODO?: remove
libp2p: { config: { pubsub: { enabled: true, emitSelf: true } } },
})
await waku.waitForRemotePeer()
return waku
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, string> = {
[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<Client> {
const client = new Client(options)
// TODO?: add start
return client
}

View File

@ -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<string, ChatMessage[]> = {}
constructor(waku: Waku, publicKey: string) {
this.waku = waku
this.publicKey = publicKey
}
// todo: observer only if some callbacks set
// todo: doc as to be called after callbacks have been added
public async start() {
this.symKey = await createSymKeyFromPassword(this.publicKey)
const communityTopic = idToContentTopic(this.publicKey)
await this.fetchCommunity(communityTopic)
// this.observeCommunity(communityTopic)
// const channelTopics = []
// this.observeChannelMessages(channelTopics)
}
// TODO: should return unsubscribe function
public addCallback(type: Events, callback: () => void) {
const callbacks = this.callbacks[type]
if (!callbacks?.length) {
this.callbacks[type] = [callback]
} else {
this.callbacks[type].push(callback)
}
return () => {
this.callbacks[type].filter(cb => cb === callback)
}
}
private async fetchCommunity(topic: string): Promise<void> {
let payload = undefined
// todo: request Waku to replace callbacks
await this.waku.store
.queryHistory([topic], {
decryptionKeys: [this.symKey],
callback: messages => {
for (const message of messages.reverse()) {
if (!message.payload) {
return
}
const decodedMetadata = ApplicationMetadataMessage.decode(
message.payload
)
if (!decodedMetadata.payload) {
return
}
const decodedPayload = CommunityDescription.decode(
decodedMetadata.payload
)
// todo: explain
if (!decodedPayload.identity) {
return
}
payload = decodedPayload
// this.channels[chatId] = [
// ...maessages,
// ...this.channels[chatId]
// ]
// this.callbacks[''].forEach(cb => cb(this.channels[chatId]))
return true
}
},
})
.catch(error => {
console.error(error)
})
if (payload) {
const result = {
name: undefined,
description: undefined,
color: undefined,
channels: undefined,
members: undefined,
}
// todo: set `this.communityData`
this.callbacks['communityfetch'].forEach(callback => callback(result))
}
// todo: handle no (valid) messages case
}
private observeCommunity(topic: string) {
let payload = undefined
this.waku.relay.addObserver(
message => {
if (!message.payload) {
return
}
const decodedMetadata = ApplicationMetadataMessage.decode(
message.payload
)
if (!decodedMetadata.payload) {
return
}
const decodedPayload = CommunityDescription.decode(
decodedMetadata.payload
)
// todo: explain
if (!decodedPayload.identity) {
return
}
payload = decodedPayload
},
[topic]
)
if (payload) {
const result = {
name: undefined,
description: undefined,
color: undefined,
channels: undefined,
members: undefined,
}
this.callbacks['communityupdate'].forEach(callback => callback(result))
}
// todo: handle no (valid) messages case
}
private observeChannelMessages(topics: string[]) {
let payload = undefined
this.waku.relay.addObserver(message => {
if (!message.payload || !message.timestamp) {
return
}
const decodedMetadata = ApplicationMetadataMessage.decode(message.payload)
if (!decodedMetadata.payload) {
return
}
switch (decodedMetadata.type) {
case ApplicationMetadataMessage_Type.TYPE_CHAT_MESSAGE: {
const message = ChatMessage.decode(decodedMetadata.payload)
this.channels[message.chatId].push(message)
this.callbacks.forEach(callback => callback(this.channels[chatId]))
// handle chat message
// push
payload = message
return
}
default:
return
}
}, topics)
if (payload) {
// todo: push to `this.channelEvents` by type
this.callbacks['channelmessagereceive'].forEach(callback =>
callback(result)
)
}
// todo: handle no (valid) messages case
}
}
export function createCommunityBeta(waku: Waku, publicKey: string) {
const community = new CommunityBeta(waku, publicKey)
return community
}

View File

@ -1,3 +1,4 @@
// see
import { PageDirection, WakuMessage } from 'js-waku'
import { idToContactCodeTopic } from './contentTopic'

View File

@ -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()
})()

View File

@ -2,9 +2,7 @@ import pbkdf2 from 'pbkdf2'
const AESKeyLength = 32 // bytes
export async function createSymKeyFromPassword(
password: string
): Promise<Uint8Array> {
export function createSymKeyFromPassword(password: string): Uint8Array {
return pbkdf2.pbkdf2Sync(
Buffer.from(password, 'utf-8'),
'',

View File

@ -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()
})()

View File

@ -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]

View File

@ -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() {}
}

View File

@ -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'

View File

@ -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'

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,7 @@
import { idToContentTopic } from './id-to-content-topic'
describe('idToContentTopic', () => {
it('should return content topic', () => {
expect(idToContentTopic).toBeDefined()
})
})

View File

@ -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`
}

View File

@ -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)

View File

@ -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])

View File

@ -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",

View File

@ -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 = () => (
<ContentLoader
speed={2}
width={185}
height={50}
viewBox="0 0 185 50"
backgroundColor="rgba(233, 237, 241, 1)"
foregroundColor="rgba(246, 248, 250, 1)"
>
<rect x="50" y="10" rx="3" ry="3" width="120" height="12" />
<rect x="50" y="30" rx="3" ry="3" width="120" height="8" />
<circle cx="24" cy="24" r="18" />
</ContentLoader>
)
const ChannelLoader = () => {
return (
<ContentLoader
speed={2}
width={272}
height={40}
viewBox="0 0 272 40"
backgroundColor="rgba(233, 237, 241, 1)"
foregroundColor="rgba(246, 248, 250, 1)"
>
<rect x="41" y="13" rx="3" ry="3" width="140" height="14" />
<circle cx="20" cy="20" r="12" />
</ContentLoader>
)
}
export const Loading = () => {
return (
<Wrapper>
<Sidebar>
<CommunityInfoLoader />
<Box css={{ padding: '8px 0' }}>
<ChannelLoader />
<ChannelLoader />
<ChannelLoader />
<ChannelLoader />
<ChannelLoader />
</Box>
</Sidebar>
<Box css={{ padding: '6px 10px' }}>
<CommunityInfoLoader />
</Box>
</Wrapper>
)
}
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',
},
})

View File

@ -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 (
<Box>
{Object.entries(CHANNELS).map(([group, channels]) => (
<Box css={{padding:'8px 0'}}>
{chats.map((chat) => (
<ChannelItem
key={chat.id}
to={`/${chat.id}`}
unread={false}
muted={false}
>
{chat.identity!.displayName}
</ChannelItem>
))}
{/* {Object.entries(community.chats).map(([group, channels]) => (
<ChannelGroup key={group} name={group}>
{channels.map(channel => (
<ChannelItem
@ -26,7 +37,7 @@ export const Channels = () => {
</ChannelItem>
))}
</ChannelGroup>
))}
))} */}
</Box>
)
}

View File

@ -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 (
<Dialog title={name}>
<Dialog title={displayName}>
<Dialog.Body>
<Text>{description}</Text>
</Dialog.Body>
@ -17,7 +19,7 @@ export const CommunityDialog = () => {
<CopyInput label="Community Public Key" value={publicKey} />
<Text size="13" color="gray">
To access this community, paste community public key in Status
desktop or mobile app
desktop or mobile app.
</Text>
</Grid>
</Dialog.Body>

View File

@ -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 (
<DialogTrigger>
<Button>
<Avatar size={36} src={imageUrl} />
<Avatar size={36} />
<div>
<Text>{name}</Text>
<Text>{community.identity?.displayName}</Text>
<Text color="gray" size={12}>
{membersCount} members
{members.length} members
</Text>
</div>
</Button>

View File

@ -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 (
<Flex direction="column" align="center" gap={5}>
<svg
@ -147,19 +150,20 @@ export const GetStarted = () => {
<SyncStatusProfileDialog />
</DialogTrigger> */}
<DialogTrigger>
{/* <DialogTrigger>
<Button>Connect Wallet</Button>
<ConnectWalletDialog />
</DialogTrigger>
</DialogTrigger> */}
<DialogTrigger>
<Button variant="outline">Use Throwaway Profile</Button>
{throwawayProfile ? (
<Button onClick={createAccount}>Use Throwaway Profile</Button>
{/* <DialogTrigger>
<Button>Use Throwaway Profile</Button>
{account ? (
<ThrowawayProfileFoundDialog onSkip={handleSkip} />
) : (
<CreateProfileDialog />
)}
</DialogTrigger>
</DialogTrigger> */}
</Grid>
</Flex>
)

View File

@ -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 (
<Dialog title="Throwaway Profile Found">
<Dialog.Body gap="5">
<Flex direction="column" align="center" gap="2">
<Avatar size={64} src={profile.imageUrl} />
<Heading weight="600">{profile.name}</Heading>
<Avatar size={64} src={account.imageUrl} />
<Heading weight="600">{account.name}</Heading>
<Text color="gray">
Chatkey: 0x63FaC9201494f0bd17B9892B9fae4d52fe3BD377
</Text>

View File

@ -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 (
<ContextMenuTrigger>

View File

@ -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 (
<Box>
<Grid
@ -21,7 +21,7 @@ export const Messages = () => {
<EditIcon />
</IconButton>
</Grid>
{CHATS.map(chat => (
{chats.map(chat => (
<ChatItem key={chat} to={`/${chat}`} unread={false} muted={false}>
{chat}
</ChatItem>

View File

@ -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 (
<Wrapper>
<CommunityInfo />
<Channels />
{/* <Separator css={{ margin: '16px 0' }} />
<Messages /> */}
{ !account && (
<>
<Separator css={{ margin: '16px 0' }} />
<GetStarted />
</>
)}
</Wrapper>
)
}

View File

@ -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 (
<Dialog title="Disconnect">
<Dialog.Body gap="5">
<Text>Do you want to disconnect your profile from this browser?</Text>
<Flex direction="column" align="center" gap="2">
<Avatar size={64} src={profile.imageUrl} />
<Heading weight="600">{profile.name}</Heading>
<Avatar size={64} src={account.imageUrl} />
<Heading weight="600">{account.name}</Heading>
<Text color="gray">
Chatkey: 0x63FaC9201494f0bd17B9892B9fae4d52fe3BD377
Chatkey: {account.chatKey}
</Text>
<EmojiHash />
</Flex>

View File

@ -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 (
<Wrapper>
<Heading size="15" css={{ marginBottom: '$3' }}>
@ -18,28 +21,19 @@ export function MemberSidebar() {
<UserItem />
</MemberGroup>
<MemberGroup label="Online">
<MemberItem verified={true} untrustworthy={false} indicator="online">
pvl.eth
</MemberItem>
<MemberItem verified={false} untrustworthy={false} indicator="online">
carmen
</MemberItem>
<MemberItem verified={false} untrustworthy={false} indicator="online">
carmen
</MemberItem>
</MemberGroup>
<MemberGroup label="Offline">
<MemberItem verified={false} untrustworthy indicator="offline">
mark
</MemberItem>
{members.map(member => (
<MemberItem
key={member}
verified={false}
untrustworthy={false}
indicator="offline"
indicator="online"
chatKey={member}
>
mark
{member}
</MemberItem>
))}
</MemberGroup>
{/* <MemberGroup label="Offline"></MemberGroup> */}
</Grid>
</Wrapper>
)

View File

@ -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 (
<Flex gap="2" align="center" css={{ height: 56 }}>
<Avatar
size={32}
indicator={indicator}
src="https://images.unsplash.com/photo-1499155286265-79a9dc9c6380?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1284&q=80"
/>
<div>
<Flex align="center" gap={1}>
<Text size="15" color="accent">
<Text size="15" color="accent" truncate>
{children}
</Text>
{verified && (
@ -62,7 +62,7 @@ export const MemberItem = (props: Props) => {
)}
</Flex>
<EthAddress size={10} color="gray">
71C7656EC7ab88b098defB751B7401B5f6d8976F
{chatKey}
</EthAddress>
</div>
</Flex>

View File

@ -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 (
<Flex align="center" justify="between">
<Flex gap="2" align="center" css={{ height: 56 }}>
<Avatar size={32} src={profile.imageUrl} />
<Avatar size={32} src={account.imageUrl} />
<div>
<Flex align="center" gap={1}>
<Text size="15" color="accent">
{profile.name}
{account.name}
</Text>
</Flex>
<EthAddress size={10} color="gray">
{profile.publicKey}
{account.publicKey}
</EthAddress>
</div>
</Flex>
<DialogTrigger>
<DisconnectButton>
<svg
@ -44,7 +50,10 @@ export const UserItem = () => {
/>
</svg>
</DisconnectButton>
{account && (
<DisconnectDialog />
)}
</DialogTrigger>
</Flex>
)

View File

@ -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'

View File

@ -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<ClientContext | undefined>(undefined)
const Context = createContext<Client | undefined>(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<Client>()
const [community, setCommunity] = useState<Community['communityMetadata']>()
const [account, setAccount] = useState<Account>()
const [loading, setLoading] = useState(true)
const client = useMemo(() => {
// return createClient({ ...config })
return { client: config }
}, [config])
const { options, children } = props
return <Context.Provider value={client}>{children}</Context.Provider>
// 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 (
<Context.Provider value={client}>
<CommunityContext.Provider value={community}>
{loading ? (
<Loading />
) : (
children
)}
</CommunityContext.Provider>
</Context.Provider>
)
}

View File

@ -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<any>()
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
}

View File

@ -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!
}

View File

@ -1,5 +0,0 @@
import { useClient } from './provider'
export const useChannels = () => {
const client = useClient()
}

View File

@ -1,35 +0,0 @@
export interface Chat {
type: 'channel' | 'group-chat' | 'chat'
imageUrl?: string
}
const chats: Record<string, Chat> = {
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]
}

View File

@ -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])
}

View File

@ -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
}

View File

@ -1,3 +0,0 @@
export const useContacts = () => {
return []
}

View File

@ -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<State>({
fetching: false,
stale: false,
data: undefined,
error: undefined,
})
useEffect(() => {
isMounted.current = true
return () => {
isMounted.current = false
}
}, [])
return state
}

View File

@ -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<Result>((state,action) => {}, {})
const [data, setData] = useState<any[]>(() => client.community.getMessages(channelId))
const [loading, setLoading] = useState(true)
const [error, setError] = useState<Error>()
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 },
// },
// },
// ]
}

View File

@ -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',
}
}

View File

@ -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') {
// if (chat.type == 'channel') {
return (
<Flex align="center" gap="2">
<Avatar size={36} src={chat.imageUrl} />
<Avatar size={36} />
<div>
<Text>#{params.id}</Text>
<Text>#{chat.identity?.displayName}</Text>
<Flex align="center">
<Text size={12} color="gray">
<DialogTrigger>
<button
style={{ display: 'inline-flex', alignItems: 'center' }}
>
{/* <PinIcon width={7} height={13} />2 pinned messages */}2
pinned messages
</button>
<PinnedMessagesDialog />
</DialogTrigger>{' '}
| General discussions about CryptoKitties.
{chat.identity?.description}
</Text>
</Flex>
</div>
</Flex>
)
}
if (chat.type == 'group-chat') {
return (
<Flex align="center" gap="2">
<Avatar size={36} src={chat.imageUrl} />
<div>
<Text>Climate Change</Text>
<Flex align="center">
<DialogTrigger>
<Text as="button" size={12} color="gray">
<PinIcon width={7} /> 2 pinned messages
</Text>
<PinnedMessagesDialog />
</DialogTrigger>
<Text size={12} color="gray">
| 5 members
</Text>
</Flex>
</div>
</Flex>
)
}
// <DialogTrigger>
// <button style={{ display: 'inline-flex', alignItems: 'center' }}>
// {/* <PinIcon width={7} height={13} />2 pinned messages */}2
// pinned messages
// </button>
// <PinnedMessagesDialog />
// </DialogTrigger>{' '}
// |
// }
return (
<Flex align="center" gap="2">
<Avatar size={36} src={chat.imageUrl} />
<div>
<Text>pvl.eth</Text>
<Text size={12} color="gray">
0x63FaC9201494f0bd17B9892B9fae4d52fe3BD377
</Text>
</div>
</Flex>
)
// if (chat.type == 'group-chat') {
// return (
// <Flex align="center" gap="2">
// <Avatar size={36} src={chat.imageUrl} />
// <div>
// <Text>Climate Change</Text>
// <Flex align="center">
// <DialogTrigger>
// <Text as="button" size={12} color="gray">
// <PinIcon width={7} /> 2 pinned messages
// </Text>
// <PinnedMessagesDialog />
// </DialogTrigger>
// <Text size={12} color="gray">
// | 5 members
// </Text>
// </Flex>
// </div>
// </Flex>
// )
// }
// return (
// <Flex align="center" gap="2">
// <Avatar size={36} src={chat.imageUrl} />
// <div>
// <Text>pvl.eth</Text>
// <Text size={12} color="gray">
// 0x63FaC9201494f0bd17B9892B9fae4d52fe3BD377
// </Text>
// </div>
// </Flex>
// )
}

View File

@ -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': {
// <AlertDialogTrigger>
// <MessageLink href="https://specs.status.im/spec">
// https://specs.status.im/spec
@ -155,19 +161,20 @@ export const ChatMessage = (props: Props) => {
<Box>
<DropdownMenuTrigger>
<button type="button">
<Avatar size={44} src={contact.imageUrl} />
{/* <Avatar size={44} src={contact.imageUrl} /> */}
</button>
<DropdownMenu>
<Flex direction="column" align="center" gap="1">
<Avatar size="36" src={contact.imageUrl} />
<Text>{contact.name}</Text>
{/* <Avatar size="36" src={contact.imageUrl} /> */}
{/* <Text>{contact.name}</Text> */}
<Text>{displayName}</Text>
<EmojiHash />
</Flex>
<DropdownMenu.Separator />
<DropdownMenu.Item
icon={<BellIcon />}
onSelect={() =>
userProfileDialog.open({ name: contact.name })
userProfileDialog.open({ name: displayName })
}
>
View Profile
@ -199,7 +206,8 @@ export const ChatMessage = (props: Props) => {
<Flex gap="1" align="center">
<Text color="primary" weight="500" size="15">
{contact.name}
{displayName}
{/* {contact.name} */}
</Text>
<Text size="10" color="gray">
10:00 AM

View File

@ -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 (
<NavbarWrapper>
@ -44,7 +44,7 @@ export const Navbar = (props: Props) => {
<IconButton label="Options">
<DotsIcon />
</IconButton>
<ChatMenu type="dropdown" chatType={chat.type} />
<ChatMenu type="dropdown" chatType="channel" />
</DropdownMenuTrigger>
<Separator orientation="vertical" css={{ height: 24 }} />

View File

@ -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 (
<Flex direction="column" gap="3" align="center" css={{ marginBottom: 50 }}>
<Avatar size={120} src={chat.imageUrl} />
<Heading>general</Heading>
<Text>Welcome to the beginning of the #general channel!</Text>
{/* <Avatar size={120} src={chat.imageUrl} /> */}
<Heading>{chat.identity?.displayName}</Heading>
<Text>
Welcome to the beginning of the #{chat.identity?.displayName} channel!
</Text>
</Flex>
)
}
@ -32,18 +34,20 @@ const ChatStart = () => {
const Content = () => {
const contentRef = useRef<HTMLDivElement>(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 (
<ContentWrapper ref={contentRef}>
<ChatStart />
{messages.map(message => (
<ChatMessage key={message.id} message={message} />
{messages.data.map(message => (
<ChatMessage key={message.messageId} message={message} />
))}
</ContentWrapper>
)

View File

@ -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,6 +24,7 @@ export const Community = (props: Props) => {
return (
<Router>
<AppProvider config={props}>
<ClientProvider options={{ publicKey: props.publicKey }}>
<ThemeProvider theme={theme}>
<DialogProvider>
<GlobalStyle />
@ -35,6 +37,7 @@ export const Community = (props: Props) => {
</Wrapper>
</DialogProvider>
</ThemeProvider>
</ClientProvider>
</AppProvider>
</Router>
)

View File

@ -13,7 +13,7 @@ const EthAddress = (props: Props) => {
return (
<Text {...textProps}>
0x{children.substring(0, 3)}...{children.substring(children.length - 3)}
{children.substring(0, 5)}...{children.substring(children.length - 3)}
</Text>
)
}

209
yarn.lock
View File

@ -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==