Add Audio, Image and Sticker messages

This commit is contained in:
Franck Royer 2021-10-07 14:21:49 +11:00
parent 304a1021b1
commit e6b3ec616a
No known key found for this signature in database
GPG Key ID: A82ED75A8DFC50A4
4 changed files with 194 additions and 130 deletions

View File

@ -1,4 +1,4 @@
import { ChatMessage } from "./chat_message"; import { ChatMessage, MediaContent } from "./chat_message";
import { chatIdToContentTopic } from "./contentTopic"; import { chatIdToContentTopic } from "./contentTopic";
import { createSymKeyFromPassword } from "./encryption"; import { createSymKeyFromPassword } from "./encryption";
@ -24,10 +24,16 @@ export class Chat {
return chatIdToContentTopic(this.id); return chatIdToContentTopic(this.id);
} }
public createMessage(text: string): ChatMessage { public createMessage(text: string, mediaContent?: MediaContent): ChatMessage {
const { timestamp, clock } = this._nextClockAndTimestamp(); const { timestamp, clock } = this._nextClockAndTimestamp();
const message = ChatMessage.createMessage(clock, timestamp, text, this.id); const message = ChatMessage.createMessage(
clock,
timestamp,
text,
this.id,
mediaContent
);
this._updateClockFromMessage(message); this._updateClockFromMessage(message);

View File

@ -0,0 +1,95 @@
import { expect } from "chai";
import {
AudioContent,
ChatMessage,
ContentType,
ImageContent,
StickerContent,
} from "./chat_message";
import {
AudioMessage_AudioType,
ChatMessage_ContentType,
} from "./proto/communities/v1/chat_message";
import { ImageType } from "./proto/communities/v1/enums";
describe("Chat Message", () => {
it("Encode & decode Image 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,
"Some text",
"chat-id",
imageContent
);
const buf = message.encode();
const dec = ChatMessage.decode(buf);
expect(dec.contentType).eq(ChatMessage_ContentType.CONTENT_TYPE_IMAGE);
expect(dec.image?.payload?.toString()).eq(payload.toString());
expect(dec.image?.type).eq(ImageType.IMAGE_TYPE_PNG);
});
it("Encode & decode Audio message", () => {
const payload = Buffer.from([1, 1]);
const durationMs = 12345;
const audioContent: AudioContent = {
audio: payload,
audioType: AudioMessage_AudioType.AUDIO_TYPE_AAC,
durationMs,
contentType: ContentType.Audio,
};
const message = ChatMessage.createMessage(
1,
1,
"Some text",
"chat-id",
audioContent
);
const buf = message.encode();
const dec = ChatMessage.decode(buf);
expect(dec.contentType).eq(ChatMessage_ContentType.CONTENT_TYPE_AUDIO);
expect(dec.audio?.payload?.toString()).eq(payload.toString());
expect(dec.audio?.type).eq(ImageType.IMAGE_TYPE_PNG);
expect(dec.audio?.durationMs).eq(durationMs);
});
it("Encode & decode Sticker message", () => {
const hash = "deadbeef";
const pack = 12345;
const stickerContent: StickerContent = {
hash,
pack,
contentType: ContentType.Sticker,
};
const message = ChatMessage.createMessage(
1,
1,
"Some text",
"chat-id",
stickerContent
);
const buf = message.encode();
const dec = ChatMessage.decode(buf);
expect(dec.contentType).eq(ChatMessage_ContentType.CONTENT_TYPE_STICKER);
expect(dec.sticker?.hash).eq(hash);
expect(dec.sticker?.pack).eq(pack);
});
});

View File

