# Group Messaging without Encryption ###### tags: `Kizuna-architecture` <style> .ui-infobar, #doc.markdown-body { max-width: 1000px; } </style> <img src="https://i.imgur.com/Ei4AjHI.png" width="150" style="padding-right:5px; padding-bottom:5px"/> <img src="https://i.imgur.com/KZZK6rP.png" width="150" style="padding-right:5px; padding-bottom:5px"/> <img src="https://i.imgur.com/pRh0t9E.png" width="150" style="padding-right:5px; padding-bottom:5px"/> <img src="https://i.imgur.com/jkRjXyA.png" width="150" style="padding-right:5px; padding-bottom:5px"/> <img src="https://i.imgur.com/GZlmVbA.png" width="150" style="padding-right:5px; padding-bottom:5px"/> <img src="https://i.imgur.com/sDMto7Q.png" width="150" style="padding-right:5px; padding-bottom:5px"/> :::info Without Encryption ::: ## Entry Structures ```rust= #[hdk_entry(id = "group_message", visibility = "public")] struct GroupMessage { // EntryHash of first ver of Group group_hash: EntryHash, payload: Payload, created: Timestamp, sender: AgentPubKey, reply_to: Option<EntryHash> } #[hdk_entry(id = "group_file_bytes", visibility = "public")] struct GroupFileBytes(SerializedBytes) ``` ## Data Structures ```rust= /* This will be defined in a /commons crate that will be used for both p2pmessage and group zome */ #[derive(Serialize, Deserialize, SerializedBytes, Clone)] #[serde(rename_all="camelCase")] struct FileMetadataInput { file_name: String, file_size: usize, file_type: String, } #[derive(Serialize, Deserialize, SerializedBytes, Clone)] #[serde(rename_all="camelCase")] enum PayloadInput { Text {payload: String}, File { metadata: FileMetadataInput file_type: FileType } } #[derive(Serialize, Deserialize, SerializedBytes, Clone)] #[serde(rename_all="camelCase")] pub struct FileMetadata { file_name: String, file_size: usize, file_type: String, file_hash: EntryHash } // https://serde.rs/enum-representations.html#internally-tagged #[derive(Serialize, Deserialize, SerializedBytes, Clone)] #[serde(tag = "fileType")] pub enum FileType { Image { thumbnail: serializedBytes, }, Video { thumbnail: serializedBytes, }, Other } #[derive(Serialize, Deserialize, SerializedBytes, Clone)] #[serde(tag = "type")] enum Payload { Text {payload: String}, File { metadata: FileMetadata, file_type: FileType } } #[derive(Serialize, Deserialize, SerializedBytes, Clone, Debug)] #[serde(rename_all = "camelCase")] pub struct GroupMessageWithId { // entry_hash of GroupMessage pub id: EntryHash, pub content: GroupMessage, } #[derive(Serialize, Deserialize, SerializedBytes, Clone)] #[serde(rename_all = "camelCase")] pub struct GroupMessageData { pub group_hash: EntryHash, pub payload: Payload, pub created: Timestamp, pub sender: AgentPubKey, pub reply_to: Option<GroupMessageWithId> }; #[derive(Serialize, Deserialize, SerializedBytes, Clone, Debug)] #[serde(rename_all = "camelCase")] pub struct GroupMessageElement { pub entry: GroupMessageData, pub signed_header: SignedHeaderHashed, } ``` ## Zome Functions ### `init` ```mermaid sequenceDiagram participant ALC as Alice_Lobby_Cell ALC-->>ALC: create unrestricted cap grant for recv_remote_signal ``` ### `send_message` - [x] build with no errors - [x] send_message test - [x] check messages - [x] tryorama tests for send_message ```rust= struct GroupMessageInput{ group_hash: EntryHash, payload: PayloadInput, // Will only have value if Payload::File file_bytes: Option<SerializedBytes> sender: AgentPubKey, reply_to: Option<EntryHash> } #[hdk_extern] pub fn send_message(message: GroupMessageInput) -> ExternResult<GroupMessageData> ``` ```mermaid sequenceDiagram participant AUI as Alice_UI participant ALC as Alice_Lobby_Cell participant BLC as Bobby_Lobby_Cell participant LDHT as Lobby_DHT AUI-->>ALC: call send_message opt Payload::File ALC-->>ALC: create GroupFileBytes entry from file_bytes ALC-->>LDHT: commit GroupFileBytes to DHT end ALC-->>ALC: create GroupMessagentry ALC-->>LDHT: commit GroupMessage to DHT ALC-->>ALC: generate path "group_hash.unix_timestamp".ensure() note right of ALC: orignal EntryHash note right of ALC: unix_timestamp should be created 1 per day in UTC timestamp format alt payload is a text ALC-->>LDHT: link from unix_timestamp -> GroupMessage with tag "text" else payload is a file ALC-->>LDHT: link from unix_timestamp -> GroupMessage with tag "file" end note right of ALC: we can get the timestamp per message from the timestamp of the Link ALC-->>AUI: return GroupMessage (skip this until post_commit is possible) note right of ALC: yellow box will be moved to post_commit rect rgba(255,255,0,0.2) rect rgba(250,100,0,0.2) note right of ALC: call get_group_latest_version() for the dark orange box ALC-->>LDHT: get_details with given EntryHash of Group note right of ALC: GetOption latest() LDHT-->>ALC: Group EntryDetails ALC-->>ALC: check if there is update or not alt there is an update for group Entry ALC-->>ALC: check which update header is the latest ALC-->>LDHT: get entry of the EntryHash in latest update header note right of ALC: GetOption contents() note right of ALC: may change once get_details() API returns elements in update field and not just header LDHT-->>ALC: latest Group entry end end ALC-->>ALC: get the current group members loop for all members ALC-->>BLC: invoke 'remote_signal' payload GroupMessageData end alt User is online rect rgba(0,230, 100, .5) BLC-->>BLC: invoke `recv_remote_signal` end end end ``` ### `recv_remote_signal` ```rust= #[hdk_extern] fn pin_message(signal: SerializedBytes) -> ExternResult<()> ``` ```mermaid sequenceDiagram participant BUI as Bobby_UI participant BLC as Bobby_Lobby_Cell participant ALC as Alice_Lobby_Cell BLC-->>BLC: check Payload enum type alt "GroupMessage" BLC-->>BUI: emit_signal Message to UI else "GroupMessageRead" BLC-->>BUI: emit_signal GroupMessageRead end ``` ### `indicate_group_typing` ```rust= #[derive(Serialize, Deserialize, SerializedBytes, Clone, Debug)] pub struct GroupTypingDetailIO { group_id: EntryHash, indicated_by: AgentPubKey, members: Vec<AgentPubKey>, is_typing: bool, } enum SignalPayload { GroupTypingDetail(GroupTypingDetailIO) } #[hdk_extern] fn indicate_group_typing(group_typing_detail: GroupTypingDetailIO) -> ExternResult<()> ``` ```mermaid sequenceDiagram participant AUI as Alice_UI participant ALD as Alice_Cell participant OC as Others_Cell participant OUI as Others_UI AUI-->>ALD: sets preference ALD-->>AUI: fetch preference AUI-->>AUI: starts or stops typing alt typing preference is on alt the Group's EntryHash is not in Alice's typing_indicator within PerGroupPreference AUI-->>ALD: call group_typing ALD-->>OC: remote_signal GroupTypingDetailIO alt User is online OC-->>OUI: emit_signal GroupTypingDetailIO alt if is_typing is true OUI-->>OUI: Add Alice's profile in typing array else if is_typing is false OUI-->>OUI: Remove Alice's profile in typing array end else User is offline Note left of OUI: Do nothing end end end ``` ### read_group_message (tats) ```rust= struct GroupMessageReadData { group_id: EntryHash message_ids: Vec<EntryHash>; reader: AgentPubKey; timestamp: Timestamp; message_created: Timestamp; members: Vec<AgentPubKey> } enum SignalPayload { GroupMessageRead(GroupMessageReadData), } pub fn read_group_message(group_message_read_data: GroupMessageReadData) -> ExternResult<GroupMessageReadData> ``` ```mermaid sequenceDiagram participant AUI as Alice_UI participant AC as Alice_Cell participant OC as Others_Cell participant OUI as Others_UI AUI-->AC: sets preference AC-->>AUI: returns preference AUI-->>AUI: reads group message note over AC,AUI: premise til here AUI-->>AC: read_group_message AC-->>AC: create_link AgentPubKey -> group EntryHash with Tag (group_id,unix_timestamp,message_hash) note over AC: unix_timestamp is the timestamp of message creation AC-->>OC: invoke 'remote_signal' with payload to all members alt Others are online OC-->>OC: invoke 'recv_remote_signal' OC-->>OUI: emit_signal GroupMessageRead end AC-->>AUI: GroupMessageReadData ``` ## Pin Message Related Fns ### `pin_message` ```rust= struct PinDetail { group_hash: EntryHash, group_message_hash: EntryHash, } #[hdk_extern] fn pin_message(pin_detail: PinDetail) -> ExternResult<()> ``` ```mermaid sequenceDiagram participant AUI as Alice_UI participant ALC as Alice_Lobby_Cell participant BLC as Bobby_Lobby_Cell participant LDHT as Lobby_DHT AUI-->>ALC: call pin_message ALC-->>LDHT: create_link (base: group, target: group_message, tag: "pinned") LDHT-->>ALC: HeaderHash ALC-->>AUI: Ok() ``` ### `unpin_message` ```rust= struct pinDetail { group_hash: EntryHash, group_message_hash: EntryHash } #[hdk_extern] fn unpin_message(pin_detail: pinDetail) -> ExternResult<()> ``` ```mermaid sequenceDiagram participant AUI as Alice_UI participant ALC as Alice_Lobby_Cell participant BLC as Bobby_Lobby_Cell participant LDHT as Lobby_DHT AUI-->>ALC: call unpin_message ALC-->>LDHT: get_links (base: group_hash, link_tag: "pinned") LDHT-->>ALC: Links ALC-->>ALC: get the index of the desired link to be removed with `position` method alt the Link to be deleted exists ALC-->>LDHT: delete_link() the link which contains the pinned message hash LDHT-->>ALC: HeaderHash else the Link does not exist ALC-->>AUI: Ok() end ALC-->>AUI: Ok() ``` ### `get_pinned_messages` ```rust= struct GroupHash(EntryHash) struct PinContents(HashMap<GroupMessageHash, GroupMessageElement>) #[hdk_extern] fn get_pinned_messages(group_hash: GroupHash) -> ExternResult<PinContents> ``` ```mermaid sequenceDiagram participant AUI as Alice_UI participant ALC as Alice_Lobby_Cell participant BLC as Bobby_Lobby_Cell participant LDHT as Lobby_DHT AUI-->>ALC: call get_pinned_messages ALC-->>ALC: initialize PinContents HashMap ALC-->>LDHT: get_links (base: group_hash, link_tag: "pinned") LDHT-->>ALC: Links loop for link in Links ALC-->>LDHT: get on target (GroupMessage EntryHash) note over ALC: contents() GetOptions LDHT-->>ALC: GroupMessage Element alt Message is a replyTo another message ALC-->>LDHT: get() on the EntryHash of the replied message LDHT-->>ALC: GroupMessage Element end ALC-->>ALC: insert GroupMessage Hash and GroupMessage Element (k,v) end ALC-->>AUI: Ok(pinContents) ``` ### Getters * UI will be doing the consolidation of values * pass as much data from the backend * standardize return values/structures across zome functions #### Structures ```rust= pub enum PayloadType { Text, File, All } pub struct GroupMessageHash(EntryHash) pub struct ReadList(HashMap<AgentPubKey, Timestamp>) pub struct GroupMessageContent(GroupMessageElement, ReadList) pub struct MessagesByGroup(HashMap<GroupEntryHash, Vec<GroupMessageHash>>) pub struct GroupMessagesContents(HashMap<GroupMessageHash, GroupMessageContent>) pub struct GroupMessagesOutput( MessagesByGroup, GroupMessagesContents ) ``` ### `get_messages_by_group_by_timestamp` **USE CASE IN UI** - to fetch texts for a particular date - to fetch files for a particular date - to fetch all messages for a particular date ```rust= pub struct GroupChatFilter { // has to be the original EntryHash group_id: EntryHash, // has to be divideable with no remainder in YYYY/MM/DD/00:00 date: Timestamp, // This signifies which payload type is desired // to be fetched. payload_type: PayloadType } pub fn get_messages_by_group_by_timestamp(group_chat_filter: GroupChatFilter) -> ExternResult<GroupMessagesOutput> ``` ```mermaid sequenceDiagram participant BUI as Bobby_UI participant BLC as Bobby_Lobby_Cell participant LDHT as Lobby_DHT BUI-->>BLC: call `fetch_messages_by_group_by_timestamp` BLC-->>BLC: constrcut path group_hash.timestamp alt payload_type is File BLC-->>LDHT: get_links on given group_hash.timestamp with tag "file" else payload_type is Text BLC-->>LDHT: get_links on given group_hash.timestamp with tag "text" else payload_type is All BLC-->>LDHT: get_links on given group_hash.timestamp with NO tag end LDHT-->>BLC: Links (the target is groupmessage entryhash) BLC-->>BLC: initialize MessagesByGroup (group_id as single key) BLC-->>BLC: construct GroupMessagesContents HashMap loop for all Targets BLC-->>LDHT: get() on target (GroupMessage EntryHash) note right of BLC: contents() GetOptions LDHT-->>BLC: GroupMessage Element alt Message is a replyTo another message BLC-->>LDHT: get() on the EntryHash of the replied message end BLC-->>BLC: insert GroupEntryHash:GroupMessageHash to MessagesByGroup loop for all members BLC-->>LDHT: get_links with member AgentPubKey|group_id:| LDHT-->>BLC: Links (target AgentPubKey) end BLC-->>BLC: construct empty ReadList HashMap loop for all links BLC-->>BLC: insert AgentPubKey:link.timestamp end BLC-->>BLC: construct GroupMessageData BLC-->>BLC: construct GroupMessageElement BLC-->>BLC: construct GroupMessageContent with (GroupMessageElement, ReadList) BLC-->>BLC: insert GroupMessageHash:GroupMessageBundle to MessagesByGroup end BLC-->>BLC: construct GroupMessagesOutput BLC-->>BUI: return GroupMessagesOutput ``` ### `get_previous_group_messages` **USE CASE IN UI** - get the previous (timestamp wise) batch of messages (both text and files) when the user scrolls up in the group chatroom page - get the pervious batch of files when the user scrolls in the file tab of the group chat (see the ui picture above for reference) - get the latest batch of messages for a particular group. Useful when the user refreshes the page when s/he is in the group chatroom page. - get the latest batch of files for a particular group when the user opens up the file page for a particular group ```rust= pub struct GroupMsgBatchFetchFilter { group_id: EntryHash, // the last message of the last batch last_fetched: Option<EntryHash>, last_message_timestamp: Option<Timestamp>, batch_size: u8, payload_type: PayloadType } pub fn get_previous_group_messages(filter: GroupMsgBatchFetchFilter) -> ExternResult<GroupMessagesOutput> ``` ```mermaid sequenceDiagram participant BUI as Bobby_UI participant BLC as Bobby_Lobby_Cell participant LDHT as Lobby_DHT BUI-->>BLC: call `get_previous_group_messages` BLC-->>BLC: initialize MessagesByGroup (group_id as single key) BLC-->>BLC: initialize GroupMessagesContents HashMap alt if last_fetched && last_message_timestamp BLC-->>BLC: last_message_timestamp.0 /(SECONDS * MINUTES * HOURS) BLC-->>BLC: construct the Path group_hash.timestamp(the one divided to days) with the group_id and timestamp given in arg BLC-->>BLC: construct Path entry hash alt payload_type is File BLC-->>LDHT: get_links with Path entry hash with tag "file" else payload_type is Text BLC-->>LDHT: get_links with Path entry hash with tag "text" else payload_type is All BLC-->>LDHT: get_links with Path entry hash with NO tag end LDHT-->>BLC: links (the target is groupmessage entryhash) BLC-->>BLC: find the last_fetched EntryHash given in arg from all the Target loop for link.timestamp < last_fetched_message_link.timestamp loop while vec.length (in MessagesByGroup) != batch_size BLC-->>BLC: append GroupMessageEntryhash found in Target in MessagesByGroup[group_id] (newest to oldest) end end end opt while vec.length (in MessagesByGroup) != batch_size BLC-->>BLC: construct Path from group_id BLC-->>LDHT: get_links on the path (group_hash to string) LDHT-->>BLC: return links (timestamp) BLC-->>BLC: sort Links(timestamp): newest to oldest based on timestamp in Link loop for every Link (timestamp) starting with the Link that has the previous date (when compared with the Link that had the last fetched message) as timestamp alt payload_type is File BLC-->>LDHT: get_links the Target with tag "file" else payload_type is Text BLC-->>LDHT: get_links the Target with tag "text" else payload_type is All BLC-->>LDHT: get_links the Target with NO tag end LDHT-->>BLC: Links (group_message EntryHash) loop for every group_message Link BLC-->>BLC: append GroupMessageEntryhash found in Target in MessagesByGroup[group_id] (newest to oldest) opt batch_size is met BLC-->>BLC: break the loop end end end end loop for GroupMessageEntryHash found in MessagesByGroup BLC-->>LDHT: get() with EntryHash note right of BLC: getOption content() LDHT-->>BLC: GroupMessage Element alt Message is a replyTo another message BLC-->>LDHT: get() on the EntryHash of the replied message LDHT-->>BLC: GroupMessage Element end BLC-->>LDHT: get_links with GroupMessage EntryHash|read| LDHT-->>BLC: Links (target AgentPubKey) BLC-->>BLC: construct empty ReadList HashMap loop for all links BLC-->>BLC: insert AgentPubKey:link.timestamp end BLC-->>BLC: construct GroupMessageData BLC-->>BLC: construct GroupMessageElement BLC-->>BLC: construct GroupMessageContent with (GroupMessageElement, ReadList) BLC-->>BLC: insert GroupMessageHash:GroupMessageContent to GroupMessagesContents end BLC-->>BLC: construct GroupMessagesOutput BLC-->>BUI: return GroupMessagesOutput ``` ### `get_subsequent_group_messages` **USE CASE IN UI** - get the next (later timestamp) batch of group messages. Useful when the user jumps into a pinned or replied message and tries to scroll back in to the latest message. ```rust= pub struct GroupMsgBatchFetchFilter { group_id: EntryHash, // the latest message of the last batch fetched last_fetched: EntryHash, last_message_timestamp: Timestamp, batch_size: u8, payload_type: PayloadType } pub fn get_subsequent_group_messages(filter: GroupMsgBatchFetchFilter) -> ExternResult<GroupMessagesOutput> ``` ```mermaid sequenceDiagram participant BUI as Bobby_UI participant BLC as Bobby_Lobby_Cell participant LDHT as Lobby_DHT BUI-->>BLC: call `get_subsequent_group_messages` BLC-->>BLC: initialize MessagesByGroup (group_id as single key) BLC-->>BLC: initialize GroupMessagesContents HashMap alt if last_fetched && last_message_timestamp BLC-->>BLC: last_message_timestamp.0 /(SECONDS * MINUTES * HOURS) BLC-->>BLC: construct the Path group_hash.timestamp(the one divided to days) with the group_id and timestamp given in arg BLC-->>BLC: construct Path entry hash alt payload_type is File BLC-->>LDHT: get_links with Path entry hash with tag "file" else payload_type is Text BLC-->>LDHT: get_links with Path entry hash with tag "text" else payload_type is All BLC-->>LDHT: get_links with Path entry hash with NO tag end LDHT-->>BLC: links (the target is groupmessage entryhash) BLC-->>BLC: find the last_fetched EntryHash given in arg from all the Target loop for link.timestamp > last_fetched_message_link.timestamp loop while vec.length (in MessagesByGroup) != batch_size BLC-->>BLC: append GroupMessageEntryhash found in Target in MessagesByGroup[group_id] end end end opt while vec.length (in MessagesByGroup) != batch_size BLC-->>BLC: construct Path from group_id BLC-->>LDHT: get_links on the path (group_hash to string) LDHT-->>BLC: return links (timestamp) BLC-->>BLC: sort Links(timestamp): newest to oldest based on timestamp in Link loop for every Link (timestamp) starting with the Link that has the next date (when compared with the Link that had the last fetched message) as timestamp alt payload_type is File BLC-->>LDHT: get_links the Target with tag "file" else payload_type is Text BLC-->>LDHT: get_links the Target with tag "text" else payload_type is All BLC-->>LDHT: get_links the Target with NO tag end LDHT-->>BLC: Links (group_message EntryHash) loop for every group_message Link BLC-->>BLC: append GroupMessageEntryhash found in Target in MessagesByGroup[group_id] opt batch_size is met BLC-->>BLC: break the loop end end end end loop for GroupMessageEntryHash found in MessagesByGroup BLC-->>LDHT: get() with EntryHash note right of BLC: getOption content() LDHT-->>BLC: GroupMessage Element alt Message is a replyTo another message BLC-->>LDHT: get() on the EntryHash of the replied message LDHT-->>BLC: GroupMessage Element end BLC-->>LDHT: get_links with GroupMessage EntryHash|read| LDHT-->>BLC: Links (target AgentPubKey) BLC-->>BLC: construct empty ReadList HashMap loop for all links BLC-->>BLC: insert AgentPubKey:link.timestamp end BLC-->>BLC: construct GroupMessageData BLC-->>BLC: construct GroupMessageElement BLC-->>BLC: construct GroupMessageContent with (GroupMessageElement, ReadList) BLC-->>BLC: insert GroupMessageHash:GroupMessageContent to GroupMessagesContents end BLC-->>BLC: construct GroupMessagesOutput BLC-->>BUI: return GroupMessagesOutput ``` ### `get_adjacent_group_messages` **USE CASE IN UI** - get certain number of messages previous and later to a certain message. Useful when we want to be able to jump to a pinned or replied message. ```rust= pub struct GroupMsgAdjacentFetchFilter { group_id: EntryHash, // the message EntryHash that has previous and later adjacent messages adjacent_message: EntryHash, message_timestamp: Timestamp, // This batch size goes for both forward and backward batch_size: u8, } pub fn get_adjacent_group_messages(filter: GroupMsgAdjacentFetchFilter) -> ExternResult<GroupMessagesOutput> ``` ```mermaid sequenceDiagram participant BUI as Bobby_UI participant BLC as Bobby_Lobby_Cell participant LDHT as Lobby_DHT BUI-->>BLC: call `get_adjacent_group_messages` BLC-->>BLC: initialize MessagesByGroup (group_id as single key) BLC-->>BLC: initialize GroupMessagesContents HashMap BLC-->>BLC: message_timestamp.0 /(SECONDS * MINUTES * HOURS) BLC-->>BLC: construct the Path group_hash.timestamp(the one divided to days) with the group_id and timestamp given in arg BLC-->>BLC: construct Path entry hash BLC-->>LDHT: get_links with Path entry hash with NO Tag LDHT-->>BLC: links (the target is groupmessage entryhash) BLC-->>BLC: find the last_fetched EntryHash given in arg from all the Target loop for link.timestamp > message_timestamp note over BLC: for messages with timestamp later than the adjacent message timestamp loop while no of message EntryHash in MessagesByGroup (where messageId.timestamp > message_timestamp) != batch_size BLC-->>BLC: append GroupMessageEntryhash found in Target in MessagesByGroup[group_id] end note over BLC,LDHT: repeat the loop where link.timestamp < message_timestamp end opt while no of message EntryHash in MessagesByGroup (where messageId.timestamp > message_timestamp) != batch_size note over BLC: for messages with timestamp later than the adjacent message timestamp BLC-->>BLC: construct Path from group_id BLC-->>LDHT: get_links on the path (group_hash to string) LDHT-->>BLC: return links (timestamp) BLC-->>BLC: sort Links (timestamp): newest to oldest based on timestamp in Link loop for every Link (timestamp) starting with the Link that has the next date (when compared with the Link that BLC-->>LDHT: get_links the Target with NO tag LDHT-->>BLC: Links (group_message EntryHash) loop for every group_message Link BLC-->>BLC: append GroupMessageEntryhash found in Target in MessagesByGroup[group_id] (newest to oldest) opt batch_size is met BLC-->>BLC: break the loop end end end note over BLC,LDHT: repeat the loop above where messageId.timestamp < message_timestamp end loop for GroupMessageEntryHash found in MessagesByGroup BLC-->>LDHT: get() with EntryHash note right of BLC: getOption content() LDHT-->>BLC: GroupMessage Element alt Message is a replyTo another message BLC-->>LDHT: get() on the EntryHash of the replied message LDHT-->>BLC: GroupMessage Element end BLC-->>LDHT: get_links with GroupMessage EntryHash|read| LDHT-->>BLC: Links (target AgentPubKey) BLC-->>BLC: construct empty ReadList HashMap loop for all links BLC-->>BLC: insert AgentPubKey:link.timestamp end BLC-->>BLC: construct GroupMessageData BLC-->>BLC: construct GroupMessageElement BLC-->>BLC: construct GroupMessageContent with (GroupMessageElement, ReadList) BLC-->>BLC: insert GroupMessageHash:GroupMessageContent to GroupMessagesContents end BLC-->>BLC: construct GroupMessagesOutput BLC-->>BUI: return GroupMessagesOutput ``` ### `get_latest_messages_for_all_groups` **USE CASE IN UI** - get the latest messages in batch for all groups when the app loads up. (this function will be called by the aggregator zome together with other necessary functions to render the ui) ```rust= pub struct BatchSize(u8) pub fn get_latest_messages_for_all_groups(batch_size: BatchSize) -> ExternResult<GroupMessagesOutput> ``` ```mermaid sequenceDiagram participant AG as Aggregator_Zome participant BLC as Bobby_Lobby_Cell participant LDHT as Lobby_DHT AG-->>BLC: call `fetch_latest_messages_for_all_groups` BLC-->>BLC: initialize MessagesByGroup BLC-->>BLC: initialize GroupMessagesContents HashMap BLC-->>LDHT: get_links agent_pubkey->group|member| (group_hash) LDHT-->>BLC: Links (Group EntryHash) loop for each Link (group_hash) BLC-->>BLC: call fetch_next_batch_group_messages() with payload_type All note over BLC: last_fetched/last_message_timestamp as None BLC-->>BLC: return GroupMessagesOutput BLC-->>BLC: insert GroupMessagesContents and MessagesByGroup values from returned GroupMessagesOutput into the initialized MessagesByGroup and GroupMessagesContents end BLC-->>BLC: construct GroupMessagesOutput BLC-->>AG: return GroupMessagesOutput ``` ### `get_file_bytes` - Useful when the user would like to download a single file ```rust= pub struct EntryHashWrapper(EntryHash) fn get_file_bytes(file_entry_hash: EntryHash) -> ExternResult<GroupFileBytes> ``` ```mermaid sequenceDiagram participant BUI as Bobby_UI participant BLC as Bobby_Lobby_Cell participant LDHT as Lobby_DHT BUI-->>BLC: call get_file_bytes BLC-->>LDHT: get() with file_entry_hash note right of BLC: getOption content() note right of BLC: GroupFileBytes can have multiple headers but we are only concern with the content not the metadata so content() option is fine LDHT-->>BLC: GroupFileBytes BLC-->>BUI: return GroupFileBytes ``` ## Redux ```rust= pub struct GroupMessageHash(EntryHash) pub struct ReadList(HashMap<AgentPubKey, Timestamp>) pub struct GroupMessageContent(GroupMessageElement, ReadList) pub struct MessagesByGroup(HashMap<GroupEntryHash, Vec<GroupMessageHash>>) pub struct GroupMessagesContents(HashMap<GroupMessageHash, GroupMessageContent>) pub struct GroupMessagesOutput( MessagesByGroup, GroupMessagesContents ) ``` ```typescript= type GroupMessageIDB64 = string; type GroupIDB64 = string; // Group's EntryHash type GroupFileBytesID = string; // GroupFileBytes' EntryHash type GroupRevisionID = string; // Group's HeaderHash type Payload = TextPayload | FilePayload interface TextPayload { type: "TEXT"; payload: { payload: string }; } interface FilePayload { type: "FILE"; fileName: string; fileSize: number; fileType: "IMAGE" | "VIDEO" | "OTHER"; fileHash: FileBytesID; thumbnail?: Uint8Array; } interface GroupMessage { groupMessageId: GroupMessageIDB64; groupId: GroupIDB64; author: string; // AgentPubKey in b64 payload: Payload; // subject to change timestamp: Date; replyTo?: GroupMessageIDB64; readList: { // key is AgentPubKey [key: string]: Date; }; } // TODO: make sure this is fetched from holochain at some point interface GroupVersion { groupEntryHash: GroupID name: string; conversants: ProfileID[]; timestamp: Date; } interface GroupConversation { originalGroupId: GroupIDB64; // EntryHash originalGroupRevisionId: GroupRevisionIDB64; // HeaderHash // versions: GroupVersion[]; name: string; members: string[]; createdAt: Date; creator: string; // TODO: enable setting of avatar for a GroupConversation avatar?: string; messages: GroupMessageIDB64[]; pinned: GroupMessageIDB64[]; } interface MessagesByGroup { // key here is the base64 string of Group EntryHash [key: string]: GroupMessageIDB64[]; } interface GroupMessagesContents { // key here is the base 64 string of GroupMessage EntryHash [key: string]: GroupMessage; } interface GroupMessagesOutput { messagesByGroup: MessagesByGroup; groupMessagesContents: GroupMessagesContents; } interface UpdateGroupMembersData { // base64 string members: string[]; groupId: GroupIDB64; groupRevisionId: GroupRevisionIDB64; } interface UpdateGroupNameData { name: string; groupId: GroupIDB64; groupRevisionId: GroupRevisionIDB64; } /* * Group Conversation interface */ export interface GroupConversationsState { conversations: { // key should be the originalGroupEntryHash [key: string]: GroupConversation; }; messages: { [key: string]: GroupMessage; }; groupFiles: { [key: string]: Uint8Array; }; // This makes it easier to manage when member of a group updates their username (username has no update fn yet) // and saves network calls to fetch username in case a member is part of multiple groups // key is agentPubKey members: { [key: string]: Profile; }; typing: { // key is GroupID [key: string]: Profile[]; }; pinned: { [key: string]: GroupMessage } } // group conversations redux // reducer const initialState: GroupConversationsState = { conversations: {}, messages: {}, groupFiles: {}, members: {}, typing: {}, pinned: {}, }; ```