on master help sign in
Simplify server code and improve type safety: use sum types for auth/result handlers, nest prune results, eliminate dummy values and shouldInsert flag
commit13e9b38829a3f3c286fdce62545e8c58101d5be8 authoriko <mail@iko.soy> dateFri 10 Apr 2026, 10:44 (6 months ago) parent61c0ab0
15 files changed+107−103
client/ApiClient.fs+23−25 client/App.fs+12−20 client/bin/Debug/net10.0-maccatalyst/maccatalyst-arm64/Skrepka.app/Contents/MonoBundle/Skrepka.dll client/bin/Debug/net10.0-maccatalyst/maccatalyst-arm64/Skrepka.app/Contents/MonoBundle/Skrepka.pdb client/bin/Debug/net10.0-maccatalyst/maccatalyst-arm64/Skrepka.app/Contents/_CodeSignature/CodeResources+2−2 client/bin/Debug/net10.0-maccatalyst/maccatalyst-arm64/Skrepka.dll client/bin/Debug/net10.0-maccatalyst/maccatalyst-arm64/Skrepka.pdb client/obj/Debug/net10.0-maccatalyst/maccatalyst-arm64/Skrepka.AssemblyInfo.fs+1−1 client/obj/Debug/net10.0-maccatalyst/maccatalyst-arm64/Skrepka.AssemblyInfoInputs.cache+1−1 client/obj/Debug/net10.0-maccatalyst/maccatalyst-arm64/Skrepka.dll client/obj/Debug/net10.0-maccatalyst/maccatalyst-arm64/Skrepka.pdb client/obj/Debug/net10.0-maccatalyst/maccatalyst-arm64/Skrepka.sourcelink.json+1−1 client/obj/Debug/net10.0-maccatalyst/maccatalyst-arm64/ref/Skrepka.dll client/obj/Debug/net10.0-maccatalyst/maccatalyst-arm64/refint/Skrepka.dll server.knot+67−53
client/ApiClient.fs+23−25
@@ -4,32 +4,28 @@ open System
44open System.Net.Http
55open System.Text
66open System.Text.Json
7+open System.Text.Json.Serialization
78open System.Threading.Tasks
89
910module ApiClient =
1011
11− type EventPayload = { EncryptedBlob: string; Timestamp: int64 }
12− type PollEvent = { Id: string; Payload: EventPayload }
13−
1412 [<CLIMutable>]
15− type SendResult = { Status: string; MessageId: string }
16−
17− type PollResponse = { Events: PollEvent list; Cursor: uint64 }
13+ type EventPayload = { EncryptedBlob: string; Timestamp: int64 }
1814
1915 [<CLIMutable>]
20− type ChallengeResponse = { challenge: string }
16+ type PollEvent = { Id: string; EventType: string; Payload: EventPayload }
2117
2218 [<CLIMutable>]
23− type VerifyResponse = { token: string }
19+ type SendResult = { Status: string; MessageId: string }
2420
2521 [<CLIMutable>]
26− type PollPayloadDto = { encryptedBlob: string; timestamp: JsonElement }
22+ type PollResponse = { Cursor: uint64; Events: PollEvent array }
2723
2824 [<CLIMutable>]
29− type PollEventDto = { id: string; eventType: string; payload: PollPayloadDto }
25+ type ChallengeResponse = { challenge: string }
3026
3127 [<CLIMutable>]
32− type PollResponseDto = { cursor: uint64; events: PollEventDto array }
28+ type VerifyResponse = { token: string }
3329
3430 /// Awaits a Task without converting TaskCanceledException to F# async
3531 /// cancellation (which bypasses try...with). Re-raises it as a regular exception.
@@ -41,7 +37,18 @@ module ApiClient =
4137 else ok t.Result)
4238 |> ignore)
4339
44− let private jsonOpts = JsonSerializerOptions(PropertyNameCaseInsensitive = true)
40+ type private FlexibleInt64Converter() =
41+ inherit JsonConverter<int64>()
42+ override _.Read(reader, _, _) =
43+ match reader.TokenType with
44+ | JsonTokenType.String -> Int64.Parse(reader.GetString())
45+ | _ -> reader.GetInt64()
46+ override _.Write(writer, value, _) = writer.WriteNumberValue(value)
47+
48+ let private jsonOpts =
49+ let opts = JsonSerializerOptions(PropertyNameCaseInsensitive = true)
50+ opts.Converters.Add(FlexibleInt64Converter())
51+ opts
4552
4653 let private client =
4754 let c = new HttpClient()
@@ -100,19 +107,10 @@ module ApiClient =
100107 let poll (serverUrl: string) (token: string) (cursor: uint64) =
101108 async {
102109 let body = JsonSerializer.Serialize({| cursor = cursor; timeout = 30000 |})
103− let! response = postJson<PollResponseDto> pollClient $"{serverUrl}/poll" body (Some token)
104−
105− let events =
106− [ for e in response.events do
107− if e.eventType = "message" then
108− let ts =
109− if e.payload.timestamp.ValueKind = JsonValueKind.String
110− then Int64.Parse(e.payload.timestamp.GetString())
111− else e.payload.timestamp.GetInt64()
112− { Id = e.id
113− Payload = { EncryptedBlob = e.payload.encryptedBlob; Timestamp = ts } } ]
114−
115− return { Events = events; Cursor = response.cursor }
110+ let! response = postJson<PollResponse> pollClient $"{serverUrl}/poll" body (Some token)
111+ return
112+ { response with
113+ Events = response.Events |> Array.filter (fun e -> e.EventType = "message") }
116114 }
117115
118116 let ackMessages (serverUrl: string) (token: string) (messageIds: string list) =
client/App.fs+12−20
@@ -45,15 +45,6 @@ module App =
4545 | Connecting of Identity
4646 | Online of Session
4747
48− type PollResultData =
49− { Messages: (string * ChatMessage) list
50− Status: string
51− Cursor: uint64 }
52−
53− type LoadedState =
54− | NoIdentity
55− | Loaded of identity: Identity * data: Store.DataDto option
56−
5748 // ── Model ──
5849
5950 type Model =
@@ -88,10 +79,11 @@ module App =
8879 | SetNewPubkey of string
8980 | SetNewNickname of string
9081 | DoSaveContact
91− | PollResult of PollResultData
82+ | PollResult of messages: (string * ChatMessage) list * status: string * cursor: uint64
9283 | CopyPubKey
9384 | DismissError
94− | StateLoaded of LoadedState
85+ | NoIdentityFound
86+ | StateLoaded of Identity * Store.DataDto option
9587
9688 // ── CmdMsg ──
9789
@@ -216,7 +208,7 @@ module App =
216208
217209 | Sent result -> { model with LastSendStatus = $"{result.Status}:{result.MessageId}" }, []
218210
219− | PollResult { Messages = incoming; Status = status; Cursor = newCursor } ->
211+ | PollResult(incoming, status, newCursor) ->
220212 let model' =
221213 incoming
222214 |> List.fold
@@ -263,8 +255,8 @@ module App =
263255
264256 | DismissError -> { model with Error = None }, []
265257
266− | StateLoaded NoIdentity -> model, []
267− | StateLoaded(Loaded(identity, dataOpt)) ->
258+ | NoIdentityFound -> model, []
259+ | StateLoaded(identity, dataOpt) ->
268260 let model' = { model with Identity = Some identity; Conn = Connecting identity }
269261
270262 let model' =
@@ -363,7 +355,7 @@ module App =
363355 let! response = ApiClient.poll session.Url session.Token cursor
364356 let messages, ackIds, errors =
365357 response.Events
366− |> List.fold (fun (msgs, acks, errs) evt ->
358+ |> Array.fold (fun (msgs, acks, errs) evt ->
367359 match decryptEvent session.Identity.PrivKey evt.Payload with
368360 | Ok msg -> (msg :: msgs, evt.Id :: acks, errs)
369361 | Error err -> (msgs, acks, err :: errs))
@@ -378,14 +370,14 @@ module App =
378370 $"[{now}] evts:{response.Events.Length} msgs:{messages.Length} errs:{errors.Length}"
379371 + (match errors with err :: _ -> $" [{err}]" | [] -> "")
380372
381− return PollResult { Messages = List.rev messages; Status = status; Cursor = response.Cursor }
373+ return PollResult(List.rev messages, status, response.Cursor)
382374 with
383375 | :? TimeoutException ->
384− return PollResult { Messages = []; Status = "polling..."; Cursor = cursor }
376+ return PollResult([], "polling...", cursor)
385377 | ex ->
386378 let now = DateTimeOffset.UtcNow.ToString("HH:mm:ss")
387379 do! Async.Sleep 3000
388− return PollResult { Messages = []; Status = $"[{now}] poll error: {ex.Message}"; Cursor = cursor }
380+ return PollResult([], $"[{now}] poll error: {ex.Message}", cursor)
389381 })
390382
391383 | CmdCopyToClipboard text ->
@@ -397,10 +389,10 @@ module App =
397389 asyncCmd (async {
398390 let! identity = Store.loadIdentity ()
399391 match identity with
400− | None -> return StateLoaded NoIdentity
392+ | None -> return NoIdentityFound
401393 | Some id ->
402394 let data = Store.loadData ()
403− return StateLoaded(Loaded(id, data))
395+ return StateLoaded(id, data)
404396 })
405397
406398 | CmdSaveIdentity identity ->
client/bin/Debug/net10.0-maccatalyst/maccatalyst-arm64/Skrepka.app/Contents/MonoBundle/Skrepka.dll

