Remove WrappedConvo

This commit is contained in:
Jazz Turner-Baggs 2025-08-20 10:16:52 -07:00
parent 85718fefdc
commit 776b726249
6 changed files with 187 additions and 117 deletions

View File

@ -12,7 +12,9 @@ import # Foreign
tables
import #local
conversations/private_v1,
conversation_store,
conversation,
convo_impl,
crypto,
identity,
inbox,
@ -21,6 +23,8 @@ import #local
utils,
waku_client
import #to be removed
conversations/private_v1
logScope:
topics = "chat client"
@ -29,32 +33,16 @@ logScope:
# Definitions
#################################################
type KeyEntry* = object
keyType: string
privateKey: PrivateKey
timestamp: int64
type
SupportedConvoTypes* = Inbox | PrivateV1
ConvoType* = enum
InboxV1Type, PrivateV1Type
ConvoWrapper* = object
case convoType*: ConvoType
of InboxV1Type:
inboxV1*: Inbox
of PrivateV1Type:
privateV1*: PrivateV1
type Client* = ref object
ident: Identity
ds*: WakuClient
keyStore: Table[string, KeyEntry] # Keyed by HexEncoded Public Key
conversations: Table[string, ConvoWrapper] # Keyed by conversation ID
conversations: Table[string, Conversation] # Keyed by conversation ID
inboundQueue: QueueRef
isRunning: bool
@ -62,27 +50,29 @@ type Client* = ref object
# Constructors
#################################################
proc newClient*(name: string, cfg: WakuConfig): Client =
proc newClient*(name: string, cfg: WakuConfig): Client {.raises: [IOError,
ValueError, SerializationError].} =
## Creates new instance of a `Client` with a given `WakuConfig`
let waku = initWakuClient(cfg)
var q = QueueRef(queue: newAsyncQueue[ChatPayload](10))
var c = Client(ident: createIdentity(name),
ds: waku,
keyStore: initTable[string, KeyEntry](),
conversations: initTable[string, ConvoWrapper](),
inboundQueue: q,
isRunning: false)
let defaultInbox = initInbox(c.ident.getAddr())
c.conversations[conversationIdFor(c.ident.getPubkey(
))] = ConvoWrapper(convoType: InboxV1Type, inboxV1: defaultInbox)
try:
let waku = initWakuClient(cfg)
notice "Client started", client = c.ident.getId(),
defaultInbox = defaultInbox
result = c
var q = QueueRef(queue: newAsyncQueue[ChatPayload](10))
var c = Client(ident: createIdentity(name),
ds: waku,
keyStore: initTable[string, KeyEntry](),
conversations: initTable[string, Conversation](),
inboundQueue: q,
isRunning: false)
let defaultInbox = initInbox(c.ident.getPubkey())
c.conversations[defaultInbox.id()] = defaultInbox
notice "Client started", client = c.ident.getId(),
defaultInbox = defaultInbox
result = c
except Exception as e:
error "newCLient", err = e.msg
#################################################
# Parameter Access
#################################################
@ -90,16 +80,19 @@ proc newClient*(name: string, cfg: WakuConfig): Client =
proc getId(client: Client): string =
result = client.ident.getId()
proc identity*(client: Client): Identity =
result = client.ident
proc defaultInboxConversationId*(self: Client): string =
## Returns the default inbox address for the client.
result = conversationIdFor(self.ident.getPubkey())
proc getConversationFromHint(self: Client,
conversationHint: string): Result[Option[ConvoWrapper], string] =
conversationHint: string): Result[Option[Conversation], string] =
# TODO: Implementing Hinting
if not self.conversations.hasKey(conversationHint):
ok(none(ConvoWrapper))
ok(none(Conversation))
else:
ok(some(self.conversations[conversationHint]))
@ -134,21 +127,13 @@ proc createIntroBundle*(self: var Client): IntroBundle =
# Conversation Initiation
#################################################
proc createPrivateConversation(client: Client, participant: PublicKey,
discriminator: string = "default"): Option[ChatError] =
## Creates a private conversation with the given participant and discriminator.
## Discriminator allows multiple conversations to exist between the same
## participants.
proc addConversation*(client: Client, convo: Conversation) =
notice "Creating conversation", topic = convo.id()
client.conversations[convo.id()] = convo
let convo = initPrivateV1(client.ident, participant, discriminator)
notice "Creating PrivateV1 conversation", topic = convo.getConvoId()
client.conversations[convo.getConvoId()] = ConvoWrapper(
convoType: PrivateV1Type,
privateV1: convo
)
return some(convo.getConvoId())
proc getConversation*(client: Client, convoId: string): Conversation =
notice "Get conversation", convoId = convoId
result = client.conversations[convoId]
proc newPrivateConversation*(client: Client,
introBundle: IntroBundle): Future[Option[ChatError]] {.async.} =
@ -174,68 +159,21 @@ proc newPrivateConversation*(client: Client,
let env = wrapEnv(encrypt(InboxV1Frame(invitePrivateV1: invite,
recipient: "")), convoId)
discard createPrivateConversation(client, destPubkey)
let convo = initPrivateV1(client.identity(), destPubkey, "default")
client.addConversation(convo)
# TODO: Subscribe to new content topic
await client.ds.sendPayload(destConvoTopic, env)
return none(ChatError)
proc acceptPrivateInvite(client: Client,
invite: InvitePrivateV1): Option[ChatError] =
## Allows Recipients to join the conversation.
notice "ACCEPT PRIVATE Convo ", clientId = client.getId(),
fromm = invite.initiator.mapIt(it.toHex(2)).join("")
let destPubkey = loadPublicKeyFromBytes(invite.initiator).valueOr:
raise newException(ValueError, "Invalid public key in intro bundle.")
discard createPrivateConversation(client, destPubkey)
# TODO: Subscribe to new content topic
result = none(ChatError)
#################################################
# Payload Handling
#################################################
proc handleInboxFrame(client: Client, convo: Inbox, bytes: seq[byte]) =
## Dispatcher for Incoming `InboxV1Frames`.
## Calls further processing depending on the kind of frame.
let enc = decode(bytes, EncryptedPayload).valueOr:
raise newException(ValueError, "Failed to decode payload")
let frame = convo.decrypt(enc).valueOr:
error "Decrypt failed", error = error
raise newException(ValueError, "Failed to Decrypt MEssage: " &
error)
case getKind(frame):
of typeInvitePrivateV1:
notice "Receive PrivateInvite", client = client.getId(),
frame = frame.invitePrivateV1
discard client.acceptPrivateInvite(frame.invitePrivateV1)
of typeNote:
notice "Receive Note", text = frame.note.text
proc handlePrivateFrame(client: Client, convo: PrivateV1, bytes: seq[byte]) =
## Dispatcher for Incoming `PrivateV1Frames`.
## Calls further processing depending on the kind of frame.
let enc = decode(bytes, EncryptedPayload).get() # TODO: handle result
let frame = convo.decrypt(enc) # TODO: handle result
case frame.getKind():
of typeContentFrame:
notice "Got Mail", client = client.getId(),
text = frame.content.bytes.toUtfString()
of typePlaceholder:
notice "Got Placeholder", client = client.getId(),
text = frame.placeholder.counter
proc parseMessage(client: Client, msg: ChatPayload) =
proc parseMessage(client: Client, msg: ChatPayload) {.raises: [ValueError,
SerializationError].} =
## Receives a incoming payload, decodes it, and processes it.
info "Parse", clientId = client.getId(), msg = msg,
contentTopic = msg.contentTopic
@ -243,7 +181,7 @@ proc parseMessage(client: Client, msg: ChatPayload) =
let envelope = decode(msg.bytes, WapEnvelopeV1).valueOr:
raise newException(ValueError, "Failed to decode WapEnvelopeV1: " & error)
let wrappedConvo = block:
let convo = block:
let opt = client.getConversationFromHint(envelope.conversationHint).valueOr:
raise newException(ValueError, "Failed to get conversation: " & error)
@ -255,12 +193,11 @@ proc parseMessage(client: Client, msg: ChatPayload) =
hint = envelope.conversationHint, knownIds = k
return
case wrappedConvo.convoType:
of InboxV1Type:
client.handleInboxFrame(wrappedConvo.inboxV1, envelope.payload)
try:
convo.handleFrame(client, envelope.payload)
except Exception as e:
error "HandleFrame Failed", error = e.msg
of PrivateV1Type:
client.handlePrivateFrame(wrappedConvo.privateV1, envelope.payload)
proc addMessage*(client: Client, convo: PrivateV1,
text: string = "") {.async.} =
@ -303,9 +240,10 @@ proc simulateMessages(client: Client){.async.} =
for a in 1..5:
await sleepAsync(4.seconds)
for convoWrapper in client.conversations.values():
if convoWrapper.convoType == PrivateV1Type:
await client.addMessage(convoWrapper.privateV1, fmt"message: {a}")
for conversation in client.conversations.values():
if conversation of PrivateV1:
await client.addMessage(PrivateV1(conversation), fmt"message: {a}")
#################################################
# Control Functions

17
src/conversation.nim Normal file
View File

@ -0,0 +1,17 @@
# import conversations/private_v1
import strformat
type
ConvoTypes* = enum
InboxV1Type, PrivateV1Type
type
Conversation* = ref object of RootObj
name: string
proc `$`(conv: Conversation): string =
fmt"Convo: {conv.name}"
method id*(self: Conversation): string {.raises: [Defect].} =
raise newException(Defect, "Abstract function")

View File

@ -0,0 +1,16 @@
import std/[options, times]
import conversation
import crypto
import identity
type ConvoId = string
type
ConversationStore* = concept
proc addConversation(self: Self, convo: Conversation)
proc getConversation(self: Self, convoId: string): Conversation
proc identity(self: Self): Identity
proc getId(self: Self): string

View File

@ -7,6 +7,8 @@ import std/algorithm
import sugar
import ../[
conversation,
conversation_store,
crypto,
identity,
proto_types,
@ -14,8 +16,10 @@ import ../[
waku_client
]
type
PrivateV1* = object
PrivateV1* = ref object of Conversation
# Placeholder for PrivateV1 conversation type
owner: Identity
topic: string
@ -70,3 +74,20 @@ proc sendMessage*(self: PrivateV1, ds: WakuClient,
discard ds.sendPayload(self.getTopic(), encryptedBytes.toEnvelope(
self.getConvoId()))
method id*(self: PrivateV1): string =
return getConvoIdRaw(self.participants, self.discriminator)
proc handleFrame*[T: ConversationStore](convo: PrivateV1, client: T,
bytes: seq[byte]) =
## Dispatcher for Incoming `PrivateV1Frames`.
## Calls further processing depending on the kind of frame.
let enc = decode(bytes, EncryptedPayload).get() # TODO: handle result
let frame = convo.decrypt(enc) # TODO: handle result
case frame.getKind():
of typeContentFrame:
notice "Got Mail", text = frame.content.bytes.toUtfString()
of typePlaceholder:
notice "Got Placeholder", text = frame.placeholder.counter

30
src/convo_impl.nim Normal file
View File

@ -0,0 +1,30 @@
import conversation_store
import conversation
import inbox
import conversations/private_v1
proc getType(convo: Conversation): ConvoTypes =
if convo of Inbox:
return InboxV1Type
elif convo of PrivateV1:
return PrivateV1Type
else:
raise newException(Defect, "Conversation Type not processed")
proc handleFrame*[T: ConversationStore](convo: Conversation, client: T,
bytes: seq[byte]) =
case convo.getType():
of InboxV1Type:
let inbox = Inbox(convo)
inbox.handleFrame(client, bytes)
of PrivateV1Type:
let priv = PrivateV1(convo)
priv.handleFrame(client, bytes)

View File

@ -1,20 +1,30 @@
import
chronicles,
chronos,
results
results,
strformat
import
conversation,
conversations/private_v1,
conversation_store,
crypto,
proto_types,
utils
type
Inbox* = object
Inbox* = ref object of Conversation
pubkey: PublicKey
inbox_addr: string
proc initInbox*(inbox_addr: string): Inbox =
proc `$`*(conv: Inbox): string =
fmt"Inbox: addr->{conv.inbox_addr}"
proc initInbox*(pubkey: PublicKey): Inbox =
## Initializes an Inbox object with the given address and invite callback.
return Inbox(inbox_addr: inbox_addr)
return Inbox(pubkey: pubkey)
proc encrypt*(frame: InboxV1Frame): EncryptedPayload =
return encrypt_plain(frame)
@ -43,3 +53,41 @@ proc conversation_id_for*(pubkey: PublicKey): string =
# TODO derive this from instance of Inbox
proc topic_inbox*(client_addr: string): string =
return "/inbox/" & client_addr
method id*(convo: Inbox): string =
return conversation_id_for(convo.pubkey)
#################################################
# Conversation Creation
#################################################
proc createPrivateV1FromInvite*[T: ConversationStore](client: T,
invite: InvitePrivateV1) =
let destPubkey = loadPublicKeyFromBytes(invite.initiator).valueOr:
raise newException(ValueError, "Invalid public key in intro bundle.")
let convo = initPrivateV1(client.identity(), destPubkey, "default")
notice "Creating PrivateV1 conversation", topic = convo.getConvoId()
client.addConversation(convo)
proc handleFrame*[T: ConversationStore](convo: Inbox, client: T, bytes: seq[byte]) =
## Dispatcher for Incoming `InboxV1Frames`.
## Calls further processing depending on the kind of frame.
let enc = decode(bytes, EncryptedPayload).valueOr:
raise newException(ValueError, "Failed to decode payload")
let frame = convo.decrypt(enc).valueOr:
error "Decrypt failed", error = error
raise newException(ValueError, "Failed to Decrypt MEssage: " &
error)
case getKind(frame):
of typeInvitePrivateV1:
createPrivateV1FromInvite(client, frame.invitePrivateV1)
of typeNote:
notice "Receive Note", text = frame.note.text