Implement chat message protobuf to support nick and time handles

This commit is contained in:
Franck Royer 2021-03-31 10:43:29 +11:00
parent 0f694cf8e1
commit cca1d685dc
No known key found for this signature in database
GPG Key ID: A82ED75A8DFC50A4
8 changed files with 104 additions and 11 deletions

View File

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

View File

@ -0,0 +1,9 @@
syntax = "proto3";
package chat.v2;
message ChatMessageProto {
uint64 timestamp = 1;
string nick = 2;
bytes payload = 3;
}

View File

@ -0,0 +1,26 @@
import { expect } from 'chai';
import fc from 'fast-check';
import { ChatMessage } from './chat_message';
describe('Chat Message', function () {
it('Chat message round trip binary serialization', function () {
fc.assert(
fc.property(
fc.date({ min: new Date(0) }),
fc.string(),
fc.string(),
(timestamp, nick, message) => {
const msg = new ChatMessage(timestamp, nick, message);
const buf = msg.encode();
const actual = ChatMessage.decode(buf);
// Date.toString does not include ms, as we loose this precision by design
expect(actual.timestamp.toString()).to.eq(timestamp.toString());
expect(actual.nick).to.eq(nick);
expect(actual.message).to.eq(message);
}
)
);
});
});

35
src/chat/chat_message.ts Normal file
View File

@ -0,0 +1,35 @@
import { Reader } from 'protobufjs/minimal';
import { ChatMessageProto } from '../proto/chat/v2/chat_message';
export class ChatMessage {
public constructor(
public timestamp: Date,
public nick: string,
public message: string
) {}
static decode(bytes: Uint8Array): ChatMessage {
const protoMsg = ChatMessageProto.decode(Reader.create(bytes));
const timestamp = new Date(protoMsg.timestamp * 1000);
const message = protoMsg.payload
? Array.from(protoMsg.payload)
.map((char) => {
return String.fromCharCode(char);
})
.join('')
: '';
return new ChatMessage(timestamp, protoMsg.nick, message);
}
encode(): Uint8Array {
const timestamp = Math.floor(this.timestamp.valueOf() / 1000);
const payload = Buffer.from(this.message, 'utf-8');
return ChatMessageProto.encode({
timestamp,
nick: this.nick,
payload,
}).finish();
}
}

View File

@ -5,6 +5,8 @@ import { Message } from '../lib/waku_message';
import { TOPIC } from '../lib/waku_relay'; import { TOPIC } from '../lib/waku_relay';
import { delay } from '../test_utils/delay'; import { delay } from '../test_utils/delay';
import { ChatMessage } from './chat_message';
(async function () { (async function () {
const opts = processArguments(); const opts = processArguments();
@ -12,8 +14,18 @@ import { delay } from '../test_utils/delay';
// TODO: Bubble event to waku, infer topic, decode msg // TODO: Bubble event to waku, infer topic, decode msg
waku.libp2p.pubsub.on(TOPIC, (event) => { waku.libp2p.pubsub.on(TOPIC, (event) => {
const msg = Message.fromBinary(event.data); const wakuMsg = Message.decode(event.data);
console.log(msg.utf8Payload()); if (wakuMsg.payload) {
const chatMsg = ChatMessage.decode(wakuMsg.payload);
const timestamp = chatMsg.timestamp.toLocaleString([], {
month: 'short',
day: 'numeric',
hour: 'numeric',
minute: '2-digit',
hour12: false,
});
console.log(`<${timestamp}> ${chatMsg.nick}: ${chatMsg.message}`);
}
}); });
console.log('Waku started'); console.log('Waku started');
@ -47,7 +59,9 @@ import { delay } from '../test_utils/delay';
rl.prompt(); rl.prompt();
rl.on('line', async (line) => { rl.on('line', async (line) => {
rl.prompt(); rl.prompt();
const msg = Message.fromUtf8String('(js-chat) ' + line); const chatMessage = new ChatMessage(new Date(), 'js-chat', line);
const msg = Message.fromBytes(chatMessage.encode());
await waku.relay.publish(msg); await waku.relay.publish(msg);
}); });
})(); })();

View File

@ -8,7 +8,7 @@ describe('Waku Message', function () {
fc.property(fc.string(), (s) => { fc.property(fc.string(), (s) => {
const msg = Message.fromUtf8String(s); const msg = Message.fromUtf8String(s);
const binary = msg.toBinary(); const binary = msg.toBinary();
const actual = Message.fromBinary(binary); const actual = Message.decode(binary);
return actual.isEqualTo(msg); return actual.isEqualTo(msg);
}) })

View File

@ -15,16 +15,25 @@ export class Message {
) {} ) {}
/** /**
* Create Message from utf-8 string * Create Message with a utf-8 string as payload
* @param message * @param payload
* @returns {Message} * @returns {Message}
*/ */
static fromUtf8String(message: string): Message { static fromUtf8String(payload: string): Message {
const payload = Buffer.from(message, 'utf-8'); const buf = Buffer.from(payload, 'utf-8');
return new Message(buf, DEFAULT_CONTENT_TOPIC, DEFAULT_VERSION);
}
/**
* Create Message with a byte array as payload
* @param payload
* @returns {Message}
*/
static fromBytes(payload: Uint8Array): Message {
return new Message(payload, DEFAULT_CONTENT_TOPIC, DEFAULT_VERSION); return new Message(payload, DEFAULT_CONTENT_TOPIC, DEFAULT_VERSION);
} }
static fromBinary(bytes: Uint8Array): Message { static decode(bytes: Uint8Array): Message {
const wakuMsg = WakuMessage.decode(Reader.create(bytes)); const wakuMsg = WakuMessage.decode(Reader.create(bytes));
return new Message(wakuMsg.payload, wakuMsg.contentTopic, wakuMsg.version); return new Message(wakuMsg.payload, wakuMsg.contentTopic, wakuMsg.version);
} }

View File

@ -392,6 +392,6 @@ function waitForNextData(pubsub: Pubsub): Promise<Message> {
return new Promise((resolve) => { return new Promise((resolve) => {
pubsub.once(TOPIC, resolve); pubsub.once(TOPIC, resolve);
}).then((msg: any) => { }).then((msg: any) => {
return Message.fromBinary(msg.data); return Message.decode(msg.data);
}); });
} }