Binary file not shown.

client/bin/Debug/net10.0-maccatalyst/maccatalyst-arm64/Skrepka.app/Contents/MonoBundle/Skrepka.pdb

Binary file not shown.

client/bin/Debug/net10.0-maccatalyst/maccatalyst-arm64/Skrepka.app/Contents/_CodeSignature/CodeResources+2−2
@@ -240,14 +240,14 @@
240240 <dict>
241241 <key>hash2</key>
242242 <data>
243− Tb9gTNQn0vC6xYnphz/SPqKOypsS1ii7ovZ7S6s4QoY=
243+ kz4trELqvTzwqCWvJX0TwklUlGDMQ7PmstX/wX+L1ys=
244244 </data>
245245 </dict>
246246 <key>MonoBundle/Skrepka.pdb</key>
247247 <dict>
248248 <key>hash2</key>
249249 <data>
250− k0t0cbTVWz/CfZ4rlDzYjSX4lniTHLZRvz2UrUCSZdM=
250+ R15BgIEVnOLSGbnwifZrUZ3UxFh8o3aOr8vzoOqfToE=
251251 </data>
252252 </dict>
253253 <key>MonoBundle/Sodium.Core.dll</key>
client/bin/Debug/net10.0-maccatalyst/maccatalyst-arm64/Skrepka.dll

