@@ -81,7 +81,7 @@ module App =
8181 | SendFailed of string
8282 | SendOk
8383 | DoSaveContact
84− | PollResult of events: IncomingEvent list * status: PollStatus * cursor: int64
84+ | PollResult of events: IncomingEvent list * ackIds: string list * status: PollStatus * cursor: int64
8585 | CopyPubKey
8686 | DismissError
8787 | StateLoaded of StateLoadResult
@@ -95,6 +95,7 @@ module App =
9595 | CmdConnect of url: string * identity: Identity
9696 | CmdSend of session: Session * recipientHex: string * text: string * messageId: string
9797 | CmdPoll of session: Session * cursor: int64
98+ | CmdAckAndDeliver of session: Session * ackIds: string list * events: IncomingEvent list
9899 | CmdCopyToClipboard of string
99100 | CmdLoadState
100101 | CmdSaveIdentity of Identity
@@ -154,7 +155,7 @@ module App =
154155 match evt.Envelope with
155156 | Envelope.TextMessage(id, body) ->
156157 let message =
157− { Id = id; Body = body; Timestamp = DateTimeOffset.FromUnixTimeSeconds(evt.Timestamp)
158+ { Id = id; Body = body; Timestamp = DateTimeOffset.FromUnixTimeMilliseconds(evt.Timestamp)
158159 IsOutgoing = false; Status = DeliveryStatus.Delivered }
159160 { m with
160161 Contacts =
@@ -221,7 +222,7 @@ module App =
221222 match model.Auth with
222223 | Identified(id, Connecting) ->
223224 let session = { Url = model.ServerUrl; Token = token; Identity = id }
224− { model with Auth = Identified(id, Online token); Page = Conversations; Error = None },
225+ { model with Auth = Identified(id, Online token); Page = Conversations; Error = None; PollStatus = Polling },
225226 [ CmdPoll(session, model.PollCursor) ]
226227 | _ -> model, []
227228
@@ -258,12 +259,15 @@ module App =
258259 | SendFailed err -> { model with Error = Some err }, []
259260 | SendOk -> model, []
260261
261− | PollResult(events, status, newCursor) ->
262+ | PollResult(events, ackIds, status, newCursor) ->
262263 let model' = events |> List.fold applyEvent { model with PollStatus = status; PollCursor = newCursor }
263264
264265 model',
265266 [ match trySession model' with
266− | Some session -> CmdPoll(session, model'.PollCursor)
267+ | Some session ->
268+ if not ackIds.IsEmpty || not events.IsEmpty then
269+ CmdAckAndDeliver(session, ackIds, events)
270+ CmdPoll(session, model'.PollCursor)
267271 | None -> ()
268272 if not events.IsEmpty then saveCmdMsg model' ]
269273
@@ -329,6 +333,8 @@ module App =
329333
330334 // ── mapCmd ──
331335
336+ let private log msg = eprintfn $"[skrepka] {msg}"
337+
332338 let private asyncCmd (op: Async<Msg>) : Cmd<Msg> =
333339 Cmd.ofEffect (fun dispatch ->
334340 async {
@@ -343,13 +349,13 @@ module App =
343349 | Envelope.DeliveryAck ackIds ->
344350 JsonSerializer.Serialize({| ``type`` = "delivery.ack"; id = Guid.NewGuid().ToString(); ack_ids = ackIds |})
345351 | Envelope.ProfileMessage profile ->
346− JsonSerializer.Serialize({| ``type`` = "profile"; id = Guid.NewGuid().ToString(); timestamp = DateTimeOffset.UtcNow.ToUnixTimeSeconds(); display_name = profile.DisplayName; bio = profile.Bio; photo = profile.PhotoBase64 |})
352+ JsonSerializer.Serialize({| ``type`` = "profile"; id = Guid.NewGuid().ToString(); timestamp = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(); display_name = profile.DisplayName; bio = profile.Bio; photo = profile.PhotoBase64 |})
347353
348354 let private sendEnvelope (session: Session) (recipientHex: string) (envelope: Envelope) =
349355 async {
350356 let payload = serializeEnvelope envelope
351357 let blob = Crypto.encrypt session.Identity.PrivKey (Crypto.fromHex recipientHex) payload
352− let ts = DateTimeOffset.UtcNow.ToUnixTimeSeconds()
358+ let ts = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds()
353359 do! sendMessage session.Url session.Token recipientHex (Crypto.toHex blob) ts
354360 }
355361
@@ -421,8 +427,11 @@ module App =
421427
422428 | CmdPoll(session, cursor) ->
423429 asyncCmd (async {
430+ log $"poll start cursor={cursor}"
424431 try
425432 let! response = poll session.Url session.Token cursor
433+ log $"poll response: {response.Events.Length} events, cursor={response.Cursor}"
434+
426435 let ackIds = response.Events |> Array.map _.Id |> Array.toList
427436
428437 let results =
@@ -430,30 +439,40 @@ module App =
430439 |> Array.choose (fun evt ->
431440 match evt.EventType with
432441 | Message -> Some(decryptEvent session.Identity.PrivKey evt.Payload)
433− | UnknownEvent _ -> None)
442+ | UnknownEvent s -> log $"unknown event type: {s}"; None)
434443
435444 let events = results |> Array.choose Result.toOption |> Array.toList
436445 let errors = results |> Array.choose (function Error e -> Some e | _ -> None) |> Array.toList
437446
438− if not ackIds.IsEmpty then
439− try do! ackMessages session.Url session.Token ackIds
440− with _ -> ()
447+ for e in errors do log $"decrypt error: {e}"
441448
442− do! sendDeliveryAcks session events
449+ if response.Events.Length = 0 then
450+ do! Async.Sleep 5000
443451
444452 let chatCount = events |> List.sumBy (fun e -> match e.Envelope with Envelope.TextMessage _ -> 1 | _ -> 0)
445453 let status =
446454 $"evts:{response.Events.Length} msgs:{chatCount} errs:{errors.Length}"
447455 + (match List.tryHead errors with Some e -> $" [{e}]" | None -> "")
448− return PollResult(events, Received status, response.Cursor)
456+ return PollResult(events, ackIds, Received status, response.Cursor)
449457 with
450458 | :? TimeoutException ->
451− return PollResult([], Polling, cursor)
459+ log "poll timeout"
460+ return PollResult([], [], Polling, cursor)
452461 | ex ->
462+ log $"poll error: {ex.Message}"
453463 do! Async.Sleep 3000
454− return PollResult([], PollError ex.Message, cursor)
464+ return PollResult([], [], PollError ex.Message, cursor)
455465 })
456466
467+ | CmdAckAndDeliver(session, ackIds, events) ->
468+ Cmd.ofEffect (fun _ ->
469+ async {
470+ if not ackIds.IsEmpty then
471+ try do! ackMessages session.Url session.Token ackIds
472+ with _ -> ()
473+ do! sendDeliveryAcks session events
474+ } |> Async.Start)
475+
457476 | CmdCopyToClipboard text ->
458477 Cmd.ofEffect (fun _ ->
459478 Microsoft.Maui.ApplicationModel.DataTransfer.Clipboard.Default.SetTextAsync(text)
@@ -594,7 +613,8 @@ module App =
594613 Button("Settings", SetPage Settings)
595614 }
596615
597− Label($"{connStatusLabel model.Auth} | {formatPollStatus model.PollStatus}")
616+ let pubPrefix = match model.Auth with Identified(id, _) -> id.PubKeyHex.[..7] | _ -> "?"
617+ Label($"{connStatusLabel model.Auth} [{pubPrefix}] | {formatPollStatus model.PollStatus}")
598618 .font(size = 10.)
599619 .textColor(Colors.DimGray)
600620