@@ -33,15 +33,15 @@ module App =
3333 | AddContact
3434 | Settings
3535
36− type ConnState =
37− | Offline
38− | Connecting
39− | Online of token: string
40−
4136 type Identity = { PrivKey: byte[]; PubKeyHex: string }
4237
4338 type Session = { Url: string; Token: string; Identity: Identity }
4439
40+ type ConnState =
41+ | Offline
42+ | Connecting
43+ | Online of Session
44+
4545 // ── Model ──
4646
4747 type Model =
@@ -72,7 +72,7 @@ module App =
7272 | DoDisconnect
7373 | SetCompose of string
7474 | DoSend
75− | Sent of recipient: string * status: string
75+ | Sent of status: string
7676 | SetNewPubkey of string
7777 | SetNewNickname of string
7878 | DoSaveContact
@@ -87,7 +87,7 @@ module App =
8787 type CmdMsg =
8888 | CmdGenIdentity
8989 | CmdConnect of url: string * identity: Identity
90− | CmdSend of session: Session * recipientHex: string * text: string
90+ | CmdSend of session: Session * recipientHex: string * text: string * messageId: string
9191 | CmdPoll of session: Session * cursor: uint64
9292 | CmdCopyToClipboard of string
9393
@@ -119,8 +119,8 @@ module App =
119119 model.Identity |> Option.map _.PubKeyHex |> Option.defaultValue ""
120120
121121 let private trySession model =
122− match model.Identity, model.Conn with
123− | Some id, Online token -> Some { Url = model.ServerUrl; Token = token; Identity = id }
122+ match model.Conn with
123+ | Online session -> Some session
124124 | _ -> None
125125
126126 // ── Init ──
@@ -164,7 +164,7 @@ module App =
164164 match model.Identity with
165165 | Some id ->
166166 let session = { Url = model.ServerUrl; Token = token; Identity = id }
167− { model with Conn = Online token; Page = Conversations; Error = None },
167+ { model with Conn = Online session; Page = Conversations; Error = None },
168168 [ CmdPoll(session, model.PollCursor) ]
169169 | None ->
170170 { model with Error = Some "No identity generated" }, []
@@ -179,8 +179,9 @@ module App =
179179 match model.Page, trySession model with
180180 | Chat pk, Some session when model.ComposeText <> "" ->
181181 let text = model.ComposeText
182+ let id = Guid.NewGuid().ToString()
182183 let outMsg =
183− { Id = Guid.NewGuid().ToString()
184+ { Id = id
184185 Body = text
185186 Timestamp = DateTimeOffset.UtcNow
186187 IsOutgoing = true }
@@ -189,10 +190,10 @@ module App =
189190 { model with
190191 ComposeText = ""
191192 Messages = model.Messages |> Map.add pk (msgs @ [ outMsg ]) },
192− [ CmdSend(session, pk, text) ]
193+ [ CmdSend(session, pk, text, id) ]
193194 | _ -> model, []
194195
195− | Sent(_, status) -> { model with LastSendStatus = status }, []
196+ | Sent status -> { model with LastSendStatus = status }, []
196197
197198 | PollResult(incoming, status, newCursor) ->
198199 let model' =
@@ -312,21 +313,20 @@ module App =
312313 return AuthErr ex.Message
313314 })
314315
315− | CmdSend(session, recipientHex, text) ->
316+ | CmdSend(session, recipientHex, text, messageId) ->
316317 asyncCmd (async {
317318 try
318319 let pubKey = session.Identity.PrivKey.[32..63]
319320 let recipPub = Crypto.fromHex recipientHex
320− let id = Guid.NewGuid().ToString()
321321 let ts = DateTimeOffset.UtcNow.ToUnixTimeSeconds()
322322
323323 let payload =
324− $"""{{ "type": "text", "id": "{id}", "timestamp": {ts}, "body": {JsonSerializer.Serialize(text)} }}"""
324+ $"""{{ "type": "text", "id": "{messageId}", "timestamp": {ts}, "body": {JsonSerializer.Serialize(text)} }}"""
325325
326326 let blob = Crypto.encrypt session.Identity.PrivKey pubKey recipPub payload
327327 let blobHex = Crypto.toHex blob
328328 let! (status, msgId) = ApiClient.sendMessage session.Url session.Token recipientHex blobHex ts
329− return Sent(recipientHex, $"{status}:{msgId}")
329+ return Sent $"{status}:{msgId}"
330330 with ex ->
331331 return DismissError // TODO: show send errors properly
332332 })
@@ -335,29 +335,26 @@ module App =
335335 asyncCmd (async {
336336 try
337337 let! (events, newCursor) = ApiClient.poll session.Url session.Token cursor
338− let messages = ResizeArray()
339− let ackIds = ResizeArray()
340− let errors = ResizeArray()
341−
342− for (evtId, evtType, payload) in events do
343− if evtType = "message" then
344− match decryptEvent session.Identity.PrivKey payload with
345− | Ok msg ->
346− messages.Add(msg)
347− ackIds.Add(evtId)
348− | Error err ->
349− errors.Add(err)
350−
351− if ackIds.Count > 0 then
352− do! ApiClient.ackMessages session.Url session.Token (Seq.toList ackIds)
338+ let messages, ackIds, errors =
339+ events
340+ |> List.fold (fun (msgs, acks, errs) (evtId, evtType, payload) ->
341+ if evtType <> "message" then (msgs, acks, errs)
342+ else
343+ match decryptEvent session.Identity.PrivKey payload with
344+ | Ok msg -> (msg :: msgs, evtId :: acks, errs)
345+ | Error err -> (msgs, acks, err :: errs))
346+ ([], [], [])
347+
348+ if not ackIds.IsEmpty then
349+ do! ApiClient.ackMessages session.Url session.Token ackIds
353350
354351 let now = DateTimeOffset.UtcNow.ToString("HH:mm:ss")
355352
356353 let status =
357− $"[{now}] evts:{events.Length} msgs:{messages.Count} errs:{errors.Count}"
358− + (if errors.Count > 0 then $" [{errors.[0]}]" else "")
354+ $"[{now}] evts:{events.Length} msgs:{messages.Length} errs:{errors.Length}"
355+ + (match errors with err :: _ -> $" [{err}]" | [] -> "")
359356
360− return PollResult(Seq.toList messages, status, newCursor)
357+ return PollResult(List.rev messages, status, newCursor)
361358 with
362359 | :? TimeoutException ->
363360 return PollResult([], "polling...", cursor)
@@ -411,12 +408,14 @@ module App =
411408
412409 Entry(model.ServerUrl, SetServerUrl)
413410
414− if model.Error.IsSome then
415− Label(model.Error.Value)
411+ match model.Error with
412+ | Some err ->
413+ Label(err)
416414 .textColor(Colors.Red)
417415 .font(size = 12.)
418416
419417 Button("Dismiss", DismissError)
418+ | None -> ()
420419
421420 match model.Conn with
422421 | Offline -> Button("Connect", DoConnect)
@@ -504,10 +503,12 @@ module App =
504503 .centerTextHorizontal()
505504 }
506505
507− if model.Error.IsSome then
508− Label(model.Error.Value)
506+ match model.Error with
507+ | Some err ->
508+ Label(err)
509509 .textColor(Colors.Red)
510510 .font(size = 11.)
511+ | None -> ()
511512
512513 Label($"[{msgs.Length} message(s), compose: \"{model.ComposeText}\", send: \"{model.LastSendStatus}\"]")
513514 .textColor(Colors.Gray)