# 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: {},
};
```