Wire delivery acknowledgements

This commit is contained in:
Jazz Turner-Baggs 2025-09-26 17:26:11 -07:00
parent f52150ba83
commit aefacba0e4
No known key found for this signature in database
6 changed files with 43 additions and 68 deletions

View File

@ -35,6 +35,8 @@ type
sender: string sender: string
content: string content: string
timestamp: DateTime timestamp: DateTime
id: string
isAcknowledged: bool
ConvoInfo = object ConvoInfo = object
name: string name: string
@ -71,13 +73,15 @@ proc `==`(a,b: ConvoInfo):bool =
# Data Management # Data Management
################################################# #################################################
proc addMessage(conv: var ConvoInfo, sender: string, content: string) = proc addMessage(conv: var ConvoInfo, messageId: MessageId, sender: string, content: string) =
let now = now() let now = now()
conv.messages.add(Message( conv.messages.add(Message(
sender: sender, sender: sender,
id: messageId,
content: content, content: content,
timestamp: now timestamp: now,
isAcknowledged: false
)) ))
conv.lastMsgTime = now conv.lastMsgTime = now
@ -117,10 +121,13 @@ proc createConvo(app: ChatApp) {.async.} =
discard await app.client.newPrivateConversation(toBundle(app.inputInviteBuffer.strip()).get()) discard await app.client.newPrivateConversation(toBundle(app.inputInviteBuffer.strip()).get())
proc sendMessage(app: ChatApp, convoInfo: ptr ConvoInfo, msg: string) {.async.} = proc sendMessage(app: ChatApp, convoInfo: ptr ConvoInfo, msg: string) {.async.} =
convoInfo[].addMessage("You", app.inputBuffer)
var msgId = ""
if convoInfo.convo != nil: if convoInfo.convo != nil:
await convoInfo.convo.sendMessage(app.client.ds, initTextFrame(msg).toContentFrame()) msgId = await convoInfo.convo.sendMessage(app.client.ds, initTextFrame(msg).toContentFrame())
convoInfo[].addMessage(msgId, "You", app.inputBuffer)
proc setupChatSdk(app: ChatApp) = proc setupChatSdk(app: ChatApp) =
@ -149,6 +156,16 @@ proc setupChatSdk(app: ChatApp) =
app.client.onDeliveryAck(proc(convo: Conversation, msgId: string) {.async.} = app.client.onDeliveryAck(proc(convo: Conversation, msgId: string) {.async.} =
info "DeliveryAck", msgId=msgId info "DeliveryAck", msgId=msgId
app.logMsgs.add(LogEntry(level: "info",ts: now(), msg: fmt"Ack:{msgId}")) app.logMsgs.add(LogEntry(level: "info",ts: now(), msg: fmt"Ack:{msgId}"))
var s = ""
var msgs = addr app.conversations[convo.id()].messages
for i in countdown(msgs[].high, 0):
s = fmt"{s},{msgs[i].id}"
var m = addr msgs[i]
if m.id == msgId:
m.isAcknowledged = true
break # Stop after
) )
@ -265,11 +282,12 @@ proc drawMsgPane( app: ChatApp, layout: Pane) =
let m = convo.messages[i] let m = convo.messages[i]
let timeStr = m.timestamp.format("HH:mm:ss") let timeStr = m.timestamp.format("HH:mm:ss")
var deliveryIcon = " "
var remainingText = m.content var remainingText = m.content
if m.sender == "You": if m.sender == "You":
tb.setForegroundColor(fgYellow) tb.setForegroundColor(fgYellow)
deliveryIcon = if m.isAcknowledged: "" else: ""
else: else:
tb.setForegroundColor(fgGreen) tb.setForegroundColor(fgGreen)
@ -278,7 +296,7 @@ proc drawMsgPane( app: ChatApp, layout: Pane) =
app.logMsgs.add(LogEntry(level: "info",ts: now(), msg: fmt" TOO LONG: {convo.name}")) app.logMsgs.add(LogEntry(level: "info",ts: now(), msg: fmt" TOO LONG: {convo.name}"))
return return
tb.write(x, y, fmt"[{timeStr}] {m.sender}") tb.write(x, y, fmt"[{timeStr}] {deliveryIcon} {m.sender}")
y = y + 1 y = y + 1
while remainingText.len > 0: while remainingText.len > 0:
@ -300,11 +318,12 @@ proc drawMsgPane( app: ChatApp, layout: Pane) =
let m = convo.messages[i] let m = convo.messages[i]
let timeStr = m.timestamp.format("HH:mm:ss") let timeStr = m.timestamp.format("HH:mm:ss")
var deliveryIcon = " "
var remainingText = m.content var remainingText = m.content
if m.sender == "You": if m.sender == "You":
tb.setForegroundColor(fgYellow) tb.setForegroundColor(fgYellow)
deliveryIcon = if m.isAcknowledged: "" else: ""
else: else:
tb.setForegroundColor(fgGreen) tb.setForegroundColor(fgGreen)
@ -322,7 +341,7 @@ proc drawMsgPane( app: ChatApp, layout: Pane) =
tb.write(x+3, y, line) tb.write(x+3, y, line)
y = y - 1 y = y - 1
tb.write(x, y, fmt"[{timeStr}] {m.sender}") tb.write(x, y, fmt"[{timeStr}] {deliveryIcon} {m.sender}")
y = y - 2 y = y - 2
proc drawMsgInput( app: ChatApp, layout: Pane) = proc drawMsgInput( app: ChatApp, layout: Pane) =
@ -528,59 +547,10 @@ proc initChatApp(client: Client): Future[ChatApp] {.async.} =
# Add some sample conversations with messages # Add some sample conversations with messages
var conv1 = ConvoInfo(name: "Alice", messages: @[]) var conv1 = ConvoInfo(name: "ReadMe", messages: @[])
conv1.addMessage("36Alice", "Hey there! How are you doing?") conv1.addMessage("","Bob", "TODO")
conv1.addMessage("35You", "I'm doing well, thanks! Working on a new project.")
conv1.addMessage("34Alice", "That sounds exciting! What kind of project?")
conv1.addMessage("33You", "A terminal-based chat application in Nim!")
conv1.addMessage("32Alice", "Hey there! How are you doing?")
conv1.addMessage("31You", "I'm doing well, thanks! Working on a new project.")
conv1.addMessage("30Alice", "That sounds exciting! What kind of project?")
conv1.addMessage("29You", "A terminal-based chat application in Nim!")
conv1.addMessage("28Alice", "Hey there! How are you doing?")
conv1.addMessage("27You", "I'm doing well, thanks! Working on a new project.")
conv1.addMessage("26Alice", "That sounds exciting! What kind of project?")
conv1.addMessage("25You", "A terminal-based chat application in Nim! A terminal-based chat application in Nim! A terminal-based chat application in Nim! A terminal-based chat application in Nim! A terminal-based chat application in Nim! A terminal-based chat application in Nim! A terminal-based chat application in Nim! A terminal-based chat application in Nim! A terminal-based chat application in Nim! A terminal-based chat application in Nim!")
conv1.addMessage("24Alice", "Hey there! How are you doing?")
conv1.addMessage("23You", "I'm doing well, thanks! Working on a new project.")
conv1.addMessage("22Alice", "That sounds exciting! What kind of project?")
conv1.addMessage("21You", "A terminal-based chat application in Nim!")
conv1.addMessage("20Alice", "Hey there! How are you doing?")
conv1.addMessage("19You", "I'm doing well, thanks! Working on a new project.")
conv1.addMessage("18Alice", "That sounds exciting! What kind of project?")
conv1.addMessage("17You", "A terminal-based chat application in Nim!")
conv1.addMessage("16Alice", "Hey there! How are you doing?")
conv1.addMessage("15You", "I'm doing well, thanks! Working on a new project.")
conv1.addMessage("14Alice", "That sounds exciting! What kind of project?")
conv1.addMessage("13You", "A terminal-based chat application in Nim!")
conv1.addMessage("12Alice", "Hey there! How are you doing?")
conv1.addMessage("11You", "I'm doing well, thanks! Working on a new project.")
conv1.addMessage("10Alice", "That sounds exciting! What kind of project?")
conv1.addMessage("9You", "The system architecture consists of three main components: the client interface, the processing engine, and the data storage layer. Each component communicates through well-defined APIs that ensure scalability and maintainability. The client interface handles user interactions and validates input data before forwarding requests to the processing engine.")
conv1.addMessage("8Alice", "Hey there! How are you doing?")
conv1.addMessage("7You", "I'm doing well, thanks! Working on a new project.")
conv1.addMessage("6Alice", "That sounds exciting! What kind of project?")
conv1.addMessage("5You", "A terminal-based chat application in Nim!")
conv1.addMessage("4Alice", "Hey there! How are you doing?")
conv1.addMessage("3You", "I'm doing well, thanks! Working on a new project.")
conv1.addMessage("2Alice", "That sounds exciting! What kind of project?")
conv1.addMessage("1You", "A terminal-based chat application in Nim!")
app.conversations[conv1.name] = conv1 app.conversations[conv1.name] = conv1
var conv2 = ConvoInfo(name: "Bob", messages: @[])
conv2.addMessage("Bob", "Did you see the game last night?")
conv2.addMessage("You", "The system architecture consists of three main components: the client interface, the processing engine, and the data storage layer. Each component communicates through well-defined APIs that ensure scalability and maintainability. The client interface handles user interactions and validates input data before forwarding requests to the processing engine")
conv2.addMessage("Bob", "The home team crushed it! 3-0")
app.conversations[conv2.name] = conv2
var conv3 = ConvoInfo(name: "Development Team", messages: @[])
conv3.addMessage("Sarah", "Morning standup in 10 minutes")
conv3.addMessage("Mike", "I'll be there. Just finishing up the last bug fix.")
conv3.addMessage("You", "On my way!")
conv3.addMessage("Sarah", "Great! Let's review the sprint progress today.")
app.conversations[conv3.name] = conv3
return app return app