@ -10,53 +10,96 @@ import {
} from "./proto/communities/v1/chat_message"; } from "./proto/communities/v1/chat_message";
import { ImageType, MessageType } from "./proto/communities/v1/enums"; import { ImageType, MessageType } from "./proto/communities/v1/enums";
export type MediaContent = StickerContent | ImageContent | AudioContent;
export enum ContentType {
Sticker,
Image,
Audio,
}
export interface StickerContent {
hash: string;
pack: number;
contentType: ContentType.Sticker;
}
export interface ImageContent {
image: Uint8Array;
imageType: ImageType;
contentType: ContentType.Image;
}
export interface AudioContent {
audio: Uint8Array;
audioType: AudioMessage_AudioType;
durationMs: number;
contentType: ContentType.Audio;
}
function isSticker(content: MediaContent): content is StickerContent {
return content.contentType === ContentType.Sticker;
}
function isImage(content: MediaContent): content is ImageContent {
return content.contentType === ContentType.Image;
}
function isAudio(content: MediaContent): content is AudioContent {
return content.contentType === ContentType.Audio;
}
export class ChatMessage { export class ChatMessage {
private constructor(public proto: proto.ChatMessage) {} private constructor(public proto: proto.ChatMessage) {}
/** /**
* Create a chat message to be sent to an Open (permission = no membership) community * Create a chat message to be sent to an Open (permission = no membership) community.
*
* @throws string If mediaContent is malformed
*/ */
public static createMessage( public static createMessage(
clock: number,
timestamp: number,
text: string,
chatId: string
): ChatMessage {
const proto = {
clock, // ms?
timestamp, //ms?
text,
/** Id of the message that we are replying to */
responseTo: "",
/** Ens name of the sender */
ensName: "",
/** Public Key of the community (TBC) **/
chatId,
/** The type of message (public/one-to-one/private-group-chat) */
messageType: MessageType.MESSAGE_TYPE_COMMUNITY_CHAT,
/** The type of the content of the message */
contentType: ChatMessage_ContentType.CONTENT_TYPE_TEXT_PLAIN,
sticker: undefined,
image: undefined,
audio: undefined,
community: undefined, // Used to share a community
grant: undefined,
};
return new ChatMessage(proto);
}
/**
* Create a an image chat message to be sent to an Open (permission = no membership) community
*/
public static createImageMessage(
clock: number, clock: number,
timestamp: number, timestamp: number,
text: string, text: string,
chatId: string, chatId: string,
image: Uint8Array, mediaContent?: MediaContent
imageType: ImageType
): ChatMessage { ): ChatMessage {
let sticker, image, audio;
let contentType = ChatMessage_ContentType.CONTENT_TYPE_TEXT_PLAIN;
if (mediaContent) {
if (isSticker(mediaContent)) {
if (!mediaContent.hash || !mediaContent.pack)
throw "Malformed Sticker Content";
sticker = {
hash: mediaContent.hash,
pack: mediaContent.pack,
};
contentType = ChatMessage_ContentType.CONTENT_TYPE_STICKER;
} else if (isImage(mediaContent)) {
if (!mediaContent.image || !mediaContent.imageType)
throw "Malformed Image Content";
image = {
payload: mediaContent.image,
type: mediaContent.imageType,
};
contentType = ChatMessage_ContentType.CONTENT_TYPE_IMAGE;
} else if (isAudio(mediaContent)) {
if (
!mediaContent.audio ||
!mediaContent.audioType ||
!mediaContent.durationMs
)
throw "Malformed Audio Content";
audio = {
payload: mediaContent.audio,
type: mediaContent.audioType,
durationMs: mediaContent.durationMs,
};
contentType = ChatMessage_ContentType.CONTENT_TYPE_AUDIO;
}
}
const proto = { const proto = {
clock, // ms? clock, // ms?
timestamp, //ms? timestamp, //ms?
@ -70,91 +113,10 @@ export class ChatMessage {
/** The type of message (public/one-to-one/private-group-chat) */ /** The type of message (public/one-to-one/private-group-chat) */
messageType: MessageType.MESSAGE_TYPE_COMMUNITY_CHAT, messageType: MessageType.MESSAGE_TYPE_COMMUNITY_CHAT,
/** The type of the content of the message */ /** The type of the content of the message */
contentType: ChatMessage_ContentType.CONTENT_TYPE_IMAGE, contentType,
sticker: undefined, sticker,
image: { image,
payload: image, audio,
type: imageType,
},
audio: undefined,
community: undefined, // Used to share a community
grant: undefined,
};
return new ChatMessage(proto);
}
/**
* Create a a sticker chat message to be sent to an Open (permission = no membership) community
*/
public static createStickerMessage(
clock: number,
timestamp: number,
text: string,
chatId: string,
stickerHash: string,
stickerPack: number
): ChatMessage {
const proto = {
clock, // ms?
timestamp, //ms?
text,
/** Id of the message that we are replying to */
responseTo: "",
/** Ens name of the sender */
ensName: "",
/** Public Key of the community (TBC) **/
chatId,
/** The type of message (public/one-to-one/private-group-chat) */
messageType: MessageType.MESSAGE_TYPE_COMMUNITY_CHAT,
/** The type of the content of the message */
contentType: ChatMessage_ContentType.CONTENT_TYPE_STICKER,
sticker: {
hash: stickerHash,
pack: stickerPack,
},
image: undefined,
audio: undefined,
community: undefined, // Used to share a community
grant: undefined,
};
return new ChatMessage(proto);
}
/**
* Create a a sticker chat message to be sent to an Open (permission = no membership) community
*/
public static createAudioMessage(
clock: number,
timestamp: number,
text: string,
chatId: string,
audio: Uint8Array,
audioType: AudioMessage_AudioType,
audioDurationMs: number
): ChatMessage {
const proto = {
clock, // ms?
timestamp, //ms?
text,
/** Id of the message that we are replying to */
responseTo: "",
/** Ens name of the sender */
ensName: "",
/** Public Key of the community (TBC) **/
chatId,
/** The type of message (public/one-to-one/private-group-chat) */
messageType: MessageType.MESSAGE_TYPE_COMMUNITY_CHAT,
/** The type of the content of the message */
contentType: ChatMessage_ContentType.CONTENT_TYPE_AUDIO,
sticker: undefined,
image: undefined,
audio: {
payload: audio,
type: audioType,
durationMs: audioDurationMs,
},
community: undefined, // Used to share a community community: undefined, // Used to share a community
grant: undefined, grant: undefined,
}; };

View File

@ -4,7 +4,7 @@ import { CreateOptions as WakuCreateOptions } from "js-waku/build/main/lib/waku"
import { ApplicationMetadataMessage } from "./application_metadata_message"; import { ApplicationMetadataMessage } from "./application_metadata_message";
import { Chat } from "./chat"; import { Chat } from "./chat";
import { ChatMessage } from "./chat_message"; import { ChatMessage, MediaContent } from "./chat_message";
import { Identity } from "./identity"; import { Identity } from "./identity";
import { ApplicationMetadataMessage_Type } from "./proto/status/v1/application_metadata_message"; import { ApplicationMetadataMessage_Type } from "./proto/status/v1/application_metadata_message";
@ -71,15 +71,16 @@ export class Messenger {
/** /**
* Sends a message on the given chat Id. * Sends a message on the given chat Id.
*
* @param text
* @param chatId
*/ */
public async sendMessage(text: string, chatId: string): Promise<void> { public async sendMessage(
text: string,
chatId: string,
mediaContent?: MediaContent
): Promise<void> {
const chat = this.chatsById.get(chatId); const chat = this.chatsById.get(chatId);
if (!chat) throw `Failed to send message, chat not joined: ${chatId}`; if (!chat) throw `Failed to send message, chat not joined: ${chatId}`;
const chatMessage = chat.createMessage(text); const chatMessage = chat.createMessage(text, mediaContent);
const appMetadataMessage = ApplicationMetadataMessage.create( const appMetadataMessage = ApplicationMetadataMessage.create(
chatMessage.encode(), chatMessage.encode(),