Binary file not shown.

client/bin/Debug/net10.0-maccatalyst/maccatalyst-arm64/Skrepka.pdb

Binary file not shown.

client/obj/Debug/net10.0-maccatalyst/maccatalyst-arm64/Skrepka.AssemblyInfo.fs+1−1
@@ -10,7 +10,7 @@ open System.Reflection
1010[<assembly: System.Reflection.AssemblyCompanyAttribute("Skrepka")>]
1111[<assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")>]
1212[<assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")>]
13−[<assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0+f11ecd2201e52dce49ef2d913500531961808e00")>]
13+[<assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0+61c0ab01502e0f3b882828035225b639018f6ac4")>]
1414[<assembly: System.Reflection.AssemblyProductAttribute("Skrepka")>]
1515[<assembly: System.Reflection.AssemblyTitleAttribute("Skrepka")>]
1616[<assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")>]
client/obj/Debug/net10.0-maccatalyst/maccatalyst-arm64/Skrepka.AssemblyInfoInputs.cache+1−1
@@ -1 +1 @@
1−90103b2909e2d6c308fd0241aa80b308b414d7047eeca8cef6ba5f4642cdf15a
1+64ef513869c07b61515326591a2ef70b3274d7205ba59736fabaf7a6219440eb
client/obj/Debug/net10.0-maccatalyst/maccatalyst-arm64/Skrepka.dll

Binary file not shown.

client/obj/Debug/net10.0-maccatalyst/maccatalyst-arm64/Skrepka.pdb

Binary file not shown.

client/obj/Debug/net10.0-maccatalyst/maccatalyst-arm64/Skrepka.sourcelink.json+1−1
@@ -1 +1 @@
1−{"documents":{"/Users/iko/Developer/skrepka/*":"https://raw.githubusercontent.com/ilyakooo0/skrepka/f11ecd2201e52dce49ef2d913500531961808e00/*"}}
1+{"documents":{"/Users/iko/Developer/skrepka/*":"https://raw.githubusercontent.com/ilyakooo0/skrepka/61c0ab01502e0f3b882828035225b639018f6ac4/*"}}
client/obj/Debug/net10.0-maccatalyst/maccatalyst-arm64/ref/Skrepka.dll

Binary file not shown.

client/obj/Debug/net10.0-maccatalyst/maccatalyst-arm64/refint/Skrepka.dll

Binary file not shown.

server.knot+67−53
@@ -655,8 +655,9 @@ pruneAndRetry = \t -> do
655655 forwards <- *forwards
656656 ackedIds <- *ackedIds
657657 let pendingAcks = do a <- ackedIds; where isFederatedOrigin a.originServer; yield a
658− yield {expiredLocal: pr.expiredLocal, peerServers: pr.peerServers, pendingForwards: forwards, pendingAcks: pendingAcks}
659− broadcastOfflineGossip t result.peerServers result.expiredLocal
658+ yield {pr: pr, pendingForwards: forwards, pendingAcks: pendingAcks}
659+ let pr = result.pr
660+ broadcastOfflineGossip t pr.peerServers pr.expiredLocal
660661 when (federationEnabled && count result.pendingForwards > 0)
661662 (retryPendingForwards result.pendingForwards)
662663 when (federationEnabled && count result.pendingAcks > 0) (do
@@ -748,7 +749,7 @@ serve = \req -> case req of
748749 Nothing {} -> False {}
749750 _ -> verify (bytesFromHex pubkey) (textToBytes challenge) (bytesFromHex signature)
750751 if not sigValid
751− then yield {sigValid: False {}, peerServers: [], expiredLocal: pr.expiredLocal, offlinePeers: pr.peerServers}
752+ then yield VerifyFailed {pr: pr}
752753 else do
753754 set *challenges = do
754755 c <- challenges
@@ -768,21 +769,24 @@ serve = \req -> case req of
768769 let peerServers = if federationEnabled
769770 then collectPeerServers updatedPresence (\p -> p.server)
770771 else []
771− yield {sigValid: True {}, peerServers: peerServers, expiredLocal: pr.expiredLocal, offlinePeers: pr.peerServers}
772− fork (do
773− when (result.sigValid && federationEnabled)
774− (sendToEach result.peerServers (\server -> do
775− _ <- fetch (federationScheme ++ server) (RecvGossip {
776− authorization: "Bearer " ++ federationSecret,
777− events: [{eventType: "online", pubkey: pubkey, server: serverHostN, timestamp: t, ttl: onlineGossipTtl}], hopsRemaining: 2, fromServer: serverHostN})
778− yield {}))
779− broadcastOfflineGossip t result.offlinePeers (do
780− p <- result.expiredLocal
781− where not (result.sigValid && p.pubkey == pubkey)
782− yield p))
783− yield (if result.sigValid
784− then respond {token: token, expiresAt: expiry}
785− else respond {token: "", expiresAt: 0})
772+ yield VerifyOk {peerServers: peerServers, pr: pr}
773+ fork (case result of
774+ VerifyFailed {pr} ->
775+ broadcastOfflineGossip t pr.peerServers pr.expiredLocal
776+ VerifyOk {peerServers, pr} -> do
777+ when federationEnabled
778+ (sendToEach peerServers (\server -> do
779+ _ <- fetch (federationScheme ++ server) (RecvGossip {
780+ authorization: "Bearer " ++ federationSecret,
781+ events: [{eventType: "online", pubkey: pubkey, server: serverHostN, timestamp: t, ttl: onlineGossipTtl}], hopsRemaining: 2, fromServer: serverHostN})
782+ yield {}))
783+ broadcastOfflineGossip t pr.peerServers (do
784+ p <- pr.expiredLocal
785+ where not (p.pubkey == pubkey)
786+ yield p))
787+ yield (case result of
788+ VerifyFailed _ -> respond {token: "", expiresAt: 0}
789+ VerifyOk _ -> respond {token: token, expiresAt: expiry})
786790
787791 Poll {cursor, timeout, authorization, respond} -> do
788792 t <- now
@@ -808,7 +812,7 @@ serve = \req -> case req of
808812 let auth = authenticateSession sessions (extractToken authorization) t
809813 case auth of
810814 Unauthorized {} ->
811− yield {status: "unauthorized", shouldInsert: False {}, remoteServers: [], fromKey: "", expiredLocal: pr.expiredLocal, peerServers: pr.peerServers}
815+ yield SendUnauthorized {pr: pr}
812816 Authorized {pubkey: fromKey} -> do
813817 messages <- *messages
814818 presence <- *presence
@@ -833,21 +837,26 @@ serve = \req -> case req of
833837 set *forwards = if shouldInsert && federationEnabled && count forwardServers > 0
834838 then union forwards (do s <- forwardServers; yield {messageId: msgId, toServer: s, createdAt: t, toKey: to, fromKey: fromKey, encryptedBlob: encryptedBlob, timestamp: clampedTs})
835839 else forwards
836− yield {status: status, shouldInsert: shouldInsert, remoteServers: remoteServers, fromKey: fromKey, expiredLocal: pr.expiredLocal, peerServers: pr.peerServers}
837− fork (do
838− when (count result.remoteServers > 0 && result.shouldInsert && federationEnabled)
839− (sendToEach result.remoteServers (\server -> do
840− fwdResult <- fetch (federationScheme ++ server) (ForwardMessage {
841− toKey: to, fromKey: result.fromKey, encryptedBlob: encryptedBlob,
842− timestamp: clampedTs, originServer: serverHostN, messageId: msgId,
843− authorization: "Bearer " ++ federationSecret})
844− case fwdResult of
845− Ok _ -> removeForward msgId server
846− Err _ -> yield {}))
847− when (result.status == "queued" && count result.remoteServers > 0 && result.shouldInsert && federationEnabled)
848− (println ("sendMessage: forwards at capacity (" ++ show maxForwardsEntries ++ "), skipping forward tracking"))
849− broadcastOfflineGossip t result.peerServers result.expiredLocal)
850− yield (respond {status: result.status, messageId: if result.shouldInsert then msgId else ""})
840+ yield SendDone {status: status, remoteServers: remoteServers, fromKey: fromKey, pr: pr}
841+ fork (case result of
842+ SendUnauthorized {pr} ->
843+ broadcastOfflineGossip t pr.peerServers pr.expiredLocal
844+ SendDone {status, remoteServers, fromKey, pr} -> do
845+ when (count remoteServers > 0 && not (status == "rejected") && federationEnabled)
846+ (sendToEach remoteServers (\server -> do
847+ fwdResult <- fetch (federationScheme ++ server) (ForwardMessage {
848+ toKey: to, fromKey: fromKey, encryptedBlob: encryptedBlob,
849+ timestamp: clampedTs, originServer: serverHostN, messageId: msgId,
850+ authorization: "Bearer " ++ federationSecret})
851+ case fwdResult of
852+ Ok _ -> removeForward msgId server
853+ Err _ -> yield {}))
854+ when (status == "queued" && count remoteServers > 0 && federationEnabled)
855+ (println ("sendMessage: forwards at capacity (" ++ show maxForwardsEntries ++ "), skipping forward tracking"))
856+ broadcastOfflineGossip t pr.peerServers pr.expiredLocal)
857+ yield (case result of
858+ SendUnauthorized _ -> respond {status: "unauthorized", messageId: ""}
859+ SendDone {status, remoteServers, fromKey, pr} -> respond {status: status, messageId: if status == "rejected" then "" else msgId})
851860
852861 AckMessages {messageIds, authorization, respond} -> do
853862 t <- now
@@ -858,7 +867,7 @@ serve = \req -> case req of
858867 let auth = authenticateSession sessions (extractToken authorization) t
859868 case auth of
860869 Unauthorized {} ->
861− yield {ok: False {}, pendingAcks: [], expiredLocal: pr.expiredLocal, peerServers: pr.peerServers, evictedEntries: []}
870+ yield AckUnauthorized {pr: pr}
862871 Authorized {pubkey} -> do
863872 messages <- *messages
864873 let ackedMessages = do
@@ -880,15 +889,17 @@ serve = \req -> case req of
880889 let evictedEntries = if excessCount <= 0 then []
881890 else diff combinedAckedIds afterEviction
882891 set *ackedIds = afterEviction
883− yield {ok: True {}, pendingAcks: ackedFederated, expiredLocal: pr.expiredLocal, peerServers: pr.peerServers, evictedEntries: evictedEntries}
884− let originServers = collectPeerServers result.pendingAcks (\a -> a.originServer)
885− fork (do
886− when result.ok (do
892+ yield AckDone {pendingAcks: ackedFederated, pr: pr, evictedEntries: evictedEntries}
893+ fork (case result of
894+ AckUnauthorized {pr} ->
895+ broadcastOfflineGossip t pr.peerServers pr.expiredLocal
896+ AckDone {pendingAcks, pr, evictedEntries} -> do
897+ let originServers = collectPeerServers pendingAcks (\a -> a.originServer)
887898 when (count originServers > 0 && federationEnabled)
888− (sendAcksAndClean result.pendingAcks originServers)
899+ (sendAcksAndClean pendingAcks originServers)
889900 let evictedToRetry = do
890− a <- result.evictedEntries
891− where none result.pendingAcks (\a2 -> a2.messageId == a.messageId && a2.toKey == a.toKey)
901+ a <- evictedEntries
902+ where none pendingAcks (\a2 -> a2.messageId == a.messageId && a2.toKey == a.toKey)
892903 yield a
893904 when (count evictedToRetry > 0 && federationEnabled) (do
894905 let evictedOriginServers = collectPeerServers evictedToRetry (\a -> a.originServer)
@@ -910,9 +921,11 @@ serve = \req -> case req of
910921 yield excess
911922 when (dropped > 0)
912923 (println ("ackMessages: ackedIds at capacity (" ++ show maxAckedIds ++ "), " ++ show dropped ++ " ack entries permanently lost for " ++ server))
913− _ -> yield {})))
914− broadcastOfflineGossip t result.peerServers result.expiredLocal)
915− yield (respond {ok: result.ok})
924+ _ -> yield {}))
925+ broadcastOfflineGossip t pr.peerServers pr.expiredLocal)
926+ yield (case result of
927+ AckUnauthorized _ -> respond {ok: False {}}
928+ AckDone _ -> respond {ok: True {}})
916929
917930 LookupPresence {pubkey, authorization, respond} -> do
918931 let pubkey = normalizeHex pubkey
@@ -923,7 +936,7 @@ serve = \req -> case req of
923936 let auth = authenticateSession sessions (extractToken authorization) t
924937 case auth of
925938 Unauthorized {} ->
926− yield {foundServer: "", authorized: False {}, expiredLocal: pr.expiredLocal, peerServers: pr.peerServers}
939+ yield {foundServer: "", authorized: False {}, pr: pr}
927940 Authorized _ -> do
928941 presence <- *presence
929942 let valid = do
@@ -934,8 +947,8 @@ serve = \req -> case req of
934947 let foundServer = if any valid (\p -> p.server == serverHostN)
935948 then serverHostN
936949 else (fold (\acc p -> if p.expiresAt > acc.best then {server: p.server, best: p.expiresAt} else acc) {server: "", best: 0} valid).server
937− yield {foundServer: foundServer, authorized: True {}, expiredLocal: pr.expiredLocal, peerServers: pr.peerServers}
938− forkBroadcastOffline t result
950+ yield {foundServer: foundServer, authorized: True {}, pr: pr}
951+ forkBroadcastOffline t result.pr
939952 yield (respond {online: not (result.foundServer == ""), server: result.foundServer, authorized: result.authorized})
940953
941954 RecvGossip {events, hopsRemaining, fromServer, authorization, respond} -> do
@@ -998,7 +1011,7 @@ serve = \req -> case req of
9981011 let finalPresence = if validRequest then capResult.finalPresence else presence
9991012 set *presence = finalPresence
10001013 let gossipPeerServers = collectPeerServers finalPresence (\p -> p.server)
1001− yield {appliedKeys: union capResult.appliedOnline appliedOffline, expiredLocal: pr.expiredLocal, peerServers: pr.peerServers, gossipPeerServers: gossipPeerServers, withinCapacity: withinCapacity}
1014+ yield {appliedKeys: union capResult.appliedOnline appliedOffline, pr: pr, gossipPeerServers: gossipPeerServers, withinCapacity: withinCapacity}
10021015 let effectiveEvents = do
10031016 e <- union filteredOnline filteredOffline
10041017 where any gossipResult.appliedKeys (\a -> samePubkeyServer a e)
@@ -1016,7 +1029,8 @@ serve = \req -> case req of
10161029 authorization: "Bearer " ++ federationSecret,
10171030 events: effectiveEvents, hopsRemaining: cappedHops - 1, fromServer: serverHostN})
10181031 yield {}))
1019− broadcastOfflineGossip t gossipResult.peerServers gossipResult.expiredLocal)
1032+ let pr = gossipResult.pr
1033+ broadcastOfflineGossip t pr.peerServers pr.expiredLocal)
10201034 yield (respond {ok: validRequest})
10211035
10221036 ForwardMessage {toKey, fromKey, encryptedBlob, timestamp, originServer, messageId, authorization, respond} -> do
@@ -1047,8 +1061,8 @@ serve = \req -> case req of
10471061 [{messageId: messageId, toKey: toKey, fromKey: fromKey,
10481062 encryptedBlob: encryptedBlob, timestamp: clampedTs, originServer: originServer, receivedAt: seqTs}]
10491063 else messages
1050− yield {peerServers: pr.peerServers, expiredLocal: pr.expiredLocal, ok: shouldInsert || alreadyExists}
1051− forkBroadcastOffline t result
1064+ yield {pr: pr, ok: shouldInsert || alreadyExists}
1065+ forkBroadcastOffline t result.pr
10521066 yield (respond {ok: result.ok})
10531067
10541068 FederationAck {messageIds, originServer, fromServer, authorization, respond} -> do
@@ -1070,7 +1084,7 @@ serve = \req -> case req of
10701084 set *forwards = if validAck
10711085 then diff forwards forwardsToRemove
10721086 else forwards
1073− yield {peerServers: pr.peerServers, expiredLocal: pr.expiredLocal}
1087+ yield pr
10741088 forkBroadcastOffline t pruneResult
10751089 yield (respond {ok: validAck})
10761090