Merge pull request #83 from status-im/community-test
This commit is contained in:
commit
e8a754f418
Binary file not shown.
|
@ -25,6 +25,7 @@
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/chai": "^4.2.22",
|
"@types/chai": "^4.2.22",
|
||||||
"@types/mocha": "^9.0.0",
|
"@types/mocha": "^9.0.0",
|
||||||
|
"@types/pbkdf2": "^3.1.0",
|
||||||
"@types/secp256k1": "^4.0.3",
|
"@types/secp256k1": "^4.0.3",
|
||||||
"@typescript-eslint/eslint-plugin": "^4.31.1",
|
"@typescript-eslint/eslint-plugin": "^4.31.1",
|
||||||
"@typescript-eslint/parser": "^4.31.1",
|
"@typescript-eslint/parser": "^4.31.1",
|
||||||
|
@ -47,6 +48,7 @@
|
||||||
"ecies-geth": "^1.5.3",
|
"ecies-geth": "^1.5.3",
|
||||||
"js-sha3": "^0.8.0",
|
"js-sha3": "^0.8.0",
|
||||||
"js-waku": "^0.13.1",
|
"js-waku": "^0.13.1",
|
||||||
|
"pbkdf2": "^3.1.2",
|
||||||
"protobufjs": "^6.11.2",
|
"protobufjs": "^6.11.2",
|
||||||
"secp256k1": "^4.0.2"
|
"secp256k1": "^4.0.2"
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,6 +22,8 @@ message ChatIdentity {
|
||||||
string description = 5;
|
string description = 5;
|
||||||
|
|
||||||
string color = 6;
|
string color = 6;
|
||||||
|
|
||||||
|
string emoji = 7;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ProfileImage represents data associated with a user's profile image
|
// ProfileImage represents data associated with a user's profile image
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import { idToContentTopic } from "./contentTopic";
|
import { idToContentTopic } from "./contentTopic";
|
||||||
import { createSymKeyFromPassword } from "./encryption";
|
import { createSymKeyFromPassword } from "./encryption";
|
||||||
import { ChatMessage, Content } from "./wire/chat_message";
|
import { ChatMessage, Content } from "./wire/chat_message";
|
||||||
|
import { CommunityChat } from "./wire/community_chat";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represent a chat room. Only public chats are currently supported.
|
* Represent a chat room. Only public chats are currently supported.
|
||||||
|
@ -9,16 +10,23 @@ export class Chat {
|
||||||
private lastClockValue?: number;
|
private lastClockValue?: number;
|
||||||
private lastMessage?: ChatMessage;
|
private lastMessage?: ChatMessage;
|
||||||
|
|
||||||
private constructor(public id: string, public symKey: Uint8Array) {}
|
private constructor(
|
||||||
|
public id: string,
|
||||||
|
public symKey: Uint8Array,
|
||||||
|
public communityChat?: CommunityChat
|
||||||
|
) {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a public chat room.
|
* Create a public chat room.
|
||||||
* [[Community.instantiateChat]] MUST be used for chats belonging to a community.
|
* [[Community.instantiateChat]] MUST be used for chats belonging to a community.
|
||||||
*/
|
*/
|
||||||
public static async create(id: string): Promise<Chat> {
|
public static async create(
|
||||||
|
id: string,
|
||||||
|
communityChat?: CommunityChat
|
||||||
|
): Promise<Chat> {
|
||||||
const symKey = await createSymKeyFromPassword(id);
|
const symKey = await createSymKeyFromPassword(id);
|
||||||
|
|
||||||
return new Chat(id, symKey);
|
return new Chat(id, symKey, communityChat);
|
||||||
}
|
}
|
||||||
|
|
||||||
public get contentTopic(): string {
|
public get contentTopic(): string {
|
||||||
|
|
|
@ -0,0 +1,42 @@
|
||||||
|
import { expect } from "chai";
|
||||||
|
import { Waku } from "js-waku";
|
||||||
|
|
||||||
|
import { Community } from "./community";
|
||||||
|
import { CommunityDescription } from "./wire/community_description";
|
||||||
|
|
||||||
|
describe("Community live data", () => {
|
||||||
|
before(function () {
|
||||||
|
if (process.env.CI) {
|
||||||
|
// Skip live data test in CI
|
||||||
|
this.skip();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Retrieves community description For DappConnect Test from Waku prod fleet", async function () {
|
||||||
|
this.timeout(20000);
|
||||||
|
const waku = await Waku.create({ bootstrap: true });
|
||||||
|
|
||||||
|
await waku.waitForConnectedPeer();
|
||||||
|
|
||||||
|
const community = await Community.instantiateCommunity(
|
||||||
|
"0x0262c65c881f5a9f79343a26faaa02aad3af7c533d9445fb1939ed11b8bf4d2abd",
|
||||||
|
waku
|
||||||
|
);
|
||||||
|
const desc = community.description as CommunityDescription;
|
||||||
|
expect(desc).to.not.be.undefined;
|
||||||
|
|
||||||
|
expect(desc.identity?.displayName).to.eq("DappConnect Test");
|
||||||
|
|
||||||
|
const descChats = Array.from(desc.chats.values()).map(
|
||||||
|
(chat) => chat?.identity?.displayName
|
||||||
|
);
|
||||||
|
expect(descChats).to.include("foobar");
|
||||||
|
expect(descChats).to.include("another-channel!");
|
||||||
|
|
||||||
|
const chats = Array.from(community.chats.values()).map(
|
||||||
|
(chat) => chat?.communityChat?.identity?.displayName
|
||||||
|
);
|
||||||
|
expect(chats).to.include("foobar");
|
||||||
|
expect(chats).to.include("another-channel!");
|
||||||
|
});
|
||||||
|
});
|
|
@ -11,12 +11,13 @@ const dbg = debug("communities:community");
|
||||||
export class Community {
|
export class Community {
|
||||||
public publicKey: Uint8Array;
|
public publicKey: Uint8Array;
|
||||||
private waku: Waku;
|
private waku: Waku;
|
||||||
|
public chats: Map<string, Chat>; // Chat id, Chat
|
||||||
public description?: CommunityDescription;
|
public description?: CommunityDescription;
|
||||||
|
|
||||||
constructor(publicKey: Uint8Array, waku: Waku) {
|
constructor(publicKey: Uint8Array, waku: Waku) {
|
||||||
this.publicKey = publicKey;
|
this.publicKey = publicKey;
|
||||||
this.waku = waku;
|
this.waku = waku;
|
||||||
|
this.chats = new Map();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -60,6 +61,12 @@ export class Community {
|
||||||
}
|
}
|
||||||
|
|
||||||
this.description = desc;
|
this.description = desc;
|
||||||
|
|
||||||
|
await Promise.all(
|
||||||
|
Array.from(this.description.chats).map(([chatUuid, communityChat]) => {
|
||||||
|
return this.instantiateChat(chatUuid, communityChat);
|
||||||
|
})
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -68,28 +75,18 @@ export class Community {
|
||||||
*
|
*
|
||||||
* @throws string If the Community Description is unavailable or the chat is not found;
|
* @throws string If the Community Description is unavailable or the chat is not found;
|
||||||
*/
|
*/
|
||||||
public async instantiateChat(chatName: string): Promise<Chat> {
|
private async instantiateChat(
|
||||||
if (!this.description) {
|
chatUuid: string,
|
||||||
await this.refreshCommunityDescription();
|
communityChat: CommunityChat
|
||||||
if (!this.description)
|
): Promise<void> {
|
||||||
throw "Failed to retrieve community description, cannot instantiate chat";
|
if (!this.description)
|
||||||
}
|
throw "Failed to retrieve community description, cannot instantiate chat";
|
||||||
|
|
||||||
let communityChat: CommunityChat | undefined;
|
const chatId = this.publicKeyStr + chatUuid;
|
||||||
let chatUuid: string | undefined;
|
if (this.chats.get(chatId)) return;
|
||||||
|
|
||||||
this.description.chats.forEach((_chat, _id) => {
|
const chat = await Chat.create(chatId, communityChat);
|
||||||
if (chatUuid) return;
|
|
||||||
|
|
||||||
if (_chat.identity?.displayName === chatName) {
|
this.chats.set(chatId, chat);
|
||||||
chatUuid = _id;
|
|
||||||
communityChat = _chat;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!communityChat || !chatUuid)
|
|
||||||
throw `Failed to retrieve community community chat with name ${chatName}`;
|
|
||||||
|
|
||||||
return Chat.create(this.publicKeyStr + chatUuid);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,14 @@
|
||||||
|
import { expect } from "chai";
|
||||||
|
|
||||||
|
import { createSymKeyFromPassword } from "./encryption";
|
||||||
|
|
||||||
|
describe("Encryption", () => {
|
||||||
|
it("Generate symmetric key from password", async function () {
|
||||||
|
const str = "arbitrary data here";
|
||||||
|
const symKey = await createSymKeyFromPassword(str);
|
||||||
|
|
||||||
|
expect(Buffer.from(symKey).toString("hex")).to.eq(
|
||||||
|
"c49ad65ebf2a7b7253bf400e3d27719362a91b2c9b9f54d50a69117021666c33"
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
|
@ -1,9 +1,15 @@
|
||||||
import { kdf } from "ecies-geth";
|
import pbkdf2 from "pbkdf2";
|
||||||
|
|
||||||
const AESKeyLength = 32; // bytes
|
const AESKeyLength = 32; // bytes
|
||||||
|
|
||||||
export async function createSymKeyFromPassword(
|
export async function createSymKeyFromPassword(
|
||||||
password: string
|
password: string
|
||||||
): Promise<Uint8Array> {
|
): Promise<Uint8Array> {
|
||||||
return kdf(Buffer.from(password, "utf-8"), AESKeyLength);
|
return pbkdf2.pbkdf2Sync(
|
||||||
|
Buffer.from(password, "utf-8"),
|
||||||
|
"",
|
||||||
|
65356,
|
||||||
|
AESKeyLength,
|
||||||
|
"sha256"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,6 +22,7 @@ export interface ChatIdentity {
|
||||||
/** description is the user set description, valid only for organisations */
|
/** description is the user set description, valid only for organisations */
|
||||||
description: string;
|
description: string;
|
||||||
color: string;
|
color: string;
|
||||||
|
emoji: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ChatIdentity_ImagesEntry {
|
export interface ChatIdentity_ImagesEntry {
|
||||||
|
@ -98,6 +99,7 @@ const baseChatIdentity: object = {
|
||||||
displayName: "",
|
displayName: "",
|
||||||
description: "",
|
description: "",
|
||||||
color: "",
|
color: "",
|
||||||
|
emoji: "",
|
||||||
};
|
};
|
||||||
|
|
||||||
export const ChatIdentity = {
|
export const ChatIdentity = {
|
||||||
|
@ -126,6 +128,9 @@ export const ChatIdentity = {
|
||||||
if (message.color !== "") {
|
if (message.color !== "") {
|
||||||
writer.uint32(50).string(message.color);
|
writer.uint32(50).string(message.color);
|
||||||
}
|
}
|
||||||
|
if (message.emoji !== "") {
|
||||||
|
writer.uint32(58).string(message.emoji);
|
||||||
|
}
|
||||||
return writer;
|
return writer;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -161,6 +166,9 @@ export const ChatIdentity = {
|
||||||
case 6:
|
case 6:
|
||||||
message.color = reader.string();
|
message.color = reader.string();
|
||||||
break;
|
break;
|
||||||
|
case 7:
|
||||||
|
message.emoji = reader.string();
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
reader.skipType(tag & 7);
|
reader.skipType(tag & 7);
|
||||||
break;
|
break;
|
||||||
|
@ -202,6 +210,11 @@ export const ChatIdentity = {
|
||||||
} else {
|
} else {
|
||||||
message.color = "";
|
message.color = "";
|
||||||
}
|
}
|
||||||
|
if (object.emoji !== undefined && object.emoji !== null) {
|
||||||
|
message.emoji = String(object.emoji);
|
||||||
|
} else {
|
||||||
|
message.emoji = "";
|
||||||
|
}
|
||||||
return message;
|
return message;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -220,6 +233,7 @@ export const ChatIdentity = {
|
||||||
message.description !== undefined &&
|
message.description !== undefined &&
|
||||||
(obj.description = message.description);
|
(obj.description = message.description);
|
||||||
message.color !== undefined && (obj.color = message.color);
|
message.color !== undefined && (obj.color = message.color);
|
||||||
|
message.emoji !== undefined && (obj.emoji = message.emoji);
|
||||||
return obj;
|
return obj;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -258,6 +272,11 @@ export const ChatIdentity = {
|
||||||
} else {
|
} else {
|
||||||
message.color = "";
|
message.color = "";
|
||||||
}
|
}
|
||||||
|
if (object.emoji !== undefined && object.emoji !== null) {
|
||||||
|
message.emoji = object.emoji;
|
||||||
|
} else {
|
||||||
|
message.emoji = "";
|
||||||
|
}
|
||||||
return message;
|
return message;
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import { Reader } from "protobufjs";
|
import { Reader } from "protobufjs";
|
||||||
|
|
||||||
import * as proto from "../proto/communities/v1/chat_identity";
|
import * as proto from "../proto/communities/v1/chat_identity";
|
||||||
|
import { IdentityImage } from "../proto/communities/v1/chat_identity";
|
||||||
|
|
||||||
export class ChatIdentity {
|
export class ChatIdentity {
|
||||||
public constructor(public proto: proto.ChatIdentity) {}
|
public constructor(public proto: proto.ChatIdentity) {}
|
||||||
|
@ -14,4 +15,37 @@ export class ChatIdentity {
|
||||||
encode(): Uint8Array {
|
encode(): Uint8Array {
|
||||||
return proto.ChatIdentity.encode(this.proto).finish();
|
return proto.ChatIdentity.encode(this.proto).finish();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Lamport timestamp of the message */
|
||||||
|
get clock(): number | undefined {
|
||||||
|
return this.proto.clock;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** ens_name is the valid ENS name associated with the chat key */
|
||||||
|
get ensName(): string | undefined {
|
||||||
|
return this.proto.ensName;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** images is a string indexed mapping of images associated with an identity */
|
||||||
|
get images(): { [key: string]: IdentityImage } | undefined {
|
||||||
|
return this.proto.images;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** display name is the user set identity, valid only for organisations */
|
||||||
|
get displayName(): string | undefined {
|
||||||
|
return this.proto.displayName;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** description is the user set description, valid only for organisations */
|
||||||
|
get description(): string | undefined {
|
||||||
|
return this.proto.description;
|
||||||
|
}
|
||||||
|
|
||||||
|
get color(): string | undefined {
|
||||||
|
return this.proto.color;
|
||||||
|
}
|
||||||
|
|
||||||
|
get emoji(): string | undefined {
|
||||||
|
return this.proto.emoji;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,12 +1,13 @@
|
||||||
import { Reader } from "protobufjs";
|
import { Reader } from "protobufjs";
|
||||||
|
|
||||||
import { ChatIdentity } from "../proto/communities/v1/chat_identity";
|
|
||||||
import * as proto from "../proto/communities/v1/communities";
|
import * as proto from "../proto/communities/v1/communities";
|
||||||
import {
|
import {
|
||||||
CommunityMember,
|
CommunityMember,
|
||||||
CommunityPermissions,
|
CommunityPermissions,
|
||||||
} from "../proto/communities/v1/communities";
|
} from "../proto/communities/v1/communities";
|
||||||
|
|
||||||
|
import { ChatIdentity } from "./chat_identity";
|
||||||
|
|
||||||
export class CommunityChat {
|
export class CommunityChat {
|
||||||
public constructor(public proto: proto.CommunityChat) {}
|
public constructor(public proto: proto.CommunityChat) {}
|
||||||
|
|
||||||
|
@ -41,7 +42,9 @@ export class CommunityChat {
|
||||||
}
|
}
|
||||||
|
|
||||||
public get identity(): ChatIdentity | undefined {
|
public get identity(): ChatIdentity | undefined {
|
||||||
return this.proto.identity;
|
if (!this.proto.identity) return;
|
||||||
|
|
||||||
|
return new ChatIdentity(this.proto.identity);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Document this
|
// TODO: Document this
|
||||||
|
|
|
@ -3,9 +3,11 @@ import { WakuMessage, WakuStore } from "js-waku";
|
||||||
import { Reader } from "protobufjs";
|
import { Reader } from "protobufjs";
|
||||||
|
|
||||||
import { idToContentTopic } from "../contentTopic";
|
import { idToContentTopic } from "../contentTopic";
|
||||||
|
import { createSymKeyFromPassword } from "../encryption";
|
||||||
import * as proto from "../proto/communities/v1/communities";
|
import * as proto from "../proto/communities/v1/communities";
|
||||||
import { bufToHex } from "../utils";
|
import { bufToHex } from "../utils";
|
||||||
|
|
||||||
|
import { ApplicationMetadataMessage } from "./application_metadata_message";
|
||||||
import { ChatIdentity } from "./chat_identity";
|
import { ChatIdentity } from "./chat_identity";
|
||||||
import { CommunityChat } from "./community_chat";
|
import { CommunityChat } from "./community_chat";
|
||||||
|
|
||||||
|
@ -45,8 +47,11 @@ export class CommunityDescription {
|
||||||
orderedMessages.forEach((message: WakuMessage) => {
|
orderedMessages.forEach((message: WakuMessage) => {
|
||||||
if (!message.payload) return;
|
if (!message.payload) return;
|
||||||
try {
|
try {
|
||||||
|
const metadata = ApplicationMetadataMessage.decode(message.payload);
|
||||||
|
if (!metadata.payload) return;
|
||||||
|
|
||||||
const _communityDescription = CommunityDescription.decode(
|
const _communityDescription = CommunityDescription.decode(
|
||||||
message.payload
|
metadata.payload
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!_communityDescription.identity) return;
|
if (!_communityDescription.identity) return;
|
||||||
|
@ -61,9 +66,12 @@ export class CommunityDescription {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const symKey = await createSymKeyFromPassword(hexCommunityPublicKey);
|
||||||
|
|
||||||
await wakuStore
|
await wakuStore
|
||||||
.queryHistory([contentTopic], {
|
.queryHistory([contentTopic], {
|
||||||
callback,
|
callback,
|
||||||
|
decryptionKeys: [symKey],
|
||||||
})
|
})
|
||||||
.catch((e) => {
|
.catch((e) => {
|
||||||
dbg(
|
dbg(
|
||||||
|
|
13
yarn.lock
13
yarn.lock
|
@ -938,6 +938,15 @@ __metadata:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"@types/pbkdf2@npm:^3.1.0":
|
||||||
|
version: 3.1.0
|
||||||
|
resolution: "@types/pbkdf2@npm:3.1.0"
|
||||||
|
dependencies:
|
||||||
|
"@types/node": "*"
|
||||||
|
checksum: d15024b1957c21cf3b8887329d9bd8dfde754cf13a09d76ae25f1391cfc62bb8b8d7b760773c5dbaa748172fba8b3e0c3dbe962af6ccbd69b76df12a48dfba40
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"@types/prettier@npm:^1.19.0":
|
"@types/prettier@npm:^1.19.0":
|
||||||
version: 1.19.1
|
version: 1.19.1
|
||||||
resolution: "@types/prettier@npm:1.19.1"
|
resolution: "@types/prettier@npm:1.19.1"
|
||||||
|
@ -8755,7 +8764,7 @@ fsevents@~2.3.2:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"pbkdf2@npm:^3.0.3":
|
"pbkdf2@npm:^3.0.3, pbkdf2@npm:^3.1.2":
|
||||||
version: 3.1.2
|
version: 3.1.2
|
||||||
resolution: "pbkdf2@npm:3.1.2"
|
resolution: "pbkdf2@npm:3.1.2"
|
||||||
dependencies:
|
dependencies:
|
||||||
|
@ -10487,6 +10496,7 @@ resolve@^2.0.0-next.3:
|
||||||
dependencies:
|
dependencies:
|
||||||
"@types/chai": ^4.2.22
|
"@types/chai": ^4.2.22
|
||||||
"@types/mocha": ^9.0.0
|
"@types/mocha": ^9.0.0
|
||||||
|
"@types/pbkdf2": ^3.1.0
|
||||||
"@types/secp256k1": ^4.0.3
|
"@types/secp256k1": ^4.0.3
|
||||||
"@typescript-eslint/eslint-plugin": ^4.31.1
|
"@typescript-eslint/eslint-plugin": ^4.31.1
|
||||||
"@typescript-eslint/parser": ^4.31.1
|
"@typescript-eslint/parser": ^4.31.1
|
||||||
|
@ -10503,6 +10513,7 @@ resolve@^2.0.0-next.3:
|
||||||
js-waku: ^0.13.1
|
js-waku: ^0.13.1
|
||||||
mocha: ^9.1.1
|
mocha: ^9.1.1
|
||||||
npm-run-all: ^4.1.5
|
npm-run-all: ^4.1.5
|
||||||
|
pbkdf2: ^3.1.2
|
||||||
prettier: ^2.4.0
|
prettier: ^2.4.0
|
||||||
protobufjs: ^6.11.2
|
protobufjs: ^6.11.2
|
||||||
secp256k1: ^4.0.2
|
secp256k1: ^4.0.2
|
||||||
|
|
Loading…
Reference in New Issue