Add dynamic contentFrame

This commit is contained in:
Jazz Turner-Baggs 2025-08-23 17:30:04 -07:00
parent 87adb3b5d0
commit f595ff554d
8 changed files with 126 additions and 17 deletions

View File

@ -51,7 +51,7 @@ type Client* = ref object
inboundQueue: QueueRef
isRunning: bool
newMessageCallbacks: seq[MessageCallback[string]]
newMessageCallbacks: seq[MessageCallback[ContentFrame]]
newConvoCallbacks: seq[NewConvoCallback]
#################################################
@ -115,12 +115,13 @@ proc listConversations*(client: Client): seq[Conversation] =
# Callback Handling
#################################################
proc onNewMessage*(client: Client, callback: MessageCallback[string]) =
proc onNewMessage*(client: Client, callback: MessageCallback[ContentFrame]) =
client.newMessageCallbacks.add(callback)
proc notifyNewMessage(client: Client, convo: Conversation, msg: string) =
proc notifyNewMessage(client: Client, convo: Conversation,
content: ContentFrame) =
for cb in client.newMessageCallbacks:
discard cb(convo, msg)
discard cb(convo, content)
proc onNewConversation*(client: Client, callback: NewConvoCallback) =
client.newConvoCallbacks.add(callback)

View File

@ -3,6 +3,7 @@ import std/[options, times]
import ./conversations/convo_type
import crypto
import identity
import proto_types
type ConvoId = string
@ -13,4 +14,5 @@ type
proc identity(self: Self): Identity
proc getId(self: Self): string
proc notifyNewMessage(self: Self, convo: Conversation, msg: string)
proc notifyNewMessage(self: Self, convo: Conversation,
content: ContentFrame)

View File

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

View File

@ -91,17 +91,18 @@ proc handleFrame*[T: ConversationStore](convo: PrivateV1, client: T,
case frame.getKind():
of typeContentFrame:
# TODO: Using client.getId() results in an error in this context
client.notifyNewMessage(convo, toUtfString(frame.content.bytes))
client.notifyNewMessage(convo, frame.content)
of typePlaceholder:
notice "Got Placeholder", text = frame.placeholder.counter
method sendMessage*(convo: PrivateV1, ds: WakuClient, text: string) {.async.} =
method sendMessage*(convo: PrivateV1, ds: WakuClient,
content_frame: ContentFrame) {.async.} =
try:
let frame = PrivateV1Frame(sender: @(convo.owner.getPubkey().bytes()),
content: ContentFrame(domain: 0, tag: 1, bytes: text.toBytes()))
content: content_frame)
await convo.sendFrame(ds, frame)
except Exception as e:

View File