View File

@ -4,11 +4,12 @@ import chat_sdk/[
delivery/waku_client, delivery/waku_client,
identity, identity,
links, links,
proto_types proto_types,
types
] ]
export client, conversations, waku_client, identity, links export client, conversations, waku_client, identity, links
#export specific frames need by applications #export specific frames need by applications
export ContentFrame export ContentFrame, MessageId

View File

@ -231,8 +231,6 @@ proc newPrivateConversation*(client: Client,
proc parseMessage(client: Client, msg: ChatPayload) {.raises: [ValueError, proc parseMessage(client: Client, msg: ChatPayload) {.raises: [ValueError,
SerializationError].} = SerializationError].} =
## Receives a incoming payload, decodes it, and processes it. ## Receives a incoming payload, decodes it, and processes it.
info "Parse", clientId = client.getId(), msg = msg,
contentTopic = msg.contentTopic
let envelope = decode(msg.bytes, WapEnvelopeV1).valueOr: let envelope = decode(msg.bytes, WapEnvelopeV1).valueOr:
raise newException(ValueError, "Failed to decode WapEnvelopeV1: " & error) raise newException(ValueError, "Failed to decode WapEnvelopeV1: " & error)

View File

@ -5,6 +5,7 @@ import strutils
import ../proto_types import ../proto_types
import ../delivery/waku_client import ../delivery/waku_client
import ../utils import ../utils
import ../types
type type
ConvoTypes* = enum ConvoTypes* = enum
@ -25,6 +26,6 @@ method id*(self: Conversation): string {.raises: [Defect, ValueError].} =
panic("ProgramError: Missing concrete implementation") panic("ProgramError: Missing concrete implementation")
method sendMessage*(convo: Conversation, ds: WakuClient, method sendMessage*(convo: Conversation, ds: WakuClient,
content_frame: ContentFrame) {.async, base, gcsafe.} = content_frame: ContentFrame) : Future[MessageId] {.async, base, gcsafe.} =
# TODO: make this a compile time check # TODO: make this a compile time check
panic("ProgramError: Missing concrete implementation") panic("ProgramError: Missing concrete implementation")

View File

@ -116,7 +116,7 @@ proc initPrivateV1*(owner: Identity, participant: PublicKey,
raise newException(ValueError, "bad sds channel") raise newException(ValueError, "bad sds channel")
proc sendFrame(self: PrivateV1, ds: WakuClient, proc sendFrame(self: PrivateV1, ds: WakuClient,
msg: PrivateV1Frame): Future[void]{.async.} = msg: PrivateV1Frame): Future[MessageId]{.async.} =
let frameBytes = encode(msg) let frameBytes = encode(msg)
let msgId = self.calcMsgId(frameBytes) let msgId = self.calcMsgId(frameBytes)
@ -130,6 +130,9 @@ proc sendFrame(self: PrivateV1, ds: WakuClient,
discard ds.sendPayload(self.getTopic(), encryptedBytes.toEnvelope( discard ds.sendPayload(self.getTopic(), encryptedBytes.toEnvelope(
self.getConvoId())) self.getConvoId()))
result = msgId
method id*(self: PrivateV1): string = method id*(self: PrivateV1): string =
return getConvoIdRaw(self.participants, self.discriminator) return getConvoIdRaw(self.participants, self.discriminator)
@ -166,13 +169,13 @@ proc handleFrame*[T: ConversationStore](convo: PrivateV1, client: T,
method sendMessage*(convo: PrivateV1, ds: WakuClient, method sendMessage*(convo: PrivateV1, ds: WakuClient,
content_frame: ContentFrame) {.async.} = content_frame: ContentFrame) : Future[MessageId] {.async.} =
try: try:
let frame = PrivateV1Frame(sender: @(convo.owner.getPubkey().bytes()), let frame = PrivateV1Frame(sender: @(convo.owner.getPubkey().bytes()),
timestamp: getCurrentTimestamp(), content: content_frame) timestamp: getCurrentTimestamp(), content: content_frame)
await convo.sendFrame(ds, frame) result = await convo.sendFrame(ds, frame)
except Exception as e: except Exception as e:
error "Unknown error in PrivateV1:SendMessage" error "Unknown error in PrivateV1:SendMessage"

View File

@ -11,6 +11,7 @@ import
crypto, crypto,
delivery/waku_client, delivery/waku_client,
proto_types, proto_types,
types,
utils utils
logScope: logScope:
@ -104,5 +105,6 @@ proc handleFrame*[T: ConversationStore](convo: Inbox, client: T, bytes: seq[
method sendMessage*(convo: Inbox, ds: WakuClient, method sendMessage*(convo: Inbox, ds: WakuClient,
content_frame: ContentFrame) {.async.} = content_frame: ContentFrame) : Future[MessageId] {.async.} =
warn "Cannot send message to Inbox" warn "Cannot send message to Inbox"
result = "program_error"