From 776b7262499b7050aff9e4b863432e1c67b7e117 Mon Sep 17 00:00:00 2001 From: Jazz Turner-Baggs <473256+jazzz@users.noreply.github.com> Date: Wed, 20 Aug 2025 10:16:52 -0700 Subject: [PATCH] Remove WrappedConvo --- src/client.nim | 162 ++++++++++--------------------- src/conversation.nim | 17 ++++ src/conversation_store.nim | 16 +++ src/conversations/private_v1.nim | 23 ++++- src/convo_impl.nim | 30 ++++++ src/inbox.nim | 56 ++++++++++- 6 files changed, 187 insertions(+), 117 deletions(-) create mode 100644 src/conversation.nim create mode 100644 src/conversation_store.nim create mode 100644 src/convo_impl.nim diff --git a/src/client.nim b/src/client.nim index 7eca9e5..f15cc28 100644 --- a/src/client.nim +++ b/src/client.nim @@ -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 diff --git a/src/conversation.nim b/src/conversation.nim new file mode 100644 index 0000000..2e38d42 --- /dev/null +++ b/src/conversation.nim @@ -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") + diff --git a/src/conversation_store.nim b/src/conversation_store.nim new file mode 100644 index 0000000..6e89f6b --- /dev/null +++ b/src/conversation_store.nim @@ -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 diff --git a/src/conversations/private_v1.nim b/src/conversations/private_v1.nim index 6fe56dd..bfe11ab 100644 --- a/src/conversations/private_v1.nim +++ b/src/conversations/private_v1.nim @@ -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 diff --git a/src/convo_impl.nim b/src/convo_impl.nim new file mode 100644 index 0000000..f5cbc14 --- /dev/null +++ b/src/convo_impl.nim @@ -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) + + diff --git a/src/inbox.nim b/src/inbox.nim index 435b125..273caa2 100644 --- a/src/inbox.nim +++ b/src/inbox.nim @@ -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