33 changed files with 601 additions and 397 deletions
@ -0,0 +1,81 @@ |
|||||
|
import React from "react"; |
||||
|
import { Button } from "@components/UI/Button.tsx"; |
||||
|
import type { LucideIcon } from "lucide-react"; |
||||
|
import { cn } from "@core/utils/cn.ts"; |
||||
|
import { useSidebar } from "@core/stores/sidebarStore.tsx"; |
||||
|
|
||||
|
export interface SidebarButtonProps { |
||||
|
label: string; |
||||
|
count?: number; |
||||
|
active?: boolean; |
||||
|
Icon?: LucideIcon; |
||||
|
children?: React.ReactNode; |
||||
|
onClick?: () => void; |
||||
|
disabled?: boolean; |
||||
|
preventCollapse?: boolean; |
||||
|
} |
||||
|
|
||||
|
export const SidebarButton = ({ |
||||
|
label, |
||||
|
active, |
||||
|
Icon, |
||||
|
count, |
||||
|
children, |
||||
|
onClick, |
||||
|
disabled = false, |
||||
|
preventCollapse = false, |
||||
|
}: SidebarButtonProps) => { |
||||
|
const { isCollapsed: isSidebarCollapsed } = useSidebar(); |
||||
|
const isButtonCollapsed = isSidebarCollapsed && !preventCollapse; |
||||
|
|
||||
|
return ( |
||||
|
<Button |
||||
|
onClick={onClick} |
||||
|
variant={active ? "subtle" : "ghost"} |
||||
|
size="sm" |
||||
|
className={cn( |
||||
|
"flex w-full items-center text-wrap", |
||||
|
isButtonCollapsed |
||||
|
? 'justify-center gap-0 px-2 h-9' |
||||
|
: 'justify-start gap-2 min-h-9' |
||||
|
)} |
||||
|
disabled={disabled} |
||||
|
> |
||||
|
{Icon && ( |
||||
|
<Icon |
||||
|
size={isButtonCollapsed ? 20 : 18} |
||||
|
className="flex-shrink-0" |
||||
|
/> |
||||
|
)} |
||||
|
|
||||
|
{children} |
||||
|
|
||||
|
<span |
||||
|
className={cn( |
||||
|
'flex flex-wrap justify-start text-left text-wrap break-all', |
||||
|
'min-w-0', |
||||
|
'px-1', |
||||
|
'transition-all duration-300 ease-in-out', |
||||
|
isButtonCollapsed |
||||
|
? 'opacity-0 max-w-0 invisible w-0 overflow-hidden' |
||||
|
: 'opacity-100 max-w-full visible flex-1 whitespace-normal' |
||||
|
)} |
||||
|
> |
||||
|
{label} |
||||
|
</span> |
||||
|
|
||||
|
{!isButtonCollapsed && !active && count && count > 0 && ( |
||||
|
<div |
||||
|
className={cn( |
||||
|
"ml-auto flex-shrink-0 justify-end text-white text-xs rounded-full px-1.5 py-0.5 bg-red-600", |
||||
|
"flex-shrink-0", |
||||
|
"transition-opacity duration-300 ease-in-out", |
||||
|
isButtonCollapsed ? 'opacity-0 invisible' : 'opacity-100 visible' |
||||
|
)} |
||||
|
> |
||||
|
{count} |
||||
|
</div> |
||||
|
)} |
||||
|
</Button> |
||||
|
); |
||||
|
}; |
||||
@ -1,234 +0,0 @@ |
|||||
import { create } from 'zustand'; |
|
||||
import { persist, createJSONStorage } from 'zustand/middleware'; |
|
||||
import { produce } from 'immer'; |
|
||||
import { Types } from '@meshtastic/core'; |
|
||||
import { zustandIndexDBStorage } from "./storage/indexDB.ts"; |
|
||||
|
|
||||
export enum MessageState { |
|
||||
Ack = "ack", |
|
||||
Waiting = "waiting", |
|
||||
Failed = "failed", |
|
||||
} |
|
||||
|
|
||||
export enum MessageType { |
|
||||
Direct = "direct", |
|
||||
Broadcast = "broadcast", |
|
||||
} |
|
||||
|
|
||||
interface MessageBase { |
|
||||
channel: Types.ChannelNumber; |
|
||||
to: number; |
|
||||
from: number; |
|
||||
date: number; |
|
||||
messageId: number; |
|
||||
state: MessageState; |
|
||||
message: string; |
|
||||
} |
|
||||
|
|
||||
interface GenericMessage<T extends MessageType> extends MessageBase { |
|
||||
type: T; |
|
||||
} |
|
||||
|
|
||||
export type Message = GenericMessage<MessageType.Direct> | GenericMessage<MessageType.Broadcast>; |
|
||||
|
|
||||
export interface MessageStore { |
|
||||
messages: { |
|
||||
direct: Record<number, Record<number, Record<number, Message>>>; |
|
||||
broadcast: Record<number, Record<number, Message>>; // channel -> messageId -> Message
|
|
||||
}; |
|
||||
draft: Map<Types.Destination, string>; |
|
||||
nodeNum: number; // This device's node number
|
|
||||
activeChat: number; // Represents otherNodeNum for Direct, or channel for Broadcast
|
|
||||
chatType: MessageType; |
|
||||
|
|
||||
setNodeNum: (nodeNum: number) => void; |
|
||||
getNodeNum: () => number; |
|
||||
setActiveChat: (chat: number) => void; |
|
||||
setChatType: (type: MessageType) => void; |
|
||||
saveMessage: (message: Message) => void; |
|
||||
setMessageState: (params: { |
|
||||
type: MessageType; |
|
||||
// For Direct: Represents the *other* node number involved in the chat.
|
|
||||
// For Broadcast: Represents the channel number.
|
|
||||
key: number; |
|
||||
messageId: number; |
|
||||
newState?: MessageState; |
|
||||
}) => void; |
|
||||
getMessages: (type: MessageType, options: { myNodeNum: number; otherNodeNum?: number; channel?: number }) => Message[]; |
|
||||
getDraft: (key: Types.Destination) => string; |
|
||||
setDraft: (key: Types.Destination, message: string) => void; |
|
||||
deleteAllMessages: () => void; |
|
||||
clearMessageByMessageId: (params: { |
|
||||
type: MessageType; |
|
||||
from?: number; |
|
||||
to?: number; |
|
||||
channel?: number; |
|
||||
messageId: number |
|
||||
}) => void; |
|
||||
clearDraft: (key: Types.Destination) => void; |
|
||||
} |
|
||||
|
|
||||
const CURRENT_STORE_VERSION = 0; |
|
||||
|
|
||||
export const useMessageStore = create<MessageStore>()( |
|
||||
// persist(
|
|
||||
(set, get) => ({ |
|
||||
messages: { |
|
||||
direct: {}, // Record<sender, Record<recipient, Record<messageId, Message>>>
|
|
||||
broadcast: {}, |
|
||||
}, |
|
||||
draft: new Map<number, string>(), |
|
||||
activeChat: 0, |
|
||||
chatType: MessageType.Broadcast, |
|
||||
nodeNum: 0, |
|
||||
setNodeNum: (nodeNum) => { |
|
||||
set(produce((state: MessageStore) => { |
|
||||
state.nodeNum = nodeNum; |
|
||||
})); |
|
||||
}, |
|
||||
getNodeNum: () => get().nodeNum, |
|
||||
setActiveChat: (chat) => { |
|
||||
set(produce((state: MessageStore) => { |
|
||||
state.activeChat = chat; |
|
||||
})); |
|
||||
}, |
|
||||
setChatType: (type) => { |
|
||||
set(produce((state: MessageStore) => { |
|
||||
state.chatType = type; |
|
||||
})); |
|
||||
}, |
|
||||
saveMessage: (message) => { |
|
||||
set(produce((state: MessageStore) => { |
|
||||
if (message.type === MessageType.Direct) { |
|
||||
const sender = Number(message.from); |
|
||||
const recipient = Number(message.to); |
|
||||
|
|
||||
if (!state.messages.direct[sender]) { |
|
||||
state.messages.direct[sender] = {}; |
|
||||
} |
|
||||
if (!state.messages.direct[sender][recipient]) { |
|
||||
state.messages.direct[sender][recipient] = {}; |
|
||||
} |
|
||||
state.messages.direct[sender][recipient][message.messageId] = message; |
|
||||
|
|
||||
} else if (message.type === MessageType.Broadcast) { |
|
||||
const channel = Number(message.channel); |
|
||||
if (!state.messages.broadcast[channel]) { |
|
||||
state.messages.broadcast[channel] = {}; |
|
||||
} |
|
||||
state.messages.broadcast[channel][message.messageId] = message; |
|
||||
} |
|
||||
})); |
|
||||
}, |
|
||||
setMessageState: ({ |
|
||||
type, |
|
||||
key, |
|
||||
messageId, |
|
||||
newState = MessageState.Ack, |
|
||||
}) => { |
|
||||
set( |
|
||||
produce((state: MessageStore) => { |
|
||||
let message: Message | undefined; |
|
||||
|
|
||||
if (type === MessageType.Broadcast) { |
|
||||
const channel = key; |
|
||||
message = state.messages.broadcast?.[channel]?.[messageId]; |
|
||||
} else if (type === MessageType.Direct) { |
|
||||
const otherNodeNum = key; |
|
||||
const myNodeNum = state.nodeNum; |
|
||||
|
|
||||
message = state.messages.direct?.[myNodeNum]?.[otherNodeNum]?.[messageId]; |
|
||||
|
|
||||
if (!message) { |
|
||||
message = state.messages.direct?.[otherNodeNum]?.[myNodeNum]?.[messageId]; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
if (message) { |
|
||||
message.state = newState; |
|
||||
} else { |
|
||||
console.warn(`Message not found for state update - type: ${type}, key (otherNode/channel): ${key}, messageId: ${messageId}, myNodeNum: ${state.nodeNum}`); |
|
||||
} |
|
||||
}), |
|
||||
); |
|
||||
}, |
|
||||
getMessages: (type, options) => { |
|
||||
const state = get(); |
|
||||
|
|
||||
if (type === MessageType.Broadcast && options.channel !== undefined) { |
|
||||
const messageMap = state.messages.broadcast[options.channel] ?? {}; |
|
||||
return Object.values(messageMap).sort((a, b) => a.date - b.date); |
|
||||
} |
|
||||
|
|
||||
if (type === MessageType.Direct && options.myNodeNum !== undefined && options.otherNodeNum !== undefined) { |
|
||||
const myNodeNum = options.myNodeNum; |
|
||||
const otherNodeNum = options.otherNodeNum; |
|
||||
|
|
||||
// Messages sent BY ME TO OTHER
|
|
||||
const sentByMeMap = state.messages.direct?.[myNodeNum]?.[otherNodeNum] ?? {}; |
|
||||
const sentByMe = Object.values(sentByMeMap); |
|
||||
|
|
||||
// Messages sent BY OTHER TO ME
|
|
||||
const sentByOtherMap = state.messages.direct?.[otherNodeNum]?.[myNodeNum] ?? {}; |
|
||||
const sentByOther = Object.values(sentByOtherMap); |
|
||||
|
|
||||
// Merge and sort chronologically
|
|
||||
return [...sentByMe, ...sentByOther].sort((a, b) => a.date - b.date); |
|
||||
} |
|
||||
return []; |
|
||||
}, |
|
||||
clearMessageByMessageId: ({ type, from, to, channel, messageId }) => { |
|
||||
set(produce((state: MessageStore) => { |
|
||||
if (type === MessageType.Broadcast && channel !== undefined) { |
|
||||
const messageMap = state.messages.broadcast[channel]; |
|
||||
if (messageMap?.[messageId]) { |
|
||||
delete messageMap[messageId]; |
|
||||
if (Object.keys(messageMap).length === 0) { |
|
||||
delete state.messages.broadcast[channel]; |
|
||||
} |
|
||||
} |
|
||||
} else if (type === MessageType.Direct && from !== undefined && to !== undefined) { |
|
||||
const messageMap = state.messages.direct?.[from]?.[to]; |
|
||||
if (messageMap?.[messageId]) { |
|
||||
delete messageMap[messageId]; |
|
||||
if (Object.keys(messageMap).length === 0) { |
|
||||
delete state.messages.direct[from][to]; |
|
||||
if (Object.keys(state.messages.direct[from]).length === 0) { |
|
||||
delete state.messages.direct[from]; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
console.warn("clearMessageByMessageId called without sufficient identifiers for type", type); |
|
||||
} |
|
||||
})); |
|
||||
}, |
|
||||
getDraft: (key) => { |
|
||||
return get().draft.get(key) ?? ''; |
|
||||
}, |
|
||||
setDraft: (key, message) => { |
|
||||
set(produce((state: MessageStore) => { |
|
||||
state.draft.set(key, message); |
|
||||
})); |
|
||||
}, |
|
||||
clearDraft: (key) => { |
|
||||
set(produce((state: MessageStore) => { |
|
||||
state.draft.delete(key); |
|
||||
})); |
|
||||
}, |
|
||||
deleteAllMessages: () => { |
|
||||
set(produce((state: MessageStore) => { |
|
||||
state.messages.direct = {}; |
|
||||
state.messages.broadcast = {}; |
|
||||
})); |
|
||||
} |
|
||||
}), |
|
||||
// {
|
|
||||
// name: 'meshtastic-message-store',
|
|
||||
// storage: createJSONStorage(() => zustandIndexDBStorage),
|
|
||||
// version: CURRENT_STORE_VERSION,
|
|
||||
// partialize: (state) => ({
|
|
||||
// messages: state.messages,
|
|
||||
// nodeNum: state.nodeNum,
|
|
||||
// }),
|
|
||||
// }
|
|
||||
); |
|
||||
@ -0,0 +1,212 @@ |
|||||
|
import { create } from 'zustand'; |
||||
|
import { persist } from 'zustand/middleware'; |
||||
|
import { produce } from 'immer'; |
||||
|
import { Types } from '@meshtastic/core'; |
||||
|
import { storageWithMapSupport } from "../storage/indexDB.ts"; |
||||
|
import { ChannelId, ClearMessageParams, ConversationId, GetMessagesParams, Message, MessageId, MessageLogMap, NodeNum, SetMessageStateParams } from "@core/stores/messageStore/types.ts"; |
||||
|
|
||||
|
export enum MessageState { |
||||
|
Ack = "ack", |
||||
|
Waiting = "waiting", |
||||
|
Failed = "failed", |
||||
|
} |
||||
|
|
||||
|
export enum MessageType { |
||||
|
Direct = "direct", |
||||
|
Broadcast = "broadcast", |
||||
|
} |
||||
|
|
||||
|
function getConversationId(node1: NodeNum, node2: NodeNum): ConversationId { |
||||
|
return [node1, node2].sort((a, b) => a - b).join(':'); |
||||
|
} |
||||
|
|
||||
|
export interface MessageStore { |
||||
|
messages: { |
||||
|
direct: Map<ConversationId, MessageLogMap>; |
||||
|
broadcast: Map<ChannelId, MessageLogMap>; |
||||
|
}; |
||||
|
}; |
||||
|
export interface MessageStore { |
||||
|
messages: MessageStore['messages']; |
||||
|
draft: Map<Types.Destination, string>; |
||||
|
nodeNum: number; // This device's node number
|
||||
|
activeChat: number; // Represents otherNodeNum for Direct, or channel for Broadcast
|
||||
|
chatType: MessageType; |
||||
|
|
||||
|
setNodeNum: (nodeNum: number) => void; |
||||
|
getMyNodeNum: () => number; |
||||
|
setActiveChat: (chat: number) => void; |
||||
|
setChatType: (type: MessageType) => void; |
||||
|
saveMessage: (message: Message) => void; |
||||
|
setMessageState: (params: SetMessageStateParams) => void; |
||||
|
getMessages: (params: GetMessagesParams) => Message[]; |
||||
|
getDraft: (key: Types.Destination) => string; |
||||
|
setDraft: (key: Types.Destination, message: string) => void; |
||||
|
deleteAllMessages: () => void; |
||||
|
clearMessageByMessageId: (params: ClearMessageParams) => void; |
||||
|
clearDraft: (key: Types.Destination) => void; |
||||
|
} |
||||
|
|
||||
|
const CURRENT_STORE_VERSION = 0; |
||||
|
|
||||
|
export const useMessageStore = create<MessageStore>()( |
||||
|
persist( |
||||
|
(set, get) => ({ |
||||
|
messages: { |
||||
|
direct: new Map<ConversationId, MessageLogMap>(), |
||||
|
broadcast: new Map<ChannelId, MessageLogMap>(), |
||||
|
}, |
||||
|
draft: new Map<number, string>(), |
||||
|
activeChat: 0, |
||||
|
chatType: MessageType.Broadcast, |
||||
|
nodeNum: 0, |
||||
|
setNodeNum: (nodeNum) => { |
||||
|
set(produce((state: MessageStore) => { |
||||
|
state.nodeNum = nodeNum; |
||||
|
})); |
||||
|
}, |
||||
|
getMyNodeNum: () => get().nodeNum, |
||||
|
setActiveChat: (chat) => { |
||||
|
set(produce((state: MessageStore) => { |
||||
|
state.activeChat = chat; |
||||
|
})); |
||||
|
}, |
||||
|
setChatType: (type) => { |
||||
|
set(produce((state: MessageStore) => { |
||||
|
state.chatType = type; |
||||
|
})); |
||||
|
}, |
||||
|
saveMessage: (message: Message) => { |
||||
|
set( |
||||
|
produce((state: MessageStore) => { |
||||
|
if (message.type === MessageType.Direct) { |
||||
|
const conversationId = getConversationId(message.from, message.to); |
||||
|
if (!state.messages.direct.has(conversationId)) { |
||||
|
state.messages.direct.set(conversationId, new Map<MessageId, Message>()); |
||||
|
} |
||||
|
state.messages.direct.get(conversationId)!.set(message.messageId, message); |
||||
|
} else if (message.type === MessageType.Broadcast) { |
||||
|
const channelId = message.channel as ChannelId; |
||||
|
if (!state.messages.broadcast.has(channelId)) { |
||||
|
state.messages.broadcast.set(channelId, new Map<MessageId, Message>()); |
||||
|
} |
||||
|
state.messages.broadcast.get(channelId)!.set(message.messageId, message); |
||||
|
} |
||||
|
}) |
||||
|
); |
||||
|
}, |
||||
|
|
||||
|
setMessageState: (params: SetMessageStateParams) => { |
||||
|
set( |
||||
|
produce((state: MessageStore) => { |
||||
|
let messageLog: MessageLogMap | undefined; |
||||
|
let targetMessage: Message | undefined; |
||||
|
|
||||
|
if (params.type === MessageType.Direct) { |
||||
|
const conversationId = getConversationId(params.nodeA, params.nodeB); |
||||
|
messageLog = state.messages.direct.get(conversationId); |
||||
|
if (messageLog) { |
||||
|
targetMessage = messageLog.get(params.messageId); |
||||
|
} |
||||
|
} else { // Broadcast
|
||||
|
messageLog = state.messages.broadcast.get(params.channelId); |
||||
|
if (messageLog) { |
||||
|
targetMessage = messageLog.get(params.messageId); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
if (targetMessage) { |
||||
|
targetMessage.state = params.newState ?? MessageState.Ack; |
||||
|
} else { |
||||
|
console.warn(`Message or conversation/channel not found for state update. Params: ${JSON.stringify(params)}`); |
||||
|
} |
||||
|
}) |
||||
|
); |
||||
|
}, |
||||
|
getMessages: (params: GetMessagesParams): Message[] => { |
||||
|
const state = get(); |
||||
|
let messageMap: MessageLogMap | undefined; |
||||
|
|
||||
|
if (params.type === MessageType.Direct) { |
||||
|
const conversationId = getConversationId(params.nodeA, params.nodeB); |
||||
|
messageMap = state.messages.direct.get(conversationId); |
||||
|
|
||||
|
} else { |
||||
|
messageMap = state.messages.broadcast.get(params.channelId); |
||||
|
} |
||||
|
|
||||
|
if (messageMap === undefined) { |
||||
|
return []; |
||||
|
} |
||||
|
|
||||
|
const messagesArray = Array.from(messageMap.values()); |
||||
|
messagesArray.sort((a, b) => a.date - b.date); |
||||
|
return messagesArray; |
||||
|
}, |
||||
|
|
||||
|
clearMessageByMessageId: (params: ClearMessageParams) => { |
||||
|
set( |
||||
|
produce((state: MessageStore) => { |
||||
|
let messageLog: MessageLogMap | undefined; |
||||
|
let parentMap: Map<ConversationId | ChannelId, MessageLogMap>; |
||||
|
let parentKey: ConversationId | ChannelId; |
||||
|
|
||||
|
if (params.type === MessageType.Direct) { |
||||
|
parentKey = getConversationId(params.nodeA, params.nodeB); |
||||
|
parentMap = state.messages.direct; |
||||
|
messageLog = parentMap.get(parentKey); |
||||
|
} else { |
||||
|
parentKey = params.channelId; |
||||
|
parentMap = state.messages.broadcast; |
||||
|
messageLog = parentMap.get(parentKey); |
||||
|
} |
||||
|
|
||||
|
if (messageLog) { |
||||
|
const deleted = messageLog.delete(params.messageId); |
||||
|
|
||||
|
if (deleted) { |
||||
|
console.log(`Deleted message ${params.messageId} from ${params.type} chat ${parentKey}`); |
||||
|
// Clean up empty MessageLogMap and its entry in the parent map
|
||||
|
if (messageLog.size === 0) { |
||||
|
parentMap.delete(parentKey); |
||||
|
console.log(`Cleaned up empty chat entry for ${parentKey}`); |
||||
|
} |
||||
|
} else { |
||||
|
console.warn(`Message ${params.messageId} not found in ${params.type} chat ${parentKey} for deletion.`); |
||||
|
} |
||||
|
} else { |
||||
|
console.warn(`Chat entry ${parentKey} not found for message deletion.`); |
||||
|
} |
||||
|
}) |
||||
|
); |
||||
|
}, |
||||
|
getDraft: (key) => { |
||||
|
return get().draft.get(key) ?? ''; |
||||
|
}, |
||||
|
setDraft: (key, message) => { |
||||
|
set(produce((state: MessageStore) => { |
||||
|
state.draft.set(key, message); |
||||
|
})); |
||||
|
}, |
||||
|
clearDraft: (key) => { |
||||
|
set(produce((state: MessageStore) => { |
||||
|
state.draft.delete(key); |
||||
|
})); |
||||
|
}, |
||||
|
deleteAllMessages: () => { |
||||
|
set(produce((state: MessageStore) => { |
||||
|
state.messages.direct = new Map<ConversationId, MessageLogMap>(); |
||||
|
state.messages.broadcast = new Map<ChannelId, MessageLogMap>(); |
||||
|
})); |
||||
|
} |
||||
|
}), |
||||
|
{ |
||||
|
name: 'meshtastic-message-store', |
||||
|
storage: storageWithMapSupport, |
||||
|
version: CURRENT_STORE_VERSION, |
||||
|
partialize: (state) => ({ |
||||
|
messages: state.messages, |
||||
|
nodeNum: state.nodeNum, |
||||
|
}), |
||||
|
}) |
||||
|
) |
||||
@ -0,0 +1,69 @@ |
|||||
|
import { Types } from "@meshtastic/core"; |
||||
|
import { MessageState, MessageType } from "@core/stores/messageStore/index.ts"; |
||||
|
|
||||
|
type NodeNum = number; |
||||
|
type MessageId = number; |
||||
|
type ChannelId = Types.ChannelNumber; |
||||
|
type ConversationId = string; |
||||
|
type MessageLogMap = Map<MessageId, Message>; |
||||
|
|
||||
|
export interface MessageBase { |
||||
|
channel: Types.ChannelNumber; |
||||
|
to: number; |
||||
|
from: number; |
||||
|
date: number; |
||||
|
messageId: number; |
||||
|
state: MessageState; |
||||
|
message: string; |
||||
|
} |
||||
|
|
||||
|
interface GenericMessage<T extends MessageType> extends MessageBase { |
||||
|
type: T; |
||||
|
} |
||||
|
|
||||
|
export type Message = GenericMessage<MessageType.Direct> | GenericMessage<MessageType.Broadcast>; |
||||
|
|
||||
|
|
||||
|
type GetMessagesParams = |
||||
|
| { type: MessageType.Direct; nodeA: NodeNum; nodeB: NodeNum } |
||||
|
| { type: MessageType.Broadcast; channelId: ChannelId }; |
||||
|
|
||||
|
|
||||
|
type SetMessageStateParams = |
||||
|
| { |
||||
|
type: MessageType.Direct; |
||||
|
nodeA: NodeNum; |
||||
|
nodeB: NodeNum; |
||||
|
messageId: MessageId; // ID of the message within that chat
|
||||
|
newState?: MessageState; // Optional new state, defaults to Ack
|
||||
|
} |
||||
|
| { |
||||
|
type: MessageType.Broadcast; |
||||
|
channelId: ChannelId; |
||||
|
messageId: MessageId; |
||||
|
newState?: MessageState; // Optional new state, defaults to Ack
|
||||
|
}; |
||||
|
|
||||
|
type ClearMessageParams = |
||||
|
| { |
||||
|
type: MessageType.Direct; |
||||
|
nodeA: NodeNum; |
||||
|
nodeB: NodeNum; |
||||
|
messageId: MessageId; |
||||
|
} |
||||
|
| { |
||||
|
type: MessageType.Broadcast; |
||||
|
channelId: ChannelId; |
||||
|
messageId: MessageId; |
||||
|
}; |
||||
|
|
||||
|
export type { |
||||
|
ConversationId, |
||||
|
NodeNum, |
||||
|
MessageLogMap, |
||||
|
ChannelId, |
||||
|
MessageId, |
||||
|
GetMessagesParams, |
||||
|
SetMessageStateParams, |
||||
|
ClearMessageParams, |
||||
|
} |
||||
Loading…
Reference in new issue