@ -99,5 +99,6 @@ proc handleFrame*[T: ConversationStore](convo: Inbox, client: T, bytes: seq[
notice "Receive Note", client = client.getId(), text = frame.note.text
method sendMessage*(convo: Inbox, ds: WakuClient, text: string) {.async.} =
method sendMessage*(convo: Inbox, ds: WakuClient,
content_frame: ContentFrame) {.async.} =
warn "Cannot send message to Inbox"

55
src/content_types/all.nim Normal file
View File

@ -0,0 +1,55 @@
# Can this be an external package? It would be preferable to have these types
# easy to import and use.
import protobuf_serialization # This import is needed or th macro will not work
import protobuf_serialization/proto_parser
import results
import std/random
import strformat
import ../chat_sdk/proto_types
export protobuf_serialization
import_proto3 "protos/text_frame.proto"
# import_proto3 "../../protos/common_frames.proto"
export ContentFrame, TextFrame
type ContentTypes = TextFrame
# protobuf_serialization does not support enums, so it needs to be manually implemented
type
TextEncoding* = enum
Utf8 = 0
proc encode*(frame: object): seq[byte] =
## Encodes the frame into a byte sequence using Protobuf serialization.
result = Protobuf.encode(frame)
proc decode*[T: object] (bytes: seq[byte], proto: typedesc[
T]): Result[T, string] =
## Encodes the frame into a byte sequence using Protobuf serialization.
try:
result = ok(Protobuf.decode(bytes, proto))
except ProtobufError as e:
result = err("Failed to decode payload: " & e.msg)
proc toContentFrame*(frame: TextFrame): ContentFrame =
result = ContentFrame(domain: 0, tag: 0, bytes: encode(frame))
proc initTextFrame*(text: string): TextFrame =
result = TextFrame(encoding: ord(Utf8), text: text)
proc `$`*(frame: TextFrame): string =
result = fmt"TextFrame(encoding:{TextEncoding(frame.encoding)} text:{frame.text})"

View File

@ -0,0 +1,9 @@
syntax = "proto3";
package wap.content_types;
message TextFrame {
uint32 encoding = 1;
string text = 2;
}

View File

@ -7,6 +7,24 @@ import chat_sdk/conversations
import chat_sdk/delivery/waku_client
import chat_sdk/utils
import content_types/all
const SELF_DEFINED = 99
type ImageFrame {.proto3.} = object
url {.fieldNumber: 1.}: string
altText {.fieldNumber: 2.}: string
proc initImage(url: string): ContentFrame =
result = ContentFrame(domain: SELF_DEFINED, tag: 0, bytes: encode(ImageFrame(
url: url, altText: "This is an image")))
proc `$`*(frame: ImageFrame): string =
result = fmt"ImageFrame(url:{frame.url} alt_text:{frame.altText})"
proc initLogging() =
when defined(chronicles_runtime_filtering):
setLogLevel(LogLevel.Debug)
@ -14,6 +32,25 @@ proc initLogging() =
discard setTopicState("waku relay", chronicles.Normal, LogLevel.Error)
discard setTopicState("chat client", chronicles.Enabled, LogLevel.Debug)
proc getContent(content: ContentFrame): string =
notice "GetContent", domain = content.domain, tag = content.tag
# TODO: Hide this complexity from developers
if content.domain == 0:
if content.tag == 0:
let m = decode(content.bytes, TextFrame).valueOr:
raise newException(ValueError, fmt"Badly formed Content (domain:{content.domain} tag:{content.tag})")
return fmt"{m}"
if content.domain == SELF_DEFINED:
if content.tag == 0:
let m = decode(content.bytes, ImageFrame).valueOr:
raise newException(ValueError, fmt"Badly formed Content (domain:{content.domain} tag:{content.tag})")
return fmt"{m}"
raise newException(ValueError, fmt"Unhandled content (domain:{content.domain} tag:{content.tag})")
proc main() {.async.} =
# Create Configurations
@ -29,22 +66,24 @@ proc main() {.async.} =
# Start Clients
var saro = newClient("Saro", cfg_saro)
saro.onNewMessage(proc(convo: Conversation, msg: string) {.async.} =
echo " Saro <------ :: " & msg
saro.onNewMessage(proc(convo: Conversation, msg: ContentFrame) {.async.} =
echo " Saro <------ :: " & getContent(msg)
await sleepAsync(10000)
await convo.sendMessage(saro.ds, "Ping"))
await convo.sendMessage(saro.ds, initImage(
"https://waku.org/theme/image/logo-black.svg"))
)
await saro.start()
var raya = newClient("Raya", cfg_raya)
raya.onNewMessage(proc(convo: Conversation, msg: string) {.async.} =
echo " ------> Raya :: " & msg
raya.onNewMessage(proc(convo: Conversation, msg: ContentFrame) {.async.} =
echo " ------> Raya :: " & getContent(msg)
await sleepAsync(10000)
await convo.sendMessage(raya.ds, "Pong")
await convo.sendMessage(raya.ds, initTextFrame("Pong").toContentFrame())
)
raya.onNewConversation(proc(convo: Conversation) {.async.} =
echo " ------> Raya :: New Conversation: " & convo.id()
await convo.sendMessage(raya.ds, "Hello")
await convo.sendMessage(raya.ds, initTextFrame("Hello").toContentFrame())
)
await raya.start()