@@ -37,9 +37,11 @@ module App =
3737 | Identified of identity: Identity * conn: ConnStatus
3838
3939 type DecryptedEvent =
40− | IncomingChat of senderPubkey: string * message: ChatMessage
41− | IncomingDeliveryAck of senderPubkey: string * ackIds: string list
42− | IncomingProfile of senderPubkey: string * profile: Profile
40+ | ChatMsg of ChatMessage
41+ | DeliveryAck of ackIds: string list
42+ | ProfileUpdate of Profile
43+
44+ type IncomingEvent = { Sender: string; Event: DecryptedEvent }
4345
4446 type PollReceived = { Events: int; Messages: int; Errors: int; FirstError: string option }
4547
@@ -83,7 +85,7 @@ module App =
8385 | SendFailed of string
8486 | SendOk
8587 | DoSaveContact
86− | PollResult of events: DecryptedEvent list * status: PollStatus * cursor: int64
88+ | PollResult of events: IncomingEvent list * status: PollStatus * cursor: int64
8789 | CopyPubKey
8890 | DismissError
8991 | StateLoaded of StateLoadResult
@@ -130,13 +132,10 @@ module App =
130132
131133 let private contactName (contacts: Map<string, Contact>) (pk: string) =
132134 let fallback = truncKey pk
133− contacts
134− |> Map.tryFind pk
135− |> Option.map (fun c ->
136− if c.Nickname <> fallback then c.Nickname
137− elif c.DisplayName <> "" then c.DisplayName
138− else fallback)
139− |> Option.defaultValue fallback
135+ match Map.tryFind pk contacts with
136+ | Some c when c.Nickname <> fallback -> c.Nickname
137+ | Some c when c.DisplayName <> "" -> c.DisplayName
138+ | _ -> fallback
140139
141140 let private messagesFor (pk: string) (messages: Map<string, ChatMessage list>) =
142141 messages |> Map.tryFind pk |> Option.defaultValue []
@@ -157,17 +156,18 @@ module App =
157156 let private withProfile (p: Profile) (c: Contact) =
158157 { c with DisplayName = p.DisplayName; Bio = p.Bio; PhotoBase64 = p.PhotoBase64 }
159158
160− let private applyEvent (m: Model) = function
161− | IncomingChat(senderPubkey, message) ->
159+ let private applyEvent (m: Model) (evt: IncomingEvent) =
160+ match evt.Event with
161+ | ChatMsg message ->
162162 { m with
163163 Contacts =
164− if Map.containsKey senderPubkey m.Contacts then m.Contacts
165− else Map.add senderPubkey (newContact senderPubkey (truncKey senderPubkey)) m.Contacts
166− Messages = m.Messages |> appendMessage senderPubkey message }
167− | IncomingDeliveryAck(senderPubkey, ackIds) ->
168− { m with Messages = m.Messages |> markDelivered senderPubkey ackIds }
169− | IncomingProfile(senderPubkey, profile) ->
170− { m with Contacts = m.Contacts |> Map.change senderPubkey (Option.map (withProfile profile)) }
164+ if Map.containsKey evt.Sender m.Contacts then m.Contacts
165+ else Map.add evt.Sender (newContact evt.Sender (truncKey evt.Sender)) m.Contacts
166+ Messages = m.Messages |> appendMessage evt.Sender message }
167+ | DeliveryAck ackIds ->
168+ { m with Messages = m.Messages |> markDelivered evt.Sender ackIds }
169+ | ProfileUpdate profile ->
170+ { m with Contacts = m.Contacts |> Map.change evt.Sender (Option.map (withProfile profile)) }
171171
172172 let private trySession model =
173173 match model.Auth with
@@ -363,17 +363,18 @@ module App =
363363 }
364364 |> Async.Start)
365365
366+ [<RequireQualifiedAccess>]
366367 type private Envelope =
367368 | TextMessage of id: string * body: string
368369 | DeliveryAck of ackIds: string list
369370 | ProfileMessage of profile: Profile
370371
371372 let private serializeEnvelope = function
372− | TextMessage(id, body) ->
373+ | Envelope.TextMessage(id, body) ->
373374 JsonSerializer.Serialize({| ``type`` = "text"; id = id; body = body |})
374− | DeliveryAck ackIds ->
375+ | Envelope.DeliveryAck ackIds ->
375376 JsonSerializer.Serialize({| ``type`` = "delivery.ack"; id = Guid.NewGuid().ToString(); ack_ids = ackIds |})
376− | ProfileMessage profile ->
377+ | Envelope.ProfileMessage profile ->
377378 JsonSerializer.Serialize({| ``type`` = "profile"; id = Guid.NewGuid().ToString(); timestamp = DateTimeOffset.UtcNow.ToUnixTimeSeconds(); display_name = profile.DisplayName; bio = profile.Bio; photo = profile.PhotoBase64 |})
378379
379380 let private sendEnvelope (session: Session) (recipientHex: string) (envelope: Envelope) =
@@ -394,49 +395,49 @@ module App =
394395 let root = doc.RootElement
395396 match root.GetProperty("type").GetString() with
396397 | "text" ->
397− Some(TextMessage(root.GetProperty("id").GetString(), root.GetProperty("body").GetString()))
398+ Some(Envelope.TextMessage(root.GetProperty("id").GetString(), root.GetProperty("body").GetString()))
398399 | "delivery.ack" ->
399400 let ackIds = root.GetProperty("ack_ids").EnumerateArray() |> Seq.map _.GetString() |> Seq.toList
400− Some(DeliveryAck ackIds)
401+ Some(Envelope.DeliveryAck ackIds)
401402 | "profile" ->
402403 let displayName = root.GetProperty("display_name").GetString()
403404 let bio = tryGetJsonString root "bio" |> Option.defaultValue ""
404405 let photo = tryGetJsonString root "photo" |> Option.defaultValue ""
405− Some(ProfileMessage { DisplayName = displayName; Bio = bio; PhotoBase64 = photo })
406+ Some(Envelope.ProfileMessage { DisplayName = displayName; Bio = bio; PhotoBase64 = photo })
406407 | _ -> None
407408
409+ let private toDecryptedEvent timestamp = function
410+ | Envelope.TextMessage(id, body) ->
411+ ChatMsg
412+ { Id = id; Body = body
413+ Timestamp = DateTimeOffset.FromUnixTimeSeconds(timestamp)
414+ IsOutgoing = false
415+ Status = DeliveryStatus.Delivered }
416+ | Envelope.DeliveryAck ackIds -> DeliveryAck ackIds
417+ | Envelope.ProfileMessage profile -> ProfileUpdate profile
418+
408419 let private decryptEvent (privKey: byte[]) (payload: EventPayload) =
409420 try
410421 let blob = Crypto.fromHex payload.EncryptedBlob
411−
412422 match Crypto.decrypt privKey blob with
413423 | Some(plaintext, senderHex) ->
414424 match parseEnvelope plaintext with
415− | Some(TextMessage(id, body)) ->
416− Ok(IncomingChat(senderHex,
417− { Id = id; Body = body
418− Timestamp = DateTimeOffset.FromUnixTimeSeconds(payload.Timestamp)
419− IsOutgoing = false
420− Status = DeliveryStatus.Delivered }))
421− | Some(DeliveryAck ackIds) ->
422− Ok(IncomingDeliveryAck(senderHex, ackIds))
423− | Some(ProfileMessage profile) ->
424− Ok(IncomingProfile(senderHex, profile))
425+ | Some envelope -> Ok { Sender = senderHex; Event = toDecryptedEvent payload.Timestamp envelope }
425426 | None -> Error "unknown envelope type"
426427 | None -> Error "decrypt failed"
427428 with ex ->
428429 Error $"parse: {ex.Message}"
429430
430− let private sendDeliveryAcks (session: Session) (events: DecryptedEvent list) =
431+ let private sendDeliveryAcks (session: Session) (events: IncomingEvent list) =
431432 async {
432433 let chatsBySender =
433434 events
434− |> List.choose (function IncomingChat(senderPubkey, msg) -> Some(senderPubkey, msg.Id) | _ -> None)
435+ |> List.choose (fun evt -> match evt.Event with ChatMsg msg -> Some(evt.Sender, msg.Id) | _ -> None)
435436 |> List.groupBy fst
436437 |> List.map (fun (sender, pairs) -> sender, List.map snd pairs)
437438
438439 for sender, msgIds in chatsBySender do
439− try do! sendEnvelope session sender (DeliveryAck msgIds) |> Async.Ignore
440+ try do! sendEnvelope session sender (Envelope.DeliveryAck msgIds) |> Async.Ignore
440441 with _ -> ()
441442 }
442443
@@ -454,7 +455,7 @@ module App =
454455 | CmdSend(session, recipientHex, text, messageId) ->
455456 asyncCmd (async {
456457 try
457− let! status = sendEnvelope session recipientHex (TextMessage(messageId, text))
458+ let! status = sendEnvelope session recipientHex (Envelope.TextMessage(messageId, text))
458459 match messageStatusToError status with
459460 | Some err -> return SendFailed err
460461 | None -> return SendOk
@@ -484,7 +485,7 @@ module App =
484485
485486 do! sendDeliveryAcks session events
486487
487− let chatCount = events |> List.sumBy (function IncomingChat _ -> 1 | _ -> 0)
488+ let chatCount = events |> List.sumBy (fun e -> match e.Event with ChatMsg _ -> 1 | _ -> 0)
488489 let status = Received { Events = response.Events.Length; Messages = chatCount; Errors = errors.Length; FirstError = List.tryHead errors }
489490 return PollResult(events, status, response.Cursor)
490491 with
@@ -536,7 +537,7 @@ module App =
536537 Cmd.ofEffect (fun _ ->
537538 async {
538539 for recipientHex in recipientHexes do
539− try do! sendEnvelope session recipientHex (ProfileMessage profile) |> Async.Ignore
540+ try do! sendEnvelope session recipientHex (Envelope.ProfileMessage profile) |> Async.Ignore
540541 with _ -> ()
541542 } |> Async.Start)
542543